{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "api-keys",
  "title": "Api Keys",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/api-keys/ApiKeys.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Check, Copy, Eye, EyeOff, KeyRound, Plus } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { SectionCard } from '@/components/ui/section-card'\nimport { Button } from '@/components/ui/button'\nimport { Badge } from '@/components/ui/badge'\nimport { Input } from '@/components/ui/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n} from '@/components/ui/dialog'\n\nexport type ApiKeyEnvironment = 'production' | 'staging' | 'development'\n\nexport type ApiKeyStatus = 'active' | 'revoked' | 'expiring'\n\nexport interface ApiKeyItem {\n  id: string\n  name: string\n  environment: ApiKeyEnvironment\n  value: string\n  createdAt: Date\n  lastUsedAt: Date | null\n  status: ApiKeyStatus\n}\n\nexport interface ApiKeysProps {\n  /** Replacement seed data. Pass [] to start from the empty state. */\n  initialKeys?: ApiKeyItem[]\n  initialCreateOpen?: boolean\n  density?: 'default' | 'compact'\n  className?: string\n}\n\nconst statusMeta: Record<ApiKeyStatus, { label: string; variant: 'success' | 'warning' | 'destructive' }> = {\n  active: { label: 'Active', variant: 'success' },\n  expiring: { label: 'Expires soon', variant: 'warning' },\n  revoked: { label: 'Revoked', variant: 'destructive' },\n}\n\nconst environmentPrefix: Record<ApiKeyEnvironment, string> = {\n  production: 'uipkge_live_',\n  staging: 'sk_stg_',\n  development: 'sk_dev_',\n}\n\nfunction maskKeyValue(value: string): string {\n  const parts = value.split('_')\n  const prefix = parts.length > 2 ? `${parts[0]}_${parts[1]}_` : ''\n  return `${prefix}••••••••${value.slice(-4)}`\n}\n\nfunction formatDate(d: Date): string {\n  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' })\n}\n\nfunction createStubKeys(now: Date): ApiKeyItem[] {\n  const daysAgo = (days: number) => new Date(now.getTime() - days * 24 * 60 * 60 * 1000)\n  const hoursAgo = (hours: number) => new Date(now.getTime() - hours * 60 * 60 * 1000)\n  return [\n    {\n      id: 'key-prod-server',\n      name: 'Production server',\n      environment: 'production',\n      value: 'uipkge_live_51HxQp9mZvKYlo2C4f2a',\n      createdAt: daysAgo(94),\n      lastUsedAt: hoursAgo(2),\n      status: 'active',\n    },\n    {\n      id: 'key-ci-pipeline',\n      name: 'CI pipeline (GitHub Actions)',\n      environment: 'staging',\n      value: 'sk_stg_6JdRw3nAwLZmp7xQb91e',\n      createdAt: daysAgo(41),\n      lastUsedAt: hoursAgo(26),\n      status: 'active',\n    },\n    {\n      id: 'key-mobile-backend',\n      name: 'Mobile app backend',\n      environment: 'production',\n      value: 'uipkge_live_8KfSx1oCyUZiqw9Ma2b7',\n      createdAt: daysAgo(27),\n      lastUsedAt: hoursAgo(5),\n      status: 'expiring',\n    },\n    {\n      id: 'key-local-dev',\n      name: 'Local development',\n      environment: 'development',\n      value: 'sk_dev_k2MnQ8tReFvBgy5Xc40d',\n      createdAt: daysAgo(12),\n      lastUsedAt: daysAgo(3),\n      status: 'active',\n    },\n    {\n      id: 'key-legacy-webhooks',\n      name: 'Legacy webhook integration',\n      environment: 'production',\n      value: 'uipkge_live_2PvBgR6nTxAokdJe5f8c',\n      createdAt: daysAgo(210),\n      lastUsedAt: daysAgo(64),\n      status: 'revoked',\n    },\n  ]\n}\n\nexport function ApiKeys({ initialKeys, initialCreateOpen = false, density = 'default', className }: ApiKeysProps) {\n  const [now] = React.useState(() => new Date())\n  const [keys, setKeys] = React.useState<ApiKeyItem[]>(() =>\n    initialKeys ? initialKeys.map((k) => ({ ...k })) : createStubKeys(now),\n  )\n  const [revealed, setRevealed] = React.useState<Record<string, boolean>>({})\n  const [copiedId, setCopiedId] = React.useState<string | null>(null)\n  const [confirmingId, setConfirmingId] = React.useState<string | null>(null)\n  const [createOpen, setCreateOpen] = React.useState(initialCreateOpen)\n  const [newName, setNewName] = React.useState('')\n  const [newEnv, setNewEnv] = React.useState<ApiKeyEnvironment>('production')\n  const copyTimer = React.useRef<number | undefined>(undefined)\n  const confirmTimer = React.useRef<number | undefined>(undefined)\n\n  const dense = density === 'compact'\n\n  function lastUsedText(d: Date | null): string {\n    if (!d) return 'Never'\n    const minutes = Math.max(1, Math.round((now.getTime() - d.getTime()) / 60000))\n    if (minutes < 60) return `${minutes}m ago`\n    const hours = Math.round(minutes / 60)\n    if (hours < 24) return `${hours}h ago`\n    return `${Math.round(hours / 24)}d ago`\n  }\n\n  function toggleRevealed(id: string) {\n    setRevealed((r) => ({ ...r, [id]: !r[id] }))\n  }\n\n  async function copyKey(key: ApiKeyItem) {\n    try {\n      await navigator.clipboard.writeText(key.value)\n      setCopiedId(key.id)\n      window.clearTimeout(copyTimer.current)\n      copyTimer.current = window.setTimeout(() => setCopiedId(null), 1600)\n    } catch {\n      // Clipboard unavailable (e.g. insecure context) — skip the feedback swap.\n    }\n  }\n\n  function requestRevoke(id: string) {\n    if (confirmingId === id) {\n      revokeKey(id)\n      return\n    }\n    setConfirmingId(id)\n    window.clearTimeout(confirmTimer.current)\n    confirmTimer.current = window.setTimeout(() => setConfirmingId(null), 3000)\n  }\n\n  function cancelRevoke(id: string) {\n    if (confirmingId === id) {\n      window.clearTimeout(confirmTimer.current)\n      setConfirmingId(null)\n    }\n  }\n\n  function revokeKey(id: string) {\n    window.clearTimeout(confirmTimer.current)\n    setConfirmingId(null)\n    setKeys((ks) => ks.map((k) => (k.id === id ? { ...k, status: 'revoked' as const } : k)))\n  }\n\n  function openCreate() {\n    setNewName('')\n    setNewEnv('production')\n    setCreateOpen(true)\n  }\n\n  function handleCreateOpenChange(open: boolean) {\n    if (open) openCreate()\n    else setCreateOpen(false)\n  }\n\n  function createKey() {\n    const name = newName.trim()\n    if (!name) return\n    const random = Array.from({ length: 2 }, () => Math.random().toString(36).slice(2, 14)).join('')\n    setKeys((ks) => [\n      {\n        id: `key-${Date.now()}`,\n        name,\n        environment: newEnv,\n        value: `${environmentPrefix[newEnv]}${random}`,\n        createdAt: new Date(),\n        lastUsedAt: null,\n        status: 'active',\n      },\n      ...ks,\n    ])\n    setCreateOpen(false)\n  }\n\n  return (\n    <div data-slot=\"api-keys\" className={cn('w-full', className)}>\n      <SectionCard\n        title=\"API Keys\"\n        description=\"Secret keys used to authenticate requests to your API.\"\n        headerAction={\n          <Button size=\"sm\" onClick={openCreate}>\n            <Plus className=\"size-4\" />\n            Create key\n          </Button>\n        }\n      >\n        {keys.length === 0 ? (\n          <div className=\"flex flex-col items-center justify-center px-6 py-14 text-center\">\n            <div className=\"bg-muted text-muted-foreground mx-auto grid size-12 place-items-center rounded-full\">\n              <KeyRound className=\"size-5\" />\n            </div>\n            <p className=\"mt-3 text-sm font-medium\">No API keys yet</p>\n            <p className=\"text-muted-foreground mt-0.5 max-w-xs text-xs\">\n              Create a key to start authenticating requests against your API.\n            </p>\n            <Button size=\"sm\" className=\"mt-4\" onClick={openCreate}>\n              Create your first key\n            </Button>\n          </div>\n        ) : (\n          <ul className=\"-my-4 divide-y\">\n            {keys.map((key) => (\n              <li key={key.id} className={cn('flex items-center gap-3 sm:gap-4', dense ? 'py-2.5' : 'py-4')}>\n                <div className=\"min-w-0 flex-1\">\n                  <div className=\"flex items-center gap-2\">\n                    <p className=\"truncate text-sm font-medium\">{key.name}</p>\n                    <Badge variant={statusMeta[key.status].variant}>{statusMeta[key.status].label}</Badge>\n                  </div>\n                  <p className=\"text-muted-foreground mt-0.5 truncate text-xs\">\n                    Created {formatDate(key.createdAt)} · Last used {lastUsedText(key.lastUsedAt)}\n                  </p>\n                </div>\n\n                <code className=\"bg-muted hidden shrink-0 rounded px-2 py-1 font-mono text-xs md:block\">\n                  {revealed[key.id] ? key.value : maskKeyValue(key.value)}\n                </code>\n\n                <div className=\"flex shrink-0 items-center gap-1\">\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    aria-label={revealed[key.id] ? 'Hide key value' : 'Reveal key value'}\n                    onClick={() => toggleRevealed(key.id)}\n                  >\n                    {revealed[key.id] ? <EyeOff className=\"size-4\" /> : <Eye className=\"size-4\" />}\n                  </Button>\n                  <Button\n                    variant=\"ghost\"\n                    size=\"icon-sm\"\n                    aria-label={copiedId === key.id ? 'Copied' : 'Copy key value'}\n                    onClick={() => copyKey(key)}\n                  >\n                    {copiedId === key.id ? (\n                      <Check className=\"text-success size-4\" />\n                    ) : (\n                      <Copy className=\"text-muted-foreground size-4\" />\n                    )}\n                  </Button>\n                  {key.status !== 'revoked' && (\n                    <Button\n                      size=\"sm\"\n                      variant={confirmingId === key.id ? 'destructive' : 'ghost'}\n                      className={confirmingId === key.id ? undefined : 'text-muted-foreground hover:text-destructive'}\n                      onClick={() => requestRevoke(key.id)}\n                      onBlur={() => cancelRevoke(key.id)}\n                    >\n                      {confirmingId === key.id ? 'Confirm?' : 'Revoke'}\n                    </Button>\n                  )}\n                </div>\n              </li>\n            ))}\n          </ul>\n        )}\n      </SectionCard>\n\n      <Dialog open={createOpen} onOpenChange={handleCreateOpenChange}>\n        <DialogContent className=\"sm:max-w-md\">\n          <DialogHeader>\n            <DialogTitle>Create API key</DialogTitle>\n            <DialogDescription>\n              Generate a new secret key. The full value is only shown once — store it somewhere safe.\n            </DialogDescription>\n          </DialogHeader>\n          <div className=\"grid gap-4 py-2\">\n            <div className=\"grid gap-1.5\">\n              <label htmlFor=\"api-key-name\" className=\"text-sm leading-none font-medium\">\n                Name\n              </label>\n              <Input\n                id=\"api-key-name\"\n                value={newName}\n                onChange={(e) => setNewName(e.target.value)}\n                placeholder=\"e.g. Production server\"\n                onKeyDown={(e) => e.key === 'Enter' && createKey()}\n              />\n            </div>\n            <div className=\"grid gap-1.5\">\n              <label htmlFor=\"api-key-environment\" className=\"text-sm leading-none font-medium\">\n                Environment\n              </label>\n              <Select value={newEnv} onValueChange={(v) => setNewEnv(v as ApiKeyEnvironment)}>\n                <SelectTrigger id=\"api-key-environment\" className=\"w-full\">\n                  <SelectValue placeholder=\"Choose an environment\" />\n                </SelectTrigger>\n                <SelectContent>\n                  <SelectItem value=\"production\">Production</SelectItem>\n                  <SelectItem value=\"staging\">Staging</SelectItem>\n                  <SelectItem value=\"development\">Development</SelectItem>\n                </SelectContent>\n              </Select>\n            </div>\n          </div>\n          <DialogFooter>\n            <Button variant=\"outline\" onClick={() => setCreateOpen(false)}>\n              Cancel\n            </Button>\n            <Button disabled={!newName.trim()} onClick={createKey}>\n              Create key\n            </Button>\n          </DialogFooter>\n        </DialogContent>\n      </Dialog>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/ApiKeys.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/section-card.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/dialog.json",
    "https://uipkge.dev/r/react/input.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/badge.json"
  ],
  "description": "Developer API-key manager in a SectionCard. Self-contained stateful block with stub keys, reveal/copy/revoke row actions, an empty state, and a create-key dialog.",
  "categories": [
    "security",
    "dashboard",
    "data"
  ]
}