```refactor Add reset password form component with Zod validation, email input handling, and internationalization support for custom auth flow

```
This commit is contained in:
2025-05-16 11:07:13 +08:00
parent bf059147c7
commit a8b0a63648
2 changed files with 132 additions and 2 deletions

View File

@@ -7,7 +7,7 @@
"node": "==22"
},
"scripts": {
"dev": "next dev",
"dev": "next dev -H 0.0.0.0",
"build": "next build",
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
"start": "next start",
@@ -117,4 +117,4 @@
"protobufjs"
]
}
}
}

View File

@@ -0,0 +1,130 @@
'use client';
import * as React from 'react';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import Alert from '@mui/material/Alert';
import Button from '@mui/material/Button';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import OutlinedInput from '@mui/material/OutlinedInput';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z as zod } from 'zod';
import { paths } from '@/paths';
import { authClient } from '@/lib/auth/custom/client';
export function ResetPasswordForm(): React.JSX.Element {
const { t } = useTranslation(['sign_in']);
const router = useRouter();
const [isPending, setIsPending] = React.useState<boolean>(false);
const schema = zod.object({
email: zod
.string()
.min(1, { message: t('email-is-required') })
.email(),
});
type Values = zod.infer<typeof schema>;
const defaultValues = { email: '' } satisfies Values;
const {
control,
handleSubmit,
setError,
formState: { errors },
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(
async (values: Values): Promise<void> => {
setIsPending(true);
const { error } = await authClient.resetPassword(values);
if (error) {
setError('root', { type: 'server', message: error });
setIsPending(false);
return;
}
setIsPending(false);
// Redirect to confirm password reset
},
[setError]
);
function handleBackToLoginClick(): void {
router.replace(paths.auth.custom.signIn);
}
return (
<Stack spacing={4}>
{/*
<div>
<Box
component={RouterLink}
href={paths.home}
sx={{ display: 'inline-block', fontSize: 0 }}
>
<DynamicLogo
colorDark="light"
colorLight="dark"
height={32}
width={122}
/>
</Box>
</div>
*/}
<Typography variant="h5">{t('reset-password')}</Typography>
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={2}>
<Controller
control={control}
name="email"
render={({ field }) => (
<FormControl error={Boolean(errors.email)}>
<InputLabel>{t('email-address')}</InputLabel>
<OutlinedInput
{...field}
type="email"
/>
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
</FormControl>
)}
/>
{errors.root ? <Alert color="error">{errors.root.message}</Alert> : null}
<Stack
direction="row"
spacing={4}
justifyContent="space-between"
>
<Button
disabled={isPending}
type="reset"
variant="outlined"
color="secondary"
onClick={handleBackToLoginClick}
>
{t('back-to-login')}
</Button>
<Button
disabled={isPending}
type="submit"
variant="contained"
>
{t('send-recovery-link')}
</Button>
</Stack>
</Stack>
</form>
</Stack>
);
}