Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix login submit issue #1660

Merged
merged 4 commits into from
Jul 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions webapp/src/components/common/Form/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ 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 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";
Expand All @@ -57,6 +63,7 @@ export interface FormProps<
| ((formApi: UseFormReturnPlus<TFieldValues, TContext>) => React.ReactNode)
| React.ReactNode;
submitButtonText?: string;
submitButtonIcon?: LoadingButtonProps["startIcon"];
hideSubmitButton?: boolean;
onStateChange?: (state: FormState<TFieldValues>) => void;
autoSubmit?: boolean | AutoSubmitConfig;
Expand All @@ -78,6 +85,7 @@ function Form<TFieldValues extends FieldValues, TContext>(
onSubmitError,
children,
submitButtonText,
submitButtonIcon,
hideSubmitButton,
onStateChange,
autoSubmit,
Expand Down Expand Up @@ -123,14 +131,17 @@ function Form<TFieldValues extends FieldValues, TContext>(
: 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,
Expand Down Expand Up @@ -240,8 +251,17 @@ function Form<TFieldValues extends FieldValues, TContext>(
}

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;
Expand Down Expand Up @@ -304,8 +324,13 @@ function Form<TFieldValues extends FieldValues, TContext>(
<FormProvider {...formApiPlus}>{children}</FormProvider>
)}
</FormContext.Provider>
{rootError && (
<Box color="error.main" sx={{ fontSize: "0.9rem", mb: 2 }}>
{rootError.message || t("form.submit.error")}
</Box>
)}
{showFooter && (
<Box sx={{ display: "flex" }}>
<Box sx={{ display: "flex" }} className="Form__Footer">
{showSubmitButton && (
<>
<LoadingButton
Expand All @@ -314,7 +339,13 @@ function Form<TFieldValues extends FieldValues, TContext>(
disabled={!isSubmitAllowed}
loading={isSubmitting}
loadingPosition="start"
startIcon={<SaveIcon />}
startIcon={
RA.isNotUndefined(submitButtonIcon) ? (
submitButtonIcon
) : (
<SaveIcon />
)
}
>
{submitButtonText || t("global.save")}
</LoadingButton>
Expand Down
5 changes: 2 additions & 3 deletions webapp/src/components/common/Form/useFormApiPlus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,8 @@ function useFormApiPlus<TFieldValues extends FieldValues, TContext>(

// `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;
}
Expand Down
2 changes: 2 additions & 0 deletions webapp/src/components/common/Form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ export function stringToPath(input: string): string[] {
.split(/\.|\[/)
.filter(Boolean);
}

export const ROOT_ERROR_KEY = "default";
64 changes: 24 additions & 40 deletions webapp/src/components/wrappers/LoginWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -62,18 +60,8 @@ function LoginWrapper(props: Props) {
// Event Handlers
////////////////////////////////////////////////////////////////

const handleSubmit = async (data: SubmitHandlerPlus<FormValues>) => {
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<FormValues>) => {
return dispatch(login(values)).unwrap();
};

////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -150,47 +138,43 @@ function LoginWrapper(props: Props) {
</Typography>
</Box>
<Box width="70%" my={2}>
<Form onSubmit={handleSubmit} hideSubmitButton>
{({ control, formState: { isDirty, isSubmitting } }) => (
<Form
onSubmit={handleSubmit}
submitButtonText={t("global.connexion")}
submitButtonIcon={<LoginIcon />}
sx={{
".Form__Footer": {
justifyContent: "center",
},
}}
>
{({ control }) => (
<>
<StringFE
name="username"
sx={{ my: 3 }}
label="NNI"
variant="filled"
size="small"
sx={{ mb: 2 }}
fullWidth
control={control}
rules={{ required: t("form.field.required") }}
/>
<PasswordFE
name="password"
variant="filled"
label={t("global.password")}
inputProps={{ autoComplete: "current-password" }}
variant="filled"
size="small"
inputProps={{
// https://web.dev/sign-in-form-best-practices/#current-password
autoComplete: "current-password",
id: "current-password",
}}
sx={{ mb: 3 }}
fullWidth
control={control}
rules={{ required: t("form.field.required") }}
/>
{loginError && (
<Box
mt={2}
color="error.main"
mb={4}
sx={{ fontSize: "0.9rem" }}
>
{loginError}
</Box>
)}
<Box display="flex" justifyContent="center" mt={6}>
<LoadingButton
type="submit"
variant="contained"
loading={isSubmitting}
disabled={!isDirty || isSubmitting}
>
{t("global.connexion")}
</LoadingButton>
</Box>
</>
)}
</Form>
Expand Down