{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-recovery-codes",
  "title": "Auth Recovery Codes",
  "description": "Display and manage two-factor authentication recovery codes.",
  "registryDependencies": [
    "alert",
    "alert-dialog",
    "button",
    "card"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-recovery-codes.tsx",
      "content": "\"use client\";\n\nimport {\n  AlertTriangle,\n  CheckCircle2,\n  Copy,\n  Download,\n  Eye,\n  EyeOff,\n  Loader2,\n  RefreshCw,\n  Shield,\n} from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Alert,\n  AlertDescription,\n  AlertTitle,\n} from \"@/registry/new-york/ui/alert\";\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogDescription,\n  AlertDialogFooter,\n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogTrigger,\n} from \"@/registry/new-york/ui/alert-dialog\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\n\nexport interface AuthRecoveryCodesProps {\n  codes?: string[];\n  onGenerate?: () => void;\n  onRegenerate?: () => void;\n  className?: string;\n  isLoading?: boolean;\n  errors?: {\n    general?: string;\n  };\n}\n\nfunction downloadCodes(codes: string[]): void {\n  if (codes.length === 0) return;\n  const content = codes.join(\"\\n\");\n  const blob = new Blob([content], { type: \"text/plain\" });\n  const url = URL.createObjectURL(blob);\n  const a = document.createElement(\"a\");\n  a.href = url;\n  a.download = \"recovery-codes.txt\";\n  document.body.appendChild(a);\n  a.click();\n  document.body.removeChild(a);\n  URL.revokeObjectURL(url);\n}\n\ninterface ErrorAlertProps {\n  message: string;\n}\n\nfunction ErrorAlert({ message }: ErrorAlertProps) {\n  return (\n    <div\n      aria-live=\"polite\"\n      className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm\"\n      role=\"alert\"\n    >\n      {message}\n    </div>\n  );\n}\n\ninterface EmptyStateProps {\n  className?: string;\n}\n\nfunction EmptyState({ className }: EmptyStateProps) {\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle className=\"flex items-center gap-2\">\n          Recovery codes\n        </CardTitle>\n        <CardDescription>\n          Backup codes for two-factor authentication\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n          <p className=\"text-muted-foreground text-sm\">\n            No recovery codes available. Generate codes to get started.\n          </p>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\ninterface EmptyStateWithGenerateProps {\n  isLoading: boolean;\n  onGenerate?: () => void;\n}\n\nfunction EmptyStateWithGenerate({\n  isLoading,\n  onGenerate,\n}: EmptyStateWithGenerateProps) {\n  return (\n    <div className=\"flex flex-col items-center justify-center gap-4 rounded-lg border bg-muted/50 p-8 text-center\">\n      <div className=\"flex size-12 items-center justify-center rounded-full bg-muted\">\n        <Shield aria-hidden=\"true\" className=\"size-6 text-muted-foreground\" />\n      </div>\n      <div className=\"flex flex-col gap-2\">\n        <p className=\"font-medium text-sm\">No recovery codes generated</p>\n        <p className=\"text-muted-foreground text-xs\">\n          Generate recovery codes to use as a backup for two-factor\n          authentication\n        </p>\n      </div>\n      {onGenerate && (\n        <Button\n          aria-busy={isLoading}\n          className=\"min-h-[44px] touch-manipulation\"\n          data-loading={isLoading}\n          disabled={isLoading}\n          onClick={onGenerate}\n          type=\"button\"\n        >\n          {isLoading ? (\n            <>\n              <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n              Generating…\n            </>\n          ) : (\n            <>\n              <RefreshCw aria-hidden=\"true\" className=\"size-4\" />\n              Generate recovery codes\n            </>\n          )}\n        </Button>\n      )}\n    </div>\n  );\n}\n\ninterface RecoveryCodeItemProps {\n  code: string;\n  copied: boolean;\n  index: number;\n  onCopy: (code: string, index: number) => void;\n  showCode: boolean;\n}\n\nfunction RecoveryCodeItem({\n  code,\n  copied,\n  index,\n  onCopy,\n  showCode,\n}: RecoveryCodeItemProps) {\n  if (showCode) {\n    return (\n      <div className=\"flex items-center justify-between gap-2 rounded-md border bg-muted/50 py-0.5 pl-2 font-mono text-sm\">\n        <span className=\"flex-1\">{code}</span>\n        <Button\n          aria-label={`Copy recovery code ${index + 1}`}\n          className=\"min-h-[10px] min-w-[10px] touch-manipulation\"\n          onClick={() => onCopy(code, index)}\n          size=\"icon-sm\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          {copied ? (\n            <CheckCircle2\n              aria-hidden=\"true\"\n              className=\"size-3.5 text-primary\"\n            />\n          ) : (\n            <Copy aria-hidden=\"true\" className=\"size-3.5\" />\n          )}\n        </Button>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"flex items-center justify-between gap-2 rounded-md border bg-muted/50 p-2\">\n      <span className=\"font-mono text-sm\">\n        {Array.from({ length: 8 })\n          .map(() => \"•\")\n          .join(\"\")}\n      </span>\n      <Copy aria-hidden=\"true\" className=\"size-3.5 text-muted-foreground\" />\n    </div>\n  );\n}\n\ninterface RecoveryCodesListProps {\n  codes: string[];\n  copiedIndex: number | null;\n  onCopyCode: (code: string, index: number) => void;\n  showCodes: boolean;\n}\n\nfunction RecoveryCodesList({\n  codes,\n  copiedIndex,\n  onCopyCode,\n  showCodes,\n}: RecoveryCodesListProps) {\n  return (\n    <div className=\"grid grid-cols-1 gap-2 rounded-lg border bg-background p-4 sm:grid-cols-2\">\n      {codes.map((code, index) => (\n        <RecoveryCodeItem\n          code={code}\n          copied={copiedIndex === index}\n          index={index}\n          key={index}\n          onCopy={onCopyCode}\n          showCode={showCodes}\n        />\n      ))}\n    </div>\n  );\n}\n\ninterface RecoveryCodesActionsProps {\n  codes: string[];\n  isRegenerating: boolean;\n  onCopyAll: () => void;\n  onDownload: () => void;\n  onRegenerate?: () => void;\n}\n\nfunction RecoveryCodesActions({\n  codes,\n  isRegenerating,\n  onCopyAll,\n  onDownload,\n  onRegenerate,\n}: RecoveryCodesActionsProps) {\n  return (\n    <div className=\"flex flex-col gap-2 sm:flex-row sm:flex-wrap\">\n      <Button\n        className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n        onClick={onCopyAll}\n        type=\"button\"\n        variant=\"outline\"\n      >\n        <Copy aria-hidden=\"true\" className=\"size-4\" />\n        Copy all codes\n      </Button>\n      <Button\n        className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n        onClick={onDownload}\n        type=\"button\"\n        variant=\"outline\"\n      >\n        <Download aria-hidden=\"true\" className=\"size-4\" />\n        Download codes\n      </Button>\n      {onRegenerate && (\n        <RegenerateDialog\n          isRegenerating={isRegenerating}\n          onRegenerate={onRegenerate}\n        />\n      )}\n    </div>\n  );\n}\n\ninterface RegenerateDialogProps {\n  isRegenerating: boolean;\n  onRegenerate: () => void;\n}\n\nfunction RegenerateDialog({\n  isRegenerating,\n  onRegenerate,\n}: RegenerateDialogProps) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>\n        <Button\n          aria-busy={isRegenerating}\n          className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n          data-loading={isRegenerating}\n          disabled={isRegenerating}\n          type=\"button\"\n          variant=\"outline\"\n        >\n          {isRegenerating ? (\n            <>\n              <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n              Regenerating…\n            </>\n          ) : (\n            <>\n              <RefreshCw aria-hidden=\"true\" className=\"size-4\" />\n              Regenerate codes\n            </>\n          )}\n        </Button>\n      </AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>Regenerate recovery codes?</AlertDialogTitle>\n          <AlertDialogDescription>\n            This will invalidate all existing recovery codes and generate new\n            ones. Make sure to save the new codes in a secure location. This\n            action cannot be undone.\n          </AlertDialogDescription>\n        </AlertDialogHeader>\n        <AlertDialogFooter>\n          <AlertDialogCancel>Cancel</AlertDialogCancel>\n          <AlertDialogAction\n            className=\"bg-destructive text-destructive-foreground hover:bg-destructive/90\"\n            onClick={onRegenerate}\n          >\n            Regenerate codes\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n\nexport default function AuthRecoveryCodes({\n  codes = [],\n  onGenerate,\n  onRegenerate,\n  className,\n  isLoading = false,\n  errors,\n}: AuthRecoveryCodesProps) {\n  const [showCodes, setShowCodes] = useState(false);\n  const [copiedIndex, setCopiedIndex] = useState<number | null>(null);\n  const [isRegenerating, setIsRegenerating] = useState(false);\n\n  const handleCopyCode = useCallback(async (code: string, index: number) => {\n    try {\n      await navigator.clipboard.writeText(code);\n      setCopiedIndex(index);\n      setTimeout(() => setCopiedIndex(null), 2000);\n    } catch {}\n  }, []);\n\n  const handleCopyAll = useCallback(async () => {\n    if (codes.length === 0) return;\n    try {\n      await navigator.clipboard.writeText(codes.join(\"\\n\"));\n    } catch {}\n  }, [codes]);\n\n  const handleDownload = useCallback(() => {\n    downloadCodes(codes);\n  }, [codes]);\n\n  const handleRegenerate = useCallback(async () => {\n    setIsRegenerating(true);\n    try {\n      await onRegenerate?.();\n      setShowCodes(true);\n    } finally {\n      setIsRegenerating(false);\n    }\n  }, [onRegenerate]);\n\n  const handleToggleShowCodes = useCallback(() => {\n    setShowCodes((prev) => !prev);\n  }, []);\n\n  const generalError = errors?.general;\n\n  if (codes.length === 0 && !onGenerate) {\n    return <EmptyState className={className} />;\n  }\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex items-center justify-between\">\n          <div className=\"flex flex-col gap-2\">\n            <CardTitle className=\"flex items-center gap-2\">\n              <Shield aria-hidden=\"true\" className=\"size-5\" />\n              Recovery codes\n            </CardTitle>\n            <CardDescription>\n              Save these codes in a safe place. You can use them to access your\n              account if you lose your authenticator device.\n            </CardDescription>\n          </div>\n          {codes.length === 0 && onGenerate && (\n            <Button\n              aria-busy={isLoading}\n              className=\"min-h-[44px] touch-manipulation\"\n              data-loading={isLoading}\n              disabled={isLoading}\n              onClick={onGenerate}\n              type=\"button\"\n            >\n              {isLoading ? (\n                <>\n                  <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n                  Generating…\n                </>\n              ) : (\n                <>\n                  <RefreshCw aria-hidden=\"true\" className=\"size-4\" />\n                  Generate codes\n                </>\n              )}\n            </Button>\n          )}\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-6\">\n          {generalError && <ErrorAlert message={generalError} />}\n\n          {codes.length > 0 ? (\n            <>\n              <Alert variant=\"destructive\">\n                <AlertTriangle aria-hidden=\"true\" />\n                <AlertTitle>Save your recovery codes</AlertTitle>\n                <AlertDescription>\n                  Keep these codes somewhere safe. Each code works only once.\n                </AlertDescription>\n              </Alert>\n\n              <div className=\"flex flex-col gap-3\">\n                <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n                  <h3 className=\"font-medium text-sm\">Your recovery codes</h3>\n                  <Button\n                    className=\"flex min-h-[44px] w-full touch-manipulation items-center sm:w-auto\"\n                    onClick={handleToggleShowCodes}\n                    type=\"button\"\n                    variant=\"ghost\"\n                  >\n                    {showCodes ? (\n                      <>\n                        <EyeOff aria-hidden=\"true\" className=\"size-4\" />\n                        Hide codes\n                      </>\n                    ) : (\n                      <>\n                        <Eye aria-hidden=\"true\" className=\"size-4\" />\n                        Show codes\n                      </>\n                    )}\n                  </Button>\n                </div>\n\n                <RecoveryCodesList\n                  codes={codes}\n                  copiedIndex={copiedIndex}\n                  onCopyCode={handleCopyCode}\n                  showCodes={showCodes}\n                />\n\n                <RecoveryCodesActions\n                  codes={codes}\n                  isRegenerating={isRegenerating}\n                  onCopyAll={handleCopyAll}\n                  onDownload={handleDownload}\n                  onRegenerate={onRegenerate ? handleRegenerate : undefined}\n                />\n              </div>\n            </>\n          ) : (\n            <EmptyStateWithGenerate\n              isLoading={isLoading}\n              onGenerate={onGenerate}\n            />\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}