{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-integrations",
  "title": "Settings Integrations",
  "description": "Connect and manage third-party integrations.",
  "registryDependencies": [
    "alert-dialog",
    "badge",
    "button",
    "card"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-integrations.tsx",
      "content": "\"use client\";\n\nimport {\n  AlertCircle,\n  Check,\n  Link as LinkIcon,\n  Loader2,\n  RefreshCw,\n  Unlink,\n  X,\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\";\n\nexport interface Integration {\n  id: string;\n  name: string;\n  description: string;\n  icon?: React.ComponentType<{ className?: string }>;\n  status: \"connected\" | \"disconnected\" | \"error\" | \"expired\";\n  lastSynced?: Date;\n  scopes?: string[];\n  needsReconnection?: boolean;\n}\n\nexport interface SettingsIntegrationsProps {\n  integrations?: Integration[];\n  onConnect?: (integrationId: string) => Promise<void>;\n  onDisconnect?: (integrationId: string) => Promise<void>;\n  onReauthorize?: (integrationId: string) => Promise<void>;\n  className?: string;\n}\n\nconst defaultIntegrations: Integration[] = [\n  {\n    id: \"github\",\n    name: \"GitHub\",\n    description: \"Connect your GitHub account to sync repositories\",\n    status: \"connected\",\n    lastSynced: new Date(Date.now() - 2 * 60 * 60 * 1000),\n    scopes: [\"repo\", \"read:user\"],\n  },\n  {\n    id: \"slack\",\n    name: \"Slack\",\n    description: \"Send notifications to your Slack workspace\",\n    status: \"disconnected\",\n    scopes: [\"chat:write\", \"channels:read\"],\n  },\n  {\n    id: \"google\",\n    name: \"Google Drive\",\n    description: \"Access and sync files from Google Drive\",\n    status: \"expired\",\n    lastSynced: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),\n    needsReconnection: true,\n    scopes: [\"drive.readonly\"],\n  },\n  {\n    id: \"stripe\",\n    name: \"Stripe\",\n    description: \"Manage payments and subscriptions\",\n    status: \"error\",\n    lastSynced: new Date(Date.now() - 5 * 60 * 60 * 1000),\n    scopes: [\"read\"],\n  },\n];\n\nfunction getStatusConfig(status: Integration[\"status\"]) {\n  switch (status) {\n    case \"connected\":\n      return {\n        label: \"Connected\",\n        variant: \"default\" as const,\n        icon: Check,\n      };\n    case \"disconnected\":\n      return {\n        label: \"Disconnected\",\n        variant: \"secondary\" as const,\n        icon: Unlink,\n      };\n    case \"error\":\n      return {\n        label: \"Error\",\n        variant: \"destructive\" as const,\n        icon: AlertCircle,\n      };\n    case \"expired\":\n      return {\n        label: \"Expired\",\n        variant: \"outline\" as const,\n        icon: X,\n      };\n  }\n}\n\nfunction formatLastSynced(date?: Date): string {\n  if (!date) return \"Never\";\n  const now = Date.now();\n  const diff = now - date.getTime();\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(minutes / 60);\n  const days = Math.floor(hours / 24);\n\n  if (minutes < 1) return \"Just now\";\n  if (minutes < 60) return `${minutes}m ago`;\n  if (hours < 24) return `${hours}h ago`;\n  return `${days}d ago`;\n}\n\nexport default function SettingsIntegrations({\n  integrations = defaultIntegrations,\n  onConnect,\n  onDisconnect,\n  onReauthorize,\n  className,\n}: SettingsIntegrationsProps) {\n  const [connecting, setConnecting] = useState<string | null>(null);\n  const [disconnecting, setDisconnecting] = useState<string | null>(null);\n\n  const handleConnect = async (integrationId: string) => {\n    setConnecting(integrationId);\n    try {\n      await onConnect?.(integrationId);\n    } finally {\n      setConnecting(null);\n    }\n  };\n\n  const handleDisconnect = async (integrationId: string) => {\n    setDisconnecting(integrationId);\n    try {\n      await onDisconnect?.(integrationId);\n    } finally {\n      setDisconnecting(null);\n    }\n  };\n\n  const handleReauthorize = async (integrationId: string) => {\n    setConnecting(integrationId);\n    try {\n      await onReauthorize?.(integrationId);\n    } finally {\n      setConnecting(null);\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\">Integrations</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              Connect and manage third-party integrations\n            </CardDescription>\n          </div>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-4\">\n          {integrations.map((integration) => {\n            const statusConfig = getStatusConfig(integration.status);\n            const StatusIcon = statusConfig.icon;\n            const isConnecting = connecting === integration.id;\n            const isDisconnecting = disconnecting === integration.id;\n            const isConnected = integration.status === \"connected\";\n            const needsReconnect =\n              integration.status === \"expired\" || integration.needsReconnection;\n\n            return (\n              <div\n                className=\"flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:items-start\"\n                key={integration.id}\n              >\n                <div className=\"flex min-w-0 flex-1 flex-col gap-3\">\n                  <div className=\"flex items-start gap-3\">\n                    {integration.icon && (\n                      <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted\">\n                        <integration.icon className=\"size-5 text-muted-foreground\" />\n                      </div>\n                    )}\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <div className=\"flex flex-wrap items-center gap-2\">\n                        <h4 className=\"font-medium text-sm\">\n                          {integration.name}\n                        </h4>\n                        <Badge\n                          className=\"flex items-center gap-1 text-xs\"\n                          variant={statusConfig.variant}\n                        >\n                          <StatusIcon className=\"size-3\" />\n                          <span>{statusConfig.label}</span>\n                        </Badge>\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">\n                        {integration.description}\n                      </p>\n                    </div>\n                  </div>\n\n                  {integration.lastSynced && (\n                    <p className=\"text-muted-foreground text-xs\">\n                      Last synced: {formatLastSynced(integration.lastSynced)}\n                    </p>\n                  )}\n\n                  {integration.scopes && integration.scopes.length > 0 && (\n                    <div className=\"flex flex-col gap-1\">\n                      <p className=\"font-medium text-muted-foreground text-xs\">\n                        Permissions:\n                      </p>\n                      <div className=\"flex flex-wrap gap-1\">\n                        {integration.scopes.map((scope) => (\n                          <Badge\n                            className=\"text-xs\"\n                            key={scope}\n                            variant=\"outline\"\n                          >\n                            {scope}\n                          </Badge>\n                        ))}\n                      </div>\n                    </div>\n                  )}\n                </div>\n\n                <div className=\"flex shrink-0 flex-col gap-2 sm:items-end\">\n                  {isConnected ? (\n                    <AlertDialog>\n                      <AlertDialogTrigger asChild>\n                        <Button\n                          className=\"w-full sm:w-auto\"\n                          disabled={isDisconnecting}\n                          type=\"button\"\n                          variant=\"outline\"\n                        >\n                          {isDisconnecting ? (\n                            <>\n                              <Loader2 className=\"size-4 animate-spin\" />\n                              Disconnecting…\n                            </>\n                          ) : (\n                            <>\n                              <Unlink className=\"size-4\" />\n                              Disconnect\n                            </>\n                          )}\n                        </Button>\n                      </AlertDialogTrigger>\n                      <AlertDialogContent>\n                        <AlertDialogHeader>\n                          <AlertDialogTitle>\n                            Disconnect {integration.name}?\n                          </AlertDialogTitle>\n                          <AlertDialogDescription>\n                            This will revoke access and stop syncing data from{\" \"}\n                            {integration.name}. You can reconnect anytime.\n                          </AlertDialogDescription>\n                        </AlertDialogHeader>\n                        <AlertDialogFooter>\n                          <AlertDialogCancel>Cancel</AlertDialogCancel>\n                          <AlertDialogAction\n                            onClick={() => handleDisconnect(integration.id)}\n                          >\n                            Disconnect\n                          </AlertDialogAction>\n                        </AlertDialogFooter>\n                      </AlertDialogContent>\n                    </AlertDialog>\n                  ) : needsReconnect ? (\n                    <Button\n                      className=\"w-full sm:w-auto\"\n                      disabled={isConnecting}\n                      onClick={() => handleReauthorize(integration.id)}\n                      type=\"button\"\n                    >\n                      {isConnecting ? (\n                        <>\n                          <Loader2 className=\"size-4 animate-spin\" />\n                          Reconnecting…\n                        </>\n                      ) : (\n                        <>\n                          <RefreshCw className=\"size-4\" />\n                          Reconnect\n                        </>\n                      )}\n                    </Button>\n                  ) : (\n                    <Button\n                      className=\"w-full sm:w-auto\"\n                      disabled={isConnecting}\n                      onClick={() => handleConnect(integration.id)}\n                      type=\"button\"\n                    >\n                      {isConnecting ? (\n                        <>\n                          <Loader2 className=\"size-4 animate-spin\" />\n                          Connecting…\n                        </>\n                      ) : (\n                        <>\n                          <LinkIcon className=\"size-4\" />\n                          Connect\n                        </>\n                      )}\n                    </Button>\n                  )}\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}