Spaces:
Sleeping
Sleeping
| // 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> | |
| ); | |
| } |