Files
supabase/apps/studio/pages/organizations.tsx
T
Saxon FletcherandJoshen Lim 9ed6ea6cce Project list updates (#38021)
* update project list page layout

* prettier

* size actions

* Clean up

* Smol fix

* Fix TS

* Refactor to improve readability, remove duplicated code, make org page consistent

* use TimestampInfo

* Last bit

* Fix

* niiiiiiit

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2025-08-25 22:57:12 +08:00

124 lines
4.5 KiB
TypeScript

import { Plus, Search } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { useParams } from 'common'
import { OrganizationCard } from 'components/interfaces/Organization/OrganizationCard'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageLayout } from 'components/layouts/PageLayout/PageLayout'
import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import NoSearchResults from 'components/ui/NoSearchResults'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { withAuth } from 'hooks/misc/withAuth'
import { NextPageWithLayout } from 'types'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
CriticalIcon,
Skeleton,
} from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
const OrganizationsPage: NextPageWithLayout = () => {
const router = useRouter()
const [search, setSearch] = useState('')
const { error: orgNotFoundError, org: orgSlug } = useParams()
const orgNotFound = orgNotFoundError === 'org_not_found'
const { data: organizations = [], error, isLoading, isError, isSuccess } = useOrganizationsQuery()
const organizationCreationEnabled = useIsFeatureEnabled('organizations:create')
const filteredOrganizations =
search.length === 0
? organizations
: organizations?.filter(
(x) => x.name.toLowerCase().includes(search) || x.slug.toLowerCase().includes(search)
)
useEffect(() => {
// If there are no organizations, force the user to create one
// unless the user is on the not found page
if (isSuccess && organizations.length <= 0 && !orgNotFound) {
router.push('/new')
}
}, [isSuccess, organizations])
return (
<ScaffoldContainer>
<ScaffoldSection isFullWidth className="flex flex-col gap-y-4">
{orgNotFound && (
<Alert_Shadcn_ variant="destructive">
<CriticalIcon />
<AlertTitle_Shadcn_>Organization not found</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
That organization (<code>{orgSlug}</code>) does not exist or you don't have access to
it.
</AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="mt-3">
If you think this is an error, please reach out to the org owner to get access.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{organizations.length === 0 && orgNotFound && (
<p className="-mt-4">You don't have any organizations yet. Create one to get started.</p>
)}
<div className="flex items-center justify-between gap-x-2 md:gap-x-3">
{organizations.length > 0 && (
<Input
size="tiny"
placeholder="Search for an organization"
icon={<Search size={16} />}
className="w-full flex-1 md:w-64 [&>div>div>div>input]:!pl-7 [&>div>div>div>div]:!pl-2"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
)}
{organizationCreationEnabled && (
<Button asChild icon={<Plus />} type="primary" className="w-min">
<Link href={`/new`}>New organization</Link>
</Button>
)}
</div>
{search.length > 0 && filteredOrganizations.length === 0 && (
<NoSearchResults searchString={search} />
)}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{isLoading && (
<>
<Skeleton className="h-[70px] rounded-md" />
<Skeleton className="h-[70px] rounded-md" />
<Skeleton className="h-[70px] rounded-md" />
</>
)}
{isError && <AlertError error={error} subject="Failed to load organizations" />}
{isSuccess &&
filteredOrganizations.map((org) => (
<OrganizationCard key={org.id} organization={org} />
))}
</div>
</ScaffoldSection>
</ScaffoldContainer>
)
}
OrganizationsPage.getLayout = (page) => (
<AppLayout>
<DefaultLayout headerTitle="Organizations">
<PageLayout title="Your Organizations">{page}</PageLayout>
</DefaultLayout>
</AppLayout>
)
export default withAuth(OrganizationsPage)