diff --git a/.prettierrc b/.prettierrc.json similarity index 100% rename from .prettierrc rename to .prettierrc.json diff --git a/apps/studio/pages/sign-in.tsx b/apps/studio/pages/sign-in.tsx index 62f383d2777..9d7b294ca42 100644 --- a/apps/studio/pages/sign-in.tsx +++ b/apps/studio/pages/sign-in.tsx @@ -44,7 +44,7 @@ const SignInPage: NextPageWithLayout = () => {
- or + or
diff --git a/apps/ui-library/app/api/blocks/[slug]/route.ts b/apps/ui-library/app/api/blocks/[slug]/route.ts new file mode 100644 index 00000000000..7c3db50abb5 --- /dev/null +++ b/apps/ui-library/app/api/blocks/[slug]/route.ts @@ -0,0 +1,126 @@ +import fs from 'fs' +import { template } from 'lodash' +import path from 'path' +import * as prettier from 'prettier' +import { type RegistryItem } from 'shadcn/schema' + +const SOCIAL_PROVIDERS = [ + { label: 'Password', value: 'password' }, + { label: 'Google', value: 'google' }, + { label: 'Facebook', value: 'facebook' }, + { label: 'Apple', value: 'apple' }, + { label: 'Azure (Microsoft)', value: 'azure' }, + { label: 'Twitter', value: 'twitter' }, + { label: 'GitHub', value: 'github' }, + { label: 'Gitlab', value: 'gitlab' }, + { label: 'Bitbucket', value: 'bitbucket' }, + { label: 'Discord', value: 'discord' }, + { label: 'Figma', value: 'figma' }, + { label: 'Kakao', value: 'kakao' }, + { label: 'Keycloak', value: 'keycloak' }, + { label: 'LinkedIn', value: 'linkedin' }, + { label: 'Notion', value: 'notion' }, + { label: 'Slack', value: 'slack' }, + { label: 'Spotify', value: 'spotify' }, + { label: 'Twitch', value: 'twitch' }, + { label: 'WorkOS', value: 'workos' }, + { label: 'Zoom', value: 'zoom' }, +] + +export async function GET(_request: Request, { params }: { params: Promise<{ slug: string }> }) { + try { + const slug = (await params).slug.replace('.json', '') + const parts = slug.split('-') + + let [framework, ...authTypes] = parts + + // Build block names + const passwordBlockName = `password-based-auth-${framework}` + const socialBlockName = `social-auth-${framework}` + + const blocksDir = path.join(process.cwd(), 'public', 'r') + + const blocks: RegistryItem[] = [] + const imports: string[] = [] + const components: string[] = [] + + const commonBlockPath = path.join(blocksDir, `common-auth-nextjs.json`) + const commonBlock: RegistryItem = JSON.parse(fs.readFileSync(commonBlockPath, 'utf-8')) + blocks.push(commonBlock) + + if (authTypes.includes('password')) { + // Read the base auth block + const baseBlockPath = path.join(blocksDir, `${passwordBlockName}.json`) + if (!fs.existsSync(baseBlockPath)) { + return Response.json( + { error: `Base block not found: ${passwordBlockName}` }, + { status: 404 } + ) + } + const baseBlock: RegistryItem = JSON.parse(fs.readFileSync(baseBlockPath, 'utf-8')) + blocks.push(baseBlock) + imports.push( + "import { PasswordLoginForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/password-login-form'" + ) + components.push('') + if (authTypes.length > 1) { + components.push( + `
or
` + ) + } + } + + for (let authType of authTypes) { + if (authType === 'password') { + // Already handled above + continue + } + const provider = SOCIAL_PROVIDERS.find((p) => p.value === authType) + if (!provider) { + return Response.json({ error: `Unknown auth type: ${authType}` }, { status: 400 }) + } + + const socialBlockPath = path.join(blocksDir, `${socialBlockName}.json`) + const socialBlock: RegistryItem = JSON.parse(fs.readFileSync(socialBlockPath, 'utf-8')) + blocks.push(socialBlock) + if (imports.find((imp) => imp.includes('SocialLoginButton')) === undefined) { + imports.push( + "import { SocialLoginButton } from '@/registry/default/blocks/social-auth-nextjs/components/social-login-button'" + ) + } + + components.push( + `` + ) + } + + // Create combined block + const combinedBlock: RegistryItem = { + name: slug, + type: 'registry:block', + title: `Combined Auth with ${authTypes.join(', ')} for ${framework}`, + description: `Combined authentication flow with password-based auth and ${authTypes.join(', ')} social providers for ${framework}`, + registryDependencies: Array.from( + new Set(blocks.flatMap((b) => b.registryDependencies || [])) + ), + dependencies: Array.from(new Set(blocks.flatMap((b) => b.dependencies || []))), + files: blocks.flatMap((b) => b.files || []), + } + + const loginForm = combinedBlock.files?.find((f) => f.path.endsWith('app/auth/login/page.tsx')) + if (loginForm) { + const compiled = template(loginForm.content, { interpolate: /{?\/\*<%=(.+?)%> \*\/}?/g }) + const templated = compiled({ + imports: imports.join('\n'), + components: components.join(''), + }) + const content = await prettier.format(templated, { parser: 'typescript' } as prettier.Options) + loginForm.content = content + } + + return Response.json(combinedBlock, { status: 200 }) + } catch (error) { + console.error('Error processing block request:', error) + return Response.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/apps/ui-library/components/command.tsx b/apps/ui-library/components/command.tsx index 1956537604e..7814efccb07 100644 --- a/apps/ui-library/components/command.tsx +++ b/apps/ui-library/components/command.tsx @@ -28,7 +28,7 @@ export function Command({ name, highlight }: CommandCopyProps) { } const baseUrl = getBaseUrl() - const componentPath = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/r/${name}.json` + const componentPath = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/api/blocks/${name}.json` const commands: Record = { npm: `npx shadcn@latest add ${baseUrl}${componentPath}`, diff --git a/apps/ui-library/components/generated-auth-block-item.tsx b/apps/ui-library/components/generated-auth-block-item.tsx new file mode 100644 index 00000000000..d6ddaa5340d --- /dev/null +++ b/apps/ui-library/components/generated-auth-block-item.tsx @@ -0,0 +1,74 @@ +'use client' + +import { OpenInV0Button } from '@/components/open-in-v0-button' +import { type Provider } from '@supabase/supabase-js' +import dynamic from 'next/dynamic' +import { useState } from 'react' +import { Label_Shadcn_, Switch } from 'ui' + +//The dynamic import is to prevent the command component from being rendered on the server and cause hydration errors +const Command = dynamic(() => import('./command').then((mod) => mod.Command), { ssr: false }) + +interface BlockItemProps { + name: string +} + +const PROVIDERS: { label: string; value: Provider | 'password' }[] = [ + { label: 'Password', value: 'password' }, + { label: 'Google', value: 'google' }, + { label: 'Facebook', value: 'facebook' }, + { label: 'Apple', value: 'apple' }, + { label: 'Azure (Microsoft)', value: 'azure' }, + { label: 'Twitter', value: 'twitter' }, + { label: 'GitHub', value: 'github' }, + { label: 'Gitlab', value: 'gitlab' }, + { label: 'Bitbucket', value: 'bitbucket' }, + { label: 'Discord', value: 'discord' }, + { label: 'Figma', value: 'figma' }, + { label: 'Kakao', value: 'kakao' }, + { label: 'Keycloak', value: 'keycloak' }, + { label: 'LinkedIn', value: 'linkedin' }, + { label: 'Notion', value: 'notion' }, + { label: 'Slack', value: 'slack' }, + { label: 'Spotify', value: 'spotify' }, + { label: 'Twitch', value: 'twitch' }, + { label: 'WorkOS', value: 'workos' }, + { label: 'Zoom', value: 'zoom' }, +] + +export const GeneratedAuthBlockItem = ({ + name = 'social-auth-nextjs', +}: { + name?: string + description?: string +}) => { + const [selected, setSelected] = useState([]) + + const handleChange = (provider: string) => { + setSelected((prev: string[]) => + prev.includes(provider) ? prev.filter((p) => p !== provider) : [...prev, provider] + ) + } + + const url = `nextjs-${selected.join('-')}` + + return ( +
+
+ {PROVIDERS.map((provider) => ( + + handleChange(provider.value)} + className="mr-2" + /> + {provider.label} + + ))} +
+ + + +
+ ) +} diff --git a/apps/ui-library/components/mdx-components.tsx b/apps/ui-library/components/mdx-components.tsx index ff192643d0b..f5cafc479a3 100644 --- a/apps/ui-library/components/mdx-components.tsx +++ b/apps/ui-library/components/mdx-components.tsx @@ -15,6 +15,7 @@ import { Callout } from './callout' import { ComponentPreview } from './component-preview' import { CopyButton } from './copy-button' import { DualRealtimeChat } from './dual-realtime-chat' +import { GeneratedAuthBlockItem } from './generated-auth-block-item' import { RegistryBlock } from './registry-block' import { StyleWrapper } from './style-wrapper' import TanStackBeta from './tanstack-beta' @@ -169,6 +170,7 @@ const components = { /> ), BlockItem, + GeneratedAuthBlockItem, BlockPreview, DualRealtimeChat, } diff --git a/apps/ui-library/content/docs/nextjs/generated-auth.mdx b/apps/ui-library/content/docs/nextjs/generated-auth.mdx new file mode 100644 index 00000000000..3f4750ea2be --- /dev/null +++ b/apps/ui-library/content/docs/nextjs/generated-auth.mdx @@ -0,0 +1,44 @@ +--- +title: Generated Authentication +description: Generated authentication block for Next.js +--- + + + Select which providers to enable below. The generated URL will call the API route for block + generation. + + +## Installation + +Select which social providers you want to be added to the block. + + + +## Folder structure + +This block includes the [Supabase client](/ui/docs/nextjs/client). If you already have one installed, you can skip overwriting it. + +## Usage + +Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up authentication flow with the specified providers. + +### Getting started + +After installing the block, you'll have the following environment variables in your `.env.local` file: + +```env +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY= +``` + +- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api). + +- If you're using a local instance of Supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running). + +## Further reading + +- [Social login](https://supabase.com/docs/guides/auth/social-login) +- [Authentication error codes](https://supabase.com/docs/guides/auth/debugging/error-codes) diff --git a/apps/ui-library/public/r/common-auth-nextjs.json b/apps/ui-library/public/r/common-auth-nextjs.json new file mode 100644 index 00000000000..c23438f8695 --- /dev/null +++ b/apps/ui-library/public/r/common-auth-nextjs.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "common-auth-nextjs", + "type": "registry:block", + "title": "Common Auth flow for Nextjs and Supabase", + "description": "Common Auth flow for Nextjs and Supabase", + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "files": [ + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx", + "content": "import {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n/*<%= imports %> */\n\nexport default function Page() {\n return (\n
\n
\n
\n \n \n Login\n Sign in to your account to continue\n \n {/*<%= components %> */}\n \n
\n
\n
\n )\n}\n", + "type": "registry:page", + "target": "app/auth/login/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx", + "content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\n\nexport default async function Page({ searchParams }: { searchParams: Promise<{ error: string }> }) {\n const params = await searchParams\n\n return (\n
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {params?.error ? (\n

Code error: {params.error}

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\n )\n}\n", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/protected/page.tsx", + "content": "import { redirect } from 'next/navigation'\n\nimport { LogoutButton } from '@/registry/default/blocks/password-based-auth-nextjs/components/logout-button'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/server'\n\nexport default async function ProtectedPage() {\n const supabase = await createClient()\n\n const { data, error } = await supabase.auth.getClaims()\n if (error || !data?.claims) {\n redirect('/auth/login')\n }\n\n return (\n
\n

\n Hello {data.claims.email}\n

\n \n
\n )\n}\n", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/middleware.ts", + "content": "import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware'\nimport { type NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n return await updateSession(request)\n}\n\nexport const config = {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * Feel free to modify this pattern to include more paths.\n */\n '/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',\n ],\n}\n", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/components/logout-button.tsx", + "content": "'use client'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { useRouter } from 'next/navigation'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!\n )\n}\n", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "content": "import { createServerClient } from '@supabase/ssr'\nimport { NextResponse, type NextRequest } from 'next/server'\n\nexport async function updateSession(request: NextRequest) {\n let supabaseResponse = NextResponse.next({\n request,\n })\n\n // With Fluid compute, don't put this client in a global environment\n // variable. Always create a new one on each request.\n const supabase = createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return request.cookies.getAll()\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value))\n supabaseResponse = NextResponse.next({\n request,\n })\n cookiesToSet.forEach(({ name, value, options }) =>\n supabaseResponse.cookies.set(name, value, options)\n )\n },\n },\n }\n )\n\n // Do not run code between createServerClient and\n // supabase.auth.getClaims(). A simple mistake could make it very hard to debug\n // issues with users being randomly logged out.\n\n // IMPORTANT: If you remove getClaims() and you use server-side rendering\n // with the Supabase client, your users may be randomly logged out.\n const { data } = await supabase.auth.getClaims()\n const user = data?.claims\n\n if (\n !user &&\n !request.nextUrl.pathname.startsWith('/login') &&\n !request.nextUrl.pathname.startsWith('/auth')\n ) {\n // no user, potentially respond by redirecting the user to the login page\n const url = request.nextUrl.clone()\n url.pathname = '/auth/login'\n return NextResponse.redirect(url)\n }\n\n // IMPORTANT: You *must* return the supabaseResponse object as it is.\n // If you're creating a new response object with NextResponse.next() make sure to:\n // 1. Pass the request in it, like so:\n // const myNewResponse = NextResponse.next({ request })\n // 2. Copy over the cookies, like so:\n // myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())\n // 3. Change the myNewResponse object to fit your needs, but avoid changing\n // the cookies!\n // 4. Finally:\n // return myNewResponse\n // If this is not done, you may be causing the browser and server to go out\n // of sync and terminate the user's session prematurely!\n\n return supabaseResponse\n}\n", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "content": "import { createServerClient } from '@supabase/ssr'\nimport { cookies } from 'next/headers'\n\n/**\n * If using Fluid compute: Don't put this client in a global variable. Always create a new client within each\n * function when using it.\n */\nexport async function createClient() {\n const cookieStore = await cookies()\n\n return createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return cookieStore.getAll()\n },\n setAll(cookiesToSet) {\n try {\n cookiesToSet.forEach(({ name, value, options }) =>\n cookieStore.set(name, value, options)\n )\n } catch {\n // The `setAll` method was called from a Server Component.\n // This can be ignored if you have middleware refreshing\n // user sessions.\n }\n },\n },\n }\n )\n}\n", + "type": "registry:lib" + } + ], + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + }, + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`." +} \ No newline at end of file diff --git a/apps/ui-library/public/r/password-based-auth-nextjs.json b/apps/ui-library/public/r/password-based-auth-nextjs.json index f5c48585d09..e897d272c36 100644 --- a/apps/ui-library/public/r/password-based-auth-nextjs.json +++ b/apps/ui-library/public/r/password-based-auth-nextjs.json @@ -15,12 +15,6 @@ "label" ], "files": [ - { - "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx", - "content": "import { LoginForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/login-form'\n\nexport default function Page() {\n return (\n
\n
\n \n
\n
\n )\n}\n", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/error/page.tsx", "content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\n\nexport default async function Page({ searchParams }: { searchParams: Promise<{ error: string }> }) {\n const params = await searchParams\n\n return (\n
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {params?.error ? (\n

Code error: {params.error}

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\n )\n}\n", @@ -39,14 +33,9 @@ "type": "registry:page", "target": "app/auth/confirm/route.ts" }, - { - "path": "registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx", - "content": "'use client'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\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'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n router.push('/protected')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", - "type": "registry:component" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/middleware.ts", - "content": "import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware'\nimport { type NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n return await updateSession(request)\n}\n\nexport const config = {\n matcher: [\n /*\n * Match all request paths except:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * - images - .svg, .png, .jpg, .jpeg, .gif, .webp\n * Feel free to modify this pattern to include more paths.\n */\n '/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',\n ],\n}\n", + "content": "import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware'\nimport { type NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n return await updateSession(request)\n}\n\nexport const config = {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * Feel free to modify this pattern to include more paths.\n */\n '/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',\n ],\n}\n", "type": "registry:file", "target": "middleware.ts" }, @@ -94,6 +83,11 @@ "content": "'use client'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { useRouter } from 'next/navigation'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", "type": "registry:component" }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx", + "content": "'use client'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nexport function PasswordLoginForm() {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n router.push('/protected')\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n )\n}\n", + "type": "registry:component" + }, { "path": "registry/default/clients/nextjs/lib/supabase/client.ts", "content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!\n )\n}\n", diff --git a/apps/ui-library/public/r/registry.json b/apps/ui-library/public/r/registry.json new file mode 100644 index 00000000000..70522f5bc0c --- /dev/null +++ b/apps/ui-library/public/r/registry.json @@ -0,0 +1,1795 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry.json", + "name": "Supabase UI Library", + "homepage": "https://supabase.com/ui", + "items": [ + { + "name": "common-auth-nextjs", + "type": "registry:block", + "title": "Common Auth flow for Nextjs and Supabase", + "description": "Common Auth flow for Nextjs and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx", + "type": "registry:page", + "target": "app/auth/login/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/components/logout-button.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "password-based-auth-nextjs", + "type": "registry:block", + "title": "Password Based Auth flow for Nextjs and Supabase", + "description": "Password Based Auth flow for Nextjs and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/confirm/route.ts", + "type": "registry:page", + "target": "app/auth/confirm/route.ts" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/sign-up/page.tsx", + "type": "registry:page", + "target": "app/auth/sign-up/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/sign-up-success/page.tsx", + "type": "registry:page", + "target": "app/auth/sign-up-success/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/forgot-password/page.tsx", + "type": "registry:page", + "target": "app/auth/forgot-password/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/update-password/page.tsx", + "type": "registry:page", + "target": "app/auth/update-password/page.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "password-based-auth-react", + "type": "registry:block", + "title": "Password Based Auth flow for React and Supabase", + "description": "Password Based Auth flow for React and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-react/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-react/components/update-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "password-based-auth-react-router", + "type": "registry:block", + "title": "Password Based Auth flow for React Router and Supabase", + "description": "Password Based Auth flow for React Router and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@react-router/dev@latest", + "@react-router/fs-routes@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx", + "type": "registry:file", + "target": "app/routes/auth.confirm.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx", + "type": "registry:file", + "target": "app/routes/auth.error.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx", + "type": "registry:file", + "target": "app/routes/forgot-password.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx", + "type": "registry:file", + "target": "app/routes/login.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx", + "type": "registry:file", + "target": "app/routes/logout.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx", + "type": "registry:file", + "target": "app/routes/protected.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx", + "type": "registry:file", + "target": "app/routes/sign-up.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx", + "type": "registry:file", + "target": "app/routes/update-password.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-react-router/app/routes.ts", + "type": "registry:file", + "target": "app/routes.ts" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "password-based-auth-tanstack", + "type": "registry:block", + "title": "Password Based Auth flow for tanstack and Supabase", + "description": "Password Based Auth flow for tanstack and Supabase", + "registryDependencies": [ + "button", + "card", + "input", + "label" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/login.tsx", + "type": "registry:file", + "target": "routes/login.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx", + "type": "registry:file", + "target": "routes/auth/error.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx", + "type": "registry:file", + "target": "routes/_protected.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected/protected.tsx", + "type": "registry:file", + "target": "routes/_protected/protected.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts", + "type": "registry:file", + "target": "routes/auth/confirm.ts" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx", + "type": "registry:file", + "target": "routes/sign-up.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx", + "type": "registry:file", + "target": "routes/sign-up-success.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx", + "type": "registry:file", + "target": "routes/forgot-password.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx", + "type": "registry:file", + "target": "routes/update-password.tsx" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "social-auth-nextjs", + "type": "registry:block", + "title": "Social Auth flow for Nextjs and Supabase", + "description": "Social Auth flow for Nextjs and Supabase", + "registryDependencies": [ + "button", + "card" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/social-auth-nextjs/app/auth/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/app/auth/oauth/route.ts", + "type": "registry:page", + "target": "app/auth/oauth/route.ts" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/components/logout-button.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "social-auth-react", + "type": "registry:block", + "title": "Social Auth flow for React and Supabase", + "description": "Social Auth flow for React and Supabase", + "registryDependencies": [ + "button", + "card" + ], + "files": [ + { + "path": "registry/default/blocks/social-auth-react/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "social-auth-react-router", + "type": "registry:block", + "title": "Social Auth flow for React Router and Supabase", + "description": "Social Auth flow for React Router and Supabase", + "registryDependencies": [ + "button", + "card" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@react-router/dev@latest", + "@react-router/fs-routes@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/social-auth-react-router/app/routes/auth.error.tsx", + "type": "registry:file", + "target": "app/routes/auth.error.tsx" + }, + { + "path": "registry/default/blocks/social-auth-react-router/app/routes/auth.oauth.tsx", + "type": "registry:file", + "target": "app/routes/auth.oauth.tsx" + }, + { + "path": "registry/default/blocks/social-auth-react-router/app/routes/login.tsx", + "type": "registry:file", + "target": "app/routes/login.tsx" + }, + { + "path": "registry/default/blocks/social-auth-react-router/app/routes/logout.tsx", + "type": "registry:file", + "target": "app/routes/logout.tsx" + }, + { + "path": "registry/default/blocks/social-auth-react-router/app/routes/protected.tsx", + "type": "registry:file", + "target": "app/routes/protected.tsx" + }, + { + "path": "registry/default/blocks/social-auth-react-router/app/routes.ts", + "type": "registry:file", + "target": "app/routes.ts" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "name": "social-auth-tanstack", + "type": "registry:block", + "title": "Social Auth flow for TanStack and Supabase", + "description": "Social Auth flow for TanStack and Supabase", + "registryDependencies": [ + "button", + "card" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/social-auth-tanstack/components/login-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/lib/supabase/fetch-user-server-fn.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/routes/_protected.tsx", + "type": "registry:file", + "target": "routes/_protected.tsx" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/routes/_protected/protected.tsx", + "type": "registry:file", + "target": "routes/_protected/protected.tsx" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/routes/auth/error.tsx", + "type": "registry:file", + "target": "routes/auth/error.tsx" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/routes/auth/oauth.ts", + "type": "registry:file", + "target": "routes/auth/oauth.ts" + }, + { + "path": "registry/default/blocks/social-auth-tanstack/routes/login.tsx", + "type": "registry:file", + "target": "routes/login.tsx" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dropzone-nextjs", + "type": "registry:component", + "title": "Dropzone (File Upload)", + "description": "Displays a control for easier uploading of files directly to Supabase Storage.", + "registryDependencies": [ + "button" + ], + "dependencies": [ + "react-dropzone", + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/dropzone/components/dropzone.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/dropzone/hooks/use-supabase-upload.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dropzone-react", + "type": "registry:component", + "title": "Dropzone (File Upload)", + "description": "Displays a control for easier uploading of files directly to Supabase Storage.", + "registryDependencies": [ + "button" + ], + "dependencies": [ + "react-dropzone", + "lucide-react", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/dropzone/components/dropzone.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/dropzone/hooks/use-supabase-upload.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dropzone-react-router", + "type": "registry:component", + "title": "Dropzone (File Upload)", + "description": "Displays a control for easier uploading of files directly to Supabase Storage.", + "registryDependencies": [ + "button" + ], + "dependencies": [ + "react-dropzone", + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/dropzone/components/dropzone.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/dropzone/hooks/use-supabase-upload.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "dropzone-tanstack", + "type": "registry:component", + "title": "Dropzone (File Upload)", + "description": "Displays a control for easier uploading of files directly to Supabase Storage.", + "registryDependencies": [ + "button" + ], + "dependencies": [ + "react-dropzone", + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/dropzone/components/dropzone.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/dropzone/hooks/use-supabase-upload.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-cursor-nextjs", + "type": "registry:component", + "title": "Realtime Cursor", + "description": "Component which renders realtime cursors from other users in a room.", + "registryDependencies": [], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-cursor/components/cursor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-cursor-react", + "type": "registry:component", + "title": "Realtime Cursor", + "description": "Component which renders realtime cursors from other users in a room.", + "registryDependencies": [], + "dependencies": [ + "lucide-react", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-cursor/components/cursor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-cursor-react-router", + "type": "registry:component", + "title": "Realtime Cursor", + "description": "Component which renders realtime cursors from other users in a room.", + "registryDependencies": [], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-cursor/components/cursor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-cursor-tanstack", + "type": "registry:component", + "title": "Realtime Cursor", + "description": "Component which renders realtime cursors from other users in a room.", + "registryDependencies": [], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-cursor/components/cursor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "current-user-avatar-nextjs", + "type": "registry:component", + "title": "Current User Avatar", + "description": "Component which renders the current user's avatar.", + "registryDependencies": [ + "avatar" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "current-user-avatar-react", + "type": "registry:component", + "title": "Current User Avatar", + "description": "Component which renders the current user's avatar.", + "registryDependencies": [ + "avatar" + ], + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "current-user-avatar-react-router", + "type": "registry:component", + "title": "Current User Avatar", + "description": "Component which renders the current user's avatar.", + "registryDependencies": [ + "avatar" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "current-user-avatar-tanstack", + "type": "registry:component", + "title": "Current User Avatar", + "description": "Component which renders the current user's avatar.", + "registryDependencies": [ + "avatar" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-avatar-stack-nextjs", + "type": "registry:component", + "title": "Avatar Stack with Realtime Presence", + "description": "Component which stack of avatars, tracked by realtime presence.", + "registryDependencies": [ + "avatar", + "tooltip" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-avatar-stack-react", + "type": "registry:component", + "title": "Avatar Stack with Realtime Presence", + "description": "Component which stack of avatars, tracked by realtime presence.", + "registryDependencies": [ + "avatar", + "tooltip" + ], + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-avatar-stack-react-router", + "type": "registry:component", + "title": "Avatar Stack with Realtime Presence", + "description": "Component which stack of avatars, tracked by realtime presence.", + "registryDependencies": [ + "avatar", + "tooltip" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-avatar-stack-tanstack", + "type": "registry:component", + "title": "Avatar Stack with Realtime Presence", + "description": "Component which stack of avatars, tracked by realtime presence.", + "registryDependencies": [ + "avatar", + "tooltip" + ], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-chat-nextjs", + "type": "registry:component", + "title": "Realtime Chat", + "description": "Component which renders realtime chat messages from other users in a room.", + "registryDependencies": [ + "input", + "button" + ], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-chat/components/chat-message.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/components/realtime-chat.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-chat-react", + "type": "registry:component", + "title": "Realtime Chat", + "description": "Component which renders realtime chat messages from other users in a room.", + "registryDependencies": [ + "input", + "button" + ], + "dependencies": [ + "lucide-react", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-chat/components/chat-message.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/components/realtime-chat.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-chat-react-router", + "type": "registry:component", + "title": "Realtime Chat", + "description": "Component which renders realtime chat messages from other users in a room.", + "registryDependencies": [ + "input", + "button" + ], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-chat/components/chat-message.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/components/realtime-chat.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "realtime-chat-tanstack", + "type": "registry:component", + "title": "Realtime Chat", + "description": "Component which renders realtime chat messages from other users in a room.", + "registryDependencies": [ + "input", + "button" + ], + "dependencies": [ + "lucide-react", + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "files": [ + { + "path": "registry/default/blocks/realtime-chat/components/chat-message.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/components/realtime-chat.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-realtime-chat.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/blocks/realtime-chat/hooks/use-chat-scroll.tsx", + "type": "registry:hook" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "infinite-query-hook", + "type": "registry:block", + "title": "Infinite Query Hook", + "description": "React hook for infinite lists, fetching data from Supabase.", + "dependencies": [ + "@supabase/supabase-js", + "@supabase/postgrest-js@*" + ], + "registryDependencies": [], + "files": [ + { + "path": "registry/default/blocks/infinite-query-hook/hooks/use-infinite-query.ts", + "type": "registry:hook" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-nextjs", + "type": "registry:lib", + "title": "Supabase Client for Next.js", + "description": "", + "registryDependencies": [], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + }, + "files": [ + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-react", + "type": "registry:lib", + "title": "Supabase Client for React", + "description": "", + "registryDependencies": [], + "dependencies": [ + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + }, + "files": [ + { + "path": "registry/default/clients/react/lib/supabase/client.ts", + "type": "registry:lib" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-react-router", + "type": "registry:lib", + "title": "Supabase Client for React Router", + "description": "", + "registryDependencies": [], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + }, + "files": [ + { + "path": "registry/default/clients/react-router/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/react-router/lib/supabase/server.ts", + "type": "registry:lib" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "supabase-client-tanstack", + "type": "registry:lib", + "title": "Supabase Client for TanStack Start", + "description": "", + "registryDependencies": [], + "dependencies": [ + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" + ], + "docs": "You'll need to set the following environment variables in your project: `VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "VITE_SUPABASE_URL": "", + "VITE_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + }, + "files": [ + { + "path": "registry/default/clients/tanstack/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/tanstack/lib/supabase/server.ts", + "type": "registry:lib" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "platform-kit-nextjs", + "type": "registry:block", + "title": "Platform Kit for Nextjs and Supabase", + "description": "Platform Kit for Nextjs and Supabase", + "registryDependencies": [ + "alert", + "badge", + "button", + "card", + "chart", + "command", + "dialog", + "drawer", + "form", + "hover-card", + "input", + "label", + "popover", + "select", + "skeleton", + "switch", + "table", + "toggle", + "toggle-group", + "tooltip" + ], + "dependencies": [ + "@hookform/resolvers", + "@monaco-editor/react", + "@tanstack/react-query", + "@tanstack/react-table", + "axios", + "cmdk", + "common-tags", + "openai", + "openapi-fetch", + "react-hook-form", + "react-markdown", + "recharts", + "sonner" + ], + "files": [ + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/ai/sql/route.ts", + "type": "registry:page", + "target": "app/api/ai/sql/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/app/api/supabase-proxy/[...path]/route.ts", + "type": "registry:page", + "target": "app/api/supabase-proxy/[...path]/route.ts" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/dynamic-form.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/logo-supabase.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/results-table.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/sql-editor.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/auth.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/database.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/index.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/logs.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/secrets.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/storage.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/suggestions.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/supabase-manager/users.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/components/users-growth-chart.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-auth.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-logs.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-run-query.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-secrets.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-storage.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-suggestions.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-tables.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/hooks/use-user-counts.ts", + "type": "registry:hook" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/logs.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api-schema.d.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/management-api.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/sql.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/index.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/pg-meta/types.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/auth.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/lib/schemas/secrets.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/platform/platform-kit-nextjs/contexts/SheetNavigationContext.tsx", + "type": "registry:component", + "target": "contexts/SheetNavigationContext.tsx" + } + ] + }, + { + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "ai-editor-rules", + "type": "registry:file", + "title": "Prompt rules for AI Editors", + "description": "Prompts for working with Supabase using AI-powered IDE tools", + "registryDependencies": [], + "dependencies": [], + "files": [ + { + "path": "registry/default/ai-editor-rules/create-db-functions.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/create-db-functions.mdc" + }, + { + "path": "registry/default/ai-editor-rules/create-migration.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/create-migration.mdc" + }, + { + "path": "registry/default/ai-editor-rules/create-rls-policies.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/create-rls-policies.mdc" + }, + { + "path": "registry/default/ai-editor-rules/postgres-sql-style-guide.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/postgres-sql-style-guide.mdc" + }, + { + "path": "registry/default/ai-editor-rules/writing-supabase-edge-functions.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/writing-supabase-edge-functions.mdc" + }, + { + "path": "registry/default/ai-editor-rules/use-realtime.mdc", + "type": "registry:file", + "target": "~/.cursor/rules/use-realtime.mdc" + } + ] + } + ] +} \ No newline at end of file diff --git a/apps/ui-library/public/r/social-auth-nextjs.json b/apps/ui-library/public/r/social-auth-nextjs.json index df08867c11c..4c06500fa9c 100644 --- a/apps/ui-library/public/r/social-auth-nextjs.json +++ b/apps/ui-library/public/r/social-auth-nextjs.json @@ -13,12 +13,6 @@ "card" ], "files": [ - { - "path": "registry/default/blocks/social-auth-nextjs/app/auth/login/page.tsx", - "content": "import { LoginForm } from '@/registry/default/blocks/social-auth-nextjs/components/login-form'\n\nexport default function Page() {\n return (\n
\n
\n \n
\n
\n )\n}\n", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/social-auth-nextjs/app/auth/error/page.tsx", "content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\n\nexport default async function Page({ searchParams }: { searchParams: Promise<{ error: string }> }) {\n const params = await searchParams\n\n return (\n
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {params?.error ? (\n

Code error: {params.error}

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\n )\n}\n", @@ -37,11 +31,6 @@ "type": "registry:page", "target": "app/auth/oauth/route.ts" }, - { - "path": "registry/default/blocks/social-auth-nextjs/components/login-form.tsx", - "content": "'use client'\n\nimport { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\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'\nimport { useState } from 'react'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithOAuth({\n provider: 'github',\n options: {\n redirectTo: `${window.location.origin}/auth/oauth?next=/protected`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n \n \n Welcome!\n Sign in to your account to continue\n \n \n
\n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n )\n}\n", - "type": "registry:component" - }, { "path": "registry/default/blocks/social-auth-nextjs/middleware.ts", "content": "import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware'\nimport { type NextRequest } from 'next/server'\n\nexport async function middleware(request: NextRequest) {\n return await updateSession(request)\n}\n\nexport const config = {\n matcher: [\n /*\n * Match all request paths except for the ones starting with:\n * - _next/static (static files)\n * - _next/image (image optimization files)\n * - favicon.ico (favicon file)\n * Feel free to modify this pattern to include more paths.\n */\n '/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',\n ],\n}\n", @@ -53,6 +42,11 @@ "content": "'use client'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { useRouter } from 'next/navigation'\n\nexport function LogoutButton() {\n const router = useRouter()\n\n const logout = async () => {\n const supabase = createClient()\n await supabase.auth.signOut()\n router.push('/auth/login')\n }\n\n return \n}\n", "type": "registry:component" }, + { + "path": "registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx", + "content": "'use client'\n\nimport { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { type Provider } from '@supabase/supabase-js'\nimport { useState } from 'react'\n\nexport function SocialLoginButton({ label, provider }: { label: string; provider: Provider }) {\n const [error, setError] = useState(null)\n const [isLoading, setIsLoading] = useState(false)\n\n const handleSocialLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n const supabase = createClient()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithOAuth({\n provider,\n options: {\n redirectTo: `${window.location.origin}/auth/oauth?next=/protected`,\n },\n })\n\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n setIsLoading(false)\n }\n }\n\n return (\n
\n
\n {error &&

{error}

}\n \n
\n
\n )\n}\n", + "type": "registry:component" + }, { "path": "registry/default/clients/nextjs/lib/supabase/client.ts", "content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY!\n )\n}\n", diff --git a/apps/ui-library/registry.json b/apps/ui-library/registry.json index 5b9ff396fe1..d014887933f 100644 --- a/apps/ui-library/registry.json +++ b/apps/ui-library/registry.json @@ -3,6 +3,57 @@ "name": "Supabase UI Library", "homepage": "https://supabase.com/ui", "items": [ + { + "name": "common-auth-nextjs", + "type": "registry:block", + "title": "Common Auth flow for Nextjs and Supabase", + "description": "Common Auth flow for Nextjs and Supabase", + "registryDependencies": ["button", "card", "input", "label"], + "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], + "files": [ + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx", + "type": "registry:page", + "target": "app/auth/login/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/components/logout-button.tsx", + "type": "registry:component" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/client.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/middleware.ts", + "type": "registry:lib" + }, + { + "path": "registry/default/clients/nextjs/lib/supabase/server.ts", + "type": "registry:lib" + } + ], + "docs": "You'll need to set the following environment variables in your project: `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY`.", + "envVars": { + "NEXT_PUBLIC_SUPABASE_URL": "", + "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY": "" + } + }, { "name": "password-based-auth-nextjs", "type": "registry:block", @@ -11,11 +62,6 @@ "registryDependencies": ["button", "card", "input", "label"], "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], "files": [ - { - "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/error/page.tsx", "type": "registry:page", @@ -31,10 +77,6 @@ "type": "registry:page", "target": "app/auth/confirm/route.ts" }, - { - "path": "registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx", - "type": "registry:component" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/middleware.ts", "type": "registry:file", @@ -76,6 +118,10 @@ "path": "registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx", "type": "registry:component" }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx", + "type": "registry:component" + }, { "path": "registry/default/clients/nextjs/lib/supabase/client.ts", "type": "registry:lib" @@ -299,11 +345,6 @@ "registryDependencies": ["button", "card"], "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], "files": [ - { - "path": "registry/default/blocks/social-auth-nextjs/app/auth/login/page.tsx", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/social-auth-nextjs/app/auth/error/page.tsx", "type": "registry:page", @@ -319,10 +360,6 @@ "type": "registry:page", "target": "app/auth/oauth/route.ts" }, - { - "path": "registry/default/blocks/social-auth-nextjs/components/login-form.tsx", - "type": "registry:component" - }, { "path": "registry/default/blocks/social-auth-nextjs/middleware.ts", "type": "registry:file", @@ -332,6 +369,10 @@ "path": "registry/default/blocks/social-auth-nextjs/components/logout-button.tsx", "type": "registry:component" }, + { + "path": "registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx", + "type": "registry:component" + }, { "path": "registry/default/clients/nextjs/lib/supabase/client.ts", "type": "registry:lib" diff --git a/apps/ui-library/registry/blocks.ts b/apps/ui-library/registry/blocks.ts index 53183786f00..273a385e70a 100644 --- a/apps/ui-library/registry/blocks.ts +++ b/apps/ui-library/registry/blocks.ts @@ -9,6 +9,7 @@ import realtimeAvatarStack from './default/blocks/realtime-avatar-stack/registry import realtimeChat from './default/blocks/realtime-chat/registry-item.json' with { type: 'json' } import realtimeCursor from './default/blocks/realtime-cursor/registry-item.json' with { type: 'json' } +import commonAuthNextjs from './default/blocks/common-auth-nextjs/registry-item.json' with { type: 'json' } import passwordBasedAuthNextjs from './default/blocks/password-based-auth-nextjs/registry-item.json' with { type: 'json' } import passwordBasedAuthReactRouter from './default/blocks/password-based-auth-react-router/registry-item.json' with { type: 'json' } import passwordBasedAuthReact from './default/blocks/password-based-auth-react/registry-item.json' with { type: 'json' } @@ -19,7 +20,7 @@ import socialAuthReactRouter from './default/blocks/social-auth-react-router/reg import socialAuthReact from './default/blocks/social-auth-react/registry-item.json' with { type: 'json' } import socialAuthTanstack from './default/blocks/social-auth-tanstack/registry-item.json' with { type: 'json' } -const combine = (component: Registry['items'][number]) => { +const combine = (component: RegistryItem) => { return clients.flatMap((client) => { return registryItemAppend( { @@ -37,6 +38,8 @@ const tanstackClient = clients.find((client) => client.name === 'supabase-client const reactRouterClient = clients.find((client) => client.name === 'supabase-client-react-router') export const blocks = [ + registryItemAppend(commonAuthNextjs as RegistryItem, [nextjsClient!]), + registryItemAppend(passwordBasedAuthNextjs as RegistryItem, [nextjsClient!]), registryItemAppend(passwordBasedAuthReact as RegistryItem, [reactClient!]), registryItemAppend(passwordBasedAuthReactRouter as RegistryItem, [reactRouterClient!]), @@ -54,4 +57,4 @@ export const blocks = [ ...combine(realtimeChat as RegistryItem), // infinite query hook is intentionally not combined with the clients since it depends on clients having database types. infiniteQueryHook as RegistryItem, -] as Registry['items'] +] as RegistryItem[] diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx new file mode 100644 index 00000000000..ed8def8595f --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx @@ -0,0 +1,26 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card' + +export default async function Page({ searchParams }: { searchParams: Promise<{ error: string }> }) { + const params = await searchParams + + return ( +
+
+
+ + + Sorry, something went wrong. + + + {params?.error ? ( +

Code error: {params.error}

+ ) : ( +

An unspecified error occurred.

+ )} +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx new file mode 100644 index 00000000000..b476dac8ea8 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx @@ -0,0 +1,26 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' +/*<%= imports %> */ + +export default function Page() { + return ( +
+
+
+ + + Login + Sign in to your account to continue + + {/*<%= components %> */} + +
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/protected/page.tsx b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/protected/page.tsx new file mode 100644 index 00000000000..f84213f2771 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/app/protected/page.tsx @@ -0,0 +1,22 @@ +import { redirect } from 'next/navigation' + +import { LogoutButton } from '@/registry/default/blocks/password-based-auth-nextjs/components/logout-button' +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/server' + +export default async function ProtectedPage() { + const supabase = await createClient() + + const { data, error } = await supabase.auth.getClaims() + if (error || !data?.claims) { + redirect('/auth/login') + } + + return ( +
+

+ Hello {data.claims.email} +

+ +
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/components/logout-button.tsx b/apps/ui-library/registry/default/blocks/common-auth-nextjs/components/logout-button.tsx new file mode 100644 index 00000000000..88110ddc6c7 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/components/logout-button.tsx @@ -0,0 +1,17 @@ +'use client' + +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { useRouter } from 'next/navigation' + +export function LogoutButton() { + const router = useRouter() + + const logout = async () => { + const supabase = createClient() + await supabase.auth.signOut() + router.push('/example/password-based-auth/auth/login') + } + + return +} diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/middleware.ts b/apps/ui-library/registry/default/blocks/common-auth-nextjs/middleware.ts new file mode 100644 index 00000000000..c04d54bc883 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/middleware.ts @@ -0,0 +1,19 @@ +import { updateSession } from '@/registry/default/clients/nextjs/lib/supabase/middleware' +import { type NextRequest } from 'next/server' + +export async function middleware(request: NextRequest) { + return await updateSession(request) +} + +export const config = { + matcher: [ + /* + * Match all request paths except for the ones starting with: + * - _next/static (static files) + * - _next/image (image optimization files) + * - favicon.ico (favicon file) + * Feel free to modify this pattern to include more paths. + */ + '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', + ], +} diff --git a/apps/ui-library/registry/default/blocks/common-auth-nextjs/registry-item.json b/apps/ui-library/registry/default/blocks/common-auth-nextjs/registry-item.json new file mode 100644 index 00000000000..c80776274f9 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/common-auth-nextjs/registry-item.json @@ -0,0 +1,34 @@ +{ + "name": "common-auth-nextjs", + "type": "registry:block", + "title": "Common Auth flow for Nextjs and Supabase", + "description": "Common Auth flow for Nextjs and Supabase", + "registryDependencies": ["button", "card", "input", "label"], + "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], + "files": [ + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/login/page.tsx", + "type": "registry:page", + "target": "app/auth/login/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/auth/error/page.tsx", + "type": "registry:page", + "target": "app/auth/error/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/app/protected/page.tsx", + "type": "registry:page", + "target": "app/protected/page.tsx" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/middleware.ts", + "type": "registry:file", + "target": "middleware.ts" + }, + { + "path": "registry/default/blocks/common-auth-nextjs/components/logout-button.tsx", + "type": "registry:component" + } + ] +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx index 0cd66abb285..5a7fb44c390 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx @@ -1,10 +1,10 @@ -import { LoginForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/login-form' +import { PasswordLoginForm } from '@/registry/default/blocks/password-based-auth-nextjs/components/password-login-form' export default function Page() { return (
- +
) diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx deleted file mode 100644 index 6a4d2231d89..00000000000 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx +++ /dev/null @@ -1,102 +0,0 @@ -'use client' - -import { cn } from '@/lib/utils' -import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' -import { Button } from '@/registry/default/components/ui/button' -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/registry/default/components/ui/card' -import { Input } from '@/registry/default/components/ui/input' -import { Label } from '@/registry/default/components/ui/label' -import { Link } from '@/registry/default/components/ui/link' -import { useRouter } from 'next/navigation' -import { useState } from 'react' - -export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(false) - const router = useRouter() - - const handleLogin = async (e: React.FormEvent) => { - e.preventDefault() - const supabase = createClient() - setIsLoading(true) - setError(null) - - try { - const { error } = await supabase.auth.signInWithPassword({ - email, - password, - }) - if (error) throw error - // Update this route to redirect to an authenticated route. The user already has an active session. - router.push('/example/password-based-auth/protected') - } catch (error: unknown) { - setError(error instanceof Error ? error.message : 'An error occurred') - } finally { - setIsLoading(false) - } - } - - return ( -
- - - Login - Enter your email below to login to your account - - -
-
-
- - setEmail(e.target.value)} - /> -
-
-
- - - Forgot your password? - -
- setPassword(e.target.value)} - /> -
- {error &&

{error}

} - -
-
- Don't have an account?{' '} - - Sign up - -
-
-
-
-
- ) -} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx new file mode 100644 index 00000000000..e6f12c3cc7b --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx @@ -0,0 +1,84 @@ +'use client' + +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { Input } from '@/registry/default/components/ui/input' +import { Label } from '@/registry/default/components/ui/label' +import { Link } from '@/registry/default/components/ui/link' +import { useRouter } from 'next/navigation' +import { useState } from 'react' + +export function PasswordLoginForm() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault() + const supabase = createClient() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + if (error) throw error + // Update this route to redirect to an authenticated route. The user already has an active session. + router.push('/example/password-based-auth/protected') + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + } finally { + setIsLoading(false) + } + } + + return ( +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ + + Forgot your password? + +
+ setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Don't have an account?{' '} + + Sign up + +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts index 71fac6460bd..c04d54bc883 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/middleware.ts @@ -8,11 +8,10 @@ export async function middleware(request: NextRequest) { export const config = { matcher: [ /* - * Match all request paths except: + * Match all request paths except for the ones starting with: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) - * - images - .svg, .png, .jpg, .jpeg, .gif, .webp * Feel free to modify this pattern to include more paths. */ '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json index 2203774c8dc..fab59e5e599 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json @@ -6,11 +6,6 @@ "registryDependencies": ["button", "card", "input", "label"], "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], "files": [ - { - "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/login/page.tsx", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/app/auth/error/page.tsx", "type": "registry:page", @@ -26,10 +21,6 @@ "type": "registry:page", "target": "app/auth/confirm/route.ts" }, - { - "path": "registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx", - "type": "registry:component" - }, { "path": "registry/default/blocks/password-based-auth-nextjs/middleware.ts", "type": "registry:file", @@ -70,6 +61,10 @@ { "path": "registry/default/blocks/password-based-auth-nextjs/components/logout-button.tsx", "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-nextjs/components/password-login-form.tsx", + "type": "registry:component" } ] } diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx index ca6a76de612..6144d4f8c49 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/login-form.tsx @@ -1,8 +1,6 @@ 'use client' import { cn } from '@/lib/utils' -import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' -import { Button } from '@/registry/default/components/ui/button' import { Card, CardContent, @@ -10,33 +8,9 @@ import { CardHeader, CardTitle, } from '@/registry/default/components/ui/card' -import { useState } from 'react' +import { SocialLoginButton } from './social-login-button' export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { - const [error, setError] = useState(null) - const [isLoading, setIsLoading] = useState(false) - - const handleSocialLogin = async (e: React.FormEvent) => { - e.preventDefault() - const supabase = createClient() - setIsLoading(true) - setError(null) - - try { - const { error } = await supabase.auth.signInWithOAuth({ - provider: 'github', - options: { - redirectTo: `${window.location.origin}/auth/oauth?next=/protected`, - }, - }) - - if (error) throw error - } catch (error: unknown) { - setError(error instanceof Error ? error.message : 'An error occurred') - setIsLoading(false) - } - } - return (
@@ -45,14 +19,7 @@ export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRe Sign in to your account to continue -
-
- {error &&

{error}

} - -
-
+
diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx new file mode 100644 index 00000000000..13da61e0d5c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx @@ -0,0 +1,43 @@ +'use client' + +import { createClient } from '@/registry/default/clients/nextjs/lib/supabase/client' +import { Button } from '@/registry/default/components/ui/button' +import { type Provider } from '@supabase/supabase-js' +import { useState } from 'react' + +export function SocialLoginButton({ label, provider }: { label: string; provider: Provider }) { + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + const handleSocialLogin = async (e: React.FormEvent) => { + e.preventDefault() + const supabase = createClient() + setIsLoading(true) + setError(null) + + try { + const { error } = await supabase.auth.signInWithOAuth({ + provider, + options: { + redirectTo: `${window.location.origin}/auth/oauth?next=/protected`, + }, + }) + + if (error) throw error + } catch (error: unknown) { + setError(error instanceof Error ? error.message : 'An error occurred') + setIsLoading(false) + } + } + + return ( +
+
+ {error &&

{error}

} + +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/social-auth-nextjs/registry-item.json b/apps/ui-library/registry/default/blocks/social-auth-nextjs/registry-item.json index c37a59023ea..acecb29956d 100644 --- a/apps/ui-library/registry/default/blocks/social-auth-nextjs/registry-item.json +++ b/apps/ui-library/registry/default/blocks/social-auth-nextjs/registry-item.json @@ -6,11 +6,6 @@ "registryDependencies": ["button", "card"], "dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"], "files": [ - { - "path": "registry/default/blocks/social-auth-nextjs/app/auth/login/page.tsx", - "type": "registry:page", - "target": "app/auth/login/page.tsx" - }, { "path": "registry/default/blocks/social-auth-nextjs/app/auth/error/page.tsx", "type": "registry:page", @@ -26,10 +21,6 @@ "type": "registry:page", "target": "app/auth/oauth/route.ts" }, - { - "path": "registry/default/blocks/social-auth-nextjs/components/login-form.tsx", - "type": "registry:component" - }, { "path": "registry/default/blocks/social-auth-nextjs/middleware.ts", "type": "registry:file", @@ -38,6 +29,10 @@ { "path": "registry/default/blocks/social-auth-nextjs/components/logout-button.tsx", "type": "registry:component" + }, + { + "path": "registry/default/blocks/social-auth-nextjs/components/social-login-button.tsx", + "type": "registry:component" } ] }