107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
||
|
|
import { Link, useSearchParams } from 'react-router-dom';
|
||
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
import { useForm } from 'react-hook-form';
|
||
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||
|
|
import { z } from 'zod';
|
||
|
|
import { usePasswordResetConfirm } from '@/api/hooks';
|
||
|
|
import { Button } from '@/components/ui/Button';
|
||
|
|
import { Input } from '@/components/ui/Input';
|
||
|
|
|
||
|
|
const resetConfirmSchema = z.object({
|
||
|
|
password: z.string().min(8),
|
||
|
|
confirmPassword: z.string().min(8),
|
||
|
|
}).refine((data) => data.password === data.confirmPassword, {
|
||
|
|
message: 'Passwords do not match',
|
||
|
|
path: ['confirmPassword'],
|
||
|
|
});
|
||
|
|
|
||
|
|
type ResetConfirmFormData = z.infer<typeof resetConfirmSchema>;
|
||
|
|
|
||
|
|
export function PasswordResetConfirmPage() {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
const [searchParams] = useSearchParams();
|
||
|
|
const token = searchParams.get('token') || '';
|
||
|
|
const resetMutation = usePasswordResetConfirm();
|
||
|
|
const [result, setResult] = useState<'success' | 'error' | null>(null);
|
||
|
|
|
||
|
|
const {
|
||
|
|
register,
|
||
|
|
handleSubmit,
|
||
|
|
formState: { errors },
|
||
|
|
} = useForm<ResetConfirmFormData>({
|
||
|
|
resolver: zodResolver(resetConfirmSchema),
|
||
|
|
defaultValues: { password: '', confirmPassword: '' },
|
||
|
|
});
|
||
|
|
|
||
|
|
const onSubmit = async (data: ResetConfirmFormData) => {
|
||
|
|
try {
|
||
|
|
await resetMutation.mutateAsync({ token, password: data.password });
|
||
|
|
setResult('success');
|
||
|
|
} catch {
|
||
|
|
setResult('error');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (result === 'success') {
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen flex items-center justify-center bg-secondary-50 px-4" data-testid="reset-confirm-page">
|
||
|
|
<div className="w-full max-w-md">
|
||
|
|
<div className="bg-white rounded-lg shadow-md p-8 text-center">
|
||
|
|
<p className="text-secondary-700 mb-6">{t('auth.resetConfirmSuccess')}</p>
|
||
|
|
<Link to="/login" className="text-sm text-primary-600 hover:text-primary-700">
|
||
|
|
{t('auth.backToLogin')}
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen flex items-center justify-center bg-secondary-50 px-4" data-testid="reset-confirm-page">
|
||
|
|
<div className="w-full max-w-md">
|
||
|
|
<div className="text-center mb-8">
|
||
|
|
<h1 className="text-3xl font-bold text-primary-600">leocrm</h1>
|
||
|
|
</div>
|
||
|
|
<div className="bg-white rounded-lg shadow-md p-8">
|
||
|
|
<h2 className="text-xl font-semibold text-secondary-900 mb-2">{t('auth.resetConfirmTitle')}</h2>
|
||
|
|
<p className="text-sm text-secondary-500 mb-6">{t('auth.resetConfirmDesc')}</p>
|
||
|
|
{result === 'error' && (
|
||
|
|
<div role="alert" className="text-sm text-danger-600 bg-danger-50 rounded-md p-3 mb-4">
|
||
|
|
{t('auth.resetConfirmFailed')}
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4" noValidate>
|
||
|
|
<Input
|
||
|
|
label={t('auth.newPassword')}
|
||
|
|
type="password"
|
||
|
|
autoComplete="new-password"
|
||
|
|
required
|
||
|
|
error={errors.password?.message}
|
||
|
|
helperText={t('auth.passwordTooShort')}
|
||
|
|
{...register('password')}
|
||
|
|
/>
|
||
|
|
<Input
|
||
|
|
label={t('auth.confirmPassword')}
|
||
|
|
type="password"
|
||
|
|
autoComplete="new-password"
|
||
|
|
required
|
||
|
|
error={errors.confirmPassword?.message}
|
||
|
|
{...register('confirmPassword')}
|
||
|
|
/>
|
||
|
|
<Button type="submit" fullWidth isLoading={resetMutation.isPending}>
|
||
|
|
{t('auth.resetConfirmButton')}
|
||
|
|
</Button>
|
||
|
|
</form>
|
||
|
|
<div className="mt-4 text-center">
|
||
|
|
<Link to="/login" className="text-sm text-primary-600 hover:text-primary-700">
|
||
|
|
{t('auth.backToLogin')}
|
||
|
|
</Link>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|