{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "oauth-callback",
  "title": "OAuth Callback",
  "author": "Satchmo <https://bigblocks.dev>",
  "description": "OAuth callback page block that handles the redirect after Sigma Identity authentication. Reads URL params, exchanges code for tokens via a provided handler, and displays loading, success, or error states with auto-redirect.",
  "registryDependencies": [
    "avatar",
    "button",
    "card",
    "skeleton"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/oauth-callback/index.tsx",
      "content": "\"use client\"\n\nimport { OAuthCallbackUI } from \"./oauth-callback-ui\"\nimport {\n  useOAuthCallback,\n  type OAuthCallbackResult,\n  type OAuthCallbackUser,\n  type OAuthCallbackStatus,\n  type UseOAuthCallbackOptions,\n  type UseOAuthCallbackReturn,\n} from \"./use-oauth-callback\"\n\n// ---------------------------------------------------------------------------\n// Re-exports\n// ---------------------------------------------------------------------------\n\nexport {\n  OAuthCallbackUI,\n  type OAuthCallbackUIProps,\n} from \"./oauth-callback-ui\"\nexport {\n  useOAuthCallback,\n  type OAuthCallbackResult,\n  type OAuthCallbackUser,\n  type OAuthCallbackStatus,\n  type UseOAuthCallbackOptions,\n  type UseOAuthCallbackReturn,\n} from \"./use-oauth-callback\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthCallbackProps {\n  /**\n   * The handler function that processes the OAuth callback.\n   * Receives the authorization code and state from URL params,\n   * and should exchange them for user data and tokens.\n   */\n  onCallback: (params: { code: string; state: string }) => Promise<OAuthCallbackResult>\n  /** Called after successful callback processing */\n  onSuccess?: (result: OAuthCallbackResult) => void\n  /** Called when callback processing fails */\n  onError?: (error: Error) => void\n  /** URL to redirect to after successful authentication (default: \"/\") */\n  redirectUrl?: string\n  /** Delay in ms before redirecting after success (default: 2000) */\n  redirectDelay?: number\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Composed component\n// ---------------------------------------------------------------------------\n\n/**\n * OAuth callback page block that handles the redirect after authentication.\n *\n * Reads `code`, `state`, and `error` from the current URL search params,\n * calls the provided `onCallback` to exchange the code for tokens, and\n * displays loading, success, or error states.\n *\n * This block is auth-system agnostic. Pass your own `onCallback` function\n * that returns an `OAuthCallbackResult`.\n *\n * @example\n * ```tsx\n * // In your callback page (e.g. app/auth/callback/page.tsx):\n * import { OAuthCallback } from \"@/components/blocks/oauth-callback\"\n * import { authClient } from \"@/lib/auth-client\"\n *\n * export default function CallbackPage() {\n *   return (\n *     <OAuthCallback\n *       onCallback={async ({ code, state }) => {\n *         const result = await authClient.sigma.handleCallback(\n *           new URLSearchParams({ code, state })\n *         )\n *         return {\n *           user: {\n *             id: result.user.sub,\n *             name: result.user.name,\n *             image: result.user.picture ?? undefined,\n *             pubkey: result.user.pubkey,\n *             bapId: result.user.bap_id,\n *           },\n *           accessToken: result.access_token,\n *         }\n *       }}\n *       redirectUrl=\"/dashboard\"\n *       onSuccess={(result) => console.log(\"Authenticated:\", result.user.id)}\n *     />\n *   )\n * }\n * ```\n */\nexport function OAuthCallback({\n  onCallback,\n  onSuccess,\n  onError,\n  redirectUrl = \"/\",\n  redirectDelay = 2000,\n  className,\n}: OAuthCallbackProps) {\n  const { status, user, errorMessage, retry } = useOAuthCallback({\n    onCallback,\n    onSuccess,\n    onError,\n    redirectUrl,\n    redirectDelay,\n  })\n\n  return (\n    <OAuthCallbackUI\n      status={status}\n      user={user}\n      errorMessage={errorMessage}\n      redirectUrl={redirectUrl}\n      onRetry={retry}\n      className={className}\n    />\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/oauth-callback/index.tsx"
    },
    {
      "path": "registry/new-york/blocks/oauth-callback/oauth-callback-ui.tsx",
      "content": "\"use client\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardFooter,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Avatar, AvatarFallback } from \"@/components/ui/avatar\"\nimport type { OAuthCallbackStatus, OAuthCallbackUser } from \"./use-oauth-callback\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface OAuthCallbackUIProps {\n  /** Current callback processing status */\n  status: OAuthCallbackStatus\n  /** Authenticated user data (when status is \"success\") */\n  user: OAuthCallbackUser | null\n  /** Error message (when status is \"error\") */\n  errorMessage: string | null\n  /** Where the user will be redirected after success */\n  redirectUrl: string\n  /** Handler for the retry button */\n  onRetry: () => void\n  /** Additional CSS classes */\n  className?: string\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction LoadingState() {\n  return (\n    <Card className=\"w-full max-w-sm\">\n      <CardHeader className=\"items-center text-center\">\n        <div className=\"mb-2 flex size-12 items-center justify-center rounded-full bg-muted\">\n          <svg\n            className=\"size-6 animate-spin text-muted-foreground\"\n            fill=\"none\"\n            viewBox=\"0 0 24 24\"\n            aria-hidden=\"true\"\n          >\n            <circle\n              className=\"opacity-25\"\n              cx=\"12\"\n              cy=\"12\"\n              r=\"10\"\n              stroke=\"currentColor\"\n              strokeWidth=\"4\"\n            />\n            <path\n              className=\"opacity-75\"\n              fill=\"currentColor\"\n              d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n            />\n          </svg>\n        </div>\n        <CardTitle>Completing sign in</CardTitle>\n        <CardDescription>\n          Please wait while we verify your authentication.\n        </CardDescription>\n      </CardHeader>\n      <CardContent className=\"space-y-3\">\n        <Skeleton className=\"mx-auto h-4 w-3/4\" />\n        <Skeleton className=\"mx-auto h-4 w-1/2\" />\n      </CardContent>\n    </Card>\n  )\n}\n\ninterface SuccessStateProps {\n  user: OAuthCallbackUser\n  redirectUrl: string\n}\n\nfunction SuccessState({ user, redirectUrl }: SuccessStateProps) {\n  const initials = user.name\n    ? user.name\n        .split(\" \")\n        .map((part) => part[0])\n        .join(\"\")\n        .toUpperCase()\n        .slice(0, 2)\n    : user.id.slice(0, 2).toUpperCase()\n\n  return (\n    <Card className=\"w-full max-w-sm\">\n      <CardHeader className=\"items-center text-center\">\n        <div className=\"mb-2 flex size-12 items-center justify-center rounded-full bg-primary/10\">\n          <svg\n            className=\"size-6 text-primary\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            viewBox=\"0 0 24 24\"\n            aria-hidden=\"true\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              d=\"M5 13l4 4L19 7\"\n            />\n          </svg>\n        </div>\n        <CardTitle>Sign in successful</CardTitle>\n        <CardDescription>\n          You have been authenticated successfully.\n        </CardDescription>\n      </CardHeader>\n      <CardContent className=\"flex flex-col items-center gap-3\">\n        <Avatar className=\"size-14\">\n          {user.image ? (\n            <img\n              src={user.image}\n              alt={user.name ?? \"User avatar\"}\n              className=\"size-full rounded-full object-cover\"\n            />\n          ) : (\n            <AvatarFallback className=\"text-sm font-medium\">\n              {initials}\n            </AvatarFallback>\n          )}\n        </Avatar>\n        {user.name && (\n          <p className=\"text-sm font-medium\">{user.name}</p>\n        )}\n        {user.pubkey && (\n          <p className=\"max-w-full truncate text-xs font-mono text-muted-foreground\">\n            {user.pubkey}\n          </p>\n        )}\n      </CardContent>\n      <CardFooter className=\"justify-center\">\n        <p className=\"text-sm text-muted-foreground\">\n          Redirecting to{\" \"}\n          <span className=\"font-medium text-foreground\">{redirectUrl}</span>\n          &hellip;\n        </p>\n      </CardFooter>\n    </Card>\n  )\n}\n\ninterface ErrorStateProps {\n  errorMessage: string\n  onRetry: () => void\n}\n\nfunction ErrorState({ errorMessage, onRetry }: ErrorStateProps) {\n  return (\n    <Card className=\"w-full max-w-sm border-destructive/50\">\n      <CardHeader className=\"items-center text-center\">\n        <div className=\"mb-2 flex size-12 items-center justify-center rounded-full bg-destructive/10\">\n          <svg\n            className=\"size-6 text-destructive\"\n            fill=\"none\"\n            stroke=\"currentColor\"\n            strokeWidth=\"2\"\n            viewBox=\"0 0 24 24\"\n            aria-hidden=\"true\"\n          >\n            <path\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              d=\"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z\"\n            />\n          </svg>\n        </div>\n        <CardTitle className=\"text-destructive\">\n          Authentication failed\n        </CardTitle>\n        <CardDescription>{errorMessage}</CardDescription>\n      </CardHeader>\n      <CardFooter className=\"justify-center\">\n        <Button variant=\"outline\" onClick={onRetry}>\n          Try again\n        </Button>\n      </CardFooter>\n    </Card>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// Main UI component\n// ---------------------------------------------------------------------------\n\n/**\n * Pure presentational component for the OAuth callback page.\n *\n * Displays loading, success, or error states based on the `status` prop.\n * All logic is managed by the companion `useOAuthCallback` hook.\n */\nexport function OAuthCallbackUI({\n  status,\n  user,\n  errorMessage,\n  redirectUrl,\n  onRetry,\n  className,\n}: OAuthCallbackUIProps) {\n  return (\n    <div\n      className={cn(\n        \"flex min-h-[400px] items-center justify-center p-4\",\n        className,\n      )}\n      role=\"status\"\n      aria-live=\"polite\"\n    >\n      {status === \"loading\" && <LoadingState />}\n      {status === \"success\" && user && (\n        <SuccessState user={user} redirectUrl={redirectUrl} />\n      )}\n      {status === \"error\" && errorMessage && (\n        <ErrorState errorMessage={errorMessage} onRetry={onRetry} />\n      )}\n    </div>\n  )\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/oauth-callback/oauth-callback-ui.tsx"
    },
    {
      "path": "registry/new-york/blocks/oauth-callback/use-oauth-callback.ts",
      "content": "\"use client\"\n\nimport { useCallback, useEffect, useRef, useState } from \"react\"\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** User data returned from a successful OAuth callback */\nexport interface OAuthCallbackUser {\n  /** Unique user identifier */\n  id: string\n  /** Display name */\n  name?: string\n  /** Profile image URL */\n  image?: string\n  /** Bitcoin public key */\n  pubkey?: string\n  /** BAP identity ID */\n  bapId?: string\n}\n\n/** Result of a successful OAuth callback exchange */\nexport interface OAuthCallbackResult {\n  /** Authenticated user data */\n  user: OAuthCallbackUser\n  /** Access token for API requests */\n  accessToken?: string\n}\n\n/** Current status of the OAuth callback flow */\nexport type OAuthCallbackStatus = \"loading\" | \"success\" | \"error\"\n\n/** Return value from the useOAuthCallback hook */\nexport interface UseOAuthCallbackReturn {\n  /** Current callback processing status */\n  status: OAuthCallbackStatus\n  /** Authenticated user data (available when status is \"success\") */\n  user: OAuthCallbackUser | null\n  /** Error message (available when status is \"error\") */\n  errorMessage: string | null\n  /** Retry the callback processing */\n  retry: () => void\n}\n\n/** Options for configuring the useOAuthCallback hook */\nexport interface UseOAuthCallbackOptions {\n  /**\n   * The handler function that processes the OAuth callback.\n   * Receives the authorization code and state, returns user data.\n   */\n  onCallback: (params: { code: string; state: string }) => Promise<OAuthCallbackResult>\n  /** Called after successful callback processing */\n  onSuccess?: (result: OAuthCallbackResult) => void\n  /** Called when callback processing fails */\n  onError?: (error: Error) => void\n  /** URL to redirect to after successful authentication (default: \"/\") */\n  redirectUrl?: string\n  /** Delay in ms before redirecting after success (default: 2000) */\n  redirectDelay?: number\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Parse URL search params from the current window location.\n * Returns null if running on the server.\n */\nfunction getSearchParams(): URLSearchParams | null {\n  if (typeof window === \"undefined\") return null\n  return new URLSearchParams(window.location.search)\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Hook that handles an OAuth callback page flow.\n *\n * Reads `code`, `state`, and `error` from URL search params,\n * calls the provided `onCallback` handler to exchange the code for tokens,\n * and manages loading/success/error states.\n *\n * @example\n * ```tsx\n * const { status, user, errorMessage, retry } = useOAuthCallback({\n *   onCallback: async ({ code, state }) => {\n *     const result = await authClient.sigma.handleCallback(\n *       new URLSearchParams({ code, state })\n *     )\n *     return {\n *       user: { id: result.user.sub, name: result.user.name },\n *       accessToken: result.access_token,\n *     }\n *   },\n *   onSuccess: () => router.push(\"/dashboard\"),\n * })\n * ```\n */\nexport function useOAuthCallback({\n  onCallback,\n  onSuccess,\n  onError,\n  redirectUrl = \"/\",\n  redirectDelay = 2000,\n}: UseOAuthCallbackOptions): UseOAuthCallbackReturn {\n  const [status, setStatus] = useState<OAuthCallbackStatus>(\"loading\")\n  const [user, setUser] = useState<OAuthCallbackUser | null>(null)\n  const [errorMessage, setErrorMessage] = useState<string | null>(null)\n\n  // Guard against double-invocation in React Strict Mode\n  const processedRef = useRef(false)\n\n  const processCallback = useCallback(async () => {\n    const searchParams = getSearchParams()\n    if (!searchParams) {\n      setStatus(\"error\")\n      setErrorMessage(\"Unable to read URL parameters. Please try again.\")\n      return\n    }\n\n    // Check for OAuth error in URL params\n    const urlError = searchParams.get(\"error\")\n    if (urlError) {\n      const errorDescription =\n        searchParams.get(\"error_description\") ?? urlError\n      const err = new Error(errorDescription)\n      setStatus(\"error\")\n      setErrorMessage(errorDescription)\n      onError?.(err)\n      return\n    }\n\n    // Extract code and state\n    const code = searchParams.get(\"code\")\n    const state = searchParams.get(\"state\")\n\n    if (!code) {\n      const err = new Error(\n        \"Missing authorization code. The authentication server did not return a valid response.\"\n      )\n      setStatus(\"error\")\n      setErrorMessage(err.message)\n      onError?.(err)\n      return\n    }\n\n    if (!state) {\n      const err = new Error(\n        \"Missing state parameter. This may indicate a security issue. Please try signing in again.\"\n      )\n      setStatus(\"error\")\n      setErrorMessage(err.message)\n      onError?.(err)\n      return\n    }\n\n    try {\n      setStatus(\"loading\")\n      const result = await onCallback({ code, state })\n      setUser(result.user)\n      setStatus(\"success\")\n      onSuccess?.(result)\n\n      // Auto-redirect after delay\n      if (typeof window !== \"undefined\" && redirectUrl) {\n        setTimeout(() => {\n          window.location.href = redirectUrl\n        }, redirectDelay)\n      }\n    } catch (err) {\n      const error = err instanceof Error ? err : new Error(String(err))\n      setStatus(\"error\")\n      setErrorMessage(error.message)\n      onError?.(error)\n    }\n  }, [onCallback, onSuccess, onError, redirectUrl, redirectDelay])\n\n  useEffect(() => {\n    if (processedRef.current) return\n    processedRef.current = true\n    void processCallback()\n  }, [processCallback])\n\n  const retry = useCallback(() => {\n    processedRef.current = false\n    setStatus(\"loading\")\n    setErrorMessage(null)\n    setUser(null)\n    processedRef.current = true\n    void processCallback()\n  }, [processCallback])\n\n  return {\n    status,\n    user,\n    errorMessage,\n    retry,\n  }\n}\n",
      "type": "registry:component",
      "target": "~/components/blocks/oauth-callback/use-oauth-callback.ts"
    }
  ],
  "categories": [
    "authentication"
  ],
  "type": "registry:block"
}