| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205 |
- import { useParams } from 'common'
- import { Box, Plus } from 'lucide-react'
- import Link from 'next/link'
- import { useRouter } from 'next/router'
- import type { ComponentProps } from 'react'
- import { useState } from 'react'
- import { Button, CommandGroup, CommandItem } from 'ui'
- import { ShimmeringLoader } from 'ui-patterns'
- import { AppLayoutDropdownTriggerButton } from './AppLayoutDropdown'
- import { sanitizeRoute } from './ProjectDropdown.utils'
- import { ProjectRowLink } from './ProjectRowLink'
- import { useEmbeddedCloseHandler } from './useEmbeddedCloseHandler'
- import { OrganizationProjectSelector } from '@/components/ui/OrganizationProjectSelector'
- import PartnerIcon from '@/components/ui/PartnerIcon'
- import { getManagedByFromOrganizationPartner } from '@/data/organizations/managed-by-utils'
- import type { OrgProject } from '@/data/projects/org-projects-infinite-query'
- import { useProjectDetailQuery } from '@/data/projects/project-detail-query'
- import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
- import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
- import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
- import { IS_PLATFORM } from '@/lib/constants'
- import type { ManagedBy } from '@/lib/constants/infrastructure'
- import { useTrack } from '@/lib/telemetry/track'
- // --- Sub-components ---
- interface ProjectDropdownNewProjectActionsProps {
- organizationSlug: string | undefined
- embedded: boolean
- onClose: () => void
- onNavigate: (href: string) => void
- }
- function ProjectDropdownNewProjectActions({
- organizationSlug,
- embedded,
- onClose,
- onNavigate,
- }: ProjectDropdownNewProjectActionsProps) {
- const href = `/new/${organizationSlug}`
- if (embedded) {
- return (
- <Button type="default" block size="small" asChild icon={<Plus size={14} strokeWidth={1.5} />}>
- <Link
- href={href}
- onClick={onClose}
- className="text-xs text-foreground-light hover:text-foreground"
- >
- New project
- </Link>
- </Button>
- )
- }
- return (
- <CommandGroup>
- <CommandItem
- className="cursor-pointer w-full"
- onSelect={() => {
- onClose()
- onNavigate(href)
- }}
- onClick={onClose}
- >
- <Link href={href} onClick={onClose} className="w-full flex items-center gap-2">
- <Plus size={14} strokeWidth={1.5} />
- <p>New project</p>
- </Link>
- </CommandItem>
- </CommandGroup>
- )
- }
- const ProjectDropdownNonPlatformView = ({ projectName }: { projectName: string }) => {
- return <div className="text-sm px-3 py-1">{projectName}</div>
- }
- interface ProjectDropdownPlatformViewProps {
- projectRef: string | undefined
- projectName: string
- projectManagedBy?: ManagedBy
- selectorProps: Omit<
- ComponentProps<typeof OrganizationProjectSelector>,
- 'renderTrigger' | 'embedded'
- >
- }
- function ProjectDropdownPlatformView({
- projectRef,
- projectName,
- projectManagedBy,
- selectorProps,
- }: ProjectDropdownPlatformViewProps) {
- return (
- <div className="flex items-center shrink-0">
- <Link href={`/project/${projectRef}`} className="flex items-center gap-2 shrink-0 text-sm">
- <Box size={14} strokeWidth={1.5} className="text-foreground-lighter" />
- <span title={projectName} className="text-foreground max-w-32 lg:max-w-64 truncate">
- {projectName}
- </span>
- {projectManagedBy && <PartnerIcon organization={{ managed_by: projectManagedBy }} />}
- </Link>
- <OrganizationProjectSelector
- {...selectorProps}
- renderTrigger={() => <AppLayoutDropdownTriggerButton className="shrink-0" />}
- />
- </div>
- )
- }
- // --- Main component ---
- interface ProjectDropdownProps {
- embedded?: boolean
- className?: string
- onClose?: () => void
- }
- export const ProjectDropdown = ({
- embedded = false,
- className,
- onClose,
- }: ProjectDropdownProps = {}) => {
- const router = useRouter()
- const { ref } = useParams()
- const { data: project, isPending: isLoadingProject } = useSelectedProjectQuery()
- const { data: selectedOrganization } = useSelectedOrganizationQuery()
- const isBranch = project?.parentRef !== project?.ref
- const { data: parentProject, isPending: isLoadingParentProject } = useProjectDetailQuery(
- { ref: project?.parent_project_ref },
- { enabled: isBranch }
- )
- const selectedProject = parentProject ?? project
- const projectCreationEnabled = useIsFeatureEnabled('projects:create')
- const track = useTrack()
- const [open, setOpen] = useState(false)
- const close = useEmbeddedCloseHandler(embedded, onClose, setOpen)
- const selectedProjectManagedBy = selectedProject?.integration_source
- ? getManagedByFromOrganizationPartner(undefined, selectedProject.integration_source)
- : selectedOrganization?.billing_partner
- ? selectedOrganization.managed_by
- : undefined
- if (isLoadingProject || (isBranch && isLoadingParentProject) || !selectedProject) {
- if (!embedded) return <ShimmeringLoader className="p-2 md:mr-2 md:w-[90px]" />
- }
- const handleSetOpen = embedded
- ? (_value: boolean) => onClose?.()
- : (next: boolean) => {
- if (next) track('header_project_dropdown_opened')
- setOpen(next)
- }
- const selectorProps = {
- open,
- setOpen: handleSetOpen,
- selectedRef: ref,
- onSelect: (project: { ref: string }) => {
- const sanitizedRoute = sanitizeRoute(router.route, router.query)
- const href = sanitizedRoute?.replace('[ref]', project.ref) ?? `/project/${project.ref}`
- close()
- router.push(href)
- },
- renderRow: (project: Pick<OrgProject, 'ref' | 'name' | 'status' | 'integration_source'>) => (
- <ProjectRowLink
- project={project}
- selectedRef={ref}
- route={router.route}
- routerQueries={router.query}
- />
- ),
- renderActions: (_setOpen: (value: boolean) => void, options?: { embedded?: boolean }) =>
- projectCreationEnabled ? (
- <ProjectDropdownNewProjectActions
- organizationSlug={selectedOrganization?.slug}
- embedded={options?.embedded ?? false}
- onClose={close}
- onNavigate={(href) => router.push(href)}
- />
- ) : null,
- }
- if (embedded)
- return (
- <OrganizationProjectSelector {...selectorProps} embedded className={className} fetchOnMount />
- )
- return IS_PLATFORM ? (
- <ProjectDropdownPlatformView
- projectRef={project?.ref}
- projectName={selectedProject?.name ?? ''}
- projectManagedBy={selectedProjectManagedBy}
- selectorProps={selectorProps}
- />
- ) : (
- <ProjectDropdownNonPlatformView projectName={selectedProject?.name ?? ''} />
- )
- }
|