{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "social-auth-react-router",
  "title": "Social Auth flow for React Router and Supabase",
  "description": "Social Auth flow for React Router and Supabase",
  "dependencies": [
    "@supabase/ssr@latest",
    "@react-router/dev@latest",
    "@react-router/fs-routes@latest",
    "@supabase/supabase-js@latest"
  ],
  "registryDependencies": [
    "button",
    "card",
    "https://supabase.com/library/r/safe-next-path.json"
  ],
  "files": [
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes/auth.error.tsx",
      "content": "import { useSearchParams } from 'react-router'\n\nimport { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\n\nexport default function Page() {\n  let [searchParams] = useSearchParams()\n\n  return (\n    <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n      <div className=\"w-full max-w-sm\">\n        <div className=\"flex flex-col gap-6\">\n          <Card>\n            <CardHeader>\n              <CardTitle className=\"font-medium text-lg tracking-normal\">\n                Sorry, something went wrong.\n              </CardTitle>\n            </CardHeader>\n            <CardContent>\n              {searchParams?.get('error') ? (\n                <p className=\"text-sm text-muted-foreground\">\n                  Code error: {searchParams?.get('error')}\n                </p>\n              ) : (\n                <p className=\"text-sm text-muted-foreground\">An unspecified error occurred.</p>\n              )}\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:file",
      "target": "app/routes/auth.error.tsx"
    },
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes/auth.oauth.tsx",
      "content": "import { redirect, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n  const requestUrl = new URL(request.url)\n  const code = requestUrl.searchParams.get('code')\n  const _next = requestUrl.searchParams.get('next')\n  const next = _next?.startsWith('/') ? _next : '/'\n  if (code) {\n    const { supabase, headers } = createClient(request)\n\n    const { error } = await supabase.auth.exchangeCodeForSession(code)\n    if (!error) {\n      return redirect(next, { headers })\n    } else {\n      return redirect(`/auth/error?error=${error?.message}`)\n    }\n  }\n  // redirect the user to an error page with some instructions\n  return redirect(`/auth/error`)\n}\n",
      "type": "registry:file",
      "target": "app/routes/auth.oauth.tsx"
    },
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes/login.tsx",
      "content": "import { redirect, useFetcher, useSearchParams, type ActionFunctionArgs } from 'react-router'\n\nimport { safeNextPath } from '@/registry/default/blocks/safe-next-path/lib/safe-next-path'\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n  const { supabase, headers } = createClient(request)\n  const origin = new URL(request.url).origin\n\n  const formData = await request.formData()\n  const next = safeNextPath(formData.get('next'), '/protected', origin)\n\n  const { data, error } = await supabase.auth.signInWithOAuth({\n    provider: 'github',\n    options: {\n      redirectTo: `${origin}/auth/oauth?next=${encodeURIComponent(next)}`,\n    },\n  })\n\n  if (data.url) {\n    return redirect(data.url, { headers })\n  }\n\n  if (error) {\n    return {\n      error: error instanceof Error ? error.message : 'An error occurred',\n    }\n  }\n}\n\nexport default function Login() {\n  const fetcher = useFetcher<typeof action>()\n  const [searchParams] = useSearchParams()\n\n  const error = fetcher.data?.error\n  const loading = fetcher.state === 'submitting'\n\n  return (\n    <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n      <div className=\"w-full max-w-sm\">\n        <div className=\"flex flex-col gap-6\">\n          <Card>\n            <CardHeader>\n              <CardTitle className=\"font-medium text-lg tracking-normal\">Welcome!</CardTitle>\n              <CardDescription>Sign in to your account to continue</CardDescription>\n            </CardHeader>\n            <CardContent>\n              <fetcher.Form method=\"post\">\n                <input type=\"hidden\" name=\"next\" value={searchParams.get('next') ?? ''} />\n                <div className=\"flex flex-col gap-6\">\n                  {error && <p className=\"text-sm text-destructive-500\">{error}</p>}\n                  <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n                    {loading ? 'Signing in...' : 'Continue with GitHub'}\n                  </Button>\n                </div>\n              </fetcher.Form>\n            </CardContent>\n          </Card>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:file",
      "target": "app/routes/login.tsx"
    },
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes/logout.tsx",
      "content": "import { redirect, type ActionFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\n\nexport async function loader({ request }: ActionFunctionArgs) {\n  const { supabase, headers } = createClient(request)\n\n  const { error } = await supabase.auth.signOut()\n\n  if (error) {\n    console.error(error)\n    return { success: false, error: error.message }\n  }\n\n  // Redirect to dashboard or home page after successful sign-in\n  return redirect('/', { headers })\n}\n",
      "type": "registry:file",
      "target": "app/routes/logout.tsx"
    },
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes/protected.tsx",
      "content": "import { redirect, useLoaderData, type LoaderFunctionArgs } from 'react-router'\n\nimport { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n  const { supabase } = createClient(request)\n\n  const { data, error } = await supabase.auth.getUser()\n  if (error || !data?.user) {\n    return redirect('/login')\n  }\n\n  return data\n}\n\nexport default function ProtectedPage() {\n  let data = useLoaderData<typeof loader>()\n\n  return (\n    <div className=\"flex items-center justify-center h-screen gap-2\">\n      <p>\n        Hello <span className=\"text-primary font-semibold\">{data.user.email}</span>\n      </p>\n      <a href=\"/logout\">\n        <Button>Sign out</Button>\n      </a>\n    </div>\n  )\n}\n",
      "type": "registry:file",
      "target": "app/routes/protected.tsx"
    },
    {
      "path": "registry/default/blocks/social-auth-react-router/app/routes.ts",
      "content": "import { type RouteConfig } from '@react-router/dev/routes'\nimport { flatRoutes } from '@react-router/fs-routes'\n\nexport default flatRoutes() satisfies RouteConfig\n",
      "type": "registry:file",
      "target": "app/routes.ts"
    },
    {
      "path": "registry/default/clients/react-router/lib/supabase/client.ts",
      "content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n  return createBrowserClient(\n    import.meta.env.VITE_SUPABASE_URL!,\n    import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!\n  )\n}\n",
      "type": "registry:lib"
    },
    {
      "path": "registry/default/clients/react-router/lib/supabase/server.ts",
      "content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n  const headers = new Headers()\n\n  const supabase = createServerClient(\n    process.env.VITE_SUPABASE_URL!,\n    process.env.VITE_SUPABASE_PUBLISHABLE_KEY!,\n    {\n      cookies: {\n        getAll() {\n          return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n            name: string\n            value: string\n          }[]\n        },\n        setAll(cookiesToSet) {\n          cookiesToSet.forEach(({ name, value, options }) =>\n            headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n          )\n        },\n      },\n    }\n  )\n\n  return { supabase, headers }\n}\n",
      "type": "registry:lib"
    }
  ],
  "envVars": {
    "VITE_SUPABASE_URL": "",
    "VITE_SUPABASE_PUBLISHABLE_KEY": ""
  },
  "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_KEY`.",
  "type": "registry:block"
}