SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
3.0 KB · 64 lines tsx
Raw Blame History
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