import { useState } from "react"; import { Smartphone } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Field, FieldError } from "@/components/ui/field"; import { InputGroup, InputGroupInput } from "@/components/ui/input-group"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import type { ReactNode } from "react"; // ─── Types ──────────────────────────────────────────────────────────────────── export type TwoFAApp = { name: string; icon?: ReactNode; qrNode?: ReactNode; }; export type TwoFASetupFormProps = { title?: string; apps?: TwoFAApp[]; qrCodeNode?: ReactNode; onVerify: (token: string) => Promise; onSetupLater?: () => void; required?: boolean; className?: string; }; const DEFAULT_APPS: TwoFAApp[] = [ { name: "Google Authenticator" }, { name: "Microsoft Authenticator" }, ]; const STEP_META = { 1: { subtitle: "We recommend Google Authenticator or Microsoft Authenticator", defaultTitle: "2FA Setup", }, 2: { title: "Open Auth App and Scan Code", subtitle: "Open your authenticator app and scan the QR code", }, } as const; // ─── AppDownloadStep ───────────────────────────────────────────────────────── function AppDownloadStep({ apps, onNext, onSetupLater, required, }: { apps: TwoFAApp[]; onNext: () => void; onSetupLater?: () => void; required?: boolean; }) { return (
{apps.map((app) => (
{app.qrNode ?? (
Scan
)}
{app.icon ?? ( )} {app.name}

Scan to download

))}

Once downloaded, or if you already have the app installed, click Next.

{!required && onSetupLater && ( )}
); } // ─── VerificationStep ──────────────────────────────────────────────────────── function VerificationStep({ qrCodeNode, onVerify, onBack, onSetupLater, required, }: { qrCodeNode?: ReactNode; onVerify: (token: string) => Promise; onBack: () => void; onSetupLater?: () => void; required?: boolean; }) { const [token, setToken] = useState(""); const [error, setError] = useState(""); const [isLoading, setIsLoading] = useState(false); const handleVerify = async () => { if (!token) { setError("Required"); return; } setError(""); setIsLoading(true); try { await onVerify(token); } catch (err) { setError( err instanceof Error ? err.message : "Invalid code. Please try again." ); } finally { setIsLoading(false); } }; return (
{qrCodeNode ?? (
QR Code
)}

The temporary code will show for authentication. Insert below.

{ setToken(e.target.value); if (error) setError(""); }} onKeyDown={(e) => { if (e.key === "Enter") handleVerify(); }} /> {error && {error}}
{!required && onSetupLater && ( )}
); } // ─── TwoFASetupForm ─────────────────────────────────────────────────────────── export function TwoFASetupForm({ title, apps = DEFAULT_APPS, qrCodeNode, onVerify, onSetupLater, required = true, className, }: TwoFASetupFormProps) { const [step, setStep] = useState<1 | 2>(1); const stepTitle = step === 1 ? title ?? STEP_META[1].defaultTitle : STEP_META[2].title; const stepSubtitle = STEP_META[step].subtitle; return (

{stepTitle}

{stepSubtitle}

{step === 1 ? ( setStep(2)} onSetupLater={onSetupLater} required={required} /> ) : ( setStep(1)} onSetupLater={onSetupLater} required={required} /> )}
); }