OrganizationSelector.tsx96 lines · main
| 1 | import { PermissionAction } from '@supabase/shared-types/out/constants' |
| 2 | import { useQueryClient } from '@tanstack/react-query' |
| 3 | import { useParams } from 'common' |
| 4 | import { useRouter } from 'next/router' |
| 5 | import { useEffect } from 'react' |
| 6 | import { UseFormReturn } from 'react-hook-form' |
| 7 | import { |
| 8 | Badge, |
| 9 | FormControl, |
| 10 | FormField, |
| 11 | Select, |
| 12 | SelectContent, |
| 13 | SelectGroup, |
| 14 | SelectItem, |
| 15 | SelectTrigger, |
| 16 | SelectValue, |
| 17 | } from 'ui' |
| 18 | import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' |
| 19 | |
| 20 | import { OrgNotFound } from '../Organization/OrgNotFound' |
| 21 | import { CreateProjectForm } from './ProjectCreation.schema' |
| 22 | import { NoPermission } from '@/components/ui/NoPermission' |
| 23 | import Panel from '@/components/ui/Panel' |
| 24 | import { useOrganizationsQuery } from '@/data/organizations/organizations-query' |
| 25 | import { permissionKeys } from '@/data/permissions/keys' |
| 26 | import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' |
| 27 | import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' |
| 28 | |
| 29 | interface OrganizationSelectorProps { |
| 30 | form: UseFormReturn<CreateProjectForm> |
| 31 | } |
| 32 | |
| 33 | export const OrganizationSelector = ({ form }: OrganizationSelectorProps) => { |
| 34 | const router = useRouter() |
| 35 | const { slug } = useParams() |
| 36 | const queryClient = useQueryClient() |
| 37 | const { data: currentOrg } = useSelectedOrganizationQuery() |
| 38 | const { can: isAdmin } = useAsyncCheckPermissions(PermissionAction.CREATE, 'projects') |
| 39 | |
| 40 | // Permissions may be stale for newly created accounts due to replication lag between |
| 41 | // org setup and the permissions endpoint. Invalidate in the background on mount so the |
| 42 | // check reflects the latest state before the user tries to create a project. |
| 43 | useEffect(() => { |
| 44 | queryClient.invalidateQueries({ queryKey: permissionKeys.list() }) |
| 45 | }, [queryClient]) |
| 46 | |
| 47 | const { data: organizations, isSuccess: isOrganizationsSuccess } = useOrganizationsQuery() |
| 48 | const isInvalidSlug = isOrganizationsSuccess && currentOrg === undefined |
| 49 | const orgNotFound = (organizations?.length ?? 0) > 0 && isInvalidSlug |
| 50 | |
| 51 | return ( |
| 52 | <Panel.Content className="space-y-4"> |
| 53 | <FormField |
| 54 | control={form.control} |
| 55 | name="organization" |
| 56 | render={({ field }) => ( |
| 57 | <FormItemLayout label="Organization" layout="horizontal"> |
| 58 | {(organizations?.length ?? 0) > 0 && ( |
| 59 | <Select |
| 60 | onValueChange={(slug) => { |
| 61 | field.onChange(slug) |
| 62 | router.push(`/new/${slug}`) |
| 63 | }} |
| 64 | value={field.value} |
| 65 | defaultValue={field.value} |
| 66 | > |
| 67 | <FormControl> |
| 68 | <SelectTrigger> |
| 69 | <SelectValue placeholder="Select an organization" /> |
| 70 | </SelectTrigger> |
| 71 | </FormControl> |
| 72 | <SelectContent> |
| 73 | <SelectGroup> |
| 74 | {organizations?.map((x) => ( |
| 75 | <SelectItem key={x.id} value={x.slug}> |
| 76 | <div className="flex justify-between items-center gap-2 w-full"> |
| 77 | <span>{x.name}</span> |
| 78 | <Badge className="mt-px">{x.plan.name}</Badge> |
| 79 | </div> |
| 80 | </SelectItem> |
| 81 | ))} |
| 82 | </SelectGroup> |
| 83 | </SelectContent> |
| 84 | </Select> |
| 85 | )} |
| 86 | </FormItemLayout> |
| 87 | )} |
| 88 | /> |
| 89 | |
| 90 | {isOrganizationsSuccess && !isAdmin && !orgNotFound && ( |
| 91 | <NoPermission resourceText="create a project" /> |
| 92 | )} |
| 93 | {orgNotFound && <OrgNotFound slug={slug} />} |
| 94 | </Panel.Content> |
| 95 | ) |
| 96 | } |