aboalaa147's picture
Initial deployment
eb6a2f9
Raw
History Blame Contribute Delete
4.82 kB
// src/pages/ForgotPassword/VerifyOtp.tsx
import { useState, useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { Button } from '../../components/ui/button';
import { Input } from '../../components/ui/input';
import { Label } from '../../components/ui/label';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../components/ui/card';
import { KeyRound, ArrowLeft, RefreshCw } from 'lucide-react';
import { forgotPasswordApi } from '../../lib/api/forgotPasswordApi';
import { toast } from 'sonner';
export function VerifyOtp() {
const [otp, setOtp] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [resendLoading, setResendLoading] = useState(false);
const [countdown, setCountdown] = useState(0);
const navigate = useNavigate();
const email = sessionStorage.getItem('resetEmail');
useEffect(() => {
if (!email) {
navigate('/forgot-password');
}
}, [email, navigate]);
useEffect(() => {
let timer: NodeJS.Timeout;
if (countdown > 0) {
timer = setTimeout(() => setCountdown(countdown - 1), 1000);
}
return () => clearTimeout(timer);
}, [countdown]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!email) return;
setLoading(true);
setError(null);
try {
await forgotPasswordApi.verifyOtp({ email, otp });
sessionStorage.setItem('resetOtp', otp);
navigate('/reset-password');
} catch (err) {
console.error('OTP verification error:', err);
setError(err instanceof Error ? err.message : 'Invalid or expired OTP');
} finally {
setLoading(false);
}
};
const handleResendOtp = async () => {
if (!email || countdown > 0) return;
setResendLoading(true);
setError(null);
try {
await forgotPasswordApi.forgotPassword({ email });
setCountdown(60); // 60 seconds cooldown
toast.sucess('A new verification code has been sent to your email');
} catch (err) {
console.error('Resend OTP error:', err);
setError(err instanceof Error ? err.message : 'Failed to resend OTP');
} finally {
setResendLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-50 p-4">
<Card className="w-full max-w-md">
<CardHeader>
<Link to="/forgot-password" className="text-emerald-600 hover:text-emerald-700 mb-2 inline-flex items-center gap-1">
<ArrowLeft className="h-4 w-4" />
Back
</Link>
<CardTitle className="text-center">Verify Your Identity</CardTitle>
<CardDescription className="text-center">
Enter the 6-digit verification code sent to {email}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="otp">Verification Code</Label>
<div className="relative">
<KeyRound className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
<Input
id="otp"
type="text"
maxLength={6}
value={otp}
onChange={(e) => setOtp(e.target.value.replace(/\D/g, ''))}
placeholder="Enter 6-digit code"
className="pl-10 text-center text-2xl tracking-widest"
required
/>
</div>
</div>
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
<p className="text-red-600 text-sm text-center">{error}</p>
</div>
)}
<Button
type="submit"
className="w-full bg-emerald-600"
disabled={loading || otp.length !== 6}
>
{loading ? 'Verifying...' : 'Verify Code'}
</Button>
</form>
<div className="text-center">
<button
onClick={handleResendOtp}
disabled={resendLoading || countdown > 0}
className="text-emerald-600 hover:text-emerald-700 text-sm disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-1"
>
<RefreshCw className={`h-3 w-3 ${resendLoading ? 'animate-spin' : ''}`} />
{countdown > 0
? `Resend code in ${countdown}s`
: 'Resend verification code'
}
</button>
</div>
</CardContent>
</Card>
</div>
);
}