mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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:
1 parent
8fd6c9276d
commit
0990155ae7
53 files changed
+1872
-460
No files matched your search
@@ -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} />}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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} />
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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" />
|
||||
@@ -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}`
|
||||
}
|
||||
@@ -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)",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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'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'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'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'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"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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']
|
||||
+22
-1
@@ -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>
|
||||
)
|
||||
}
|
||||
+28
-1
@@ -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>
|
||||
)
|
||||
}
|
||||
+51
-34
@@ -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'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'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>
|
||||
)
|
||||
}
|
||||
+96
@@ -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'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>
|
||||
)
|
||||
}
|
||||
+99
@@ -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't have an account?{' '}
|
||||
<Link to="/sign-up" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+110
@@ -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>
|
||||
)
|
||||
}
|
||||
+77
@@ -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>
|
||||
)
|
||||
}
|
||||
+19
@@ -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
|
||||
})
|
||||
+75
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+338
@@ -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 */
|
||||
+16
@@ -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,
|
||||
}
|
||||
},
|
||||
})
|
||||
+16
@@ -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>
|
||||
}
|
||||
+62
@@ -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 }),
|
||||
})
|
||||
+37
@@ -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>
|
||||
)
|
||||
}
|
||||
+16
@@ -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>
|
||||
)
|
||||
}
|
||||
+35
@@ -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>
|
||||
)
|
||||
}
|
||||
+16
@@ -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>
|
||||
)
|
||||
}
|
||||
+16
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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>
|
||||
Generated
+49
-67
@@ -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: {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user