{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-social-accounts",
  "title": "Auth Social Accounts",
  "description": "Manage connected social accounts for authentication.",
  "registryDependencies": [
    "alert-dialog",
    "badge",
    "button",
    "card"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-social-accounts.tsx",
      "content": "\"use client\";\n\nimport { IconBrandGithub } from \"@tabler/icons-react\";\nimport {\n  CheckCircle2,\n  ExternalLink,\n  Loader2,\n  Unlink,\n  XCircle,\n} from \"lucide-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 type SocialProvider =\n  | \"google\"\n  | \"github\"\n  | \"apple\"\n  | \"microsoft\"\n  | \"twitter\";\n\nexport interface SocialAccount {\n  provider: SocialProvider;\n  email?: string;\n  name?: string;\n  isConnected: boolean;\n  isVerified?: boolean;\n  isPrimary?: boolean;\n  connectedAt?: Date;\n}\n\nfunction formatConnectedDate(date: Date): string {\n  const year = date.getFullYear();\n  const month = date.toLocaleString(\"en-US\", { month: \"short\" });\n  const day = date.getDate();\n  return `${month} ${day}, ${year}`;\n}\n\nexport interface AuthSocialAccountsProps {\n  accounts?: SocialAccount[];\n  onConnect?: (provider: SocialProvider) => void;\n  onDisconnect?: (provider: SocialProvider) => void;\n  onSetPrimary?: (provider: SocialProvider) => void;\n  className?: string;\n  isLoading?: boolean;\n  errors?: {\n    general?: string;\n  };\n}\n\nconst GoogleIcon = ({ className }: { className?: string }) => (\n  <svg\n    className={className}\n    viewBox=\"0 0 24 24\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n  >\n    <path\n      d=\"M12 2a9.96 9.96 0 0 1 6.29 2.226a1 1 0 0 1 .04 1.52l-1.51 1.362a1 1 0 0 1-1.265.06a6 6 0 1 0 2.103 6.836l.001-.004h-3.66a1 1 0 0 1-.992-.883L13 13v-2a1 1 0 0 1 1-1h6.945a1 1 0 0 1 .994.89q.06.55.061 1.11c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2\"\n      fill=\"currentColor\"\n    />\n  </svg>\n);\n\nconst PROVIDER_CONFIG: Record<\n  SocialProvider,\n  {\n    name: string;\n    icon: React.ComponentType<{ className?: string }>;\n    color: string;\n  }\n> = {\n  google: {\n    name: \"Google\",\n    icon: GoogleIcon,\n    color: \"text-blue-600\",\n  },\n  github: {\n    name: \"GitHub\",\n    icon: IconBrandGithub,\n    color: \"text-foreground\",\n  },\n  apple: {\n    name: \"Apple\",\n    icon: ({ className }: { className?: string }) => (\n      <svg\n        className={className}\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.08-1.85 3.8-3.74 4.25z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    ),\n    color: \"text-foreground\",\n  },\n  microsoft: {\n    name: \"Microsoft\",\n    icon: ({ className }: { className?: string }) => (\n      <svg\n        className={className}\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path d=\"M1 1h10v10H1z\" fill=\"#f25022\" />\n        <path d=\"M13 1h10v10H13z\" fill=\"#00a4ef\" />\n        <path d=\"M1 13h10v10H1z\" fill=\"#7fba00\" />\n        <path d=\"M13 13h10v10H13z\" fill=\"#ffb900\" />\n      </svg>\n    ),\n    color: \"text-foreground\",\n  },\n  twitter: {\n    name: \"Twitter\",\n    icon: ({ className }: { className?: string }) => (\n      <svg\n        className={className}\n        viewBox=\"0 0 24 24\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <path\n          d=\"M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z\"\n          fill=\"currentColor\"\n        />\n      </svg>\n    ),\n    color: \"text-blue-500\",\n  },\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  message?: string;\n}\n\nfunction EmptyState({\n  message = \"No social accounts available\",\n}: EmptyStateProps) {\n  return (\n    <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n      <p className=\"text-muted-foreground text-sm\">{message}</p>\n    </div>\n  );\n}\n\ninterface DisconnectDialogProps {\n  isLoading: boolean;\n  onDisconnect: (provider: SocialProvider) => void;\n  providerName: string;\n  provider: SocialProvider;\n}\n\nfunction DisconnectDialog({\n  isLoading,\n  onDisconnect,\n  providerName,\n  provider,\n}: DisconnectDialogProps) {\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>\n        <Button\n          aria-label={`Disconnect ${providerName}`}\n          className=\"min-h-[32px] min-w-[32px] touch-manipulation\"\n          disabled={isLoading}\n          size=\"icon\"\n          type=\"button\"\n          variant=\"ghost\"\n        >\n          <Unlink aria-hidden=\"true\" className=\"size-4\" />\n        </Button>\n      </AlertDialogTrigger>\n      <AlertDialogContent>\n        <AlertDialogHeader>\n          <AlertDialogTitle>Disconnect {providerName}?</AlertDialogTitle>\n          <AlertDialogDescription>\n            This will disconnect your {providerName} account. You&apos;ll need\n            to reconnect it to use it for sign-in again.\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={() => onDisconnect(provider)}\n          >\n            Disconnect\n          </AlertDialogAction>\n        </AlertDialogFooter>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n\ninterface ConnectButtonProps {\n  isLoading: boolean;\n  onConnect: (provider: SocialProvider) => void;\n  provider: SocialProvider;\n  providerName: string;\n}\n\nfunction ConnectButton({\n  isLoading,\n  onConnect,\n  provider,\n  providerName,\n}: ConnectButtonProps) {\n  return (\n    <Button\n      aria-label={`Connect ${providerName}`}\n      className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n      disabled={isLoading}\n      onClick={() => onConnect(provider)}\n      type=\"button\"\n    >\n      {isLoading ? (\n        <>\n          <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n          Connecting…\n        </>\n      ) : (\n        <>\n          <ExternalLink aria-hidden=\"true\" className=\"size-4\" />\n          Connect\n        </>\n      )}\n    </Button>\n  );\n}\n\ninterface SetPrimaryButtonProps {\n  isLoading: boolean;\n  onSetPrimary: (provider: SocialProvider) => void;\n  provider: SocialProvider;\n  providerName: string;\n}\n\nfunction SetPrimaryButton({\n  isLoading,\n  onSetPrimary,\n  provider,\n  providerName,\n}: SetPrimaryButtonProps) {\n  return (\n    <Button\n      aria-label={`Set ${providerName} as primary`}\n      className=\"min-h-[44px] w-full touch-manipulation sm:w-auto\"\n      disabled={isLoading}\n      onClick={() => onSetPrimary(provider)}\n      type=\"button\"\n      variant=\"outline\"\n    >\n      Set primary\n    </Button>\n  );\n}\n\ninterface SocialAccountItemProps {\n  account: SocialAccount;\n  isLoading: boolean;\n  onConnect?: (provider: SocialProvider) => void;\n  onDisconnect?: (provider: SocialProvider) => void;\n  onSetPrimary?: (provider: SocialProvider) => void;\n}\n\nfunction SocialAccountItem({\n  account,\n  onConnect,\n  onDisconnect,\n  onSetPrimary,\n  isLoading,\n}: SocialAccountItemProps) {\n  const config = PROVIDER_CONFIG[account.provider];\n  const Icon = config.icon;\n\n  return (\n    <div className=\"flex flex-col gap-3 rounded-lg border bg-card p-4 sm:flex-row sm:items-center sm:justify-between sm:gap-4\">\n      <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n        <div\n          className={cn(\n            \"flex size-10 shrink-0 items-center justify-center rounded-lg\",\n            account.isConnected ? \"bg-muted\" : \"bg-muted/50\"\n          )}\n        >\n          <Icon aria-hidden=\"true\" className={cn(\"size-5\", config.color)} />\n        </div>\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\">{config.name}</h4>\n            {account.isPrimary && (\n              <Badge className=\"text-xs\" variant=\"secondary\">\n                Primary\n              </Badge>\n            )}\n            {account.isVerified && account.isConnected && (\n              <CheckCircle2\n                aria-hidden=\"true\"\n                className=\"size-3.5 text-primary\"\n              />\n            )}\n            {account.isConnected && !account.isVerified && (\n              <XCircle\n                aria-hidden=\"true\"\n                className=\"size-3.5 text-yellow-500\"\n              />\n            )}\n          </div>\n          {account.email && (\n            <p className=\"text-muted-foreground text-xs\">{account.email}</p>\n          )}\n          {account.connectedAt && (\n            <p className=\"text-muted-foreground text-xs\">\n              Connected {formatConnectedDate(account.connectedAt)}\n            </p>\n          )}\n        </div>\n      </div>\n      <div className=\"flex shrink-0 flex-col gap-2 sm:flex-row sm:items-center\">\n        {account.isConnected ? (\n          <>\n            {!account.isPrimary && onSetPrimary && (\n              <SetPrimaryButton\n                isLoading={isLoading}\n                onSetPrimary={onSetPrimary}\n                provider={account.provider}\n                providerName={config.name}\n              />\n            )}\n            {onDisconnect && (\n              <DisconnectDialog\n                isLoading={isLoading}\n                onDisconnect={onDisconnect}\n                provider={account.provider}\n                providerName={config.name}\n              />\n            )}\n          </>\n        ) : (\n          onConnect && (\n            <ConnectButton\n              isLoading={isLoading}\n              onConnect={onConnect}\n              provider={account.provider}\n              providerName={config.name}\n            />\n          )\n        )}\n      </div>\n    </div>\n  );\n}\n\nconst DEFAULT_ACCOUNTS: SocialAccount[] = [\n  { provider: \"google\", isConnected: false },\n  { provider: \"github\", isConnected: false },\n  { provider: \"apple\", isConnected: false },\n  { provider: \"microsoft\", isConnected: false },\n];\n\nfunction getDisplayAccounts(accounts: SocialAccount[]): SocialAccount[] {\n  if (accounts.length > 0) {\n    return accounts;\n  }\n  return DEFAULT_ACCOUNTS.filter(\n    (acc) => !accounts.some((a) => a.provider === acc.provider)\n  );\n}\n\nexport default function AuthSocialAccounts({\n  accounts = [],\n  onConnect,\n  onDisconnect,\n  onSetPrimary,\n  className,\n  isLoading = false,\n  errors,\n}: AuthSocialAccountsProps) {\n  const displayAccounts = getDisplayAccounts(accounts);\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Connected accounts</CardTitle>\n        <CardDescription>\n          Manage your social account connections for sign-in\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col gap-4\">\n          {errors?.general && <ErrorAlert message={errors.general} />}\n\n          {displayAccounts.length === 0 ? (\n            <EmptyState />\n          ) : (\n            <div className=\"flex flex-col gap-3\">\n              {displayAccounts.map((account) => (\n                <SocialAccountItem\n                  account={account}\n                  isLoading={isLoading}\n                  key={account.provider}\n                  onConnect={onConnect}\n                  onDisconnect={onDisconnect}\n                  onSetPrimary={onSetPrimary}\n                />\n              ))}\n            </div>\n          )}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}