import EasyModal, { type InnerModalProps } from "ez-modal-react"; import { Field, Form, Formik } from "formik"; import { type ReactNode, useCallback, useEffect, useState } from "react"; import { Alert } from "react-bootstrap"; import Modal from "react-bootstrap/Modal"; import { disable2FA, enable2FA, get2FAStatus, regenerateBackupCodes, start2FASetup, } from "src/api/backend"; import { Button } from "src/components"; import { T } from "src/locale"; import { validateString } from "src/modules/Validations"; type Step = "loading" | "status" | "setup" | "verify" | "backup" | "disable"; const showTwoFactorModal = (id: number | "me") => { EasyModal.show(TwoFactorModal, { id }); }; interface Props extends InnerModalProps { id: number | "me"; } const TwoFactorModal = EasyModal.create(({ id, visible, remove }: Props) => { const [error, setError] = useState(null); const [step, setStep] = useState("loading"); const [isEnabled, setIsEnabled] = useState(false); const [backupCodesRemaining, setBackupCodesRemaining] = useState(0); const [setupData, setSetupData] = useState<{ secret: string; otpauthUrl: string } | null>(null); const [backupCodes, setBackupCodes] = useState([]); const [isSubmitting, setIsSubmitting] = useState(false); const loadStatus = useCallback(async () => { try { const status = await get2FAStatus(id); setIsEnabled(status.enabled); setBackupCodesRemaining(status.backupCodesRemaining); setStep("status"); } catch (err: any) { setError(err.message || "Failed to load 2FA status"); setStep("status"); } }, [id]); useEffect(() => { loadStatus(); }, [loadStatus]); const handleStartSetup = async () => { setError(null); setIsSubmitting(true); try { const data = await start2FASetup(id); setSetupData(data); setStep("setup"); } catch (err: any) { setError(err.message || "Failed to start 2FA setup"); } setIsSubmitting(false); }; const handleVerify = async (values: { code: string }) => { setError(null); setIsSubmitting(true); try { const result = await enable2FA(id, values.code); setBackupCodes(result.backupCodes); setStep("backup"); } catch (err: any) { setError(err.message || "Failed to enable 2FA"); } setIsSubmitting(false); }; const handleDisable = async (values: { code: string }) => { setError(null); setIsSubmitting(true); try { await disable2FA(id, values.code); setIsEnabled(false); setStep("status"); } catch (err: any) { setError(err.message || "Failed to disable 2FA"); } setIsSubmitting(false); }; const handleRegenerateBackup = async (values: { code: string }) => { setError(null); setIsSubmitting(true); try { const result = await regenerateBackupCodes(id, values.code); setBackupCodes(result.backupCodes); setStep("backup"); } catch (err: any) { setError(err.message || "Failed to regenerate backup codes"); } setIsSubmitting(false); }; const handleBackupDone = () => { setIsEnabled(true); setBackupCodes([]); loadStatus(); }; const renderContent = () => { if (step === "loading") { return (
Loading...
); } if (step === "status") { return (
{isEnabled ? : }
{isEnabled && (

)}
{!isEnabled ? ( ) : (
)}
); } if (step === "setup" && setupData) { return (

QR Code
{() => (
{({ field, form }: any) => ( )}
)}
); } if (step === "backup") { return (
{backupCodes.map((code, index) => (
{code}
))}
); } if (step === "disable") { return (
{() => (
{({ field, form }: any) => ( )}
)}
); } if (step === "verify") { return (

{() => (
{({ field, form }: any) => ( )}
)}
); } return null; }; return ( setError(null)} dismissible> {error} {renderContent()} ); }); export { showTwoFactorModal };