From 0990155ae7f4fd92a6a2e36532fa963852860925 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Mon, 24 Mar 2025 15:24:59 +0200 Subject: [PATCH] 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. --- apps/ui-library/__registry__/index.tsx | 488 ++++++++---------- .../ui-library/components/block-item-code.tsx | 10 +- apps/ui-library/components/command.tsx | 4 +- apps/ui-library/components/files.tsx | 66 +++ .../components/open-in-v0-button.tsx | 2 +- apps/ui-library/components/registry-block.tsx | 2 +- apps/ui-library/config/docs.ts | 18 + .../ui-library/content/docs/nextjs/client.mdx | 16 +- .../docs/nextjs/password-based-auth.mdx | 8 +- .../content/docs/tanstack/client.mdx | 6 + .../docs/tanstack/password-based-auth.mdx | 16 + apps/ui-library/lib/process-registry.ts | 11 +- apps/ui-library/lib/themes/supabase-2.json | 2 +- apps/ui-library/package.json | 4 +- apps/ui-library/public/llms.txt | 60 ++- apps/ui-library/public/r/dropzone-react.json | 2 +- .../public/r/dropzone-tanstack.json | 7 +- .../public/r/password-based-auth-nextjs.json | 21 +- .../public/r/password-based-auth-react.json | 2 +- .../r/password-based-auth-tanstack.json | 108 ++++ .../public/r/realtime-cursor-react.json | 2 +- .../public/r/realtime-cursor-tanstack.json | 7 +- .../public/r/supabase-client-react.json | 2 +- .../public/r/supabase-client-tanstack.json | 7 +- apps/ui-library/registry.json | 106 +++- apps/ui-library/registry/blocks.ts | 17 +- .../app/error/page.tsx | 23 +- .../app/sign-up-success/page.tsx | 29 +- .../components/forgot-password-form.tsx | 85 +-- .../components/forgot-password-form.tsx | 96 ++++ .../components/login-form.tsx | 99 ++++ .../components/sign-up-form.tsx | 110 ++++ .../components/update-password-form.tsx | 77 +++ .../lib/supabase/fetch-user-server-fn.ts | 19 + .../registry-item.json | 75 +++ .../routeTree.gen.ts | 338 ++++++++++++ .../routes/_protected.tsx | 16 + .../routes/_protected/info.tsx | 16 + .../routes/auth/confirm.ts | 62 +++ .../routes/auth/error.tsx | 37 ++ .../routes/forgot-password.tsx | 16 + .../routes/login.tsx | 16 + .../routes/sign-up-success.tsx | 35 ++ .../routes/sign-up.tsx | 16 + .../routes/update-password.tsx | 16 + .../clients/react/lib/supabase/client.ts | 5 +- .../clients/tanstack/lib/supabase/client.ts | 6 +- .../clients/tanstack/lib/supabase/server.ts | 2 +- .../clients/tanstack/registry-item.json | 2 +- apps/ui-library/scripts/build-llms-txt.ts | 3 +- apps/ui-library/supabase/config.toml | 14 +- .../supabase/templates/confirmation.html | 9 + pnpm-lock.yaml | 116 ++--- 53 files changed, 1872 insertions(+), 460 deletions(-) create mode 100644 apps/ui-library/components/files.tsx create mode 100644 apps/ui-library/content/docs/tanstack/password-based-auth.mdx create mode 100644 apps/ui-library/public/r/password-based-auth-tanstack.json create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/registry-item.json create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routeTree.gen.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/login.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx create mode 100644 apps/ui-library/supabase/templates/confirmation.html diff --git a/apps/ui-library/__registry__/index.tsx b/apps/ui-library/__registry__/index.tsx index a35cb1d63bb..a3fcd4b347e 100644 --- a/apps/ui-library/__registry__/index.tsx +++ b/apps/ui-library/__registry__/index.tsx @@ -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 = { - 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: [] + } + }, } diff --git a/apps/ui-library/components/block-item-code.tsx b/apps/ui-library/components/block-item-code.tsx index 05007873138..3d0ba4f0823 100644 --- a/apps/ui-library/components/block-item-code.tsx +++ b/apps/ui-library/components/block-item-code.tsx @@ -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]" > - + {selectedFileDemoUrl && } diff --git a/apps/ui-library/components/command.tsx b/apps/ui-library/components/command.tsx index ebb49e77d62..cfd4adfa1f8 100644 --- a/apps/ui-library/components/command.tsx +++ b/apps/ui-library/components/command.tsx @@ -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 ( <> diff --git a/apps/ui-library/components/files.tsx b/apps/ui-library/components/files.tsx new file mode 100644 index 00000000000..2eb32cdeff3 --- /dev/null +++ b/apps/ui-library/components/files.tsx @@ -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): React.ReactElement { + return ( +
+ {props.children} +
+ ) +} + +export interface FileProps extends HTMLAttributes { + name: string + icon?: ReactNode +} + +export interface FolderProps extends HTMLAttributes { + name: string + + disabled?: boolean + + /** + * Open folder by default + * + * @defaultValue false + */ + defaultOpen?: boolean +} + +export function File({ + name, + icon = , + className, + ...rest +}: FileProps): React.ReactElement { + return ( +
+ {icon} + {name} +
+ ) +} + +export function Folder({ name, defaultOpen = false, ...props }: FolderProps): React.ReactElement { + const [open, setOpen] = useState(defaultOpen) + + return ( + + + {open ? : } + {name} + + +
{props.children}
+
+
+ ) +} diff --git a/apps/ui-library/components/open-in-v0-button.tsx b/apps/ui-library/components/open-in-v0-button.tsx index bc9c4b61116..6946dbd9303 100644 --- a/apps/ui-library/components/open-in-v0-button.tsx +++ b/apps/ui-library/components/open-in-v0-button.tsx @@ -15,7 +15,7 @@ export function OpenInV0Button({ asChild > diff --git a/apps/ui-library/components/registry-block.tsx b/apps/ui-library/components/registry-block.tsx index be8ffe38cdb..9da49d30139 100644 --- a/apps/ui-library/components/registry-block.tsx +++ b/apps/ui-library/components/registry-block.tsx @@ -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 } diff --git a/apps/ui-library/config/docs.ts b/apps/ui-library/config/docs.ts index be5fafe5734..722620c80f6 100644 --- a/apps/ui-library/config/docs.ts +++ b/apps/ui-library/config/docs.ts @@ -45,6 +45,12 @@ export const frameworkPages: Record = { 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 = { 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', diff --git a/apps/ui-library/content/docs/nextjs/client.mdx b/apps/ui-library/content/docs/nextjs/client.mdx index ce067457f12..48d55d8c801 100644 --- a/apps/ui-library/content/docs/nextjs/client.mdx +++ b/apps/ui-library/content/docs/nextjs/client.mdx @@ -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 + +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. diff --git a/apps/ui-library/content/docs/nextjs/password-based-auth.mdx b/apps/ui-library/content/docs/nextjs/password-based-auth.mdx index 0b48450fee0..092f84e6434 100644 --- a/apps/ui-library/content/docs/nextjs/password-based-auth.mdx +++ b/apps/ui-library/content/docs/nextjs/password-based-auth.mdx @@ -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 ## 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: diff --git a/apps/ui-library/content/docs/tanstack/client.mdx b/apps/ui-library/content/docs/tanstack/client.mdx index 3dd1e162b8c..15c5d31b544 100644 --- a/apps/ui-library/content/docs/tanstack/client.mdx +++ b/apps/ui-library/content/docs/tanstack/client.mdx @@ -7,6 +7,12 @@ 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 diff --git a/apps/ui-library/content/docs/tanstack/password-based-auth.mdx b/apps/ui-library/content/docs/tanstack/password-based-auth.mdx new file mode 100644 index 00000000000..5d648efa33f --- /dev/null +++ b/apps/ui-library/content/docs/tanstack/password-based-auth.mdx @@ -0,0 +1,16 @@ +--- +title: Tanstack Start +description: Supabase client for Tanstack Start +--- + +## Installation + + + +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 + + diff --git a/apps/ui-library/lib/process-registry.ts b/apps/ui-library/lib/process-registry.ts index 11275edb020..e3b9cd7877e 100644 --- a/apps/ui-library/lib/process-registry.ts +++ b/apps/ui-library/lib/process-registry.ts @@ -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}` } diff --git a/apps/ui-library/lib/themes/supabase-2.json b/apps/ui-library/lib/themes/supabase-2.json index 81ffd9ec7cb..d4120e118a2 100644 --- a/apps/ui-library/lib/themes/supabase-2.json +++ b/apps/ui-library/lib/themes/supabase-2.json @@ -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)", diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json index 0b95a704609..e726f214a3a 100644 --- a/apps/ui-library/package.json +++ b/apps/ui-library/package.json @@ -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" } } diff --git a/apps/ui-library/public/llms.txt b/apps/ui-library/public/llms.txt index 18beeae9c75..0e75851c35f 100644 --- a/apps/ui-library/public/llms.txt +++ b/apps/ui-library/public/llms.txt @@ -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 diff --git a/apps/ui-library/public/r/dropzone-react.json b/apps/ui-library/public/r/dropzone-react.json index 9e5cfb86841..496fb4bc8ea 100644 --- a/apps/ui-library/public/r/dropzone-react.json +++ b/apps/ui-library/public/r/dropzone-react.json @@ -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" } ] diff --git a/apps/ui-library/public/r/dropzone-tanstack.json b/apps/ui-library/public/r/dropzone-tanstack.json index 273f6ff8fd5..da0de6cb718 100644 --- a/apps/ui-library/public/r/dropzone-tanstack.json +++ b/apps/ui-library/public/r/dropzone-tanstack.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/password-based-auth-nextjs.json b/apps/ui-library/public/r/password-based-auth-nextjs.json index dcce3ce1961..5acf598499a 100644 --- a/apps/ui-library/public/r/password-based-auth-nextjs.json +++ b/apps/ui-library/public/r/password-based-auth-nextjs.json @@ -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
Sorry, something went wrong. Code error: {searchParams.error}
\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
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {searchParams?.error ? (\n

Code error: {searchParams.error}

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\n )\n}\n", "type": "registry:page", "target": "app/auth/error/page.tsx" }, @@ -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
Sign up success
\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
\n
\n
\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n

\n
\n
\n
\n
\n
\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(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
\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n
\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(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
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\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(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
\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" } ] } \ No newline at end of file diff --git a/apps/ui-library/public/r/password-based-auth-react.json b/apps/ui-library/public/r/password-based-auth-react.json index 392b2238cbd..ee7132ee296 100644 --- a/apps/ui-library/public/r/password-based-auth-react.json +++ b/apps/ui-library/public/r/password-based-auth-react.json @@ -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" } ] diff --git a/apps/ui-library/public/r/password-based-auth-tanstack.json b/apps/ui-library/public/r/password-based-auth-tanstack.json new file mode 100644 index 00000000000..876a091a4a2 --- /dev/null +++ b/apps/ui-library/public/r/password-based-auth-tanstack.json @@ -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
\n
\n \n
\n
\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
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {params?.error ? (\n

Code error: {params.error}

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\n )\n}\n", + "type": "registry: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

Hello {data.user.email}

\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(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
\n \n \n Login\n Enter your email below to login to your account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n \n Forgot your password?\n \n
\n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n )\n}\n", + "type": "registry:component" + }, + { + "path": "registry/default/blocks/password-based-auth-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
\n
\n \n
\n
\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
\n
\n
\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n

\n
\n
\n
\n
\n
\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(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
\n \n \n Sign up\n Create a new account\n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n
\n
\n \n
\n setPassword(e.target.value)}\n />\n
\n
\n
\n \n
\n setRepeatPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n
\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
\n
\n \n
\n
\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
\n
\n \n
\n
\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(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
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

\n If you registered using your email and password, you will receive a password reset\n email.\n

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n
\n
\n
\n \n setEmail(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\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(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
\n \n \n Reset Your Password\n Please enter your new password below.\n \n \n
\n
\n
\n \n setPassword(e.target.value)}\n />\n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n
\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 = 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": "/// \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" + } + ] +} \ No newline at end of file diff --git a/apps/ui-library/public/r/realtime-cursor-react.json b/apps/ui-library/public/r/realtime-cursor-react.json index 645ba4621b5..548fce3bb73 100644 --- a/apps/ui-library/public/r/realtime-cursor-react.json +++ b/apps/ui-library/public/r/realtime-cursor-react.json @@ -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" } ] diff --git a/apps/ui-library/public/r/realtime-cursor-tanstack.json b/apps/ui-library/public/r/realtime-cursor-tanstack.json index a0fc11211c7..aeef652a424 100644 --- a/apps/ui-library/public/r/realtime-cursor-tanstack.json +++ b/apps/ui-library/public/r/realtime-cursor-tanstack.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/supabase-client-react.json b/apps/ui-library/public/r/supabase-client-react.json index 7fd2a7a7270..b32bdebd7d0 100644 --- a/apps/ui-library/public/r/supabase-client-react.json +++ b/apps/ui-library/public/r/supabase-client-react.json @@ -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" } ] diff --git a/apps/ui-library/public/r/supabase-client-tanstack.json b/apps/ui-library/public/r/supabase-client-tanstack.json index ee9566bc77e..116e5c7243c 100644 --- a/apps/ui-library/public/r/supabase-client-tanstack.json +++ b/apps/ui-library/public/r/supabase-client-tanstack.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/registry.json b/apps/ui-library/registry.json index 6eeeac7227a..911e292cf12 100644 --- a/apps/ui-library/registry.json +++ b/apps/ui-library/registry.json @@ -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", diff --git a/apps/ui-library/registry/blocks.ts b/apps/ui-library/registry/blocks.ts index dce4bc3b8f7..7db43270b51 100644 --- a/apps/ui-library/registry/blocks.ts +++ b/apps/ui-library/registry/blocks.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'] diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx index 32a1d119c5e..4f8c30a9e88 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/error/page.tsx @@ -1,3 +1,24 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card' + export default function Page({ searchParams }: { searchParams: { error: string } }) { - return
Sorry, something went wrong. Code error: {searchParams.error}
+ return ( +
+
+
+ + + Sorry, something went wrong. + + + {searchParams?.error ? ( +

Code error: {searchParams.error}

+ ) : ( +

An unspecified error occurred.

+ )} +
+
+
+
+
+ ) } diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx index 5243588092d..95be7c7a3c8 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/app/sign-up-success/page.tsx @@ -1,3 +1,30 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/registry/default/components/ui/card' + export default function Page() { - return
Sign up success
+ return ( +
+
+
+ + + Thank you for signing up! + Check your email to confirm + + +

+ You've successfully signed up. Please check your email to confirm your account + before signing in. +

+
+
+
+
+
+ ) } diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx index 5f2ef4852d8..e288b984a83 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/components/forgot-password-form.tsx @@ -18,6 +18,7 @@ import { useState } from 'react' export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) { const [email, setEmail] = useState('') const [error, setError] = useState(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 (
- - - Reset Your Password - - Type in your email and we'll send you a link to reset your password - - - -
-
-
- - setEmail(e.target.value)} - /> + {success ? ( + + + Check Your Email + Password reset instructions sent + + +

+ If you registered using your email and password, you will receive a password reset + email. +

+
+
+ ) : ( + + + Reset Your Password + + Type in your email and we'll send you a link to reset your password + + + + +
+
+ + setEmail(e.target.value)} + /> +
+ {error &&

{error}

} +
- {error &&

{error}

} - -
-
- Already have an account?{' '} - - Login - -
- - - +
+ Already have an account?{' '} + + Login + +
+ + + + )}
) } diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx new file mode 100644 index 00000000000..fb5bf13be5d --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx @@ -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(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 ( +
+ {success ? ( + + + Check Your Email + Password reset instructions sent + + +

+ If you registered using your email and password, you will receive a password reset + email. +

+
+
+ ) : ( + + + Reset Your Password + + Type in your email and we'll send you a link to reset your password + + + +
+
+
+ + setEmail(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+ )} +
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx new file mode 100644 index 00000000000..e95417627d2 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx @@ -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(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 ( +
+ + + Login + Enter your email below to login to your account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ + + Forgot your password? + +
+ setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Don't have an account?{' '} + + Sign up + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx new file mode 100644 index 00000000000..079a3bea5a1 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx @@ -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(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 ( +
+ + + Sign up + Create a new account + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+
+ +
+ setPassword(e.target.value)} + /> +
+
+
+ +
+ setRepeatPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx new file mode 100644 index 00000000000..a8858306219 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx @@ -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(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 ( +
+ + + Reset Your Password + Please enter your new password below. + + +
+
+
+ + setPassword(e.target.value)} + /> +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts new file mode 100644 index 00000000000..3f3f50c4183 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts @@ -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 = createServerFn({ + method: 'GET', +}).handler(async () => { + const supabase = createClient() + const { data, error } = await supabase.auth.getUser() + + if (error) { + return null + } + + return data.user as SSRSafeUser +}) diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/registry-item.json new file mode 100644 index 00000000000..36de9cf65cc --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/registry-item.json @@ -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" + } + ] +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routeTree.gen.ts b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routeTree.gen.ts new file mode 100644 index 00000000000..40087e7a39c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routeTree.gen.ts @@ -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() + +/* 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 */ diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx new file mode 100644 index 00000000000..d5fda348370 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx @@ -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, + } + }, +}) diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx new file mode 100644 index 00000000000..bbd39020e1f --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx @@ -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

Hello {data.user.email}

+} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts new file mode 100644 index 00000000000..8e72087fc68 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts @@ -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 }), +}) diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx new file mode 100644 index 00000000000..cb7a4669b08 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx @@ -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 ( +
+
+
+ + + Sorry, something went wrong. + + + {params?.error ? ( +

Code error: {params.error}

+ ) : ( +

An unspecified error occurred.

+ )} +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx new file mode 100644 index 00000000000..9e6475f68af --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx @@ -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 ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/login.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/login.tsx new file mode 100644 index 00000000000..5680a1a1670 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/login.tsx @@ -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 ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx new file mode 100644 index 00000000000..5a59b834113 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx @@ -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 ( +
+
+
+ + + Thank you for signing up! + Check your email to confirm + + +

+ You've successfully signed up. Please check your email to confirm your account + before signing in. +

+
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx new file mode 100644 index 00000000000..661b8ab6cf8 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx @@ -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 ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx new file mode 100644 index 00000000000..9959cb9be34 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx @@ -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 ( +
+
+ +
+
+ ) +} diff --git a/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts index 1ca4c8ce35f..489b93483ee 100644 --- a/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts +++ b/apps/ui-library/registry/default/clients/react/lib/supabase/client.ts @@ -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!) } diff --git a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts index 8b0f3fe4181..3c171dbcaca 100644 --- a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts +++ b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/client.ts @@ -1,5 +1,9 @@ +/// 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! + ) } diff --git a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts index fa17b6272ab..33f11832d5d 100644 --- a/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts +++ b/apps/ui-library/registry/default/clients/tanstack/lib/supabase/server.ts @@ -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() { diff --git a/apps/ui-library/registry/default/clients/tanstack/registry-item.json b/apps/ui-library/registry/default/clients/tanstack/registry-item.json index 30e09ce8236..5c86eee2515 100644 --- a/apps/ui-library/registry/default/clients/tanstack/registry-item.json +++ b/apps/ui-library/registry/default/clients/tanstack/registry-item.json @@ -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", diff --git a/apps/ui-library/scripts/build-llms-txt.ts b/apps/ui-library/scripts/build-llms-txt.ts index f5a31f6fe75..a935f25e406 100644 --- a/apps/ui-library/scripts/build-llms-txt.ts +++ b/apps/ui-library/scripts/build-llms-txt.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) diff --git a/apps/ui-library/supabase/config.toml b/apps/ui-library/supabase/config.toml index 99d82e36c47..73e1ef8f078 100644 --- a/apps/ui-library/supabase/config.toml +++ b/apps/ui-library/supabase/config.toml @@ -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 = "" diff --git a/apps/ui-library/supabase/templates/confirmation.html b/apps/ui-library/supabase/templates/confirmation.html new file mode 100644 index 00000000000..13948c48cff --- /dev/null +++ b/apps/ui-library/supabase/templates/confirmation.html @@ -0,0 +1,9 @@ +

Confirm your signup

+ +

Follow this link to confirm your user:

+

+ Confirm your email +

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88bfe046ce2..a26fb02f2d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {}