mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
Chore/project wildcard route sort last visited org (#35680)
* Project wildcard route, sort last visited org first * Add search and filter status * Smol fix
This commit is contained in:
1 parent
0880e0e1d0
commit
5c99584001
4 files changed
+141
-91
No files matched your search
@@ -1,7 +1,6 @@
|
||||
import { Plus } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import AlertError from 'components/ui/AlertError'
|
||||
import NoSearchResults from 'components/ui/NoSearchResults'
|
||||
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
|
||||
@@ -19,7 +18,7 @@ import ProjectCard from './ProjectCard'
|
||||
import ShimmeringCard from './ShimmeringCard'
|
||||
|
||||
export interface ProjectListProps {
|
||||
forOrganization?: Organization
|
||||
organization?: Organization
|
||||
rewriteHref?: (projectRef: string) => string
|
||||
search?: string
|
||||
filterStatus?: string[]
|
||||
@@ -28,13 +27,13 @@ export interface ProjectListProps {
|
||||
|
||||
const ProjectList = ({
|
||||
search = '',
|
||||
forOrganization,
|
||||
organization: organization_,
|
||||
rewriteHref,
|
||||
filterStatus,
|
||||
resetFilterStatus,
|
||||
}: ProjectListProps) => {
|
||||
const { slug } = useParams()
|
||||
const organization = useSelectedOrganization() ?? forOrganization
|
||||
const selectedOrganization = useSelectedOrganization()
|
||||
const organization = organization_ ?? selectedOrganization
|
||||
|
||||
const {
|
||||
data: allProjects = [],
|
||||
@@ -50,9 +49,7 @@ const ProjectList = ({
|
||||
} = usePermissionsQuery()
|
||||
const { data: resourceWarnings } = useResourceWarningsQuery()
|
||||
|
||||
const orgProjects = allProjects.filter(
|
||||
(x) => x.organization_slug === slug || x.organization_slug === organization?.slug
|
||||
)
|
||||
const orgProjects = allProjects.filter((x) => x.organization_slug === organization?.slug)
|
||||
const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false
|
||||
|
||||
const hasFilterStatusApplied = filterStatus !== undefined && filterStatus.length !== 2
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
interface HomePageActionsProps {
|
||||
search: string
|
||||
filterStatus: string[]
|
||||
hideNewProject?: boolean
|
||||
setSearch: (value: string) => void
|
||||
setFilterStatus: (value: string[]) => void
|
||||
}
|
||||
@@ -24,6 +25,7 @@ interface HomePageActionsProps {
|
||||
const HomePageActions = ({
|
||||
search,
|
||||
filterStatus,
|
||||
hideNewProject = false,
|
||||
setSearch,
|
||||
setFilterStatus,
|
||||
}: HomePageActionsProps) => {
|
||||
@@ -32,7 +34,7 @@ const HomePageActions = ({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 md:gap-3 md:flex-row">
|
||||
{projectCreationEnabled && (
|
||||
{projectCreationEnabled && !hideNewProject && (
|
||||
<Button asChild type="primary">
|
||||
<Link href={`/new/${slug}`}>New project</Link>
|
||||
</Button>
|
||||
|
||||
@@ -8,7 +8,7 @@ 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 } from 'ui'
|
||||
import { Button, cn } from 'ui'
|
||||
|
||||
const Header = () => {
|
||||
return (
|
||||
@@ -27,6 +27,7 @@ const Header = () => {
|
||||
)
|
||||
}
|
||||
|
||||
// [Joshen] Thinking we can deprecate this page in favor of /organizations
|
||||
const GenericOrganizationPage: NextPage = () => {
|
||||
const router = useRouter()
|
||||
|
||||
@@ -51,64 +52,68 @@ const GenericOrganizationPage: NextPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<div className="flex flex-col mx-auto w-full max-w-5xl space-y-3">
|
||||
<h3 className="mt-8 text-2xl">Select an organization to continue</h3>
|
||||
<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 space-y-8 overflow-y-auto"
|
||||
className="flex-grow py-6 overflow-y-auto"
|
||||
style={{ maxHeight: 'calc(100vh - 49px - 64px)' }}
|
||||
>
|
||||
{isLoading ? (
|
||||
<ul
|
||||
className={[
|
||||
'mx-auto grid grid-cols-1 gap-4',
|
||||
'sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3',
|
||||
].join(' `')}
|
||||
>
|
||||
<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 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'
|
||||
)}
|
||||
>
|
||||
<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>
|
||||
<div>
|
||||
<Button asChild icon={<Plus />}>
|
||||
<Link href="/new">New organization</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ul
|
||||
className={[
|
||||
'mx-auto grid grid-cols-1 gap-4',
|
||||
'sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3',
|
||||
].join(' `')}
|
||||
>
|
||||
{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>
|
||||
)}
|
||||
) : (
|
||||
<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>
|
||||
</>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { partition } from 'lodash'
|
||||
import { AlertTriangleIcon, Boxes } from 'lucide-react'
|
||||
import { NextPage } from 'next'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Fragment } from 'react'
|
||||
import { Fragment, useMemo, useState } from 'react'
|
||||
|
||||
import { IS_PLATFORM } from 'common'
|
||||
import { IS_PLATFORM, LOCAL_STORAGE_KEYS } from 'common'
|
||||
import { ProjectList } from 'components/interfaces/Home/ProjectList'
|
||||
import HomePageActions from 'components/interfaces/HomePageActions'
|
||||
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_, AlertTitle_Shadcn_ } from 'ui'
|
||||
import { BASE_PATH, PROJECT_STATUS } from 'lib/constants'
|
||||
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Badge } from 'ui'
|
||||
import ShimmeringLoader from 'ui-patterns/ShimmeringLoader'
|
||||
|
||||
const Header = () => {
|
||||
@@ -37,8 +40,19 @@ const GenericProjectPage: NextPage = () => {
|
||||
const router = useRouter()
|
||||
const { routeSlug, ...queryParams } = router.query
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const [filterStatus, setFilterStatus] = useState<string[]>([
|
||||
PROJECT_STATUS.ACTIVE_HEALTHY,
|
||||
PROJECT_STATUS.INACTIVE,
|
||||
])
|
||||
|
||||
const [lastVisitedOrgSlug] = useLocalStorageQuery(
|
||||
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
|
||||
''
|
||||
)
|
||||
|
||||
const {
|
||||
data: organizations,
|
||||
data: organizations = [],
|
||||
isLoading: isLoadingOrganizations,
|
||||
isError: isErrorOrganizations,
|
||||
} = useOrganizationsQuery({
|
||||
@@ -62,34 +76,66 @@ const GenericProjectPage: NextPage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const [[lastVisitedOrganization], otherOrganizations] = useMemo(
|
||||
() => partition(organizations, (org) => org.slug === lastVisitedOrgSlug),
|
||||
[lastVisitedOrgSlug, organizations]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<div className="flex flex-col mx-auto w-full max-w-5xl">
|
||||
<h1 className="mt-8 text-2xl">Select a project to continue</h1>
|
||||
<div className="flex flex-col mx-auto w-full">
|
||||
<h1 className="mt-8 text-2xl max-w-5xl mx-auto w-full">Select a project to continue</h1>
|
||||
<div
|
||||
className="flex-grow py-6 space-y-8 overflow-y-auto"
|
||||
className="flex-grow py-6 overflow-y-auto"
|
||||
style={{ maxHeight: 'calc(100vh - 49px - 64px)' }}
|
||||
>
|
||||
{isLoadingOrganizations ? (
|
||||
<OrganizationLoadingState />
|
||||
) : isErrorOrganizations ? (
|
||||
<OrganizationErrorState />
|
||||
) : (
|
||||
organizations.map((organization) => (
|
||||
<Fragment key={organization.id}>
|
||||
<h2 className="flex items-center gap-2">
|
||||
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
|
||||
{organization.name}
|
||||
</h2>
|
||||
<ProjectList
|
||||
forOrganization={organization}
|
||||
rewriteHref={urlRewriterFactory(routeSlug)}
|
||||
search=""
|
||||
/>
|
||||
</Fragment>
|
||||
))
|
||||
)}
|
||||
<div className="w-full max-w-5xl mx-auto flex flex-col gap-y-8">
|
||||
<HomePageActions
|
||||
hideNewProject
|
||||
search={search}
|
||||
setSearch={setSearch}
|
||||
filterStatus={filterStatus}
|
||||
setFilterStatus={setFilterStatus}
|
||||
/>
|
||||
{isLoadingOrganizations ? (
|
||||
<OrganizationLoadingState />
|
||||
) : isErrorOrganizations ? (
|
||||
<OrganizationErrorState />
|
||||
) : (
|
||||
<>
|
||||
{!!lastVisitedOrganization && (
|
||||
<>
|
||||
<h2 className="flex items-center gap-2">
|
||||
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
|
||||
{lastVisitedOrganization.name}
|
||||
<Badge variant="default">Recently visited</Badge>
|
||||
</h2>
|
||||
<ProjectList
|
||||
search={search}
|
||||
filterStatus={filterStatus}
|
||||
organization={lastVisitedOrganization}
|
||||
rewriteHref={urlRewriterFactory(routeSlug)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{otherOrganizations.map((organization) => (
|
||||
<Fragment key={organization.id}>
|
||||
<h2 className="flex items-center gap-2">
|
||||
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
|
||||
{organization.name}
|
||||
</h2>
|
||||
<ProjectList
|
||||
search={search}
|
||||
filterStatus={filterStatus}
|
||||
organization={organization}
|
||||
rewriteHref={urlRewriterFactory(routeSlug)}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user