From 121d4e5343a87f15f34a1a63108d6888232ffef4 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:14:58 +0200 Subject: [PATCH 1/4] fix(ui-common): error with formState in Form --- webapp/src/components/common/Form/useFormApiPlus.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/webapp/src/components/common/Form/useFormApiPlus.ts b/webapp/src/components/common/Form/useFormApiPlus.ts index 14c61787a9..e9a4eedfdb 100644 --- a/webapp/src/components/common/Form/useFormApiPlus.ts +++ b/webapp/src/components/common/Form/useFormApiPlus.ts @@ -184,9 +184,8 @@ function useFormApiPlus( // `formState` is wrapped with a Proxy and updated in batch. // The API is updated here to keep reference, like `useForm` return. - useEffect(() => { - formApiPlus.formState = formState; - }, [formApiPlus, formState]); + // ! Don't used `useEffect`, because it's read before render. + formApiPlus.formState = formState; return formApiPlus; } From 6b16e010f143b859d6214d2f7cd850e7b72f44ec Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:17:25 +0200 Subject: [PATCH 2/4] feat(ui-common): add submitButtonIcon prop in Form --- webapp/src/components/common/Form/index.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/common/Form/index.tsx b/webapp/src/components/common/Form/index.tsx index 2abc8b1949..7f510a7afc 100644 --- a/webapp/src/components/common/Form/index.tsx +++ b/webapp/src/components/common/Form/index.tsx @@ -27,7 +27,7 @@ import SaveIcon from "@mui/icons-material/Save"; import { useUpdateEffect } from "react-use"; import * as R from "ramda"; import clsx from "clsx"; -import { LoadingButton } from "@mui/lab"; +import { LoadingButton, LoadingButtonProps } from "@mui/lab"; import UndoIcon from "@mui/icons-material/Undo"; import RedoIcon from "@mui/icons-material/Redo"; import useEnqueueErrorSnackbar from "../../../hooks/useEnqueueErrorSnackbar"; @@ -57,6 +57,7 @@ export interface FormProps< | ((formApi: UseFormReturnPlus) => React.ReactNode) | React.ReactNode; submitButtonText?: string; + submitButtonIcon?: LoadingButtonProps["startIcon"]; hideSubmitButton?: boolean; onStateChange?: (state: FormState) => void; autoSubmit?: boolean | AutoSubmitConfig; @@ -78,6 +79,7 @@ function Form( onSubmitError, children, submitButtonText, + submitButtonIcon, hideSubmitButton, onStateChange, autoSubmit, @@ -314,7 +316,13 @@ function Form( disabled={!isSubmitAllowed} loading={isSubmitting} loadingPosition="start" - startIcon={} + startIcon={ + RA.isNotUndefined(submitButtonIcon) ? ( + submitButtonIcon + ) : ( + + ) + } > {submitButtonText || t("global.save")} From 92d5a1ed3c2c98802e3a65f49985960183bb6ac5 Mon Sep 17 00:00:00 2001 From: Samir Kamal <1954121+skamril@users.noreply.github.com> Date: Mon, 24 Jul 2023 15:22:45 +0200 Subject: [PATCH 3/4] feat(ui-common): display submit error in Form --- webapp/src/components/common/Form/index.tsx | 35 +++++++++++++++++---- webapp/src/components/common/Form/utils.ts | 2 ++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/webapp/src/components/common/Form/index.tsx b/webapp/src/components/common/Form/index.tsx index 7f510a7afc..9e8d6721c2 100644 --- a/webapp/src/components/common/Form/index.tsx +++ b/webapp/src/components/common/Form/index.tsx @@ -30,9 +30,15 @@ import clsx from "clsx"; import { LoadingButton, LoadingButtonProps } from "@mui/lab"; import UndoIcon from "@mui/icons-material/Undo"; import RedoIcon from "@mui/icons-material/Redo"; +import axios from "axios"; import useEnqueueErrorSnackbar from "../../../hooks/useEnqueueErrorSnackbar"; import useDebounce from "../../../hooks/useDebounce"; -import { getDirtyValues, stringToPath, toAutoSubmitConfig } from "./utils"; +import { + ROOT_ERROR_KEY, + getDirtyValues, + stringToPath, + toAutoSubmitConfig, +} from "./utils"; import useDebouncedState from "../../../hooks/useDebouncedState"; import usePrompt from "../../../hooks/usePrompt"; import { mergeSxProp } from "../../../utils/muiUtils"; @@ -125,14 +131,17 @@ function Form( : config?.defaultValues, }); - const { getValues, setValue, handleSubmit, formState, reset } = formApi; + const { getValues, setValue, setError, handleSubmit, formState, reset } = + formApi; // * /!\ `formState` is a proxy - const { isSubmitting, isSubmitSuccessful, isDirty, dirtyFields } = formState; + const { isSubmitting, isSubmitSuccessful, isDirty, dirtyFields, errors } = + formState; // Don't add `isValid` because we need to trigger fields validation. // In case we have invalid default value for example. const isSubmitAllowed = isDirty && !isSubmitting; const showSubmitButton = !hideSubmitButton && !autoSubmitConfig.enable; const showFooter = showSubmitButton || enableUndoRedo; + const rootError = errors.root?.[ROOT_ERROR_KEY]; const formApiPlus = useFormApiPlus({ formApi, @@ -242,8 +251,17 @@ function Form( } return Promise.all(res) - .catch((error) => { - enqueueErrorSnackbar(t("form.submit.error"), error); + .catch((err) => { + enqueueErrorSnackbar(t("form.submit.error"), err); + + // Any error under the `root` key are not persisted with each submission. + // They will be deleted automatically. + // cf. https://www.react-hook-form.com/api/useform/seterror/ + setError(`root.${ROOT_ERROR_KEY}`, { + message: axios.isAxiosError(err) + ? err.response?.data.description + : err?.toString(), + }); }) .finally(() => { preventClose.current = false; @@ -306,8 +324,13 @@ function Form( {children} )} + {rootError && ( + + {rootError.message || t("form.submit.error")} + + )} {showFooter && ( - + {showSubmitButton && ( <> Date: Thu, 20 Jul 2023 18:11:47 +0200 Subject: [PATCH 4/4] feat(ui-login): update form --- .../src/components/wrappers/LoginWrapper.tsx | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/webapp/src/components/wrappers/LoginWrapper.tsx b/webapp/src/components/wrappers/LoginWrapper.tsx index 77fa54a626..c03c079109 100644 --- a/webapp/src/components/wrappers/LoginWrapper.tsx +++ b/webapp/src/components/wrappers/LoginWrapper.tsx @@ -1,7 +1,6 @@ -import { useState } from "react"; import { Box, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; -import { LoadingButton } from "@mui/lab"; +import LoginIcon from "@mui/icons-material/Login"; import { login } from "../../redux/ducks/auth"; import logo from "../../assets/logo.png"; import topRightBackground from "../../assets/top-right-background.png"; @@ -30,7 +29,6 @@ interface Props { function LoginWrapper(props: Props) { const { children } = props; - const [loginError, setLoginError] = useState(""); const { t } = useTranslation(); const user = useAppSelector(getAuthUser); const dispatch = useAppDispatch(); @@ -62,18 +60,8 @@ function LoginWrapper(props: Props) { // Event Handlers //////////////////////////////////////////////////////////////// - const handleSubmit = async (data: SubmitHandlerPlus) => { - const { values } = data; - - setLoginError(""); - - try { - await dispatch(login(values)).unwrap(); - } catch (err) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - setLoginError((err as any).data?.message || t("login.error")); - throw err; - } + const handleSubmit = ({ values }: SubmitHandlerPlus) => { + return dispatch(login(values)).unwrap(); }; //////////////////////////////////////////////////////////////// @@ -150,47 +138,43 @@ function LoginWrapper(props: Props) { -
- {({ control, formState: { isDirty, isSubmitting } }) => ( + } + sx={{ + ".Form__Footer": { + justifyContent: "center", + }, + }} + > + {({ control }) => ( <> - {loginError && ( - - {loginError} - - )} - - - {t("global.connexion")} - - )}