{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sign-in-card", "title": "Sign-In Card", "author": "MR ", "description": "A multi-provider sign-in chooser card. Server-renderable via an href template, or client-driven via a callback.", "registryDependencies": [ "card", "@cantera/provider-sign-in-button", "@cantera/oauth-types" ], "files": [ { "path": "registry/ui/sign-in-card.tsx", "content": "'use client'\n\nimport type * as React from 'react'\nimport { useState } from 'react'\n\nimport {\n Card,\n CardContent,\n CardDescription,\n CardFooter,\n CardHeader,\n CardTitle,\n} from '@/components/ui/card'\nimport { ProviderSignInButton } from '@/components/ui/provider-sign-in-button'\nimport type { OAuthProvider } from '@/lib/oauth-types'\nimport { cn } from '@/lib/utils'\n\ninterface SignInCardProps extends Omit, 'title'> {\n providers: OAuthProvider[]\n /**\n * Href for a provider's auth route. \"{provider}\" is replaced with the\n * provider id, e.g. \"/api/auth/{provider}\". Serializable, so the card can be\n * rendered from a server component.\n */\n hrefTemplate?: string\n /** Click handler alternative to hrefTemplate, for client-side flows. */\n onSignIn?: (providerId: string) => void | Promise\n /** Id of the provider currently authenticating, to show its spinner. */\n loadingProvider?: string\n title?: React.ReactNode\n /**\n * Heading element for the title. A card dropped onto a page needs a real\n * heading, not a styled div — pick the level that fits the page outline, or\n * pass \"div\" to opt out when the surrounding page already provides one.\n */\n titleAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div'\n description?: React.ReactNode\n footer?: React.ReactNode\n}\n\n/**\n * A multi-provider sign-in chooser. Data-agnostic: pass any providers and\n * either an hrefTemplate (server-rendered flows) or an onSignIn callback.\n */\nfunction SignInCard({\n providers,\n hrefTemplate,\n onSignIn,\n loadingProvider,\n title = 'Sign in',\n titleAs: TitleTag = 'h2',\n description,\n footer,\n className,\n ...props\n}: SignInCardProps) {\n const [pendingProvider, setPendingProvider] = useState()\n // Consumer-driven pending wins; otherwise an async onSignIn drives it.\n const activeProvider = loadingProvider ?? pendingProvider\n\n function handleSignIn(providerId: string) {\n const result = onSignIn?.(providerId)\n if (!(result instanceof Promise)) return\n setPendingProvider(providerId)\n result.then(\n () => setPendingProvider(undefined),\n () => setPendingProvider(undefined),\n )\n }\n\n return (\n \n \n \n {title}\n \n {description && {description}}\n \n \n {providers.map((provider) => {\n const href = hrefTemplate?.replaceAll('{provider}', provider.id)\n const loading = activeProvider === provider.id\n // One OAuth flow at a time: a second redirect would race the first.\n const disabled = activeProvider !== undefined && !loading\n\n return href !== undefined ? (\n \n ) : (\n handleSignIn(provider.id) : undefined}\n loading={loading}\n disabled={disabled}\n />\n )\n })}\n \n {footer && (\n \n {footer}\n \n )}\n \n )\n}\n\nexport { SignInCard, type SignInCardProps }\n", "type": "registry:ui" } ], "categories": [ "authentication", "login" ], "type": "registry:component" }