import { useState } from "react"; import { beginTotpSetup, confirmTotpSetup, disableTwoFactor, newRecoveryCodes } from "~/fns/account-security"; import { useAction } from "~/hooks/use-action"; import { Button } from "~/ui/Button"; import { TextField } from "~/ui/Field"; import { ErrorBanner } from "~/ui/Callout"; import { Badge } from "~/ui/Badge"; import { Panel } from "~/ui/Panel"; import { RecoveryCodes } from "./RecoveryCodes"; import { ExternalLink } from "~/ui/TextLink"; import { QrCode } from "~/ui/QrCode"; type Props = { enabled: boolean; recoveryCodesLeft: number; hasPassword: boolean }; function field(form: HTMLFormElement, name: string): string { return String(new FormData(form).get(name) ?? ""); } export function TwoFactorPanel({ enabled, recoveryCodesLeft, hasPassword }: Props) { const [setup, setSetup] = useState<{ secret: string; uri: string } | null>(null); const [codes, setCodes] = useState(null); const begin = useAction(beginTotpSetup, { invalidate: false }); const confirm = useAction(confirmTotpSetup); const disable = useAction(disableTwoFactor); const regenerate = useAction(newRecoveryCodes); const error = begin.error ?? confirm.error ?? disable.error ?? regenerate.error; const passwordField = hasPassword ? ( ) : null; return ( Authenticator app {enabled && Enabled} } >
{codes && } {enabled ? ( <>

Sign-ins ask for a code from your authenticator app. {recoveryCodesLeft} recovery codes left.

{ event.preventDefault(); const submitter = event.nativeEvent.submitter; const action = submitter instanceof HTMLButtonElement ? submitter.value : null; const password = field(event.currentTarget, "password"); if (action === "disable") { if (await disable.run({ data: { password } })) setCodes(null); } else { const result = await regenerate.run({ data: { password } }); if (result) setCodes(result.recoveryCodes); } }} > {passwordField}
) : setup ? (
{ event.preventDefault(); const result = await confirm.run({ data: { code: field(event.currentTarget, "code") } }); if (result) { setCodes(result.recoveryCodes); setSetup(null); } }} >

Scan this QR code with your authenticator app, then enter the 6-digit code.

Advanced: can't scan the code?

Enter this setup key manually, or open the link on your phone.

{setup.secret} {setup.uri}
) : (
{ event.preventDefault(); const result = await begin.run({ data: { password: field(event.currentTarget, "password") } }); if (result) setSetup(result); }} >

Require a time-based code from an authenticator app when you sign in.

{passwordField}
)}
); }