tahamajs/IE / Projects /front /src /components /auth /TwoFactorWindow.tsx
tahamajs's picture
download
raw
6.38 kB
import { useState } from "react";
import { useSecuritySettings } from "@/hooks/useSecuritySettings";
import { GlassCard } from "@/components/ui/GlassCard";
import { motion, AnimatePresence } from "framer-motion";
import { Shield, QrCode, CheckCircle, AlertCircle, Copy, Download } from "lucide-react";
import { toast } from "react-toastify";
export function TwoFactorWindow() {
const {
twoFactorSecret,
twoFactorQrCode,
twoFactorCode,
setTwoFactorCode,
verifyTwoFactor,
twoFactorError,
twoFactorSuccess,
twoFactorLoading,
} = useSecuritySettings();
const [step, setStep] = useState<"setup" | "verify">("setup");
const copySecret = () => {
if (twoFactorSecret) {
navigator.clipboard.writeText(twoFactorSecret);
toast.info("Secret copied to clipboard");
}
};
const downloadQr = () => {
if (twoFactorQrCode) {
const link = document.createElement("a");
link.href = twoFactorQrCode;
link.download = "2fa-qr-code.png";
link.click();
}
};
return (
<GlassCard className="p-6 w-full max-w-md mx-auto">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<div className="flex items-center gap-2 mb-4">
<Shield className="w-6 h-6 text-primary" />
<h2 className="text-xl font-bold text-gray-900 dark:text-white">Two‑Factor Authentication</h2>
</div>
<AnimatePresence mode="wait">
{step === "setup" && (
<motion.div
key="setup"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 20 }}
className="space-y-4 text-center"
>
<p className="text-sm text-gray-600 dark:text-gray-400">
Scan this QR code with Google Authenticator or any TOTP app:
</p>
{twoFactorQrCode && (
<div className="bg-white p-2 rounded-xl inline-block mx-auto">
<img src={twoFactorQrCode} alt="QR Code" className="w-48 h-48 mx-auto" />
</div>
)}
<div className="bg-gray-50 dark:bg-gray-700/50 p-3 rounded-lg">
<p className="text-xs text-gray-500 dark:text-gray-400 mb-1">Secret key (if QR fails):</p>
<code className="text-sm font-mono break-all bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">
{twoFactorSecret}
</code>
<button
onClick={copySecret}
className="ml-2 text-primary hover:text-primary/80"
title="Copy secret"
>
<Copy className="w-4 h-4 inline" />
</button>
</div>
<div className="flex gap-2">
<button
onClick={downloadQr}
className="flex-1 flex items-center justify-center gap-2 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 transition"
>
<Download className="w-4 h-4" /> Save QR
</button>
<button
onClick={() => setStep("verify")}
className="flex-1 py-2 bg-primary text-white rounded-lg hover:bg-primary/90 transition"
>
Next
</button>
</div>
</motion.div>
)}
{step === "verify" && (
<motion.form
key="verify"
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
onSubmit={(e) => {
e.preventDefault();
verifyTwoFactor();
}}
className="space-y-4"
>
<p className="text-sm text-gray-600 dark:text-gray-400">
Enter the 6‑digit code from your authenticator app:
</p>
<div className="relative">
<Shield className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
placeholder="000000"
value={twoFactorCode}
onChange={(e) => setTwoFactorCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
className="w-full pl-9 pr-3 py-2 glass-card rounded-lg text-center text-2xl tracking-widest font-mono"
required
autoFocus
maxLength={6}
/>
</div>
{twoFactorError && (
<div className="flex items-center gap-2 p-2 bg-red-50 dark:bg-red-900/20 text-red-600 dark:text-red-400 rounded-lg text-sm">
<AlertCircle className="w-4 h-4" />
<span>{twoFactorError}</span>
</div>
)}
{twoFactorSuccess && (
<div className="flex items-center gap-2 p-2 bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 rounded-lg text-sm">
<CheckCircle className="w-4 h-4" />
<span>{twoFactorSuccess}</span>
</div>
)}
<button
type="submit"
disabled={twoFactorLoading || twoFactorCode.length !== 6}
className="w-full py-2 rounded-lg bg-gradient-to-r from-primary to-secondary text-white font-semibold disabled:opacity-50 flex items-center justify-center gap-2"
>
{twoFactorLoading ? (
<div className="w-5 h-5 border-2 border-white border-t-transparent rounded-full animate-spin" />
) : (
<CheckCircle className="w-5 h-5" />
)}
Enable 2FA
</button>
<button
type="button"
onClick={() => setStep("setup")}
className="w-full text-sm text-gray-500 hover:text-primary transition"
>
← Back to QR code
</button>
</motion.form>
)}
</AnimatePresence>
</motion.div>
</GlassCard>
);
}

Xet Storage Details

Size:
6.38 kB
·
Xet hash:
f76d6a3c6cdaf692a26807f38485b4870c5701b72a55e7a0e16b053f0eb155e6

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