"use client"; import { useState } from "react"; import QRCode from "qrcode"; import { toast } from "sonner"; import { useTranslations } from "next-intl"; import { ShieldCheck, ShieldOff, Copy, Check } from "lucide-react"; import { authClient } from "@/lib/auth/client"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from "@/components/ui/dialog"; type Step = "closed" | "password" | "setup" | "disable-password"; export function TwoFactorSettings({ initialEnabled, hasPassword }: { initialEnabled: boolean; hasPassword: boolean }) { const t = useTranslations("settingsForm"); const [enabled, setEnabled] = useState(initialEnabled); const [step, setStep] = useState("closed"); const [password, setPassword] = useState(""); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [qrDataUrl, setQrDataUrl] = useState(null); const [backupCodes, setBackupCodes] = useState([]); const [confirmCode, setConfirmCode] = useState(""); const [copied, setCopied] = useState(false); function closeAndReset() { setStep("closed"); setPassword(""); setError(null); setQrDataUrl(null); setBackupCodes([]); setConfirmCode(""); setCopied(false); } async function startEnable() { setError(null); setBusy(true); try { const { data, error } = await authClient.twoFactor.enable( hasPassword ? { password } : {} ); if (error || !data) { setError(error?.message ?? t("twoFactorEnableFailed")); return; } const dataUrl = await QRCode.toDataURL(data.totpURI, { margin: 1, width: 200 }); setQrDataUrl(dataUrl); setBackupCodes(data.backupCodes); setStep("setup"); } finally { setBusy(false); } } async function confirmEnable() { setError(null); setBusy(true); try { const { error } = await authClient.twoFactor.verifyTotp({ code: confirmCode.trim() }); if (error) { setError(error.message ?? t("twoFactorInvalidCode")); return; } setEnabled(true); toast.success(t("twoFactorEnabled")); closeAndReset(); } finally { setBusy(false); } } async function handleDisable() { setError(null); setBusy(true); try { const { error } = await authClient.twoFactor.disable(hasPassword ? { password } : {}); if (error) { setError(error.message ?? t("twoFactorDisableFailed")); return; } setEnabled(false); toast.success(t("twoFactorDisabled")); closeAndReset(); } finally { setBusy(false); } } async function copyBackupCodes() { try { await navigator.clipboard.writeText(backupCodes.join("\n")); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch { toast.error(t("twoFactorCopyFailed")); } } return (

{t("twoFactorTitle")}

{t("twoFactorDescription")}

{enabled ? ( ) : ( )}
{/* Password confirmation before enabling */} !open && closeAndReset()}> {t("twoFactorConfirmPasswordTitle")} {t("twoFactorConfirmPasswordDescription")}
setPassword(e.target.value)} autoComplete="current-password" autoFocus /> {error &&

{error}

}
{/* QR + backup codes + confirmation code */} !open && closeAndReset()}> {t("twoFactorSetupTitle")} {t("twoFactorSetupDescription")}
{qrDataUrl && (
{/* eslint-disable-next-line @next/next/no-img-element -- a data: URL generated client-side, not an optimizable remote image */} {t("twoFactorQrAlt")}
)}

{t("twoFactorBackupCodesLabel")}

{backupCodes.map((c) => {c})}

{t("twoFactorBackupCodesHint")}

setConfirmCode(e.target.value)} inputMode="numeric" placeholder="000000" autoComplete="one-time-code" /> {error &&

{error}

}
{/* Password confirmation before disabling */} !open && closeAndReset()}> {t("twoFactorDisableTitle")} {t("twoFactorDisableDescription")} {hasPassword && (
setPassword(e.target.value)} autoComplete="current-password" autoFocus />
)} {error &&

{error}

}
); }