From 3d931aceb7438d9b882e864f335ad7e89e2b0a04 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Mon, 29 Jun 2026 20:08:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(studio):=20TanStack=20app=20shell=20?= =?UTF-8?q?=E2=80=94=20root=20+=20auth/org=20routes=20(stack=203/6,=20from?= =?UTF-8?q?=20#46424)=20(#47112)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Stack 3/6** of the TanStack Start migration (#46424). Stacked on **#47110** (S2) → review that first; this PR's diff is the app shell. > [!NOTE] > Thin route wrappers that render the existing pages-router page components through the compat shims (S2). Next is untouched — it builds `pages/` and ignores `routes/`. The app doesn't function end-to-end on TanStack until the API + project routes land (S4/S5) and the flag is flipped. ## What's in this PR - `routes/__root.tsx` — root layout + a `beforeLoad` that runs the shared redirect rules; `router.tsx`. - `routes/_auth/*` — sign-in/up, forgot/reset password, SSO/MFA/partner sign-in, CLI login, Stripe-projects login. - `routes/_app/*` — account (me/security/audit/tokens), `org/$slug/*` (general/billing/team/usage/…), support. - `routeTree.gen.ts` — **regenerated** by the tanstackStart vite plugin for exactly the routes in this PR (the migration branch's tree references all ~300 routes, so it can't be copied verbatim here). It's a generated artifact; the meaningful review surface is the route files. ## Verification On top of S1+S2: `studio` typecheck ✓, lint (0 errors) ✓. ## Summary by CodeRabbit * **Refactor** * Enhanced internal routing infrastructure to improve application performance and code organization. These behind-the-scenes updates ensure a more stable and maintainable foundation for the platform without affecting existing functionality or user experience. --------- Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com> Co-authored-by: Ivan Vasilov --- apps/studio/routeTree.gen.ts | 850 ++++++++++++++++++ apps/studio/router.tsx | 97 ++ apps/studio/routes/__root.tsx | 395 ++++++++ apps/studio/routes/_app/account.tsx | 23 + apps/studio/routes/_app/account/audit.tsx | 15 + apps/studio/routes/_app/account/me.tsx | 16 + apps/studio/routes/_app/account/security.tsx | 15 + .../routes/_app/account/tokens/index.tsx | 15 + .../routes/_app/account/tokens/scoped.tsx | 15 + apps/studio/routes/_app/new/index.tsx | 24 + apps/studio/routes/_app/org.tsx | 23 + apps/studio/routes/_app/org/$slug/apps.tsx | 19 + .../_app/org/$slug/audit-log-drains.tsx | 19 + apps/studio/routes/_app/org/$slug/audit.tsx | 19 + apps/studio/routes/_app/org/$slug/billing.tsx | 14 + .../routes/_app/org/$slug/documents.tsx | 19 + apps/studio/routes/_app/org/$slug/general.tsx | 19 + apps/studio/routes/_app/org/$slug/index.tsx | 19 + .../routes/_app/org/$slug/integrations.tsx | 14 + .../_app/org/$slug/private-apps/index.tsx | 22 + .../studio/routes/_app/org/$slug/security.tsx | 19 + apps/studio/routes/_app/org/$slug/sso.tsx | 19 + apps/studio/routes/_app/org/$slug/team.tsx | 14 + apps/studio/routes/_app/org/$slug/usage.tsx | 14 + .../_app/org/$slug/webhooks/$endpointId.tsx | 19 + .../routes/_app/org/$slug/webhooks/index.tsx | 19 + apps/studio/routes/_app/org/index.tsx | 14 + apps/studio/routes/_app/organizations.tsx | 21 + apps/studio/routes/_app/support/link.tsx | 11 + apps/studio/routes/_app/support/new.tsx | 14 + apps/studio/routes/_auth/cli/login.tsx | 12 + .../routes/_auth/forgot-password-mfa.tsx | 12 + apps/studio/routes/_auth/forgot-password.tsx | 19 + .../_auth/partners/stripe/projects/login.tsx | 12 + apps/studio/routes/_auth/reset-password.tsx | 20 + apps/studio/routes/_auth/sign-in-mfa.tsx | 12 + apps/studio/routes/_auth/sign-in-partner.tsx | 25 + apps/studio/routes/_auth/sign-in-sso.tsx | 20 + apps/studio/routes/_auth/sign-in.tsx | 21 + apps/studio/routes/_auth/sign-up.tsx | 16 + 40 files changed, 1985 insertions(+) create mode 100644 apps/studio/routeTree.gen.ts create mode 100644 apps/studio/router.tsx create mode 100644 apps/studio/routes/__root.tsx create mode 100644 apps/studio/routes/_app/account.tsx create mode 100644 apps/studio/routes/_app/account/audit.tsx create mode 100644 apps/studio/routes/_app/account/me.tsx create mode 100644 apps/studio/routes/_app/account/security.tsx create mode 100644 apps/studio/routes/_app/account/tokens/index.tsx create mode 100644 apps/studio/routes/_app/account/tokens/scoped.tsx create mode 100644 apps/studio/routes/_app/new/index.tsx create mode 100644 apps/studio/routes/_app/org.tsx create mode 100644 apps/studio/routes/_app/org/$slug/apps.tsx create mode 100644 apps/studio/routes/_app/org/$slug/audit-log-drains.tsx create mode 100644 apps/studio/routes/_app/org/$slug/audit.tsx create mode 100644 apps/studio/routes/_app/org/$slug/billing.tsx create mode 100644 apps/studio/routes/_app/org/$slug/documents.tsx create mode 100644 apps/studio/routes/_app/org/$slug/general.tsx create mode 100644 apps/studio/routes/_app/org/$slug/index.tsx create mode 100644 apps/studio/routes/_app/org/$slug/integrations.tsx create mode 100644 apps/studio/routes/_app/org/$slug/private-apps/index.tsx create mode 100644 apps/studio/routes/_app/org/$slug/security.tsx create mode 100644 apps/studio/routes/_app/org/$slug/sso.tsx create mode 100644 apps/studio/routes/_app/org/$slug/team.tsx create mode 100644 apps/studio/routes/_app/org/$slug/usage.tsx create mode 100644 apps/studio/routes/_app/org/$slug/webhooks/$endpointId.tsx create mode 100644 apps/studio/routes/_app/org/$slug/webhooks/index.tsx create mode 100644 apps/studio/routes/_app/org/index.tsx create mode 100644 apps/studio/routes/_app/organizations.tsx create mode 100644 apps/studio/routes/_app/support/link.tsx create mode 100644 apps/studio/routes/_app/support/new.tsx create mode 100644 apps/studio/routes/_auth/cli/login.tsx create mode 100644 apps/studio/routes/_auth/forgot-password-mfa.tsx create mode 100644 apps/studio/routes/_auth/forgot-password.tsx create mode 100644 apps/studio/routes/_auth/partners/stripe/projects/login.tsx create mode 100644 apps/studio/routes/_auth/reset-password.tsx create mode 100644 apps/studio/routes/_auth/sign-in-mfa.tsx create mode 100644 apps/studio/routes/_auth/sign-in-partner.tsx create mode 100644 apps/studio/routes/_auth/sign-in-sso.tsx create mode 100644 apps/studio/routes/_auth/sign-in.tsx create mode 100644 apps/studio/routes/_auth/sign-up.tsx diff --git a/apps/studio/routeTree.gen.ts b/apps/studio/routeTree.gen.ts new file mode 100644 index 00000000000..1ac39c28ab0 --- /dev/null +++ b/apps/studio/routeTree.gen.ts @@ -0,0 +1,850 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as AuthSignUpRouteImport } from './routes/_auth/sign-up' +import { Route as AuthSignInSsoRouteImport } from './routes/_auth/sign-in-sso' +import { Route as AuthSignInPartnerRouteImport } from './routes/_auth/sign-in-partner' +import { Route as AuthSignInMfaRouteImport } from './routes/_auth/sign-in-mfa' +import { Route as AuthSignInRouteImport } from './routes/_auth/sign-in' +import { Route as AuthResetPasswordRouteImport } from './routes/_auth/reset-password' +import { Route as AuthForgotPasswordMfaRouteImport } from './routes/_auth/forgot-password-mfa' +import { Route as AuthForgotPasswordRouteImport } from './routes/_auth/forgot-password' +import { Route as AppOrganizationsRouteImport } from './routes/_app/organizations' +import { Route as AppOrgRouteImport } from './routes/_app/org' +import { Route as AppAccountRouteImport } from './routes/_app/account' +import { Route as AppOrgIndexRouteImport } from './routes/_app/org/index' +import { Route as AppNewIndexRouteImport } from './routes/_app/new/index' +import { Route as AuthCliLoginRouteImport } from './routes/_auth/cli/login' +import { Route as AppSupportNewRouteImport } from './routes/_app/support/new' +import { Route as AppSupportLinkRouteImport } from './routes/_app/support/link' +import { Route as AppAccountSecurityRouteImport } from './routes/_app/account/security' +import { Route as AppAccountMeRouteImport } from './routes/_app/account/me' +import { Route as AppAccountAuditRouteImport } from './routes/_app/account/audit' +import { Route as AppOrgSlugIndexRouteImport } from './routes/_app/org/$slug/index' +import { Route as AppAccountTokensIndexRouteImport } from './routes/_app/account/tokens/index' +import { Route as AppOrgSlugUsageRouteImport } from './routes/_app/org/$slug/usage' +import { Route as AppOrgSlugTeamRouteImport } from './routes/_app/org/$slug/team' +import { Route as AppOrgSlugSsoRouteImport } from './routes/_app/org/$slug/sso' +import { Route as AppOrgSlugSecurityRouteImport } from './routes/_app/org/$slug/security' +import { Route as AppOrgSlugIntegrationsRouteImport } from './routes/_app/org/$slug/integrations' +import { Route as AppOrgSlugGeneralRouteImport } from './routes/_app/org/$slug/general' +import { Route as AppOrgSlugDocumentsRouteImport } from './routes/_app/org/$slug/documents' +import { Route as AppOrgSlugBillingRouteImport } from './routes/_app/org/$slug/billing' +import { Route as AppOrgSlugAuditLogDrainsRouteImport } from './routes/_app/org/$slug/audit-log-drains' +import { Route as AppOrgSlugAuditRouteImport } from './routes/_app/org/$slug/audit' +import { Route as AppOrgSlugAppsRouteImport } from './routes/_app/org/$slug/apps' +import { Route as AppAccountTokensScopedRouteImport } from './routes/_app/account/tokens/scoped' +import { Route as AppOrgSlugWebhooksIndexRouteImport } from './routes/_app/org/$slug/webhooks/index' +import { Route as AppOrgSlugPrivateAppsIndexRouteImport } from './routes/_app/org/$slug/private-apps/index' +import { Route as AuthPartnersStripeProjectsLoginRouteImport } from './routes/_auth/partners/stripe/projects/login' +import { Route as AppOrgSlugWebhooksEndpointIdRouteImport } from './routes/_app/org/$slug/webhooks/$endpointId' + +const AuthSignUpRoute = AuthSignUpRouteImport.update({ + id: '/_auth/sign-up', + path: '/sign-up', + getParentRoute: () => rootRouteImport, +} as any) +const AuthSignInSsoRoute = AuthSignInSsoRouteImport.update({ + id: '/_auth/sign-in-sso', + path: '/sign-in-sso', + getParentRoute: () => rootRouteImport, +} as any) +const AuthSignInPartnerRoute = AuthSignInPartnerRouteImport.update({ + id: '/_auth/sign-in-partner', + path: '/sign-in-partner', + getParentRoute: () => rootRouteImport, +} as any) +const AuthSignInMfaRoute = AuthSignInMfaRouteImport.update({ + id: '/_auth/sign-in-mfa', + path: '/sign-in-mfa', + getParentRoute: () => rootRouteImport, +} as any) +const AuthSignInRoute = AuthSignInRouteImport.update({ + id: '/_auth/sign-in', + path: '/sign-in', + getParentRoute: () => rootRouteImport, +} as any) +const AuthResetPasswordRoute = AuthResetPasswordRouteImport.update({ + id: '/_auth/reset-password', + path: '/reset-password', + getParentRoute: () => rootRouteImport, +} as any) +const AuthForgotPasswordMfaRoute = AuthForgotPasswordMfaRouteImport.update({ + id: '/_auth/forgot-password-mfa', + path: '/forgot-password-mfa', + getParentRoute: () => rootRouteImport, +} as any) +const AuthForgotPasswordRoute = AuthForgotPasswordRouteImport.update({ + id: '/_auth/forgot-password', + path: '/forgot-password', + getParentRoute: () => rootRouteImport, +} as any) +const AppOrganizationsRoute = AppOrganizationsRouteImport.update({ + id: '/_app/organizations', + path: '/organizations', + getParentRoute: () => rootRouteImport, +} as any) +const AppOrgRoute = AppOrgRouteImport.update({ + id: '/_app/org', + path: '/org', + getParentRoute: () => rootRouteImport, +} as any) +const AppAccountRoute = AppAccountRouteImport.update({ + id: '/_app/account', + path: '/account', + getParentRoute: () => rootRouteImport, +} as any) +const AppOrgIndexRoute = AppOrgIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => AppOrgRoute, +} as any) +const AppNewIndexRoute = AppNewIndexRouteImport.update({ + id: '/_app/new/', + path: '/new/', + getParentRoute: () => rootRouteImport, +} as any) +const AuthCliLoginRoute = AuthCliLoginRouteImport.update({ + id: '/_auth/cli/login', + path: '/cli/login', + getParentRoute: () => rootRouteImport, +} as any) +const AppSupportNewRoute = AppSupportNewRouteImport.update({ + id: '/_app/support/new', + path: '/support/new', + getParentRoute: () => rootRouteImport, +} as any) +const AppSupportLinkRoute = AppSupportLinkRouteImport.update({ + id: '/_app/support/link', + path: '/support/link', + getParentRoute: () => rootRouteImport, +} as any) +const AppAccountSecurityRoute = AppAccountSecurityRouteImport.update({ + id: '/security', + path: '/security', + getParentRoute: () => AppAccountRoute, +} as any) +const AppAccountMeRoute = AppAccountMeRouteImport.update({ + id: '/me', + path: '/me', + getParentRoute: () => AppAccountRoute, +} as any) +const AppAccountAuditRoute = AppAccountAuditRouteImport.update({ + id: '/audit', + path: '/audit', + getParentRoute: () => AppAccountRoute, +} as any) +const AppOrgSlugIndexRoute = AppOrgSlugIndexRouteImport.update({ + id: '/$slug/', + path: '/$slug/', + getParentRoute: () => AppOrgRoute, +} as any) +const AppAccountTokensIndexRoute = AppAccountTokensIndexRouteImport.update({ + id: '/tokens/', + path: '/tokens/', + getParentRoute: () => AppAccountRoute, +} as any) +const AppOrgSlugUsageRoute = AppOrgSlugUsageRouteImport.update({ + id: '/$slug/usage', + path: '/$slug/usage', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugTeamRoute = AppOrgSlugTeamRouteImport.update({ + id: '/$slug/team', + path: '/$slug/team', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugSsoRoute = AppOrgSlugSsoRouteImport.update({ + id: '/$slug/sso', + path: '/$slug/sso', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugSecurityRoute = AppOrgSlugSecurityRouteImport.update({ + id: '/$slug/security', + path: '/$slug/security', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugIntegrationsRoute = AppOrgSlugIntegrationsRouteImport.update({ + id: '/$slug/integrations', + path: '/$slug/integrations', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugGeneralRoute = AppOrgSlugGeneralRouteImport.update({ + id: '/$slug/general', + path: '/$slug/general', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugDocumentsRoute = AppOrgSlugDocumentsRouteImport.update({ + id: '/$slug/documents', + path: '/$slug/documents', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugBillingRoute = AppOrgSlugBillingRouteImport.update({ + id: '/$slug/billing', + path: '/$slug/billing', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugAuditLogDrainsRoute = + AppOrgSlugAuditLogDrainsRouteImport.update({ + id: '/$slug/audit-log-drains', + path: '/$slug/audit-log-drains', + getParentRoute: () => AppOrgRoute, + } as any) +const AppOrgSlugAuditRoute = AppOrgSlugAuditRouteImport.update({ + id: '/$slug/audit', + path: '/$slug/audit', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugAppsRoute = AppOrgSlugAppsRouteImport.update({ + id: '/$slug/apps', + path: '/$slug/apps', + getParentRoute: () => AppOrgRoute, +} as any) +const AppAccountTokensScopedRoute = AppAccountTokensScopedRouteImport.update({ + id: '/tokens/scoped', + path: '/tokens/scoped', + getParentRoute: () => AppAccountRoute, +} as any) +const AppOrgSlugWebhooksIndexRoute = AppOrgSlugWebhooksIndexRouteImport.update({ + id: '/$slug/webhooks/', + path: '/$slug/webhooks/', + getParentRoute: () => AppOrgRoute, +} as any) +const AppOrgSlugPrivateAppsIndexRoute = + AppOrgSlugPrivateAppsIndexRouteImport.update({ + id: '/$slug/private-apps/', + path: '/$slug/private-apps/', + getParentRoute: () => AppOrgRoute, + } as any) +const AuthPartnersStripeProjectsLoginRoute = + AuthPartnersStripeProjectsLoginRouteImport.update({ + id: '/_auth/partners/stripe/projects/login', + path: '/partners/stripe/projects/login', + getParentRoute: () => rootRouteImport, + } as any) +const AppOrgSlugWebhooksEndpointIdRoute = + AppOrgSlugWebhooksEndpointIdRouteImport.update({ + id: '/$slug/webhooks/$endpointId', + path: '/$slug/webhooks/$endpointId', + getParentRoute: () => AppOrgRoute, + } as any) + +export interface FileRoutesByFullPath { + '/account': typeof AppAccountRouteWithChildren + '/org': typeof AppOrgRouteWithChildren + '/organizations': typeof AppOrganizationsRoute + '/forgot-password': typeof AuthForgotPasswordRoute + '/forgot-password-mfa': typeof AuthForgotPasswordMfaRoute + '/reset-password': typeof AuthResetPasswordRoute + '/sign-in': typeof AuthSignInRoute + '/sign-in-mfa': typeof AuthSignInMfaRoute + '/sign-in-partner': typeof AuthSignInPartnerRoute + '/sign-in-sso': typeof AuthSignInSsoRoute + '/sign-up': typeof AuthSignUpRoute + '/account/audit': typeof AppAccountAuditRoute + '/account/me': typeof AppAccountMeRoute + '/account/security': typeof AppAccountSecurityRoute + '/support/link': typeof AppSupportLinkRoute + '/support/new': typeof AppSupportNewRoute + '/cli/login': typeof AuthCliLoginRoute + '/new/': typeof AppNewIndexRoute + '/org/': typeof AppOrgIndexRoute + '/account/tokens/scoped': typeof AppAccountTokensScopedRoute + '/org/$slug/apps': typeof AppOrgSlugAppsRoute + '/org/$slug/audit': typeof AppOrgSlugAuditRoute + '/org/$slug/audit-log-drains': typeof AppOrgSlugAuditLogDrainsRoute + '/org/$slug/billing': typeof AppOrgSlugBillingRoute + '/org/$slug/documents': typeof AppOrgSlugDocumentsRoute + '/org/$slug/general': typeof AppOrgSlugGeneralRoute + '/org/$slug/integrations': typeof AppOrgSlugIntegrationsRoute + '/org/$slug/security': typeof AppOrgSlugSecurityRoute + '/org/$slug/sso': typeof AppOrgSlugSsoRoute + '/org/$slug/team': typeof AppOrgSlugTeamRoute + '/org/$slug/usage': typeof AppOrgSlugUsageRoute + '/account/tokens/': typeof AppAccountTokensIndexRoute + '/org/$slug/': typeof AppOrgSlugIndexRoute + '/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute + '/partners/stripe/projects/login': typeof AuthPartnersStripeProjectsLoginRoute + '/org/$slug/private-apps/': typeof AppOrgSlugPrivateAppsIndexRoute + '/org/$slug/webhooks/': typeof AppOrgSlugWebhooksIndexRoute +} +export interface FileRoutesByTo { + '/account': typeof AppAccountRouteWithChildren + '/organizations': typeof AppOrganizationsRoute + '/forgot-password': typeof AuthForgotPasswordRoute + '/forgot-password-mfa': typeof AuthForgotPasswordMfaRoute + '/reset-password': typeof AuthResetPasswordRoute + '/sign-in': typeof AuthSignInRoute + '/sign-in-mfa': typeof AuthSignInMfaRoute + '/sign-in-partner': typeof AuthSignInPartnerRoute + '/sign-in-sso': typeof AuthSignInSsoRoute + '/sign-up': typeof AuthSignUpRoute + '/account/audit': typeof AppAccountAuditRoute + '/account/me': typeof AppAccountMeRoute + '/account/security': typeof AppAccountSecurityRoute + '/support/link': typeof AppSupportLinkRoute + '/support/new': typeof AppSupportNewRoute + '/cli/login': typeof AuthCliLoginRoute + '/new': typeof AppNewIndexRoute + '/org': typeof AppOrgIndexRoute + '/account/tokens/scoped': typeof AppAccountTokensScopedRoute + '/org/$slug/apps': typeof AppOrgSlugAppsRoute + '/org/$slug/audit': typeof AppOrgSlugAuditRoute + '/org/$slug/audit-log-drains': typeof AppOrgSlugAuditLogDrainsRoute + '/org/$slug/billing': typeof AppOrgSlugBillingRoute + '/org/$slug/documents': typeof AppOrgSlugDocumentsRoute + '/org/$slug/general': typeof AppOrgSlugGeneralRoute + '/org/$slug/integrations': typeof AppOrgSlugIntegrationsRoute + '/org/$slug/security': typeof AppOrgSlugSecurityRoute + '/org/$slug/sso': typeof AppOrgSlugSsoRoute + '/org/$slug/team': typeof AppOrgSlugTeamRoute + '/org/$slug/usage': typeof AppOrgSlugUsageRoute + '/account/tokens': typeof AppAccountTokensIndexRoute + '/org/$slug': typeof AppOrgSlugIndexRoute + '/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute + '/partners/stripe/projects/login': typeof AuthPartnersStripeProjectsLoginRoute + '/org/$slug/private-apps': typeof AppOrgSlugPrivateAppsIndexRoute + '/org/$slug/webhooks': typeof AppOrgSlugWebhooksIndexRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/_app/account': typeof AppAccountRouteWithChildren + '/_app/org': typeof AppOrgRouteWithChildren + '/_app/organizations': typeof AppOrganizationsRoute + '/_auth/forgot-password': typeof AuthForgotPasswordRoute + '/_auth/forgot-password-mfa': typeof AuthForgotPasswordMfaRoute + '/_auth/reset-password': typeof AuthResetPasswordRoute + '/_auth/sign-in': typeof AuthSignInRoute + '/_auth/sign-in-mfa': typeof AuthSignInMfaRoute + '/_auth/sign-in-partner': typeof AuthSignInPartnerRoute + '/_auth/sign-in-sso': typeof AuthSignInSsoRoute + '/_auth/sign-up': typeof AuthSignUpRoute + '/_app/account/audit': typeof AppAccountAuditRoute + '/_app/account/me': typeof AppAccountMeRoute + '/_app/account/security': typeof AppAccountSecurityRoute + '/_app/support/link': typeof AppSupportLinkRoute + '/_app/support/new': typeof AppSupportNewRoute + '/_auth/cli/login': typeof AuthCliLoginRoute + '/_app/new/': typeof AppNewIndexRoute + '/_app/org/': typeof AppOrgIndexRoute + '/_app/account/tokens/scoped': typeof AppAccountTokensScopedRoute + '/_app/org/$slug/apps': typeof AppOrgSlugAppsRoute + '/_app/org/$slug/audit': typeof AppOrgSlugAuditRoute + '/_app/org/$slug/audit-log-drains': typeof AppOrgSlugAuditLogDrainsRoute + '/_app/org/$slug/billing': typeof AppOrgSlugBillingRoute + '/_app/org/$slug/documents': typeof AppOrgSlugDocumentsRoute + '/_app/org/$slug/general': typeof AppOrgSlugGeneralRoute + '/_app/org/$slug/integrations': typeof AppOrgSlugIntegrationsRoute + '/_app/org/$slug/security': typeof AppOrgSlugSecurityRoute + '/_app/org/$slug/sso': typeof AppOrgSlugSsoRoute + '/_app/org/$slug/team': typeof AppOrgSlugTeamRoute + '/_app/org/$slug/usage': typeof AppOrgSlugUsageRoute + '/_app/account/tokens/': typeof AppAccountTokensIndexRoute + '/_app/org/$slug/': typeof AppOrgSlugIndexRoute + '/_app/org/$slug/webhooks/$endpointId': typeof AppOrgSlugWebhooksEndpointIdRoute + '/_auth/partners/stripe/projects/login': typeof AuthPartnersStripeProjectsLoginRoute + '/_app/org/$slug/private-apps/': typeof AppOrgSlugPrivateAppsIndexRoute + '/_app/org/$slug/webhooks/': typeof AppOrgSlugWebhooksIndexRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/account' + | '/org' + | '/organizations' + | '/forgot-password' + | '/forgot-password-mfa' + | '/reset-password' + | '/sign-in' + | '/sign-in-mfa' + | '/sign-in-partner' + | '/sign-in-sso' + | '/sign-up' + | '/account/audit' + | '/account/me' + | '/account/security' + | '/support/link' + | '/support/new' + | '/cli/login' + | '/new/' + | '/org/' + | '/account/tokens/scoped' + | '/org/$slug/apps' + | '/org/$slug/audit' + | '/org/$slug/audit-log-drains' + | '/org/$slug/billing' + | '/org/$slug/documents' + | '/org/$slug/general' + | '/org/$slug/integrations' + | '/org/$slug/security' + | '/org/$slug/sso' + | '/org/$slug/team' + | '/org/$slug/usage' + | '/account/tokens/' + | '/org/$slug/' + | '/org/$slug/webhooks/$endpointId' + | '/partners/stripe/projects/login' + | '/org/$slug/private-apps/' + | '/org/$slug/webhooks/' + fileRoutesByTo: FileRoutesByTo + to: + | '/account' + | '/organizations' + | '/forgot-password' + | '/forgot-password-mfa' + | '/reset-password' + | '/sign-in' + | '/sign-in-mfa' + | '/sign-in-partner' + | '/sign-in-sso' + | '/sign-up' + | '/account/audit' + | '/account/me' + | '/account/security' + | '/support/link' + | '/support/new' + | '/cli/login' + | '/new' + | '/org' + | '/account/tokens/scoped' + | '/org/$slug/apps' + | '/org/$slug/audit' + | '/org/$slug/audit-log-drains' + | '/org/$slug/billing' + | '/org/$slug/documents' + | '/org/$slug/general' + | '/org/$slug/integrations' + | '/org/$slug/security' + | '/org/$slug/sso' + | '/org/$slug/team' + | '/org/$slug/usage' + | '/account/tokens' + | '/org/$slug' + | '/org/$slug/webhooks/$endpointId' + | '/partners/stripe/projects/login' + | '/org/$slug/private-apps' + | '/org/$slug/webhooks' + id: + | '__root__' + | '/_app/account' + | '/_app/org' + | '/_app/organizations' + | '/_auth/forgot-password' + | '/_auth/forgot-password-mfa' + | '/_auth/reset-password' + | '/_auth/sign-in' + | '/_auth/sign-in-mfa' + | '/_auth/sign-in-partner' + | '/_auth/sign-in-sso' + | '/_auth/sign-up' + | '/_app/account/audit' + | '/_app/account/me' + | '/_app/account/security' + | '/_app/support/link' + | '/_app/support/new' + | '/_auth/cli/login' + | '/_app/new/' + | '/_app/org/' + | '/_app/account/tokens/scoped' + | '/_app/org/$slug/apps' + | '/_app/org/$slug/audit' + | '/_app/org/$slug/audit-log-drains' + | '/_app/org/$slug/billing' + | '/_app/org/$slug/documents' + | '/_app/org/$slug/general' + | '/_app/org/$slug/integrations' + | '/_app/org/$slug/security' + | '/_app/org/$slug/sso' + | '/_app/org/$slug/team' + | '/_app/org/$slug/usage' + | '/_app/account/tokens/' + | '/_app/org/$slug/' + | '/_app/org/$slug/webhooks/$endpointId' + | '/_auth/partners/stripe/projects/login' + | '/_app/org/$slug/private-apps/' + | '/_app/org/$slug/webhooks/' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + AppAccountRoute: typeof AppAccountRouteWithChildren + AppOrgRoute: typeof AppOrgRouteWithChildren + AppOrganizationsRoute: typeof AppOrganizationsRoute + AuthForgotPasswordRoute: typeof AuthForgotPasswordRoute + AuthForgotPasswordMfaRoute: typeof AuthForgotPasswordMfaRoute + AuthResetPasswordRoute: typeof AuthResetPasswordRoute + AuthSignInRoute: typeof AuthSignInRoute + AuthSignInMfaRoute: typeof AuthSignInMfaRoute + AuthSignInPartnerRoute: typeof AuthSignInPartnerRoute + AuthSignInSsoRoute: typeof AuthSignInSsoRoute + AuthSignUpRoute: typeof AuthSignUpRoute + AppSupportLinkRoute: typeof AppSupportLinkRoute + AppSupportNewRoute: typeof AppSupportNewRoute + AuthCliLoginRoute: typeof AuthCliLoginRoute + AppNewIndexRoute: typeof AppNewIndexRoute + AuthPartnersStripeProjectsLoginRoute: typeof AuthPartnersStripeProjectsLoginRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/_auth/sign-up': { + id: '/_auth/sign-up' + path: '/sign-up' + fullPath: '/sign-up' + preLoaderRoute: typeof AuthSignUpRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/sign-in-sso': { + id: '/_auth/sign-in-sso' + path: '/sign-in-sso' + fullPath: '/sign-in-sso' + preLoaderRoute: typeof AuthSignInSsoRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/sign-in-partner': { + id: '/_auth/sign-in-partner' + path: '/sign-in-partner' + fullPath: '/sign-in-partner' + preLoaderRoute: typeof AuthSignInPartnerRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/sign-in-mfa': { + id: '/_auth/sign-in-mfa' + path: '/sign-in-mfa' + fullPath: '/sign-in-mfa' + preLoaderRoute: typeof AuthSignInMfaRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/sign-in': { + id: '/_auth/sign-in' + path: '/sign-in' + fullPath: '/sign-in' + preLoaderRoute: typeof AuthSignInRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/reset-password': { + id: '/_auth/reset-password' + path: '/reset-password' + fullPath: '/reset-password' + preLoaderRoute: typeof AuthResetPasswordRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/forgot-password-mfa': { + id: '/_auth/forgot-password-mfa' + path: '/forgot-password-mfa' + fullPath: '/forgot-password-mfa' + preLoaderRoute: typeof AuthForgotPasswordMfaRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/forgot-password': { + id: '/_auth/forgot-password' + path: '/forgot-password' + fullPath: '/forgot-password' + preLoaderRoute: typeof AuthForgotPasswordRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/organizations': { + id: '/_app/organizations' + path: '/organizations' + fullPath: '/organizations' + preLoaderRoute: typeof AppOrganizationsRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/org': { + id: '/_app/org' + path: '/org' + fullPath: '/org' + preLoaderRoute: typeof AppOrgRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/account': { + id: '/_app/account' + path: '/account' + fullPath: '/account' + preLoaderRoute: typeof AppAccountRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/org/': { + id: '/_app/org/' + path: '/' + fullPath: '/org/' + preLoaderRoute: typeof AppOrgIndexRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/new/': { + id: '/_app/new/' + path: '/new' + fullPath: '/new/' + preLoaderRoute: typeof AppNewIndexRouteImport + parentRoute: typeof rootRouteImport + } + '/_auth/cli/login': { + id: '/_auth/cli/login' + path: '/cli/login' + fullPath: '/cli/login' + preLoaderRoute: typeof AuthCliLoginRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/support/new': { + id: '/_app/support/new' + path: '/support/new' + fullPath: '/support/new' + preLoaderRoute: typeof AppSupportNewRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/support/link': { + id: '/_app/support/link' + path: '/support/link' + fullPath: '/support/link' + preLoaderRoute: typeof AppSupportLinkRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/account/security': { + id: '/_app/account/security' + path: '/security' + fullPath: '/account/security' + preLoaderRoute: typeof AppAccountSecurityRouteImport + parentRoute: typeof AppAccountRoute + } + '/_app/account/me': { + id: '/_app/account/me' + path: '/me' + fullPath: '/account/me' + preLoaderRoute: typeof AppAccountMeRouteImport + parentRoute: typeof AppAccountRoute + } + '/_app/account/audit': { + id: '/_app/account/audit' + path: '/audit' + fullPath: '/account/audit' + preLoaderRoute: typeof AppAccountAuditRouteImport + parentRoute: typeof AppAccountRoute + } + '/_app/org/$slug/': { + id: '/_app/org/$slug/' + path: '/$slug' + fullPath: '/org/$slug/' + preLoaderRoute: typeof AppOrgSlugIndexRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/account/tokens/': { + id: '/_app/account/tokens/' + path: '/tokens' + fullPath: '/account/tokens/' + preLoaderRoute: typeof AppAccountTokensIndexRouteImport + parentRoute: typeof AppAccountRoute + } + '/_app/org/$slug/usage': { + id: '/_app/org/$slug/usage' + path: '/$slug/usage' + fullPath: '/org/$slug/usage' + preLoaderRoute: typeof AppOrgSlugUsageRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/team': { + id: '/_app/org/$slug/team' + path: '/$slug/team' + fullPath: '/org/$slug/team' + preLoaderRoute: typeof AppOrgSlugTeamRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/sso': { + id: '/_app/org/$slug/sso' + path: '/$slug/sso' + fullPath: '/org/$slug/sso' + preLoaderRoute: typeof AppOrgSlugSsoRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/security': { + id: '/_app/org/$slug/security' + path: '/$slug/security' + fullPath: '/org/$slug/security' + preLoaderRoute: typeof AppOrgSlugSecurityRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/integrations': { + id: '/_app/org/$slug/integrations' + path: '/$slug/integrations' + fullPath: '/org/$slug/integrations' + preLoaderRoute: typeof AppOrgSlugIntegrationsRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/general': { + id: '/_app/org/$slug/general' + path: '/$slug/general' + fullPath: '/org/$slug/general' + preLoaderRoute: typeof AppOrgSlugGeneralRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/documents': { + id: '/_app/org/$slug/documents' + path: '/$slug/documents' + fullPath: '/org/$slug/documents' + preLoaderRoute: typeof AppOrgSlugDocumentsRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/billing': { + id: '/_app/org/$slug/billing' + path: '/$slug/billing' + fullPath: '/org/$slug/billing' + preLoaderRoute: typeof AppOrgSlugBillingRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/audit-log-drains': { + id: '/_app/org/$slug/audit-log-drains' + path: '/$slug/audit-log-drains' + fullPath: '/org/$slug/audit-log-drains' + preLoaderRoute: typeof AppOrgSlugAuditLogDrainsRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/audit': { + id: '/_app/org/$slug/audit' + path: '/$slug/audit' + fullPath: '/org/$slug/audit' + preLoaderRoute: typeof AppOrgSlugAuditRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/apps': { + id: '/_app/org/$slug/apps' + path: '/$slug/apps' + fullPath: '/org/$slug/apps' + preLoaderRoute: typeof AppOrgSlugAppsRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/account/tokens/scoped': { + id: '/_app/account/tokens/scoped' + path: '/tokens/scoped' + fullPath: '/account/tokens/scoped' + preLoaderRoute: typeof AppAccountTokensScopedRouteImport + parentRoute: typeof AppAccountRoute + } + '/_app/org/$slug/webhooks/': { + id: '/_app/org/$slug/webhooks/' + path: '/$slug/webhooks' + fullPath: '/org/$slug/webhooks/' + preLoaderRoute: typeof AppOrgSlugWebhooksIndexRouteImport + parentRoute: typeof AppOrgRoute + } + '/_app/org/$slug/private-apps/': { + id: '/_app/org/$slug/private-apps/' + path: '/$slug/private-apps' + fullPath: '/org/$slug/private-apps/' + preLoaderRoute: typeof AppOrgSlugPrivateAppsIndexRouteImport + parentRoute: typeof AppOrgRoute + } + '/_auth/partners/stripe/projects/login': { + id: '/_auth/partners/stripe/projects/login' + path: '/partners/stripe/projects/login' + fullPath: '/partners/stripe/projects/login' + preLoaderRoute: typeof AuthPartnersStripeProjectsLoginRouteImport + parentRoute: typeof rootRouteImport + } + '/_app/org/$slug/webhooks/$endpointId': { + id: '/_app/org/$slug/webhooks/$endpointId' + path: '/$slug/webhooks/$endpointId' + fullPath: '/org/$slug/webhooks/$endpointId' + preLoaderRoute: typeof AppOrgSlugWebhooksEndpointIdRouteImport + parentRoute: typeof AppOrgRoute + } + } +} + +interface AppAccountRouteChildren { + AppAccountAuditRoute: typeof AppAccountAuditRoute + AppAccountMeRoute: typeof AppAccountMeRoute + AppAccountSecurityRoute: typeof AppAccountSecurityRoute + AppAccountTokensScopedRoute: typeof AppAccountTokensScopedRoute + AppAccountTokensIndexRoute: typeof AppAccountTokensIndexRoute +} + +const AppAccountRouteChildren: AppAccountRouteChildren = { + AppAccountAuditRoute: AppAccountAuditRoute, + AppAccountMeRoute: AppAccountMeRoute, + AppAccountSecurityRoute: AppAccountSecurityRoute, + AppAccountTokensScopedRoute: AppAccountTokensScopedRoute, + AppAccountTokensIndexRoute: AppAccountTokensIndexRoute, +} + +const AppAccountRouteWithChildren = AppAccountRoute._addFileChildren( + AppAccountRouteChildren, +) + +interface AppOrgRouteChildren { + AppOrgIndexRoute: typeof AppOrgIndexRoute + AppOrgSlugAppsRoute: typeof AppOrgSlugAppsRoute + AppOrgSlugAuditRoute: typeof AppOrgSlugAuditRoute + AppOrgSlugAuditLogDrainsRoute: typeof AppOrgSlugAuditLogDrainsRoute + AppOrgSlugBillingRoute: typeof AppOrgSlugBillingRoute + AppOrgSlugDocumentsRoute: typeof AppOrgSlugDocumentsRoute + AppOrgSlugGeneralRoute: typeof AppOrgSlugGeneralRoute + AppOrgSlugIntegrationsRoute: typeof AppOrgSlugIntegrationsRoute + AppOrgSlugSecurityRoute: typeof AppOrgSlugSecurityRoute + AppOrgSlugSsoRoute: typeof AppOrgSlugSsoRoute + AppOrgSlugTeamRoute: typeof AppOrgSlugTeamRoute + AppOrgSlugUsageRoute: typeof AppOrgSlugUsageRoute + AppOrgSlugIndexRoute: typeof AppOrgSlugIndexRoute + AppOrgSlugWebhooksEndpointIdRoute: typeof AppOrgSlugWebhooksEndpointIdRoute + AppOrgSlugPrivateAppsIndexRoute: typeof AppOrgSlugPrivateAppsIndexRoute + AppOrgSlugWebhooksIndexRoute: typeof AppOrgSlugWebhooksIndexRoute +} + +const AppOrgRouteChildren: AppOrgRouteChildren = { + AppOrgIndexRoute: AppOrgIndexRoute, + AppOrgSlugAppsRoute: AppOrgSlugAppsRoute, + AppOrgSlugAuditRoute: AppOrgSlugAuditRoute, + AppOrgSlugAuditLogDrainsRoute: AppOrgSlugAuditLogDrainsRoute, + AppOrgSlugBillingRoute: AppOrgSlugBillingRoute, + AppOrgSlugDocumentsRoute: AppOrgSlugDocumentsRoute, + AppOrgSlugGeneralRoute: AppOrgSlugGeneralRoute, + AppOrgSlugIntegrationsRoute: AppOrgSlugIntegrationsRoute, + AppOrgSlugSecurityRoute: AppOrgSlugSecurityRoute, + AppOrgSlugSsoRoute: AppOrgSlugSsoRoute, + AppOrgSlugTeamRoute: AppOrgSlugTeamRoute, + AppOrgSlugUsageRoute: AppOrgSlugUsageRoute, + AppOrgSlugIndexRoute: AppOrgSlugIndexRoute, + AppOrgSlugWebhooksEndpointIdRoute: AppOrgSlugWebhooksEndpointIdRoute, + AppOrgSlugPrivateAppsIndexRoute: AppOrgSlugPrivateAppsIndexRoute, + AppOrgSlugWebhooksIndexRoute: AppOrgSlugWebhooksIndexRoute, +} + +const AppOrgRouteWithChildren = + AppOrgRoute._addFileChildren(AppOrgRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + AppAccountRoute: AppAccountRouteWithChildren, + AppOrgRoute: AppOrgRouteWithChildren, + AppOrganizationsRoute: AppOrganizationsRoute, + AuthForgotPasswordRoute: AuthForgotPasswordRoute, + AuthForgotPasswordMfaRoute: AuthForgotPasswordMfaRoute, + AuthResetPasswordRoute: AuthResetPasswordRoute, + AuthSignInRoute: AuthSignInRoute, + AuthSignInMfaRoute: AuthSignInMfaRoute, + AuthSignInPartnerRoute: AuthSignInPartnerRoute, + AuthSignInSsoRoute: AuthSignInSsoRoute, + AuthSignUpRoute: AuthSignUpRoute, + AppSupportLinkRoute: AppSupportLinkRoute, + AppSupportNewRoute: AppSupportNewRoute, + AuthCliLoginRoute: AuthCliLoginRoute, + AppNewIndexRoute: AppNewIndexRoute, + AuthPartnersStripeProjectsLoginRoute: AuthPartnersStripeProjectsLoginRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() + +import type { getRouter } from './router.tsx' +import type { startInstance } from './start.ts' +declare module '@tanstack/react-start' { + interface Register { + ssr: true + router: Awaited> + config: Awaited> + } +} diff --git a/apps/studio/router.tsx b/apps/studio/router.tsx new file mode 100644 index 00000000000..451058eaf75 --- /dev/null +++ b/apps/studio/router.tsx @@ -0,0 +1,97 @@ +import type { QueryClient } from '@tanstack/react-query' +import { createRouter } from '@tanstack/react-router' +import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' + +import { routeTree } from './routeTree.gen' +import { getQueryClient } from '@/data/query-client' +import { BASE_PATH, IS_PLATFORM } from '@/lib/constants' + +export interface RouterContext { + queryClient: QueryClient +} + +// Skew protection: pin this browser session to the deployment that served it, +// so a long-lived dashboard session never 404s on a lazily-loaded JS chunk or +// gets a surprise version change mid-session. It's a session cookie (no +// expiry) — cleared when the tab/window closes, so the next visit gets the +// latest deploy. Scoped to BASE_PATH so the version check (hit at the root +// `/api/...`, outside this Path) stays unpinned and can still detect new +// deploys; the "Refresh" toast clears it before reloading (see +// use-check-latest-deploy). No-op unless we're on a Vercel deploy with Skew +// Protection enabled. +function pinDeploymentForSession() { + if (typeof document === 'undefined') return + if (!IS_PLATFORM) return + if (process.env.NEXT_PUBLIC_VERCEL_SKEW_PROTECTION_ENABLED !== '1') return + const deploymentId = process.env.NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID + if (!deploymentId) return + if (document.cookie.includes('__vdpl=')) return + document.cookie = `__vdpl=${deploymentId}; Path=${BASE_PATH || '/'}; SameSite=Lax; Secure` +} + +// Backstop for the pin above: if a lazily-loaded chunk 404s — most likely the +// pinned deployment aged out of Skew Protection's Maximum Age, so its hashed +// chunks are gone — Vite emits `vite:preloadError`. Drop the pin and reload so +// we land on the latest deployment (a plain reload wouldn't recover, since the +// cookie would just re-pin to the dead deployment). A short time-window guard +// prevents a reload loop if the latest deployment is itself broken. +function registerChunkErrorBackstop() { + if (typeof window === 'undefined') return + window.addEventListener('vite:preloadError', (event) => { + const KEY = 'studio:chunk-error-reload-at' + let last = 0 + try { + last = Number(sessionStorage.getItem(KEY) || 0) + } catch { + // sessionStorage unavailable — fall through and attempt a reload anyway. + } + // Reloaded very recently → likely a loop; let Vite surface the error. + if (Date.now() - last < 10_000) return + event.preventDefault() + try { + sessionStorage.setItem(KEY, String(Date.now())) + } catch { + // ignore — worst case we lose loop protection for this reload. + } + document.cookie = `__vdpl=; Path=${BASE_PATH || '/'}; Max-Age=0` + window.location.reload() + }) +} + +function getContext(): RouterContext { + return { + queryClient: getQueryClient(), + } +} + +export function getRouter() { + pinDeploymentForSession() + registerChunkErrorBackstop() + + const context = getContext() + + const router = createRouter({ + routeTree, + context, + scrollRestoration: true, + defaultPreload: 'intent', + // Inlined via Vite's `define` at build time; stays undefined (= app at `/`) + // unless NEXT_PUBLIC_BASE_PATH is set. Must agree with Vite `base` + basepath: process.env.NEXT_PUBLIC_BASE_PATH || undefined, + }) + + // @tanstack/react-router-ssr-query@1.166.12 pulls in @tanstack/query-core@5.100 + // as a peer, but our app pins react-query to 5.83. The QueryClient class is + // structurally identical between the two, but TS treats them as nominally + // distinct types because each version has its own `#private` field. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setupRouterSsrQueryIntegration({ router, queryClient: context.queryClient as any }) + + return router +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType + } +} diff --git a/apps/studio/routes/__root.tsx b/apps/studio/routes/__root.tsx new file mode 100644 index 00000000000..f386d7126b3 --- /dev/null +++ b/apps/studio/routes/__root.tsx @@ -0,0 +1,395 @@ +/// + +import 'react-data-grid/lib/styles.css' +import '@/styles/code.css' +import '@/styles/editor.css' +import '@/styles/focus.css' +// Vite-only: defines @font-face for the custom fonts. The Next pipeline +// (pages/_app.tsx) loads these via next/font instead, so this import has no +// counterpart there — but dropping it under Vite makes the browser fall back +// to system fonts. +import '@/styles/fonts.css' +import '@/styles/graphiql-base.css' +import '@/styles/grid.css' +import '@/styles/globals.css' +import '@/styles/markdown-preview.css' +import '@/styles/monaco.css' +import '@/styles/react-data-grid-logs.css' +import '@/styles/reactflow.css' +import '@/styles/storage.css' +import '@/styles/stripe.css' +import '@/styles/ui.css' +import 'ui-patterns/ShimmeringLoader/index.css' + +import { loader } from '@monaco-editor/react' +import * as Sentry from '@sentry/react' +import { TanStackDevtools } from '@tanstack/react-devtools' +import type { QueryClient } from '@tanstack/react-query' +import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' +import { + createRootRouteWithContext, + HeadContent, + Outlet, + redirect, + Scripts, +} from '@tanstack/react-router' +import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' +import { + FeatureFlagProvider, + getFlags, + TelemetryTagManager, + ThemeProvider, + useThemeSandbox, +} from 'common' +import dayjs from 'dayjs' +import customParseFormat from 'dayjs/plugin/customParseFormat' +import duration from 'dayjs/plugin/duration' +import relativeTime from 'dayjs/plugin/relativeTime' +import timezone from 'dayjs/plugin/timezone' +import utc from 'dayjs/plugin/utc' +import { DevToolbar, DevToolbarProvider, DevToolbarTrigger, type ExtraTab } from 'dev-tools' +import { NuqsAdapter } from 'nuqs/adapters/tanstack-router' +import { + lazy, + Suspense, + useCallback, + useEffect, + type ComponentProps, + type ErrorInfo, + type ReactNode, +} from 'react' +import { ErrorBoundary } from 'react-error-boundary' +import { TooltipProvider } from 'ui' + +import { StudioCommandMenu } from '@/components/interfaces/App/CommandMenu' +import { StudioCommandProvider as CommandProvider } from '@/components/interfaces/App/CommandMenu/StudioCommandProvider' +import { FeaturePreviewContextProvider } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewContext' +import { FeaturePreviewModal } from '@/components/interfaces/App/FeaturePreview/FeaturePreviewModal' +import { MonacoThemeProvider } from '@/components/interfaces/App/MonacoThemeProvider' +import { RouteValidationWrapper } from '@/components/interfaces/App/RouteValidationWrapper' +import { MainScrollContainerProvider } from '@/components/layouts/MainScrollContainerContext' +import { BannerStackProvider } from '@/components/ui/BannerStack/BannerStackProvider' +import { GlobalErrorBoundaryState } from '@/components/ui/ErrorBoundary/GlobalErrorBoundaryState' +import { useCustomContent } from '@/hooks/custom-content/useCustomContent' +import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' +import { AuthProvider } from '@/lib/auth' +import { API_URL, BASE_PATH, IS_PLATFORM, useDefaultProvider } from '@/lib/constants' +import { ProfileProvider } from '@/lib/profile' +import { Telemetry } from '@/lib/telemetry' +import { Toaster } from '@/lib/toaster' +import Error404 from '@/pages/404' +import Error500 from '@/pages/500' +import { matchRedirect } from '@/redirects.shared' +import { AiAssistantStateContextProvider } from '@/state/ai-assistant-state' + +dayjs.extend(customParseFormat) +dayjs.extend(utc) +dayjs.extend(timezone) +dayjs.extend(relativeTime) +dayjs.extend(duration) + +interface RouterContext { + queryClient: QueryClient +} + +const FeatureFlagProviderWithOrgContext = ({ + children, + ...props +}: ComponentProps) => { + const { data: selectedOrganization } = useSelectedOrganizationQuery({ enabled: IS_PLATFORM }) + + return ( + + {children} + + ) +} + +// Keep dev-only components out of the production bundle. +const IS_DEV_TOOLBAR_ENABLED = + process.env.NEXT_PUBLIC_ENVIRONMENT === 'local' || + process.env.NEXT_PUBLIC_ENVIRONMENT === 'staging' + +const ResourceWarningsTab = IS_DEV_TOOLBAR_ENABLED + ? lazy(() => + import('@/components/ui/DevToolbar/ResourceWarningsTab').then((m) => ({ + default: m.ResourceWarningsTab, + })) + ) + : () => null + +const devToolbarExtraTabs: ExtraTab[] = IS_DEV_TOOLBAR_ENABLED + ? [ + { + id: 'warnings', + label: 'Warnings', + content: ( + + + + ), + }, + ] + : [] + +// [Joshen] Attempt for offline support/bypass ISP issues is to store the assets required for monaco +// locally. We're however, only storing the assets which we need (based on what the network tab loads +// while using monaco). If we end up facing more effort trying to maintain this, probably to either +// use cloudflare or find some way to pull all the files from a CDN via a CLI, rather than tracking individual files +// The alternative was to import * as monaco from 'monaco-editor' but i couldn't get it working +loader.config({ + paths: { + vs: IS_PLATFORM + ? 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' + : `${BASE_PATH}/monaco-editor`, + }, +}) + +const FAVICON_ROUTE = '/favicon' +const THEME_COLOR = '1E1E1E' +const APPLICATION_NAME = 'Supabase Studio' + +const APPLE_TOUCH_ICON_SIZES = [ + '57x57', + '60x60', + '72x72', + '76x76', + '114x114', + '120x120', + '144x144', + '152x152', +] +// The 128 variant ships as `favicon-128.png` (no dimensions in the filename); +// the rest follow `favicon-.png`. +const FAVICON_PNG_VARIANTS: Array<{ sizes: string; file: string }> = [ + { sizes: '16x16', file: 'favicon-16x16.png' }, + { sizes: '32x32', file: 'favicon-32x32.png' }, + { sizes: '48x48', file: 'favicon-48x48.png' }, + { sizes: '96x96', file: 'favicon-96x96.png' }, + { sizes: '128x128', file: 'favicon-128.png' }, + { sizes: '180x180', file: 'favicon-180x180.png' }, + { sizes: '196x196', file: 'favicon-196x196.png' }, +] + +function buildRootHead() { + const meta: Array> = [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'initial-scale=1.0, width=device-width' }, + { property: 'og:image', content: `${BASE_PATH}/img/supabase-logo.png` }, + { name: 'googlebot', content: 'notranslate' }, + { name: 'application-name', content: APPLICATION_NAME }, + { name: 'msapplication-TileColor', content: `#${THEME_COLOR}` }, + { name: 'msapplication-TileImage', content: `${BASE_PATH}${FAVICON_ROUTE}/mstile-144x144.png` }, + { + name: 'msapplication-square70x70logo', + content: `${BASE_PATH}${FAVICON_ROUTE}/mstile-70x70.png`, + }, + { + name: 'msapplication-square150x150logo', + content: `${BASE_PATH}${FAVICON_ROUTE}/mstile-150x150.png`, + }, + { + name: 'msapplication-wide310x150logo', + content: `${BASE_PATH}${FAVICON_ROUTE}/mstile-310x150.png`, + }, + { + name: 'msapplication-square310x310logo', + content: `${BASE_PATH}${FAVICON_ROUTE}/mstile-310x310.png`, + }, + { name: 'theme-color', content: `#${THEME_COLOR}` }, + { title: 'Supabase' }, + ] + + const links: Array> = [ + // Google Fonts — Source Code Pro (the local CustomFont is declared in styles/fonts.css). + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossOrigin: '' }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;500;600;700&display=swap', + }, + ...APPLE_TOUCH_ICON_SIZES.map((size) => ({ + rel: 'apple-touch-icon-precomposed', + sizes: size, + href: `${BASE_PATH}${FAVICON_ROUTE}/apple-icon-${size}.png`, + })), + ...FAVICON_PNG_VARIANTS.map(({ sizes, file }) => ({ + rel: 'icon', + type: 'image/png', + sizes, + href: `${BASE_PATH}${FAVICON_ROUTE}/${file}`, + })), + { rel: 'shortcut icon', href: `${BASE_PATH}${FAVICON_ROUTE}/favicon.ico` }, + { rel: 'icon', type: 'image/x-icon', href: `${BASE_PATH}${FAVICON_ROUTE}/favicon.ico` }, + { rel: 'apple-touch-icon', href: `${BASE_PATH}${FAVICON_ROUTE}/favicon.ico` }, + { rel: 'manifest', href: `${BASE_PATH}${FAVICON_ROUTE}/manifest.json` }, + ] + + if (IS_PLATFORM) { + links.unshift({ + rel: 'preconnect', + href: new URL(API_URL).origin, + crossOrigin: 'use-credentials', + }) + } + + return { meta, links } +} + +function DynamicTitle() { + const { appTitle } = useCustomContent(['app:title']) + if (!appTitle) return null + + return {appTitle} +} + +const errorBoundaryHandler = (error: Error, _info: ErrorInfo) => { + Sentry.withScope(function (scope) { + scope.setTag('globalErrorBoundary', true) + const eventId = Sentry.captureException(error) + // Attach the Sentry event ID to the error object so it can be accessed by the error boundary + if (eventId && error && typeof error === 'object') { + ;(error as Error & { sentryId?: string }).sentryId = eventId + } + }) + + console.error(error.stack) +} + +function NotFound() { + return +} + +function ErrorBoundaryRoute({ error }: { error: Error }) { + // Mirrors `errorBoundaryHandler` above (used by the in-tree + // `react-error-boundary`) — TanStack's `errorComponent` covers + // errors thrown during route load/render before the in-tree + // boundary mounts, so we report from both layers. + useEffect(() => { + Sentry.withScope((scope) => { + scope.setTag('routerErrorComponent', true) + const eventId = Sentry.captureException(error) + if (eventId && error && typeof error === 'object') { + ;(error as Error & { sentryId?: string }).sentryId = eventId + } + }) + console.error(error.stack) + }, [error]) + + return +} + +export const Route = createRootRouteWithContext()({ + head: buildRootHead, + // Mirrors the redirect rules in `next.config.ts` / `vercel.ts`. Vercel's + // edge layer already handles these for the platform deploy; this is the + // self-hosted (Node-server) fallback and the client-side safety net. + beforeLoad: ({ location }) => { + const match = matchRedirect({ + pathname: location.pathname, + search: location.search as Record, + isPlatform: IS_PLATFORM, + }) + if (!match) return + const href = BASE_PATH ? `${BASE_PATH}${match.destination}` : match.destination + throw redirect({ href, statusCode: match.permanent ? 308 : 307 }) + }, + component: RootComponent, + shellComponent: RootDocument, + notFoundComponent: NotFound, + errorComponent: ErrorBoundaryRoute, +}) + +function RootComponent() { + useThemeSandbox() + + const cloudProvider = useDefaultProvider() + + const getConfigCatFlags = useCallback( + (userEmail?: string) => { + const customAttributes = cloudProvider ? { cloud_provider: cloudProvider } : undefined + return getFlags(userEmail, customAttributes) + }, + [cloudProvider] + ) + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +function RootDocument({ children }: Readonly<{ children: ReactNode }>) { + return ( + // suppressHydrationWarning is for next-themes: it writes data-theme and + // color-scheme onto from localStorage pre-hydration, which the + // prerendered shell can't know. Scoped to this element only. + + + + + + {children} + , + }, + { + name: 'TanStack Query', + render: , + }, + ]} + /> + + + + ) +} diff --git a/apps/studio/routes/_app/account.tsx b/apps/studio/routes/_app/account.tsx new file mode 100644 index 00000000000..ff1ab14c6f2 --- /dev/null +++ b/apps/studio/routes/_app/account.tsx @@ -0,0 +1,23 @@ +import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router' + +import AccountLayout from '@/components/layouts/AccountLayout/AccountLayout' + +export const Route = createFileRoute('/_app/account')({ + component: AccountShell, +}) + +function AccountShell() { + // `select` form: only re-render when the selected title changes, not on + // every router tick. See routes/_app.tsx for the full rationale. + const title = useMatches({ + select: (matches) => + (matches[matches.length - 1]?.staticData as { accountLayoutTitle?: string } | undefined) + ?.accountLayoutTitle ?? '', + }) + + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/account/audit.tsx b/apps/studio/routes/_app/account/audit.tsx new file mode 100644 index 00000000000..040c0e546bb --- /dev/null +++ b/apps/studio/routes/_app/account/audit.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' + +import AuditPage from '@/pages/account/audit' + +export const Route = createFileRoute('/_app/account/audit')({ + component: AccountAuditPage, + staticData: { + defaultLayoutHeaderTitle: 'Account', + accountLayoutTitle: 'Audit Logs', + }, +}) + +function AccountAuditPage() { + return +} diff --git a/apps/studio/routes/_app/account/me.tsx b/apps/studio/routes/_app/account/me.tsx new file mode 100644 index 00000000000..56492a64105 --- /dev/null +++ b/apps/studio/routes/_app/account/me.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { IS_PLATFORM } from '@/lib/constants' +import User from '@/pages/account/me' + +export const Route = createFileRoute('/_app/account/me')({ + component: AccountMePage, + staticData: { + defaultLayoutHeaderTitle: IS_PLATFORM ? 'Account' : 'Preferences', + accountLayoutTitle: 'Preferences', + }, +}) + +function AccountMePage() { + return +} diff --git a/apps/studio/routes/_app/account/security.tsx b/apps/studio/routes/_app/account/security.tsx new file mode 100644 index 00000000000..b092ae4a172 --- /dev/null +++ b/apps/studio/routes/_app/account/security.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' + +import SecurityPage from '@/pages/account/security' + +export const Route = createFileRoute('/_app/account/security')({ + component: AccountSecurityPage, + staticData: { + defaultLayoutHeaderTitle: 'Account', + accountLayoutTitle: 'Security', + }, +}) + +function AccountSecurityPage() { + return +} diff --git a/apps/studio/routes/_app/account/tokens/index.tsx b/apps/studio/routes/_app/account/tokens/index.tsx new file mode 100644 index 00000000000..7131adf0f02 --- /dev/null +++ b/apps/studio/routes/_app/account/tokens/index.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' + +import UserAccessTokensPage from '@/pages/account/tokens' + +export const Route = createFileRoute('/_app/account/tokens/')({ + component: AccountTokensPage, + staticData: { + defaultLayoutHeaderTitle: 'Account', + accountLayoutTitle: 'Access Tokens', + }, +}) + +function AccountTokensPage() { + return +} diff --git a/apps/studio/routes/_app/account/tokens/scoped.tsx b/apps/studio/routes/_app/account/tokens/scoped.tsx new file mode 100644 index 00000000000..1d92e92f9f5 --- /dev/null +++ b/apps/studio/routes/_app/account/tokens/scoped.tsx @@ -0,0 +1,15 @@ +import { createFileRoute } from '@tanstack/react-router' + +import ScopedTokensPage from '@/pages/account/tokens/scoped' + +export const Route = createFileRoute('/_app/account/tokens/scoped')({ + component: AccountScopedTokensPage, + staticData: { + defaultLayoutHeaderTitle: 'Account', + accountLayoutTitle: 'Access Tokens', + }, +}) + +function AccountScopedTokensPage() { + return +} diff --git a/apps/studio/routes/_app/new/index.tsx b/apps/studio/routes/_app/new/index.tsx new file mode 100644 index 00000000000..5e6d587e1b7 --- /dev/null +++ b/apps/studio/routes/_app/new/index.tsx @@ -0,0 +1,24 @@ +import { createFileRoute } from '@tanstack/react-router' + +import WizardLayout from '@/components/layouts/WizardLayout' +import Wizard from '@/pages/new/index' + +export const Route = createFileRoute('/_app/new/')({ + component: NewOrgRoute, + staticData: { + hideMobileMenu: true, + defaultLayoutHeaderTitle: 'New organization', + }, +}) + +// Inline WizardLayout here rather than via a `_app/new.tsx` sub-shell — +// `_app/new/$slug.tsx` is not under _app (no AppLayout in Next), and the +// only other potential leaf has a different inner wrapper (PageLayout), +// so a shared shell would not actually share anything. +function NewOrgRoute() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org.tsx b/apps/studio/routes/_app/org.tsx new file mode 100644 index 00000000000..ff366ba2816 --- /dev/null +++ b/apps/studio/routes/_app/org.tsx @@ -0,0 +1,23 @@ +import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router' + +import OrganizationLayout from '@/components/layouts/OrganizationLayout' + +export const Route = createFileRoute('/_app/org')({ + component: OrgShell, +}) + +function OrgShell() { + // `select` form: only re-render when the selected title changes, not on + // every router tick. See routes/_app.tsx for the full rationale. + const title = useMatches({ + select: (matches) => + (matches[matches.length - 1]?.staticData as { orgLayoutTitle?: string } | undefined) + ?.orgLayoutTitle ?? '', + }) + + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/apps.tsx b/apps/studio/routes/_app/org/$slug/apps.tsx new file mode 100644 index 00000000000..4acafb2add3 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/apps.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgOAuthAppsPage from '@/pages/org/[slug]/apps' + +export const Route = createFileRoute('/_app/org/$slug/apps')({ + component: OrgApps, + staticData: { + orgLayoutTitle: 'OAuth Apps', + }, +}) + +function OrgApps() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/audit-log-drains.tsx b/apps/studio/routes/_app/org/$slug/audit-log-drains.tsx new file mode 100644 index 00000000000..534ef513cb7 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/audit-log-drains.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgAuditLogDrainsPage from '@/pages/org/[slug]/audit-log-drains' + +export const Route = createFileRoute('/_app/org/$slug/audit-log-drains')({ + component: OrgAuditLogDrains, + staticData: { + orgLayoutTitle: 'Audit Log Drains', + }, +}) + +function OrgAuditLogDrains() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/audit.tsx b/apps/studio/routes/_app/org/$slug/audit.tsx new file mode 100644 index 00000000000..786a7740f98 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/audit.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgAuditLogsPage from '@/pages/org/[slug]/audit' + +export const Route = createFileRoute('/_app/org/$slug/audit')({ + component: OrgAudit, + staticData: { + orgLayoutTitle: 'Audit Logs', + }, +}) + +function OrgAudit() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/billing.tsx b/apps/studio/routes/_app/org/$slug/billing.tsx new file mode 100644 index 00000000000..a1e3254ac1f --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/billing.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import OrgBillingSettingsPage from '@/pages/org/[slug]/billing' + +export const Route = createFileRoute('/_app/org/$slug/billing')({ + component: OrgBilling, + staticData: { + orgLayoutTitle: 'Billing', + }, +}) + +function OrgBilling() { + return +} diff --git a/apps/studio/routes/_app/org/$slug/documents.tsx b/apps/studio/routes/_app/org/$slug/documents.tsx new file mode 100644 index 00000000000..4b9343ae86f --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/documents.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgDocumentsPage from '@/pages/org/[slug]/documents' + +export const Route = createFileRoute('/_app/org/$slug/documents')({ + component: OrgDocuments, + staticData: { + orgLayoutTitle: 'Legal Documents', + }, +}) + +function OrgDocuments() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/general.tsx b/apps/studio/routes/_app/org/$slug/general.tsx new file mode 100644 index 00000000000..ebc9452db03 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/general.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgGeneralSettingsPage from '@/pages/org/[slug]/general' + +export const Route = createFileRoute('/_app/org/$slug/general')({ + component: OrgGeneral, + staticData: { + orgLayoutTitle: 'General', + }, +}) + +function OrgGeneral() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/index.tsx b/apps/studio/routes/_app/org/$slug/index.tsx new file mode 100644 index 00000000000..d618ab94fee --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' +import ProjectsPage from '@/pages/org/[slug]/index' + +export const Route = createFileRoute('/_app/org/$slug/')({ + component: OrgSlugIndex, + staticData: { + orgLayoutTitle: 'Projects', + }, +}) + +function OrgSlugIndex() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/integrations.tsx b/apps/studio/routes/_app/org/$slug/integrations.tsx new file mode 100644 index 00000000000..bd382bf95b6 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/integrations.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import OrgIntegrationSettingsPage from '@/pages/org/[slug]/integrations' + +export const Route = createFileRoute('/_app/org/$slug/integrations')({ + component: OrgIntegrations, + staticData: { + orgLayoutTitle: 'Integrations', + }, +}) + +function OrgIntegrations() { + return +} diff --git a/apps/studio/routes/_app/org/$slug/private-apps/index.tsx b/apps/studio/routes/_app/org/$slug/private-apps/index.tsx new file mode 100644 index 00000000000..34c8c811396 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/private-apps/index.tsx @@ -0,0 +1,22 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { PrivateAppsProvider } from '@/components/interfaces/Organization/PrivateApps/PrivateAppsContext' +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import PrivateAppsPage from '@/pages/org/[slug]/private-apps/index' + +export const Route = createFileRoute('/_app/org/$slug/private-apps/')({ + component: OrgPrivateApps, + staticData: { + orgLayoutTitle: 'Private Apps', + }, +}) + +function OrgPrivateApps() { + return ( + + + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/security.tsx b/apps/studio/routes/_app/org/$slug/security.tsx new file mode 100644 index 00000000000..863dc372067 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/security.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgSecuritySettingsPage from '@/pages/org/[slug]/security' + +export const Route = createFileRoute('/_app/org/$slug/security')({ + component: OrgSecurity, + staticData: { + orgLayoutTitle: 'Security', + }, +}) + +function OrgSecurity() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/sso.tsx b/apps/studio/routes/_app/org/$slug/sso.tsx new file mode 100644 index 00000000000..d70ce69f85d --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/sso.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgSSOPage from '@/pages/org/[slug]/sso' + +export const Route = createFileRoute('/_app/org/$slug/sso')({ + component: OrgSSO, + staticData: { + orgLayoutTitle: 'SSO', + }, +}) + +function OrgSSO() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/team.tsx b/apps/studio/routes/_app/org/$slug/team.tsx new file mode 100644 index 00000000000..7866d6defcb --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/team.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import OrgTeamSettingsPage from '@/pages/org/[slug]/team' + +export const Route = createFileRoute('/_app/org/$slug/team')({ + component: OrgTeam, + staticData: { + orgLayoutTitle: 'Team', + }, +}) + +function OrgTeam() { + return +} diff --git a/apps/studio/routes/_app/org/$slug/usage.tsx b/apps/studio/routes/_app/org/$slug/usage.tsx new file mode 100644 index 00000000000..5dd888c8b0c --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/usage.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import OrgUsagePage from '@/pages/org/[slug]/usage' + +export const Route = createFileRoute('/_app/org/$slug/usage')({ + component: OrgUsage, + staticData: { + orgLayoutTitle: 'Usage', + }, +}) + +function OrgUsage() { + return +} diff --git a/apps/studio/routes/_app/org/$slug/webhooks/$endpointId.tsx b/apps/studio/routes/_app/org/$slug/webhooks/$endpointId.tsx new file mode 100644 index 00000000000..daab53c0839 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/webhooks/$endpointId.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgWebhookEndpointSettingsPage from '@/pages/org/[slug]/webhooks/[endpointId]' + +export const Route = createFileRoute('/_app/org/$slug/webhooks/$endpointId')({ + component: OrgWebhookEndpoint, + staticData: { + orgLayoutTitle: 'Webhooks', + }, +}) + +function OrgWebhookEndpoint() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/$slug/webhooks/index.tsx b/apps/studio/routes/_app/org/$slug/webhooks/index.tsx new file mode 100644 index 00000000000..2c119cc6058 --- /dev/null +++ b/apps/studio/routes/_app/org/$slug/webhooks/index.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { OrganizationSettingsLayout } from '@/components/layouts/ProjectLayout/OrganizationSettingsLayout' +import OrgWebhooksSettingsPage from '@/pages/org/[slug]/webhooks/index' + +export const Route = createFileRoute('/_app/org/$slug/webhooks/')({ + component: OrgWebhooks, + staticData: { + orgLayoutTitle: 'Webhooks', + }, +}) + +function OrgWebhooks() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/org/index.tsx b/apps/studio/routes/_app/org/index.tsx new file mode 100644 index 00000000000..117db478376 --- /dev/null +++ b/apps/studio/routes/_app/org/index.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import OrgIndexPage from '@/pages/org/index' + +export const Route = createFileRoute('/_app/org/')({ + component: OrgIndex, + staticData: { + orgLayoutTitle: 'Organizations', + }, +}) + +function OrgIndex() { + return +} diff --git a/apps/studio/routes/_app/organizations.tsx b/apps/studio/routes/_app/organizations.tsx new file mode 100644 index 00000000000..117da507ee7 --- /dev/null +++ b/apps/studio/routes/_app/organizations.tsx @@ -0,0 +1,21 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { PageLayout } from '@/components/layouts/PageLayout/PageLayout' +import OrganizationsPage from '@/pages/organizations' + +export const Route = createFileRoute('/_app/organizations')({ + component: Organizations, + staticData: { + defaultLayoutHeaderTitle: 'Organizations', + hideMobileMenu: true, + }, +}) + +function Organizations() { + // Next page default export is already wrapped in withAuth. + return ( + + + + ) +} diff --git a/apps/studio/routes/_app/support/link.tsx b/apps/studio/routes/_app/support/link.tsx new file mode 100644 index 00000000000..c6c16d6e4a7 --- /dev/null +++ b/apps/studio/routes/_app/support/link.tsx @@ -0,0 +1,11 @@ +import { createFileRoute } from '@tanstack/react-router' + +import LinkSupportTicketPageRoute from '@/pages/support/link' + +export const Route = createFileRoute('/_app/support/link')({ + component: SupportLinkRoute, +}) + +function SupportLinkRoute() { + return +} diff --git a/apps/studio/routes/_app/support/new.tsx b/apps/studio/routes/_app/support/new.tsx new file mode 100644 index 00000000000..494e6200c9f --- /dev/null +++ b/apps/studio/routes/_app/support/new.tsx @@ -0,0 +1,14 @@ +import { createFileRoute } from '@tanstack/react-router' + +import SupportPage from '@/pages/support/new' + +export const Route = createFileRoute('/_app/support/new')({ + component: SupportNewRoute, + // _app shell reads hideMobileMenu from leaf staticData and forwards it to + // DefaultLayout. The Next-side getLayout sets hideMobileMenu on DefaultLayout. + staticData: { hideMobileMenu: true }, +}) + +function SupportNewRoute() { + return +} diff --git a/apps/studio/routes/_auth/cli/login.tsx b/apps/studio/routes/_auth/cli/login.tsx new file mode 100644 index 00000000000..ea50cb77a3f --- /dev/null +++ b/apps/studio/routes/_auth/cli/login.tsx @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +import CliLoginPage from '@/pages/cli/login' + +export const Route = createFileRoute('/_auth/cli/login')({ + component: CliLogin, +}) + +function CliLogin() { + // Next page default export is already wrapped in withAuth and inlines APIAuthorizationLayout. + return +} diff --git a/apps/studio/routes/_auth/forgot-password-mfa.tsx b/apps/studio/routes/_auth/forgot-password-mfa.tsx new file mode 100644 index 00000000000..7cf36e9e381 --- /dev/null +++ b/apps/studio/routes/_auth/forgot-password-mfa.tsx @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +import ForgotPasswordMfaPage from '@/pages/forgot-password-mfa' + +export const Route = createFileRoute('/_auth/forgot-password-mfa')({ + component: ForgotPasswordMfa, +}) + +function ForgotPasswordMfa() { + // The Next page inlines its own ForgotPasswordLayout; just render it. + return +} diff --git a/apps/studio/routes/_auth/forgot-password.tsx b/apps/studio/routes/_auth/forgot-password.tsx new file mode 100644 index 00000000000..4bc89db15d7 --- /dev/null +++ b/apps/studio/routes/_auth/forgot-password.tsx @@ -0,0 +1,19 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { ForgotPasswordLayout } from '@/components/layouts/SignInLayout/ForgotPasswordLayout' +import ForgotPasswordPage from '@/pages/forgot-password' + +export const Route = createFileRoute('/_auth/forgot-password')({ + component: ForgotPassword, +}) + +function ForgotPassword() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_auth/partners/stripe/projects/login.tsx b/apps/studio/routes/_auth/partners/stripe/projects/login.tsx new file mode 100644 index 00000000000..642d4438277 --- /dev/null +++ b/apps/studio/routes/_auth/partners/stripe/projects/login.tsx @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +import StripeProjectsLoginPage from '@/pages/partners/stripe/projects/login' + +export const Route = createFileRoute('/_auth/partners/stripe/projects/login')({ + component: StripeProjectsLogin, +}) + +function StripeProjectsLogin() { + // Next page default export is already wrapped in withAuth and inlines APIAuthorizationLayout. + return +} diff --git a/apps/studio/routes/_auth/reset-password.tsx b/apps/studio/routes/_auth/reset-password.tsx new file mode 100644 index 00000000000..68f51f339b3 --- /dev/null +++ b/apps/studio/routes/_auth/reset-password.tsx @@ -0,0 +1,20 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { ForgotPasswordLayout } from '@/components/layouts/SignInLayout/ForgotPasswordLayout' +import ResetPasswordPage from '@/pages/reset-password' + +export const Route = createFileRoute('/_auth/reset-password')({ + component: ResetPassword, +}) + +function ResetPassword() { + // Next page default export is already wrapped in withAuth — session guard runs inline. + return ( + + + + ) +} diff --git a/apps/studio/routes/_auth/sign-in-mfa.tsx b/apps/studio/routes/_auth/sign-in-mfa.tsx new file mode 100644 index 00000000000..b6d80932270 --- /dev/null +++ b/apps/studio/routes/_auth/sign-in-mfa.tsx @@ -0,0 +1,12 @@ +import { createFileRoute } from '@tanstack/react-router' + +import SignInMfaPage from '@/pages/sign-in-mfa' + +export const Route = createFileRoute('/_auth/sign-in-mfa')({ + component: SignInMfa, +}) + +function SignInMfa() { + // The Next page inlines its own SignInLayout; just render it. + return +} diff --git a/apps/studio/routes/_auth/sign-in-partner.tsx b/apps/studio/routes/_auth/sign-in-partner.tsx new file mode 100644 index 00000000000..e3f41dfaaaa --- /dev/null +++ b/apps/studio/routes/_auth/sign-in-partner.tsx @@ -0,0 +1,25 @@ +import { createFileRoute } from '@tanstack/react-router' +import { cn } from 'ui' + +import { ForgotPasswordLayout } from '@/components/layouts/SignInLayout/ForgotPasswordLayout' +import SignInPartnerPage from '@/pages/sign-in-partner' + +export const Route = createFileRoute('/_auth/sign-in-partner')({ + component: SignInPartner, +}) + +function SignInPartner() { + return ( + // [Joshen] Just using this layout for the styling + div:first-child]:absolute [&>div:first-child]:!px-0', + '[&>div:last-child]:flex-grow [&>div:last-child>main]:!px-0' + )} + > + + + ) +} diff --git a/apps/studio/routes/_auth/sign-in-sso.tsx b/apps/studio/routes/_auth/sign-in-sso.tsx new file mode 100644 index 00000000000..800e7e8e957 --- /dev/null +++ b/apps/studio/routes/_auth/sign-in-sso.tsx @@ -0,0 +1,20 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { SignInLayout } from '@/components/layouts/SignInLayout/SignInLayout' +import SignInSSOPage from '@/pages/sign-in-sso' + +export const Route = createFileRoute('/_auth/sign-in-sso')({ + component: SignInSSO, +}) + +function SignInSSO() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_auth/sign-in.tsx b/apps/studio/routes/_auth/sign-in.tsx new file mode 100644 index 00000000000..6a1da95e7df --- /dev/null +++ b/apps/studio/routes/_auth/sign-in.tsx @@ -0,0 +1,21 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { SignInLayout } from '@/components/layouts/SignInLayout/SignInLayout' +import SignInPage from '@/pages/sign-in' + +export const Route = createFileRoute('/_auth/sign-in')({ + component: SignIn, +}) + +function SignIn() { + return ( + + + + ) +} diff --git a/apps/studio/routes/_auth/sign-up.tsx b/apps/studio/routes/_auth/sign-up.tsx new file mode 100644 index 00000000000..568fd41c489 --- /dev/null +++ b/apps/studio/routes/_auth/sign-up.tsx @@ -0,0 +1,16 @@ +import { createFileRoute } from '@tanstack/react-router' + +import { SignInLayout } from '@/components/layouts/SignInLayout/SignInLayout' +import SignUpPage from '@/pages/sign-up' + +export const Route = createFileRoute('/_auth/sign-up')({ + component: SignUp, +}) + +function SignUp() { + return ( + + + + ) +}