{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "auth-mfa",
  "title": "Auth Mfa",
  "type": "registry:page",
  "files": [
    {
      "path": "packages/registry-angular/blocks/auth-mfa/auth-mfa.component.ts",
      "content": "// Two-step verification surface. 6-digit OTP pin input auto-submits on entry, shows\n// verifying state and an inline error on mismatch, has a 30-second resend cooldown, and\n// swaps to a Verified card with a Continue button on success. Port of the Vue/React block\n// 1:1 — emits `verify` (code), `resend`, and `continue`; `demoCode` controls the value\n// the built-in mock validator accepts.\nimport {\n  ChangeDetectionStrategy,\n  Component,\n  DestroyRef,\n  EventEmitter,\n  Input,\n  Output,\n  inject,\n  signal,\n} from '@angular/core'\nimport { LucideAngularModule, RotateCw, ShieldCheck } from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport {\n  UiPinInputComponent,\n  UiPinInputGroupComponent,\n  UiPinInputSlotComponent,\n} from '@/ui/pin-input/pin-input.component'\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-auth-mfa, [ui-auth-mfa]',\n  standalone: true,\n  // The block renders its own full-page root <div>: keep the host out of layout.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiPinInputComponent,\n    UiPinInputGroupComponent,\n    UiPinInputSlotComponent,\n  ],\n  template: `\n    <div data-slot=\"auth-mfa\" [class]=\"rootClass\">\n      <div ui-card class=\"w-full max-w-sm\">\n        @if (!verified()) {\n          <div ui-card-header class=\"text-center\">\n            <div class=\"bg-primary/10 text-primary mx-auto mb-2 flex size-12 items-center justify-center rounded-full\">\n              <lucide-icon [img]=\"ShieldCheck\" class=\"size-6\" />\n            </div>\n            <h2 ui-card-title class=\"text-2xl\">{{ title }}</h2>\n            <p ui-card-description>{{ description }}</p>\n          </div>\n          <div ui-card-content class=\"space-y-4\">\n            <div class=\"flex justify-center\">\n              <ui-pin-input [value]=\"code()\" (valueChange)=\"onCodeChange($event)\" [disabled]=\"verifying()\">\n                <ui-pin-input-group>\n                  @for (i of slots; track i) {\n                    <ui-pin-input-slot [index]=\"i\" />\n                  }\n                </ui-pin-input-group>\n              </ui-pin-input>\n            </div>\n            @if (verifying()) {\n              <p class=\"text-muted-foreground text-center text-sm\">Verifying…</p>\n            }\n            @if (error()) {\n              <p class=\"text-destructive text-center text-sm\">{{ error() }}</p>\n            }\n            <div class=\"text-center\">\n              @if (resendIn() === 0) {\n                <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)=\"startResendCooldown()\"\n                >\n                  <lucide-icon [img]=\"RotateCw\" class=\"size-3\" />Resend code\n                </button>\n              } @else {\n                <p class=\"text-muted-foreground text-xs\">Resend available in {{ resendIn() }}s</p>\n              }\n            </div>\n          </div>\n          <div ui-card-footer class=\"justify-center\">\n            <p class=\"text-muted-foreground text-xs\">\n              Lost your device?\n              <a [href]=\"recoveryHref\" class=\"text-foreground underline-offset-4 hover:underline\"\n                >Use a recovery code</a\n              >\n            </p>\n          </div>\n        } @else {\n          <div ui-card-content class=\"space-y-4 pt-6 text-center\">\n            <div class=\"bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full\">\n              <lucide-icon [img]=\"ShieldCheck\" class=\"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&apos;re all set. Continuing to your dashboard…</p>\n            </div>\n            <a [href]=\"continueHref\" (click)=\"continue.emit()\"><button ui-button class=\"w-full\">Continue</button></a>\n          </div>\n        }\n      </div>\n    </div>\n  `,\n})\nexport class UiAuthMfaComponent {\n  protected readonly RotateCw = RotateCw\n  protected readonly ShieldCheck = ShieldCheck\n\n  protected readonly slots = [0, 1, 2, 3, 4, 5]\n\n  @Input() title = 'Two-step verification'\n  @Input() description = 'Enter the 6-digit code from your authenticator app.'\n  @Input() continueHref = '/'\n  @Input() recoveryHref = '#'\n  @Input() demoCode = '123456'\n  @Input('class') className?: string\n\n  @Output() readonly verify = new EventEmitter<string>()\n  @Output() readonly resend = new EventEmitter<void>()\n  @Output() readonly continue = new EventEmitter<void>()\n\n  readonly code = signal('')\n  readonly verifying = signal(false)\n  readonly verified = signal(false)\n  readonly error = signal('')\n  readonly resendIn = signal(0)\n\n  private verifyTimer: ReturnType<typeof setTimeout> | undefined\n  private resendTimer: ReturnType<typeof setInterval> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => {\n      clearTimeout(this.verifyTimer)\n      clearInterval(this.resendTimer)\n    })\n  }\n\n  get rootClass(): string {\n    return cn('bg-background flex min-h-svh items-center justify-center p-6', this.className)\n  }\n\n  onCodeChange(value: string): void {\n    this.code.set(value)\n    if (value.length === 6 && !this.verifying()) {\n      this.error.set('')\n      this.verifying.set(true)\n      this.verify.emit(value)\n      clearTimeout(this.verifyTimer)\n      this.verifyTimer = setTimeout(() => {\n        this.verifying.set(false)\n        if (value === this.demoCode) {\n          this.verified.set(true)\n        } else {\n          this.error.set(`Invalid code. Try ${this.demoCode} for the demo.`)\n          this.code.set('')\n        }\n      }, 700)\n    }\n  }\n\n  startResendCooldown(): void {\n    this.resendIn.set(30)\n    this.resend.emit()\n    clearInterval(this.resendTimer)\n    this.resendTimer = setInterval(() => {\n      this.resendIn.update((prev) => {\n        if (prev <= 1) {\n          clearInterval(this.resendTimer)\n          return 0\n        }\n        return prev - 1\n      })\n    }, 1000)\n  }\n}\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/auth-mfa/auth-mfa.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/auth-mfa/index.ts",
      "content": "export { UiAuthMfaComponent } from './auth-mfa.component'\n",
      "type": "registry:page",
      "target": "~/src/app/components/blocks/auth-mfa/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/pin-input.json"
  ],
  "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"
  ]
}