Mamadou DICKO 2023-12-05 16:00:22 +01:00 committed by GitHub
parent 8ba376e0a1
commit 27b2df8898
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 308 additions and 69 deletions

View File

@ -5,6 +5,7 @@ import Button from "@/lib/components/ui/Button";
import { Modal } from "@/lib/components/ui/Modal"; import { Modal } from "@/lib/components/ui/Modal";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { BrainParamsStep } from "./components/BrainParamsStep/BrainParamsStep";
import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep"; import { BrainTypeSelectionStep } from "./components/BrainTypeSelectionStep/BrainTypeSelectionStep";
import { Stepper } from "./components/Stepper/Stepper"; import { Stepper } from "./components/Stepper/Stepper";
import { useAddBrainConfig } from "./hooks/useAddBrainConfig"; import { useAddBrainConfig } from "./hooks/useAddBrainConfig";
@ -22,37 +23,38 @@ export const AddBrainSteps = ({
useAddBrainConfig(); useAddBrainConfig();
return ( return (
<> <Modal
<Modal Trigger={
Trigger={ <Button
<Button onClick={() => void 0}
onClick={() => void 0} variant={"tertiary"}
variant={"tertiary"} className={cn("border-0", triggerClassName)}
className={cn("border-0", triggerClassName)} data-testid="add-brain-button"
data-testid="add-brain-button"
>
{t("newBrain", { ns: "brain" })}
<MdAdd className="text-xl" />
</Button>
}
title={t("newBrainTitle", { ns: "brain" })}
desc={t("newBrainSubtitle", { ns: "brain" })}
isOpen={isBrainCreationModalOpened}
setOpen={setIsBrainCreationModalOpened}
CloseTrigger={<div />}
>
<form
onSubmit={(e) => {
e.preventDefault();
}}
className="my-10 flex flex-col items-center gap-2 justify-center"
> >
<Stepper /> {t("newBrain", { ns: "brain" })}
<BrainTypeSelectionStep <MdAdd className="text-xl" />
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)} </Button>
/> }
</form> title={t("newBrainTitle", { ns: "brain" })}
</Modal> desc={t("newBrainSubtitle", { ns: "brain" })}
</> isOpen={isBrainCreationModalOpened}
setOpen={setIsBrainCreationModalOpened}
CloseTrigger={<div />}
>
<form
onSubmit={(e) => {
e.preventDefault();
}}
className="my-10 flex flex-col items-center gap-2 justify-center"
>
<Stepper />
<BrainTypeSelectionStep
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
/>
<BrainParamsStep
onCancelBrainCreation={() => setIsBrainCreationModalOpened(false)}
/>
</form>
</Modal>
); );
}; };

View File

@ -0,0 +1,105 @@
import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { FaArrowLeft, FaArrowRight } from "react-icons/fa";
import { PublicAccessConfirmationModal } from "@/lib/components/AddBrainModalOld/components/AddBrainConfig/components/PublicAccessConfirmationModal";
import Button from "@/lib/components/ui/Button";
import Field from "@/lib/components/ui/Field";
import { Radio } from "@/lib/components/ui/Radio";
import { TextArea } from "@/lib/components/ui/TextArea";
import { useBrainParamsStep } from "./hooks/useBrainParamsStep";
import { useBrainStatusOptions } from "./hooks/useBrainStatusOptions";
import { usePublicAccessConfirmationModal } from "./hooks/usePublicAccessConfirmationModal";
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
import { useBrainTypeSelectionStep } from "../BrainTypeSelectionStep/hooks/useBrainTypeSelectionStep";
type BrainParamsStepProps = {
onCancelBrainCreation: () => void;
};
export const BrainParamsStep = ({
onCancelBrainCreation,
}: BrainParamsStepProps): JSX.Element => {
const { goToNextStep, goToPreviousStep, currentStep } =
useBrainCreationSteps();
const { register } = useBrainTypeSelectionStep();
const { t } = useTranslation(["translation", "brain", "config"]);
const { brainStatusOptions } = useBrainStatusOptions();
const { isNextButtonDisabled } = useBrainParamsStep();
const {
isPublicAccessConfirmationModalOpened,
onCancelPublicAccess,
onConfirmPublicAccess,
} = usePublicAccessConfirmationModal();
if (currentStep !== "BRAIN_PARAMS") {
return <Fragment />;
}
return (
<>
<Field
label={t("brainName", { ns: "brain" })}
autoFocus
placeholder={t("brainNamePlaceholder", { ns: "brain" })}
autoComplete="off"
className="flex-1"
required
{...register("name")}
/>
<TextArea
label={t("brainDescription", { ns: "brain" })}
placeholder={t("brainDescriptionPlaceholder", { ns: "brain" })}
autoComplete="off"
className="flex-1 m-3"
{...register("description")}
/>
<fieldset className="w-full flex flex-col">
<Radio
items={brainStatusOptions}
label={t("brain_status_label", { ns: "brain" })}
className="flex-1 justify-between w-[50%]"
{...register("status")}
/>
</fieldset>
<div className="flex flex-row justify-between items-center flex-1 mt-10 w-full">
<Button
type="button"
variant="tertiary"
onClick={onCancelBrainCreation}
className="text-primary"
>
{t("cancel", { ns: "translation" })}
</Button>
<div className="flex gap-4">
<Button
type="button"
variant="secondary"
onClick={goToPreviousStep}
className="py-2 border-primary text-primary"
>
<FaArrowLeft className="text-xl" size={16} />
{t("previous", { ns: "translation" })}
</Button>
<Button
className="bg-primary text-white py-2 border-none"
type="button"
onClick={goToNextStep}
disabled={isNextButtonDisabled}
>
{t("next", { ns: "translation" })}
<FaArrowRight className="text-xl" size={16} />
</Button>
</div>
</div>
<PublicAccessConfirmationModal
opened={isPublicAccessConfirmationModalOpened}
onClose={onCancelPublicAccess}
onCancel={onCancelPublicAccess}
onConfirm={onConfirmPublicAccess}
/>
</>
);
};

View File

@ -0,0 +1,16 @@
import { useFormContext } from "react-hook-form";
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainParamsStep = () => {
const { watch } = useFormContext<CreateBrainProps>();
const brainName = watch("name");
const description = watch("description");
const isNextButtonDisabled = brainName === "" || description === "";
return {
isNextButtonDisabled,
};
};

View File

@ -0,0 +1,25 @@
import { useTranslation } from "react-i18next";
import { BrainStatus } from "@/lib/types/brainConfig";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainStatusOptions = () => {
const { t } = useTranslation(["translation", "brain", "config"]);
const brainStatusOptions: {
label: string;
value: BrainStatus;
}[] = [
{
label: t("private_brain_label", { ns: "brain" }),
value: "private",
},
{
label: t("public_brain_label", { ns: "brain" }),
value: "public",
},
];
return {
brainStatusOptions,
};
};

View File

@ -0,0 +1,43 @@
import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const usePublicAccessConfirmationModal = () => {
const {
watch,
setValue,
formState: { dirtyFields },
} = useFormContext<CreateBrainProps>();
const [
isPublicAccessConfirmationModalOpened,
setIsPublicAccessConfirmationModalOpened,
] = useState(false);
const status = watch("status");
useEffect(() => {
if (status === "public" && dirtyFields.status === true) {
setIsPublicAccessConfirmationModalOpened(true);
}
}, [dirtyFields.status, status]);
const onConfirmPublicAccess = () => {
setIsPublicAccessConfirmationModalOpened(false);
};
const onCancelPublicAccess = () => {
setValue("status", "private", {
shouldDirty: true,
});
setIsPublicAccessConfirmationModalOpened(false);
};
return {
isPublicAccessConfirmationModalOpened,
onConfirmPublicAccess,
onCancelPublicAccess,
};
};

View File

@ -1,4 +1,5 @@
import { Fragment } from "react"; import { Fragment } from "react";
import { useTranslation } from "react-i18next";
import { FaArrowRight } from "react-icons/fa"; import { FaArrowRight } from "react-icons/fa";
import Button from "@/lib/components/ui/Button"; import Button from "@/lib/components/ui/Button";
@ -18,7 +19,7 @@ export const BrainTypeSelectionStep = ({
const { knowledgeSourceOptions } = useKnowledgeSourceLabel(); const { knowledgeSourceOptions } = useKnowledgeSourceLabel();
const { register } = useBrainTypeSelectionStep(); const { register } = useBrainTypeSelectionStep();
const { goToNextStep, currentStep } = useBrainCreationSteps(); const { goToNextStep, currentStep } = useBrainCreationSteps();
const { t } = useTranslation(["translation"]);
if (currentStep !== "BRAIN_TYPE") { if (currentStep !== "BRAIN_TYPE") {
return <Fragment />; return <Fragment />;
} }
@ -36,7 +37,7 @@ export const BrainTypeSelectionStep = ({
variant="tertiary" variant="tertiary"
onClick={onCancelBrainCreation} onClick={onCancelBrainCreation}
> >
Annuler {t("cancel")}
</Button> </Button>
<Button <Button
@ -45,7 +46,7 @@ export const BrainTypeSelectionStep = ({
data-testid="create-brain-submit-button" data-testid="create-brain-submit-button"
onClick={goToNextStep} onClick={goToNextStep}
> >
Suivant {t("next")}
<FaArrowRight className="text-xl" size={16} /> <FaArrowRight className="text-xl" size={16} />
</Button> </Button>
</div> </div>

View File

@ -1,10 +1,19 @@
import { useEffect } from "react";
import { useFormContext } from "react-hook-form"; import { useFormContext } from "react-hook-form";
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types"; import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainTypeSelectionStep = () => { export const useBrainTypeSelectionStep = () => {
const { register } = useFormContext<CreateBrainProps>(); const { register, watch, reset, setValue } =
useFormContext<CreateBrainProps>();
const brainType = watch("brain_type");
useEffect(() => {
const currentBrainType = brainType;
reset();
setValue("brain_type", currentBrainType);
}, [brainType, reset, setValue]);
return { return {
register, register,

View File

@ -2,6 +2,7 @@ import { Fragment } from "react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Step } from "./components/Step";
import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps"; import { useBrainCreationSteps } from "../../hooks/useBrainCreationSteps";
export const Stepper = (): JSX.Element => { export const Stepper = (): JSX.Element => {
@ -11,24 +12,7 @@ export const Stepper = (): JSX.Element => {
<div className="flex flex-row justify-between w-full px-12 mb-12"> <div className="flex flex-row justify-between w-full px-12 mb-12">
{steps.map((step, index) => ( {steps.map((step, index) => (
<Fragment key={step.value}> <Fragment key={step.value}>
<div <Step index={index} step={step} />
key={step.label}
className="flex flex-row justify-center items-center flex-1"
>
<div className="flex flex-col justify-center items-center">
<div
className={cn(
"h-[40px] w-[40px] border-solid rounded-full flex flex-row items-center justify-center mb-2 border-primary border-2",
step.value === currentStep ? "bg-primary text-white" : ""
)}
>
{index + 1}
</div>
<span key={step.label} className="text-xs text-center">
{step.label}
</span>
</div>
</div>
{index < steps.length - 1 && ( // Add horizontal line for all but the last step {index < steps.length - 1 && ( // Add horizontal line for all but the last step
<hr <hr
className={cn( className={cn(

View File

@ -0,0 +1,39 @@
import { FaCheckCircle } from "react-icons/fa";
import { cn } from "@/lib/utils";
import { useBrainCreationSteps } from "../../../hooks/useBrainCreationSteps";
import { Step as StepType } from "../../../types";
type StepProps = {
index: number;
step: StepType;
};
export const Step = ({ index, step }: StepProps): JSX.Element => {
const { currentStep, currentStepIndex } = useBrainCreationSteps();
const isStepDone = index < currentStepIndex;
const stepContent = isStepDone ? <FaCheckCircle /> : index + 1;
return (
<div
key={step.label}
className="flex flex-row justify-center items-center flex-1"
>
<div className="flex flex-col justify-center items-center">
<div
className={cn(
"h-[40px] w-[40px] border-solid rounded-full flex flex-row items-center justify-center mb-2 border-primary border-2 text-primary",
isStepDone ? "bg-primary text-white" : "",
step.value === currentStep ? "bg-primary text-white" : ""
)}
>
{stepContent}
</div>
<span key={step.label} className="text-xs text-center">
{step.label}
</span>
</div>
</div>
);
};

View File

@ -3,13 +3,13 @@ import { useTranslation } from "react-i18next";
import { CreateBrainProps } from "@/lib/components/AddBrainModal/types"; import { CreateBrainProps } from "@/lib/components/AddBrainModal/types";
import { StepperStep } from "../types"; import { Step } from "../types";
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export const useBrainCreationSteps = () => { export const useBrainCreationSteps = () => {
const { t } = useTranslation("brain"); const { t } = useTranslation("brain");
const steps: StepperStep[] = [ const steps: Step[] = [
{ {
label: t("brain_type"), label: t("brain_type"),
value: "BRAIN_TYPE", value: "BRAIN_TYPE",
@ -25,11 +25,11 @@ export const useBrainCreationSteps = () => {
]; ];
const { watch, setValue } = useFormContext<CreateBrainProps>(); const { watch, setValue } = useFormContext<CreateBrainProps>();
const currentStep = watch("brainCreationStep"); const currentStep = watch("brainCreationStep");
const currentStepIndex = steps.findIndex(
(step) => step.value === currentStep
);
const goToNextStep = () => { const goToNextStep = () => {
const currentStepIndex = steps.findIndex(
(step) => step.value === currentStep
);
if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) { if (currentStepIndex === -1 || currentStepIndex === steps.length - 1) {
return; return;
} }
@ -39,9 +39,6 @@ export const useBrainCreationSteps = () => {
}; };
const goToPreviousStep = () => { const goToPreviousStep = () => {
const currentStepIndex = steps.findIndex(
(step) => step.value === currentStep
);
if (currentStepIndex === -1 || currentStepIndex === 0) { if (currentStepIndex === -1 || currentStepIndex === 0) {
return; return;
} }
@ -55,5 +52,6 @@ export const useBrainCreationSteps = () => {
steps, steps,
goToNextStep, goToNextStep,
goToPreviousStep, goToPreviousStep,
currentStepIndex,
}; };
}; };

View File

@ -1,6 +1,6 @@
import { BrainCreationStep } from "../../types"; import { BrainCreationStep } from "../../types";
export type StepperStep = { export type Step = {
label: string; label: string;
value: BrainCreationStep; value: BrainCreationStep;
}; };

View File

@ -30,5 +30,8 @@
"invalidUrl": "Invalid URL", "invalidUrl": "Invalid URL",
"crawlButton": "Crawl", "crawlButton": "Crawl",
"themeSelect": "Interface theme", "themeSelect": "Interface theme",
"languageSelect": "Preferred language" "languageSelect": "Preferred language",
"cancel": "Cancel",
"next": "Next",
"previous": "Previous"
} }

View File

@ -30,5 +30,8 @@
"invalidUrl": "URL inválida", "invalidUrl": "URL inválida",
"crawlButton": "Rastrear", "crawlButton": "Rastrear",
"themeSelect": "Tema de interfaz", "themeSelect": "Tema de interfaz",
"languageSelect": "Idioma preferido" "languageSelect": "Idioma preferido",
"cancel": "Cancelar",
"next": "Siguiente",
"previous": "Anterior"
} }

View File

@ -6,7 +6,6 @@
"email": "Email", "email": "Email",
"or": "ou", "or": "ou",
"and": "et", "and": "et",
"logoutButton": "Déconnexion", "logoutButton": "Déconnexion",
"updateButton": "Mettre à jour", "updateButton": "Mettre à jour",
"uploadButton": "Télécharger", "uploadButton": "Télécharger",
@ -31,5 +30,8 @@
"invalidUrl": "URL invalide", "invalidUrl": "URL invalide",
"crawlButton": "Crawler", "crawlButton": "Crawler",
"themeSelect": "Thème de l'interface", "themeSelect": "Thème de l'interface",
"languageSelect": "Langue préférée" "languageSelect": "Langue préférée",
"cancel": "Annuler",
"next": "Suivant",
"previous": "Précédent"
} }

View File

@ -30,5 +30,8 @@
"invalidUrl": "URL inválida", "invalidUrl": "URL inválida",
"crawlButton": "Rastrear", "crawlButton": "Rastrear",
"themeSelect": "Tema de interface", "themeSelect": "Tema de interface",
"languageSelect": "Língua preferida" "languageSelect": "Língua preferida",
"cancel": "Cancelar",
"next": "Próximo",
"previous": "Anterior"
} }

View File

@ -30,5 +30,8 @@
"invalidUrl": "Неверный URL", "invalidUrl": "Неверный URL",
"crawlButton": "Поиск", "crawlButton": "Поиск",
"themeSelect": "Тема интерфейса", "themeSelect": "Тема интерфейса",
"languageSelect": "предпочтительный язык" "languageSelect": "предпочтительный язык",
"cancel": "Отмена",
"next": "Далее",
"previous": "Назад"
} }

View File

@ -30,5 +30,8 @@
"invalidUrl": "无效的 URL", "invalidUrl": "无效的 URL",
"crawlButton": "爬取", "crawlButton": "爬取",
"themeSelect": "主题", "themeSelect": "主题",
"languageSelect": "首选语言" "languageSelect": "首选语言",
"cancel": "取消",
"next": "下一个",
"previous": "上一个"
} }