import React, { useState, useEffect, useRef } from 'react'; import { Button, Input, Card } from '../../ui'; import { PasswordCriteria, validatePassword, getPasswordErrors } from '../../ui/PasswordCriteria'; import { useAuthActions } from '../../../stores/auth.store'; import { useToast } from '../../../hooks/ui/useToast'; import { useResetPassword } from '../../../api/hooks/auth'; interface PasswordResetFormProps { token?: string; onSuccess?: () => void; onBackClick?: () => void; className?: string; autoFocus?: boolean; mode?: 'request' | 'reset'; } export const PasswordResetForm: React.FC = ({ token, onSuccess, onBackClick, className, autoFocus = true, mode }) => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [errors, setErrors] = useState>({}); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [isEmailSent, setIsEmailSent] = useState(false); const [passwordStrength, setPasswordStrength] = useState(0); const [isTokenValid, setIsTokenValid] = useState(null); const emailInputRef = useRef(null); const passwordInputRef = useRef(null); // Password reset mutation hooks const { mutateAsync: resetPasswordMutation, isPending: isResetting } = useResetPassword(); const isLoading = isResetting; const error = null; const { showToast } = useToast(); const isResetMode = Boolean(token) || mode === 'reset'; // Auto-focus on appropriate field when component mounts useEffect(() => { if (autoFocus) { if (isResetMode && passwordInputRef.current) { passwordInputRef.current.focus(); } else if (!isResetMode && emailInputRef.current) { emailInputRef.current.focus(); } } }, [autoFocus, isResetMode]); // Token validation effect useEffect(() => { if (token) { // Simple token format validation (you might want to add actual API validation) const isValidFormat = /^[A-Za-z0-9+/=_-]+$/.test(token) && token.length > 20; setIsTokenValid(isValidFormat); if (!isValidFormat) { showToast({ type: 'error', title: 'Token inválido', message: 'El enlace de restablecimiento no es válido o ha expirado' }); } } }, [token, showToast]); // Calculate password strength const calculatePasswordStrength = (password: string): number => { let strength = 0; if (password.length >= 8) strength += 25; if (/[a-z]/.test(password)) strength += 25; if (/[A-Z]/.test(password)) strength += 25; if (/[0-9]/.test(password)) strength += 15; if (/[^A-Za-z0-9]/.test(password)) strength += 10; return Math.min(strength, 100); }; const getPasswordStrengthLabel = (strength: number): string => { if (strength < 25) return 'Muy débil'; if (strength < 50) return 'Débil'; if (strength < 75) return 'Media'; return 'Fuerte'; }; const getPasswordStrengthColor = (strength: number): string => { if (strength < 25) return 'bg-color-error'; if (strength < 50) return 'bg-yellow-500'; if (strength < 75) return 'bg-blue-500'; return 'bg-color-success'; }; const validateEmail = (): boolean => { const newErrors: Record = {}; if (!email.trim()) { newErrors.email = 'El email es requerido'; } else if (!/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email)) { newErrors.email = 'Por favor, ingrese un email válido'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const validatePasswords = (): boolean => { const newErrors: Record = {}; if (!password) { newErrors.password = 'La contraseña es requerida'; } else { const passwordErrors = getPasswordErrors(password); if (passwordErrors.length > 0) { newErrors.password = passwordErrors[0]; // Show first error } } if (!confirmPassword) { newErrors.confirmPassword = 'Confirma tu nueva contraseña'; } else if (password !== confirmPassword) { newErrors.confirmPassword = 'Las contraseñas no coinciden'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; // Handle password change to update strength const handlePasswordChange = (e: React.ChangeEvent) => { const value = e.target.value; setPassword(value); setPasswordStrength(calculatePasswordStrength(value)); clearError('password'); }; const handleRequestReset = async (e: React.FormEvent) => { e.preventDefault(); if (!validateEmail()) { // Focus on email field if validation fails if (emailInputRef.current) { emailInputRef.current.focus(); } return; } try { // Note: Password reset request functionality needs to be implemented in backend // For now, show a message that the feature is coming soon setIsEmailSent(true); showToast({ type: 'info', title: 'Función en desarrollo', message: 'La solicitud de restablecimiento de contraseña estará disponible próximamente. Por favor, contacta al administrador.' }); } catch (err) { showToast({ type: 'error', title: 'Error de conexión', message: 'No se pudo conectar con el servidor. Verifica tu conexión a internet.' }); } }; const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); if (!validatePasswords()) { // Focus on password field if validation fails if (passwordInputRef.current) { passwordInputRef.current.focus(); } return; } if (!token || isTokenValid === false) { showToast({ type: 'error', title: 'Token inválido', message: 'El enlace de restablecimiento no es válido. Solicita uno nuevo.' }); return; } try { // Call the reset password API await resetPasswordMutation({ token: token, new_password: password }); showToast({ type: 'success', title: 'Contraseña actualizada', message: '¡Tu contraseña ha sido restablecida exitosamente! Ya puedes iniciar sesión.' }); onSuccess?.(); } catch (err: any) { const errorMessage = err?.response?.data?.detail || err?.message || 'El enlace ha expirado o no es válido. Solicita un nuevo restablecimiento.'; showToast({ type: 'error', title: 'Error al restablecer contraseña', message: errorMessage }); } }; const clearError = (field: string) => { if (errors[field]) { setErrors(prev => ({ ...prev, [field]: undefined })); } }; // Handle Enter key submission const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !isLoading) { if (isResetMode) { handleResetPassword(e as any); } else { handleRequestReset(e as any); } } }; // Success screen for email sent if (isEmailSent && !isResetMode) { return (

¡Email enviado correctamente!

Hemos enviado las instrucciones de restablecimiento a:

{email}

Revisa tu bandeja de entrada (y la carpeta de spam). El enlace es válido por 24 horas.

{onBackClick && ( )}
); } // Show token validation error if (isResetMode && isTokenValid === false) { return (

Enlace No Válido

El enlace de restablecimiento no es válido o ha expirado. Solicita un nuevo enlace para restablecer tu contraseña.

{onBackClick && ( )}
); } return (

{isResetMode ? 'Crear Nueva Contraseña' : 'Restablecer Contraseña'}

{isResetMode ? 'Crea una contraseña segura para acceder a tu cuenta' : 'Te enviaremos un enlace seguro para restablecer tu contraseña' }

{!isResetMode && (
{ setEmail(e.target.value); clearError('email'); }} error={errors.email} disabled={isLoading} autoComplete="email" required aria-describedby={errors.email ? 'email-error' : undefined} leftIcon={ } /> {errors.email && ( )}
)} {isResetMode && ( <>
} rightIcon={ } /> {/* Password Strength Indicator */} {password && (
Seguridad: {getPasswordStrengthLabel(passwordStrength)}
)} {errors.password && ( )}
{ setConfirmPassword(e.target.value); clearError('confirmPassword'); }} error={errors.confirmPassword} disabled={isLoading} autoComplete="new-password" required aria-describedby={errors.confirmPassword ? 'confirm-password-error' : undefined} leftIcon={ } rightIcon={ } /> {errors.confirmPassword && ( )}
)} {error && (
{error}
)}
{isResetMode ? 'Presiona Enter o haz clic para actualizar tu contraseña' : 'Presiona Enter o haz clic para enviar el enlace de restablecimiento' }
{onBackClick && (
)} ); }; export default PasswordResetForm;