2025-12-22 21:56:37 +00:00
|
|
|
import { useState, useEffect, useRef } from 'react';
|
|
|
|
|
import { useNavigate, useSearchParams } from 'react-router-dom';
|
2025-12-03 21:56:50 +00:00
|
|
|
import { AuthLayout } from '../components/AuthLayout';
|
|
|
|
|
import { AuthButton } from '../components/AuthButton';
|
2026-01-15 19:40:46 +00:00
|
|
|
import { authApi } from '@/services/api/auth';
|
2026-01-15 16:03:35 +00:00
|
|
|
import type { ApiError } from '@/schemas/apiSchemas';
|
2025-12-03 21:56:50 +00:00
|
|
|
|
|
|
|
|
export function VerifyEmailPage() {
|
|
|
|
|
const [searchParams] = useSearchParams();
|
|
|
|
|
const navigate = useNavigate();
|
2025-12-13 02:34:34 +00:00
|
|
|
const [status, setStatus] = useState<'verifying' | 'success' | 'error'>(
|
|
|
|
|
'verifying',
|
|
|
|
|
);
|
|
|
|
|
const [message, setMessage] = useState(
|
|
|
|
|
'Vérification de votre email en cours...',
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [resendLoading, setResendLoading] = useState(false);
|
|
|
|
|
const [resendCooldown, setResendCooldown] = useState(0);
|
|
|
|
|
const cooldownIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
|
|
|
const [token, setToken] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
// Extraire le token depuis l'URL
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const tokenParam = searchParams.get('token');
|
|
|
|
|
if (tokenParam) {
|
|
|
|
|
setToken(tokenParam);
|
|
|
|
|
// Vérifier l'email automatiquement si le token est présent
|
|
|
|
|
handleVerifyEmail(tokenParam);
|
|
|
|
|
} else {
|
|
|
|
|
setStatus('error');
|
|
|
|
|
setMessage('Lien de vérification invalide ou manquant');
|
|
|
|
|
}
|
|
|
|
|
}, [searchParams]);
|
|
|
|
|
|
|
|
|
|
// Cleanup cooldown interval on unmount
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
return () => {
|
|
|
|
|
if (cooldownIntervalRef.current) {
|
|
|
|
|
clearInterval(cooldownIntervalRef.current);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Gérer le cooldown pour le renvoi d'email
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (resendCooldown > 0) {
|
|
|
|
|
cooldownIntervalRef.current = setInterval(() => {
|
|
|
|
|
setResendCooldown((prev) => {
|
|
|
|
|
if (prev <= 1) {
|
|
|
|
|
if (cooldownIntervalRef.current) {
|
|
|
|
|
clearInterval(cooldownIntervalRef.current);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return prev - 1;
|
|
|
|
|
});
|
|
|
|
|
}, 1000);
|
|
|
|
|
} else {
|
|
|
|
|
if (cooldownIntervalRef.current) {
|
|
|
|
|
clearInterval(cooldownIntervalRef.current);
|
|
|
|
|
cooldownIntervalRef.current = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
if (cooldownIntervalRef.current) {
|
|
|
|
|
clearInterval(cooldownIntervalRef.current);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}, [resendCooldown]);
|
|
|
|
|
|
|
|
|
|
// Rediriger vers login après succès
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (status === 'success') {
|
|
|
|
|
const timer = setTimeout(() => {
|
|
|
|
|
navigate('/login', { replace: true });
|
|
|
|
|
}, 3000);
|
|
|
|
|
return () => clearTimeout(timer);
|
|
|
|
|
}
|
2025-12-27 17:40:36 +00:00
|
|
|
return undefined;
|
2025-12-03 21:56:50 +00:00
|
|
|
}, [status, navigate]);
|
|
|
|
|
|
|
|
|
|
const handleVerifyEmail = async (emailToken: string) => {
|
|
|
|
|
try {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
setStatus('verifying');
|
|
|
|
|
setMessage('Vérification de votre email en cours...');
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2026-02-12 23:32:08 +00:00
|
|
|
await authApi.verifyEmail({ token: emailToken });
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
setStatus('success');
|
|
|
|
|
setMessage('Votre email a été vérifié avec succès !');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
setStatus('error');
|
|
|
|
|
const apiError = error as ApiError;
|
|
|
|
|
setMessage(apiError.message || 'La vérification a échoué');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleResendVerificationEmail = async () => {
|
|
|
|
|
if (resendCooldown > 0 || resendLoading) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
setResendLoading(true);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Récupérer l'email depuis localStorage (stocké lors de l'inscription)
|
|
|
|
|
const email = localStorage.getItem('pendingVerificationEmail');
|
|
|
|
|
if (!email) {
|
2025-12-13 02:34:34 +00:00
|
|
|
setMessage(
|
|
|
|
|
'Email non trouvé. Veuillez vous réinscrire ou contacter le support.',
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 19:40:46 +00:00
|
|
|
await authApi.resendVerification({ email });
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Définir cooldown de 60 secondes
|
|
|
|
|
setResendCooldown(60);
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
// Afficher message de confirmation
|
2025-12-13 02:34:34 +00:00
|
|
|
setMessage(
|
|
|
|
|
'Email de vérification envoyé ! Veuillez vérifier votre boîte mail.',
|
|
|
|
|
);
|
2025-12-03 21:56:50 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
const apiError = error as ApiError;
|
2025-12-13 02:34:34 +00:00
|
|
|
setMessage(apiError.message || "Échec de l'envoi de l'email");
|
2025-12-03 21:56:50 +00:00
|
|
|
} finally {
|
|
|
|
|
setResendLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Afficher le statut de vérification
|
|
|
|
|
if (status === 'verifying') {
|
|
|
|
|
return (
|
|
|
|
|
<AuthLayout
|
|
|
|
|
title="Vérification de l'email"
|
|
|
|
|
subtitle="Vérification en cours..."
|
2025-12-13 02:34:34 +00:00
|
|
|
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
|
2025-12-03 21:56:50 +00:00
|
|
|
>
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
|
|
|
|
className="text-center space-y-4"
|
|
|
|
|
role="status"
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
aria-busy="true"
|
|
|
|
|
>
|
2025-12-03 21:56:50 +00:00
|
|
|
<div className="flex justify-center" aria-hidden="true">
|
2026-02-07 15:02:52 +00:00
|
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
|
2025-12-03 21:56:50 +00:00
|
|
|
</div>
|
2026-02-08 23:04:51 +00:00
|
|
|
<p className="text-muted-foreground">{message}</p>
|
2025-12-13 02:34:34 +00:00
|
|
|
<span className="sr-only">
|
|
|
|
|
Vérification de votre email en cours, veuillez patienter
|
|
|
|
|
</span>
|
2025-12-03 21:56:50 +00:00
|
|
|
</div>
|
|
|
|
|
</AuthLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Afficher le message de succès
|
|
|
|
|
if (status === 'success') {
|
|
|
|
|
return (
|
|
|
|
|
<AuthLayout
|
|
|
|
|
title="Email vérifié"
|
|
|
|
|
subtitle="Votre email a été vérifié avec succès"
|
2025-12-13 02:34:34 +00:00
|
|
|
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
|
2025-12-03 21:56:50 +00:00
|
|
|
>
|
|
|
|
|
<div className="text-center space-y-4" role="status" aria-live="polite">
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
2026-02-07 15:02:52 +00:00
|
|
|
className="bg-success/10 border border-success text-success px-4 py-4 rounded"
|
2025-12-13 02:34:34 +00:00
|
|
|
role="alert"
|
|
|
|
|
>
|
2025-12-03 21:56:50 +00:00
|
|
|
<p className="font-medium">Succès !</p>
|
|
|
|
|
<p className="text-sm mt-1">{message}</p>
|
2026-02-08 23:04:51 +00:00
|
|
|
<p className="text-xs mt-2 text-muted-foreground">
|
2025-12-03 21:56:50 +00:00
|
|
|
Vous allez être redirigé vers la page de connexion...
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</AuthLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Afficher le message d'erreur avec options de retry/resend
|
|
|
|
|
return (
|
|
|
|
|
<AuthLayout
|
|
|
|
|
title="Vérification de l'email"
|
|
|
|
|
subtitle="Une erreur s'est produite"
|
2025-12-13 02:34:34 +00:00
|
|
|
footerLinks={[{ label: 'Retour à la connexion', to: '/login' }]}
|
2025-12-03 21:56:50 +00:00
|
|
|
>
|
|
|
|
|
<div className="text-center space-y-4">
|
2025-12-13 02:34:34 +00:00
|
|
|
<div
|
2026-02-07 15:02:52 +00:00
|
|
|
className="bg-destructive/10 border border-destructive text-destructive px-4 py-4 rounded"
|
2025-12-03 21:56:50 +00:00
|
|
|
role="alert"
|
|
|
|
|
aria-live="assertive"
|
|
|
|
|
>
|
|
|
|
|
<p className="font-medium">Erreur</p>
|
|
|
|
|
<p className="text-sm mt-1">{message}</p>
|
|
|
|
|
</div>
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
<div className="space-y-2">
|
|
|
|
|
{token && (
|
|
|
|
|
<AuthButton
|
|
|
|
|
onClick={() => handleVerifyEmail(token)}
|
|
|
|
|
loading={loading}
|
|
|
|
|
type="button"
|
|
|
|
|
>
|
|
|
|
|
Réessayer
|
|
|
|
|
</AuthButton>
|
|
|
|
|
)}
|
2025-12-13 02:34:34 +00:00
|
|
|
|
2025-12-03 21:56:50 +00:00
|
|
|
<AuthButton
|
|
|
|
|
onClick={handleResendVerificationEmail}
|
|
|
|
|
loading={resendLoading}
|
|
|
|
|
disabled={resendCooldown > 0}
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
2025-12-13 02:34:34 +00:00
|
|
|
aria-label={
|
|
|
|
|
resendCooldown > 0
|
|
|
|
|
? `Renvoyer l'email de vérification dans ${resendCooldown} secondes`
|
|
|
|
|
: "Renvoyer l'email de vérification"
|
|
|
|
|
}
|
2025-12-03 21:56:50 +00:00
|
|
|
>
|
|
|
|
|
{resendCooldown > 0 ? (
|
|
|
|
|
<>
|
2025-12-13 02:34:34 +00:00
|
|
|
<span className="sr-only">
|
|
|
|
|
Renvoyer dans {resendCooldown} secondes
|
|
|
|
|
</span>
|
2025-12-03 21:56:50 +00:00
|
|
|
<span aria-hidden="true">Renvoyer dans {resendCooldown}s</span>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
2025-12-13 02:34:34 +00:00
|
|
|
"Renvoyer l'email de vérification"
|
2025-12-03 21:56:50 +00:00
|
|
|
)}
|
|
|
|
|
</AuthButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</AuthLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default VerifyEmailPage;
|