import { useState } from "react"; import { beginTotpSetup, confirmTotpSetup, disableTwoFactor, newRecoveryCodes } from "~/fns/account-security"; import { useAction } from "~/hooks/use-action"; import { Button } from "~/components/ui/Button"; import { ErrorBanner, TextField } from "~/components/ui/Field"; import { Badge, Panel } from "~/components/ui/Primitives"; import { RecoveryCodes } from "./RecoveryCodes"; 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); } }} >

Add this key to your authenticator app (or open the link on your phone), then enter the 6-digit code.

{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}
)}
); }