import { useSession } from '@documenso/lib/client-only/providers/session'; import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app'; import { type INTERNAL_CLAIM_ID, internalClaims } from '@documenso/lib/types/subscription'; import { formatAvatarUrl } from '@documenso/lib/utils/avatars'; import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations'; import { getSettingsNavGroups } from '@documenso/lib/utils/settings-nav'; import { computeSwitcherContinuityPath } from '@documenso/lib/utils/settings-switcher'; import { canExecuteTeamAction } from '@documenso/lib/utils/teams'; import { cn } from '@documenso/ui/lib/utils'; import { AvatarWithText } from '@documenso/ui/primitives/avatar'; import { Button } from '@documenso/ui/primitives/button'; import { Input } from '@documenso/ui/primitives/input'; import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; import { Trans, useLingui } from '@lingui/react/macro'; import { ChevronsUpDownIcon, PlusIcon, SearchIcon } from 'lucide-react'; import { useMemo, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; const SEARCH_THRESHOLD = 5; export type SettingsOrgSwitcherProps = { currentOrgUrl: string; }; export const SettingsOrgSwitcher = ({ currentOrgUrl }: SettingsOrgSwitcherProps) => { const { t } = useLingui(); const { organisations } = useSession(); const { pathname } = useLocation(); const navigate = useNavigate(); const [isOpen, setIsOpen] = useState(false); const [query, setQuery] = useState(''); const manageableOrgs = useMemo( () => organisations.filter( (org) => canExecuteOrganisationAction('MANAGE_ORGANISATION', org.currentOrganisationRole) || org.teams.some((team) => canExecuteTeamAction('MANAGE_TEAM', team.currentTeamRole)), ), [organisations], ); const currentOrg = manageableOrgs.find((org) => org.url === currentOrgUrl); const hasManageableBillingOrgs = useMemo( () => organisations.some((org) => canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole)), [organisations], ); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); if (!q) { return manageableOrgs; } return manageableOrgs.filter((org) => org.name.toLowerCase().includes(q)); }, [manageableOrgs, query]); const isBillingEnabled = IS_BILLING_ENABLED(); const handleSelect = (orgUrl: string) => { const destinationOrg = manageableOrgs.find((org) => org.url === orgUrl); if (!destinationOrg) { return; } const manageableTeam = destinationOrg.teams.find((team) => canExecuteTeamAction('MANAGE_TEAM', team.currentTeamRole), ); const destinationGroups = getSettingsNavGroups({ organisation: { url: destinationOrg.url, currentOrganisationRole: destinationOrg.currentOrganisationRole, organisationClaim: destinationOrg.organisationClaim, }, team: manageableTeam ? { url: manageableTeam.url, currentTeamRole: manageableTeam.currentTeamRole } : null, hasManageableBillingOrgs, }); // The list also contains organisations the user can only reach through a team they // manage — for those `getSettingsNavGroups` returns no organisation group, so we land // them in the team group instead of on an organisation page they aren't authorised for. const destinationGroup = destinationGroups.organisation ?? destinationGroups.team; if (!destinationGroup) { return; } setIsOpen(false); void navigate( computeSwitcherContinuityPath({ currentPath: pathname, destinationPaths: destinationGroup.items.map((item) => item.path), fallbackPath: destinationGroup.items[0].path, }), ); }; if (!currentOrg) { return null; } // Resolve an organisation's plan label. Unknown or custom claims (including // self-hosted custom claim IDs) fall back to "Custom Plan". const getPlanName = (organisationClaimId: string | null) => { const planClaim = organisationClaimId && organisationClaimId in internalClaims ? internalClaims[organisationClaimId as INTERNAL_CLAIM_ID] : undefined; return planClaim ? t`${planClaim.name} Plan` : t`Custom Plan`; }; return ( {manageableOrgs.length >= SEARCH_THRESHOLD && (
setQuery(e.target.value)} placeholder={t`Search organisations…`} className="h-8 pl-7" data-testid="settings-org-switcher-search" />
)}
); };