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:
Terry Sutton
2025-03-27 19:18:28 -02:30
committed by GitHub
parent 641940e546
commit f3b67a10d3
32 changed files with 1307 additions and 121 deletions

View File

@@ -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: []

View File

@@ -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',
},

View File

@@ -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.

View File

@@ -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",

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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",

View File

@@ -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&apos;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&apos;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"
}
]
}

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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"
}
]

View File

@@ -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),

View File

@@ -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",

View File

@@ -0,0 +1,4 @@
import { type RouteConfig } from '@react-router/dev/routes'
import { flatRoutes } from '@react-router/fs-routes'
export default flatRoutes() satisfies RouteConfig

View File

@@ -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`)
}

View File

@@ -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>
)
}

View File

@@ -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&apos;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>
)
}

View File

@@ -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&apos;t have an account?{' '}
<Link to="/sign-up" className="underline underline-offset-4">
Sign up
</Link>
</div>
</fetcher.Form>
</CardContent>
</Card>
</div>
</div>
</div>
)
}

View File

@@ -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 })
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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>
)
}

View File

@@ -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"
}
]
}

View File

@@ -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)
}

View File

@@ -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 }
}

View File

@@ -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!
)
}

View File

@@ -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 }
}

View File

@@ -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"
}
]

View File

@@ -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

View 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
View File

@@ -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