Files
emaui/libs/auth/src/lib/pages/SetPasswordPage.tsx
2026-07-11 10:09:18 +03:00

139 lines
4.2 KiB
TypeScript

import { useState } from 'react';
import { Alert, Button, PasswordInput, Stack, Text, ThemeIcon, Title } from '@mantine/core';
import { IconArrowRight, IconLock, IconLockOpen } from '@tabler/icons-react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useNavigate } from 'react-router-dom';
import { useApiMutation } from '@ema-platform/api';
import { notify } from '@ema-platform/ui';
import { AuthShell } from '../components/AuthShell';
const schema = z
.object({
newPassword: z.string().min(8, { message: 'Password must be at least 8 characters' }),
confirmPassword: z.string().min(8, { message: 'Confirm your password' }),
})
.refine((data) => data.newPassword === data.confirmPassword, {
message: 'Passwords do not match',
path: ['confirmPassword'],
});
type FormValues = z.infer<typeof schema>;
export function SetPasswordPage() {
const navigate = useNavigate();
const [serverError, setServerError] = useState<string | null>(null);
const [setPasswordTrigger, { isLoading }] = useApiMutation();
const userId = sessionStorage.getItem('fayda_userId');
const userInfoRaw = sessionStorage.getItem('fayda_userInfo');
let userName = 'your account';
if (userInfoRaw) {
try {
const info = JSON.parse(userInfoRaw);
userName = info.name || 'your account';
} catch {
// ignore
}
}
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>({
resolver: zodResolver(schema),
});
const onSubmit = async (values: FormValues) => {
if (!userId) {
notify.error('Session expired. Please sign up again.');
navigate('/signup', { replace: true });
return;
}
try {
await setPasswordTrigger({
url: '/v1/auth/set-fayda-password',
method: 'PATCH',
body: {
userId,
newPassword: values.newPassword,
confirmPassword: values.confirmPassword,
},
}).unwrap();
sessionStorage.removeItem('fayda_userId');
sessionStorage.removeItem('fayda_userInfo');
notify.success('Password set successfully');
navigate('/login', { replace: true });
} catch (err: unknown) {
const msg =
(err as { data?: { message?: string } })?.data?.message ??
(err instanceof Error ? err.message : 'Something went wrong');
setServerError(msg);
notify.error(msg);
}
};
return (
<AuthShell
brandTitle="Set your account password."
brandSubtitle="Choose a strong password to secure your account and access all Portal features."
>
<Stack gap="lg">
<ThemeIcon size={56} radius="md" variant="light" color="emaPrimary">
<IconLockOpen size={30} />
</ThemeIcon>
<div>
<Title order={2} fz={30}>
Set your password
</Title>
<Text c="dimmed" mt={6}>
Welcome, <Text span fw={600}>{userName}</Text>. Your National ID has been verified successfully.
Choose a password to complete your account setup.
</Text>
</div>
{serverError && (
<Alert variant="light" color="red" withCloseButton onClose={() => setServerError(null)}>
{serverError}
</Alert>
)}
<form onSubmit={handleSubmit(onSubmit)}>
<Stack gap="md">
<PasswordInput
label="New password"
placeholder="At least 8 characters"
leftSection={<IconLock size={18} />}
error={errors.newPassword?.message}
{...register('newPassword')}
/>
<PasswordInput
label="Confirm password"
placeholder="Re-enter password"
leftSection={<IconLock size={18} />}
error={errors.confirmPassword?.message}
{...register('confirmPassword')}
/>
<Button
type="submit"
loading={isLoading}
fullWidth
size="md"
rightSection={<IconArrowRight size={18} />}
>
Set password &amp; continue
</Button>
</Stack>
</form>
</Stack>
</AuthShell>
);
}