feat: more fixes for UI library (#34355)

* Regenerate the registry files.

* Regenerate the llms.txt file.

* Set the supabase project for testing.

* Fix the registryBlock component.

* Various fixes.

* Add missing deps to satisfy TS build.

* Fix the tanstack client.

* Add a tanstack block for password-based auth.

* Fix the command URL.

* Fix the env vars in the React client.

* Add missing pages to the nextjs auth.

* Simplify the tanstack auth block.

* Add success state to the forgot-password form.

* Fix the v0 button. Add some more docs.

* Revert some changes.
This commit is contained in:
Ivan Vasilov authored and GitHub committed 2025-03-24 14:24:59 +01:00
1 parent 8fd6c9276d
commit 0990155ae7
53 files changed
+1872 -460

No files matched your search

+209 -279
View File
@@ -1,297 +1,227 @@
// @ts-nocheck
// This file is autogenerated by scripts/build-registry.ts
// Do not edit this file directly.
import * as React from 'react'
import * as React from "react"
export const Index: Record<string, any> = {
default: {
'password-based-auth-nextjs': {
name: 'password-based-auth-nextjs',
type: 'registry:block',
registryDependencies: ['button', 'card', 'input', 'label'],
component: React.lazy(
() => import('@/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx')
),
source: '',
files: [
'registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts',
'registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx',
'registry/default/blocks/password-based-auth-nextjs/middleware.ts',
'registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx',
'registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx',
'registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'password-based-auth-react': {
name: 'password-based-auth-react',
type: 'registry:block',
registryDependencies: ['button', 'card', 'input', 'label'],
component: React.lazy(
() =>
import('@/registry/default/blocks/password-based-auth-react/components/login-form.tsx')
),
source: '',
files: [
'registry/default/blocks/password-based-auth-react/components/login-form.tsx',
'registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx',
'registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx',
'registry/default/blocks/password-based-auth-react/components/update-password-form.tsx',
'registry/default/clients/react/lib/supabase/client.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'dropzone-nextjs': {
name: 'dropzone-nextjs',
type: 'registry:component',
registryDependencies: ['button', 'tooltip', 'progress'],
component: React.lazy(
() => import('@/registry/default/blocks/dropzone/components/dropzone.tsx')
),
source: '',
files: [
'registry/default/blocks/dropzone/components/dropzone.tsx',
'registry/default/blocks/dropzone/hooks/use-supabase-upload.ts',
'registry/default/clients/nextjs/lib/supabase/client.ts',
'registry/default/clients/nextjs/lib/supabase/middleware.ts',
'registry/default/clients/nextjs/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'dropzone-react': {
name: 'dropzone-react',
type: 'registry:component',
registryDependencies: ['button', 'tooltip', 'progress'],
component: React.lazy(
() => import('@/registry/default/blocks/dropzone/components/dropzone.tsx')
),
source: '',
files: [
'registry/default/blocks/dropzone/components/dropzone.tsx',
'registry/default/blocks/dropzone/hooks/use-supabase-upload.ts',
'registry/default/clients/react/lib/supabase/client.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'dropzone-react-router': {
name: 'dropzone-react-router',
type: 'registry:component',
registryDependencies: ['button', 'tooltip', 'progress'],
component: React.lazy(
() => import('@/registry/default/blocks/dropzone/components/dropzone.tsx')
),
source: '',
files: [
'registry/default/blocks/dropzone/components/dropzone.tsx',
'registry/default/blocks/dropzone/hooks/use-supabase-upload.ts',
'registry/default/clients/react-router/lib/supabase.client.ts',
'registry/default/clients/react-router/lib/supabase.server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'dropzone-tanstack': {
name: 'dropzone-tanstack',
type: 'registry:component',
registryDependencies: ['button', 'tooltip', 'progress'],
component: React.lazy(
() => import('@/registry/default/blocks/dropzone/components/dropzone.tsx')
),
source: '',
files: [
'registry/default/blocks/dropzone/components/dropzone.tsx',
'registry/default/blocks/dropzone/hooks/use-supabase-upload.ts',
'registry/default/clients/tanstack/lib/supabase/client.ts',
'registry/default/clients/tanstack/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'realtime-cursor-nextjs': {
name: 'realtime-cursor-nextjs',
type: 'registry:component',
"default": {
"password-based-auth-nextjs": {
name: "password-based-auth-nextjs",
type: "registry:block",
registryDependencies: ["button","card","input","label"],
component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx")),
source: "",
files: ["registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/protected/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/confirm/route.ts","registry/default/blocks/password-based-auth-nextjs/components/login-form.tsx","registry/default/blocks/password-based-auth-nextjs/middleware.ts","registry/default/blocks/password-based-auth-nextjs/app/sign-up/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/sign-up-form.tsx","registry/default/blocks/password-based-auth-nextjs/app/forgot-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/app/update-password/page.tsx","registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx","registry/default/blocks/password-based-auth-nextjs/components/update-password-form.tsx","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"password-based-auth-react": {
name: "password-based-auth-react",
type: "registry:block",
registryDependencies: ["button","card","input","label"],
component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-react/components/login-form.tsx")),
source: "",
files: ["registry/default/blocks/password-based-auth-react/components/login-form.tsx","registry/default/blocks/password-based-auth-react/components/sign-up-form.tsx","registry/default/blocks/password-based-auth-react/components/forgot-password-form.tsx","registry/default/blocks/password-based-auth-react/components/update-password-form.tsx","registry/default/clients/react/lib/supabase/client.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"password-based-auth-tanstack": {
name: "password-based-auth-tanstack",
type: "registry:block",
registryDependencies: ["button","card","input","label"],
component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-tanstack/routes/login.tsx")),
source: "",
files: ["registry/default/blocks/password-based-auth-tanstack/routes/login.tsx","registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx","registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx","registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx","registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts","registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx","registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx","registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx","registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx","registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx","registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx","registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx","registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx","registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts","registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"dropzone-nextjs": {
name: "dropzone-nextjs",
type: "registry:component",
registryDependencies: ["button","tooltip","progress"],
component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")),
source: "",
files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"dropzone-react": {
name: "dropzone-react",
type: "registry:component",
registryDependencies: ["button","tooltip","progress"],
component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")),
source: "",
files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/react/lib/supabase/client.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"dropzone-react-router": {
name: "dropzone-react-router",
type: "registry:component",
registryDependencies: ["button","tooltip","progress"],
component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")),
source: "",
files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"dropzone-tanstack": {
name: "dropzone-tanstack",
type: "registry:component",
registryDependencies: ["button","tooltip","progress"],
component: React.lazy(() => import("@/registry/default/blocks/dropzone/components/dropzone.tsx")),
source: "",
files: ["registry/default/blocks/dropzone/components/dropzone.tsx","registry/default/blocks/dropzone/hooks/use-supabase-upload.ts","registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"realtime-cursor-nextjs": {
name: "realtime-cursor-nextjs",
type: "registry:component",
registryDependencies: [],
component: React.lazy(
() => import('@/registry/default/blocks/realtime-cursor/components/cursor.tsx')
),
source: '',
files: [
'registry/default/blocks/realtime-cursor/components/cursor.tsx',
'registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx',
'registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts',
'registry/default/clients/nextjs/lib/supabase/client.ts',
'registry/default/clients/nextjs/lib/supabase/middleware.ts',
'registry/default/clients/nextjs/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'realtime-cursor-react': {
name: 'realtime-cursor-react',
type: 'registry:component',
component: React.lazy(() => import("@/registry/default/blocks/realtime-cursor/components/cursor.tsx")),
source: "",
files: ["registry/default/blocks/realtime-cursor/components/cursor.tsx","registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx","registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts","registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"realtime-cursor-react": {
name: "realtime-cursor-react",
type: "registry:component",
registryDependencies: [],
component: React.lazy(
() => import('@/registry/default/blocks/realtime-cursor/components/cursor.tsx')
),
source: '',
files: [
'registry/default/blocks/realtime-cursor/components/cursor.tsx',
'registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx',
'registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts',
'registry/default/clients/react/lib/supabase/client.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'realtime-cursor-react-router': {
name: 'realtime-cursor-react-router',
type: 'registry:component',
component: React.lazy(() => import("@/registry/default/blocks/realtime-cursor/components/cursor.tsx")),
source: "",
files: ["registry/default/blocks/realtime-cursor/components/cursor.tsx","registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx","registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts","registry/default/clients/react/lib/supabase/client.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"realtime-cursor-react-router": {
name: "realtime-cursor-react-router",
type: "registry:component",
registryDependencies: [],
component: React.lazy(
() => import('@/registry/default/blocks/realtime-cursor/components/cursor.tsx')
),
source: '',
files: [
'registry/default/blocks/realtime-cursor/components/cursor.tsx',
'registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx',
'registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts',
'registry/default/clients/react-router/lib/supabase.client.ts',
'registry/default/clients/react-router/lib/supabase.server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'realtime-cursor-tanstack': {
name: 'realtime-cursor-tanstack',
type: 'registry:component',
component: React.lazy(() => import("@/registry/default/blocks/realtime-cursor/components/cursor.tsx")),
source: "",
files: ["registry/default/blocks/realtime-cursor/components/cursor.tsx","registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx","registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts","registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"realtime-cursor-tanstack": {
name: "realtime-cursor-tanstack",
type: "registry:component",
registryDependencies: [],
component: React.lazy(
() => import('@/registry/default/blocks/realtime-cursor/components/cursor.tsx')
),
source: '',
files: [
'registry/default/blocks/realtime-cursor/components/cursor.tsx',
'registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx',
'registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts',
'registry/default/clients/tanstack/lib/supabase/client.ts',
'registry/default/clients/tanstack/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'supabase-client-nextjs': {
name: 'supabase-client-nextjs',
type: 'registry:lib',
component: React.lazy(() => import("@/registry/default/blocks/realtime-cursor/components/cursor.tsx")),
source: "",
files: ["registry/default/blocks/realtime-cursor/components/cursor.tsx","registry/default/blocks/realtime-cursor/components/realtime-cursors.tsx","registry/default/blocks/realtime-cursor/hooks/use-realtime-cursors.ts","registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"supabase-client-nextjs": {
name: "supabase-client-nextjs",
type: "registry:lib",
registryDependencies: [],
source: '',
files: [
'registry/default/clients/nextjs/lib/supabase/client.ts',
'registry/default/clients/nextjs/lib/supabase/middleware.ts',
'registry/default/clients/nextjs/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'supabase-client-react': {
name: 'supabase-client-react',
type: 'registry:lib',
source: "",
files: ["registry/default/clients/nextjs/lib/supabase/client.ts","registry/default/clients/nextjs/lib/supabase/middleware.ts","registry/default/clients/nextjs/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"supabase-client-react": {
name: "supabase-client-react",
type: "registry:lib",
registryDependencies: [],
source: '',
files: ['registry/default/clients/react/lib/supabase/client.ts'],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'supabase-client-react-router': {
name: 'supabase-client-react-router',
type: 'registry:lib',
source: "",
files: ["registry/default/clients/react/lib/supabase/client.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"supabase-client-react-router": {
name: "supabase-client-react-router",
type: "registry:lib",
registryDependencies: [],
source: '',
files: [
'registry/default/clients/react-router/lib/supabase.client.ts',
'registry/default/clients/react-router/lib/supabase.server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'supabase-client-tanstack': {
name: 'supabase-client-tanstack',
type: 'registry:lib',
source: "",
files: ["registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"supabase-client-tanstack": {
name: "supabase-client-tanstack",
type: "registry:lib",
registryDependencies: [],
source: '',
files: [
'registry/default/clients/tanstack/lib/supabase/client.ts',
'registry/default/clients/tanstack/lib/supabase/server.ts',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'ai-editor-rules': {
name: 'ai-editor-rules',
type: 'registry:file',
source: "",
files: ["registry/default/clients/tanstack/lib/supabase/client.ts","registry/default/clients/tanstack/lib/supabase/server.ts"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"ai-editor-rules": {
name: "ai-editor-rules",
type: "registry:file",
registryDependencies: [],
source: '',
files: [
'registry/default/ai-editor-rules/create-db-functions.md',
'registry/default/ai-editor-rules/create-migration.md',
'registry/default/ai-editor-rules/create-rls-policies.md',
'registry/default/ai-editor-rules/postgres-sql-style-guide.md',
'registry/default/ai-editor-rules/writing-supabase-edge-functions.md',
],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'dropzone-demo': {
name: 'dropzone-demo',
type: 'registry:example',
source: "",
files: ["registry/default/ai-editor-rules/create-db-functions.md","registry/default/ai-editor-rules/create-migration.md","registry/default/ai-editor-rules/create-rls-policies.md","registry/default/ai-editor-rules/postgres-sql-style-guide.md","registry/default/ai-editor-rules/writing-supabase-edge-functions.md"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"dropzone-demo": {
name: "dropzone-demo",
type: "registry:example",
registryDependencies: [],
component: React.lazy(() => import('@/registry/default/examples/dropzone-demo.tsx')),
source: '',
files: ['registry/default/examples/dropzone-demo.tsx'],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
'realtime-cursor-demo': {
name: 'realtime-cursor-demo',
type: 'registry:example',
component: React.lazy(() => import("@/registry/default/examples/dropzone-demo.tsx")),
source: "",
files: ["registry/default/examples/dropzone-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
,
"realtime-cursor-demo": {
name: "realtime-cursor-demo",
type: "registry:example",
registryDependencies: [],
component: React.lazy(() => import('@/registry/default/examples/realtime-cursor-demo.tsx')),
source: '',
files: ['registry/default/examples/realtime-cursor-demo.tsx'],
category: 'undefined',
subcategory: 'undefined',
chunks: [],
},
component: React.lazy(() => import("@/registry/default/examples/realtime-cursor-demo.tsx")),
source: "",
files: ["registry/default/examples/realtime-cursor-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
}
},
}
@@ -3,9 +3,10 @@
import { RegistryNode } from '@/lib/process-registry'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@ui/components/shadcn/ui/tabs'
import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'
import { File, Folder } from 'fumadocs-ui/components/files'
import { useState } from 'react'
import supabaseTheme from '../lib/themes/supabase-2.json' assert { type: 'json' }
import { BlockItemPreview } from './block-item'
import { File, Folder } from './files'
interface BlockItemCodeProps {
files: RegistryNode[]
@@ -101,7 +102,12 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
value="code"
className="rounded-none [&_figure]:rounded-none [&_figure]:h-full [&_div[data-radix-scroll-area-viewport]]:min-h-[600px]"
>
<DynamicCodeBlock lang={selectedFileExtension} code={selectedFile?.content} />
<DynamicCodeBlock
lang={selectedFileExtension}
code={selectedFile?.content}
/* the component supports a theme prop, but it's typed badly */
options={{ theme: supabaseTheme } as any}
/>
</TabsContent>
<TabsContent value="preview" className="h-full flex-1">
{selectedFileDemoUrl && <BlockItemPreview title="" src={selectedFileDemoUrl} />}
+2 -2
View File
@@ -10,8 +10,8 @@ export function Command({ name }: CommandCopyProps) {
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: process.env.VERCEL_TARGET_ENV === 'preview'
? `https://${process.env.VERCEL_PROJECT_PREVIEW_URL}`
: 'http://localhost:3000'
}/r/${name}.json`
: 'http://localhost:3004'
}${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/r/${name}.json`
return (
<>
+66
View File
@@ -0,0 +1,66 @@
'use client'
import { cva } from 'class-variance-authority'
import { FileIcon, FolderIcon, FolderOpen } from 'lucide-react'
import { useState, type HTMLAttributes, type ReactNode } from 'react'
import { cn, Collapsible_Shadcn_, CollapsibleContent_Shadcn_, CollapsibleTrigger_Shadcn_ } from 'ui'
const itemVariants = cva(
'flex flex-row items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-fd-accent hover:text-fd-accent-foreground [&_svg]:size-4'
)
export function Files({ className, ...props }: HTMLAttributes<HTMLDivElement>): React.ReactElement {
return (
<div className={cn('not-prose rounded-md border bg-fd-card p-2', className)} {...props}>
{props.children}
</div>
)
}
export interface FileProps extends HTMLAttributes<HTMLDivElement> {
name: string
icon?: ReactNode
}
export interface FolderProps extends HTMLAttributes<HTMLDivElement> {
name: string
disabled?: boolean
/**
* Open folder by default
*
* @defaultValue false
*/
defaultOpen?: boolean
}
export function File({
name,
icon = <FileIcon />,
className,
...rest
}: FileProps): React.ReactElement {
return (
<div className={cn(itemVariants({ className }))} {...rest}>
{icon}
{name}
</div>
)
}
export function Folder({ name, defaultOpen = false, ...props }: FolderProps): React.ReactElement {
const [open, setOpen] = useState(defaultOpen)
return (
<Collapsible_Shadcn_ open={open} onOpenChange={setOpen} {...props}>
<CollapsibleTrigger_Shadcn_ className={cn(itemVariants({ className: 'w-full' }))}>
{open ? <FolderOpen /> : <FolderIcon />}
{name}
</CollapsibleTrigger_Shadcn_>
<CollapsibleContent_Shadcn_>
<div className="ms-2 flex flex-col border-l ps-2">{props.children}</div>
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
)
}
@@ -15,7 +15,7 @@ export function OpenInV0Button({
asChild
>
<a
href={`https://v0.dev/chat/api/open?url=https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}/r/${name}.json`}
href={`https://v0.dev/chat/api/open?url=https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/r/${name}.json`}
target="_blank"
rel="noreferrer"
>
@@ -6,7 +6,7 @@ import { BlockItemCode } from './block-item-code'
export async function RegistryBlock({ itemName }: { itemName: string }) {
const registryPath = path.join(process.cwd(), 'public', 'r', `${itemName}.json`)
const tree = generateRegistryTree(itemName, registryPath)
const tree = generateRegistryTree(registryPath)
return <BlockItemCode files={tree} />
}
+18
View File
@@ -45,6 +45,12 @@ export const frameworkPages: Record<string, SidebarNavGroup> = {
nextjs: {
title: 'Next.js',
items: [
{
title: 'Client',
href: '/docs/nextjs/client',
items: [],
commandItemLabel: 'Supabase Client for Next.js',
},
{
title: 'Password-Based Auth',
href: '/docs/nextjs/password-based-auth',
@@ -85,6 +91,18 @@ export const frameworkPages: Record<string, SidebarNavGroup> = {
tanstack: {
title: 'Tanstack Start',
items: [
{
title: 'Client',
href: '/docs/tanstack/client',
items: [],
commandItemLabel: 'Supabase Client for Tanstack Start',
},
{
title: 'Password-Based Auth',
href: '/docs/tanstack/password-based-auth',
items: [],
commandItemLabel: 'Password-Based Auth for Tanstack Start',
},
{
title: 'Dropzone',
href: '/docs/tanstack/dropzone',
+14 -2
View File
@@ -1,6 +1,6 @@
---
title: Nextjs
description: Supabase client for Nextjs
title: Next.js
description: Supabase client for Next.js
---
## Installation
@@ -10,3 +10,15 @@ description: Supabase client for Nextjs
## Folder structure
<RegistryBlock itemName="supabase-client-nextjs" />
This block installs a Supabase client to be used by Next.js. It's meant to be used with app router and it's fully SSR compatible.
If you started a project through `npx create-next-app -e with-supabase` command, this block is already included in your project.
You need to add an .env file to your project:
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
If you're using a local instance of supabase, you can find these values by running `supabase start` or `supabase status` (if already running).
If you're using supabase.com project, you can find them on this page https://supabase.com/dashboard/project/_/settings/api or in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true) under App Frameworks.
@@ -1,6 +1,6 @@
---
title: Password-based Auth (Nextjs)
description: Password-based Auth block for Nextjs app
title: Password-based Auth (Next.js)
description: Password-based Auth block for Next.js app
---
## Installation
@@ -10,13 +10,15 @@ description: Password-based Auth block for Nextjs app
description="All needed components for the password based auth flow"
/>
This block includes the [Supabase client](/ui/docs/nextjs/client). When installing, you can skip overwriting it.
## Folder structure
<RegistryBlock itemName="password-based-auth-nextjs" />
## Usage
After installing the block in your Nextjs project, you'll have all needed pages and components to build a password-based authentication flow.
After installing the block in your Next.js project, you'll have all needed pages and components to build a password-based authentication flow.
There few more steps to finish the installation:
@@ -7,6 +7,12 @@ description: Supabase client for Tanstack Start
<BlockItem name="supabase-client-tanstack" description="Supabase Client for Tanstack Start" />
The easiest way to use Tanstack Start with Supabase is to follow the following steps:
1. Follow the [official guide](https://ui.shadcn.com/docs/installation/tanstack) for adding shadcn/ui to Tanstack Start
2. Install this block to get the Supabase clients
3. Add VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY to your .env file
## Folder structure
<RegistryBlock itemName="supabase-client-tanstack" />
@@ -0,0 +1,16 @@
---
title: Tanstack Start
description: Supabase client for Tanstack Start
---
## Installation
<BlockItem name="password-based-auth-tanstack" description="Supabase Client for Tanstack Start" />
1. Follow the steps in /ui/docs/tanstack/password-based-auth to setup your repo.
2. Try to access the /info route, you should be redirected to login screen. If you create an account and login and try accessing the /info page, you should see your email.
## Folder structure
<RegistryBlock itemName="password-based-auth-tanstack" />
+7 -4
View File
@@ -25,7 +25,7 @@ const DEFAULT_PATHS = {
/**
* Converts a flat registry array into a hierarchical file tree structure
*/
export function generateRegistryTree(blockName: string, registryPath: string): RegistryNode[] {
export function generateRegistryTree(registryPath: string): RegistryNode[] {
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf-8')) as { files: RegistryFile[] }
const tree: RegistryNode[] = []
@@ -45,8 +45,9 @@ export function generateRegistryTree(blockName: string, registryPath: string): R
// Remove any paths in the file content that point to the block directory.
const content = file.content
.replaceAll(/@\/registry\/blocks\/.+?\//gi, '@/')
.replaceAll(/@\/registry\//gi, '@/')
.replaceAll(/@\/registry\/default\/blocks\/.+?\//gi, '@/')
.replaceAll(/@\/registry\/default\//gi, '@/')
.replaceAll(/@\/clients\/.+?\//gi, '@/')
if (!node) {
node = {
@@ -76,7 +77,9 @@ function getDefaultPath(item: RegistryFile): string {
const type = item.type.toLowerCase() || ''
const basePath = DEFAULT_PATHS[type as keyof typeof DEFAULT_PATHS] || ''
// clean all paths that start with paths specific to this repo organization
const filePath = item.path.replace(/src\/registry\/blocks\/.+?\//, '')
const filePath = item.path
.replace(/registry\/default\/blocks\/.+?\//, '')
.replace(/registry\/default\/clients\/.+?\//, '')
return `${basePath}/${filePath}`
}
+1 -1
View File
@@ -3,7 +3,7 @@
"displayName": "Supabase Theme",
"semanticHighlighting": true,
"colors": {
"editor.background": "var(--code-highlight-color)",
"editor.background": "var(--background-surface-75) / 0.75",
"editor.foreground": "var(--code-foreground)",
"editor.lineHighlightBackground": "var(--code-highlight-color)",
"editorCursor.foreground": "var(--code-foreground)",
+3 -1
View File
@@ -50,6 +50,7 @@
"@shikijs/compat": "^1.1.7",
"@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.49.1",
"@tanstack/react-router": "^1.114.27",
"@tanstack/react-start": "^1.114.25",
"@types/lodash": "^4.17.16",
"@types/react": "^18.2.65",
@@ -68,6 +69,7 @@
"tsconfig": "workspace:*",
"tsx": "^4.19.3",
"typescript": "~5.5.0",
"unist-builder": "3.0.0"
"unist-builder": "3.0.0",
"vite": "^6.0.0"
}
}
+38 -22
View File
@@ -1,29 +1,45 @@
# Supabase Design System
Last updated: 2025-03-19T20:34:00.110Z
# Supabase UI Library
Last updated: 2025-03-23T14:40:10.160Z
## Overview
The Design System used in Supabase Studio.
Library of components for your project. The components integrate with Supabase and are shadcn compatible.
## Docs
- [Password-based Auth (Nextjs)](https://supabase-design-system.vercel.app/design-system/docs/blocks/password-based-auth-nextjs)
- Password-based Auth block for Nextjs app
- [Password-based Auth (React)](https://supabase-design-system.vercel.app/design-system/docs/blocks/password-based-auth-react)
- Password-based Auth block for React
- [Nextjs](https://supabase-design-system.vercel.app/design-system/docs/clients/nextjs)
- Supabase client for Nextjs
- [React](https://supabase-design-system.vercel.app/design-system/docs/clients/react)
- Supabase client for React
- [Remix](https://supabase-design-system.vercel.app/design-system/docs/clients/remix)
- Supabase client for Remix
- [Tanstack Start](https://supabase-design-system.vercel.app/design-system/docs/clients/tanstack)
- Supabase client for Tanstack Start
- [Dropzone (File Upload)](https://supabase-design-system.vercel.app/design-system/docs/components/dropzone)
- Displays a control for easier uploading of files directly to Supabase Storage
- [Realtime Cursor](https://supabase-design-system.vercel.app/design-system/docs/components/realtime-cursor)
- Real-time cursor sharing for collaborative applications
- [FAQ](https://supabase-design-system.vercel.app/design-system/docs/faq)
- [Prompts](https://supabase.com/ui/docs/ai-editors-rules/prompts)
- Rules for AI Code Editors for Supabase
- [FAQ](https://supabase.com/ui/docs/getting-started/faq)
- Frequently asked questions
- [Introduction](https://supabase-design-system.vercel.app/design-system/docs/getting-started/introduction)
- [Introduction](https://supabase.com/ui/docs/getting-started/introduction)
- Components
- [Quick Start](https://supabase-design-system.vercel.app/design-system/docs/index)
- [Quick Start](https://supabase.com/ui/docs/getting-started/quickstart)
- Components
- [Next.js](https://supabase.com/ui/docs/nextjs/client)
- Supabase client for Next.js
- [Dropzone (File Upload)](https://supabase.com/ui/docs/nextjs/dropzone)
- Displays a control for easier uploading of files directly to Supabase Storage
- [Password-based Auth (Next.js)](https://supabase.com/ui/docs/nextjs/password-based-auth)
- Password-based Auth block for Next.js app
- [Realtime Cursor](https://supabase.com/ui/docs/nextjs/realtime-cursor)
- Real-time cursor sharing for collaborative applications
- [Supabase Client for React Router](https://supabase.com/ui/docs/react-router/client)
- Supabase client for React Router
- [Dropzone (File Upload)](https://supabase.com/ui/docs/react-router/dropzone)
- Displays a control for easier uploading of files directly to Supabase Storage
- [Realtime Cursor](https://supabase.com/ui/docs/react-router/realtime-cursor)
- Real-time cursor sharing for collaborative applications
- [React](https://supabase.com/ui/docs/react/client)
- Supabase client for React
- [Dropzone (File Upload)](https://supabase.com/ui/docs/react/dropzone)
- Displays a control for easier uploading of files directly to Supabase Storage
- [Password-based Auth (React)](https://supabase.com/ui/docs/react/password-based-auth)
- Password-based Auth block for React
- [Realtime Cursor](https://supabase.com/ui/docs/react/realtime-cursor)
- Real-time cursor sharing for collaborative applications
- [Tanstack Start](https://supabase.com/ui/docs/tanstack/client)
- Supabase client for Tanstack Start
- [Dropzone (File Upload)](https://supabase.com/ui/docs/tanstack/dropzone)
- Displays a control for easier uploading of files directly to Supabase Storage
- [Tanstack Start](https://supabase.com/ui/docs/tanstack/password-based-auth)
- Supabase client for Tanstack Start
- [Realtime Cursor](https://supabase.com/ui/docs/tanstack/realtime-cursor)
- Real-time cursor sharing for collaborative applications
+1 -1
View File
@@ -27,7 +27,7 @@
},
{
"path": "registry/default/clients/react/lib/supabase/client.ts",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n )\n}\n",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"type": "registry:lib"
}
]
@@ -7,7 +7,8 @@
"dependencies": [
"react-dropzone",
"lucide-react",
"@supabase/ssr@latest"
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"registryDependencies": [
"button",
@@ -27,12 +28,12 @@
},
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
"content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_ANON_KEY!\n )\n}\n",
"type": "registry:lib"
},
{
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function getSupabaseServerClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"type": "registry:lib"
}
]
@@ -22,7 +22,7 @@
},
{
"path": "registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx",
"content": "export default function Page({ searchParams }: { searchParams: { error: string } }) {\n return <div>Sorry, something went wrong. Code error: {searchParams.error}</div>\n}\n",
"content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\n\nexport default function Page({ searchParams }: { searchParams: { error: string } }) {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sorry, something went wrong.</CardTitle>\n </CardHeader>\n <CardContent>\n {searchParams?.error ? (\n <p className=\"text-sm text-muted-foreground\">Code error: {searchParams.error}</p>\n ) : (\n <p className=\"text-sm text-muted-foreground\">An unspecified error occurred.</p>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:page",
"target": "app/auth/error/page.tsx"
},
@@ -57,7 +57,7 @@
},
{
"path": "registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx",
"content": "export default function Page() {\n return <div>Sign up success</div>\n}\n",
"content": "import {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\n\nexport default function Page() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Thank you for signing up!</CardTitle>\n <CardDescription>Check your email to confirm</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:page",
"target": "app/sign-up-success/page.tsx"
},
@@ -80,13 +80,28 @@
},
{
"path": "registry/default/blocks/password-based-auth-nextjs/components/forgot-password-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 { useState } from 'react'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we&apos;ll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Sending...' : 'Send reset email'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link href=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
"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 { useState } from 'react'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n {success ? (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Check Your Email</CardTitle>\n <CardDescription>Password reset instructions sent</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n If you registered using your email and password, you will receive a password reset\n email.\n </p>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we&apos;ll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Sending...' : 'Send reset email'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link href=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n )}\n </div>\n )\n}\n",
"type": "registry:component"
},
{
"path": "registry/default/blocks/password-based-auth-nextjs/components/update-password-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 { useRouter } from 'next/navigation'\nimport { useState } from 'react'\n\nexport function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const router = useRouter()\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.updateUser({ password })\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>Please enter your new password below.</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"password\">New password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Saving...' : 'Save new password'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <a href=\"/login\" className=\"underline underline-offset-4\">\n Login\n </a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\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_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 const supabase = createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_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.getUser(). A simple mistake could make it very hard to debug\n // issues with users being randomly logged out.\n\n // IMPORTANT: DO NOT REMOVE auth.getUser()\n\n const {\n data: { user },\n } = await supabase.auth.getUser()\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 = '/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\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_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"
}
]
}
@@ -36,7 +36,7 @@
},
{
"path": "registry/default/clients/react/lib/supabase/client.ts",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n )\n}\n",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"type": "registry:lib"
}
]
@@ -0,0 +1,108 @@
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"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",
"dependencies": [
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"registryDependencies": [
"button",
"card",
"input",
"label"
],
"files": [
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/login.tsx",
"content": "import { LoginForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/login-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/login')({\n component: Login,\n})\n\nfunction Login() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <LoginForm />\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/login.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx",
"content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/auth/error')({\n component: AuthError,\n validateSearch: (params) => {\n if (params.error && typeof params.error === 'string') {\n return { error: params.error }\n }\n return null\n },\n})\n\nfunction AuthError() {\n const params = Route.useSearch()\n\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sorry, something went wrong.</CardTitle>\n </CardHeader>\n <CardContent>\n {params?.error ? (\n <p className=\"text-sm text-muted-foreground\">Code error: {params.error}</p>\n ) : (\n <p className=\"text-sm text-muted-foreground\">An unspecified error occurred.</p>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/auth/error.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx",
"content": "import { fetchUser } from '@/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn'\nimport { createFileRoute, redirect } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/_protected')({\n beforeLoad: async () => {\n const user = await fetchUser()\n\n if (!user) {\n throw redirect({ to: '/login' })\n }\n\n return {\n user,\n }\n },\n})\n",
"type": "registry:file",
"target": "routes/_protected.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx",
"content": "import { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/_protected/info')({\n component: Info,\n loader: async ({ context }) => {\n return {\n user: context.user!,\n }\n },\n})\n\nfunction Info() {\n const data = Route.useLoaderData()\n\n return <p>Hello {data.user.email}</p>\n}\n",
"type": "registry:file",
"target": "routes/_protected/info.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts",
"content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport { type EmailOtpType } from '@supabase/supabase-js'\nimport { createFileRoute, redirect } from '@tanstack/react-router'\nimport { createServerFn } from '@tanstack/react-start'\nimport { getWebRequest } from '@tanstack/react-start/server'\n\nconst confirmFn = createServerFn({ method: 'GET' })\n .validator((searchParams: unknown) => {\n if (\n searchParams &&\n typeof searchParams === 'object' &&\n 'token_hash' in searchParams &&\n 'type' in searchParams &&\n 'next' in searchParams\n ) {\n return searchParams\n }\n throw new Error('Invalid search params')\n })\n .handler(async (ctx) => {\n const request = getWebRequest()\n\n if (!request) {\n throw redirect({ to: `/auth/error`, search: { error: 'No request' } })\n }\n\n const searchParams = ctx.data\n const token_hash = searchParams['token_hash'] as string\n const type = searchParams['type'] as EmailOtpType | null\n const next = (searchParams['next'] ?? '/') as string\n\n if (token_hash && type) {\n const supabase = createClient()\n\n const { error } = await supabase.auth.verifyOtp({\n type,\n token_hash,\n })\n console.log(error?.message)\n if (!error) {\n // redirect user to specified redirect URL or root of app\n throw redirect({ href: next })\n } else {\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: error?.message },\n })\n }\n }\n\n // redirect the user to an error page with some instructions\n throw redirect({\n to: `/auth/error`,\n search: { error: 'No token hash or type' },\n })\n })\n\nexport const Route = createFileRoute('/auth/confirm')({\n preload: false,\n loader: (opts) => confirmFn({ data: opts.location.search }),\n})\n",
"type": "registry:file",
"target": "routes/auth/confirm.ts"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx",
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/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, useNavigate } from '@tanstack/react-router'\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<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\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 await navigate({ to: '/info' })\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>Enter your email below to login to your account</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleLogin}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <Link\n to=\"/forgot-password\"\n className=\"ml-auto inline-block text-sm underline-offset-4 hover:underline\"\n >\n Forgot your password?\n </Link>\n </div>\n <Input\n id=\"password\"\n type=\"password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Logging in...' : 'Login'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don&apos;t have an account?{' '}\n <Link to=\"/sign-up\" className=\"underline underline-offset-4\">\n Sign up\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
"type": "registry:component"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx",
"content": "import { SignUpForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/sign-up')({\n component: SignUp,\n})\n\nfunction SignUp() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <SignUpForm />\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/sign-up.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx",
"content": "import {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/sign-up-success')({\n component: SignUpSuccess,\n})\n\nfunction SignUpSuccess() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Thank you for signing up!</CardTitle>\n <CardDescription>Check your email to confirm</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/sign-up-success.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx",
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/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, useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n })\n if (error) throw error\n await navigate({ to: '/sign-up-success' })\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sign up</CardTitle>\n <CardDescription>Create a new account</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSignUp}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n </div>\n <Input\n id=\"password\"\n type=\"password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"repeat-password\">Repeat Password</Label>\n </div>\n <Input\n id=\"repeat-password\"\n type=\"password\"\n required\n value={repeatPassword}\n onChange={(e) => setRepeatPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Creating an account...' : 'Sign up'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link to=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
"type": "registry:component"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx",
"content": "import { ForgotPasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/forgot-password')({\n component: ForgotPassword,\n})\n\nfunction ForgotPassword() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <ForgotPasswordForm />\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/forgot-password.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx",
"content": "import { UpdatePasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/update-password-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/update-password')({\n component: UpdatePassword,\n})\n\nfunction UpdatePassword() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <UpdatePasswordForm />\n </div>\n </div>\n )\n}\n",
"type": "registry:file",
"target": "routes/update-password.tsx"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx",
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/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 '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [success, setSuccess] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n setSuccess(true)\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n {success ? (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Check Your Email</CardTitle>\n <CardDescription>Password reset instructions sent</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n If you registered using your email and password, you will receive a password reset\n email.\n </p>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we&apos;ll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Sending...' : 'Send reset email'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link to=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n )}\n </div>\n )\n}\n",
"type": "registry:component"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx",
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/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 { useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.updateUser({ password })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n await navigate({ to: '/info' })\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 <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>Please enter your new password below.</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"password\">New password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Saving...' : 'Save new password'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <a href=\"/login\" className=\"underline underline-offset-4\">\n Login\n </a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
"type": "registry:component"
},
{
"path": "registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts",
"content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport type { Factor, User } from '@supabase/supabase-js'\nimport { createServerFn } from '@tanstack/react-start'\ntype SSRSafeUser = User & {\n factors: (Factor & { factor_type: 'phone' | 'totp' })[]\n}\n\nexport const fetchUser: () => Promise<SSRSafeUser | null> = createServerFn({\n method: 'GET',\n}).handler(async () => {\n const supabase = createClient()\n const { data, error } = await supabase.auth.getUser()\n\n if (error) {\n return null\n }\n\n return data.user as SSRSafeUser\n})\n",
"type": "registry:lib"
},
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_ANON_KEY!\n )\n}\n",
"type": "registry:lib"
},
{
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"type": "registry:lib"
}
]
}
@@ -27,7 +27,7 @@
},
{
"path": "registry/default/clients/react/lib/supabase/client.ts",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n )\n}\n",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"type": "registry:lib"
}
]
@@ -6,7 +6,8 @@
"description": "Component which renders realtime cursors from other users in a room.",
"dependencies": [
"lucide-react",
"@supabase/ssr@latest"
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"registryDependencies": [],
"files": [
@@ -27,12 +28,12 @@
},
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
"content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_ANON_KEY!\n )\n}\n",
"type": "registry:lib"
},
{
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function getSupabaseServerClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"type": "registry:lib"
}
]
@@ -11,7 +11,7 @@
"files": [
{
"path": "registry/default/clients/react/lib/supabase/client.ts",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!\n )\n}\n",
"content": "import { createClient as createSupabaseClient } from '@supabase/supabase-js'\n\nexport function createClient() {\n return createSupabaseClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"type": "registry:lib"
}
]
@@ -5,18 +5,19 @@
"title": "Supabase Client for Tanstack Start",
"description": "",
"dependencies": [
"@supabase/ssr@latest"
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"registryDependencies": [],
"files": [
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
"content": "import { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)\n}\n",
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_ANON_KEY!\n )\n}\n",
"type": "registry:lib"
},
{
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function getSupabaseServerClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
"type": "registry:lib"
}
]
+103 -3
View File
@@ -71,6 +71,18 @@
{
"path": "registry/default/blocks/password-based-auth-nextjs/components/update-password-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"
}
]
},
@@ -104,6 +116,89 @@
],
"dependencies": ["@supabase/supabase-js@latest"]
},
{
"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/info.tsx",
"type": "registry:file",
"target": "routes/_protected/info.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"
}
]
},
{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "dropzone-nextjs",
@@ -192,7 +287,12 @@
"title": "Dropzone (File Upload)",
"description": "Displays a control for easier uploading of files directly to Supabase Storage.",
"registryDependencies": ["button", "tooltip", "progress"],
"dependencies": ["react-dropzone", "lucide-react", "@supabase/ssr@latest"],
"dependencies": [
"react-dropzone",
"lucide-react",
"@supabase/ssr@latest",
"@supabase/supabase-js@latest"
],
"files": [
{
"path": "registry/default/blocks/dropzone/components/dropzone.tsx",
@@ -312,7 +412,7 @@
"title": "Realtime Cursor",
"description": "Component which renders realtime cursors from other users in a room.",
"registryDependencies": [],
"dependencies": ["lucide-react", "@supabase/ssr@latest"],
"dependencies": ["lucide-react", "@supabase/ssr@latest", "@supabase/supabase-js@latest"],
"files": [
{
"path": "registry/default/blocks/realtime-cursor/components/cursor.tsx",
@@ -400,7 +500,7 @@
"title": "Supabase Client for Tanstack Start",
"description": "",
"registryDependencies": [],
"dependencies": ["@supabase/ssr@latest"],
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
"files": [
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
+12 -5
View File
@@ -1,8 +1,10 @@
import { type Registry } from 'shadcn/registry'
import { type Registry, type RegistryItem } from 'shadcn/registry'
import { clients } from './clients'
import dropzone from './default/blocks/dropzone/registry-item.json' assert { type: 'json' }
import passwordBasedAuthNextjs from './default/blocks/password-based-auth-nextjs/registry-item.json' assert { type: 'json' }
import passwordBasedAuthReact from './default/blocks/password-based-auth-react/registry-item.json' assert { type: 'json' }
import passwordBasedAuthTanstack from './default/blocks/password-based-auth-tanstack/registry-item.json' assert { type: 'json' }
import realtimeCursor from './default/blocks/realtime-cursor/registry-item.json' assert { type: 'json' }
import { registryItemAppend } from './utils'
@@ -18,9 +20,14 @@ const combine = (component: Registry['items'][number]) => {
})
}
const nextjsClient = clients.find((client) => client.name === 'supabase-client-nextjs')
const reactClient = clients.find((client) => client.name === 'supabase-client-react')
const tanstackClient = clients.find((client) => client.name === 'supabase-client-tanstack')
export const blocks = [
passwordBasedAuthNextjs,
passwordBasedAuthReact,
...combine(dropzone as Registry['items'][number]),
...combine(realtimeCursor as Registry['items'][number]),
registryItemAppend(passwordBasedAuthNextjs as RegistryItem, [nextjsClient!]),
registryItemAppend(passwordBasedAuthReact as RegistryItem, [reactClient!]),
registryItemAppend(passwordBasedAuthTanstack as RegistryItem, [tanstackClient!]),
...combine(dropzone as RegistryItem),
...combine(realtimeCursor as RegistryItem),
] as Registry['items']
@@ -1,3 +1,24 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
export default function Page({ searchParams }: { searchParams: { error: string } }) {
return <div>Sorry, something went wrong. Code error: {searchParams.error}</div>
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Sorry, something went wrong.</CardTitle>
</CardHeader>
<CardContent>
{searchParams?.error ? (
<p className="text-sm text-muted-foreground">Code error: {searchParams.error}</p>
) : (
<p className="text-sm text-muted-foreground">An unspecified error occurred.</p>
)}
</CardContent>
</Card>
</div>
</div>
</div>
)
}
@@ -1,3 +1,30 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/registry/default/components/ui/card'
export default function Page() {
return <div>Sign up success</div>
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Thank you for signing up!</CardTitle>
<CardDescription>Check your email to confirm</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
You've successfully signed up. Please check your email to confirm your account
before signing in.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)
}
@@ -18,6 +18,7 @@ import { useState } from 'react'
export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const [email, setEmail] = useState('')
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const supabase = createClient()
@@ -32,6 +33,7 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
redirectTo: 'http://localhost:3000/update-password',
})
if (error) throw error
setSuccess(true)
} catch (error: unknown) {
setError(error instanceof Error ? error.message : 'An error occurred')
} finally {
@@ -41,41 +43,56 @@ export function ForgotPasswordForm({ className, ...props }: React.ComponentProps
return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
<CardDescription>
Type in your email and we&apos;ll send you a link to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleForgotPassword}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{success ? (
<Card>
<CardHeader>
<CardTitle className="text-2xl">Check Your Email</CardTitle>
<CardDescription>Password reset instructions sent</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
If you registered using your email and password, you will receive a password reset
email.
</p>
</CardContent>
</Card>
) : (
<Card>
<CardHeader>
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
<CardDescription>
Type in your email and we&apos;ll send you a link to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleForgotPassword}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send reset email'}
</Button>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send reset email'}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Already have an account?{' '}
<Link href="/login" className="underline underline-offset-4">
Login
</Link>
</div>
</form>
</CardContent>
</Card>
<div className="mt-4 text-center text-sm">
Already have an account?{' '}
<Link href="/login" className="underline underline-offset-4">
Login
</Link>
</div>
</form>
</CardContent>
</Card>
)}
</div>
)
}
@@ -0,0 +1,96 @@
import { cn } from '@/lib/utils'
import { createClient } from '@/registry/default/clients/tanstack/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 '@tanstack/react-router'
import { useState } from 'react'
export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const [email, setEmail] = useState('')
const [error, setError] = useState<string | null>(null)
const [success, setSuccess] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const supabase = createClient()
const handleForgotPassword = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)
try {
// The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration
const { error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'http://localhost:3000/update-password',
})
if (error) throw error
setSuccess(true)
} catch (error: unknown) {
setError(error instanceof Error ? error.message : 'An error occurred')
} finally {
setIsLoading(false)
}
}
return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
{success ? (
<Card>
<CardHeader>
<CardTitle className="text-2xl">Check Your Email</CardTitle>
<CardDescription>Password reset instructions sent</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
If you registered using your email and password, you will receive a password reset
email.
</p>
</CardContent>
</Card>
) : (
<Card>
<CardHeader>
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
<CardDescription>
Type in your email and we&apos;ll send you a link to reset your password
</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleForgotPassword}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send reset email'}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Already have an account?{' '}
<Link to="/login" className="underline underline-offset-4">
Login
</Link>
</div>
</form>
</CardContent>
</Card>
)}
</div>
)
}
@@ -0,0 +1,99 @@
import { cn } from '@/lib/utils'
import { createClient } from '@/registry/default/clients/tanstack/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, useNavigate } from '@tanstack/react-router'
import { useState } from 'react'
export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate()
const supabase = createClient()
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
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.
await navigate({ to: '/info' })
} catch (error: unknown) {
setError(error instanceof Error ? error.message : 'An error occurred')
} finally {
setIsLoading(false)
}
}
return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Login</CardTitle>
<CardDescription>Enter your email below to login to your account</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleLogin}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<Link
to="/forgot-password"
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
>
Forgot your password?
</Link>
</div>
<Input
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{' '}
<Link to="/sign-up" className="underline underline-offset-4">
Sign up
</Link>
</div>
</form>
</CardContent>
</Card>
</div>
)
}
@@ -0,0 +1,110 @@
import { cn } from '@/lib/utils'
import { createClient } from '@/registry/default/clients/tanstack/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, useNavigate } from '@tanstack/react-router'
import { useState } from 'react'
export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [repeatPassword, setRepeatPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate()
const supabase = createClient()
const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault()
setError(null)
if (password !== repeatPassword) {
setError('Passwords do not match')
return
}
setIsLoading(true)
try {
const { error } = await supabase.auth.signUp({
email,
password,
})
if (error) throw error
await navigate({ to: '/sign-up-success' })
} catch (error: unknown) {
setError(error instanceof Error ? error.message : 'An error occurred')
} finally {
setIsLoading(false)
}
}
return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Sign up</CardTitle>
<CardDescription>Create a new account</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSignUp}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
</div>
<Input
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="repeat-password">Repeat Password</Label>
</div>
<Input
id="repeat-password"
type="password"
required
value={repeatPassword}
onChange={(e) => setRepeatPassword(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Creating an account...' : 'Sign up'}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Already have an account?{' '}
<Link to="/login" className="underline underline-offset-4">
Login
</Link>
</div>
</form>
</CardContent>
</Card>
</div>
)
}
@@ -0,0 +1,77 @@
import { cn } from '@/lib/utils'
import { createClient } from '@/registry/default/clients/tanstack/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 { useNavigate } from '@tanstack/react-router'
import { useState } from 'react'
export function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
const [password, setPassword] = useState('')
const [error, setError] = useState<string | null>(null)
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate()
const supabase = createClient()
const handleForgotPassword = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
setError(null)
try {
const { error } = await supabase.auth.updateUser({ password })
if (error) throw error
// Update this route to redirect to an authenticated route. The user already has an active session.
await navigate({ to: '/info' })
} catch (error: unknown) {
setError(error instanceof Error ? error.message : 'An error occurred')
} finally {
setIsLoading(false)
}
}
return (
<div className={cn('flex flex-col gap-6', className)} {...props}>
<Card>
<CardHeader>
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
<CardDescription>Please enter your new password below.</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleForgotPassword}>
<div className="flex flex-col gap-6">
<div className="grid gap-2">
<Label htmlFor="password">New password</Label>
<Input
id="password"
type="password"
placeholder="New password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? 'Saving...' : 'Save new password'}
</Button>
</div>
<div className="mt-4 text-center text-sm">
Already have an account?{' '}
<a href="/login" className="underline underline-offset-4">
Login
</a>
</div>
</form>
</CardContent>
</Card>
</div>
)
}
@@ -0,0 +1,19 @@
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'
import type { Factor, User } from '@supabase/supabase-js'
import { createServerFn } from '@tanstack/react-start'
type SSRSafeUser = User & {
factors: (Factor & { factor_type: 'phone' | 'totp' })[]
}
export const fetchUser: () => Promise<SSRSafeUser | null> = createServerFn({
method: 'GET',
}).handler(async () => {
const supabase = createClient()
const { data, error } = await supabase.auth.getUser()
if (error) {
return null
}
return data.user as SSRSafeUser
})
@@ -0,0 +1,75 @@
{
"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"],
"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/info.tsx",
"type": "registry:file",
"target": "routes/_protected/info.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"
}
]
}
@@ -0,0 +1,338 @@
// SUPABASE NOTE: THIS FILE WAS ADDED TO SATISFY TANSTACK BUILD CONFIGURATION. IT IS NOT INCLUDED IN THE BLOCK.
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
// Import Routes
import { Route as rootRoute } from './routes/__root'
import { Route as ProtectedImport } from './routes/_protected'
import { Route as ProtectedInfoImport } from './routes/_protected/info'
import { Route as AuthConfirmImport } from './routes/auth/confirm'
import { Route as AuthErrorImport } from './routes/auth/error'
import { Route as ForgotPasswordImport } from './routes/forgot-password'
import { Route as IndexImport } from './routes/index'
import { Route as LoginImport } from './routes/login'
import { Route as SignUpImport } from './routes/sign-up'
import { Route as SignUpSuccessImport } from './routes/sign-up-success'
import { Route as UpdatePasswordImport } from './routes/update-password'
// Create/Update Routes
const UpdatePasswordRoute = UpdatePasswordImport.update({
id: '/update-password',
path: '/update-password',
getParentRoute: () => rootRoute,
} as any)
const SignUpSuccessRoute = SignUpSuccessImport.update({
id: '/sign-up-success',
path: '/sign-up-success',
getParentRoute: () => rootRoute,
} as any)
const SignUpRoute = SignUpImport.update({
id: '/sign-up',
path: '/sign-up',
getParentRoute: () => rootRoute,
} as any)
const LoginRoute = LoginImport.update({
id: '/login',
path: '/login',
getParentRoute: () => rootRoute,
} as any)
const ForgotPasswordRoute = ForgotPasswordImport.update({
id: '/forgot-password',
path: '/forgot-password',
getParentRoute: () => rootRoute,
} as any)
const ProtectedRoute = ProtectedImport.update({
id: '/_protected',
getParentRoute: () => rootRoute,
} as any)
const IndexRoute = IndexImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRoute,
} as any)
const AuthErrorRoute = AuthErrorImport.update({
id: '/auth/error',
path: '/auth/error',
getParentRoute: () => rootRoute,
} as any)
const AuthConfirmRoute = AuthConfirmImport.update({
id: '/auth/confirm',
path: '/auth/confirm',
getParentRoute: () => rootRoute,
} as any)
const ProtectedInfoRoute = ProtectedInfoImport.update({
id: '/info',
path: '/info',
getParentRoute: () => ProtectedRoute,
} as any)
// Populate the FileRoutesByPath interface
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexImport
parentRoute: typeof rootRoute
}
'/_protected': {
id: '/_protected'
path: ''
fullPath: ''
preLoaderRoute: typeof ProtectedImport
parentRoute: typeof rootRoute
}
'/forgot-password': {
id: '/forgot-password'
path: '/forgot-password'
fullPath: '/forgot-password'
preLoaderRoute: typeof ForgotPasswordImport
parentRoute: typeof rootRoute
}
'/login': {
id: '/login'
path: '/login'
fullPath: '/login'
preLoaderRoute: typeof LoginImport
parentRoute: typeof rootRoute
}
'/sign-up': {
id: '/sign-up'
path: '/sign-up'
fullPath: '/sign-up'
preLoaderRoute: typeof SignUpImport
parentRoute: typeof rootRoute
}
'/sign-up-success': {
id: '/sign-up-success'
path: '/sign-up-success'
fullPath: '/sign-up-success'
preLoaderRoute: typeof SignUpSuccessImport
parentRoute: typeof rootRoute
}
'/update-password': {
id: '/update-password'
path: '/update-password'
fullPath: '/update-password'
preLoaderRoute: typeof UpdatePasswordImport
parentRoute: typeof rootRoute
}
'/_protected/info': {
id: '/_protected/info'
path: '/info'
fullPath: '/info'
preLoaderRoute: typeof ProtectedInfoImport
parentRoute: typeof ProtectedImport
}
'/auth/confirm': {
id: '/auth/confirm'
path: '/auth/confirm'
fullPath: '/auth/confirm'
preLoaderRoute: typeof AuthConfirmImport
parentRoute: typeof rootRoute
}
'/auth/error': {
id: '/auth/error'
path: '/auth/error'
fullPath: '/auth/error'
preLoaderRoute: typeof AuthErrorImport
parentRoute: typeof rootRoute
}
}
}
// Create and export the route tree
interface ProtectedRouteChildren {
ProtectedInfoRoute: typeof ProtectedInfoRoute
}
const ProtectedRouteChildren: ProtectedRouteChildren = {
ProtectedInfoRoute: ProtectedInfoRoute,
}
const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren(ProtectedRouteChildren)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'': typeof ProtectedRouteWithChildren
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/sign-up': typeof SignUpRoute
'/sign-up-success': typeof SignUpSuccessRoute
'/update-password': typeof UpdatePasswordRoute
'/info': typeof ProtectedInfoRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/error': typeof AuthErrorRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'': typeof ProtectedRouteWithChildren
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/sign-up': typeof SignUpRoute
'/sign-up-success': typeof SignUpSuccessRoute
'/update-password': typeof UpdatePasswordRoute
'/info': typeof ProtectedInfoRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/error': typeof AuthErrorRoute
}
export interface FileRoutesById {
__root__: typeof rootRoute
'/': typeof IndexRoute
'/_protected': typeof ProtectedRouteWithChildren
'/forgot-password': typeof ForgotPasswordRoute
'/login': typeof LoginRoute
'/sign-up': typeof SignUpRoute
'/sign-up-success': typeof SignUpSuccessRoute
'/update-password': typeof UpdatePasswordRoute
'/_protected/info': typeof ProtectedInfoRoute
'/auth/confirm': typeof AuthConfirmRoute
'/auth/error': typeof AuthErrorRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| ''
| '/forgot-password'
| '/login'
| '/sign-up'
| '/sign-up-success'
| '/update-password'
| '/info'
| '/auth/confirm'
| '/auth/error'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| ''
| '/forgot-password'
| '/login'
| '/sign-up'
| '/sign-up-success'
| '/update-password'
| '/info'
| '/auth/confirm'
| '/auth/error'
id:
| '__root__'
| '/'
| '/_protected'
| '/forgot-password'
| '/login'
| '/sign-up'
| '/sign-up-success'
| '/update-password'
| '/_protected/info'
| '/auth/confirm'
| '/auth/error'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
ProtectedRoute: typeof ProtectedRouteWithChildren
ForgotPasswordRoute: typeof ForgotPasswordRoute
LoginRoute: typeof LoginRoute
SignUpRoute: typeof SignUpRoute
SignUpSuccessRoute: typeof SignUpSuccessRoute
UpdatePasswordRoute: typeof UpdatePasswordRoute
AuthConfirmRoute: typeof AuthConfirmRoute
AuthErrorRoute: typeof AuthErrorRoute
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ProtectedRoute: ProtectedRouteWithChildren,
ForgotPasswordRoute: ForgotPasswordRoute,
LoginRoute: LoginRoute,
SignUpRoute: SignUpRoute,
SignUpSuccessRoute: SignUpSuccessRoute,
UpdatePasswordRoute: UpdatePasswordRoute,
AuthConfirmRoute: AuthConfirmRoute,
AuthErrorRoute: AuthErrorRoute,
}
export const routeTree = rootRoute
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
/* ROUTE_MANIFEST_START
{
"routes": {
"__root__": {
"filePath": "__root.tsx",
"children": [
"/",
"/_protected",
"/forgot-password",
"/login",
"/sign-up",
"/sign-up-success",
"/update-password",
"/auth/confirm",
"/auth/error"
]
},
"/": {
"filePath": "index.tsx"
},
"/_protected": {
"filePath": "_protected.tsx",
"children": [
"/_protected/info"
]
},
"/forgot-password": {
"filePath": "forgot-password.tsx"
},
"/login": {
"filePath": "login.tsx"
},
"/sign-up": {
"filePath": "sign-up.tsx"
},
"/sign-up-success": {
"filePath": "sign-up-success.tsx"
},
"/update-password": {
"filePath": "update-password.tsx"
},
"/_protected/info": {
"filePath": "_protected/info.tsx",
"parent": "/_protected"
},
"/auth/confirm": {
"filePath": "auth/confirm.ts"
},
"/auth/error": {
"filePath": "auth/error.tsx"
}
}
}
ROUTE_MANIFEST_END */
@@ -0,0 +1,16 @@
import { fetchUser } from '@/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn'
import { createFileRoute, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_protected')({
beforeLoad: async () => {
const user = await fetchUser()
if (!user) {
throw redirect({ to: '/login' })
}
return {
user,
}
},
})
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/_protected/info')({
component: Info,
loader: async ({ context }) => {
return {
user: context.user!,
}
},
})
function Info() {
const data = Route.useLoaderData()
return <p>Hello {data.user.email}</p>
}
@@ -0,0 +1,62 @@
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'
import { type EmailOtpType } from '@supabase/supabase-js'
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { getWebRequest } from '@tanstack/react-start/server'
const confirmFn = createServerFn({ method: 'GET' })
.validator((searchParams: unknown) => {
if (
searchParams &&
typeof searchParams === 'object' &&
'token_hash' in searchParams &&
'type' in searchParams &&
'next' in searchParams
) {
return searchParams
}
throw new Error('Invalid search params')
})
.handler(async (ctx) => {
const request = getWebRequest()
if (!request) {
throw redirect({ to: `/auth/error`, search: { error: 'No request' } })
}
const searchParams = ctx.data
const token_hash = searchParams['token_hash'] as string
const type = searchParams['type'] as EmailOtpType | null
const next = (searchParams['next'] ?? '/') as string
if (token_hash && type) {
const supabase = createClient()
const { error } = await supabase.auth.verifyOtp({
type,
token_hash,
})
console.log(error?.message)
if (!error) {
// redirect user to specified redirect URL or root of app
throw redirect({ href: next })
} else {
// redirect the user to an error page with some instructions
throw redirect({
to: `/auth/error`,
search: { error: error?.message },
})
}
}
// redirect the user to an error page with some instructions
throw redirect({
to: `/auth/error`,
search: { error: 'No token hash or type' },
})
})
export const Route = createFileRoute('/auth/confirm')({
preload: false,
loader: (opts) => confirmFn({ data: opts.location.search }),
})
@@ -0,0 +1,37 @@
import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/auth/error')({
component: AuthError,
validateSearch: (params) => {
if (params.error && typeof params.error === 'string') {
return { error: params.error }
}
return null
},
})
function AuthError() {
const params = Route.useSearch()
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Sorry, something went wrong.</CardTitle>
</CardHeader>
<CardContent>
{params?.error ? (
<p className="text-sm text-muted-foreground">Code error: {params.error}</p>
) : (
<p className="text-sm text-muted-foreground">An unspecified error occurred.</p>
)}
</CardContent>
</Card>
</div>
</div>
</div>
)
}
@@ -0,0 +1,16 @@
import { ForgotPasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/forgot-password')({
component: ForgotPassword,
})
function ForgotPassword() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<ForgotPasswordForm />
</div>
</div>
)
}
@@ -0,0 +1,16 @@
import { LoginForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/login-form'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/login')({
component: Login,
})
function Login() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<LoginForm />
</div>
</div>
)
}
@@ -0,0 +1,35 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/registry/default/components/ui/card'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/sign-up-success')({
component: SignUpSuccess,
})
function SignUpSuccess() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Thank you for signing up!</CardTitle>
<CardDescription>Check your email to confirm</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm text-muted-foreground">
You've successfully signed up. Please check your email to confirm your account
before signing in.
</p>
</CardContent>
</Card>
</div>
</div>
</div>
)
}
@@ -0,0 +1,16 @@
import { SignUpForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/sign-up')({
component: SignUp,
})
function SignUp() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<SignUpForm />
</div>
</div>
)
}
@@ -0,0 +1,16 @@
import { UpdatePasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/update-password-form'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/update-password')({
component: UpdatePassword,
})
function UpdatePassword() {
return (
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
<div className="w-full max-w-sm">
<UpdatePasswordForm />
</div>
</div>
)
}
@@ -1,8 +1,5 @@
import { createClient as createSupabaseClient } from '@supabase/supabase-js'
export function createClient() {
return createSupabaseClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
return createSupabaseClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)
}
@@ -1,5 +1,9 @@
/// <reference types="vite/types/importMeta.d.ts" />
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!)
return createBrowserClient(
import.meta.env.VITE_SUPABASE_URL!,
import.meta.env.VITE_SUPABASE_ANON_KEY!
)
}
@@ -1,7 +1,7 @@
import { createServerClient } from '@supabase/ssr'
import { parseCookies, setCookie } from '@tanstack/react-start/server'
export function getSupabaseServerClient() {
export function createClient() {
return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {
cookies: {
getAll() {
@@ -5,7 +5,7 @@
"title": "Supabase Client for Tanstack Start",
"description": "",
"registryDependencies": [],
"dependencies": ["@supabase/ssr@latest"],
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
"files": [
{
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
+2 -1
View File
@@ -47,11 +47,12 @@ function getMdxFiles(dir: string): string[] {
// Function to get all MDX files and their metadata
function getDocFiles(): DocMeta[] {
const docsDir = path.join(process.cwd(), 'content/docs')
const mdxFiles = getMdxFiles(docsDir)
const mdxFiles = getMdxFiles(docsDir).sort((a, b) => a.localeCompare(b))
const docs: DocMeta[] = []
for (const fullPath of mdxFiles) {
console.log(fullPath)
const content = fs.readFileSync(fullPath, 'utf-8')
const { title, description } = extractFrontmatter(content)
+7 -7
View File
@@ -105,9 +105,9 @@ file_size_limit = "50MiB"
enabled = true
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://127.0.0.1:3000"
site_url = "http://localhost:3000"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://127.0.0.1:3000"]
additional_redirect_urls = ["https://127.0.0.1:3000/", "https://localhost:3000/"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
jwt_expiry = 3600
# If disabled, the refresh token will never expire.
@@ -155,7 +155,7 @@ enable_signup = true
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false
enable_confirmations = true
# If enabled, users will need to reauthenticate or have logged in recently to change their password.
secure_password_change = false
# Controls the minimum amount of time that must pass before sending another signup confirmation or password reset email.
@@ -176,9 +176,9 @@ otp_expiry = 3600
# sender_name = "Admin"
# Uncomment to customize email template
# [auth.email.template.invite]
# subject = "You have been invited"
# content_path = "./supabase/templates/invite.html"
[auth.email.template.confirmation]
subject = "You have been invited"
content_path = "./supabase/templates/confirmation.html"
[auth.sms]
# Allow/disallow new user signups via SMS to your project.
@@ -246,7 +246,7 @@ client_id = ""
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
# Overrides the default auth redirectUrl.
redirect_uri = ""
redirect_uri = "http://localhost:3000/confirm"
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
# or any other third-party OIDC providers.
url = ""
@@ -0,0 +1,9 @@
<h2>Confirm your signup</h2>
<p>Follow this link to confirm your user:</p>
<p>
<a
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email&next={{ .RedirectTo }}"
>Confirm your email</a
>
</p>
+49 -67
View File
@@ -1101,9 +1101,12 @@ importers:
'@supabase/supabase-js':
specifier: ^2.49.1
version: 2.49.1
'@tanstack/react-router':
specifier: ^1.114.27
version: 1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-start':
specifier: ^1.114.25
version: 1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)
version: 1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)
'@types/lodash':
specifier: ^4.17.16
version: 4.17.16
@@ -1121,7 +1124,7 @@ importers:
version: link:../../packages/config
fumadocs-ui:
specifier: ^15.1.0
version: 15.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(fumadocs-core@15.1.0(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1))(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.13.10)(typescript@5.5.2)))
version: 15.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(fumadocs-core@15.1.1(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1))(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.13.10)(typescript@5.5.2)))
lodash:
specifier: ^4.17.21
version: 4.17.21
@@ -1158,6 +1161,9 @@ importers:
unist-builder:
specifier: 3.0.0
version: 3.0.0
vite:
specifier: ^6.0.0
version: 6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5)
apps/www:
dependencies:
@@ -6346,8 +6352,8 @@ packages:
peerDependencies:
react: ^18.0.0
'@tanstack/react-router@1.114.25':
resolution: {integrity: sha512-4vls9pz+AOLIeTLZWUKK5ZqENc1azuSQ/UATNzZChckZqkMqtEoUci0dgp1XVAX+ocPH9bU1WP+/eonuyaLvdA==}
'@tanstack/react-router@1.114.27':
resolution: {integrity: sha512-xfM+FMXpebZQ5//NQEgj5y5SKQqTQEr2/LvePU9IiksxUBCgfjbuOXW5vgDHJ3xXJm3pivYqRx29cHqhueClow==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -9490,8 +9496,8 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
fumadocs-core@15.1.0:
resolution: {integrity: sha512-CPF9yrwT6PCt6iwTlyGDTTSvee8J2+jKFAPNdZsH8ok0ggWuMUprtm+q+3rEjqRzgDA6OB2G80CuxmvRqIN17w==}
fumadocs-core@15.1.1:
resolution: {integrity: sha512-9ZSl99vw+e0K+Is1f0xxZpA+In9RGefD4ncqHPSP1/wUJPXmZrAj+FDFOKQJJkeiAPuHKscfHAkTqarlODr+tg==}
peerDependencies:
'@oramacloud/client': 1.x.x || 2.x.x
algoliasearch: 4.24.0
@@ -9828,9 +9834,6 @@ packages:
hast-util-to-estree@2.3.3:
resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==}
hast-util-to-estree@3.1.0:
resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==}
hast-util-to-estree@3.1.3:
resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==}
@@ -17324,23 +17327,23 @@ snapshots:
'@js-temporal/polyfill@0.4.4':
dependencies:
jsbi: 4.3.0
tslib: 2.6.2
tslib: 2.8.1
'@jsonjoy.com/base64@1.1.2(tslib@2.6.2)':
'@jsonjoy.com/base64@1.1.2(tslib@2.8.1)':
dependencies:
tslib: 2.6.2
tslib: 2.8.1
'@jsonjoy.com/json-pack@1.0.4(tslib@2.6.2)':
'@jsonjoy.com/json-pack@1.0.4(tslib@2.8.1)':
dependencies:
'@jsonjoy.com/base64': 1.1.2(tslib@2.6.2)
'@jsonjoy.com/util': 1.5.0(tslib@2.6.2)
'@jsonjoy.com/base64': 1.1.2(tslib@2.8.1)
'@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
hyperdyperid: 1.2.0
thingies: 1.21.0(tslib@2.6.2)
tslib: 2.6.2
thingies: 1.21.0(tslib@2.8.1)
tslib: 2.8.1
'@jsonjoy.com/util@1.5.0(tslib@2.6.2)':
'@jsonjoy.com/util@1.5.0(tslib@2.8.1)':
dependencies:
tslib: 2.6.2
tslib: 2.8.1
'@kwsites/file-exists@1.1.1(supports-color@8.1.1)':
dependencies:
@@ -17451,8 +17454,8 @@ snapshots:
estree-util-is-identifier-name: 3.0.0
estree-util-to-js: 2.0.0
estree-walker: 3.0.3
hast-util-to-estree: 3.1.0(supports-color@8.1.1)
hast-util-to-jsx-runtime: 2.3.0(supports-color@8.1.1)
hast-util-to-estree: 3.1.3(supports-color@8.1.1)
hast-util-to-jsx-runtime: 2.3.6(supports-color@8.1.1)
markdown-extensions: 2.0.0
periscopic: 3.1.0
remark-mdx: 3.0.1(supports-color@8.1.1)
@@ -20545,7 +20548,7 @@ snapshots:
'@tanstack/query-core': 5.13.4
react: 18.2.0
'@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
'@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@tanstack/history': 1.114.22
'@tanstack/react-store': 0.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -20558,7 +20561,7 @@ snapshots:
'@tanstack/react-start-client@1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(yaml@2.4.5)':
dependencies:
'@tanstack/react-router': 1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-router': 1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/router-core': 1.114.25
'@tanstack/start-client-core': 1.114.25
cookie-es: 1.2.2
@@ -20611,12 +20614,12 @@ snapshots:
- xml2js
- yaml
'@tanstack/react-start-config@1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)':
'@tanstack/react-start-config@1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)':
dependencies:
'@tanstack/react-start-plugin': 1.114.12(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5)
'@tanstack/router-core': 1.114.25
'@tanstack/router-generator': 1.114.25(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
'@tanstack/router-plugin': 1.114.25(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(supports-color@8.1.1)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))
'@tanstack/router-generator': 1.114.25(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
'@tanstack/router-plugin': 1.114.25(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(supports-color@8.1.1)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))
'@tanstack/server-functions-plugin': 1.114.12(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5)
'@tanstack/start-server-functions-handler': 1.114.25
'@vitejs/plugin-react': 4.3.4(supports-color@8.1.1)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))
@@ -20754,7 +20757,7 @@ snapshots:
'@tanstack/react-start-server@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@tanstack/history': 1.114.22
'@tanstack/react-router': 1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-router': 1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/router-core': 1.114.25
'@tanstack/start-client-core': 1.114.25
'@tanstack/start-server-core': 1.114.25
@@ -20766,10 +20769,10 @@ snapshots:
tiny-warning: 1.0.3
unctx: 2.4.1
'@tanstack/react-start@1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)':
'@tanstack/react-start@1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)':
dependencies:
'@tanstack/react-start-client': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(yaml@2.4.5)
'@tanstack/react-start-config': 1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)
'@tanstack/react-start-config': 1.114.25(@electric-sql/pglite@0.2.15)(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))(yaml@2.4.5)
'@tanstack/react-start-router-manifest': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(yaml@2.4.5)
'@tanstack/react-start-server': 1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/start-api-routes': 1.114.25(@electric-sql/pglite@0.2.15)(@types/node@22.13.10)(db0@0.3.1(@electric-sql/pglite@0.2.15))(encoding@0.1.13)(ioredis@5.6.0(supports-color@8.1.1))(jiti@2.4.2)(sass@1.72.0)(supports-color@8.1.1)(terser@5.39.0)(tsx@4.19.3)(typescript@5.5.2)(yaml@2.4.5)
@@ -20841,16 +20844,16 @@ snapshots:
'@tanstack/store': 0.7.0
tiny-invariant: 1.3.3
'@tanstack/router-generator@1.114.25(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))':
'@tanstack/router-generator@1.114.25(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))':
dependencies:
'@tanstack/virtual-file-routes': 1.114.12
prettier: 3.5.3
tsx: 4.19.3
zod: 3.24.2
optionalDependencies:
'@tanstack/react-router': 1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-router': 1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/router-plugin@1.114.25(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(supports-color@8.1.1)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))':
'@tanstack/router-plugin@1.114.25(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(supports-color@8.1.1)(vite@6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5))(webpack@5.94.0(esbuild@0.25.1))':
dependencies:
'@babel/core': 7.26.10(supports-color@8.1.1)
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1))
@@ -20859,7 +20862,7 @@ snapshots:
'@babel/traverse': 7.26.10(supports-color@8.1.1)
'@babel/types': 7.26.10
'@tanstack/router-core': 1.114.25
'@tanstack/router-generator': 1.114.25(@tanstack/react-router@1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
'@tanstack/router-generator': 1.114.25(@tanstack/react-router@1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
'@tanstack/router-utils': 1.114.12
'@tanstack/virtual-file-routes': 1.114.12
'@types/babel__core': 7.20.5
@@ -20870,7 +20873,7 @@ snapshots:
unplugin: 2.2.1
zod: 3.24.2
optionalDependencies:
'@tanstack/react-router': 1.114.25(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-router': 1.114.27(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
vite: 6.2.1(@types/node@22.13.10)(jiti@2.4.2)(sass@1.72.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.4.5)
webpack: 5.94.0(esbuild@0.25.1)
transitivePeerDependencies:
@@ -24659,7 +24662,7 @@ snapshots:
fsevents@2.3.3:
optional: true
fumadocs-core@15.1.0(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1):
fumadocs-core@15.1.1(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1):
dependencies:
'@formatjs/intl-localematcher': 0.6.0
'@orama/orama': 3.1.3
@@ -24684,7 +24687,7 @@ snapshots:
- '@types/react'
- supports-color
fumadocs-ui@15.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(fumadocs-core@15.1.0(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1))(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.13.10)(typescript@5.5.2))):
fumadocs-ui@15.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(fumadocs-core@15.1.1(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1))(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(tailwindcss@3.4.1(ts-node@10.9.2(@types/node@22.13.10)(typescript@5.5.2))):
dependencies:
'@radix-ui/react-accordion': 1.2.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-collapsible': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -24696,7 +24699,7 @@ snapshots:
'@radix-ui/react-slot': 1.1.2(@types/react@18.3.3)(react@18.2.0)
'@radix-ui/react-tabs': 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
class-variance-authority: 0.7.1
fumadocs-core: 15.1.0(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1)
fumadocs-core: 15.1.1(@types/react@18.3.3)(next@14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(supports-color@8.1.1)
lodash.merge: 4.6.2
lucide-react: 0.482.0(react@18.2.0)
next: 14.2.21(@babel/core@7.26.10(supports-color@8.1.1))(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(sass@1.72.0)
@@ -25150,27 +25153,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
hast-util-to-estree@3.1.0(supports-color@8.1.1):
dependencies:
'@types/estree': 1.0.5
'@types/estree-jsx': 1.0.1
'@types/hast': 3.0.4
comma-separated-tokens: 2.0.3
devlop: 1.1.0
estree-util-attach-comments: 3.0.0
estree-util-is-identifier-name: 3.0.0
hast-util-whitespace: 3.0.0
mdast-util-mdx-expression: 2.0.0(supports-color@8.1.1)
mdast-util-mdx-jsx: 3.1.2(supports-color@8.1.1)
mdast-util-mdxjs-esm: 2.0.1(supports-color@8.1.1)
property-information: 6.3.0
space-separated-tokens: 2.0.2
style-to-object: 0.4.2
unist-util-position: 5.0.0
zwitch: 2.0.4
transitivePeerDependencies:
- supports-color
hast-util-to-estree@3.1.3(supports-color@8.1.1):
dependencies:
'@types/estree': 1.0.5
@@ -27154,10 +27136,10 @@ snapshots:
memfs@4.14.1:
dependencies:
'@jsonjoy.com/json-pack': 1.0.4(tslib@2.6.2)
'@jsonjoy.com/util': 1.5.0(tslib@2.6.2)
tree-dump: 1.0.1(tslib@2.6.2)
tslib: 2.6.2
'@jsonjoy.com/json-pack': 1.0.4(tslib@2.8.1)
'@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
tree-dump: 1.0.1(tslib@2.8.1)
tslib: 2.8.1
memoize-one@5.2.1: {}
@@ -31042,7 +31024,7 @@ snapshots:
dependencies:
client-only: 0.0.1
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
use-sync-external-store: 1.4.0(react@18.2.0)
swrev@4.0.0: {}
@@ -31208,9 +31190,9 @@ snapshots:
dependencies:
any-promise: 1.3.0
thingies@1.21.0(tslib@2.6.2):
thingies@1.21.0(tslib@2.8.1):
dependencies:
tslib: 2.6.2
tslib: 2.8.1
thread-stream@2.7.0:
dependencies:
@@ -31317,9 +31299,9 @@ snapshots:
punycode: 2.3.1
optional: true
tree-dump@1.0.1(tslib@2.6.2):
tree-dump@1.0.1(tslib@2.8.1):
dependencies:
tslib: 2.6.2
tslib: 2.8.1
tree-kill@1.2.2: {}