import { useState, useEffect, useRef } from 'react';
function OTPVerificationForm({ phone, messageId, onVerified, onResend }) {
const [otp, setOtp] = useState(['', '', '', '', '', '']);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [timeLeft, setTimeLeft] = useState(300); // 5 minutes
const inputRefs = useRef([]);
useEffect(() => {
// Countdown timer
const timer = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
clearInterval(timer);
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(timer);
}, []);
const handleOtpChange = (index, value) => {
if (value.length > 1) return; // Prevent multiple characters
const newOtp = [...otp];
newOtp[index] = value;
setOtp(newOtp);
// Auto-focus next input
if (value && index < 5) {
inputRefs.current[index + 1]?.focus();
}
// Auto-submit when all fields are filled
if (newOtp.every(digit => digit) && newOtp.join('').length === 6) {
handleVerify(newOtp.join(''));
}
};
const handleKeyDown = (index, e) => {
// Handle backspace
if (e.key === 'Backspace' && !otp[index] && index > 0) {
inputRefs.current[index - 1]?.focus();
}
};
const handleVerify = async (otpCode = otp.join('')) => {
if (otpCode.length !== 6) {
setError('Please enter all 6 digits');
return;
}
setLoading(true);
setError('');
try {
const response = await fetch('/api/auth/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'sms',
phone: phone,
token: otpCode
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.msg || 'Invalid verification code');
}
const authData = await response.json();
onVerified?.(authData);
} catch (err) {
setError(err.message);
// Clear OTP on error
setOtp(['', '', '', '', '', '']);
inputRefs.current[0]?.focus();
} finally {
setLoading(false);
}
};
const handleResend = async () => {
try {
await onResend?.();
setTimeLeft(300); // Reset timer
setOtp(['', '', '', '', '', '']);
setError('');
inputRefs.current[0]?.focus();
} catch (err) {
setError('Failed to resend code');
}
};
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
const maskedPhone = phone.replace(/(\+1)(\d{3})(\d{3})(\d{4})/, '$1 (***) ***-$4');
return (
<div className="otp-verification-form">
<h2>Enter Verification Code</h2>
<p>
We sent a 6-digit code to <strong>{maskedPhone}</strong>
</p>
{error && (
<div className="error-message">
{error}
</div>
)}
<div className="otp-inputs">
{otp.map((digit, index) => (
<input
key={index}
ref={el => inputRefs.current[index] = el}
type="text"
inputMode="numeric"
pattern="[0-9]"
maxLength={1}
value={digit}
onChange={(e) => handleOtpChange(index, e.target.value)}
onKeyDown={(e) => handleKeyDown(index, e)}
className="otp-input"
disabled={loading}
autoFocus={index === 0}
/>
))}
</div>
<button
onClick={() => handleVerify()}
disabled={loading || otp.some(digit => !digit)}
className="verify-button"
>
{loading ? 'Verifying...' : 'Verify Code'}
</button>
<div className="resend-section">
{timeLeft > 0 ? (
<p>
Resend code in <strong>{formatTime(timeLeft)}</strong>
</p>
) : (
<button onClick={handleResend} className="resend-button">
Resend Code
</button>
)}
</div>
<p>
<small>
Didn't receive the code? Check your messages or try again.
</small>
</p>
</div>
);
}
export default OTPVerificationForm;