TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1'use client';23import { useActionState, useState } from 'react';4import { mfaUseEmailAction, mfaVerifyAction, resendCodeAction } from '@/lib/auth/actions';5import { idle } from '@/lib/auth/state';6import { Checkbox, CodeInput, Cooldown, Field, FormMessage, SubmitButton, TextInput } from '@/components/account/form';78type Method = 'totp' | 'email_code' | 'recovery';910export function MfaForm({ stage }: { stage: 'totp' | 'email_code' }) {11 const [state, action] = useActionState(mfaVerifyAction, idle);12 const [emailState, emailAction] = useActionState(mfaUseEmailAction, idle);13 const [resend, resendAction] = useActionState(resendCodeAction, idle);14 const [method, setMethod] = useState<Method>(emailState.ok ? 'email_code' : stage);15 const effective: Method = emailState.ok && method === 'totp' ? 'email_code' : method;16 const cooldown = typeof resend.data?.retryAfter === 'number' ? resend.data.retryAfter : resend.ok || emailState.ok ? 45 : 0;17 return (18 <div className="mt-6 space-y-4">19 <form action={action} className="space-y-4" noValidate>20 <input type="hidden" name="method" value={effective} />21 <FormMessage state={state} />22 <FormMessage state={emailState} />23 {effective === 'recovery' ? (24 <Field label="Recovery code" name="code" hint="One of the 10 codes you saved when enabling the authenticator.">25 <TextInput name="code" autoComplete="off" placeholder="ABCDE-FGHJK" className="mono-num uppercase" required autoFocus />26 </Field>27 ) : (28 <CodeInput name="code" label={effective === 'totp' ? 'Authenticator code' : 'E-mailed code'} />29 )}30 <Checkbox name="trust" label="Trust this device for 30 days" description="Skip the second step on this browser." />31 <SubmitButton className="w-full" pendingText="Verifying…">32 Verify33 </SubmitButton>34 </form>35 <div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 text-xs">36 {stage === 'totp' && effective !== 'email_code' ? (37 <form action={emailAction}>38 <SubmitButton variant="ghost" className="h-8 text-xs">39 E-mail me a code instead40 </SubmitButton>41 </form>42 ) : null}43 {effective === 'email_code' ? (44 <form action={resendAction}>45 <FormMessage state={resend} className="mb-2" />46 <Cooldown seconds={cooldown}>47 {(ready, left) => (48 <SubmitButton variant="ghost" className="h-8 text-xs" disabled={!ready}>49 {ready ? 'Send a new code' : `New code in ${left}s`}50 </SubmitButton>51 )}52 </Cooldown>53 </form>54 ) : null}55 {stage === 'totp' ? (56 <button type="button" className="text-muted hover:text-fg" onClick={() => setMethod(effective === 'recovery' ? 'totp' : 'recovery')}>57 {effective === 'recovery' ? 'Use authenticator code' : 'Use a recovery code'}58 </button>59 ) : null}60 </div>61 </div>62 );63}64