128 lines
4 KiB
TypeScript
128 lines
4 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { AuthLayout } from '../components/AuthLayout';
|
|
import { AuthInput } from '../components/AuthInput';
|
|
import { AuthButton } from '../components/AuthButton';
|
|
import { usePasswordReset } from '../hooks/usePasswordReset';
|
|
import type { ForgotPasswordFormData } from '../types';
|
|
|
|
export function ForgotPasswordPage() {
|
|
const { handleRequestReset, loading, error, success } = usePasswordReset();
|
|
const [formData, setFormData] = useState<ForgotPasswordFormData>({
|
|
email: '',
|
|
});
|
|
const [errors, setErrors] = useState<
|
|
Partial<Record<keyof ForgotPasswordFormData, string>>
|
|
>({});
|
|
|
|
const validate = (): boolean => {
|
|
const newErrors: Partial<Record<keyof ForgotPasswordFormData, string>> = {};
|
|
|
|
if (!formData.email) {
|
|
newErrors.email = 'Email requis';
|
|
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
|
|
newErrors.email = 'Email invalide';
|
|
}
|
|
|
|
setErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const handleChange = (field: keyof ForgotPasswordFormData, value: string) => {
|
|
setFormData({ ...formData, [field]: value });
|
|
// Clear error for this field when user starts typing
|
|
if (errors[field]) {
|
|
setErrors({ ...errors, [field]: undefined });
|
|
}
|
|
};
|
|
|
|
const handleBlur = (field: keyof ForgotPasswordFormData) => {
|
|
const value = formData[field];
|
|
let error: string | undefined;
|
|
|
|
if (field === 'email') {
|
|
if (!value) {
|
|
error = 'Email requis';
|
|
} else if (!/\S+@\S+\.\S+/.test(value)) {
|
|
error = 'Email invalide';
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
setErrors({ ...errors, [field]: error });
|
|
} else {
|
|
setErrors({ ...errors, [field]: undefined });
|
|
}
|
|
};
|
|
|
|
const onSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (validate()) {
|
|
await handleRequestReset(formData);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AuthLayout
|
|
title="Mot de passe oublié"
|
|
subtitle="Entrez votre email pour recevoir un lien de réinitialisation"
|
|
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
|
|
>
|
|
{success ? (
|
|
<div className="text-center space-y-4" role="status" aria-live="polite">
|
|
<div
|
|
className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded"
|
|
role="alert"
|
|
aria-live="assertive"
|
|
>
|
|
<p className="font-medium">Email envoyé !</p>
|
|
<p className="text-sm mt-1">
|
|
Un lien de réinitialisation a été envoyé à {formData.email}
|
|
</p>
|
|
</div>
|
|
<p className="text-sm text-gray-600">
|
|
Veuillez vérifier votre boîte mail et cliquer sur le lien pour
|
|
réinitialiser votre mot de passe.
|
|
</p>
|
|
<Link
|
|
to="/login"
|
|
className="text-blue-600 hover:underline text-sm block focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 rounded"
|
|
>
|
|
Retour à la connexion
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<form
|
|
onSubmit={onSubmit}
|
|
className="space-y-4"
|
|
aria-label="Formulaire de réinitialisation de mot de passe"
|
|
>
|
|
{error && (
|
|
<div
|
|
className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded"
|
|
role="alert"
|
|
aria-live="assertive"
|
|
>
|
|
{error.message}
|
|
</div>
|
|
)}
|
|
<AuthInput
|
|
type="email"
|
|
label="Email"
|
|
value={formData.email}
|
|
onChange={(e) => handleChange('email', e.target.value)}
|
|
onBlur={() => handleBlur('email')}
|
|
error={errors.email}
|
|
required
|
|
autoComplete="email"
|
|
/>
|
|
<AuthButton type="submit" loading={loading}>
|
|
Envoyer le lien de réinitialisation
|
|
</AuthButton>
|
|
</form>
|
|
)}
|
|
</AuthLayout>
|
|
);
|
|
}
|
|
|
|
export default ForgotPasswordPage;
|