import React, { useState, useRef, useEffect } from "react"; import { authAPI } from "../services/api"; import { useAuth } from "../contexts/AuthContext"; interface VerificationCodeModalProps { show: boolean; onHide: () => void; email: string; onVerified: () => void; } const VerificationCodeModal: React.FC = ({ show, onHide, email, onVerified, }) => { const [code, setCode] = useState(["", "", "", "", "", ""]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [resending, setResending] = useState(false); const [resendCooldown, setResendCooldown] = useState(0); const [resendSuccess, setResendSuccess] = useState(false); const inputRefs = useRef<(HTMLInputElement | null)[]>([]); const { checkAuth } = useAuth(); // Handle resend cooldown timer useEffect(() => { if (resendCooldown > 0) { const timer = setTimeout( () => setResendCooldown(resendCooldown - 1), 1000 ); return () => clearTimeout(timer); } }, [resendCooldown]); // Focus first input on mount useEffect(() => { if (show && inputRefs.current[0]) { inputRefs.current[0].focus(); } }, [show]); const handleInputChange = (index: number, value: string) => { // Only allow digits if (value && !/^\d$/.test(value)) return; const newCode = [...code]; newCode[index] = value; setCode(newCode); setError(""); setResendSuccess(false); // Auto-advance to next input if (value && index < 5) { inputRefs.current[index + 1]?.focus(); } // Auto-submit when all 6 digits entered if (value && index === 5 && newCode.every((d) => d !== "")) { handleVerify(newCode.join("")); } }; const handleKeyDown = (index: number, e: React.KeyboardEvent) => { if (e.key === "Backspace" && !code[index] && index > 0) { inputRefs.current[index - 1]?.focus(); } }; const handlePaste = (e: React.ClipboardEvent) => { e.preventDefault(); const pastedData = e.clipboardData .getData("text") .replace(/\D/g, "") .slice(0, 6); if (pastedData.length === 6) { const newCode = pastedData.split(""); setCode(newCode); handleVerify(pastedData); } }; const handleVerify = async (verificationCode: string) => { setLoading(true); setError(""); setResendSuccess(false); try { await authAPI.verifyEmail(verificationCode); await checkAuth(); // Refresh user data onVerified(); onHide(); } catch (err: any) { const errorData = err.response?.data; if (errorData?.code === "TOO_MANY_ATTEMPTS") { setError("Too many attempts. Please request a new code below."); } else if (errorData?.code === "VERIFICATION_EXPIRED") { setError("Code expired. Please request a new code below."); } else if (errorData?.code === "VERIFICATION_INVALID") { setError( "That code didn't match. Please try again or request a new code below." ); } else { setError(errorData?.error || "Verification failed. Please try again."); } // Clear code on error setCode(["", "", "", "", "", ""]); inputRefs.current[0]?.focus(); } finally { setLoading(false); } }; const handleResend = async () => { setResending(true); setError(""); setResendSuccess(false); try { await authAPI.resendVerification(); setResendCooldown(60); // 60 second cooldown setResendSuccess(true); setCode(["", "", "", "", "", ""]); inputRefs.current[0]?.focus(); } catch (err: any) { if (err.response?.status === 429) { setError("Please wait before requesting another code."); } else { setError("Failed to resend code. Please try again."); } } finally { setResending(false); } }; if (!show) return null; return (

Verify Your Email

We sent a 6-digit code to {email}

{error && (
{error}
)} {resendSuccess && (
New code sent! Check your email.
)} {/* 6-digit code input */}
{code.map((digit, index) => ( { inputRefs.current[index] = el; }} type="text" inputMode="numeric" maxLength={1} value={digit} onChange={(e) => handleInputChange(index, e.target.value)} onKeyDown={(e) => handleKeyDown(index, e)} className="form-control text-center" style={{ width: "50px", height: "60px", fontSize: "24px", fontWeight: "bold", }} disabled={loading} /> ))}

Didn't receive the code?

Check your spam folder if you don't see the email.

); }; export default VerificationCodeModal;