import { groupBy } from 'lodash' 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 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 } 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 // if true, only show projects for the current organization filterToSlug?: boolean } const ProjectList = ({ search = '', rewriteHref, filterStatus, resetFilterStatus, filterToSlug, }: ProjectListProps) => { const { slug } = useParams() 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 && ( )}
) } const filteredOrganizations = filterToSlug ? organizations?.filter((org) => org.slug === slug) : organizations return isSuccess && organizations && organizations?.length > 0 ? ( <> {filteredOrganizations?.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} filterToSlug={filterToSlug} /> ) })} ) : ( ) } 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[] filterToSlug?: boolean } const OrganizationProjects = ({ organization, projects, overdueInvoices, resourceWarnings, isLoadingPermissions, isErrorPermissions, permissionsError, isLoadingProjects, isErrorProjects, projectsError, rewriteHref, search, filterStatus, filterToSlug, }: 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 (
{!filterToSlug && (

{organization.name}

{' '}
{!!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 && ( )}
) }