UI library/rr (#34384)
* Add rr auth block * Fix file structure * Update components with new flow * Fix the registry item for react router auth. * Update the forgot-password for react router. * Fix the rest of the link imports. * Fix the react route client. * Refactor the auth block for React Router. * Add config for recovery mail to the ui library supabase config. * snapshot. * Minor fixes. * Minor doc fixes. --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
@@ -31,6 +31,18 @@ export const Index: Record<string, any> = {
|
||||
chunks: []
|
||||
}
|
||||
,
|
||||
"password-based-auth-react-router": {
|
||||
name: "password-based-auth-react-router",
|
||||
type: "registry:block",
|
||||
registryDependencies: ["button","card","input","label"],
|
||||
component: React.lazy(() => import("@/registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx")),
|
||||
source: "",
|
||||
files: ["registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx","registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx","registry/default/blocks/password-based-auth-react-router/app/routes.ts","registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
}
|
||||
,
|
||||
"password-based-auth-tanstack": {
|
||||
name: "password-based-auth-tanstack",
|
||||
type: "registry:block",
|
||||
@@ -73,7 +85,7 @@ export const Index: Record<string, any> = {
|
||||
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"],
|
||||
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: []
|
||||
@@ -121,7 +133,7 @@ export const Index: Record<string, any> = {
|
||||
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"],
|
||||
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: []
|
||||
@@ -169,7 +181,7 @@ export const Index: Record<string, any> = {
|
||||
registryDependencies: ["avatar"],
|
||||
component: React.lazy(() => import("@/registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx")),
|
||||
source: "",
|
||||
files: ["registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx","registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts","registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
|
||||
files: ["registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx","registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts","registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
@@ -217,7 +229,7 @@ export const Index: Record<string, any> = {
|
||||
registryDependencies: ["avatar","tooltip"],
|
||||
component: React.lazy(() => import("@/registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx")),
|
||||
source: "",
|
||||
files: ["registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx","registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx","registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts","registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
|
||||
files: ["registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx","registry/default/blocks/realtime-avatar-stack/components/realtime-avatar-stack.tsx","registry/default/blocks/realtime-avatar-stack/hooks/use-realtime-presence-room.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-name.ts","registry/default/blocks/current-user-avatar/hooks/use-current-user-image.ts","registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
@@ -265,7 +277,7 @@ export const Index: Record<string, any> = {
|
||||
registryDependencies: [],
|
||||
|
||||
source: "",
|
||||
files: ["registry/default/clients/react-router/lib/supabase.client.ts","registry/default/clients/react-router/lib/supabase.server.ts"],
|
||||
files: ["registry/default/clients/react-router/lib/supabase/client.ts","registry/default/clients/react-router/lib/supabase/server.ts"],
|
||||
category: "undefined",
|
||||
subcategory: "undefined",
|
||||
chunks: []
|
||||
|
||||
@@ -67,7 +67,7 @@ export const componentPages: Record<
|
||||
},
|
||||
'password-based-auth': {
|
||||
title: 'Password-Based Auth',
|
||||
supportedFrameworks: ['nextjs', 'tanstack', 'react'],
|
||||
supportedFrameworks: ['nextjs', 'react-router', 'tanstack', 'react'],
|
||||
commandItemLabel: 'Password-Based Auth',
|
||||
href: '/docs/password-based-auth',
|
||||
},
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
title: Password-based Auth (Next.js)
|
||||
description: Password-based Auth block for Next.js app
|
||||
---
|
||||
|
||||
<BlockPreview name="password-based-auth/sign-up" />
|
||||
|
||||
## Installation
|
||||
|
||||
<BlockItem
|
||||
name="password-based-auth-nextjs"
|
||||
description="All needed components for the password based auth flow"
|
||||
/>
|
||||
|
||||
## Folder structure
|
||||
|
||||
This block includes the [Supabase client](/ui/docs/react-router/client). When installing, you can skip overwriting it.
|
||||
|
||||
<RegistryBlock itemName="password-based-auth-react-router" />
|
||||
|
||||
## Usage
|
||||
|
||||
Once you install the block in your React Router project, you'll get all the necessary pages and components to set up a password-based authentication flow.
|
||||
|
||||
### Getting started
|
||||
|
||||
First, add a `.env` file to your project with the following environment variables:
|
||||
|
||||
```env
|
||||
VITE_SUPABASE_URL=
|
||||
VITE_SUPABASE_ANON_KEY=
|
||||
```
|
||||
|
||||
- If you're using supabase.com, you can find these values in the [Connect modal](https://supabase.com/dashboard/project/_?showConnect=true) under App Frameworks or in your project's [API settings](https://supabase.com/dashboard/project/_/settings/api).
|
||||
|
||||
- If you're using a local instance of supabase, you can find these values by running `supabase start` or `supabase status` (if you already have it running).
|
||||
|
||||
### Adding email templates
|
||||
|
||||
1. Add an [email template for sign-up](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your signup email template should contain at least the following HTML:
|
||||
|
||||
```html
|
||||
<h2>Confirm your signup</h2>
|
||||
|
||||
<p>Follow this link to confirm your user:</p>
|
||||
<p>
|
||||
<a
|
||||
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email&next={{ .RedirectTo }}"
|
||||
>Confirm your email</a
|
||||
>
|
||||
</p>
|
||||
```
|
||||
|
||||
For detailed instructions on how to configure your email templates, including the use of variables like `{{ .SiteURL }}`,`{{ .TokenHash }}`, and `{{ .RedirectTo }}`, refer to our [Email Templates guide](https://supabase.com/docs/email-templates).
|
||||
|
||||
1. Add an [email template for reset password](https://supabase.com/dashboard/project/_/auth/templates) to the Supabase project. Your reset password email template should contain at least the following HTML:
|
||||
|
||||
```html
|
||||
<h2>Reset Password</h2>
|
||||
|
||||
<p>Follow this link to reset the password for your user:</p>
|
||||
<p>
|
||||
<a
|
||||
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next={{ .RedirectTo }}"
|
||||
>Reset Password</a
|
||||
>
|
||||
</p>
|
||||
```
|
||||
|
||||
### Setting up routes and redirect URLs
|
||||
|
||||
1. Set the site URL in the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings in the Supabase Dashboard.
|
||||
1. Set up the route users will visit to reset or update their password. First, update the `forgot-password.tsx` component with your `update-password` route. You'll need to tell Supabase to allow this route as the redirect URL. Go to the [URL Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) settings and add that route to the list of Redirect URLs. It should look something like: `http://example.com/update-password`.
|
||||
|
||||
1. Update the redirect paths in the `login.tsx` and `update-password.tsx` components to point to the logged-in routes in your app.
|
||||
@@ -21,6 +21,7 @@
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.0.7",
|
||||
"@react-router/fs-routes": "^7.4.0",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"common": "workspace:*",
|
||||
"contentlayer2": "0.4.6",
|
||||
@@ -50,6 +51,7 @@
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "^7.1.5",
|
||||
"@shikijs/compat": "^1.1.7",
|
||||
"@supabase/ssr": "^0.6.1",
|
||||
"@supabase/supabase-js": "^2.49.1",
|
||||
@@ -65,6 +67,7 @@
|
||||
"mdast-util-toc": "^6.1.1",
|
||||
"postcss": "^8",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-router": "^7.4.0",
|
||||
"rimraf": "^4.1.3",
|
||||
"shadcn": "2.4.0-canary.16",
|
||||
"shiki": "^1.1.7",
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"title": "Current User Avatar",
|
||||
"description": "Component which renders the current user's avatar.",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"avatar"
|
||||
@@ -27,13 +28,13 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"content": "import { createBrowserClient } from '@supabase/ssr'\n\n// Use this function to create a client for the browser. You should pass the env variables through a loader and use them\n// to instantiate the client.\nexport function createClient(supabaseUrl: string, supabaseAnonKey: string) {\n return createBrowserClient(supabaseUrl, supabaseAnonKey)\n}\n",
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase.server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n })\n\n return { supabase, headers }\n}\n",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"dependencies": [
|
||||
"react-dropzone",
|
||||
"lucide-react",
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
@@ -26,13 +27,13 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"content": "import { createBrowserClient } from '@supabase/ssr'\n\n// Use this function to create a client for the browser. You should pass the env variables through a loader and use them\n// to instantiate the client.\nexport function createClient(supabaseUrl: string, supabaseAnonKey: string) {\n return createBrowserClient(supabaseUrl, supabaseAnonKey)\n}\n",
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase.server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n })\n\n return { supabase, headers }\n}\n",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"title": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"description": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
||||
"name": "password-based-auth-react-router",
|
||||
"type": "registry:block",
|
||||
"title": "Password Based Auth flow for React Router and Supabase",
|
||||
"description": "Password Based Auth flow for React Router and Supabase",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@react-router/dev@latest",
|
||||
"@react-router/fs-routes@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"button",
|
||||
"card",
|
||||
"input",
|
||||
"label"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { type EmailOtpType } from '@supabase/supabase-js'\nimport { type LoaderFunctionArgs, redirect } from 'react-router'\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const requestUrl = new URL(request.url)\n const token_hash = requestUrl.searchParams.get('token_hash')\n const type = requestUrl.searchParams.get('type') as EmailOtpType | null\n const next = requestUrl.searchParams.get('next') || '/'\n\n if (token_hash && type) {\n const { supabase, headers } = createClient(request)\n const { error } = await supabase.auth.verifyOtp({\n type,\n token_hash,\n })\n if (!error) {\n return redirect(next, { headers })\n } else {\n return redirect(`/auth/error?error=${error?.message}`)\n }\n }\n\n // redirect the user to an error page with some instructions\n return redirect(`/auth/error?error=No token hash or type`)\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/auth.confirm.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx",
|
||||
"content": "import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'\nimport { useSearchParams } from 'react-router'\n\nexport default function Page() {\n let [searchParams] = useSearchParams()\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 {searchParams?.get('error') ? (\n <p className=\"text-sm text-muted-foreground\">\n Code error: {searchParams?.get('error')}\n </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": "app/routes/auth.error.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\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 {\n type ActionFunctionArgs,\n Link,\n data,\n redirect,\n useFetcher,\n useSearchParams,\n} from 'react-router'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const formData = await request.formData()\n const email = formData.get('email') as string\n\n const { supabase, headers } = createClient(request)\n const origin = new URL(request.url).origin\n\n // Send the actual reset password email\n const { error } = await supabase.auth.resetPasswordForEmail(email, {\n redirectTo: `${origin}/auth/confirm?next=/update-password`,\n })\n\n if (error) {\n return data(\n {\n error: error instanceof Error ? error.message : 'An error occurred',\n data: { email },\n },\n { headers }\n )\n }\n\n return redirect('/forgot-password?success')\n}\n\nexport default function ForgotPassword() {\n const fetcher = useFetcher<typeof action>()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\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 {success ? (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Check Your Email</CardTitle>\n <CardDescription>Password reset instructions sent</CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-muted-foreground\">\n If you registered using your email and password, you will receive a password reset\n email.\n </p>\n </CardContent>\n </Card>\n ) : (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Reset Your Password</CardTitle>\n <CardDescription>\n Type in your email and we'll send you a link to reset your password\n </CardDescription>\n </CardHeader>\n <CardContent>\n <fetcher.Form method=\"post\">\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 name=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n {loading ? '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 </fetcher.Form>\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/forgot-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\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 { type ActionFunctionArgs, Link, redirect, useFetcher } from 'react-router'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n\n const { error } = await supabase.auth.signInWithPassword({\n email,\n password,\n })\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n\n // Update this route to redirect to an authenticated route. The user already has an active session.\n return redirect('/protected', { headers })\n}\n\nexport default function Login() {\n const fetcher = useFetcher<typeof action>()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\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\">Login</CardTitle>\n <CardDescription>Enter your email below to login to your account</CardDescription>\n </CardHeader>\n <CardContent>\n <fetcher.Form method=\"post\">\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 name=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\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 id=\"password\" type=\"password\" name=\"password\" required />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n {loading ? '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 </fetcher.Form>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/login.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { type ActionFunctionArgs, redirect } from 'react-router'\n\nexport async function loader({ request }: ActionFunctionArgs) {\n const { supabase, headers } = createClient(request)\n\n const { error } = await supabase.auth.signOut()\n\n if (error) {\n console.error(error)\n return { success: false, error: error.message }\n }\n\n // Redirect to dashboard or home page after successful sign-in\n return redirect('/', { headers })\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/logout.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\nimport { Button } from '@/registry/default/components/ui/button'\nimport { type LoaderFunctionArgs, redirect, useLoaderData } from 'react-router'\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const { data, error } = await supabase.auth.getUser()\n if (error || !data?.user) {\n return redirect('/login')\n }\n\n return data\n}\n\nexport default function ProtectedPage() {\n let data = useLoaderData<typeof loader>()\n\n return (\n <div className=\"flex items-center justify-center h-screen gap-2\">\n <p>\n Hello <span className=\"text-primary font-semibold\">{data.user.email}</span>\n </p>\n <a href=\"/logout\">\n <Button>Logout</Button>\n </a>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/protected.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\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 { type ActionFunctionArgs, Link, redirect, useFetcher, useSearchParams } from 'react-router'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase } = createClient(request)\n\n const url = new URL(request.url)\n const origin = url.origin\n\n const formData = await request.formData()\n\n const email = formData.get('email') as string\n const password = formData.get('password') as string\n const repeatPassword = formData.get('repeat-password') as string\n\n if (!password) {\n return {\n error: 'Password is required',\n }\n }\n\n if (password !== repeatPassword) {\n return { error: 'Passwords do not match' }\n }\n\n const { error } = await supabase.auth.signUp({\n email,\n password,\n options: {\n emailRedirectTo: `${origin}/protected`,\n },\n })\n\n if (error) {\n return { error: error.message }\n }\n\n return redirect('/sign-up?success')\n}\n\nexport default function SignUp() {\n const fetcher = useFetcher<typeof action>()\n let [searchParams] = useSearchParams()\n\n const success = !!searchParams.has('success')\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\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 {success ? (\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 ) : (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-2xl\">Sign up</CardTitle>\n <CardDescription>Create a new account</CardDescription>\n </CardHeader>\n <CardContent>\n <fetcher.Form method=\"post\">\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 name=\"email\"\n type=\"email\"\n placeholder=\"m@example.com\"\n required\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 id=\"password\" name=\"password\" type=\"password\" required />\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 id=\"repeat-password\" name=\"repeat-password\" type=\"password\" required />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n {loading ? '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 </fetcher.Form>\n </CardContent>\n </Card>\n )}\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/sign-up.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx",
|
||||
"content": "import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'\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 { type ActionFunctionArgs, redirect, useFetcher } from 'react-router'\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { supabase, headers } = createClient(request)\n const formData = await request.formData()\n const password = formData.get('password') as string\n\n if (!password) {\n return { error: 'Password is required' }\n }\n\n const { error } = await supabase.auth.updateUser({ password: password })\n\n if (error) {\n return {\n error: error instanceof Error ? error.message : 'An error occurred',\n }\n }\n\n // Redirect to sign-in page after successful password update\n return redirect('/protected', { headers })\n}\n\nexport default function Page() {\n const fetcher = useFetcher<typeof action>()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\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\">Reset Your Password</CardTitle>\n <CardDescription>Please enter your new password below.</CardDescription>\n </CardHeader>\n <CardContent>\n <fetcher.Form method=\"post\">\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 name=\"password\"\n type=\"password\"\n placeholder=\"New password\"\n required\n />\n </div>\n {error && <p className=\"text-sm text-red-500\">{error}</p>}\n <Button type=\"submit\" className=\"w-full\" disabled={loading}>\n {loading ? 'Saving...' : 'Save new password'}\n </Button>\n </div>\n </fetcher.Form>\n </CardContent>\n </Card>\n </div>\n </div>\n </div>\n )\n}\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/update-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes.ts",
|
||||
"content": "import { type RouteConfig } from '@react-router/dev/routes'\nimport { flatRoutes } from '@react-router/fs-routes'\n\nexport default flatRoutes() satisfies RouteConfig\n",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"title": "Avatar Stack with Realtime Presence",
|
||||
"description": "Component which stack of avatars, tracked by realtime presence.",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [
|
||||
"avatar",
|
||||
@@ -38,13 +39,13 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"content": "import { createBrowserClient } from '@supabase/ssr'\n\n// Use this function to create a client for the browser. You should pass the env variables through a loader and use them\n// to instantiate the client.\nexport function createClient(supabaseUrl: string, supabaseAnonKey: string) {\n return createBrowserClient(supabaseUrl, supabaseAnonKey)\n}\n",
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase.server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n })\n\n return { supabase, headers }\n}\n",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"description": "Component which renders realtime cursors from other users in a room.",
|
||||
"dependencies": [
|
||||
"lucide-react",
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [],
|
||||
"files": [
|
||||
@@ -26,13 +27,13 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"content": "import { createBrowserClient } from '@supabase/ssr'\n\n// Use this function to create a client for the browser. You should pass the env variables through a loader and use them\n// to instantiate the client.\nexport function createClient(supabaseUrl: string, supabaseAnonKey: string) {\n return createBrowserClient(supabaseUrl, supabaseAnonKey)\n}\n",
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase.server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n })\n\n return { supabase, headers }\n}\n",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,18 +5,19 @@
|
||||
"title": "Supabase Client for React Router",
|
||||
"description": "",
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest"
|
||||
"@supabase/ssr@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"registryDependencies": [],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"content": "import { createBrowserClient } from '@supabase/ssr'\n\n// Use this function to create a client for the browser. You should pass the env variables through a loader and use them\n// to instantiate the client.\nexport function createClient(supabaseUrl: string, supabaseAnonKey: string) {\n return createBrowserClient(supabaseUrl, supabaseAnonKey)\n}\n",
|
||||
"path": "registry/default/clients/react-router/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/react-router/lib/supabase.server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n })\n\n return { supabase, headers }\n}\n",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"content": "import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'\n\nexport function createClient(request: Request) {\n const headers = new Headers()\n\n const supabase = createServerClient(\n process.env.VITE_SUPABASE_URL!,\n process.env.VITE_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return parseCookieHeader(request.headers.get('Cookie') ?? '') as {\n name: string\n value: string\n }[]\n },\n setAll(cookiesToSet) {\n cookiesToSet.forEach(({ name, value, options }) =>\n headers.append('Set-Cookie', serializeCookieHeader(name, value, options))\n )\n },\n },\n }\n )\n\n return { supabase, headers }\n}\n",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"title": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"description": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx",
|
||||
@@ -116,6 +116,74 @@
|
||||
],
|
||||
"dependencies": ["@supabase/supabase-js@latest"]
|
||||
},
|
||||
{
|
||||
"name": "password-based-auth-react-router",
|
||||
"type": "registry:block",
|
||||
"title": "Password Based Auth flow for React Router and Supabase",
|
||||
"description": "Password Based Auth flow for React Router and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@react-router/dev@latest",
|
||||
"@react-router/fs-routes@latest",
|
||||
"@supabase/supabase-js@latest"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/auth.confirm.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/auth.error.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/forgot-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/login.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/logout.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/protected.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/sign-up.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/update-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes.ts",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes.ts"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "password-based-auth-tanstack",
|
||||
"type": "registry:block",
|
||||
@@ -260,7 +328,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",
|
||||
@@ -271,11 +344,11 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
@@ -381,7 +454,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",
|
||||
@@ -396,11 +469,11 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
@@ -505,7 +578,7 @@
|
||||
"title": "Current User Avatar",
|
||||
"description": "Component which renders the current user's avatar.",
|
||||
"registryDependencies": ["avatar"],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/current-user-avatar/components/current-user-avatar.tsx",
|
||||
@@ -520,11 +593,11 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
@@ -645,7 +718,7 @@
|
||||
"title": "Avatar Stack with Realtime Presence",
|
||||
"description": "Component which stack of avatars, tracked by realtime presence.",
|
||||
"registryDependencies": ["avatar", "tooltip"],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/realtime-avatar-stack/components/avatar-stack.tsx",
|
||||
@@ -668,11 +741,11 @@
|
||||
"type": "registry:hook"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
@@ -761,14 +834,14 @@
|
||||
"title": "Supabase Client for React Router",
|
||||
"description": "",
|
||||
"registryDependencies": [],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@ import { clients } from './clients'
|
||||
import currentUserAvatar from './default/blocks/current-user-avatar/registry-item.json' assert { type: 'json' }
|
||||
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 passwordBasedAuthReactRouter from './default/blocks/password-based-auth-react-router/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 realtimeAvatarStack from './default/blocks/realtime-avatar-stack/registry-item.json' assert { type: 'json' }
|
||||
@@ -25,10 +26,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')
|
||||
const reactRouterClient = clients.find((client) => client.name === 'supabase-client-react-router')
|
||||
|
||||
export const blocks = [
|
||||
registryItemAppend(passwordBasedAuthNextjs as RegistryItem, [nextjsClient!]),
|
||||
registryItemAppend(passwordBasedAuthReact as RegistryItem, [reactClient!]),
|
||||
registryItemAppend(passwordBasedAuthReactRouter as RegistryItem, [reactRouterClient!]),
|
||||
registryItemAppend(passwordBasedAuthTanstack as RegistryItem, [tanstackClient!]),
|
||||
...combine(dropzone as RegistryItem),
|
||||
...combine(realtimeCursor as RegistryItem),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"title": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"description": "Password Based Auth flow for Nextjs and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-nextjs/app/login/page.tsx",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import { type RouteConfig } from '@react-router/dev/routes'
|
||||
import { flatRoutes } from '@react-router/fs-routes'
|
||||
|
||||
export default flatRoutes() satisfies RouteConfig
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
import { type EmailOtpType } from '@supabase/supabase-js'
|
||||
import { type LoaderFunctionArgs, redirect } from 'react-router'
|
||||
|
||||
export async function loader({ request }: LoaderFunctionArgs) {
|
||||
const requestUrl = new URL(request.url)
|
||||
const token_hash = requestUrl.searchParams.get('token_hash')
|
||||
const type = requestUrl.searchParams.get('type') as EmailOtpType | null
|
||||
const next = requestUrl.searchParams.get('next') || '/'
|
||||
|
||||
if (token_hash && type) {
|
||||
const { supabase, headers } = createClient(request)
|
||||
const { error } = await supabase.auth.verifyOtp({
|
||||
type,
|
||||
token_hash,
|
||||
})
|
||||
if (!error) {
|
||||
return redirect(next, { headers })
|
||||
} else {
|
||||
return redirect(`/auth/error?error=${error?.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// redirect the user to an error page with some instructions
|
||||
return redirect(`/auth/error?error=No token hash or type`)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/registry/default/components/ui/card'
|
||||
import { useSearchParams } from 'react-router'
|
||||
|
||||
export default function Page() {
|
||||
let [searchParams] = useSearchParams()
|
||||
|
||||
return (
|
||||
<div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="flex flex-col gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sorry, something went wrong.</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{searchParams?.get('error') ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Code error: {searchParams?.get('error')}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">An unspecified error occurred.</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
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 {
|
||||
type ActionFunctionArgs,
|
||||
Link,
|
||||
data,
|
||||
redirect,
|
||||
useFetcher,
|
||||
useSearchParams,
|
||||
} from 'react-router'
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const formData = await request.formData()
|
||||
const email = formData.get('email') as string
|
||||
|
||||
const { supabase, headers } = createClient(request)
|
||||
const origin = new URL(request.url).origin
|
||||
|
||||
// Send the actual reset password email
|
||||
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||
redirectTo: `${origin}/auth/confirm?next=/update-password`,
|
||||
})
|
||||
|
||||
if (error) {
|
||||
return data(
|
||||
{
|
||||
error: error instanceof Error ? error.message : 'An error occurred',
|
||||
data: { email },
|
||||
},
|
||||
{ headers }
|
||||
)
|
||||
}
|
||||
|
||||
return redirect('/forgot-password?success')
|
||||
}
|
||||
|
||||
export default function ForgotPassword() {
|
||||
const fetcher = useFetcher<typeof action>()
|
||||
let [searchParams] = useSearchParams()
|
||||
|
||||
const success = !!searchParams.has('success')
|
||||
const error = fetcher.data?.error
|
||||
const loading = fetcher.state === 'submitting'
|
||||
|
||||
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">
|
||||
{success ? (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Check Your Email</CardTitle>
|
||||
<CardDescription>Password reset instructions sent</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
If you registered using your email and password, you will receive a password reset
|
||||
email.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Reset Your Password</CardTitle>
|
||||
<CardDescription>
|
||||
Type in your email and we'll send you a link to reset your password
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<fetcher.Form method="post">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? '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>
|
||||
</fetcher.Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
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 { type ActionFunctionArgs, Link, redirect, useFetcher } from 'react-router'
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const { supabase, headers } = createClient(request)
|
||||
|
||||
const formData = await request.formData()
|
||||
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
const { error } = await supabase.auth.signInWithPassword({
|
||||
email,
|
||||
password,
|
||||
})
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'An error occurred',
|
||||
}
|
||||
}
|
||||
|
||||
// Update this route to redirect to an authenticated route. The user already has an active session.
|
||||
return redirect('/protected', { headers })
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
const fetcher = useFetcher<typeof action>()
|
||||
|
||||
const error = fetcher.data?.error
|
||||
const loading = fetcher.state === 'submitting'
|
||||
|
||||
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">Login</CardTitle>
|
||||
<CardDescription>Enter your email below to login to your account</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<fetcher.Form method="post">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</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" name="password" required />
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? '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>
|
||||
</fetcher.Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
import { type ActionFunctionArgs, redirect } from 'react-router'
|
||||
|
||||
export async function loader({ request }: ActionFunctionArgs) {
|
||||
const { supabase, headers } = createClient(request)
|
||||
|
||||
const { error } = await supabase.auth.signOut()
|
||||
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return { success: false, error: error.message }
|
||||
}
|
||||
|
||||
// Redirect to dashboard or home page after successful sign-in
|
||||
return redirect('/', { headers })
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
import { Button } from '@/registry/default/components/ui/button'
|
||||
import { type LoaderFunctionArgs, redirect, useLoaderData } from 'react-router'
|
||||
|
||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||
const { supabase } = createClient(request)
|
||||
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
if (error || !data?.user) {
|
||||
return redirect('/login')
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export default function ProtectedPage() {
|
||||
let data = useLoaderData<typeof loader>()
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen gap-2">
|
||||
<p>
|
||||
Hello <span className="text-primary font-semibold">{data.user.email}</span>
|
||||
</p>
|
||||
<a href="/logout">
|
||||
<Button>Logout</Button>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
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 { type ActionFunctionArgs, Link, redirect, useFetcher, useSearchParams } from 'react-router'
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const { supabase } = createClient(request)
|
||||
|
||||
const url = new URL(request.url)
|
||||
const origin = url.origin
|
||||
|
||||
const formData = await request.formData()
|
||||
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
const repeatPassword = formData.get('repeat-password') as string
|
||||
|
||||
if (!password) {
|
||||
return {
|
||||
error: 'Password is required',
|
||||
}
|
||||
}
|
||||
|
||||
if (password !== repeatPassword) {
|
||||
return { error: 'Passwords do not match' }
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.signUp({
|
||||
email,
|
||||
password,
|
||||
options: {
|
||||
emailRedirectTo: `${origin}/protected`,
|
||||
},
|
||||
})
|
||||
|
||||
if (error) {
|
||||
return { error: error.message }
|
||||
}
|
||||
|
||||
return redirect('/sign-up?success')
|
||||
}
|
||||
|
||||
export default function SignUp() {
|
||||
const fetcher = useFetcher<typeof action>()
|
||||
let [searchParams] = useSearchParams()
|
||||
|
||||
const success = !!searchParams.has('success')
|
||||
const error = fetcher.data?.error
|
||||
const loading = fetcher.state === 'submitting'
|
||||
|
||||
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">
|
||||
{success ? (
|
||||
<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>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Sign up</CardTitle>
|
||||
<CardDescription>Create a new account</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<fetcher.Form method="post">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="email">Email</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="m@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="password">Password</Label>
|
||||
</div>
|
||||
<Input id="password" name="password" type="password" required />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="flex items-center">
|
||||
<Label htmlFor="repeat-password">Repeat Password</Label>
|
||||
</div>
|
||||
<Input id="repeat-password" name="repeat-password" type="password" required />
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? '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>
|
||||
</fetcher.Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { createClient } from '@/registry/default/clients/react-router/lib/supabase/server'
|
||||
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 { type ActionFunctionArgs, redirect, useFetcher } from 'react-router'
|
||||
|
||||
export const action = async ({ request }: ActionFunctionArgs) => {
|
||||
const { supabase, headers } = createClient(request)
|
||||
const formData = await request.formData()
|
||||
const password = formData.get('password') as string
|
||||
|
||||
if (!password) {
|
||||
return { error: 'Password is required' }
|
||||
}
|
||||
|
||||
const { error } = await supabase.auth.updateUser({ password: password })
|
||||
|
||||
if (error) {
|
||||
return {
|
||||
error: error instanceof Error ? error.message : 'An error occurred',
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect to sign-in page after successful password update
|
||||
return redirect('/protected', { headers })
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const fetcher = useFetcher<typeof action>()
|
||||
|
||||
const error = fetcher.data?.error
|
||||
const loading = fetcher.state === 'submitting'
|
||||
|
||||
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">Reset Your Password</CardTitle>
|
||||
<CardDescription>Please enter your new password below.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<fetcher.Form method="post">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="password">New password</Label>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="New password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{error && <p className="text-sm text-red-500">{error}</p>}
|
||||
<Button type="submit" className="w-full" disabled={loading}>
|
||||
{loading ? 'Saving...' : 'Save new password'}
|
||||
</Button>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "password-based-auth-react-router",
|
||||
"type": "registry:block",
|
||||
"title": "Password Based Auth flow for React Router and Supabase",
|
||||
"description": "Password Based Auth flow for React Router and Supabase",
|
||||
"registryDependencies": ["button", "card", "input", "label"],
|
||||
"dependencies": [
|
||||
"@supabase/ssr@latest",
|
||||
"@react-router/dev@latest",
|
||||
"@react-router/fs-routes@latest"
|
||||
],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/auth.confirm.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/auth.error.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/forgot-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/login.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/logout.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/protected.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/sign-up.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes/update-password.tsx"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/blocks/password-based-auth-react-router/app/routes.ts",
|
||||
"type": "registry:file",
|
||||
"target": "app/routes.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
|
||||
// Use this function to create a client for the browser. You should pass the env variables through a loader and use them
|
||||
// to instantiate the client.
|
||||
export function createClient(supabaseUrl: string, supabaseAnonKey: string) {
|
||||
return createBrowserClient(supabaseUrl, supabaseAnonKey)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
||||
|
||||
export function createClient(request: Request) {
|
||||
const headers = new Headers()
|
||||
|
||||
const supabase = createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!, {
|
||||
cookies: {
|
||||
getAll() {
|
||||
return parseCookieHeader(request.headers.get('Cookie') ?? '') as {
|
||||
name: string
|
||||
value: string
|
||||
}[]
|
||||
},
|
||||
setAll(cookiesToSet) {
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
||||
)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return { supabase, headers }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/// <reference types="vite/types/importMeta.d.ts" />
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
|
||||
export function createClient() {
|
||||
return createBrowserClient(
|
||||
import.meta.env.VITE_SUPABASE_URL!,
|
||||
import.meta.env.VITE_SUPABASE_ANON_KEY!
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createServerClient, parseCookieHeader, serializeCookieHeader } from '@supabase/ssr'
|
||||
|
||||
export function createClient(request: Request) {
|
||||
const headers = new Headers()
|
||||
|
||||
const supabase = createServerClient(
|
||||
process.env.VITE_SUPABASE_URL!,
|
||||
process.env.VITE_SUPABASE_ANON_KEY!,
|
||||
{
|
||||
cookies: {
|
||||
getAll() {
|
||||
return parseCookieHeader(request.headers.get('Cookie') ?? '') as {
|
||||
name: string
|
||||
value: string
|
||||
}[]
|
||||
},
|
||||
setAll(cookiesToSet) {
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
headers.append('Set-Cookie', serializeCookieHeader(name, value, options))
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return { supabase, headers }
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
"title": "Supabase Client for React Router",
|
||||
"description": "",
|
||||
"registryDependencies": [],
|
||||
"dependencies": ["@supabase/ssr@latest"],
|
||||
"dependencies": ["@supabase/ssr@latest", "@supabase/supabase-js@latest"],
|
||||
"files": [
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.client.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/client.ts",
|
||||
"type": "registry:lib"
|
||||
},
|
||||
{
|
||||
"path": "registry/default/clients/react-router/lib/supabase.server.ts",
|
||||
"path": "registry/default/clients/react-router/lib/supabase/server.ts",
|
||||
"type": "registry:lib"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -129,7 +129,7 @@ password_requirements = ""
|
||||
|
||||
[auth.rate_limit]
|
||||
# Number of emails that can be sent per hour. Requires auth.email.smtp to be enabled.
|
||||
email_sent = 2
|
||||
email_sent = 100
|
||||
# Number of SMS messages that can be sent per hour. Requires auth.sms to be enabled.
|
||||
sms_sent = 30
|
||||
# Number of anonymous sign-ins that can be made per hour per IP address. Requires enable_anonymous_sign_ins = true.
|
||||
@@ -180,6 +180,10 @@ otp_expiry = 3600
|
||||
subject = "You have been invited"
|
||||
content_path = "./supabase/templates/confirmation.html"
|
||||
|
||||
[auth.email.template.recovery]
|
||||
subject = "Reset your password"
|
||||
content_path = "./supabase/templates/recovery.html"
|
||||
|
||||
[auth.sms]
|
||||
# Allow/disallow new user signups via SMS to your project.
|
||||
enable_signup = false
|
||||
|
||||
8
apps/ui-library/supabase/templates/recovery.html
Normal file
8
apps/ui-library/supabase/templates/recovery.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<h2>Reset Password</h2>
|
||||
<p>Follow this link to reset the password for your user:</p>
|
||||
<p>
|
||||
<a
|
||||
href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=recovery&next=/update-password"
|
||||
>Reset Password</a
|
||||
>
|
||||
</p>
|
||||
417
pnpm-lock.yaml
generated
417
pnpm-lock.yaml
generated
@@ -711,7 +711,7 @@ importers:
|
||||
version: 0.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
nuqs:
|
||||
specifier: ^2.4.1
|
||||
version: 2.4.1(next@14.2.26(@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@18.2.0)
|
||||
version: 2.4.1(next@14.2.26(@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-router@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
|
||||
openai:
|
||||
specifier: ^4.20.1
|
||||
version: 4.71.1(encoding@0.1.13)(zod@3.23.8)
|
||||
@@ -1007,6 +1007,9 @@ importers:
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@react-router/fs-routes':
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0(@react-router/dev@7.4.0(@types/node@22.13.10)(jiti@2.4.2)(react-router@7.4.0(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))(yaml@2.4.5))(typescript@5.5.2)
|
||||
class-variance-authority:
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.1
|
||||
@@ -1089,6 +1092,9 @@ importers:
|
||||
specifier: ^3.22.4
|
||||
version: 3.23.8
|
||||
devDependencies:
|
||||
'@react-router/dev':
|
||||
specifier: ^7.1.5
|
||||
version: 7.4.0(@types/node@22.13.10)(jiti@2.4.2)(react-router@7.4.0(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))(yaml@2.4.5)
|
||||
'@shikijs/compat':
|
||||
specifier: ^1.1.7
|
||||
version: 1.6.0
|
||||
@@ -1134,6 +1140,9 @@ importers:
|
||||
react-dropzone:
|
||||
specifier: ^14.3.8
|
||||
version: 14.3.8(react@18.2.0)
|
||||
react-router:
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
rimraf:
|
||||
specifier: ^4.1.3
|
||||
version: 4.4.1
|
||||
@@ -2458,11 +2467,6 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.26.7':
|
||||
resolution: {integrity: sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/parser@7.27.0':
|
||||
resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -2489,6 +2493,12 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-syntax-decorators@7.25.9':
|
||||
resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-syntax-import-attributes@7.26.0':
|
||||
resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2559,6 +2569,12 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-modules-commonjs@7.26.3':
|
||||
resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9':
|
||||
resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2577,6 +2593,12 @@ packages:
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/preset-typescript@7.26.0':
|
||||
resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
peerDependencies:
|
||||
'@babel/core': ^7.0.0-0
|
||||
|
||||
'@babel/runtime@7.24.7':
|
||||
resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2605,10 +2627,6 @@ packages:
|
||||
resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.26.7':
|
||||
resolution: {integrity: sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/types@7.27.0':
|
||||
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -3643,6 +3661,9 @@ packages:
|
||||
autoprefixer: ^10.0.2
|
||||
postcss: ^8.0.9
|
||||
|
||||
'@mjackson/node-fetch-server@0.2.0':
|
||||
resolution: {integrity: sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==}
|
||||
|
||||
'@monaco-editor/loader@1.4.0':
|
||||
resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==}
|
||||
peerDependencies:
|
||||
@@ -3790,11 +3811,23 @@ packages:
|
||||
resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
'@npmcli/git@4.1.0':
|
||||
resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
'@npmcli/move-file@1.1.2':
|
||||
resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
|
||||
engines: {node: '>=10'}
|
||||
deprecated: This functionality has been moved to @npmcli/fs
|
||||
|
||||
'@npmcli/package-json@4.0.1':
|
||||
resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
'@npmcli/promise-spawn@6.0.2':
|
||||
resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
'@number-flow/react@0.3.2':
|
||||
resolution: {integrity: sha512-/Rg7WjIZR/yjHJAzRHN7+Cif+s9U02QewMl9WEKPoAY9O6jg0wA/IsAl3lJgeM1ic31bDJ92wfCkwE9ud62VmQ==}
|
||||
peerDependencies:
|
||||
@@ -5456,6 +5489,44 @@ packages:
|
||||
'@react-dnd/shallowequal@4.0.2':
|
||||
resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==}
|
||||
|
||||
'@react-router/dev@7.4.0':
|
||||
resolution: {integrity: sha512-z3++B3H6k/GyMoR05CUiy2ney5ZxFWJ8YLyVN8wBU2h4aVscqxxVoX9GyrMvjz81VPtrzf0S2iPTJ758ZVmEIg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@react-router/serve': ^7.4.0
|
||||
react-router: ^7.4.0
|
||||
typescript: ^5.1.0
|
||||
vite: ^5.1.0 || ^6.0.0
|
||||
wrangler: ^3.28.2
|
||||
peerDependenciesMeta:
|
||||
'@react-router/serve':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
wrangler:
|
||||
optional: true
|
||||
|
||||
'@react-router/fs-routes@7.4.0':
|
||||
resolution: {integrity: sha512-jORT5Suo5agu2lVPqEn9PR2DWTS3JMHBoPca6HnVHRErLGG3FhyM1WSQxcShAf5qE6TtS91s5/Fpth+ij+82ig==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@react-router/dev': ^7.4.0
|
||||
typescript: ^5.1.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@react-router/node@7.4.0':
|
||||
resolution: {integrity: sha512-Y+2CXCPmnxcBbdTNGYO7pDusP+1xSlInbFXhsgjsM5WPH+5aDKHKYmDDrOT7fkg0l3b/SkzfwVsJg5XT9qjb/A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react-router: 7.4.0
|
||||
typescript: ^5.1.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@reactflow/background@11.3.6':
|
||||
resolution: {integrity: sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==}
|
||||
peerDependencies:
|
||||
@@ -9117,6 +9188,10 @@ packages:
|
||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||
engines: {node: '>=16.17'}
|
||||
|
||||
exit-hook@2.2.1:
|
||||
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
exit@0.1.2:
|
||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -9834,6 +9909,10 @@ packages:
|
||||
hosted-git-info@2.8.9:
|
||||
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
|
||||
|
||||
hosted-git-info@6.1.3:
|
||||
resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
html-encoding-sniffer@3.0.0:
|
||||
resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -10589,6 +10668,11 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.0.2:
|
||||
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
|
||||
engines: {node: '>=6'}
|
||||
hasBin: true
|
||||
|
||||
jsesc@3.1.0:
|
||||
resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -10606,6 +10690,10 @@ packages:
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
json-parse-even-better-errors@3.0.2:
|
||||
resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
json-pointer@0.6.2:
|
||||
resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==}
|
||||
|
||||
@@ -10878,6 +10966,10 @@ packages:
|
||||
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lru-cache@7.18.3:
|
||||
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
lucide-react@0.436.0:
|
||||
resolution: {integrity: sha512-N292bIxoqm1aObAg0MzFtvhYwgQE6qnIOWx/GLj5ONgcTPH6N0fD9bVq/GfdeC9ZORBXozt/XeEKDpiB3x3vlQ==}
|
||||
peerDependencies:
|
||||
@@ -11808,6 +11900,10 @@ packages:
|
||||
normalize-package-data@2.5.0:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
|
||||
normalize-package-data@5.0.0:
|
||||
resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
normalize-path@3.0.0:
|
||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -11816,10 +11912,22 @@ packages:
|
||||
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
npm-install-checks@6.3.0:
|
||||
resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
npm-normalize-package-bin@3.0.1:
|
||||
resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
npm-package-arg@10.1.0:
|
||||
resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
npm-pick-manifest@8.0.2:
|
||||
resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
npm-run-all@4.1.5:
|
||||
resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==}
|
||||
engines: {node: '>= 4'}
|
||||
@@ -12917,6 +13025,16 @@ packages:
|
||||
peerDependencies:
|
||||
react: '>= 16.3'
|
||||
|
||||
react-router@7.4.0:
|
||||
resolution: {integrity: sha512-Y2g5ObjkvX3VFeVt+0CIPuYd9PpgqCslG7ASSIdN73LwA1nNWzcMLaoMRJfP3prZFI92svxFwbn7XkLJ+UPQ6A==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: '>=18'
|
||||
react-dom: '>=18'
|
||||
peerDependenciesMeta:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
react-simple-maps@4.0.0-beta.6:
|
||||
resolution: {integrity: sha512-PVKah7p9AgmAesKTijIzUHP1iSq7FTpuY5g8DixKZWkIEvNLjL/gjPok9iqhIS6gmw6aziQxNSQ/C6umZwMePg==}
|
||||
peerDependencies:
|
||||
@@ -13783,6 +13901,9 @@ packages:
|
||||
resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
stream-slice@0.1.2:
|
||||
resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==}
|
||||
|
||||
streamsearch@1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -14320,6 +14441,9 @@ packages:
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
turbo-stream@2.4.0:
|
||||
resolution: {integrity: sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==}
|
||||
|
||||
turbo-windows-64@2.3.3:
|
||||
resolution: {integrity: sha512-O2+BS4QqjK3dOERscXqv7N2GXNcqHr9hXumkMxDj/oGx9oCatIwnnwx34UmzodloSnJpgSqjl8iRWiY65SmYoQ==}
|
||||
cpu: [x64]
|
||||
@@ -14761,9 +14885,21 @@ packages:
|
||||
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
|
||||
engines: {node: '>=10.12.0'}
|
||||
|
||||
valibot@0.41.0:
|
||||
resolution: {integrity: sha512-igDBb8CTYr8YTQlOKgaN9nSS0Be7z+WRuaeYqGf3Cjz3aKmSnqEmYnkfVjzIuumGqfHpa3fLIvMEAfhrpqN8ng==}
|
||||
peerDependencies:
|
||||
typescript: '>=5'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
|
||||
|
||||
validate-npm-package-name@5.0.1:
|
||||
resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
|
||||
valtio@1.12.0:
|
||||
resolution: {integrity: sha512-co8NkCHeY0NsL0XsL/cSICt5VhTjwZlYT8mi50dYY5thx3r3w1D15A04Lvs9WL/y/Rf98vUKY5PAAJCTLHvkJw==}
|
||||
engines: {node: '>=12.20.0'}
|
||||
@@ -14820,6 +14956,11 @@ packages:
|
||||
resolution: {integrity: sha512-4sL2SMrRzdzClapP44oXdGjCE1oq7/DagsbjY5A09EibmoIO4LP8ScRVdh03lfXxKRk7nCWK7n7dqKvm+fp/9w==}
|
||||
hasBin: true
|
||||
|
||||
vite-node@3.0.0-beta.2:
|
||||
resolution: {integrity: sha512-ofTf6cfRdL30Wbl9n/BX81EyIR5s4PReLmSurrxQ+koLaWUNOEo8E0lCM53OJkb8vpa2URM2nSrxZsIFyvY1rg==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
hasBin: true
|
||||
|
||||
vite-node@3.0.4:
|
||||
resolution: {integrity: sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==}
|
||||
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
|
||||
@@ -15086,6 +15227,11 @@ packages:
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
which@3.0.1:
|
||||
resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==}
|
||||
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
|
||||
hasBin: true
|
||||
|
||||
which@4.0.0:
|
||||
resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==}
|
||||
engines: {node: ^16.13.0 || >=18.0.0}
|
||||
@@ -15781,7 +15927,7 @@ snapshots:
|
||||
|
||||
'@babel/generator@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/types': 7.26.10
|
||||
'@jridgewell/gen-mapping': 0.3.8
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
jsesc: 2.5.2
|
||||
@@ -15829,16 +15975,16 @@ snapshots:
|
||||
|
||||
'@babel/helper-environment-visitor@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/helper-function-name@7.24.7':
|
||||
dependencies:
|
||||
'@babel/template': 7.24.7
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/template': 7.27.0
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/helper-hoist-variables@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/helper-member-expression-to-functions@7.25.9(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
@@ -15868,7 +16014,7 @@ snapshots:
|
||||
'@babel/helper-module-imports': 7.24.7(supports-color@8.1.1)
|
||||
'@babel/helper-simple-access': 7.24.7(supports-color@8.1.1)
|
||||
'@babel/helper-split-export-declaration': 7.24.7
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -15912,7 +16058,7 @@ snapshots:
|
||||
|
||||
'@babel/helper-split-export-declaration@7.24.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/helper-string-parser@7.24.7': {}
|
||||
|
||||
@@ -15928,8 +16074,8 @@ snapshots:
|
||||
|
||||
'@babel/helpers@7.24.7':
|
||||
dependencies:
|
||||
'@babel/template': 7.24.7
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/template': 7.27.0
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/helpers@7.26.10':
|
||||
dependencies:
|
||||
@@ -15938,7 +16084,7 @@ snapshots:
|
||||
|
||||
'@babel/highlight@7.24.7':
|
||||
dependencies:
|
||||
'@babel/helper-validator-identifier': 7.24.7
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.1.1
|
||||
@@ -15951,10 +16097,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/parser@7.26.7':
|
||||
dependencies:
|
||||
'@babel/types': 7.27.0
|
||||
|
||||
'@babel/parser@7.27.0':
|
||||
dependencies:
|
||||
'@babel/types': 7.27.0
|
||||
@@ -15983,6 +16125,11 @@ snapshots:
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
optional: true
|
||||
|
||||
'@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
|
||||
'@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10(supports-color@8.1.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
@@ -16059,6 +16206,14 @@ snapshots:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
|
||||
'@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
'@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10(supports-color@8.1.1))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
@@ -16080,15 +16235,26 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/preset-typescript@7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
'@babel/helper-validator-option': 7.25.9
|
||||
'@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10(supports-color@8.1.1))
|
||||
'@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
'@babel/plugin-transform-typescript': 7.27.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@babel/runtime@7.24.7':
|
||||
dependencies:
|
||||
regenerator-runtime: 0.14.0
|
||||
|
||||
'@babel/template@7.24.7':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.24.7
|
||||
'@babel/parser': 7.26.7
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/code-frame': 7.26.2
|
||||
'@babel/parser': 7.26.10
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@babel/template@7.27.0':
|
||||
dependencies:
|
||||
@@ -16134,11 +16300,6 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@babel/types@7.26.7':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@babel/types@7.27.0':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
@@ -17401,6 +17562,8 @@ snapshots:
|
||||
lodash: 4.17.21
|
||||
postcss: 8.5.3
|
||||
|
||||
'@mjackson/node-fetch-server@0.2.0': {}
|
||||
|
||||
'@monaco-editor/loader@1.4.0(monaco-editor@0.33.0)':
|
||||
dependencies:
|
||||
monaco-editor: 0.33.0
|
||||
@@ -17559,11 +17722,40 @@ snapshots:
|
||||
dependencies:
|
||||
semver: 7.7.1
|
||||
|
||||
'@npmcli/git@4.1.0':
|
||||
dependencies:
|
||||
'@npmcli/promise-spawn': 6.0.2
|
||||
lru-cache: 7.18.3
|
||||
npm-pick-manifest: 8.0.2
|
||||
proc-log: 3.0.0
|
||||
promise-inflight: 1.0.1
|
||||
promise-retry: 2.0.1
|
||||
semver: 7.7.1
|
||||
which: 3.0.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
|
||||
'@npmcli/move-file@1.1.2':
|
||||
dependencies:
|
||||
mkdirp: 1.0.4
|
||||
rimraf: 3.0.2
|
||||
|
||||
'@npmcli/package-json@4.0.1':
|
||||
dependencies:
|
||||
'@npmcli/git': 4.1.0
|
||||
glob: 10.4.5
|
||||
hosted-git-info: 6.1.3
|
||||
json-parse-even-better-errors: 3.0.2
|
||||
normalize-package-data: 5.0.0
|
||||
proc-log: 3.0.0
|
||||
semver: 7.7.1
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
|
||||
'@npmcli/promise-spawn@6.0.2':
|
||||
dependencies:
|
||||
which: 3.0.1
|
||||
|
||||
'@number-flow/react@0.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
number-flow: 0.3.7
|
||||
@@ -18068,7 +18260,7 @@ snapshots:
|
||||
'@opentelemetry/propagator-b3': 1.24.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/propagator-jaeger': 1.24.1(@opentelemetry/api@1.9.0)
|
||||
'@opentelemetry/sdk-trace-base': 1.24.1(@opentelemetry/api@1.9.0)
|
||||
semver: 7.6.3
|
||||
semver: 7.7.1
|
||||
|
||||
'@opentelemetry/semantic-conventions@1.24.1': {}
|
||||
|
||||
@@ -19355,6 +19547,72 @@ snapshots:
|
||||
|
||||
'@react-dnd/shallowequal@4.0.2': {}
|
||||
|
||||
'@react-router/dev@7.4.0(@types/node@22.13.10)(jiti@2.4.2)(react-router@7.4.0(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))(yaml@2.4.5)':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.10(supports-color@8.1.1)
|
||||
'@babel/generator': 7.27.0
|
||||
'@babel/parser': 7.26.10
|
||||
'@babel/plugin-syntax-decorators': 7.25.9(@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))
|
||||
'@babel/preset-typescript': 7.26.0(@babel/core@7.26.10(supports-color@8.1.1))(supports-color@8.1.1)
|
||||
'@babel/traverse': 7.27.0(supports-color@8.1.1)
|
||||
'@babel/types': 7.26.10
|
||||
'@npmcli/package-json': 4.0.1
|
||||
'@react-router/node': 7.4.0(react-router@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.5.2)
|
||||
arg: 5.0.2
|
||||
babel-dead-code-elimination: 1.0.9(supports-color@8.1.1)
|
||||
chokidar: 4.0.3
|
||||
dedent: 1.5.3
|
||||
es-module-lexer: 1.6.0
|
||||
exit-hook: 2.2.1
|
||||
fs-extra: 10.1.0
|
||||
jsesc: 3.0.2
|
||||
lodash: 4.17.21
|
||||
pathe: 1.1.2
|
||||
picocolors: 1.1.1
|
||||
prettier: 2.8.8
|
||||
react-refresh: 0.14.2
|
||||
react-router: 7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
semver: 7.7.1
|
||||
set-cookie-parser: 2.7.1
|
||||
valibot: 0.41.0(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)
|
||||
vite-node: 3.0.0-beta.2(@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)
|
||||
optionalDependencies:
|
||||
typescript: 5.5.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
- bluebird
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
'@react-router/fs-routes@7.4.0(@react-router/dev@7.4.0(@types/node@22.13.10)(jiti@2.4.2)(react-router@7.4.0(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))(yaml@2.4.5))(typescript@5.5.2)':
|
||||
dependencies:
|
||||
'@react-router/dev': 7.4.0(@types/node@22.13.10)(jiti@2.4.2)(react-router@7.4.0(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))(yaml@2.4.5)
|
||||
minimatch: 9.0.5
|
||||
optionalDependencies:
|
||||
typescript: 5.5.2
|
||||
|
||||
'@react-router/node@7.4.0(react-router@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(typescript@5.5.2)':
|
||||
dependencies:
|
||||
'@mjackson/node-fetch-server': 0.2.0
|
||||
react-router: 7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
source-map-support: 0.5.21
|
||||
stream-slice: 0.1.2
|
||||
undici: 6.21.2
|
||||
optionalDependencies:
|
||||
typescript: 5.5.2
|
||||
|
||||
'@reactflow/background@11.3.6(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@reactflow/core': 11.10.1(@types/react@18.3.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -21102,12 +21360,12 @@ snapshots:
|
||||
|
||||
'@types/babel__generator@7.6.5':
|
||||
dependencies:
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@types/babel__template@7.4.2':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.7
|
||||
'@babel/types': 7.26.7
|
||||
'@babel/parser': 7.26.10
|
||||
'@babel/types': 7.26.10
|
||||
|
||||
'@types/babel__template@7.4.4':
|
||||
dependencies:
|
||||
@@ -24223,6 +24481,8 @@ snapshots:
|
||||
signal-exit: 4.1.0
|
||||
strip-final-newline: 3.0.0
|
||||
|
||||
exit-hook@2.2.1: {}
|
||||
|
||||
exit@0.1.2:
|
||||
optional: true
|
||||
|
||||
@@ -25205,6 +25465,10 @@ snapshots:
|
||||
|
||||
hosted-git-info@2.8.9: {}
|
||||
|
||||
hosted-git-info@6.1.3:
|
||||
dependencies:
|
||||
lru-cache: 7.18.3
|
||||
|
||||
html-encoding-sniffer@3.0.0:
|
||||
dependencies:
|
||||
whatwg-encoding: 2.0.0
|
||||
@@ -26212,6 +26476,8 @@ snapshots:
|
||||
|
||||
jsesc@2.5.2: {}
|
||||
|
||||
jsesc@3.0.2: {}
|
||||
|
||||
jsesc@3.1.0: {}
|
||||
|
||||
json-buffer@3.0.1: {}
|
||||
@@ -26222,6 +26488,8 @@ snapshots:
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-parse-even-better-errors@3.0.2: {}
|
||||
|
||||
json-pointer@0.6.2:
|
||||
dependencies:
|
||||
foreach: 2.0.6
|
||||
@@ -26518,6 +26786,8 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
|
||||
lru-cache@7.18.3: {}
|
||||
|
||||
lucide-react@0.436.0(react@18.2.0):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
@@ -28219,12 +28489,37 @@ snapshots:
|
||||
semver: 5.7.2
|
||||
validate-npm-package-license: 3.0.4
|
||||
|
||||
normalize-package-data@5.0.0:
|
||||
dependencies:
|
||||
hosted-git-info: 6.1.3
|
||||
is-core-module: 2.16.1
|
||||
semver: 7.7.1
|
||||
validate-npm-package-license: 3.0.4
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
|
||||
normalize-range@0.1.2: {}
|
||||
|
||||
npm-install-checks@6.3.0:
|
||||
dependencies:
|
||||
semver: 7.7.1
|
||||
|
||||
npm-normalize-package-bin@3.0.1: {}
|
||||
|
||||
npm-package-arg@10.1.0:
|
||||
dependencies:
|
||||
hosted-git-info: 6.1.3
|
||||
proc-log: 3.0.0
|
||||
semver: 7.7.1
|
||||
validate-npm-package-name: 5.0.1
|
||||
|
||||
npm-pick-manifest@8.0.2:
|
||||
dependencies:
|
||||
npm-install-checks: 6.3.0
|
||||
npm-normalize-package-bin: 3.0.1
|
||||
npm-package-arg: 10.1.0
|
||||
semver: 7.7.1
|
||||
|
||||
npm-run-all@4.1.5:
|
||||
dependencies:
|
||||
ansi-styles: 3.2.1
|
||||
@@ -28273,12 +28568,13 @@ snapshots:
|
||||
mitt: 3.0.1
|
||||
next: 14.2.26(@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)
|
||||
|
||||
nuqs@2.4.1(next@14.2.26(@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@18.2.0):
|
||||
nuqs@2.4.1(next@14.2.26(@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-router@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
mitt: 3.0.1
|
||||
react: 18.2.0
|
||||
optionalDependencies:
|
||||
next: 14.2.26(@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-router: 7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
|
||||
nwsapi@2.2.18:
|
||||
optional: true
|
||||
@@ -29431,6 +29727,16 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- react-dom
|
||||
|
||||
react-router@7.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
'@types/cookie': 0.6.0
|
||||
cookie: 1.0.2
|
||||
react: 18.2.0
|
||||
set-cookie-parser: 2.7.1
|
||||
turbo-stream: 2.4.0
|
||||
optionalDependencies:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
react-simple-maps@4.0.0-beta.6(prop-types@15.8.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
||||
dependencies:
|
||||
d3-geo: 3.1.0
|
||||
@@ -29980,7 +30286,7 @@ snapshots:
|
||||
|
||||
resolve@2.0.0-next.5:
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
is-core-module: 2.16.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
@@ -30592,6 +30898,8 @@ snapshots:
|
||||
dependencies:
|
||||
internal-slot: 1.0.7
|
||||
|
||||
stream-slice@0.1.2: {}
|
||||
|
||||
streamsearch@1.1.0: {}
|
||||
|
||||
streamx@2.22.0:
|
||||
@@ -31251,6 +31559,8 @@ snapshots:
|
||||
turbo-linux-arm64@2.3.3:
|
||||
optional: true
|
||||
|
||||
turbo-stream@2.4.0: {}
|
||||
|
||||
turbo-windows-64@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -31716,11 +32026,17 @@ snapshots:
|
||||
convert-source-map: 2.0.0
|
||||
optional: true
|
||||
|
||||
valibot@0.41.0(typescript@5.5.2):
|
||||
optionalDependencies:
|
||||
typescript: 5.5.2
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
dependencies:
|
||||
spdx-correct: 3.2.0
|
||||
spdx-expression-parse: 3.0.1
|
||||
|
||||
validate-npm-package-name@5.0.1: {}
|
||||
|
||||
valtio@1.12.0(@types/react@18.3.3)(react@18.2.0):
|
||||
dependencies:
|
||||
derive-valtio: 0.1.0(valtio@1.12.0(@types/react@18.3.3)(react@18.2.0))
|
||||
@@ -31888,6 +32204,27 @@ snapshots:
|
||||
- xml2js
|
||||
- yaml
|
||||
|
||||
vite-node@3.0.0-beta.2(@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):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
es-module-lexer: 1.6.0
|
||||
pathe: 1.1.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)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
- less
|
||||
- lightningcss
|
||||
- sass
|
||||
- sass-embedded
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
- terser
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
vite-node@3.0.4(@types/node@20.12.11)(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):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
@@ -32301,6 +32638,10 @@ snapshots:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
which@3.0.1:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
which@4.0.0:
|
||||
dependencies:
|
||||
isexe: 3.1.1
|
||||
|
||||
Reference in New Issue
Block a user