OSAGI

Two-Factor Setup

Component

About

Two-factor setup in three steps: a QR code in a scanner frame with a sweeping line, the key in groups, six code cells (paste, autofill, a shake on a wrong code), then a drawn check and the backup codes. From the Auth & Access Kit → ai.osagi.finance/kits/auth-access

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Two-Factor Setup to your project and fits it to your theme.

Code

How Two-Factor Setup is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import { useCallback, useState } from "react";
import { TwoFactorSetup } from "@/components/ui/two-factor-setup";

const settings = {
  account: "olivia@example.com",
  issuer: "Acme Console",
  /** Any six digits pass, except this one, which the demo refuses. */
  wrongCode: "000000",
};

/** The well-known example key of the TOTP documentation ("Hello!" in base32), not a real one. */
const EXAMPLE_KEY = ["JBSW", "Y3DP", "EHPK", "3PXP"].join("").repeat(2);

const BACKUP_CODES = [
  "7Q2KD-4MZPA",
  "R8VNE-2WQXT",
  "J5HCF-9LBUY",
  "P3TXG-6KDRM",
  "W9AHL-5ZQNE",
  "C4MRT-8YJVK",
];

const CHECK_MS = 700;

const wait = (ms: number) =>
  new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });

/** Scan, enter any six digits (but `wrongCode`), save the backup codes, and start over. */
export default function Demo(props: Partial<typeof settings>) {
  const { account, issuer, wrongCode } = { ...settings, ...props };
  const [round, setRound] = useState(0);
  const [isDone, setDone] = useState(false);
  const verify = useCallback(
    async (code: string) => {
      await wait(CHECK_MS);
      if (code === wrongCode) {
        throw new Error(
          "That code did not match. Wait for the next one and try again."
        );
      }
      return BACKUP_CODES;
    },
    [wrongCode]
  );
  const done = useCallback(() => setDone(true), []);
  const again = useCallback(() => {
    setDone(false);
    setRound((count) => count + 1);
  }, []);

  return (
    <div className="flex min-h-[600px] w-full items-center justify-center bg-background p-6">
      <div className="w-full max-w-xl">
        {isDone ? (
          <div className="flex flex-col items-start gap-3 rounded-2xl border border-border bg-card p-5 text-sm">
            <p role="status">Two-factor sign-in is on for {account}.</p>
            <button
              className="h-11 rounded-lg border border-border px-4 font-medium hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
              onClick={again}
              type="button"
            >
              Start over
            </button>
          </div>
        ) : (
          <TwoFactorSetup
            account={account}
            issuer={issuer}
            key={round}
            onDone={done}
            onVerify={verify}
            secret={EXAMPLE_KEY}
          />
        )}
      </div>
    </div>
  );
}

Dependencies

More by OSAGI

View all
  • AI Orb

    AI Orb

    83
  • KPI Tile

    KPI Tile

    28
  • OTP Input

    OTP Input

    12
  • Reasoning Thread

    Reasoning Thread

    8
  • Assistant Composer

    Assistant Composer

    8
  • Release Notes

    Release Notes

  • Docs TOC

    Docs TOC

  • Docs Search

    Docs Search