import { groupBy } from 'lodash'
import { Plus } from 'lucide-react'
import Link from 'next/link'
import AlertError from 'components/ui/AlertError'
import NoSearchResults from 'components/ui/NoSearchResults'
import PartnerIcon from 'components/ui/PartnerIcon'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import {
OverdueInvoicesResponse,
useOverdueInvoicesQuery,
} from 'data/invoices/invoices-overdue-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { ProjectInfo, useProjectsQuery } from 'data/projects/projects-query'
import { ResourceWarning, useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import { makeRandomString } from 'lib/helpers'
import type { Organization, ResponseError } from 'types'
import { Button, cn } from 'ui'
import ProjectCard from './ProjectCard'
import ShimmeringCard from './ShimmeringCard'
export interface ProjectListProps {
rewriteHref?: (projectRef: string) => string
search: string
filterStatus?: string[]
resetFilterStatus?: () => void
}
const ProjectList = ({
search,
rewriteHref,
filterStatus,
resetFilterStatus,
}: ProjectListProps) => {
const { data: organizations, isLoading, isSuccess } = useOrganizationsQuery()
const {
data: allProjects,
isLoading: isLoadingProjects,
isSuccess: isSuccessProjects,
isError: isErrorProjects,
error: projectsError,
} = useProjectsQuery()
const {
isLoading: _isLoadingPermissions,
isError: isErrorPermissions,
error: permissionsError,
} = usePermissionsQuery()
const { data: resourceWarnings } = useResourceWarningsQuery()
const { data: allOverdueInvoices } = useOverdueInvoicesQuery()
const projectsByOrg = groupBy(allProjects, 'organization_id')
const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false
const hasFilterStatusApplied = filterStatus !== undefined && filterStatus.length !== 2
const noResultsFromSearch =
search.length > 0 &&
isSuccessProjects &&
allProjects.filter((project) => {
return (
project.name.toLowerCase().includes(search.toLowerCase()) ||
project.ref.includes(search.toLowerCase())
)
}).length === 0
const noResultsFromStatusFilter =
hasFilterStatusApplied &&
isSuccessProjects &&
allProjects.filter((project) => filterStatus.includes(project.status)).length === 0
if (isLoading) {
return (
)
}
if (noResultsFromSearch) {
return
}
if (noResultsFromStatusFilter) {
return (
{/* [Joshen] Just keeping it simple for now unless we decide to extend this to other statuses */}
{filterStatus.length === 0
? `No projects found`
: `No ${filterStatus[0] === 'INACTIVE' ? 'paused' : 'active'} projects found`}
Your search for projects with the specified status did not return any results
{resetFilterStatus !== undefined && (
)}
)
}
return isSuccess && organizations && organizations?.length > 0 ? (
<>
{organizations?.map((organization) => {
return (
it.organization_id === organization.id
)}
resourceWarnings={resourceWarnings ?? []}
rewriteHref={rewriteHref}
isLoadingPermissions={isLoadingPermissions}
isErrorPermissions={isErrorPermissions}
permissionsError={permissionsError}
isLoadingProjects={isLoadingProjects}
isErrorProjects={isErrorProjects}
projectsError={projectsError}
search={search}
filterStatus={filterStatus}
/>
)
})}
>
) : (
)
}
export default ProjectList
type OrganizationProjectsProps = {
organization: Organization
projects: ProjectInfo[]
overdueInvoices: OverdueInvoicesResponse[]
resourceWarnings: ResourceWarning[]
isLoadingPermissions: boolean
isErrorPermissions: boolean
permissionsError: ResponseError | null
isLoadingProjects: boolean
isErrorProjects: boolean
projectsError: ResponseError | null
rewriteHref?: (projectRef: string) => string
search: string
filterStatus?: string[]
}
const OrganizationProjects = ({
organization,
projects,
overdueInvoices,
resourceWarnings,
isLoadingPermissions,
isErrorPermissions,
permissionsError,
isLoadingProjects,
isErrorProjects,
projectsError,
rewriteHref,
search,
filterStatus,
}: OrganizationProjectsProps) => {
const isEmpty = !projects || projects.length === 0
const sortedProjects = [...(projects || [])].sort((a, b) => a.name.localeCompare(b.name))
const filteredProjects =
search.length > 0
? sortedProjects.filter((project) => {
return (
project.name.toLowerCase().includes(search.toLowerCase()) ||
project.ref.includes(search.toLowerCase())
)
})
: sortedProjects
// [Joshen] Just a UI thing, but we take all projects other than paused as "active"
const filteredProjectsByStatus =
filterStatus !== undefined
? filterStatus.length === 2
? filteredProjects
: filteredProjects.filter((project) => filterStatus.includes(project.status))
: filteredProjects
const { data: integrations } = useOrgIntegrationsQuery({ orgSlug: organization?.slug })
const { data: connections } = useGitHubConnectionsQuery({ organizationId: organization?.id })
const githubConnections = connections?.map((connection) => ({
id: String(connection.id),
added_by: {
id: String(connection.user?.id),
primary_email: connection.user?.primary_email ?? '',
username: connection.user?.username ?? '',
},
foreign_project_id: String(connection.repository.id),
supabase_project_ref: connection.project.ref,
organization_integration_id: 'unused',
inserted_at: connection.inserted_at,
updated_at: connection.updated_at,
metadata: {
name: connection.repository.name,
} as any,
}))
const vercelConnections = integrations
?.filter((integration) => integration.integration.name === 'Vercel')
.flatMap((integration) => integration.connections)
if (
(search.length > 0 || (filterStatus !== undefined && filterStatus.length !== 2)) &&
filteredProjectsByStatus.length === 0
)
return null
return (
{!!overdueInvoices.length && (
)}
{organization?.restriction_status === 'grace_period' && (
)}
{organization?.restriction_status === 'grace_period_over' && (
)}
{organization?.restriction_status === 'restricted' && (
)}
{isLoadingPermissions || isLoadingProjects ? (
) : (
{isErrorPermissions ? (
) : isErrorProjects ? (
) : isEmpty ? (
) : (
filteredProjectsByStatus?.map((project) => (
resourceWarning.project === project.ref
)}
githubIntegration={githubConnections?.find(
(connection) => connection.supabase_project_ref === project.ref
)}
vercelIntegration={vercelConnections?.find(
(connection) => connection.supabase_project_ref === project.ref
)}
/>
))
)}
)}
)
}
const NoProjectsState = ({ slug }: { slug: string }) => {
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
return (
No projects
Get started by creating a new project.
{projectCreationEnabled && (
}>
New Project
)}
)
}