mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 17:35:10 +03:00
fix: prevent crash on _ page when user has no organizations (#38640)
* fix: prevent crash on _ page when user has no organizations * Nit refactors to make wild card pages consistent + fix loading state of org wildcard route * Smol fix --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
This commit is contained in:
1 parent
271ee3af6d
commit
229f5560a6
4 files changed
+123
-148
No files matched your search
@@ -2,6 +2,7 @@ import { Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
@@ -16,6 +17,23 @@ import {
|
||||
} from 'ui'
|
||||
import { ShimmeringCard } from './ShimmeringCard'
|
||||
|
||||
export const Header = () => {
|
||||
return (
|
||||
<div className="border-default border-b p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link href="/projects">
|
||||
<img
|
||||
src={`${BASE_PATH}/img/supabase-logo.svg`}
|
||||
alt="Supabase"
|
||||
className="border-default rounded border p-1 hover:border-white"
|
||||
style={{ height: 24 }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const NoFilterResults = ({
|
||||
filterStatus,
|
||||
resetFilterStatus,
|
||||
@@ -122,3 +140,21 @@ export const NoProjectsState = ({ slug }: { slug: string }) => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const NoOrganizationsState = () => {
|
||||
return (
|
||||
<div className="col-span-4 space-y-4 rounded-lg border border-dashed border-muted p-6 text-center">
|
||||
<div className="space-y-1">
|
||||
<p>You are not part of any organizations yet</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Create your first organization to get started with Supabase
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button asChild icon={<Plus />}>
|
||||
<Link href="/new">New organization</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Vendored
-1
@@ -1,6 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
/// <reference path="./.next/types/routes.d.ts" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||
@@ -1,31 +1,17 @@
|
||||
import { Plus } from 'lucide-react'
|
||||
import { NextPage } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { ShimmeringCard } from 'components/interfaces/Home/ProjectList/ShimmeringCard'
|
||||
import {
|
||||
Header,
|
||||
LoadingCardView,
|
||||
NoOrganizationsState,
|
||||
} from 'components/interfaces/Home/ProjectList/EmptyStates'
|
||||
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
|
||||
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import CardButton from 'components/ui/CardButton'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<div className="border-default border-b p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link href="/projects">
|
||||
<img
|
||||
src={`${BASE_PATH}/img/supabase-logo.svg`}
|
||||
alt="Supabase"
|
||||
className="border-default rounded border p-1 hover:border-white"
|
||||
style={{ height: 24 }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
import { cn } from 'ui'
|
||||
|
||||
// [Joshen] Thinking we can deprecate this page in favor of /organizations
|
||||
const GenericOrganizationPage: NextPage = () => {
|
||||
@@ -52,70 +38,51 @@ const GenericOrganizationPage: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<div className="flex flex-col mx-auto w-full space-y-3">
|
||||
<h3 className="mt-8 text-2xl w-full max-w-5xl mx-auto">
|
||||
Select an organization to continue
|
||||
</h3>
|
||||
<div
|
||||
className="flex-grow py-6 overflow-y-auto"
|
||||
style={{ maxHeight: 'calc(100vh - 49px - 64px)' }}
|
||||
>
|
||||
<div className="w-full max-w-5xl mx-auto flex flex-col gap-y-8">
|
||||
{isLoading ? (
|
||||
<ul
|
||||
className={cn(
|
||||
'mx-auto grid grid-cols-1 gap-4',
|
||||
'sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3'
|
||||
<PageLayout className="flex-grow min-h-0" title="Select an organization to continue">
|
||||
<ScaffoldContainer>
|
||||
<ScaffoldSection isFullWidth>
|
||||
<div
|
||||
className="flex-grow overflow-y-auto"
|
||||
style={{ maxHeight: 'calc(100vh - 49px - 64px)' }}
|
||||
>
|
||||
<div className="w-full mx-auto flex flex-col gap-y-8">
|
||||
{isLoading ? (
|
||||
<LoadingCardView />
|
||||
) : organizations?.length === 0 ? (
|
||||
<NoOrganizationsState />
|
||||
) : (
|
||||
<ul
|
||||
className={cn(
|
||||
'w-full mx-auto grid grid-cols-1 gap-4',
|
||||
'sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3'
|
||||
)}
|
||||
>
|
||||
{organizations?.map((organization) => (
|
||||
<li key={organization.slug} className="col-span-1">
|
||||
<CardButton
|
||||
linkHref={urlRewriterFactory(routeSlug)(organization.slug)}
|
||||
title={
|
||||
<div className="flex w-full flex-row justify-between gap-1">
|
||||
<span className="flex-shrink truncate">{organization.name}</span>
|
||||
</div>
|
||||
}
|
||||
footer={
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="text-sm lowercase text-foreground-light">
|
||||
{organization.slug}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
>
|
||||
<ShimmeringCard />
|
||||
<ShimmeringCard />
|
||||
</ul>
|
||||
) : organizations?.length === 0 ? (
|
||||
<div className="col-span-4 space-y-4 rounded-lg border border-dashed border-muted p-6 text-center">
|
||||
<div className="space-y-1">
|
||||
<p>You are not part of any organizations yet</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Get started by creating a new organization.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<Button asChild icon={<Plus />}>
|
||||
<Link href="/new">New organization</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ul
|
||||
className={cn(
|
||||
'w-full mx-auto grid grid-cols-1 gap-4',
|
||||
'sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3'
|
||||
)}
|
||||
>
|
||||
{organizations?.map((organization) => (
|
||||
<li key={organization.slug} className="col-span-1">
|
||||
<CardButton
|
||||
linkHref={urlRewriterFactory(routeSlug)(organization.slug)}
|
||||
title={
|
||||
<div className="flex w-full flex-row justify-between gap-1">
|
||||
<span className="flex-shrink truncate">{organization.name}</span>
|
||||
</div>
|
||||
}
|
||||
footer={
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="text-sm lowercase text-foreground-light">
|
||||
{organization.slug}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScaffoldSection>
|
||||
</ScaffoldContainer>
|
||||
</PageLayout>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { AlertTriangleIcon } from 'lucide-react'
|
||||
import { NextPage } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'common'
|
||||
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'
|
||||
@@ -12,7 +16,6 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
|
||||
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
|
||||
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
|
||||
import { withAuth } from 'hooks/misc/withAuth'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import {
|
||||
Alert_Shadcn_,
|
||||
AlertDescription_Shadcn_,
|
||||
@@ -23,44 +26,6 @@ import {
|
||||
SelectTrigger_Shadcn_,
|
||||
SelectValue_Shadcn_,
|
||||
} from 'ui'
|
||||
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
<div className="border-default border-b p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link href="/projects">
|
||||
<img
|
||||
src={`${BASE_PATH}/img/supabase-logo.svg`}
|
||||
alt="Supabase"
|
||||
className="border-default rounded border p-1 hover:border-white"
|
||||
style={{ height: 24 }}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const OrganizationLoadingState = () => {
|
||||
return (
|
||||
<>
|
||||
<ShimmeringLoader className="w-3/4" />
|
||||
<ShimmeringLoader className="w-1/2" />
|
||||
<ShimmeringLoader className="w-1/4" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const OrganizationErrorState = () => {
|
||||
return (
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<AlertTriangleIcon />
|
||||
<AlertTitle_Shadcn_>Failed to load your Supabase organizations</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>Try refreshing the page</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
// [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
|
||||
@@ -108,7 +73,7 @@ const GenericProjectPage: NextPage = () => {
|
||||
if (!!lastVisitedOrgSlug) {
|
||||
setSlug(lastVisitedOrgSlug)
|
||||
} else if (isSuccessOrganizations) {
|
||||
setSlug(organizations[0].slug)
|
||||
setSlug(organizations[0]?.slug)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [lastVisitedOrgSlug, isSuccessOrganizations])
|
||||
@@ -118,31 +83,39 @@ const GenericProjectPage: NextPage = () => {
|
||||
<Header />
|
||||
<PageLayout className="flex-grow min-h-0" title="Select a project to continue">
|
||||
<ScaffoldContainer className="flex-grow flex flex-col">
|
||||
<ScaffoldSection isFullWidth>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Select_Shadcn_ value={selectedSlug} onValueChange={setSlug}>
|
||||
<SelectTrigger_Shadcn_ size="tiny" className="w-60 truncate">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p className="text-xs text-foreground-light">Organization:</p>
|
||||
<SelectValue_Shadcn_ placeholder="Select an organization" />
|
||||
</div>
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_ className="col-span-8">
|
||||
{organizations.map((org) => (
|
||||
<SelectItem_Shadcn_ key={org.slug} value={org.slug} className="text-xs">
|
||||
{org.name}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
<HomePageActions hideNewProject />
|
||||
</div>
|
||||
</ScaffoldSection>
|
||||
{organizations.length > 0 && (
|
||||
<ScaffoldSection isFullWidth>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Select_Shadcn_ value={selectedSlug} onValueChange={setSlug}>
|
||||
<SelectTrigger_Shadcn_ size="tiny" className="w-60 truncate">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<p className="text-xs text-foreground-light">Organization:</p>
|
||||
<SelectValue_Shadcn_ placeholder="Select an organization" />
|
||||
</div>
|
||||
</SelectTrigger_Shadcn_>
|
||||
<SelectContent_Shadcn_ className="col-span-8">
|
||||
{organizations.map((org) => (
|
||||
<SelectItem_Shadcn_ key={org.slug} value={org.slug} className="text-xs">
|
||||
{org.name}
|
||||
</SelectItem_Shadcn_>
|
||||
))}
|
||||
</SelectContent_Shadcn_>
|
||||
</Select_Shadcn_>
|
||||
<HomePageActions hideNewProject />
|
||||
</div>
|
||||
</ScaffoldSection>
|
||||
)}
|
||||
<ScaffoldSection isFullWidth className="flex-grow pt-0 flex flex-col gap-y-4 h-px">
|
||||
{isLoadingOrganizations ? (
|
||||
<OrganizationLoadingState />
|
||||
<LoadingCardView />
|
||||
) : isErrorOrganizations ? (
|
||||
<OrganizationErrorState />
|
||||
<Alert_Shadcn_ variant="warning">
|
||||
<AlertTriangleIcon />
|
||||
<AlertTitle_Shadcn_>Failed to load your Supabase organizations</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_>Try refreshing the page</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
) : organizations.length === 0 ? (
|
||||
<NoOrganizationsState />
|
||||
) : !!selectedOrganization ? (
|
||||
<ProjectList
|
||||
organization={selectedOrganization}
|
||||
|
||||
Reference in new issue
Block a user