From d66e7a22635f07a1bdc38242001be124fc712e56 Mon Sep 17 00:00:00 2001 From: Ravinou Date: Sun, 12 Jan 2025 19:27:14 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20=E2=9A=A1=20component=20repoList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repo/QuickCommands/QuickCommands.tsx | 10 +- Components/Repo/Repo.tsx | 7 +- Containers/RepoList/RepoList.tsx | 24 +-- Containers/RepoManage/RepoManage.tsx | 142 +++++++++--------- 4 files changed, 96 insertions(+), 87 deletions(-) diff --git a/Components/Repo/QuickCommands/QuickCommands.tsx b/Components/Repo/QuickCommands/QuickCommands.tsx index b725654..ea3be93 100644 --- a/Components/Repo/QuickCommands/QuickCommands.tsx +++ b/Components/Repo/QuickCommands/QuickCommands.tsx @@ -4,10 +4,12 @@ import { useState } from 'react'; import classes from './QuickCommands.module.css'; import { IconSettingsAutomation, IconCopy } from '@tabler/icons-react'; import lanCommandOption from '../../../helpers/functions/lanCommandOption'; +import { Optional } from '~/types'; +import { WizardEnvType } from '~/domain/config.types'; type QuickCommandsProps = { repositoryName: string; - wizardEnv: any; + wizardEnv: Optional; lanCommand: boolean; }; @@ -24,7 +26,9 @@ export default function QuickCommands(props: QuickCommandsProps) { const handleCopy = async () => { // Asynchronously call copy to clipboard navigator.clipboard - .writeText(`ssh://${wizardEnv.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}`) + .writeText( + `ssh://${wizardEnv?.UNIX_USER}@${FQDN}${SSH_SERVER_PORT}/./${props.repositoryName}` + ) .then(() => { // If successful, update the isCopied state value setIsCopied(true); @@ -43,7 +47,7 @@ export default function QuickCommands(props: QuickCommandsProps) {
Copied !
) : (
- ssh://{wizardEnv.UNIX_USER}@{FQDN} + ssh://{wizardEnv?.UNIX_USER}@{FQDN} {SSH_SERVER_PORT}/./ {props.repositoryName}
diff --git a/Components/Repo/Repo.tsx b/Components/Repo/Repo.tsx index 94fb2cc..a59f8d9 100644 --- a/Components/Repo/Repo.tsx +++ b/Components/Repo/Repo.tsx @@ -12,11 +12,12 @@ import { import { timestampConverter } from '~/helpers/functions/timestampConverter'; import StorageBar from '../UI/StorageBar/StorageBar'; import QuickCommands from './QuickCommands/QuickCommands'; -import { Repository } from '~/domain/config.types'; +import { Repository, WizardEnvType } from '~/domain/config.types'; +import { Optional } from '~/types'; -type RepoProps = Repository & { +type RepoProps = Omit & { repoManageEditHandler: () => void; - wizardEnv: string; + wizardEnv: Optional; }; export default function Repo(props: RepoProps) { diff --git a/Containers/RepoList/RepoList.tsx b/Containers/RepoList/RepoList.tsx index 4c493e3..3b20dd2 100644 --- a/Containers/RepoList/RepoList.tsx +++ b/Containers/RepoList/RepoList.tsx @@ -5,19 +5,21 @@ import { IconPlus } from '@tabler/icons-react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import useSWR, { useSWRConfig } from 'swr'; -import { ToastContainer, toast } from 'react-toastify'; +import { ToastContainer, ToastOptions, toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; //Composants import Repo from '../../Components/Repo/Repo'; import RepoManage from '../RepoManage/RepoManage'; import ShimmerRepoList from '../../Components/UI/ShimmerRepoList/ShimmerRepoList'; +import { Repository, WizardEnvType } from '~/domain/config.types'; +import { Optional } from '~/types'; export default function RepoList() { ////Var const router = useRouter(); const { mutate } = useSWRConfig(); - const toastOptions = { + const toastOptions: ToastOptions = { position: 'top-right', autoClose: 8000, hideProgressBar: false, @@ -29,7 +31,7 @@ export default function RepoList() { ////Datas //Write a fetcher function to wrap the native fetch function and return the result of a call to url in json format - const fetcher = async (url) => await fetch(url).then((res) => res.json()); + const fetcher = async (url: string) => await fetch(url).then((res) => res.json()); const { data, error } = useSWR('/api/repo', fetcher); ////LifeCycle @@ -63,7 +65,7 @@ export default function RepoList() { ////States const [displayRepoAdd, setDisplayRepoAdd] = useState(false); const [displayRepoEdit, setDisplayRepoEdit] = useState(false); - const [wizardEnv, setWizardEnv] = useState({}); + const [wizardEnv, setWizardEnv] = useState>(); ////Functions @@ -88,7 +90,7 @@ export default function RepoList() { }; //BUTTON : Display RepoManage component box for EDIT - const repoManageEditHandler = (id: number) => { + const manageRepoEditHandler = (id: number) => { router.replace('/manage-repo/edit/' + id); }; @@ -107,7 +109,7 @@ export default function RepoList() { }; //Dynamic list of repositories (with a map of Repo components) - const renderRepoList = data.repoList.map((repo, index) => { + const renderRepoList = data.repoList.map((repo: Repository) => { return ( repoManageEditHandler(repo.id)} + repoManageEditHandler={() => manageRepoEditHandler(repo.id)} wizardEnv={wizardEnv} > @@ -148,12 +150,12 @@ export default function RepoList() {
{renderRepoList}
- {displayRepoAdd ? ( + {displayRepoAdd && ( - ) : null} - {displayRepoEdit ? ( + )} + {displayRepoEdit && ( - ) : null} + )} ); } diff --git a/Containers/RepoManage/RepoManage.tsx b/Containers/RepoManage/RepoManage.tsx index a758efa..43b45f9 100644 --- a/Containers/RepoManage/RepoManage.tsx +++ b/Containers/RepoManage/RepoManage.tsx @@ -3,7 +3,7 @@ import classes from './RepoManage.module.css'; import { IconAlertCircle, IconX } from '@tabler/icons-react'; import { useState } from 'react'; import { useRouter } from 'next/router'; -import { toast } from 'react-toastify'; +import { toast, ToastOptions } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; import { useForm, Controller } from 'react-hook-form'; import { SpinnerDotted } from 'spinners-react'; @@ -11,19 +11,41 @@ import Select from 'react-select'; import Link from 'next/link'; import { IconExternalLink } from '@tabler/icons-react'; import { alertOptions } from '../../domain/constants'; +import { Repository } from '~/domain/config.types'; +import { Optional } from '~/types'; -export default function RepoManage(props) { +type RepoManageProps = { + mode: 'add' | 'edit'; + repoList: Optional>; + closeHandler: () => void; +}; + +type DataForm = { + alias: string; + storageSize: string; + sshkey: string; + comment: string; + alert: { value: Optional; label: string }; + lanCommand: boolean; + appendOnlyMode: boolean; +}; + +export default function RepoManage(props: RepoManageProps) { ////Var - let targetRepo; const router = useRouter(); + const targetRepo = + props.mode === 'edit' && router.query.slug + ? props.repoList?.find((repo) => repo.id.toString() === router.query.slug) + : undefined; + const { register, handleSubmit, control, formState: { errors, isSubmitting, isValid }, - } = useForm({ mode: 'onChange' }); + } = useForm({ mode: 'onChange' }); - const toastOptions = { + const toastOptions: ToastOptions = { position: 'top-right', autoClose: 5000, hideProgressBar: false, @@ -40,18 +62,11 @@ export default function RepoManage(props) { ////Functions //router.query.slug is undefined for few milliseconds on first render for a direct URL access (https://github.com/vercel/next.js/discussions/11484). //If I call repoManage with edit mode (props), i'm firstly waiting that router.query.slug being available before rendering. - if (!router.query.slug && props.mode == 'edit') { - return ; - } else if (props.mode == 'edit') { - for (let element in props.repoList) { - if (props.repoList[element].id == router.query.slug) { - targetRepo = props.repoList[element]; - } - } - //If the ID does not exist > 404 - if (!targetRepo) { + if (props.mode === 'edit') { + if (!router.query.slug) { + return ; + } else if (!targetRepo) { router.push('/404'); - return null; } } @@ -89,48 +104,39 @@ export default function RepoManage(props) { }); }; - //Verify that the SSH key is unique - const isSSHKeyUnique = async (sshPublicKey) => { - let isUnique = true; + const isSSHKeyUnique = async (sshPublicKey: string): Promise => { + try { + // Extract the first two columns of the SSH key in the form + const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' '); - // Extract the first two columns of the SSH key in the form - const publicKeyPrefix = sshPublicKey.split(' ').slice(0, 2).join(' '); + const response = await fetch('/api/repo', { method: 'GET' }); + const data = await response.json(); - await fetch('/api/repo', { method: 'GET' }) - .then((response) => response.json()) - .then((data) => { - for (let element in data.repoList) { - // Extract the first two columns of the SSH key in the repoList - const repoPublicKeyPrefix = data.repoList[element].sshPublicKey - .split(' ') - .slice(0, 2) - .join(' '); - - if ( - repoPublicKeyPrefix === publicKeyPrefix && // Compare the first two columns of the SSH key - (!targetRepo || data.repoList[element].id != targetRepo.id) - ) { - toast.error( - 'The SSH key is already used in repository #' + - data.repoList[element].id + - '. Please use another key or delete the key from the other repository.', - toastOptions - ); - isUnique = false; - break; - } - } - }) - .catch((error) => { - console.log(error); - toast.error('An error has occurred', toastOptions); - isUnique = false; + const conflictingRepo = data.repoList.find((repo: { sshPublicKey: string; id: number }) => { + const repoPublicKeyPrefix = repo.sshPublicKey.split(' ').slice(0, 2).join(' '); + return ( + repoPublicKeyPrefix === publicKeyPrefix && (!targetRepo || repo.id !== targetRepo.id) + ); }); - return isUnique; + + if (conflictingRepo) { + toast.error( + `The SSH key is already used in repository #${conflictingRepo.id}. Please use another key or delete the key from the other repository.`, + toastOptions + ); + return false; + } + + return true; + } catch (error) { + console.log(error); + toast.error('An error has occurred', toastOptions); + return false; + } }; //Form submit Handler for ADD or EDIT a repo - const formSubmitHandler = async (dataForm) => { + const formSubmitHandler = async (dataForm: DataForm) => { //Loading button on submit to avoid multiple send. setIsLoading(true); //Verify that the SSH key is unique @@ -194,7 +200,7 @@ export default function RepoManage(props) { .then(async (response) => { if (response.ok) { toast.success( - 'The repository #' + targetRepo.id + ' has been successfully edited !', + 'The repository #' + targetRepo?.id + ' has been successfully edited !', toastOptions ); router.replace('/'); @@ -231,14 +237,14 @@ export default function RepoManage(props) { color: 'rgba(99, 115, 129, 0.38)', }} > - #{targetRepo.id} + #{targetRepo?.id} {' '} ?
- You are about to permanently delete the repository #{targetRepo.id} and all + You are about to permanently delete the repository #{targetRepo?.id} and all the backups it contains.
The data will not be recoverable and it will not be possible to go back.
@@ -274,7 +280,7 @@ export default function RepoManage(props) { color: 'rgba(99, 115, 129, 0.38)', }} > - #{targetRepo.id} + #{targetRepo?.id} )} @@ -286,7 +292,7 @@ export default function RepoManage(props) { className='form-control is-invalid' placeholder='Alias for the repository, e.g."Server 1"' type='text' - defaultValue={props.mode == 'edit' ? targetRepo.alias : null} + defaultValue={props.mode == 'edit' ? targetRepo?.alias : undefined} {...register('alias', { required: 'An alias is required.', minLength: { @@ -304,8 +310,7 @@ export default function RepoManage(props) {