mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
7d3f72ec7d
**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>
140 lines
5.2 KiB
TypeScript
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)
|