Files
emaui/libs/auth/src/lib/pages/ForgotPasswordPage.tsx
estifanos 8f7c163b9e feat: integrate error handling across various components
- Added `useErrorHandler` hook to centralize error handling logic.
- Updated components in the backoffice and portal applications to utilize the new error handling mechanism, replacing direct notify calls with `handleError` for improved error messaging.
- Enhanced localization files to include generic error messages for better user feedback.
- Refactored error handling in forms and API interactions to ensure consistent user experience across the application.
2026-07-24 09:08:41 +00:00

198 lines
5.0 KiB
TypeScript

import { useState } from 'react';
import {
Alert,
Anchor,
Button,
Center,
Group,
Stack,
Text,
TextInput,
ThemeIcon,
Title,
} from '@mantine/core';
import {
IconArrowLeft,
IconMail,
IconMailForward,
IconSend,
} from '@tabler/icons-react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Link } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
import { notify, useErrorHandler } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
import { useAuthConfig } from '../AuthConfig';
const schema = z.object({
email: z.string().email({ message: 'Enter a valid email' }),
});
type FormValues = z.infer<typeof schema>;
export function ForgotPasswordPage() {
const { appName } = useAuthConfig();
const [forgotTrigger, { isLoading }] = useApiMutation();
const [sentTo, setSentTo] = useState<string | null>(null);
const [serverError, setServerError] = useState<string | null>(null);
const { handleError } = useErrorHandler();
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
});
const sendResetLink = async (email: string) => {
await forgotTrigger({
url: '/auth/forgot-password',
method: 'POST',
body: { email },
}).unwrap();
setSentTo(email);
};
const onSubmit = async (values: FormValues) => {
try {
await sendResetLink(values.email);
} catch (err: unknown) {
setServerError(handleError(err));
}
};
const handleResend = async () => {
if (!sentTo || isLoading) return;
try {
await sendResetLink(sentTo);
notify.success('Reset link sent again');
} catch (err: unknown) {
setServerError(handleError(err));
}
};
const backToSignIn = (
<Center>
<Anchor
component={Link}
to="/login"
size="sm"
fw={600}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}
>
<IconArrowLeft size={16} />
Back to sign in
</Anchor>
</Center>
);
if (sentTo) {
return (
<AuthShell
brandTitle="Reset your password securely."
brandSubtitle={`We'll email you a secure link to set a new password and get you back into ${appName}.`}
>
<Stack gap="lg">
<ThemeIcon size={56} radius="md" variant="light" color="emaPrimary">
<IconMailForward size={30} />
</ThemeIcon>
<div>
<Title order={2} fz={30}>
Check your email
</Title>
<Text c="dimmed" mt={6}>
We&apos;ve sent a password reset link to{' '}
<Text span fw={600} c="dark">
{sentTo}
</Text>
. Follow the link in that email to choose a new password.
</Text>
</div>
<Button
component="a"
href="https://mail.google.com"
target="_blank"
rel="noopener noreferrer"
fullWidth
size="md"
leftSection={<IconMail size={18} />}
>
Open email app
</Button>
<Group justify="center" gap={6}>
<Text size="sm" c="dimmed">
Didn&apos;t get the email?
</Text>
<Anchor
size="sm"
fw={600}
onClick={handleResend}
style={isLoading ? { pointerEvents: 'none', opacity: 0.6 } : undefined}
>
Resend link
</Anchor>
</Group>
{backToSignIn}
</Stack>
</AuthShell>
);
}
return (
<AuthShell
brandTitle="Reset your password securely."
brandSubtitle={`We'll email you a secure link to set a new password and get you back into ${appName}.`}
>
<Stack gap="lg">
<div>
<Title order={2} fz={30}>
Forgot your password?
</Title>
<Text c="dimmed" mt={6}>
Enter the email linked to your account and we&apos;ll send you a link
to reset your password.
</Text>
</div>
{serverError && (
<Alert variant="light" color="red" withCloseButton onClose={() => setServerError(null)}>
{serverError}
</Alert>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<Stack gap="md">
<TextInput
label="Email address"
placeholder="you@example.com"
size="md"
leftSection={<IconMail size={18} />}
error={errors.email?.message}
{...register('email')}
/>
<Button
type="submit"
loading={isLoading}
fullWidth
size="md"
rightSection={<IconSend size={18} />}
>
Send reset link
</Button>
</Stack>
</form>
{backToSignIn}
</Stack>
</AuthShell>
);
}