{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-api-keys",
  "title": "Settings API Keys",
  "description": "Manage API keys for programmatic access with scopes and permissions.",
  "registryDependencies": [
    "alert-dialog",
    "badge",
    "button",
    "card",
    "checkbox",
    "dialog",
    "field",
    "input-group",
    "select"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-api-keys.tsx",
      "content": "\"use client\";\n\nimport {\n  Check,\n  Copy,\n  Eye,\n  EyeOff,\n  Loader2,\n  Plus,\n  RefreshCw,\n  Trash2,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\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 { Badge } from \"@/registry/new-york/ui/badge\";\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\";\nimport { Checkbox } from \"@/registry/new-york/ui/checkbox\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/registry/new-york/ui/dialog\";\nimport {\n  Field,\n  FieldContent,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/registry/new-york/ui/select\";\n\nexport interface APIKey {\n  id: string;\n  name: string;\n  key: string;\n  createdAt: Date;\n  lastUsed?: Date;\n  expiresAt?: Date;\n  scopes: string[];\n  usageCount?: number;\n  rateLimit?: {\n    limit: number;\n    remaining: number;\n    resetAt: Date;\n  };\n}\n\nexport interface SettingsAPIKeysProps {\n  apiKeys?: APIKey[];\n  onCreate?: (data: {\n    name: string;\n    expiresAt?: Date;\n    scopes: string[];\n  }) => Promise<APIKey>;\n  onRevoke?: (keyId: string) => Promise<void>;\n  onRegenerate?: (keyId: string) => Promise<APIKey>;\n  className?: string;\n}\n\nconst availableScopes = [\n  { id: \"read\", label: \"Read\", description: \"Read-only access\" },\n  { id: \"write\", label: \"Write\", description: \"Read and write access\" },\n  { id: \"admin\", label: \"Admin\", description: \"Full administrative access\" },\n];\n\nfunction maskKey(key: string): string {\n  if (key.length <= 8) return \"•\".repeat(key.length);\n  return `${key.slice(0, 4)}${\"•\".repeat(key.length - 8)}${key.slice(-4)}`;\n}\n\nfunction formatDate(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).format(date);\n}\n\nexport default function SettingsAPIKeys({\n  apiKeys = [],\n  onCreate,\n  onRevoke,\n  onRegenerate,\n  className,\n}: SettingsAPIKeysProps) {\n  const [isCreating, setIsCreating] = useState(false);\n  const [isRevoking, setIsRevoking] = useState<string | null>(null);\n  const [isRegenerating, setIsRegenerating] = useState<string | null>(null);\n  const [createDialogOpen, setCreateDialogOpen] = useState(false);\n  const [visibleKeys, setVisibleKeys] = useState<Set<string>>(new Set());\n  const [copiedKey, setCopiedKey] = useState<string | null>(null);\n  const [errors, setErrors] = useState<Record<string, string>>({});\n\n  const [newKeyData, setNewKeyData] = useState({\n    name: \"\",\n    expiresIn: \"never\" as \"never\" | \"30\" | \"90\" | \"365\",\n    scopes: [] as string[],\n  });\n\n  const handleCreate = async () => {\n    setErrors({});\n\n    if (!newKeyData.name.trim()) {\n      setErrors({ name: \"Name is required\" });\n      return;\n    }\n\n    if (newKeyData.scopes.length === 0) {\n      setErrors({ scopes: \"At least one scope is required\" });\n      return;\n    }\n\n    setIsCreating(true);\n    try {\n      const expiresAt =\n        newKeyData.expiresIn === \"never\"\n          ? undefined\n          : new Date(\n              Date.now() +\n                Number.parseInt(newKeyData.expiresIn) * 24 * 60 * 60 * 1000\n            );\n\n      await onCreate?.({\n        name: newKeyData.name,\n        expiresAt,\n        scopes: newKeyData.scopes,\n      });\n\n      setNewKeyData({ name: \"\", expiresIn: \"never\", scopes: [] });\n      setCreateDialogOpen(false);\n    } catch (error) {\n      setErrors({\n        _general:\n          error instanceof Error ? error.message : \"Failed to create API key\",\n      });\n    } finally {\n      setIsCreating(false);\n    }\n  };\n\n  const handleRevoke = async (keyId: string) => {\n    setIsRevoking(keyId);\n    try {\n      await onRevoke?.(keyId);\n    } finally {\n      setIsRevoking(null);\n    }\n  };\n\n  const handleRegenerate = async (keyId: string) => {\n    setIsRegenerating(keyId);\n    try {\n      await onRegenerate?.(keyId);\n    } finally {\n      setIsRegenerating(null);\n    }\n  };\n\n  const toggleKeyVisibility = (keyId: string) => {\n    setVisibleKeys((prev) => {\n      const next = new Set(prev);\n      if (next.has(keyId)) {\n        next.delete(keyId);\n      } else {\n        next.add(keyId);\n      }\n      return next;\n    });\n  };\n\n  const copyKey = async (key: string) => {\n    await navigator.clipboard.writeText(key);\n    setCopiedKey(key);\n    setTimeout(() => setCopiedKey(null), 2000);\n  };\n\n  const toggleScope = (scopeId: string) => {\n    setNewKeyData((prev) => ({\n      ...prev,\n      scopes: prev.scopes.includes(scopeId)\n        ? prev.scopes.filter((s) => s !== scopeId)\n        : [...prev.scopes, scopeId],\n    }));\n  };\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <CardTitle className=\"wrap-break-word\">API Keys</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              Manage your API keys for programmatic access\n            </CardDescription>\n          </div>\n          <Dialog onOpenChange={setCreateDialogOpen} open={createDialogOpen}>\n            <DialogTrigger asChild>\n              <Button className=\"w-full shrink-0 sm:w-auto\" type=\"button\">\n                <Plus className=\"size-4\" />\n                <span className=\"whitespace-nowrap\">Create API Key</span>\n              </Button>\n            </DialogTrigger>\n            <DialogContent className=\"sm:max-w-md\">\n              <DialogHeader>\n                <DialogTitle>Create API Key</DialogTitle>\n                <DialogDescription>\n                  Create a new API key with specific permissions\n                </DialogDescription>\n              </DialogHeader>\n              <div className=\"flex flex-col gap-4\">\n                {errors._general && (\n                  <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3\">\n                    <p className=\"text-destructive text-sm\">\n                      {errors._general}\n                    </p>\n                  </div>\n                )}\n\n                <Field>\n                  <FieldLabel htmlFor=\"key-name\">\n                    Name <span className=\"text-destructive\">*</span>\n                  </FieldLabel>\n                  <FieldContent>\n                    <InputGroup>\n                      <InputGroupInput\n                        id=\"key-name\"\n                        onChange={(e) =>\n                          setNewKeyData((prev) => ({\n                            ...prev,\n                            name: e.target.value,\n                          }))\n                        }\n                        placeholder=\"My API Key\"\n                        value={newKeyData.name}\n                      />\n                    </InputGroup>\n                    {errors.name && <FieldError>{errors.name}</FieldError>}\n                  </FieldContent>\n                </Field>\n\n                <Field>\n                  <FieldLabel htmlFor=\"expires\">Expires In</FieldLabel>\n                  <FieldContent>\n                    <Select\n                      onValueChange={(value: \"never\" | \"30\" | \"90\" | \"365\") =>\n                        setNewKeyData((prev) => ({\n                          ...prev,\n                          expiresIn: value,\n                        }))\n                      }\n                      value={newKeyData.expiresIn}\n                    >\n                      <SelectTrigger id=\"expires\">\n                        <SelectValue />\n                      </SelectTrigger>\n                      <SelectContent>\n                        <SelectItem value=\"never\">Never</SelectItem>\n                        <SelectItem value=\"30\">30 days</SelectItem>\n                        <SelectItem value=\"90\">90 days</SelectItem>\n                        <SelectItem value=\"365\">1 year</SelectItem>\n                      </SelectContent>\n                    </Select>\n                  </FieldContent>\n                </Field>\n\n                <Field>\n                  <FieldLabel>\n                    Permissions <span className=\"text-destructive\">*</span>\n                  </FieldLabel>\n                  <FieldContent>\n                    <div className=\"flex flex-col gap-3\">\n                      {availableScopes.map((scope) => (\n                        <div\n                          className=\"flex items-start gap-3 rounded-lg border p-3\"\n                          key={scope.id}\n                        >\n                          <Checkbox\n                            checked={newKeyData.scopes.includes(scope.id)}\n                            id={`scope-${scope.id}`}\n                            onCheckedChange={() => toggleScope(scope.id)}\n                          />\n                          <div className=\"flex flex-1 flex-col gap-1\">\n                            <label\n                              className=\"cursor-pointer font-medium text-sm\"\n                              htmlFor={`scope-${scope.id}`}\n                            >\n                              {scope.label}\n                            </label>\n                            <p className=\"text-muted-foreground text-xs\">\n                              {scope.description}\n                            </p>\n                          </div>\n                        </div>\n                      ))}\n                    </div>\n                    {errors.scopes && <FieldError>{errors.scopes}</FieldError>}\n                  </FieldContent>\n                </Field>\n              </div>\n              <DialogFooter>\n                <Button\n                  onClick={() => setCreateDialogOpen(false)}\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  Cancel\n                </Button>\n                <Button\n                  disabled={isCreating}\n                  onClick={handleCreate}\n                  type=\"button\"\n                >\n                  {isCreating ? (\n                    <>\n                      <Loader2 className=\"size-4 animate-spin\" />\n                      Creating…\n                    </>\n                  ) : (\n                    \"Create Key\"\n                  )}\n                </Button>\n              </DialogFooter>\n            </DialogContent>\n          </Dialog>\n        </div>\n      </CardHeader>\n      <CardContent>\n        {apiKeys.length === 0 ? (\n          <div className=\"flex flex-col items-center justify-center gap-4 py-12 text-center\">\n            <div className=\"flex size-12 items-center justify-center rounded-full bg-muted\">\n              <Plus className=\"size-6 text-muted-foreground\" />\n            </div>\n            <div className=\"flex flex-col gap-2\">\n              <p className=\"font-medium text-sm\">No API keys</p>\n              <p className=\"text-muted-foreground text-sm\">\n                Create your first API key to get started\n              </p>\n            </div>\n          </div>\n        ) : (\n          <div className=\"flex flex-col gap-4\">\n            {apiKeys.map((apiKey) => {\n              const isVisible = visibleKeys.has(apiKey.id);\n              const isExpired =\n                apiKey.expiresAt && apiKey.expiresAt < new Date();\n\n              return (\n                <div\n                  className=\"flex flex-col gap-4 rounded-lg border p-4\"\n                  key={apiKey.id}\n                >\n                  <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <div className=\"flex flex-wrap items-center justify-between gap-2\">\n                        <div>\n                          <h4 className=\"font-medium text-sm\">{apiKey.name}</h4>\n\n                          {isExpired && (\n                            <Badge className=\"text-xs\" variant=\"destructive\">\n                              Expired\n                            </Badge>\n                          )}\n                        </div>\n                        <div className=\"flex shrink-0 flex-col gap-2 sm:items-end\">\n                          <div className=\"flex flex-wrap gap-2\">\n                            <Button\n                              className=\"w-full sm:w-auto\"\n                              disabled={isRegenerating === apiKey.id}\n                              onClick={() => handleRegenerate(apiKey.id)}\n                              size={\"sm\"}\n                              type=\"button\"\n                              variant=\"outline\"\n                            >\n                              {isRegenerating === apiKey.id ? (\n                                <>\n                                  <Loader2 className=\"size-4 animate-spin\" />\n                                  Regenerating…\n                                </>\n                              ) : (\n                                <>\n                                  <RefreshCw className=\"size-4\" />\n                                  Regenerate\n                                </>\n                              )}\n                            </Button>\n                            <AlertDialog>\n                              <AlertDialogTrigger asChild>\n                                <Button\n                                  className=\"w-full sm:w-auto\"\n                                  disabled={isRevoking === apiKey.id}\n                                  size={\"sm\"}\n                                  type=\"button\"\n                                  variant=\"destructive\"\n                                >\n                                  {isRevoking === apiKey.id ? (\n                                    <>\n                                      <Loader2 className=\"size-4 animate-spin\" />\n                                      Revoking…\n                                    </>\n                                  ) : (\n                                    <>\n                                      <Trash2 className=\"size-4\" />\n                                      Revoke\n                                    </>\n                                  )}\n                                </Button>\n                              </AlertDialogTrigger>\n                              <AlertDialogContent>\n                                <AlertDialogHeader>\n                                  <AlertDialogTitle>\n                                    Revoke API Key?\n                                  </AlertDialogTitle>\n                                  <AlertDialogDescription>\n                                    This will permanently revoke the API key{\" \"}\n                                    <strong>{apiKey.name}</strong>. This action\n                                    cannot be undone.\n                                  </AlertDialogDescription>\n                                </AlertDialogHeader>\n                                <AlertDialogFooter>\n                                  <AlertDialogCancel>Cancel</AlertDialogCancel>\n                                  <AlertDialogAction\n                                    onClick={() => handleRevoke(apiKey.id)}\n                                  >\n                                    Revoke Key\n                                  </AlertDialogAction>\n                                </AlertDialogFooter>\n                              </AlertDialogContent>\n                            </AlertDialog>\n                          </div>\n                        </div>\n                      </div>\n                      <div className=\"flex min-w-0 items-center gap-2 bg-muted\">\n                        <code className=\"bg- min-w-0 flex-1 break-all rounded px-2 py-1 font-mono text-xs\">\n                          {isVisible ? apiKey.key : maskKey(apiKey.key)}\n                        </code>\n                        <Button\n                          aria-label={`${isVisible ? \"Hide\" : \"Show\"} API key`}\n                          onClick={() => toggleKeyVisibility(apiKey.id)}\n                          size=\"icon-sm\"\n                          type=\"button\"\n                          variant=\"ghost\"\n                        >\n                          {isVisible ? (\n                            <EyeOff className=\"size-4\" />\n                          ) : (\n                            <Eye className=\"size-4\" />\n                          )}\n                        </Button>\n                        <Button\n                          aria-label=\"Copy API key\"\n                          onClick={() => copyKey(apiKey.key)}\n                          size=\"icon-sm\"\n                          type=\"button\"\n                          variant=\"ghost\"\n                        >\n                          {copiedKey === apiKey.key ? (\n                            <Check className=\"size-4 text-green-600\" />\n                          ) : (\n                            <Copy className=\"size-4\" />\n                          )}\n                        </Button>\n                      </div>\n                      <div className=\"flex flex-wrap items-center gap-4 text-muted-foreground text-xs\">\n                        <span>Created: {formatDate(apiKey.createdAt)}</span>\n                        {apiKey.lastUsed && (\n                          <span>Last used: {formatDate(apiKey.lastUsed)}</span>\n                        )}\n                        {apiKey.expiresAt && (\n                          <span>Expires: {formatDate(apiKey.expiresAt)}</span>\n                        )}\n                        {apiKey.usageCount !== undefined && (\n                          <span>{apiKey.usageCount} requests</span>\n                        )}\n                      </div>\n                      {apiKey.scopes.length > 0 && (\n                        <div className=\"flex flex-wrap gap-1\">\n                          {apiKey.scopes.map((scope) => (\n                            <Badge\n                              className=\"text-xs\"\n                              key={scope}\n                              variant=\"outline\"\n                            >\n                              {scope}\n                            </Badge>\n                          ))}\n                        </div>\n                      )}\n                      {apiKey.rateLimit && (\n                        <div className=\"flex flex-col gap-1\">\n                          <p className=\"text-muted-foreground text-xs\">\n                            Rate Limit: {apiKey.rateLimit.remaining} /{\" \"}\n                            {apiKey.rateLimit.limit} remaining\n                          </p>\n                          <p className=\"text-muted-foreground text-xs\">\n                            Resets: {formatDate(apiKey.rateLimit.resetAt)}\n                          </p>\n                        </div>\n                      )}\n                    </div>\n                  </div>\n                </div>\n              );\n            })}\n          </div>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}