veza/apps/web/src/features/auth/pages/VerifyEmailPage.tsx

226 lines
7.1 KiB
TypeScript
Raw Normal View History

import React, { useState, useEffect, useRef } from 'react';
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { AuthLayout } from '../components/AuthLayout';
import { AuthButton } from '../components/AuthButton';
import { verifyEmail, resendVerificationEmail } from '../services/authService';
import type { ApiError } from '../services/authService';
export function VerifyEmailPage() {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [status, setStatus] = useState<'verifying' | 'success' | 'error'>('verifying');
const [message, setMessage] = useState('Vérification de votre email en cours...');
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);
}
}, [status, navigate]);
const handleVerifyEmail = async (emailToken: string) => {
try {
setLoading(true);
setStatus('verifying');
setMessage('Vérification de votre email en cours...');
await verifyEmail(emailToken);
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);
// Récupérer l'email depuis localStorage (stocké lors de l'inscription)
const email = localStorage.getItem('pendingVerificationEmail');
if (!email) {
setMessage('Email non trouvé. Veuillez vous réinscrire ou contacter le support.');
return;
}
await resendVerificationEmail(email);
// Définir cooldown de 60 secondes
setResendCooldown(60);
// Afficher message de confirmation
setMessage('Email de vérification envoyé ! Veuillez vérifier votre boîte mail.');
} catch (error) {
const apiError = error as ApiError;
setMessage(apiError.message || 'Échec de l\'envoi de l\'email');
} 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..."
footerLinks={[
{ label: 'Retour à la connexion', to: '/login' },
]}
>
<div className="text-center space-y-4" role="status" aria-live="polite" aria-busy="true">
<div className="flex justify-center" aria-hidden="true">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
<p className="text-gray-600">{message}</p>
<span className="sr-only">Vérification de votre email en cours, veuillez patienter</span>
</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"
footerLinks={[
{ label: 'Retour à la connexion', to: '/login' },
]}
>
<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">
<p className="font-medium">Succès !</p>
<p className="text-sm mt-1">{message}</p>
<p className="text-xs mt-2 text-gray-600">
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"
footerLinks={[
{ label: 'Retour à la connexion', to: '/login' },
]}
>
<div className="text-center space-y-4">
<div
className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded"
role="alert"
aria-live="assertive"
>
<p className="font-medium">Erreur</p>
<p className="text-sm mt-1">{message}</p>
</div>
<div className="space-y-2">
{token && (
<AuthButton
onClick={() => handleVerifyEmail(token)}
loading={loading}
type="button"
>
Réessayer
</AuthButton>
)}
<AuthButton
onClick={handleResendVerificationEmail}
loading={resendLoading}
disabled={resendCooldown > 0}
type="button"
variant="secondary"
aria-label={resendCooldown > 0 ? `Renvoyer l'email de vérification dans ${resendCooldown} secondes` : "Renvoyer l'email de vérification"}
>
{resendCooldown > 0 ? (
<>
<span className="sr-only">Renvoyer dans {resendCooldown} secondes</span>
<span aria-hidden="true">Renvoyer dans {resendCooldown}s</span>
</>
) : (
'Renvoyer l\'email de vérification'
)}
</AuthButton>
</div>
</div>
</AuthLayout>
);
}
export default VerifyEmailPage;