feat(studio): TanStack project routes — data surfaces (stack 5.1/6, from #46424) (#47117)

**Stack 5.1/6** of the TanStack Start migration (#46424). The original
S5 (174 files) was over CodeRabbit's 150-file review cap, so it's split
into 5.1 + 5.2 by product. Stacked on **#47113** (S4).

> [!NOTE]
> Thin route wrappers rendering the existing pages-router components via
compat shims. Next-safe (full Next build run). The TanStack app isn't
functional end-to-end until 5.2 + the matrix flip.

## What's in this PR
- **Data-cluster project routes:** database, editor, sql, storage,
realtime, branches.
- **Top-level / onboarding routes:** `authorize`, `join`, `logout`,
`redeem`, `verify-email`, `claim-project`, aws-marketplace,
Vercel/GitHub integration entrypoints; `_app`/`_auth` layout shells;
`/org/_` + `/project/_` catch-alls.
- **Supporting edits:** hoist `BranchesPageWrapper` out of `getLayout`,
`ConnectStepsSection` `import.meta.glob`, `api/server.js`.
- `routeTree.gen.ts` regenerated for the routes present so far.

## Verification
On top of S1–S4: `studio` typecheck ✓, lint (0 errors) ✓, **Next build ✓
(181/181 pages)**.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Refactor**
* Restructured application routing infrastructure for improved code
organization and maintainability.
* Extracted and refactored layout wrapper components for enhanced
reusability across different sections of the application.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-07-03 21:22:06 +08:00
1 parent 833d3cb1d7
commit 7d3f72ec7d
80 files changed
+3112 -153

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
// STUDIO_FRAMEWORK gates whether this function actually serves the TanStack
// SSR handler. Vercel auto-detects every file under /api as a Function
// regardless of the framework preset (vercel.com/docs/functions), so we
// can't keep this file from being deployed in the Next.js prod build — we
// just make it inert when the env var is unset.
const isTanstack = process.env.STUDIO_FRAMEWORK === 'tanstack'
// Computed path keeps `dist/server/server.js` out of Vercel's function
// bundler's static analysis. In the Next.js prod deploy the `dist/` tree
// doesn't exist, but Vercel still bundles this file because it lives under
// `api/`. With the .join() the bundler treats the import as runtime-only
// and the missing dist/ isn't a build error. In TanStack mode the SSR
// bundle is shipped into the function via the `functions['api/server.js']
// .includeFiles` config in vercel.ts.
const tanstackEntry = ['..', 'dist', 'server', 'server.js'].join('/')
const handler = isTanstack
? (await import(tanstackEntry)).default
: { fetch: () => new Response('Not Found', { status: 404 }) }
// Vercel's Web API handler convention: export an object with `fetch(request)`.
// TanStack's server build is already shaped that way — default-export it
// verbatim and Vercel hands us a real Web Request.
// eslint-disable-next-line no-restricted-exports
export default handler
@@ -1,12 +1,11 @@
import { useParams } from 'common'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { useMemo, useRef } from 'react'
import { useMemo, useRef, type ComponentType } from 'react'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
import { connectSchema } from './connect.schema'
import type {
ConnectionStringPooler,
ConnectState,
@@ -129,6 +128,27 @@ function useConnectionStringPooler(deploymentMode: DeploymentMode): ConnectionSt
)
}
// Vite needs `import.meta.glob` to statically discover the step content
// modules because the `${filePath}` template can span multiple directory
// segments (`flask/supabasepy`, `steps/shadcn/explore`, ...) which Vite's
// dynamic-import-vars plugin can't analyze. Skip the glob on the SSR bundle
// — Vite replaces `import.meta.env.SSR` at build time and tree-shakes the
// call so the 37 content modules stay out of the server graph (pulling them
// in reshuffles chunks enough to surface latent circular-dep bugs in
// unrelated modules). Next/webpack doesn't know about `import.meta.glob`
// either; the try/catch lets that branch fall through to the webpack-friendly
// `import()` below.
let contentModules: Record<string, () => Promise<unknown>> = {}
if (!import.meta.env?.SSR) {
try {
contentModules = import.meta.glob('./content/**/content.{tsx,ts}')
} catch {
// webpack build: import.meta.glob is undefined, keep empty map
}
}
type StepContentModule = { default: ComponentType<StepContentProps> }
/**
* Dynamically loads and renders a content component from the content directory.
* All step content uses this unified loader - no built-in component registry needed.
@@ -151,7 +171,16 @@ function StepContent({
// Dynamically import the content component
const ContentComponent = useMemo(() => {
return dynamic<StepContentProps>(() => import(`./content/${filePath}/content`), {
const viteLoader =
contentModules[`./content/${filePath}/content.tsx`] ??
contentModules[`./content/${filePath}/content.ts`]
const loader = viteLoader
? (viteLoader as () => Promise<StepContentModule>)
: () =>
import(/* @vite-ignore */ `./content/${filePath}/content`) as Promise<StepContentModule>
return dynamic<StepContentProps>(loader, {
loading: () => (
<div className="p-4 min-h-[200px]">
<GenericSkeletonLoader />
@@ -196,11 +225,6 @@ export function ConnectStepsSection({ steps, state, projectKeys }: ConnectStepsS
const showSelfHostedMcpNotice = deploymentMode.isSelfHosted && state.mode === 'mcp'
const customPrompt = useMemo(
() => connectSchema.modes.find((m) => m.id === state.mode)?.prompt,
[state.mode]
)
if (steps.length === 0) return null
return (
@@ -244,7 +268,7 @@ export function ConnectStepsSection({ steps, state, projectKeys }: ConnectStepsS
/>
)}
<CopyPromptAdmonition stepsContainerRef={stepsContainerRef} customPrompt={customPrompt} />
<CopyPromptAdmonition stepsContainerRef={stepsContainerRef} />
<div className="mt-6" ref={stepsContainerRef}>
{steps.map((step, index) => (
+5 -1
View File
@@ -3,6 +3,7 @@ import Head from 'next/head'
import { useRouter } from 'next/router'
import { cn } from 'ui'
import { parseCatchAllRoute } from '@/compat/next/router'
import {
Header,
LoadingCardView,
@@ -20,7 +21,10 @@ import { buildStudioPageTitle } from '@/lib/page-title'
const GenericOrganizationPage: NextPage = () => {
const router = useRouter()
const { appTitle } = useCustomContent(['app:title'])
const { routeSlug, ...queryParams } = router.query
// Normalise the catch-all path across Next (`routeSlug: string[]`) and
// TanStack (`_splat: string`) so downstream URL building (buildOrgUrl)
// keeps working — see parseCatchAllRoute.
const { segments: routeSlug, queryParams } = parseCatchAllRoute(router.query, 'routeSlug')
const queryString =
Object.keys(queryParams).length > 0
? new URLSearchParams(queryParams as Record<string, string>).toString()
@@ -3,7 +3,7 @@ import { useParams } from 'common'
import { partition } from 'lodash'
import { MessageCircle } from 'lucide-react'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useState, type PropsWithChildren } from 'react'
import { toast } from 'sonner'
import { Button } from 'ui'
@@ -189,73 +189,74 @@ const BranchesPage: NextPageWithLayout = () => {
)
}
BranchesPage.getLayout = (page) => {
const BranchesPageWrapper = () => {
const snap = useAppStateSnapshot()
const { can: canCreateBranches } = useAsyncCheckPermissions(
PermissionAction.CREATE,
'preview_branches',
{
resource: { is_default: false },
}
)
// Hoisted out of `getLayout` so the TanStack route can import it
// directly. Same shape and identical body as before — accepts the page
// content as `children` instead of capturing it from a closure.
export const BranchesPageWrapper = ({ children }: PropsWithChildren) => {
const snap = useAppStateSnapshot()
const { can: canCreateBranches } = useAsyncCheckPermissions(
PermissionAction.CREATE,
'preview_branches',
{
resource: { is_default: false },
}
)
const primaryActions = (
<ButtonTooltip
variant="primary"
disabled={!canCreateBranches}
onClick={() => snap.setShowCreateBranchModal(true)}
tooltip={{
content: {
side: 'bottom',
text: !canCreateBranches
? 'You need additional permissions to create branches'
: undefined,
},
}}
const primaryActions = (
<ButtonTooltip
variant="primary"
disabled={!canCreateBranches}
onClick={() => snap.setShowCreateBranchModal(true)}
tooltip={{
content: {
side: 'bottom',
text: !canCreateBranches
? 'You need additional permissions to create branches'
: undefined,
},
}}
>
Create branch
</ButtonTooltip>
)
const secondaryActions = (
<div className="flex items-center gap-x-2">
<Button
asChild
variant="text"
icon={<MessageCircle className="text-muted" strokeWidth={1} />}
>
Create branch
</ButtonTooltip>
)
const secondaryActions = (
<div className="flex items-center gap-x-2">
<Button
asChild
variant="text"
icon={<MessageCircle className="text-muted" strokeWidth={1} />}
<a
target="_blank"
rel="noreferrer"
href="https://github.com/orgs/supabase/discussions/18937"
>
<a
target="_blank"
rel="noreferrer"
href="https://github.com/orgs/supabase/discussions/18937"
>
Branching feedback
</a>
</Button>
<DocsButton href={`${DOCS_URL}/guides/platform/branching`} />
</div>
)
return (
<PageLayout
title="Branches"
subtitle="Manage your database preview branches and deployments"
primaryActions={primaryActions}
secondaryActions={secondaryActions}
>
{page}
</PageLayout>
)
}
Branching feedback
</a>
</Button>
<DocsButton href={`${DOCS_URL}/guides/platform/branching`} />
</div>
)
return (
<DefaultLayout>
<BranchLayout>
<BranchesPageWrapper />
</BranchLayout>
</DefaultLayout>
<PageLayout
title="Branches"
subtitle="Manage your database preview branches and deployments"
primaryActions={primaryActions}
secondaryActions={secondaryActions}
>
{children}
</PageLayout>
)
}
BranchesPage.getLayout = (page) => (
<DefaultLayout>
<BranchLayout>
<BranchesPageWrapper>{page}</BranchesPageWrapper>
</BranchLayout>
</DefaultLayout>
)
export default BranchesPage
@@ -300,7 +300,7 @@ const MergeRequestsPage: NextPageWithLayout = () => {
)
}
const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
export const MergeRequestsPageWrapper = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
@@ -15,6 +15,7 @@ import {
} from 'ui'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { parseCatchAllRoute } from '@/compat/next/router'
import {
Header,
LoadingCardView,
@@ -35,7 +36,10 @@ import { withAuth } from '@/hooks/misc/withAuth'
const GenericProjectPage: NextPage = () => {
const router = useRouter()
const { slug } = useParams()
const { routeSlug, ...queryParams } = router.query
// Normalise the catch-all path across Next (`routeSlug: string[]`) and
// TanStack (`_splat: string`) so downstream URL building
// (urlRewriterFactory) keeps working — see parseCatchAllRoute.
const { segments: routeSlug, queryParams } = parseCatchAllRoute(router.query, 'routeSlug')
const { lastVisitedOrganization } = useLastVisitedOrganization()
File diff suppressed because it is too large. Load diff
+33
View File
@@ -0,0 +1,33 @@
import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router'
import { AppLayout } from '@/components/layouts/AppLayout/AppLayout'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
export const Route = createFileRoute('/_app')({
component: AppShell,
})
function AppShell() {
// useMatches() without `select` returns a fresh array on every router
// tick — even when the matched leaf hasn't changed — and re-renders the
// entire shell subtree. Use the `select` form so we only re-render when
// the values we actually consume change.
const headerTitle = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as { defaultLayoutHeaderTitle?: string } | undefined)
?.defaultLayoutHeaderTitle,
})
const hideMobileMenu = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as { hideMobileMenu?: boolean } | undefined)
?.hideMobileMenu,
})
return (
<AppLayout>
<DefaultLayout headerTitle={headerTitle} hideMobileMenu={hideMobileMenu}>
<Outlet />
</DefaultLayout>
</AppLayout>
)
}
+15
View File
@@ -0,0 +1,15 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { AuthenticationLayout } from '@/components/layouts/AuthenticationLayout'
export const Route = createFileRoute('/_auth')({
component: AuthShell,
})
function AuthShell() {
return (
<AuthenticationLayout>
<Outlet />
</AuthenticationLayout>
)
}
+11
View File
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import APIAuthorizationPage from '@/pages/authorize'
export const Route = createFileRoute('/authorize')({
component: Authorize,
})
function Authorize() {
return <APIAuthorizationPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import AwsMarketplaceOnboardingPage from '@/pages/aws-marketplace-onboarding'
export const Route = createFileRoute('/aws-marketplace-onboarding')({
component: AwsMarketplaceOnboardingRoute,
})
function AwsMarketplaceOnboardingRoute() {
return <AwsMarketplaceOnboardingPage dehydratedState={undefined} />
}
+24
View File
@@ -0,0 +1,24 @@
import { createFileRoute } from '@tanstack/react-router'
import Head from 'next/head'
import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
import ClaimProjectPage from '@/pages/claim-project'
export const Route = createFileRoute('/claim-project')({
component: ClaimProject,
})
function ClaimProject() {
const { appTitle } = useCustomContent(['app:title'])
return (
<>
<Head>
<title>{`Claim project | ${appTitle ?? 'Supabase'}`}</title>
</Head>
<main className="flex flex-col w-full min-h-screen overflow-y-auto">
<ClaimProjectPage dehydratedState={undefined} />
</main>
</>
)
}
+24
View File
@@ -0,0 +1,24 @@
import { createFileRoute, redirect } from '@tanstack/react-router'
import { IS_PLATFORM } from '@/lib/constants'
// `/` is never rendered — it always redirects. Mirrors the Next.js
// `redirects()` rules in next.config.ts: platform sends users to `/org`
// (or `/new/new-project` when deep-linked with `?next=new-project`),
// self-hosted sends them straight to `/project/default`.
export const Route = createFileRoute('/')({
validateSearch: (search: Record<string, unknown>) => ({
next: typeof search.next === 'string' ? search.next : undefined,
}),
beforeLoad: ({ search }) => {
// `href` instead of `to` because these targets aren't in the TanStack
// routeTree yet — they're still on the Next.js pages side during the
// migration. Swap to `to` once `/org`, `/new/new-project`, and
// `/project/default` are migrated.
if (IS_PLATFORM) {
if (search.next === 'new-project') throw redirect({ href: '/new/new-project' })
throw redirect({ href: '/org' })
}
throw redirect({ href: '/project/default' })
},
})
@@ -0,0 +1,13 @@
import { createFileRoute } from '@tanstack/react-router'
import GitHubIntegrationAuthorize from '@/pages/integrations/github/authorize'
export const Route = createFileRoute('/integrations/github/authorize')({
component: GitHubAuthorizeRoute,
})
// Placed at top-level — the Next page has no getLayout (renders bare),
// so adding AppLayout/DefaultLayout via _app would be a behaviour change.
function GitHubAuthorizeRoute() {
return <GitHubIntegrationAuthorize />
}
@@ -0,0 +1,19 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import VercelIntegrationWindowLayout from '@/components/layouts/IntegrationsLayout/VercelIntegrationWindowLayout'
export const Route = createFileRoute('/integrations/vercel')({
component: VercelIntegrationShell,
})
// Placed at top-level rather than under _app — Next getLayout for all
// three leaves only wraps in VercelIntegrationWindowLayout (no AppLayout
// or DefaultLayout), so adding _app's chrome here would be a behaviour
// change.
function VercelIntegrationShell() {
return (
<VercelIntegrationWindowLayout>
<Outlet />
</VercelIntegrationWindowLayout>
)
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import VercelIntegration from '@/pages/integrations/vercel/[slug]/deploy-button/new-project'
export const Route = createFileRoute('/integrations/vercel/$slug/deploy-button/new-project')({
component: VercelDeployButtonNewProjectRoute,
})
function VercelDeployButtonNewProjectRoute() {
return <VercelIntegration dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import VercelIntegration from '@/pages/integrations/vercel/[slug]/marketplace/choose-project'
export const Route = createFileRoute('/integrations/vercel/$slug/marketplace/choose-project')({
component: VercelChooseProjectRoute,
})
function VercelChooseProjectRoute() {
return <VercelIntegration dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import VercelIntegration from '@/pages/integrations/vercel/install'
export const Route = createFileRoute('/integrations/vercel/install')({
component: VercelInstallRoute,
})
function VercelInstallRoute() {
return <VercelIntegration dehydratedState={undefined} />
}
+22
View File
@@ -0,0 +1,22 @@
import { createFileRoute } from '@tanstack/react-router'
import { cn } from 'ui'
import JoinOrganizationPage from '@/pages/join'
export const Route = createFileRoute('/join')({
component: Join,
})
function Join() {
return (
<div
className={cn(
'flex h-full min-h-screen bg-studio',
'w-full flex-col place-items-center',
'items-center justify-center gap-8 px-5'
)}
>
<JoinOrganizationPage dehydratedState={undefined} />
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import LogoutPage from '@/pages/logout'
export const Route = createFileRoute('/logout')({
component: Logout,
})
function Logout() {
return <LogoutPage dehydratedState={undefined} />
}
+22
View File
@@ -0,0 +1,22 @@
import { createFileRoute } from '@tanstack/react-router'
import { cn } from 'ui'
import MaintenancePage from '@/pages/maintenance'
export const Route = createFileRoute('/maintenance')({
component: Maintenance,
})
function Maintenance() {
return (
<div
className={cn(
'flex h-full min-h-screen bg-studio',
'w-full flex-col place-items-center',
'items-center justify-center gap-8 px-5'
)}
>
<MaintenancePage dehydratedState={undefined} />
</div>
)
}
+21
View File
@@ -0,0 +1,21 @@
import { createFileRoute } from '@tanstack/react-router'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
import Wizard from '@/pages/new/[slug]'
export const Route = createFileRoute('/new/$slug')({
component: NewProjectRoute,
})
// Placed at top-level rather than under _app — the Next getLayout omits
// AppLayout, and uses PageLayout (not WizardLayout) inside DefaultLayout.
function NewProjectRoute() {
return (
<DefaultLayout hideMobileMenu headerTitle="New project">
<PageLayout>
<Wizard dehydratedState={undefined} />
</PageLayout>
</DefaultLayout>
)
}
+20
View File
@@ -0,0 +1,20 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericOrganizationPage from '@/pages/org/_/[[...routeSlug]]'
export const Route = createFileRoute('/org/_/$')({
component: OrgWildcardSplatRoute,
})
// Matches `/org/_/...`. TanStack exposes the trailing path as the
// `_splat` param; the underlying Next page normalises it back to the
// `routeSlug: string[]` shape it originally consumed.
//
// Naming delta: `org.[_].$.tsx` (path-as-filename) rather than
// `org/[_]/$.tsx` (nested directory) — see `org.[_].tsx` for the full
// rationale; in short, the directory + index.tsx form trips a
// router-generator bug that strips the escaped `_` segment when the
// last segment isn't escaped.
function OrgWildcardSplatRoute() {
return <GenericOrganizationPage />
}
+23
View File
@@ -0,0 +1,23 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericOrganizationPage from '@/pages/org/_/[[...routeSlug]]'
export const Route = createFileRoute('/org/_')({
component: OrgWildcardIndexRoute,
})
// Matches `/org/_` exactly. The companion splat route at
// `org.[_].$.tsx` handles `/org/_/...`.
//
// `[_]` is TanStack's escape syntax for a literal underscore in the URL
// (otherwise an unescaped `_` segment is treated as a pathless layout).
//
// Naming delta: this is `org.[_].tsx`, not `org/[_]/index.tsx`, to side-
// step a router-generator bug — when an index.tsx's path has a bracket-
// escaped *parent* segment, `originalRoutePath` gets wiped wholesale and
// the escape info is lost (getRouteNodes.js:132 sets originalRoutePath
// to `/`). The path-as-filename form keeps the last segment non-index,
// so the bug branch is skipped.
function OrgWildcardIndexRoute() {
return <GenericOrganizationPage />
}
+20
View File
@@ -0,0 +1,20 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericProjectPage from '@/pages/project/_/[[...routeSlug]]'
export const Route = createFileRoute('/project/_/$')({
component: ProjectWildcardSplatRoute,
})
// Matches `/project/_/...`. TanStack exposes the trailing path as the
// `_splat` param; the underlying Next page normalises it back to the
// `routeSlug: string[]` shape it originally consumed.
//
// Naming delta: `project.[_].$.tsx` (path-as-filename) rather than
// `project/[_]/$.tsx` (nested directory) — see `org.[_].tsx` for the
// full rationale; in short, the directory + index.tsx form trips a
// router-generator bug that strips the escaped `_` segment when the
// last segment isn't escaped.
function ProjectWildcardSplatRoute() {
return <GenericProjectPage />
}
+15
View File
@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import GenericProjectPage from '@/pages/project/_/[[...routeSlug]]'
export const Route = createFileRoute('/project/_')({
component: ProjectWildcardIndexRoute,
})
// Matches `/project/_` exactly. The companion splat route at
// `project.[_].$.tsx` handles `/project/_/...`. See `org.[_].tsx` for
// why this is named with the path-as-filename form rather than
// `project/[_]/index.tsx`.
function ProjectWildcardIndexRoute() {
return <GenericProjectPage />
}
+15
View File
@@ -0,0 +1,15 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
export const Route = createFileRoute('/project/$ref')({
component: ProjectShell,
})
function ProjectShell() {
return (
<DefaultLayout>
<Outlet />
</DefaultLayout>
)
}
@@ -0,0 +1,20 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import BranchLayout from '@/components/layouts/BranchLayout/BranchLayout'
export const Route = createFileRoute('/project/$ref/branches')({
component: BranchesShell,
})
// BranchLayout sibling-file shell. Each leaf renders its own per-page
// PageLayout (different titles + primary/secondary actions); we keep
// that wrapping in the leaf route files via the page-side
// `*PageWrapper` exports rather than trying to share PageLayout in the
// shell — the shell deliberately stops at BranchLayout.
function BranchesShell() {
return (
<BranchLayout>
<Outlet />
</BranchLayout>
)
}
@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import BranchesPage, { BranchesPageWrapper } from '@/pages/project/[ref]/branches/index'
export const Route = createFileRoute('/project/$ref/branches/')({
component: BranchesIndexRoute,
})
function BranchesIndexRoute() {
return (
<BranchesPageWrapper>
<BranchesPage dehydratedState={undefined} />
</BranchesPageWrapper>
)
}
@@ -0,0 +1,17 @@
import { createFileRoute } from '@tanstack/react-router'
import MergeRequestsPage, {
MergeRequestsPageWrapper,
} from '@/pages/project/[ref]/branches/merge-requests'
export const Route = createFileRoute('/project/$ref/branches/merge-requests')({
component: MergeRequestsRoute,
})
function MergeRequestsRoute() {
return (
<MergeRequestsPageWrapper>
<MergeRequestsPage dehydratedState={undefined} />
</MergeRequestsPageWrapper>
)
}
@@ -0,0 +1,36 @@
import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router'
import DatabaseLayout from '@/components/layouts/DatabaseLayout/DatabaseLayout'
export const Route = createFileRoute('/project/$ref/database')({
component: DatabaseShell,
})
function DatabaseShell() {
// useMatches() returns a fresh array reference on every router tick, which
// re-renders the shell (and its subtree, e.g. DatabaseLayout → ProjectLayout
// → the route content) even when the leaf staticData hasn't changed. The
// `select` form lets TanStack compare the selected value (a string) and
// only re-render when it actually changes — required to keep heavy children
// like ReactFlow stable, otherwise their internal compose-refs chain runs
// setRef → setState every render and trips React's max-update-depth.
const title = useMatches({
select: (matches) => {
// Walk up from the leaf to the nearest match that declares a title.
// Section layouts like `database/triggers.tsx` set `databaseLayoutTitle`
// while their leaf children (`triggers/data`, `triggers/event`, the
// index) don't, so a leaf-only read would drop the title to ''.
for (let i = matches.length - 1; i >= 0; i--) {
const data = matches[i]?.staticData as { databaseLayoutTitle?: string } | undefined
if (typeof data?.databaseLayoutTitle === 'string') return data.databaseLayoutTitle
}
return ''
},
})
return (
<DatabaseLayout title={title}>
<Outlet />
</DatabaseLayout>
)
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabasePhysicalBackups from '@/pages/project/[ref]/database/backups/pitr'
export const Route = createFileRoute('/project/$ref/database/backups/pitr')({
component: DatabaseBackupsPitrRoute,
staticData: {
databaseLayoutTitle: 'Backups',
},
})
function DatabaseBackupsPitrRoute() {
return <DatabasePhysicalBackups dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import RestoreToNewProjectPage from '@/pages/project/[ref]/database/backups/restore-to-new-project'
export const Route = createFileRoute('/project/$ref/database/backups/restore-to-new-project')({
component: DatabaseBackupsRestoreToNewProjectRoute,
staticData: {
databaseLayoutTitle: 'Backups',
},
})
function DatabaseBackupsRestoreToNewProjectRoute() {
return <RestoreToNewProjectPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseScheduledBackups from '@/pages/project/[ref]/database/backups/scheduled'
export const Route = createFileRoute('/project/$ref/database/backups/scheduled')({
component: DatabaseBackupsScheduledRoute,
staticData: {
databaseLayoutTitle: 'Backups',
},
})
function DatabaseBackupsScheduledRoute() {
return <DatabaseScheduledBackups dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import PrivilegesPage from '@/pages/project/[ref]/database/column-privileges'
export const Route = createFileRoute('/project/$ref/database/column-privileges')({
component: DatabaseColumnPrivilegesRoute,
staticData: {
databaseLayoutTitle: 'Column Privileges',
},
})
function DatabaseColumnPrivilegesRoute() {
return <PrivilegesPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseExtensions from '@/pages/project/[ref]/database/extensions'
export const Route = createFileRoute('/project/$ref/database/extensions')({
component: DatabaseExtensionsRoute,
staticData: {
databaseLayoutTitle: 'Extensions',
},
})
function DatabaseExtensionsRoute() {
return <DatabaseExtensions dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseFunctionsPage from '@/pages/project/[ref]/database/functions'
export const Route = createFileRoute('/project/$ref/database/functions')({
component: DatabaseFunctionsRoute,
staticData: {
databaseLayoutTitle: 'Functions',
},
})
function DatabaseFunctionsRoute() {
return <DatabaseFunctionsPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import IndexesPage from '@/pages/project/[ref]/database/indexes'
export const Route = createFileRoute('/project/$ref/database/indexes')({
component: DatabaseIndexesRoute,
staticData: {
databaseLayoutTitle: 'Indexes',
},
})
function DatabaseIndexesRoute() {
return <IndexesPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import MigrationsPage from '@/pages/project/[ref]/database/migrations'
export const Route = createFileRoute('/project/$ref/database/migrations')({
component: DatabaseMigrationsRoute,
staticData: {
databaseLayoutTitle: 'Migrations',
},
})
function DatabaseMigrationsRoute() {
return <MigrationsPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabasePublications from '@/pages/project/[ref]/database/publications/[id]'
export const Route = createFileRoute('/project/$ref/database/publications/$id')({
component: DatabasePublicationDetailRoute,
staticData: {
databaseLayoutTitle: 'Publications',
},
})
function DatabasePublicationDetailRoute() {
return <DatabasePublications dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabasePublications from '@/pages/project/[ref]/database/publications/index'
export const Route = createFileRoute('/project/$ref/database/publications/')({
component: DatabasePublicationsIndexRoute,
staticData: {
databaseLayoutTitle: 'Publications',
},
})
function DatabasePublicationsIndexRoute() {
return <DatabasePublications dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseReplicationPage from '@/pages/project/[ref]/database/replication/[pipelineId]'
export const Route = createFileRoute('/project/$ref/database/replication/$pipelineId')({
component: DatabaseReplicationPipelineRoute,
staticData: {
databaseLayoutTitle: 'Replication',
},
})
function DatabaseReplicationPipelineRoute() {
return <DatabaseReplicationPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseReplicationPage from '@/pages/project/[ref]/database/replication/index'
export const Route = createFileRoute('/project/$ref/database/replication/')({
component: DatabaseReplicationIndexRoute,
staticData: {
databaseLayoutTitle: 'Replication',
},
})
function DatabaseReplicationIndexRoute() {
return <DatabaseReplicationPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseReadReplicaPage from '@/pages/project/[ref]/database/replication/replica/[replicaId]'
export const Route = createFileRoute('/project/$ref/database/replication/replica/$replicaId')({
component: DatabaseReplicationReplicaRoute,
staticData: {
databaseLayoutTitle: 'Replication',
},
})
function DatabaseReplicationReplicaRoute() {
return <DatabaseReadReplicaPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseRoles from '@/pages/project/[ref]/database/roles'
export const Route = createFileRoute('/project/$ref/database/roles')({
component: DatabaseRolesRoute,
staticData: {
databaseLayoutTitle: 'Roles',
},
})
function DatabaseRolesRoute() {
return <DatabaseRoles dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import SchemasPage from '@/pages/project/[ref]/database/schemas'
export const Route = createFileRoute('/project/$ref/database/schemas')({
component: SchemasRoute,
staticData: {
databaseLayoutTitle: 'Schema Visualizer',
},
})
function SchemasRoute() {
return <SchemasPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseSettings from '@/pages/project/[ref]/database/settings'
export const Route = createFileRoute('/project/$ref/database/settings')({
component: DatabaseSettingsRoute,
staticData: {
databaseLayoutTitle: 'Settings',
},
})
function DatabaseSettingsRoute() {
return <DatabaseSettings dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseTables from '@/pages/project/[ref]/database/tables/[id]'
export const Route = createFileRoute('/project/$ref/database/tables/$id')({
component: DatabaseTableDetailRoute,
staticData: {
databaseLayoutTitle: 'Tables',
},
})
function DatabaseTableDetailRoute() {
return <DatabaseTables dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseTables from '@/pages/project/[ref]/database/tables/index'
export const Route = createFileRoute('/project/$ref/database/tables/')({
component: DatabaseTablesIndexRoute,
staticData: {
databaseLayoutTitle: 'Tables',
},
})
function DatabaseTablesIndexRoute() {
return <DatabaseTables dehydratedState={undefined} />
}
@@ -0,0 +1,39 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
import { NoPermission } from '@/components/ui/NoPermission'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
export const Route = createFileRoute('/project/$ref/database/triggers')({
component: TriggersShell,
staticData: {
databaseLayoutTitle: 'Triggers',
},
})
function TriggersShell() {
const { ref } = Route.useParams()
const { can: canReadTriggers, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
PermissionAction.TENANT_SQL_ADMIN_READ,
'triggers'
)
if (isPermissionsLoaded && !canReadTriggers) {
return <NoPermission isFullPage resourceText="view database triggers" />
}
return (
<PageLayout
title="Database Triggers"
subtitle="Execute actions automatically when database events occur"
size="large"
navigationItems={[
{ label: 'Data', href: `/project/${ref}/database/triggers/data` },
{ label: 'Event', href: `/project/${ref}/database/triggers/event` },
]}
>
<Outlet />
</PageLayout>
)
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import TriggersDataPage from '@/pages/project/[ref]/database/triggers/data'
export const Route = createFileRoute('/project/$ref/database/triggers/data')({
component: TriggersDataRoute,
})
function TriggersDataRoute() {
return <TriggersDataPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import TriggersSchemaPage from '@/pages/project/[ref]/database/triggers/event'
export const Route = createFileRoute('/project/$ref/database/triggers/event')({
component: TriggersEventRoute,
})
function TriggersEventRoute() {
return <TriggersSchemaPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseTriggersIndexPage from '@/pages/project/[ref]/database/triggers/index'
export const Route = createFileRoute('/project/$ref/database/triggers/')({
component: DatabaseTriggersIndexRoute,
})
function DatabaseTriggersIndexRoute() {
return <DatabaseTriggersIndexPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import DatabaseEnumeratedTypes from '@/pages/project/[ref]/database/types'
export const Route = createFileRoute('/project/$ref/database/types')({
component: DatabaseTypesRoute,
staticData: {
databaseLayoutTitle: 'Enumerated Types',
},
})
function DatabaseTypesRoute() {
return <DatabaseEnumeratedTypes dehydratedState={undefined} />
}
@@ -0,0 +1,34 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { EditorBaseLayout } from '@/components/layouts/editors/EditorBaseLayout'
import { TableEditorLayout } from '@/components/layouts/TableEditorLayout/TableEditorLayout'
import { TableEditorMenu } from '@/components/layouts/TableEditorLayout/TableEditorMenu'
export const Route = createFileRoute('/project/$ref/editor')({
component: TableEditorShell,
})
// All three editor leaves (index, $id, new) share identical layout
// wrapping in the Next pages router (DefaultLayout > EditorBaseLayout >
// TableEditorLayout). DefaultLayout is provided by the parent
// `routes/project/$ref.tsx` shell, so this file only adds the
// editor-specific layers.
//
// Note on the ProjectLayoutWithAuth chain: EditorBaseLayout always
// wraps in <ProjectLayoutWithAuth>. TableEditorLayout *also* wraps in
// one — but only on its no-permission path; the happy path returns
// `<>{children}<SaveQueueActionBar/></>` so no double-wrap occurs in
// normal use. Mirrors current Next behaviour exactly.
function TableEditorShell() {
return (
<EditorBaseLayout
productMenu={<TableEditorMenu />}
product="Table Editor"
productMenuClassName="overflow-y-hidden"
>
<TableEditorLayout>
<Outlet />
</TableEditorLayout>
</EditorBaseLayout>
)
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import TableEditorPage from '@/pages/project/[ref]/editor/[id]'
export const Route = createFileRoute('/project/$ref/editor/$id')({
component: TableEditorDetailRoute,
})
function TableEditorDetailRoute() {
return <TableEditorPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import TableEditorPage from '@/pages/project/[ref]/editor/index'
export const Route = createFileRoute('/project/$ref/editor/')({
component: TableEditorIndexRoute,
})
function TableEditorIndexRoute() {
return <TableEditorPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import EditorNewPage from '@/pages/project/[ref]/editor/new'
export const Route = createFileRoute('/project/$ref/editor/new')({
component: TableEditorNewRoute,
})
function TableEditorNewRoute() {
return <EditorNewPage dehydratedState={undefined} />
}
+16
View File
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import { ProjectLayoutWithAuth } from '@/components/layouts/ProjectLayout'
import MergePage from '@/pages/project/[ref]/merge'
export const Route = createFileRoute('/project/$ref/merge')({
component: ProjectMergeRoute,
})
function ProjectMergeRoute() {
return (
<ProjectLayoutWithAuth>
<MergePage dehydratedState={undefined} />
</ProjectLayoutWithAuth>
)
}
@@ -0,0 +1,23 @@
import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router'
import RealtimeLayout from '@/components/layouts/RealtimeLayout/RealtimeLayout'
export const Route = createFileRoute('/project/$ref/realtime')({
component: RealtimeShell,
})
function RealtimeShell() {
// `select` form: only re-render when the selected title changes. See
// routes/_app.tsx for the full rationale.
const title = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as { realtimeLayoutTitle?: string } | undefined)
?.realtimeLayoutTitle ?? '',
})
return (
<RealtimeLayout title={title}>
<Outlet />
</RealtimeLayout>
)
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import InspectorPage from '@/pages/project/[ref]/realtime/inspector'
export const Route = createFileRoute('/project/$ref/realtime/inspector')({
component: RealtimeInspectorRoute,
staticData: {
realtimeLayoutTitle: 'Realtime Inspector',
},
})
function RealtimeInspectorRoute() {
return <InspectorPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import RealtimePoliciesPage from '@/pages/project/[ref]/realtime/policies'
export const Route = createFileRoute('/project/$ref/realtime/policies')({
component: RealtimePoliciesRoute,
staticData: {
realtimeLayoutTitle: 'Policies',
},
})
function RealtimePoliciesRoute() {
return <RealtimePoliciesPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import RealtimeSettingsPage from '@/pages/project/[ref]/realtime/settings'
export const Route = createFileRoute('/project/$ref/realtime/settings')({
component: RealtimeSettingsRoute,
staticData: {
realtimeLayoutTitle: 'Realtime Settings',
},
})
function RealtimeSettingsRoute() {
return <RealtimeSettingsPage dehydratedState={undefined} />
}
+29
View File
@@ -0,0 +1,29 @@
import { createFileRoute, Outlet } from '@tanstack/react-router'
import { EditorBaseLayout } from '@/components/layouts/editors/EditorBaseLayout'
import SQLEditorLayout from '@/components/layouts/SQLEditorLayout/SQLEditorLayout'
import { SQLEditorMenu } from '@/components/layouts/SQLEditorLayout/SQLEditorMenu'
export const Route = createFileRoute('/project/$ref/sql')({
component: SQLEditorShell,
})
// Twin of routes/project/$ref/editor.tsx — all four sql/* leaves share
// identical layout wrapping (`<DefaultLayout><EditorBaseLayout productMenu>
// <SQLEditorLayout>{page}</SQLEditorLayout></EditorBaseLayout>`), so the
// shell hardcodes the props with no staticData overrides. DefaultLayout
// is provided by the parent `routes/project/$ref.tsx` shell.
//
// EditorBaseLayout wraps in <ProjectLayoutWithAuth>; SQLEditorLayout adds
// its own `withAuth` HOC but doesn't add another ProjectLayout — so no
// double-render concerns, only doubled auth check (a no-op when already
// authenticated).
function SQLEditorShell() {
return (
<EditorBaseLayout productMenu={<SQLEditorMenu />} product="SQL Editor">
<SQLEditorLayout>
<Outlet />
</SQLEditorLayout>
</EditorBaseLayout>
)
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import SqlEditor from '@/pages/project/[ref]/sql/[id]'
export const Route = createFileRoute('/project/$ref/sql/$id')({
component: SQLEditorDetailRoute,
})
function SQLEditorDetailRoute() {
return <SqlEditor dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import SqlExamples from '@/pages/project/[ref]/sql/examples'
export const Route = createFileRoute('/project/$ref/sql/examples')({
component: SqlExamplesRoute,
})
function SqlExamplesRoute() {
return <SqlExamples dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import SQLEditorIndexPage from '@/pages/project/[ref]/sql/index'
export const Route = createFileRoute('/project/$ref/sql/')({
component: SQLEditorIndexRoute,
})
function SQLEditorIndexRoute() {
return <SQLEditorIndexPage dehydratedState={undefined} />
}
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import SqlTemplates from '@/pages/project/[ref]/sql/templates'
export const Route = createFileRoute('/project/$ref/sql/templates')({
component: SQLTemplatesRoute,
})
function SQLTemplatesRoute() {
return <SqlTemplates dehydratedState={undefined} />
}
@@ -0,0 +1,61 @@
import { createFileRoute, Outlet, useMatches } from '@tanstack/react-router'
import { StorageBucketsLayout } from '@/components/layouts/StorageLayout/StorageBucketsLayout'
import StorageLayout from '@/components/layouts/StorageLayout/StorageLayout'
export const Route = createFileRoute('/project/$ref/storage')({
component: StorageShell,
})
type StorageStaticData = {
storageLayoutTitle?: string
// Most storage leaves wrap in <StorageBucketsLayout> (the bucket-tab
// sub-nav). Bucket detail pages (files/analytics/vectors $bucketId) skip
// it and render a full-bleed bucket UI under StorageLayout only.
skipStorageBucketsLayout?: boolean
// Inner-layout overrides — used by /storage/s3 which passes its own
// title and hides the subtitle. Most leaves leave these undefined.
storageBucketsLayoutTitle?: string
storageBucketsLayoutHideSubtitle?: boolean
}
function StorageShell() {
// `select` form across each field — only re-render when the specific
// value changes. See routes/_app.tsx for the full rationale.
const title = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as StorageStaticData | undefined)
?.storageLayoutTitle ?? '',
})
const skipBuckets = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as StorageStaticData | undefined)
?.skipStorageBucketsLayout ?? false,
})
const bucketsTitle = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as StorageStaticData | undefined)
?.storageBucketsLayoutTitle,
})
const bucketsHideSubtitle = useMatches({
select: (matches) =>
(matches[matches.length - 1]?.staticData as StorageStaticData | undefined)
?.storageBucketsLayoutHideSubtitle ?? false,
})
if (skipBuckets) {
return (
<StorageLayout title={title}>
<Outlet />
</StorageLayout>
)
}
return (
<StorageLayout title={title}>
<StorageBucketsLayout title={bucketsTitle} hideSubtitle={bucketsHideSubtitle}>
<Outlet />
</StorageBucketsLayout>
</StorageLayout>
)
}
@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import AnalyticsBucketPage from '@/pages/project/[ref]/storage/analytics/buckets/[bucketId]'
export const Route = createFileRoute('/project/$ref/storage/analytics/buckets/$bucketId')({
component: StorageAnalyticsBucketRoute,
staticData: {
storageLayoutTitle: 'Buckets',
skipStorageBucketsLayout: true,
},
})
function StorageAnalyticsBucketRoute() {
return <AnalyticsBucketPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import StorageAnalyticsPage from '@/pages/project/[ref]/storage/analytics/index'
export const Route = createFileRoute('/project/$ref/storage/analytics/')({
component: StorageAnalyticsIndexRoute,
staticData: {
storageLayoutTitle: 'Analytics',
},
})
function StorageAnalyticsIndexRoute() {
return <StorageAnalyticsPage dehydratedState={undefined} />
}
@@ -0,0 +1,17 @@
import { createFileRoute } from '@tanstack/react-router'
import BucketPage from '@/pages/project/[ref]/storage/files/buckets/[bucketId]'
export const Route = createFileRoute('/project/$ref/storage/files/buckets/$bucketId')({
component: StorageFilesBucketRoute,
// Bucket detail pages skip StorageBucketsLayout — they render the
// bucket UI full-bleed under StorageLayout only.
staticData: {
storageLayoutTitle: 'Buckets',
skipStorageBucketsLayout: true,
},
})
function StorageFilesBucketRoute() {
return <BucketPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import StorageFilesPage from '@/pages/project/[ref]/storage/files/index'
export const Route = createFileRoute('/project/$ref/storage/files/')({
component: StorageFilesIndexRoute,
staticData: {
storageLayoutTitle: 'Files',
},
})
function StorageFilesIndexRoute() {
return <StorageFilesPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import FilesPoliciesPage from '@/pages/project/[ref]/storage/files/policies'
export const Route = createFileRoute('/project/$ref/storage/files/policies')({
component: StorageFilesPoliciesRoute,
staticData: {
storageLayoutTitle: 'Policies',
},
})
function StorageFilesPoliciesRoute() {
return <FilesPoliciesPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import FilesSettingsPage from '@/pages/project/[ref]/storage/files/settings'
export const Route = createFileRoute('/project/$ref/storage/files/settings')({
component: StorageFilesSettingsRoute,
staticData: {
storageLayoutTitle: 'Settings',
},
})
function StorageFilesSettingsRoute() {
return <FilesSettingsPage dehydratedState={undefined} />
}
@@ -0,0 +1,16 @@
import { createFileRoute } from '@tanstack/react-router'
import S3SettingsPage from '@/pages/project/[ref]/storage/s3'
export const Route = createFileRoute('/project/$ref/storage/s3')({
component: StorageS3Route,
staticData: {
storageLayoutTitle: 'S3 Configuration',
storageBucketsLayoutTitle: 'S3 Configuration',
storageBucketsLayoutHideSubtitle: true,
},
})
function StorageS3Route() {
return <S3SettingsPage dehydratedState={undefined} />
}
@@ -0,0 +1,15 @@
import { createFileRoute } from '@tanstack/react-router'
import VectorsBucketPage from '@/pages/project/[ref]/storage/vectors/buckets/[bucketId]'
export const Route = createFileRoute('/project/$ref/storage/vectors/buckets/$bucketId')({
component: StorageVectorsBucketRoute,
staticData: {
storageLayoutTitle: 'Buckets',
skipStorageBucketsLayout: true,
},
})
function StorageVectorsBucketRoute() {
return <VectorsBucketPage dehydratedState={undefined} />
}
@@ -0,0 +1,14 @@
import { createFileRoute } from '@tanstack/react-router'
import StorageVectorsPage from '@/pages/project/[ref]/storage/vectors/index'
export const Route = createFileRoute('/project/$ref/storage/vectors/')({
component: StorageVectorsIndexRoute,
staticData: {
storageLayoutTitle: 'Vectors',
},
})
function StorageVectorsIndexRoute() {
return <StorageVectorsPage dehydratedState={undefined} />
}
+11
View File
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import RedeemCreditsPage from '@/pages/redeem'
export const Route = createFileRoute('/redeem')({
component: RedeemRoute,
})
function RedeemRoute() {
return <RedeemCreditsPage dehydratedState={undefined} />
}
+11
View File
@@ -0,0 +1,11 @@
import { createFileRoute } from '@tanstack/react-router'
import VerifyEmailPage from '@/pages/verify-email'
export const Route = createFileRoute('/verify-email')({
component: VerifyEmailRoute,
})
function VerifyEmailRoute() {
return <VerifyEmailPage dehydratedState={undefined} />
}