{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "auth-mfa",
  "title": "Auth Mfa",
  "type": "registry:page",
  "description": "Two-step verification surface. 6-digit OTP pin input auto-submits on entry, shows verifying state and an inline error on mismatch, has a 30-second resend cooldown, and swaps to a Verified card with a Continue button on success. Emits `verify` (code), `resend`, and `continue`. `demoCode` prop controls the value the built-in mock validator accepts.",
  "categories": [
    "auth"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/blocks/auth-mfa/auth-mfa.ts",
      "content": "import { LitElement, css, html, nothing } from 'lit'\nimport { RotateCw, ShieldCheck } from 'lucide'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\nimport { icon } from '../../lib/icon'\n// Side-effect imports: register the composed elements for standalone use\n// (consumers who install only this block still get working primitives).\nimport '../../components/button/button'\nimport '../../components/card/card'\nimport '../../components/pin-input/pin-input'\nimport type { UipPinInput } from '../../components/pin-input/pin-input'\n\n/**\n * <uip-auth-mfa> — the registry AuthMfa block as a web component.\n *\n * Class strings are React's AuthMfa verbatim. React state (useState /\n * useEffect) becomes @state fields; the code-length effect becomes an\n * `@input` check that auto-submits at 6 digits. `onVerify` (code) /\n * `onResend` / `onContinue` become `verify` / `resend` / `continue`\n * CustomEvents (bubbles, composed). `demoCode` is the value the built-in\n * mock validator accepts.\n */\nexport class UipAuthMfa extends LitElement {\n  static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n  // :host display can't be a utility class (the host has no template of its own).\n  static styles = [tailwind, css`:host { display: block; }`]\n\n  static properties = {\n    heading: {},\n    description: {},\n    continueHref: { attribute: 'continue-href' },\n    recoveryHref: { attribute: 'recovery-href' },\n    demoCode: { attribute: 'demo-code' },\n    code: { state: true },\n    verifying: { state: true },\n    verified: { state: true },\n    error: { state: true },\n    resendIn: { state: true },\n  }\n\n  /** React's `title` (`title` is taken by HTMLElement). */\n  heading = 'Two-step verification'\n  description = 'Enter the 6-digit code from your authenticator app.'\n  continueHref = '/'\n  recoveryHref = '#'\n  demoCode = '123456'\n  private code = ''\n  private verifying = false\n  private verified = false\n  private error = ''\n  private resendIn = 0\n  private verifyTimer?: number\n  private resendTimer?: number\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'auth-mfa')\n  }\n\n  disconnectedCallback() {\n    super.disconnectedCallback()\n    if (this.verifyTimer) window.clearTimeout(this.verifyTimer)\n    if (this.resendTimer) window.clearInterval(this.resendTimer)\n  }\n\n  private onCodeInput(e: Event) {\n    const next = (e.target as UipPinInput).value\n    this.code = next\n    if (next.length !== 6 || this.verifying) return\n    this.error = ''\n    this.verifying = true\n    this.dispatchEvent(new CustomEvent('verify', { detail: next, bubbles: true, composed: true }))\n    this.verifyTimer = window.setTimeout(() => {\n      this.verifying = false\n      if (this.code === this.demoCode) {\n        this.verified = true\n      } else {\n        this.error = `Invalid code. Try ${this.demoCode} for the demo.`\n        this.code = ''\n      }\n    }, 700)\n  }\n\n  private startResendCooldown() {\n    this.resendIn = 30\n    this.dispatchEvent(new CustomEvent('resend', { bubbles: true, composed: true }))\n    if (this.resendTimer) window.clearInterval(this.resendTimer)\n    this.resendTimer = window.setInterval(() => {\n      this.resendIn -= 1\n      if (this.resendIn <= 0) {\n        this.resendIn = 0\n        if (this.resendTimer) window.clearInterval(this.resendTimer)\n      }\n    }, 1000)\n  }\n\n  render() {\n    return html`\n      <div data-slot=\"auth-mfa\" class=${cn('bg-background flex min-h-svh items-center justify-center p-6')}>\n        <uip-card class=\"w-full max-w-sm\">\n          ${!this.verified\n            ? html`\n                <uip-card-header class=\"text-center\">\n                  <div\n                    class=\"bg-primary/10 text-primary mx-auto mb-2 flex size-12 items-center justify-center rounded-full\"\n                  >\n                    ${icon(ShieldCheck, 'shield-check', 'size-6')}\n                  </div>\n                  <uip-card-title class=\"text-2xl\">${this.heading}</uip-card-title>\n                  <uip-card-description>${this.description}</uip-card-description>\n                </uip-card-header>\n                <uip-card-content class=\"space-y-4\">\n                  <div class=\"flex justify-center\">\n                    <uip-pin-input\n                      .value=${this.code}\n                      ?disabled=${this.verifying}\n                      @input=${this.onCodeInput}\n                    ></uip-pin-input>\n                  </div>\n                  ${this.verifying\n                    ? html`<p class=\"text-muted-foreground text-center text-sm\">Verifying…</p>`\n                    : nothing}\n                  ${this.error ? html`<p class=\"text-destructive text-center text-sm\">${this.error}</p>` : nothing}\n                  <div class=\"text-center\">\n                    ${this.resendIn === 0\n                      ? html`<button\n                          type=\"button\"\n                          class=\"text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs underline-offset-4 hover:underline\"\n                          @click=${this.startResendCooldown}\n                        >\n                          ${icon(RotateCw, 'rotate-cw', 'size-3')} Resend code\n                        </button>`\n                      : html`<p class=\"text-muted-foreground text-xs\">Resend available in ${this.resendIn}s</p>`}\n                  </div>\n                </uip-card-content>\n                <uip-card-footer class=\"justify-center\">\n                  <p class=\"text-muted-foreground text-xs\">\n                    Lost your device?\n                    <a href=${this.recoveryHref} class=\"text-foreground underline-offset-4 hover:underline\">\n                      Use a recovery code\n                    </a>\n                  </p>\n                </uip-card-footer>\n              `\n            : html`\n                <uip-card-content class=\"space-y-4 pt-6 text-center\">\n                  <div\n                    class=\"bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full\"\n                  >\n                    ${icon(ShieldCheck, 'shield-check', 'size-6')}\n                  </div>\n                  <div class=\"space-y-1\">\n                    <h3 class=\"text-lg font-semibold\">Verified</h3>\n                    <p class=\"text-muted-foreground text-sm\">You're all set. Continuing to your dashboard…</p>\n                  </div>\n                  <a\n                    href=${this.continueHref}\n                    @click=${() => this.dispatchEvent(new CustomEvent('continue', { bubbles: true, composed: true }))}\n                  >\n                    <uip-button class=\"w-full\">Continue</uip-button>\n                  </a>\n                </uip-card-content>\n              `}\n        </uip-card>\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-auth-mfa') || customElements.define('uip-auth-mfa', UipAuthMfa)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-auth-mfa': UipAuthMfa\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/blocks/auth-mfa/auth-mfa.ts"
    }
  ],
  "dependencies": [
    "lit",
    "lucide"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/button.json",
    "https://uipkge.dev/r/lit/card.json",
    "https://uipkge.dev/r/lit/lit-core.json",
    "https://uipkge.dev/r/lit/pin-input.json"
  ]
}
