tahamajs/IE / Projects /front /src /components /settings /TwoFactorSetupModal.tsx
tahamajs's picture
download
raw
6.88 kB
// src/components/settings/TwoFactorSetupModal.tsx
import { useState, useEffect } from "react";
import { X, Shield, CheckCircle, Loader2, Copy, RefreshCw } from "lucide-react";
import { toast } from "react-toastify";
interface TwoFactorSetupModalProps {
isOpen: boolean;
onClose: () => void;
secret: string;
qrCode: string;
code: string;
setCode: (code: string) => void;
loading: boolean;
error: string;
success: string;
onVerify: () => void;
onDisable?: () => void;
backupCodes?: string[];
onRegenerateBackupCodes?: () => void;
}
export function TwoFactorSetupModal({
isOpen,
onClose,
secret,
qrCode,
code,
setCode,
loading,
error,
success,
onVerify,
onDisable,
backupCodes = [],
onRegenerateBackupCodes,
}: TwoFactorSetupModalProps) {
const [step, setStep] = useState<"intro" | "scan" | "verify" | "done">("intro");
const [showBackupCodes, setShowBackupCodes] = useState(false);
// Reset state when modal opens
useEffect(() => {
if (isOpen) {
if (backupCodes.length > 0) {
setStep("done");
setShowBackupCodes(true);
} else if (secret && qrCode) {
setStep("verify");
} else {
setStep("intro");
}
setCode("");
}
}, [isOpen, secret, qrCode, backupCodes]);
if (!isOpen) return null;
const handleEnable = () => {
setStep("scan");
};
const handleVerify = async () => {
await onVerify();
if (success && backupCodes.length > 0) {
setStep("done");
setShowBackupCodes(true);
}
};
const copyBackupCodes = () => {
navigator.clipboard.writeText(backupCodes.join("\n"));
toast.success("Backup codes copied!");
};
const handleClose = () => {
setStep("intro");
onClose();
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4" onClick={handleClose}>
<div
className="w-full max-w-md rounded-2xl bg-white dark:bg-gray-800 p-6 shadow-xl"
onClick={(e) => e.stopPropagation()}
>
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-bold flex items-center gap-2">
<Shield className="h-6 w-6 text-primary" />
Two‑Factor Authentication
</h2>
<button onClick={handleClose} className="text-gray-500 hover:text-gray-700">
<X className="h-5 w-5" />
</button>
</div>
{step === "intro" && (
<div className="space-y-4">
<p className="text-sm text-gray-600 dark:text-gray-400">
Add an extra layer of security to your account by requiring a code from your authenticator app.
</p>
<button onClick={handleEnable} className="btn-primary w-full">
Get Started
</button>
</div>
)}
{step === "scan" && (
<div className="space-y-4">
<p className="text-sm text-gray-600 dark:text-gray-400">
Scan this QR code with your authenticator app (e.g., Google Authenticator, Authy).
</p>
{qrCode ? (
<div className="flex justify-center">
<img src={qrCode} alt="QR Code" className="h-48 w-48 rounded-xl border" />
</div>
) : (
<div className="flex justify-center py-12">
<Loader2 className="h-8 w-8 animate-spin text-gray-400" />
</div>
)}
{secret && (
<div className="text-center">
<p className="text-xs text-gray-500">Or enter this secret manually:</p>
<code className="rounded bg-gray-100 px-2 py-1 font-mono text-sm dark:bg-gray-700">
{secret}
</code>
</div>
)}
<button onClick={() => setStep("verify")} className="btn-primary mt-2 w-full">
Next
</button>
</div>
)}
{step === "verify" && (
<div className="space-y-4">
<p className="text-sm text-gray-600 dark:text-gray-400">
Enter the 6‑digit code from your authenticator app to verify setup.
</p>
<input
type="text"
maxLength={6}
placeholder="000000"
value={code}
onChange={(e) => setCode(e.target.value.replace(/\D/g, ""))}
className="w-full rounded-xl border p-3 text-center text-2xl tracking-[0.5em] dark:bg-gray-700"
/>
{error && <p className="text-sm text-red-500">{error}</p>}
{success && <p className="text-sm text-green-600">{success}</p>}
<button
onClick={handleVerify}
disabled={loading || code.length !== 6}
className="btn-primary flex w-full items-center justify-center gap-2"
>
{loading && <Loader2 className="h-4 w-4 animate-spin" />}
Verify & Enable
</button>
</div>
)}
{step === "done" && (
<div className="space-y-4">
<div className="flex items-center gap-2 text-green-600">
<CheckCircle className="h-5 w-5" />
<span className="font-semibold">Two‑factor authentication is enabled</span>
</div>
{showBackupCodes && backupCodes.length > 0 && (
<div className="space-y-2">
<p className="text-sm text-gray-600 dark:text-gray-400">
Save these backup codes in a safe place. You can use them to sign in if you lose access to your authenticator app.
</p>
<div className="grid grid-cols-2 gap-1 rounded-xl bg-gray-100 p-3 font-mono text-sm dark:bg-gray-700">
{backupCodes.map((code, idx) => (
<span key={idx} className="text-gray-800 dark:text-gray-200">
{code}
</span>
))}
</div>
<div className="flex gap-2">
<button onClick={copyBackupCodes} className="btn-secondary btn-sm flex items-center gap-1">
<Copy className="h-4 w-4" /> Copy All
</button>
{onRegenerateBackupCodes && (
<button onClick={onRegenerateBackupCodes} className="btn-ghost btn-sm flex items-center gap-1">
<RefreshCw className="h-4 w-4" /> Regenerate
</button>
)}
</div>
</div>
)}
{onDisable && (
<button onClick={onDisable} className="btn-danger mt-4 w-full">
Disable 2FA
</button>
)}
</div>
)}
</div>
</div>
);
}

Xet Storage Details

Size:
6.88 kB
·
Xet hash:
5ef0dce4cb3b4ca926e5949951d393f9743801d4ac04e05e0124f847c5ee4c11

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.