import { useState } from 'react'; import { QRCodeSVG } from 'qrcode.react'; import { errorMessage } from '../../api/client'; import { Alert, Badge, Button, Card, FormField, Input, Modal, Spinner, useToast, } from '@cameleer/design-system'; import { useAccountMfaStatus, useAccountMfaSetup, useAccountMfaVerify, useAccountBackupCodes, useAccountMfaRemove, } from '../../api/account-hooks'; import styles from '../../styles/platform.module.css'; export function MfaSection({ bare }: { bare?: boolean }) { const { toast } = useToast(); const { data: mfaStatus, isLoading: statusLoading } = useAccountMfaStatus(); const setup = useAccountMfaSetup(); const verify = useAccountMfaVerify(); const backupCodes = useAccountBackupCodes(); const remove = useAccountMfaRemove(); const [setupData, setSetupData] = useState<{ secret: string; secretQrCode: string } | null>(null); const [verifyCode, setVerifyCode] = useState(''); const [codes, setCodes] = useState(null); const [codesSaved, setCodesSaved] = useState(false); const [confirmRemove, setConfirmRemove] = useState(false); const modalOpen = !!setupData || !!codes; async function handleStartSetup() { try { const data = await setup.mutateAsync(); setSetupData(data); setVerifyCode(''); } catch (err) { toast({ title: 'Failed to start MFA setup', description: errorMessage(err), variant: 'error' }); } } async function handleVerify(e: React.FormEvent) { e.preventDefault(); if (!setupData) return; try { const result = await verify.mutateAsync({ secret: setupData.secret, code: verifyCode }); if (result.verified) { const bc = await backupCodes.mutateAsync(); setCodes(bc.codes); setSetupData(null); setVerifyCode(''); setCodesSaved(false); toast({ title: 'MFA enabled successfully', variant: 'success' }); } else { toast({ title: 'Invalid code. Please try again.', variant: 'error' }); } } catch (err) { toast({ title: 'Verification failed', description: errorMessage(err), variant: 'error' }); } } async function handleRegenerateCodes() { try { const bc = await backupCodes.mutateAsync(); setCodes(bc.codes); setCodesSaved(false); toast({ title: 'Backup codes regenerated', variant: 'success' }); } catch (err) { toast({ title: 'Failed to regenerate backup codes', description: errorMessage(err), variant: 'error' }); } } async function handleRemove() { try { await remove.mutateAsync(); setConfirmRemove(false); setCodes(null); setSetupData(null); toast({ title: 'MFA removed', variant: 'success' }); } catch (err) { toast({ title: 'Failed to remove MFA', description: errorMessage(err), variant: 'error' }); } } function handleModalClose() { // During backup codes step, only allow close after confirming saved if (codes) return; // During setup, safe to cancel — TOTP is not registered until verified setSetupData(null); setVerifyCode(''); } function handleBackupCodesDone() { setCodes(null); setCodesSaved(false); } function handleCopyAll() { if (!codes) return; navigator.clipboard.writeText(codes.join('\n')); toast({ title: 'Backup codes copied to clipboard', variant: 'success' }); } function handleDownload() { if (!codes) return; const blob = new Blob([codes.join('\n')], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'cameleer-mfa-backup-codes.txt'; a.click(); URL.revokeObjectURL(url); } if (statusLoading) { const spinner =
; return bare ? spinner : {spinner}; } const content = ( <>
Status: {mfaStatus?.enrolled ? ( ) : ( )}
{mfaStatus?.enrolled ? ( <>

Your account is protected with a TOTP authenticator app.

{confirmRemove ? (
) : ( )}
) : ( <>

Add an extra layer of security to your account by enabling multi-factor authentication with an authenticator app.

)} ); return ( <> {bare ? content : {content}} {codes ? ( <> These codes can be used to sign in if you lose access to your authenticator app. Each code can only be used once. Store them in a safe place.
{codes.map((code) => ( {code} ))}
) : setupData ? ( <>

Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password, etc.), then enter the 6-digit code below.

{setupData.secret}
setVerifyCode(e.target.value.replace(/\D/g, '').slice(0, 6))} placeholder="Enter 6-digit code" required autoComplete="one-time-code" />
) : null}
); }