Files
supabase__supabase/apps/studio/pages/project/_/[[...routeSlug]].tsx
Alaister Young 7d3f72ec7d 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>
2026-07-03 21:22:06 +08:00

140 lines
5.2 KiB
TypeScript

import { IS_PLATFORM, useParams } from 'common'
import { AlertTriangleIcon } from 'lucide-react'
import { NextPage } from 'next'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import {
Alert,
AlertDescription,
AlertTitle,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from 'ui'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import { parseCatchAllRoute } from '@/compat/next/router'
import {
Header,
LoadingCardView,
NoOrganizationsState,
} from '@/components/interfaces/Home/ProjectList/EmptyStates'
import { ProjectList } from '@/components/interfaces/Home/ProjectList/ProjectList'
import { HomePageActions } from '@/components/interfaces/HomePageActions'
import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { useLastVisitedOrganization } from '@/hooks/misc/useLastVisitedOrganization'
import { withAuth } from '@/hooks/misc/withAuth'
// [Joshen] I'd say we don't do route validation here, this page will act more
// like a proxy to the project specific pages, and we let those pages handle
// any route validation logic instead
const GenericProjectPage: NextPage = () => {
const router = useRouter()
const { slug } = useParams()
// 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()
const {
data: organizations = [],
isSuccess: isSuccessOrganizations,
isPending: isLoadingOrganizations,
isError: isErrorOrganizations,
} = useOrganizationsQuery({
enabled: IS_PLATFORM,
})
const [selectedSlug, setSlug] = useState<string>(
slug || lastVisitedOrganization || organizations[0]?.slug
)
const selectedOrganization = organizations.find((x) => x.slug === selectedSlug)
const query = Object.keys(queryParams).length
? `?${new URLSearchParams(queryParams as Record<string, string>)}`
: undefined
const urlRewriterFactory = (slug: string | string[] | undefined) => {
return (projectRef: string) => {
const hash = location.hash
if (!Array.isArray(slug)) {
return [`/project/${projectRef}`, query, hash].filter(Boolean).join('')
}
const slugPath = slug.join('/')
return [`/project/${projectRef}/${slugPath}`, query, hash].filter(Boolean).join('')
}
}
useEffect(() => {
if (!!lastVisitedOrganization) {
setSlug(lastVisitedOrganization)
} else if (isSuccessOrganizations) {
setSlug(organizations[0]?.slug)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [lastVisitedOrganization, isSuccessOrganizations])
return (
<div className="h-screen flex flex-col">
<Header />
<PageLayout className="grow min-h-0" title="Select a project to continue">
<ScaffoldContainer className="grow flex flex-col gap-y-4">
<ScaffoldSection isFullWidth className="py-0">
<div className="flex items-center gap-x-2">
{isLoadingOrganizations ? (
<ShimmeringLoader className="w-60 py-0 h-[26px]" />
) : (
<Select value={selectedSlug} onValueChange={setSlug}>
<SelectTrigger size="tiny" className="w-60 truncate">
<div className="flex items-center gap-x-2">
<p className="text-xs text-foreground-light">Organization:</p>
<SelectValue placeholder="Select an organization" />
</div>
</SelectTrigger>
<SelectContent className="col-span-8">
{organizations.map((org) => (
<SelectItem key={org.slug} value={org.slug} className="text-xs">
{org.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
<HomePageActions hideNewProject />
</div>
</ScaffoldSection>
<ScaffoldSection isFullWidth className="py-0">
{isLoadingOrganizations ? (
<LoadingCardView />
) : isErrorOrganizations ? (
<Alert variant="warning">
<AlertTriangleIcon />
<AlertTitle>Failed to load your Supabase organizations</AlertTitle>
<AlertDescription>Try refreshing the page</AlertDescription>
</Alert>
) : organizations.length === 0 ? (
<NoOrganizationsState />
) : !!selectedOrganization ? (
<ProjectList
organization={selectedOrganization}
rewriteHref={urlRewriterFactory(routeSlug)}
/>
) : null}
</ScaffoldSection>
</ScaffoldContainer>
</PageLayout>
</div>
)
}
export default withAuth(GenericProjectPage)