import React, { useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card.tsx"; import { Button } from "@/components/ui/button.tsx"; import { Input } from "@/components/ui/input.tsx"; import { Label } from "@/components/ui/label.tsx"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert.tsx"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs.tsx"; import { Shield, Copy, Download, AlertCircle, CheckCircle2 } from "lucide-react"; import { setupTOTP, enableTOTP, disableTOTP, generateBackupCodes } from "@/ui/main-axios.ts"; import { toast } from "sonner"; import { useTranslation } from 'react-i18next'; interface TOTPSetupProps { isEnabled: boolean; onStatusChange?: (enabled: boolean) => void; } export function TOTPSetup({ isEnabled: initialEnabled, onStatusChange }: TOTPSetupProps) { const {t} = useTranslation(); const [isEnabled, setIsEnabled] = useState(initialEnabled); const [isSettingUp, setIsSettingUp] = useState(false); const [setupStep, setSetupStep] = useState<"init" | "qr" | "verify" | "backup">("init"); const [qrCode, setQrCode] = useState(""); const [secret, setSecret] = useState(""); const [verificationCode, setVerificationCode] = useState(""); const [backupCodes, setBackupCodes] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [password, setPassword] = useState(""); const [disableCode, setDisableCode] = useState(""); const handleSetupStart = async () => { setError(null); setLoading(true); try { const response = await setupTOTP(); setQrCode(response.qr_code); setSecret(response.secret); setSetupStep("qr"); setIsSettingUp(true); } catch (err: any) { setError(err?.response?.data?.error || "Failed to start TOTP setup"); } finally { setLoading(false); } }; const handleVerifyCode = async () => { if (verificationCode.length !== 6) { setError("Please enter a 6-digit code"); return; } setError(null); setLoading(true); try { const response = await enableTOTP(verificationCode); setBackupCodes(response.backup_codes); setSetupStep("backup"); toast.success(t('auth.twoFactorEnabledSuccess')); } catch (err: any) { setError(err?.response?.data?.error || "Invalid verification code"); } finally { setLoading(false); } }; const handleDisable = async () => { setError(null); setLoading(true); try { await disableTOTP(password || undefined, disableCode || undefined); setIsEnabled(false); setIsSettingUp(false); setSetupStep("init"); setPassword(""); setDisableCode(""); onStatusChange?.(false); toast.success(t('auth.twoFactorDisabled')); } catch (err: any) { setError(err?.response?.data?.error || "Failed to disable TOTP"); } finally { setLoading(false); } }; const handleGenerateNewBackupCodes = async () => { setError(null); setLoading(true); try { const response = await generateBackupCodes(password || undefined, disableCode || undefined); setBackupCodes(response.backup_codes); toast.success(t('auth.newBackupCodesGenerated')); } catch (err: any) { setError(err?.response?.data?.error || "Failed to generate backup codes"); } finally { setLoading(false); } }; const copyToClipboard = (text: string, label: string) => { navigator.clipboard.writeText(text); toast.success(t('messages.copiedToClipboard', {item: label})); }; const downloadBackupCodes = () => { const content = `Termix Two-Factor Authentication Backup Codes\n` + `Generated: ${new Date().toISOString()}\n\n` + `Keep these codes in a safe place. Each code can only be used once.\n\n` + backupCodes.map((code, i) => `${i + 1}. ${code}`).join('\n'); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'termix-backup-codes.txt'; a.click(); URL.revokeObjectURL(url); toast.success(t('auth.backupCodesDownloaded')); }; const handleComplete = () => { setIsEnabled(true); setIsSettingUp(false); setSetupStep("init"); setVerificationCode(""); onStatusChange?.(true); }; if (isEnabled && !isSettingUp) { return ( {t('auth.twoFactorTitle')} {t('auth.twoFactorProtected')} {t('common.enabled')} {t('auth.twoFactorActive')} {t('auth.disable2FA')} {t('auth.backupCodes')} {t('common.warning')} {t('auth.disableTwoFactorWarning')}
setPassword(e.target.value)} />

{t('auth.or')}

setDisableCode(e.target.value.replace(/\D/g, ''))} />

{t('auth.generateNewBackupCodesText')}

setPassword(e.target.value)} />

{t('auth.or')}

setDisableCode(e.target.value.replace(/\D/g, ''))} />
{backupCodes.length > 0 && (
{backupCodes.map((code, i) => (
{code}
))}
)}
{error && ( {t('common.error')} {error} )}
); } if (setupStep === "qr") { return ( {t('auth.setupTwoFactorTitle')} {t('auth.step1ScanQR')}
TOTP QR Code

{t('auth.cannotScanQRText')}

); } if (setupStep === "verify") { return ( {t('auth.verifyAuthenticator')} {t('auth.step2EnterCode')}
setVerificationCode(e.target.value.replace(/\D/g, ''))} className="text-center text-2xl tracking-widest font-mono" />
{error && ( {t('common.error')} {error} )}
); } if (setupStep === "backup") { return ( {t('auth.saveBackupCodesTitle')} {t('auth.step3StoreCodesSecurely')} {t('common.important')} {t('auth.importantBackupCodesText')}
{backupCodes.map((code, i) => (
{i + 1}. {code}
))}
); } return ( {t('auth.twoFactorTitle')} {t('auth.addExtraSecurityLayer')} {t('common.notEnabled')} {t('auth.notEnabledText')} {error && ( Error {error} )} ); }