PolicyRoles.tsx86 lines · main
1import { sortBy } from 'lodash'
2import {
3 MultiSelector,
4 MultiSelectorContent,
5 MultiSelectorItem,
6 MultiSelectorList,
7 MultiSelectorTrigger,
8} from 'ui-patterns/multi-select'
9import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
10
11import { SYSTEM_ROLES } from '@/components/interfaces/Database/Roles/Roles.constants'
12import AlertError from '@/components/ui/AlertError'
13import { useDatabaseRolesQuery } from '@/data/database-roles/database-roles-query'
14import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
15
16interface PolicyRolesProps {
17 selectedRoles: string[]
18 onUpdateSelectedRoles: (roles: string[]) => void
19}
20type SystemRole = (typeof SYSTEM_ROLES)[number]
21
22export const PolicyRoles = ({ selectedRoles, onUpdateSelectedRoles }: PolicyRolesProps) => {
23 const { data: project } = useSelectedProjectQuery()
24 const {
25 data,
26 error,
27 isPending: isLoading,
28 isError,
29 isSuccess,
30 } = useDatabaseRolesQuery({
31 projectRef: project?.ref,
32 connectionString: project?.connectionString,
33 })
34 const roles = sortBy(
35 (data ?? []).filter((role) => !SYSTEM_ROLES.includes(role.name as SystemRole)),
36 (r) => r.name.toLocaleLowerCase()
37 )
38
39 const formattedRoles = roles.map((role) => {
40 return {
41 id: role.id,
42 name: role.name,
43 value: role.name,
44 disabled: false,
45 }
46 })
47
48 return (
49 <div className="flex flex-col md:flex-row gap-4 md:gap-12">
50 <div className="flex md:w-1/3 flex-col space-y-2">
51 <label className="text-foreground-light text-base" htmlFor="policy-name">
52 Target roles
53 </label>
54 <p className="text-foreground-lighter text-sm">Apply policy to the selected roles</p>
55 </div>
56 <div className="relative md:w-2/3">
57 {isLoading && <ShimmeringLoader className="py-4" />}
58 {isError && <AlertError error={error as any} subject="Failed to retrieve database roles" />}
59 {isSuccess && (
60 <MultiSelector values={selectedRoles} onValuesChange={onUpdateSelectedRoles}>
61 <MultiSelectorTrigger
62 mode="inline-combobox"
63 label={
64 selectedRoles.length === 0
65 ? 'Defaults to all (public) roles if none selected'
66 : 'Search for a role'
67 }
68 deletableBadge
69 badgeLimit="wrap"
70 showIcon={false}
71 />
72 <MultiSelectorContent>
73 <MultiSelectorList>
74 {formattedRoles.map((role) => (
75 <MultiSelectorItem key={role.id} value={role.value} disabled={role.disabled}>
76 {role.name}
77 </MultiSelectorItem>
78 ))}
79 </MultiSelectorList>
80 </MultiSelectorContent>
81 </MultiSelector>
82 )}
83 </div>
84 </div>
85 )
86}