/** * Reset Password Page * Handles password reset with token from email link */ import { useState, useEffect } from 'react'; import { Link, useSearchParams, useNavigate } from 'react-router-dom'; import { fetchAPI } from '../../services/api'; import PageMeta from '../../components/common/PageMeta'; import { EyeIcon, EyeCloseIcon, ChevronLeftIcon, CheckCircleIcon, AlertIcon, LockIcon } from '../../icons'; type ResetState = 'form' | 'success' | 'error' | 'expired'; export default function ResetPassword() { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const token = searchParams.get('token'); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const [resetState, setResetState] = useState('form'); // Redirect to forgot-password if no token useEffect(() => { if (!token) { // No token - redirect to forgot password page navigate('/forgot-password', { replace: true }); } }, [token, navigate]); // Password validation checks const passwordChecks = { length: password.length >= 8, uppercase: /[A-Z]/.test(password), lowercase: /[a-z]/.test(password), number: /[0-9]/.test(password), }; const isPasswordStrong = Object.values(passwordChecks).every(Boolean); const passwordsMatch = password === confirmPassword && confirmPassword.length > 0; const isPasswordValid = isPasswordStrong && passwordsMatch; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setError(''); if (!token) { setResetState('expired'); return; } if (!isPasswordValid) { setError('Please ensure your password meets all requirements and both passwords match.'); return; } setLoading(true); try { // Correct API endpoint for password reset confirmation await fetchAPI('/v1/auth/password-reset/confirm/', { method: 'POST', body: JSON.stringify({ token, new_password: password, new_password_confirm: confirmPassword }), }); setResetState('success'); } catch (err: unknown) { const errorResponse = err as { response?: { data?: { message?: string; detail?: string; error?: string } } }; const message = errorResponse?.response?.data?.message || errorResponse?.response?.data?.error || errorResponse?.response?.data?.detail || 'Failed to reset password. Please try again.'; if (message.toLowerCase().includes('expired') || message.toLowerCase().includes('invalid')) { setResetState('expired'); } else { setError(message); } } finally { setLoading(false); } }; // Expired/Invalid Token State if (resetState === 'expired') { return ( <>
IGNY8 IGNY8

Link Expired

This password reset link has expired or is invalid. Please request a new one.

Back to Sign In

Need help?{' '} Contact Support

); } // Success State if (resetState === 'success') { return ( <>
IGNY8 IGNY8

Password Reset!

Your password has been successfully reset. You can now sign in with your new password.

); } // Form State return ( <>
{/* Logo */}
IGNY8 IGNY8
{/* Form Card */}
Back to Sign In

Reset Your Password

Create a strong password for your account.

{error && (

{error}

)}
{/* Password Field */}
setPassword(e.target.value)} className="w-full px-4 py-3 pr-12 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 focus:border-transparent transition-colors" placeholder="Enter new password" required />
{/* Password Requirements - Always visible for clarity */}

Password Requirements:

{[ { check: passwordChecks.length, label: 'At least 8 characters' }, { check: passwordChecks.uppercase, label: 'One uppercase letter' }, { check: passwordChecks.lowercase, label: 'One lowercase letter' }, { check: passwordChecks.number, label: 'One number' }, ].map((req, i) => (
{password.length === 0 ? '○' : req.check ? '✓' : '✕'} {req.label}
))}
{/* Confirm Password Field */}
setConfirmPassword(e.target.value)} className={`w-full px-4 py-3 pr-12 border rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-brand-500 focus:border-transparent transition-colors ${ confirmPassword.length === 0 ? 'border-gray-300 dark:border-gray-600' : passwordsMatch ? 'border-green-400 dark:border-green-500' : 'border-red-400 dark:border-red-500' }`} placeholder="Confirm new password" required />
{confirmPassword.length > 0 && (

{passwordsMatch ? '✓' : '✕'} {passwordsMatch ? 'Passwords match' : 'Passwords do not match'}

)}
{/* Submit Button */}
); }