mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
- 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.
198 lines
5.0 KiB
TypeScript
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'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'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'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>
|
|
);
|
|
}
|