File size: 4,775 Bytes
9a92a42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | import { useState } from 'react';
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 { AlertCircle, CheckCircle2, Loader2, QrCode } from 'lucide-react';
import authApi from '@/api/auth.api';
interface TwoFactorSetupProps {
onComplete?: () => void;
}
export default function TwoFactorSetup({ onComplete }: TwoFactorSetupProps) {
const [step, setStep] = useState<'initial' | 'setup' | 'verify' | 'success'>('initial');
const [qrCode, setQrCode] = useState<string>('');
const [otp, setOtp] = useState<string>('');
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const startSetup = async () => {
setIsLoading(true);
setError(null);
try {
const data = await authApi.setup2FA();
setQrCode(data.qrCode);
setStep('setup');
} catch (err: any) {
setError(err.response?.data?.message || "Failed to start 2FA setup.");
} finally {
setIsLoading(false);
}
};
const verifySetup = async () => {
if (!otp) return;
setIsLoading(true);
setError(null);
try {
await authApi.verify2FA(otp);
setStep('success');
} catch (err: any) {
setError(err.response?.data?.message || "Invalid OTP. Please try again.");
} finally {
setIsLoading(false);
}
};
const handleComplete = () => {
if (onComplete) {
onComplete();
} else {
setStep('initial');
}
};
return (
<Card className="w-full max-w-md mx-auto">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<QrCode className="w-5 h-5" />
Two-Factor Authentication
</CardTitle>
<CardDescription>
Secure your account with Google Authenticator.
</CardDescription>
</CardHeader>
<CardContent>
{step === 'initial' && (
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
Two-factor authentication adds an extra layer of security to your account.
Once enabled, you'll need to enter a code from your Google Authenticator app when logging in.
</p>
<Button onClick={startSetup} disabled={isLoading} className="w-full">
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Setup / Reset 2FA
</Button>
{error && <p className="text-sm text-red-500 mt-2">{error}</p>}
</div>
)}
{step === 'setup' && (
<div className="space-y-6">
<div className="flex flex-col items-center justify-center p-4 border rounded-lg bg-white">
<img src={qrCode} alt="2FA QR Code" className="w-48 h-48" />
</div>
<div className="space-y-2">
<Label>1. Scan QR Code</Label>
<p className="text-xs text-muted-foreground">
Open Google Authenticator and scan the QR code above.
</p>
</div>
<div className="space-y-2">
<Label>2. Enter Code</Label>
<Input
placeholder="Enter 6-digit code"
value={otp}
onChange={(e) => setOtp(e.target.value)}
maxLength={6}
/>
</div>
{error && (
<div className="flex items-center gap-2 text-sm text-red-500">
<AlertCircle className="w-4 h-4" />
{error}
</div>
)}
<Button onClick={verifySetup} disabled={isLoading || otp.length !== 6} className="w-full">
{isLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
Verify & Enable
</Button>
</div>
)}
{step === 'success' && (
<div className="flex flex-col items-center justify-center py-6 space-y-4 text-center">
<div className="w-12 h-12 rounded-full bg-green-100 flex items-center justify-center text-green-600">
<CheckCircle2 className="w-6 h-6" />
</div>
<div className="space-y-1">
<h3 className="font-medium">2FA Enabled Successfully</h3>
<p className="text-sm text-muted-foreground">
Your account is now secured with two-factor authentication.
</p>
</div>
<Button variant="outline" onClick={handleComplete} className="w-full">
Done
</Button>
</div>
)}
</CardContent>
</Card>
);
}
|