mirror of
https://github.com/supabase/supabase.git
synced 2026-05-07 09:20:21 -04:00
Add a tanstack block for password-based auth.
This commit is contained in:
@@ -31,6 +31,18 @@ export const Index: Record<string, any> = {
|
||||
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",
|
||||
|
||||
@@ -45,6 +45,12 @@ export const frameworkPages: Record<string, SidebarNavGroup> = {
|
||||
nextjs: {
|
||||
title: 'Next.js',
|
||||
items: [
|
||||
{
|
||||
title: 'Client',
|
||||
href: '/docs/nextjs/client',
|
||||
items: [],
|
||||
commandItemLabel: 'Supabase Client for Next.js',
|
||||
},
|
||||
{
|
||||
title: 'Password-Based Auth',
|
||||
href: '/docs/nextjs/password-based-auth',
|
||||
@@ -85,6 +91,18 @@ export const frameworkPages: Record<string, SidebarNavGroup> = {
|
||||
tanstack: {
|
||||
title: 'Tanstack Start',
|
||||
items: [
|
||||
{
|
||||
title: 'Client',
|
||||
href: '/docs/tanstack/client',
|
||||
items: [],
|
||||
commandItemLabel: 'Supabase Client for Tanstack Start',
|
||||
},
|
||||
{
|
||||
title: 'Password-Based Auth',
|
||||
href: '/docs/tanstack/password-based-auth',
|
||||
items: [],
|
||||
commandItemLabel: 'Password-Based Auth for Tanstack Start',
|
||||
},
|
||||
{
|
||||
title: 'Dropzone',
|
||||
href: '/docs/tanstack/dropzone',
|
||||
|
||||
@@ -7,6 +7,19 @@ description: Supabase client for Tanstack Start
|
||||
|
||||
<BlockItem name="supabase-client-tanstack" description="Supabase Client for Tanstack Start" />
|
||||
|
||||
The easiest way to use Tanstack Start with Supabase is to follow the following steps:
|
||||
|
||||
1. npx degit https://github.com/tanstack/router/examples/react/start-bare start-bare
|
||||
2. cd start-basic
|
||||
3. npm install
|
||||
4. npm run dev
|
||||
5. Follow this guide https://ui.shadcn.com/docs/installation/manual to install shadcn in your project
|
||||
|
||||
- `"@/*"` should be set to ["./src/*"] in `tsconfig.json`
|
||||
|
||||
6. Install this block to get the Supabase clients
|
||||
7. Add VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY to your .env file
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="supabase-client-tanstack" />
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Tanstack Start
|
||||
description: Supabase client for Tanstack Start
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem name="password-based-auth-tanstack" description="Supabase Client for Tanstack Start" />
|
||||
|
||||
1. Follow the steps in /ui/docs/tanstack/password-based-auth to setup your repo.
|
||||
|
||||
2. Add the following property to your `createRootRoute` function:
|
||||
|
||||
```ts
|
||||
import { fetchUser } from "@/lib/supabase/fetch-user-server-fn";
|
||||
|
||||
...
|
||||
beforeLoad: async () => {
|
||||
const user = await fetchUser();
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
},
|
||||
...
|
||||
```
|
||||
|
||||
3. Try to access the /info route, you should be redirected to login screen. If you create an account and try accessing the /info page, you should see your email.
|
||||
|
||||
## Folder structure
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-tanstack" />
|
||||
@@ -1,5 +1,5 @@
|
||||
# Supabase UI Library
|
||||
Last updated: 2025-03-21T23:10:11.061Z
|
||||
Last updated: 2025-03-22T14:10:43.312Z
|
||||
|
||||
## Overview
|
||||
Library of components for your project. The components integrate with Supabase and are shadcn compatible.
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "password-based-auth-tanstack",
|
||||
"type": "registry:block",
|
||||
"title": "Password Based Auth flow for tanstack and Supabase",
|
||||
"description": "Password Based Auth flow for tanstack and Supabase",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/login.tsx",
|
||||
"content": "import { LoginForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/login-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/login')({\n component: Login,\n})\n\nfunction Login() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <LoginForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/login.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx",
|
||||
"content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/auth/error')({\n component: AuthError,\n validateSearch: (params) => {\n if (params.error && typeof params.error === 'string') {\n return { error: params.error }\n }\n return null\n },\n})\n\nfunction AuthError() {\n const params = Route.useSearch()\n\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sorry, something went wrong.</CardTitle>\n </CardHeader>\n <CardContent>\n {params?.error ? (\n <p className=\"text-sm text-muted-foreground\">Code error: {params.error}</p>\n ) : (\n <p className=\"text-sm text-muted-foreground\">An unspecified error occurred.</p>\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/auth/error.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx",
|
||||
"content": "import { createFileRoute, redirect } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/_protected')({\n beforeLoad: ({ context }) => {\n if (!context.user) {\n throw redirect({ to: '/login' })\n }\n },\n})\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/_protected.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx",
|
||||
"content": "import { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/_protected/info')({\n component: Info,\n loader: async ({ context }) => {\n return {\n user: context.user!,\n }\n },\n})\n\nfunction Info() {\n const data = Route.useLoaderData()\n\n return <p>Hello {data.user.email}</p>\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/_protected/info.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts",
|
||||
"content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport { type EmailOtpType } from '@supabase/supabase-js'\nimport { createFileRoute, redirect } from '@tanstack/react-router'\nimport { createServerFn } from '@tanstack/react-start'\nimport { getWebRequest } from '@tanstack/react-start/server'\n\nconst confirmFn = createServerFn({ method: 'GET' })\n .validator((searchParams: unknown) => {\n if (\n searchParams &&\n typeof searchParams === 'object' &&\n 'token_hash' in searchParams &&\n 'type' in searchParams &&\n 'next' in searchParams\n ) {\n return searchParams\n }\n throw new Error('Invalid search params')\n })\n .handler(async (ctx) => {\n const request = getWebRequest()\n\n if (!request) {\n throw redirect({ to: `/auth/error`, params: { 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 params: { 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 params: { error: 'No token hash or type' },\n })\n })\n\nexport const Route = createFileRoute('/auth/confirm')({\n preload: false,\n loader: (opts) => confirmFn({ data: opts.location.search }),\n})\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/auth/confirm.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx",
|
||||
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport { Link, useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleLogin = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n await navigate({ to: '/info' })\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Login</CardTitle>\n <CardDescription>Enter your email below to login to your account</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleLogin}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n <Link\n to=\"/forgot-password\"\n className=\"ml-auto inline-block text-sm underline-offset-4 hover:underline\"\n >\n Forgot your password?\n </Link>\n </div>\n <Input\n id=\"password\"\n type=\"password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Logging in...' : 'Login'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Don't have an account?{' '}\n <Link to=\"/sign-up\" className=\"underline underline-offset-4\">\n Sign up\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx",
|
||||
"content": "import { SignUpForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/sign-up')({\n component: SignUp,\n})\n\nfunction SignUp() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <SignUpForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/sign-up.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx",
|
||||
"content": "import {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/sign-up-success')({\n component: SignUpSuccess,\n})\n\nfunction SignUpSuccess() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Thank you for signing up!</CardTitle>\n <CardDescription>Check your email to confirm</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n You've successfully signed up. Please check your email to confirm your account\n before signing in.\n </p>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/sign-up-success.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx",
|
||||
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport { Link, useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [repeatPassword, setRepeatPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleSignUp = async (e: React.FormEvent) => {\n e.preventDefault()\n setError(null)\n\n if (password !== repeatPassword) {\n setError('Passwords do not match')\n return\n }\n setIsLoading(true)\n\n try {\n const { error } = await supabase.auth.signUp({\n email,\n password,\n })\n if (error) throw error\n await navigate({ to: '/sign-up-success' })\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sign up</CardTitle>\n <CardDescription>Create a new account</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSignUp}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"password\">Password</Label>\n </div>\n <Input\n id=\"password\"\n type=\"password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n <div className=\"grid gap-2\">\n <div className=\"flex items-center\">\n <Label htmlFor=\"repeat-password\">Repeat Password</Label>\n </div>\n <Input\n id=\"repeat-password\"\n type=\"password\"\n required\n value={repeatPassword}\n onChange={(e) => setRepeatPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Creating an account...' : 'Sign up'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link to=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx",
|
||||
"content": "import { ForgotPasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/forgot-password')({\n component: ForgotPassword,\n})\n\nfunction ForgotPassword() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <ForgotPasswordForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/forgot-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx",
|
||||
"content": "import { UpdatePasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/update-password-form'\nimport { createFileRoute } from '@tanstack/react-router'\n\nexport const Route = createFileRoute('/update-password')({\n component: UpdatePassword,\n})\n\nfunction UpdatePassword() {\n return (\n <div className=\"flex min-h-svh w-full items-center justify-center p-6 md:p-10\">\n <div className=\"w-full max-w-sm\">\n <UpdatePasswordForm />\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "routes/update-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx",
|
||||
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport { Link } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [email, setEmail] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n // The url which will be included in the email. This URL needs to be configured in your redirect URLs in the Supabase dashboard at https://supabase.com/dashboard/project/_/auth/url-configuration\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: 'http://localhost:3000/update-password',\n })\n if (error) throw error\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we'll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Sending...' : 'Send reset email'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <Link to=\"/login\" className=\"underline underline-offset-4\">\n Login\n </Link>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx",
|
||||
"content": "import { cn } from '@/lib/utils'\nimport { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'\nimport { Button } from '@/registry/default/components/ui/button'\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n} from '@/registry/default/components/ui/card'\nimport { Input } from '@/registry/default/components/ui/input'\nimport { Label } from '@/registry/default/components/ui/label'\nimport { useNavigate } from '@tanstack/react-router'\nimport { useState } from 'react'\n\nexport function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [isLoading, setIsLoading] = useState(false)\n const navigate = useNavigate()\n const supabase = createClient()\n\n const handleForgotPassword = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const { error } = await supabase.auth.updateUser({ password })\n if (error) throw error\n // Update this route to redirect to an authenticated route. The user already has an active session.\n await navigate({ to: '/info' })\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : 'An error occurred')\n } finally {\n setIsLoading(false)\n }\n }\n\n return (\n <div className={cn('flex flex-col gap-6', className)} {...props}>\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>Please enter your new password below.</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleForgotPassword}>\n <div className=\"flex flex-col gap-6\">\n <div className=\"grid gap-2\">\n <Label htmlFor=\"password\">New password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Saving...' : 'Save new password'}\n </Button>\n </div>\n <div className=\"mt-4 text-center text-sm\">\n Already have an account?{' '}\n <a href=\"/login\" className=\"underline underline-offset-4\">\n Login\n </a>\n </div>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts",
|
||||
"content": "import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'\nimport type { Factor, User } from '@supabase/supabase-js'\nimport { createServerFn } from '@tanstack/react-start'\n\ntype SSRSafeUser = User & {\n factors: (Factor & { factor_type: 'phone' | 'totp' })[]\n}\n\nexport const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {\n const supabase = createClient()\n const { data, error } = await supabase.auth.getUser()\n\n if (error) {\n return null\n }\n\n return data.user as SSRSafeUser\n})\n",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/tanstack/lib/supabase/client.ts",
|
||||
"content": "/// <reference types=\"vite/types/importMeta.d.ts\" />\nimport { createBrowserClient } from '@supabase/ssr'\n\nexport function createClient() {\n return createBrowserClient(\n import.meta.env.VITE_SUPABASE_URL!,\n import.meta.env.VITE_SUPABASE_ANON_KEY!\n )\n}\n",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/tanstack/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient } from '@supabase/ssr'\nimport { parseCookies, setCookie } from '@tanstack/react-start/server'\n\nexport function createClient() {\n return createServerClient(process.env.VITE_SUPABASE_URL!, process.env.VITE_SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return Object.entries(parseCookies()).map(\n ([name, value]) =>\n ({\n name,\n value,\n }) as { name: string; value: string }\n )\n },\n setAll(cookies) {\n cookies.forEach((cookie) => {\n setCookie(cookie.name, cookie.value)\n })\n },\n },\n })\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -116,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",
|
||||
|
||||
@@ -3,6 +3,8 @@ 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'
|
||||
|
||||
@@ -20,9 +22,12 @@ 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 = [
|
||||
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']
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/registry/default/components/ui/card'
|
||||
import { Input } from '@/registry/default/components/ui/input'
|
||||
import { Label } from '@/registry/default/components/ui/label'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function ForgotPasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
const [email, setEmail] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [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
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
|
||||
<CardDescription>
|
||||
Type in your email and we'll send you a link to reset your password
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleForgotPassword}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Sending...' : 'Send reset email'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Already have an account?{' '}
|
||||
<Link to="/login" className="underline underline-offset-4">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/registry/default/components/ui/card'
|
||||
import { Input } from '@/registry/default/components/ui/input'
|
||||
import { Label } from '@/registry/default/components/ui/label'
|
||||
import { Link, useNavigate } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function LoginForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const supabase = createClient()
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
if (error) throw error
|
||||
// Update this route to redirect to an authenticated route. The user already has an active session.
|
||||
await navigate({ to: '/info' })
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Login</CardTitle>
|
||||
<CardDescription>Enter your email below to login to your account</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleLogin}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
<Link
|
||||
to="/forgot-password"
|
||||
className="ml-auto inline-block text-sm underline-offset-4 hover:underline"
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
</div>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{' '}
|
||||
<Link to="/sign-up" className="underline underline-offset-4">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/registry/default/components/ui/card'
|
||||
import { Input } from '@/registry/default/components/ui/input'
|
||||
import { Label } from '@/registry/default/components/ui/label'
|
||||
import { Link, useNavigate } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function SignUpForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [repeatPassword, setRepeatPassword] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const supabase = createClient()
|
||||
|
||||
const handleSignUp = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setError(null)
|
||||
|
||||
if (password !== repeatPassword) {
|
||||
setError('Passwords do not match')
|
||||
return
|
||||
}
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
if (error) throw error
|
||||
await navigate({ to: '/sign-up-success' })
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sign up</CardTitle>
|
||||
<CardDescription>Create a new account</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSignUp}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
</div>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="repeat-password">Repeat Password</Label>
|
||||
</div>
|
||||
<Input
|
||||
id="repeat-password"
|
||||
type="password"
|
||||
required
|
||||
value={repeatPassword}
|
||||
onChange={(e) => setRepeatPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Creating an account...' : 'Sign up'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Already have an account?{' '}
|
||||
<Link to="/login" className="underline underline-offset-4">
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/client'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/registry/default/components/ui/card'
|
||||
import { Input } from '@/registry/default/components/ui/input'
|
||||
import { Label } from '@/registry/default/components/ui/label'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
|
||||
export function UpdatePasswordForm({ className, ...props }: React.ComponentPropsWithoutRef<'div'>) {
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
const supabase = createClient()
|
||||
|
||||
const handleForgotPassword = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const { error } = await supabase.auth.updateUser({ password })
|
||||
if (error) throw error
|
||||
// Update this route to redirect to an authenticated route. The user already has an active session.
|
||||
await navigate({ to: '/info' })
|
||||
} catch (error: unknown) {
|
||||
setError(error instanceof Error ? error.message : 'An error occurred')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
|
||||
<CardDescription>Please enter your new password below.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleForgotPassword}>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">New password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="New password"
|
||||
required
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||
{isLoading ? 'Saving...' : 'Save new password'}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Already have an account?{' '}
|
||||
<a href="/login" className="underline underline-offset-4">
|
||||
Login
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
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 = createServerFn({ method: 'GET' }).handler(async () => {
|
||||
const supabase = createClient()
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
|
||||
if (error) {
|
||||
return null
|
||||
}
|
||||
|
||||
return data.user as SSRSafeUser
|
||||
})
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "password-based-auth-tanstack",
|
||||
"type": "registry:block",
|
||||
"title": "Password Based Auth flow for tanstack and Supabase",
|
||||
"description": "Password Based Auth flow for tanstack and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/login.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/login.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/error.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/auth/error.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/_protected.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/_protected/info.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/_protected/info.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/auth/confirm.ts",
|
||||
"type": "registry:file",
|
||||
"target": "routes/auth/confirm.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/login-form.tsx",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/sign-up.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/sign-up-success.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/sign-up-success.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/sign-up-form.tsx",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/forgot-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/forgot-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/routes/update-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "routes/update-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form.tsx",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/components/update-password-form.tsx",
|
||||
"type": "registry:component"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
}
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
// SUPABASE NOTE: THIS FILE WAS ADDED TO SATISFY TANSTACK BUILD CONFIGURATION. IT IS NOT INCLUDED IN THE BLOCK.
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as ProtectedImport } from './routes/_protected'
|
||||
import { Route as ProtectedInfoImport } from './routes/_protected/info'
|
||||
import { Route as AuthConfirmImport } from './routes/auth/confirm'
|
||||
import { Route as AuthErrorImport } from './routes/auth/error'
|
||||
import { Route as ForgotPasswordImport } from './routes/forgot-password'
|
||||
import { Route as IndexImport } from './routes/index'
|
||||
import { Route as LoginImport } from './routes/login'
|
||||
import { Route as SignUpImport } from './routes/sign-up'
|
||||
import { Route as SignUpSuccessImport } from './routes/sign-up-success'
|
||||
import { Route as UpdatePasswordImport } from './routes/update-password'
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const UpdatePasswordRoute = UpdatePasswordImport.update({
|
||||
id: '/update-password',
|
||||
path: '/update-password',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const SignUpSuccessRoute = SignUpSuccessImport.update({
|
||||
id: '/sign-up-success',
|
||||
path: '/sign-up-success',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const SignUpRoute = SignUpImport.update({
|
||||
id: '/sign-up',
|
||||
path: '/sign-up',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const LoginRoute = LoginImport.update({
|
||||
id: '/login',
|
||||
path: '/login',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ForgotPasswordRoute = ForgotPasswordImport.update({
|
||||
id: '/forgot-password',
|
||||
path: '/forgot-password',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ProtectedRoute = ProtectedImport.update({
|
||||
id: '/_protected',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const IndexRoute = IndexImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AuthErrorRoute = AuthErrorImport.update({
|
||||
id: '/auth/error',
|
||||
path: '/auth/error',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AuthConfirmRoute = AuthConfirmImport.update({
|
||||
id: '/auth/confirm',
|
||||
path: '/auth/confirm',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ProtectedInfoRoute = ProtectedInfoImport.update({
|
||||
id: '/info',
|
||||
path: '/info',
|
||||
getParentRoute: () => ProtectedRoute,
|
||||
} as any)
|
||||
|
||||
// Populate the FileRoutesByPath interface
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/_protected': {
|
||||
id: '/_protected'
|
||||
path: ''
|
||||
fullPath: ''
|
||||
preLoaderRoute: typeof ProtectedImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/forgot-password': {
|
||||
id: '/forgot-password'
|
||||
path: '/forgot-password'
|
||||
fullPath: '/forgot-password'
|
||||
preLoaderRoute: typeof ForgotPasswordImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/login': {
|
||||
id: '/login'
|
||||
path: '/login'
|
||||
fullPath: '/login'
|
||||
preLoaderRoute: typeof LoginImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/sign-up': {
|
||||
id: '/sign-up'
|
||||
path: '/sign-up'
|
||||
fullPath: '/sign-up'
|
||||
preLoaderRoute: typeof SignUpImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/sign-up-success': {
|
||||
id: '/sign-up-success'
|
||||
path: '/sign-up-success'
|
||||
fullPath: '/sign-up-success'
|
||||
preLoaderRoute: typeof SignUpSuccessImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/update-password': {
|
||||
id: '/update-password'
|
||||
path: '/update-password'
|
||||
fullPath: '/update-password'
|
||||
preLoaderRoute: typeof UpdatePasswordImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/_protected/info': {
|
||||
id: '/_protected/info'
|
||||
path: '/info'
|
||||
fullPath: '/info'
|
||||
preLoaderRoute: typeof ProtectedInfoImport
|
||||
parentRoute: typeof ProtectedImport
|
||||
}
|
||||
'/auth/confirm': {
|
||||
id: '/auth/confirm'
|
||||
path: '/auth/confirm'
|
||||
fullPath: '/auth/confirm'
|
||||
preLoaderRoute: typeof AuthConfirmImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/auth/error': {
|
||||
id: '/auth/error'
|
||||
path: '/auth/error'
|
||||
fullPath: '/auth/error'
|
||||
preLoaderRoute: typeof AuthErrorImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export the route tree
|
||||
|
||||
interface ProtectedRouteChildren {
|
||||
ProtectedInfoRoute: typeof ProtectedInfoRoute
|
||||
}
|
||||
|
||||
const ProtectedRouteChildren: ProtectedRouteChildren = {
|
||||
ProtectedInfoRoute: ProtectedInfoRoute,
|
||||
}
|
||||
|
||||
const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren(ProtectedRouteChildren)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'': typeof ProtectedRouteWithChildren
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/sign-up': typeof SignUpRoute
|
||||
'/sign-up-success': typeof SignUpSuccessRoute
|
||||
'/update-password': typeof UpdatePasswordRoute
|
||||
'/info': typeof ProtectedInfoRoute
|
||||
'/auth/confirm': typeof AuthConfirmRoute
|
||||
'/auth/error': typeof AuthErrorRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'': typeof ProtectedRouteWithChildren
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/sign-up': typeof SignUpRoute
|
||||
'/sign-up-success': typeof SignUpSuccessRoute
|
||||
'/update-password': typeof UpdatePasswordRoute
|
||||
'/info': typeof ProtectedInfoRoute
|
||||
'/auth/confirm': typeof AuthConfirmRoute
|
||||
'/auth/error': typeof AuthErrorRoute
|
||||
}
|
||||
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/': typeof IndexRoute
|
||||
'/_protected': typeof ProtectedRouteWithChildren
|
||||
'/forgot-password': typeof ForgotPasswordRoute
|
||||
'/login': typeof LoginRoute
|
||||
'/sign-up': typeof SignUpRoute
|
||||
'/sign-up-success': typeof SignUpSuccessRoute
|
||||
'/update-password': typeof UpdatePasswordRoute
|
||||
'/_protected/info': typeof ProtectedInfoRoute
|
||||
'/auth/confirm': typeof AuthConfirmRoute
|
||||
'/auth/error': typeof AuthErrorRoute
|
||||
}
|
||||
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| '/'
|
||||
| ''
|
||||
| '/forgot-password'
|
||||
| '/login'
|
||||
| '/sign-up'
|
||||
| '/sign-up-success'
|
||||
| '/update-password'
|
||||
| '/info'
|
||||
| '/auth/confirm'
|
||||
| '/auth/error'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/'
|
||||
| ''
|
||||
| '/forgot-password'
|
||||
| '/login'
|
||||
| '/sign-up'
|
||||
| '/sign-up-success'
|
||||
| '/update-password'
|
||||
| '/info'
|
||||
| '/auth/confirm'
|
||||
| '/auth/error'
|
||||
id:
|
||||
| '__root__'
|
||||
| '/'
|
||||
| '/_protected'
|
||||
| '/forgot-password'
|
||||
| '/login'
|
||||
| '/sign-up'
|
||||
| '/sign-up-success'
|
||||
| '/update-password'
|
||||
| '/_protected/info'
|
||||
| '/auth/confirm'
|
||||
| '/auth/error'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
ProtectedRoute: typeof ProtectedRouteWithChildren
|
||||
ForgotPasswordRoute: typeof ForgotPasswordRoute
|
||||
LoginRoute: typeof LoginRoute
|
||||
SignUpRoute: typeof SignUpRoute
|
||||
SignUpSuccessRoute: typeof SignUpSuccessRoute
|
||||
UpdatePasswordRoute: typeof UpdatePasswordRoute
|
||||
AuthConfirmRoute: typeof AuthConfirmRoute
|
||||
AuthErrorRoute: typeof AuthErrorRoute
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
ProtectedRoute: ProtectedRouteWithChildren,
|
||||
ForgotPasswordRoute: ForgotPasswordRoute,
|
||||
LoginRoute: LoginRoute,
|
||||
SignUpRoute: SignUpRoute,
|
||||
SignUpSuccessRoute: SignUpSuccessRoute,
|
||||
UpdatePasswordRoute: UpdatePasswordRoute,
|
||||
AuthConfirmRoute: AuthConfirmRoute,
|
||||
AuthErrorRoute: AuthErrorRoute,
|
||||
}
|
||||
|
||||
export const routeTree = rootRoute
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
/* ROUTE_MANIFEST_START
|
||||
{
|
||||
"routes": {
|
||||
"__root__": {
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/",
|
||||
"/_protected",
|
||||
"/forgot-password",
|
||||
"/login",
|
||||
"/sign-up",
|
||||
"/sign-up-success",
|
||||
"/update-password",
|
||||
"/auth/confirm",
|
||||
"/auth/error"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
"filePath": "index.tsx"
|
||||
},
|
||||
"/_protected": {
|
||||
"filePath": "_protected.tsx",
|
||||
"children": [
|
||||
"/_protected/info"
|
||||
]
|
||||
},
|
||||
"/forgot-password": {
|
||||
"filePath": "forgot-password.tsx"
|
||||
},
|
||||
"/login": {
|
||||
"filePath": "login.tsx"
|
||||
},
|
||||
"/sign-up": {
|
||||
"filePath": "sign-up.tsx"
|
||||
},
|
||||
"/sign-up-success": {
|
||||
"filePath": "sign-up-success.tsx"
|
||||
},
|
||||
"/update-password": {
|
||||
"filePath": "update-password.tsx"
|
||||
},
|
||||
"/_protected/info": {
|
||||
"filePath": "_protected/info.tsx",
|
||||
"parent": "/_protected"
|
||||
},
|
||||
"/auth/confirm": {
|
||||
"filePath": "auth/confirm.ts"
|
||||
},
|
||||
"/auth/error": {
|
||||
"filePath": "auth/error.tsx"
|
||||
}
|
||||
}
|
||||
}
|
||||
ROUTE_MANIFEST_END */
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_protected')({
|
||||
beforeLoad: ({ context }) => {
|
||||
if (!context.user) {
|
||||
throw redirect({ to: '/login' })
|
||||
}
|
||||
},
|
||||
})
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_protected/info')({
|
||||
component: Info,
|
||||
loader: async ({ context }) => {
|
||||
return {
|
||||
user: context.user!,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function Info() {
|
||||
const data = Route.useLoaderData()
|
||||
|
||||
return <p>Hello {data.user.email}</p>
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { createClient } from '@/registry/default/clients/tanstack/lib/supabase/server'
|
||||
import { type EmailOtpType } from '@supabase/supabase-js'
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
import { createServerFn } from '@tanstack/react-start'
|
||||
import { getWebRequest } from '@tanstack/react-start/server'
|
||||
|
||||
const confirmFn = createServerFn({ method: 'GET' })
|
||||
.validator((searchParams: unknown) => {
|
||||
if (
|
||||
searchParams &&
|
||||
typeof searchParams === 'object' &&
|
||||
'token_hash' in searchParams &&
|
||||
'type' in searchParams &&
|
||||
'next' in searchParams
|
||||
) {
|
||||
return searchParams
|
||||
}
|
||||
throw new Error('Invalid search params')
|
||||
})
|
||||
.handler(async (ctx) => {
|
||||
const request = getWebRequest()
|
||||
|
||||
if (!request) {
|
||||
throw redirect({ to: `/auth/error`, params: { 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`,
|
||||
params: { error: error?.message },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// redirect the user to an error page with some instructions
|
||||
throw redirect({
|
||||
to: `/auth/error`,
|
||||
params: { error: 'No token hash or type' },
|
||||
})
|
||||
})
|
||||
|
||||
export const Route = createFileRoute('/auth/confirm')({
|
||||
preload: false,
|
||||
loader: (opts) => confirmFn({ data: opts.location.search }),
|
||||
})
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/auth/error')({
|
||||
component: AuthError,
|
||||
validateSearch: (params) => {
|
||||
if (params.error && typeof params.error === 'string') {
|
||||
return { error: params.error }
|
||||
}
|
||||
return null
|
||||
},
|
||||
})
|
||||
|
||||
function AuthError() {
|
||||
const params = Route.useSearch()
|
||||
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sorry, something went wrong.</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{params?.error ? (
|
||||
<p className="text-sm text-muted-foreground">Code error: {params.error}</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">An unspecified error occurred.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { ForgotPasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/forgot-password-form'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/forgot-password')({
|
||||
component: ForgotPassword,
|
||||
})
|
||||
|
||||
function ForgotPassword() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<ForgotPasswordForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { LoginForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/login-form'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/login')({
|
||||
component: Login,
|
||||
})
|
||||
|
||||
function Login() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<LoginForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/registry/default/components/ui/card'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/sign-up-success')({
|
||||
component: SignUpSuccess,
|
||||
})
|
||||
|
||||
function SignUpSuccess() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Thank you for signing up!</CardTitle>
|
||||
<CardDescription>Check your email to confirm</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
You've successfully signed up. Please check your email to confirm your account
|
||||
before signing in.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { SignUpForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/sign-up-form'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/sign-up')({
|
||||
component: SignUp,
|
||||
})
|
||||
|
||||
function SignUp() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<SignUpForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { UpdatePasswordForm } from '@/registry/default/blocks/password-based-auth-tanstack/components/update-password-form'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/update-password')({
|
||||
component: UpdatePassword,
|
||||
})
|
||||
|
||||
function UpdatePassword() {
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<UpdatePasswordForm />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -36,5 +36,10 @@
|
||||
".contentlayer/generated",
|
||||
"./../../packages/ui/src/**/*.d.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "./scripts/build-registry.mts"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./scripts/build-registry.mts",
|
||||
// This file is only used in a registry block and not included in the app.
|
||||
"./registry/default/blocks/password-based-auth-tanstack/lib/supabase/fetch-user-server-fn.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user