first commit

This commit is contained in:
Michaël
2026-01-29 14:13:11 -03:00
commit 2302748c87
105 changed files with 93301 additions and 0 deletions
@@ -0,0 +1,119 @@
'use client';
import { useEffect, useState, Suspense, useRef } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useLanguage } from '@/context/LanguageContext';
import { useAuth } from '@/context/AuthContext';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import toast from 'react-hot-toast';
function MagicLinkContent() {
const router = useRouter();
const searchParams = useSearchParams();
const { locale: language } = useLanguage();
const { loginWithMagicLink } = useAuth();
const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading');
const [error, setError] = useState('');
const verificationAttempted = useRef(false);
const token = searchParams.get('token');
useEffect(() => {
// Prevent duplicate verification attempts (React StrictMode double-invokes effects)
if (verificationAttempted.current) return;
if (token) {
verificationAttempted.current = true;
verifyToken();
} else {
setStatus('error');
setError(language === 'es' ? 'Token no encontrado' : 'Token not found');
}
}, [token]);
const verifyToken = async () => {
try {
await loginWithMagicLink(token!);
setStatus('success');
toast.success(language === 'es' ? '¡Bienvenido!' : 'Welcome!');
setTimeout(() => {
router.push('/dashboard');
}, 1500);
} catch (err: any) {
setStatus('error');
setError(err.message || (language === 'es' ? 'Enlace inválido o expirado' : 'Invalid or expired link'));
}
};
return (
<div className="section-padding min-h-[70vh] flex items-center">
<div className="container-page">
<div className="max-w-md mx-auto">
<Card className="p-8 text-center">
{status === 'loading' && (
<>
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-secondary-blue mx-auto mb-4"></div>
<h2 className="text-xl font-semibold mb-2">
{language === 'es' ? 'Verificando...' : 'Verifying...'}
</h2>
<p className="text-gray-600">
{language === 'es' ? 'Por favor espera' : 'Please wait'}
</p>
</>
)}
{status === 'success' && (
<>
<div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
<h2 className="text-xl font-semibold mb-2">
{language === 'es' ? '¡Inicio de sesión exitoso!' : 'Login successful!'}
</h2>
<p className="text-gray-600">
{language === 'es' ? 'Redirigiendo...' : 'Redirecting...'}
</p>
</>
)}
{status === 'error' && (
<>
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</div>
<h2 className="text-xl font-semibold mb-2">
{language === 'es' ? 'Error de Verificación' : 'Verification Error'}
</h2>
<p className="text-gray-600 mb-6">{error}</p>
<Button onClick={() => router.push('/login')}>
{language === 'es' ? 'Ir a Iniciar Sesión' : 'Go to Login'}
</Button>
</>
)}
</Card>
</div>
</div>
</div>
);
}
function LoadingFallback() {
return (
<div className="section-padding min-h-[70vh] flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-secondary-blue"></div>
</div>
);
}
export default function MagicLinkPage() {
return (
<Suspense fallback={<LoadingFallback />}>
<MagicLinkContent />
</Suspense>
);
}