From f3b67a10d3fa3db405773698182fb99ea1409595 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 27 Mar 2025 19:18:28 -0230 Subject: [PATCH] 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 --- apps/ui-library/__registry__/index.tsx | 22 +- apps/ui-library/config/docs.ts | 2 +- .../docs/react-router/password-based-auth.mdx | 75 ++++ apps/ui-library/package.json | 3 + .../r/current-user-avatar-react-router.json | 11 +- .../public/r/dropzone-react-router.json | 11 +- .../public/r/password-based-auth-nextjs.json | 3 +- .../r/password-based-auth-react-router.json | 85 ++++ .../r/realtime-avatar-stack-react-router.json | 11 +- .../r/realtime-cursor-react-router.json | 11 +- .../r/supabase-client-react-router.json | 11 +- apps/ui-library/registry.json | 105 ++++- apps/ui-library/registry/blocks.ts | 3 + .../registry-item.json | 2 +- .../app/routes.ts | 4 + .../app/routes/auth.confirm.tsx | 26 ++ .../app/routes/auth.error.tsx | 29 ++ .../app/routes/forgot-password.tsx | 111 +++++ .../app/routes/login.tsx | 95 ++++ .../app/routes/logout.tsx | 16 + .../app/routes/protected.tsx | 29 ++ .../app/routes/sign-up.tsx | 126 ++++++ .../app/routes/update-password.tsx | 75 ++++ .../registry-item.json | 59 +++ .../react-router/lib/supabase.client.ts | 7 - .../react-router/lib/supabase.server.ts | 23 - .../react-router/lib/supabase/client.ts | 9 + .../react-router/lib/supabase/server.ts | 27 ++ .../clients/react-router/registry-item.json | 6 +- apps/ui-library/supabase/config.toml | 6 +- .../supabase/templates/recovery.html | 8 + pnpm-lock.yaml | 417 ++++++++++++++++-- 32 files changed, 1307 insertions(+), 121 deletions(-) create mode 100644 apps/ui-library/content/docs/react-router/password-based-auth.mdx create mode 100644 apps/ui-library/public/r/password-based-auth-react-router.json create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes.ts create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx create mode 100644 apps/ui-library/registry/default/blocks/password-based-auth-react-router/registry-item.json delete mode 100644 apps/ui-library/registry/default/clients/react-router/lib/supabase.client.ts delete mode 100644 apps/ui-library/registry/default/clients/react-router/lib/supabase.server.ts create mode 100644 apps/ui-library/registry/default/clients/react-router/lib/supabase/client.ts create mode 100644 apps/ui-library/registry/default/clients/react-router/lib/supabase/server.ts create mode 100644 apps/ui-library/supabase/templates/recovery.html diff --git a/apps/ui-library/__registry__/index.tsx b/apps/ui-library/__registry__/index.tsx index ea26a79d8b..d8d087761b 100644 --- a/apps/ui-library/__registry__/index.tsx +++ b/apps/ui-library/__registry__/index.tsx @@ -31,6 +31,18 @@ export const Index: Record = { 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 = { 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 = { 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 = { 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 = { 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 = { 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: [] diff --git a/apps/ui-library/config/docs.ts b/apps/ui-library/config/docs.ts index bc860467f5..b11b830daf 100644 --- a/apps/ui-library/config/docs.ts +++ b/apps/ui-library/config/docs.ts @@ -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', }, diff --git a/apps/ui-library/content/docs/react-router/password-based-auth.mdx b/apps/ui-library/content/docs/react-router/password-based-auth.mdx new file mode 100644 index 0000000000..75b7044018 --- /dev/null +++ b/apps/ui-library/content/docs/react-router/password-based-auth.mdx @@ -0,0 +1,75 @@ +--- +title: Password-based Auth (Next.js) +description: Password-based Auth block for Next.js app +--- + + + +## Installation + + + +## Folder structure + +This block includes the [Supabase client](/ui/docs/react-router/client). When installing, you can skip overwriting it. + + + +## 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 +

Confirm your signup

+ +

Follow this link to confirm your user:

+

+ Confirm your email +

+ ``` + + 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 +

Reset Password

+ +

Follow this link to reset the password for your user:

+

+ Reset Password +

+ ``` + +### 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. diff --git a/apps/ui-library/package.json b/apps/ui-library/package.json index 5e542c01ce..6a0095403a 100644 --- a/apps/ui-library/package.json +++ b/apps/ui-library/package.json @@ -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", diff --git a/apps/ui-library/public/r/current-user-avatar-react-router.json b/apps/ui-library/public/r/current-user-avatar-react-router.json index a8ee5dbd74..b05d1eff09 100644 --- a/apps/ui-library/public/r/current-user-avatar-react-router.json +++ b/apps/ui-library/public/r/current-user-avatar-react-router.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/dropzone-react-router.json b/apps/ui-library/public/r/dropzone-react-router.json index 6b4e6dc66f..b88b0ea28d 100644 --- a/apps/ui-library/public/r/dropzone-react-router.json +++ b/apps/ui-library/public/r/dropzone-react-router.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/password-based-auth-nextjs.json b/apps/ui-library/public/r/password-based-auth-nextjs.json index f37f32d92e..895de13855 100644 --- a/apps/ui-library/public/r/password-based-auth-nextjs.json +++ b/apps/ui-library/public/r/password-based-auth-nextjs.json @@ -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", diff --git a/apps/ui-library/public/r/password-based-auth-react-router.json b/apps/ui-library/public/r/password-based-auth-react-router.json new file mode 100644 index 0000000000..254b3b3ed4 --- /dev/null +++ b/apps/ui-library/public/r/password-based-auth-react-router.json @@ -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
\n
\n
\n \n \n Sorry, something went wrong.\n \n \n {searchParams?.get('error') ? (\n

\n Code error: {searchParams?.get('error')}\n

\n ) : (\n

An unspecified error occurred.

\n )}\n
\n
\n
\n
\n
\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()\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
\n
\n
\n {success ? (\n \n \n Check Your Email\n Password reset instructions sent\n \n \n

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

\n
\n
\n ) : (\n \n \n Reset Your Password\n \n Type in your email and we'll send you a link to reset your password\n \n \n \n \n
\n
\n \n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\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()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Login\n Enter your email below to login to your account\n \n \n \n
\n
\n \n \n
\n
\n
\n \n \n Forgot your password?\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Don't have an account?{' '}\n \n Sign up\n \n
\n
\n
\n
\n
\n
\n
\n )\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()\n\n return (\n
\n

\n Hello {data.user.email}\n

\n \n \n \n
\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()\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
\n
\n
\n {success ? (\n \n \n Thank you for signing up!\n Check your email to confirm\n \n \n

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

\n
\n
\n ) : (\n \n \n Sign up\n Create a new account\n \n \n \n
\n
\n \n \n
\n
\n
\n \n
\n \n
\n
\n
\n \n
\n \n
\n {error &&

{error}

}\n \n
\n
\n Already have an account?{' '}\n \n Login\n \n
\n
\n
\n
\n )}\n
\n
\n
\n )\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()\n\n const error = fetcher.data?.error\n const loading = fetcher.state === 'submitting'\n\n return (\n
\n
\n
\n \n \n Reset Your Password\n Please enter your new password below.\n \n \n \n
\n
\n \n \n
\n {error &&

{error}

}\n \n
\n
\n
\n
\n
\n
\n
\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": "/// \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" + } + ] +} \ No newline at end of file diff --git a/apps/ui-library/public/r/realtime-avatar-stack-react-router.json b/apps/ui-library/public/r/realtime-avatar-stack-react-router.json index 8fc5a2f778..b4d74875ce 100644 --- a/apps/ui-library/public/r/realtime-avatar-stack-react-router.json +++ b/apps/ui-library/public/r/realtime-avatar-stack-react-router.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/realtime-cursor-react-router.json b/apps/ui-library/public/r/realtime-cursor-react-router.json index db72aa9261..c29e36acc7 100644 --- a/apps/ui-library/public/r/realtime-cursor-react-router.json +++ b/apps/ui-library/public/r/realtime-cursor-react-router.json @@ -6,7 +6,8 @@ "description": "Component which renders realtime cursors from other users in a room.", "dependencies": [ "lucide-react", - "@supabase/ssr@latest" + "@supabase/ssr@latest", + "@supabase/supabase-js@latest" ], "registryDependencies": [], "files": [ @@ -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": "/// \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" } ] diff --git a/apps/ui-library/public/r/supabase-client-react-router.json b/apps/ui-library/public/r/supabase-client-react-router.json index ccea332671..11764c21df 100644 --- a/apps/ui-library/public/r/supabase-client-react-router.json +++ b/apps/ui-library/public/r/supabase-client-react-router.json @@ -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": "/// \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" } ] diff --git a/apps/ui-library/registry.json b/apps/ui-library/registry.json index 772924348a..5c885b18d3 100644 --- a/apps/ui-library/registry.json +++ b/apps/ui-library/registry.json @@ -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" } ] diff --git a/apps/ui-library/registry/blocks.ts b/apps/ui-library/registry/blocks.ts index f3ee03532f..2b010e1f3d 100644 --- a/apps/ui-library/registry/blocks.ts +++ b/apps/ui-library/registry/blocks.ts @@ -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), diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json index 0ac33ee531..2c897251a8 100644 --- a/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json +++ b/apps/ui-library/registry/default/blocks/password-based-auth-nextjs/registry-item.json @@ -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", diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes.ts b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes.ts new file mode 100644 index 0000000000..83c333ffc8 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes.ts @@ -0,0 +1,4 @@ +import { type RouteConfig } from '@react-router/dev/routes' +import { flatRoutes } from '@react-router/fs-routes' + +export default flatRoutes() satisfies RouteConfig diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx new file mode 100644 index 0000000000..41425222c4 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.confirm.tsx @@ -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`) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx new file mode 100644 index 0000000000..59fc585be3 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/auth.error.tsx @@ -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 ( +
+
+
+ + + Sorry, something went wrong. + + + {searchParams?.get('error') ? ( +

+ Code error: {searchParams?.get('error')} +

+ ) : ( +

An unspecified error occurred.

+ )} +
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx new file mode 100644 index 0000000000..a5ab701c18 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/forgot-password.tsx @@ -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() + let [searchParams] = useSearchParams() + + const success = !!searchParams.has('success') + const error = fetcher.data?.error + const loading = fetcher.state === 'submitting' + + return ( +
+
+
+ {success ? ( + + + Check Your Email + Password reset instructions sent + + +

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

+
+
+ ) : ( + + + Reset Your Password + + Type in your email and we'll send you a link to reset your password + + + + +
+
+ + +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+ )} +
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx new file mode 100644 index 0000000000..0f7b1d2bcb --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/login.tsx @@ -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() + + const error = fetcher.data?.error + const loading = fetcher.state === 'submitting' + + return ( +
+
+
+ + + Login + Enter your email below to login to your account + + + +
+
+ + +
+
+
+ + + Forgot your password? + +
+ +
+ {error &&

{error}

} + +
+
+ Don't have an account?{' '} + + Sign up + +
+
+
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx new file mode 100644 index 0000000000..0c22dbfb3b --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/logout.tsx @@ -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 }) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx new file mode 100644 index 0000000000..f3d7879b67 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/protected.tsx @@ -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() + + return ( +
+

+ Hello {data.user.email} +

+ + + +
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx new file mode 100644 index 0000000000..dea6dd6221 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/sign-up.tsx @@ -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() + let [searchParams] = useSearchParams() + + const success = !!searchParams.has('success') + const error = fetcher.data?.error + const loading = fetcher.state === 'submitting' + + return ( +
+
+
+ {success ? ( + + + Thank you for signing up! + Check your email to confirm + + +

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

+
+
+ ) : ( + + + Sign up + Create a new account + + + +
+
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+ {error &&

{error}

} + +
+
+ Already have an account?{' '} + + Login + +
+
+
+
+ )} +
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx new file mode 100644 index 0000000000..d0baa10b8c --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/app/routes/update-password.tsx @@ -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() + + const error = fetcher.data?.error + const loading = fetcher.state === 'submitting' + + return ( +
+
+
+ + + Reset Your Password + Please enter your new password below. + + + +
+
+ + +
+ {error &&

{error}

} + +
+
+
+
+
+
+
+ ) +} diff --git a/apps/ui-library/registry/default/blocks/password-based-auth-react-router/registry-item.json b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/registry-item.json new file mode 100644 index 0000000000..be88ea4890 --- /dev/null +++ b/apps/ui-library/registry/default/blocks/password-based-auth-react-router/registry-item.json @@ -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" + } + ] +} diff --git a/apps/ui-library/registry/default/clients/react-router/lib/supabase.client.ts b/apps/ui-library/registry/default/clients/react-router/lib/supabase.client.ts deleted file mode 100644 index 13b5e5307b..0000000000 --- a/apps/ui-library/registry/default/clients/react-router/lib/supabase.client.ts +++ /dev/null @@ -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) -} diff --git a/apps/ui-library/registry/default/clients/react-router/lib/supabase.server.ts b/apps/ui-library/registry/default/clients/react-router/lib/supabase.server.ts deleted file mode 100644 index dc67a0fdd4..0000000000 --- a/apps/ui-library/registry/default/clients/react-router/lib/supabase.server.ts +++ /dev/null @@ -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 } -} diff --git a/apps/ui-library/registry/default/clients/react-router/lib/supabase/client.ts b/apps/ui-library/registry/default/clients/react-router/lib/supabase/client.ts new file mode 100644 index 0000000000..3c171dbcac --- /dev/null +++ b/apps/ui-library/registry/default/clients/react-router/lib/supabase/client.ts @@ -0,0 +1,9 @@ +/// +import { createBrowserClient } from '@supabase/ssr' + +export function createClient() { + return createBrowserClient( + import.meta.env.VITE_SUPABASE_URL!, + import.meta.env.VITE_SUPABASE_ANON_KEY! + ) +} diff --git a/apps/ui-library/registry/default/clients/react-router/lib/supabase/server.ts b/apps/ui-library/registry/default/clients/react-router/lib/supabase/server.ts new file mode 100644 index 0000000000..daa676045b --- /dev/null +++ b/apps/ui-library/registry/default/clients/react-router/lib/supabase/server.ts @@ -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 } +} diff --git a/apps/ui-library/registry/default/clients/react-router/registry-item.json b/apps/ui-library/registry/default/clients/react-router/registry-item.json index 0481c22b5d..100d086463 100644 --- a/apps/ui-library/registry/default/clients/react-router/registry-item.json +++ b/apps/ui-library/registry/default/clients/react-router/registry-item.json @@ -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" } ] diff --git a/apps/ui-library/supabase/config.toml b/apps/ui-library/supabase/config.toml index 372fd92e8e..3722784111 100644 --- a/apps/ui-library/supabase/config.toml +++ b/apps/ui-library/supabase/config.toml @@ -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 diff --git a/apps/ui-library/supabase/templates/recovery.html b/apps/ui-library/supabase/templates/recovery.html new file mode 100644 index 0000000000..16f5609fa6 --- /dev/null +++ b/apps/ui-library/supabase/templates/recovery.html @@ -0,0 +1,8 @@ +

Reset Password

+

Follow this link to reset the password for your user:

+

+ Reset Password +

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2bc4a0243..a74a20b8cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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