OrganizationPicker.tsx127 lines · main
1import { ChevronDown } from 'lucide-react'
2import { useMemo, useRef, useState } from 'react'
3import {
4 Badge,
5 Button,
6 cn,
7 Command,
8 CommandEmpty,
9 CommandGroup,
10 CommandInput,
11 CommandItem,
12 CommandList,
13 Popover,
14 PopoverContent,
15 PopoverTrigger,
16} from 'ui'
17
18import { getHasInstalledObject } from '@/components/layouts/IntegrationsLayout/Integrations.utils'
19import PartnerIcon from '@/components/ui/PartnerIcon'
20import { useIntegrationsQuery } from '@/data/integrations/integrations-query'
21import type { IntegrationName } from '@/data/integrations/integrations.types'
22import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
23import type { Organization } from '@/types'
24
25export interface OrganizationPickerProps {
26 integrationName: IntegrationName
27 configurationId?: string
28 selectedOrg: Organization | null
29 onSelectedOrgChange: (organization: Organization) => void
30 disabled?: boolean
31}
32
33const OrganizationPicker = ({
34 integrationName,
35 configurationId,
36 selectedOrg,
37 onSelectedOrgChange,
38 disabled,
39}: OrganizationPickerProps) => {
40 const [open, setOpen] = useState(false)
41 const ref = useRef<HTMLButtonElement>(null)
42
43 const { data: integrationData } = useIntegrationsQuery()
44 const { data: organizationsData, isPending: isLoadingOrganization } = useOrganizationsQuery()
45
46 const installed = useMemo(
47 () =>
48 integrationData && organizationsData
49 ? getHasInstalledObject({
50 integrationName,
51 integrationData,
52 organizationsData,
53 installationId: configurationId,
54 })
55 : {},
56 [configurationId, integrationData, integrationName, organizationsData]
57 )
58
59 return (
60 <>
61 <Popover open={open} onOpenChange={setOpen}>
62 <PopoverTrigger asChild>
63 <Button
64 ref={ref}
65 type="default"
66 size="medium"
67 block
68 className="justify-start"
69 loading={isLoadingOrganization}
70 disabled={disabled}
71 iconRight={
72 <span className="grow flex justify-end">
73 <ChevronDown />
74 </span>
75 }
76 >
77 <div className="flex gap-2">
78 <span className={cn('truncate', !selectedOrg && 'text-foreground-light')}>
79 {selectedOrg?.name ? selectedOrg?.name : 'Choose an organization'}
80 </span>
81 {selectedOrg && configurationId && installed[selectedOrg.slug] && (
82 <Badge>Integration Installed</Badge>
83 )}
84 </div>
85 </Button>
86 </PopoverTrigger>
87 <PopoverContent className="p-0 w-full" side="bottom" align="center" sameWidthAsTrigger>
88 <Command>
89 <CommandInput placeholder="Search organizations..." />
90 <CommandList>
91 <CommandEmpty>No results found.</CommandEmpty>
92 <CommandGroup>
93 {organizationsData?.map((org) => {
94 return (
95 <CommandItem
96 value={org.slug}
97 key={org.slug}
98 className="flex gap-2 items-center"
99 onSelect={(slug) => {
100 const org = organizationsData?.find(
101 (org) => org.slug.toLowerCase() === slug.toLowerCase()
102 )
103 if (org) {
104 onSelectedOrgChange(org)
105 }
106
107 setOpen(false)
108 }}
109 >
110 <PartnerIcon organization={org} />
111 <span className="truncate">{org.name}</span>{' '}
112 {configurationId && installed[org.slug] && (
113 <Badge className="flex-none!">Integration Installed</Badge>
114 )}
115 </CommandItem>
116 )
117 })}
118 </CommandGroup>
119 </CommandList>
120 </Command>
121 </PopoverContent>
122 </Popover>
123 </>
124 )
125}
126
127export default OrganizationPicker