diff --git a/frontend/src/hooks/api/accessApproval/mutation.tsx b/frontend/src/hooks/api/accessApproval/mutation.tsx index 251e1c626..bde46dc7f 100644 --- a/frontend/src/hooks/api/accessApproval/mutation.tsx +++ b/frontend/src/hooks/api/accessApproval/mutation.tsx @@ -16,12 +16,20 @@ export const useCreateAccessApprovalPolicy = () => { const queryClient = useQueryClient(); return useMutation<{}, {}, TCreateAccessPolicyDTO>({ - mutationFn: async ({ environment, projectSlug, approvals, approvers, name, secretPath, enforcementLevel }) => { + mutationFn: async ({ + environment, + projectSlug, + approvals, + approverUserIds, + name, + secretPath, + enforcementLevel + }) => { const { data } = await apiRequest.post("/api/v1/access-approvals/policies", { environment, projectSlug, approvals, - approvers, + approverUserIds, secretPath, name, enforcementLevel diff --git a/frontend/src/hooks/api/accessApproval/types.ts b/frontend/src/hooks/api/accessApproval/types.ts index e17fe41d0..111c6a8c4 100644 --- a/frontend/src/hooks/api/accessApproval/types.ts +++ b/frontend/src/hooks/api/accessApproval/types.ts @@ -23,7 +23,14 @@ export type TAccessApprovalRequest = { id: string; policyId: string; privilegeId: string | null; - requestedBy: string; + requestedByUserId: string; + requestedByUser: { + email: string; + firstName?: string; + lastName?: string; + userId: string; + username: string; + }; createdAt: Date; updatedAt: Date; isTemporary: boolean; @@ -123,7 +130,7 @@ export type TCreateAccessPolicyDTO = { projectSlug: string; name?: string; environment: string; - approvers?: string[]; + approverUserIds?: string[]; approvals?: number; secretPath?: string; enforcementLevel?: EnforcementLevel; diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 1e5661d1a..825d468da 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -83,6 +83,7 @@ export type TWorkspaceUser = { publicKey: string; }; projectId: string; + isGroupMember: boolean; project: { id: string; name: string; diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 277499e9b..8f1b065da 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -377,14 +377,19 @@ export const useDeleteWsEnvironment = () => { }); }; -export const useGetWorkspaceUsers = (workspaceId: string) => { +export const useGetWorkspaceUsers = (workspaceId: string, includeGroupMembers?: boolean) => { return useQuery({ queryKey: workspaceKeys.getWorkspaceUsers(workspaceId), queryFn: async () => { const { data: { users } } = await apiRequest.get<{ users: TWorkspaceUser[] }>( - `/api/v1/workspace/${workspaceId}/users` + `/api/v1/workspace/${workspaceId}/users`, + { + params: { + includeGroupMembers + } + } ); return users; }, diff --git a/frontend/src/views/SecretApprovalPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx b/frontend/src/views/SecretApprovalPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx index b57c0c3ab..93e906373 100644 --- a/frontend/src/views/SecretApprovalPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx +++ b/frontend/src/views/SecretApprovalPage/components/AccessApprovalRequest/AccessApprovalRequest.tsx @@ -29,6 +29,7 @@ import { ProjectPermissionSub, useProjectPermission, useSubscription, + useUser, useWorkspace } from "@app/context"; import { usePopUp } from "@app/hooks"; @@ -47,7 +48,7 @@ import { queryClient } from "@app/reactQuery"; import { RequestAccessModal } from "./components/RequestAccessModal"; import { ReviewAccessRequestModal } from "./components/ReviewAccessModal"; -const generateRequestText = (request: TAccessApprovalRequest, membershipId: string) => { +const generateRequestText = (request: TAccessApprovalRequest, userId: string) => { const { isTemporary } = request; return ( @@ -63,7 +64,7 @@ const generateRequestText = (request: TAccessApprovalRequest, membershipId: stri
- {request.requestedBy === membershipId && ( + {request.requestedByUserId === userId && ( Requested By You @@ -81,11 +82,11 @@ export const AccessApprovalRequest = ({ projectId: string; }) => { const [selectedRequest, setSelectedRequest] = useState< - (TAccessApprovalRequest & { - user: TWorkspaceUser["user"] | null; - isRequestedByCurrentUser: boolean; - isApprover: boolean; - }) + | (TAccessApprovalRequest & { + user: TWorkspaceUser["user"] | null; + isRequestedByCurrentUser: boolean; + isApprover: boolean; + }) | null >(null); @@ -94,16 +95,19 @@ export const AccessApprovalRequest = ({ "reviewRequest", "upgradePlan" ] as const); - const { membership, permission } = useProjectPermission(); + const { permission } = useProjectPermission(); + const { user } = useUser(); const { subscription } = useSubscription(); const { currentWorkspace } = useWorkspace(); - const { data: members } = useGetWorkspaceUsers(projectId); + const { data: members } = useGetWorkspaceUsers(projectId, true); const membersGroupById = members?.reduce>( - (prev, curr) => ({ ...prev, [curr.id]: curr }), + (prev, curr) => ({ ...prev, [curr.user.id]: curr }), {} ); + console.log("membersGroupById", membersGroupById); + const [statusFilter, setStatusFilter] = useState<"open" | "close">("open"); const [requestedByFilter, setRequestedByFilter] = useState(undefined); const [envFilter, setEnvFilter] = useState(undefined); @@ -140,19 +144,18 @@ export const AccessApprovalRequest = ({ }, [requests, statusFilter, requestedByFilter, envFilter]); const generateRequestDetails = (request: TAccessApprovalRequest) => { - const isReviewedByUser = - request.reviewers.findIndex(({ member }) => member === membership.id) !== -1; + console.log(request); + + const isReviewedByUser = request.reviewers.findIndex(({ member }) => member === user.id) !== -1; const isRejectedByAnyone = request.reviewers.some( ({ status }) => status === ApprovalStatus.REJECTED ); - const isApprover = request.policy.approvers.indexOf(membership.id || "") !== -1; + const isApprover = request.policy.approvers.indexOf(user.id || "") !== -1; const isAccepted = request.isApproved; const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft; - const isRequestedByCurrentUser = request.requestedBy === membership.id; + const isRequestedByCurrentUser = request.requestedByUserId === user.id; - const userReviewStatus = request.reviewers.find( - ({ member }) => member === membership.id - )?.status; + const userReviewStatus = request.reviewers.find(({ member }) => member === user.id)?.status; let displayData: { label: string; type: "primary" | "danger" | "success" } = { label: "", @@ -303,7 +306,7 @@ export const AccessApprovalRequest = ({ Select an author - {members?.map(({ user, id }) => ( + {members?.map(({ user: membershipUser, id }) => ( setRequestedByFilter((state) => (state === id ? undefined : id)) @@ -312,7 +315,7 @@ export const AccessApprovalRequest = ({ icon={requestedByFilter === id && } iconPos="right" > - {user.username} + {membershipUser.username} ))} @@ -341,22 +344,21 @@ export const AccessApprovalRequest = ({ tabIndex={0} onClick={() => { if ( - ( - !details.isApprover - || details.isReviewedByUser - || details.isRejectedByAnyone - || details.isAccepted - ) && !( - details.isSoftEnforcement - && details.isRequestedByCurrentUser - && !details.isAccepted + (!details.isApprover || + details.isReviewedByUser || + details.isRejectedByAnyone || + details.isAccepted) && + !( + details.isSoftEnforcement && + details.isRequestedByCurrentUser && + !details.isAccepted ) ) return; setSelectedRequest({ ...request, - user: membersGroupById?.[request.requestedBy].user!, + user: membersGroupById?.[request.requestedByUserId].user!, isRequestedByCurrentUser: details.isRequestedByCurrentUser, isApprover: details.isApprover }); @@ -373,7 +375,7 @@ export const AccessApprovalRequest = ({ if (evt.key === "Enter") { setSelectedRequest({ ...request, - user: membersGroupById?.[request.requestedBy].user!, + user: membersGroupById?.[request.requestedByUserId].user!, isRequestedByCurrentUser: details.isRequestedByCurrentUser, isApprover: details.isApprover }); @@ -385,16 +387,17 @@ export const AccessApprovalRequest = ({
- {generateRequestText(request, membership.id)} + {generateRequestText(request, user.id)}
- {membersGroupById?.[request.requestedBy]?.user && ( + {membersGroupById?.[request.requestedByUserId]?.user && ( <> Requested {formatDistance(new Date(request.createdAt), new Date())}{" "} - ago by {membersGroupById?.[request.requestedBy]?.user?.firstName}{" "} - {membersGroupById?.[request.requestedBy]?.user?.lastName} ( - {membersGroupById?.[request.requestedBy]?.user?.email}){" "} + ago by{" "} + {membersGroupById?.[request.requestedByUserId]?.user?.firstName}{" "} + {membersGroupById?.[request.requestedByUserId]?.user?.lastName} ( + {membersGroupById?.[request.requestedByUserId]?.user?.email}){" "} )}
diff --git a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx index 2639f2351..7234c18ce 100644 --- a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx +++ b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/ApprovalPolicyList.tsx @@ -1,5 +1,10 @@ -import { useMemo,useState } from "react"; -import { faCheckCircle,faChevronDown, faFileShield, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { useMemo, useState } from "react"; +import { + faCheckCircle, + faChevronDown, + faFileShield, + faPlus +} from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; @@ -32,7 +37,12 @@ import { useWorkspace } from "@app/context"; import { usePopUp } from "@app/hooks"; -import { useDeleteAccessApprovalPolicy, useDeleteSecretApprovalPolicy, useGetSecretApprovalPolicies, useGetWorkspaceUsers } from "@app/hooks/api"; +import { + useDeleteAccessApprovalPolicy, + useDeleteSecretApprovalPolicy, + useGetSecretApprovalPolicies, + useGetWorkspaceUsers +} from "@app/hooks/api"; import { useGetAccessApprovalPolicies } from "@app/hooks/api/accessApproval/queries"; import { PolicyType } from "@app/hooks/api/policies/enums"; import { TAccessApprovalPolicy, Workspace } from "@app/hooks/api/types"; @@ -45,27 +55,32 @@ interface IProps { } const useApprovalPolicies = (permission: TProjectPermission, currentWorkspace?: Workspace) => { - const { data: accessPolicies, isLoading: isAccessPoliciesLoading } = useGetAccessApprovalPolicies({ - projectSlug: currentWorkspace?.slug as string, - options: { - enabled: - permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && - !!currentWorkspace?.slug + const { data: accessPolicies, isLoading: isAccessPoliciesLoading } = useGetAccessApprovalPolicies( + { + projectSlug: currentWorkspace?.slug as string, + options: { + enabled: + permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && + !!currentWorkspace?.slug + } } - }); - const { data: secretPolicies, isLoading: isSecretPoliciesLoading } = useGetSecretApprovalPolicies({ - workspaceId: currentWorkspace?.id as string, - options: { - enabled: - permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && - !!currentWorkspace?.id + ); + const { data: secretPolicies, isLoading: isSecretPoliciesLoading } = useGetSecretApprovalPolicies( + { + workspaceId: currentWorkspace?.id as string, + options: { + enabled: + permission.can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval) && + !!currentWorkspace?.id + } } - }); + ); // merge data sorted by updatedAt const policies = [ - ...(accessPolicies?.map(policy => ({ ...policy, policyType: PolicyType.AccessPolicy })) || []), - ...(secretPolicies?.map(policy => ({ ...policy, policyType: PolicyType.ChangePolicy })) || []) + ...(accessPolicies?.map((policy) => ({ ...policy, policyType: PolicyType.AccessPolicy })) || + []), + ...(secretPolicies?.map((policy) => ({ ...policy, policyType: PolicyType.ChangePolicy })) || []) ].sort((a, b) => { return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); }); @@ -86,15 +101,16 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { const { subscription } = useSubscription(); const { currentWorkspace } = useWorkspace(); - const { data: members } = useGetWorkspaceUsers(workspaceId); - const { policies, isLoading: isPoliciesLoading } = useApprovalPolicies(permission, currentWorkspace); + const { data: members } = useGetWorkspaceUsers(workspaceId, true); + const { policies, isLoading: isPoliciesLoading } = useApprovalPolicies( + permission, + currentWorkspace + ); const [filterType, setFilterType] = useState(null); const filteredPolicies = useMemo(() => { - return filterType - ? policies.filter(policy => policy.policyType === filterType) - : policies; + return filterType ? policies.filter((policy) => policy.policyType === filterType) : policies; }, [policies, filterType]); const { mutateAsync: deleteSecretApprovalPolicy } = useDeleteSecretApprovalPolicy(); @@ -177,8 +193,10 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { @@ -194,14 +212,22 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => { setFilterType(PolicyType.AccessPolicy)} - icon={filterType === PolicyType.AccessPolicy && } + icon={ + filterType === PolicyType.AccessPolicy && ( + + ) + } iconPos="right" > Access Policy setFilterType(PolicyType.ChangePolicy)} - icon={filterType === PolicyType.ChangePolicy && } + icon={ + filterType === PolicyType.ChangePolicy && ( + + ) + } iconPos="right" > Change Policy diff --git a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx index c9eb61185..7bec3ccde 100644 --- a/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx +++ b/frontend/src/views/SecretApprovalPage/components/ApprovalPolicyList/components/AccessPolicyModal.tsx @@ -7,363 +7,398 @@ import { z } from "zod"; import { createNotification } from "@app/components/notifications"; import { - Button, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, - FormControl, - Input, - Modal, - ModalContent, - Select, - SelectItem + Button, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger, + FormControl, + Input, + Modal, + ModalContent, + Select, + SelectItem } from "@app/components/v2"; import { useWorkspace } from "@app/context"; import { policyDetails } from "@app/helpers/policies"; import { useCreateSecretApprovalPolicy, useUpdateSecretApprovalPolicy } from "@app/hooks/api"; import { - useCreateAccessApprovalPolicy, - useUpdateAccessApprovalPolicy + useCreateAccessApprovalPolicy, + useUpdateAccessApprovalPolicy } from "@app/hooks/api/accessApproval"; import { TAccessApprovalPolicy } from "@app/hooks/api/accessApproval/types"; import { EnforcementLevel, PolicyType } from "@app/hooks/api/policies/enums"; import { TWorkspaceUser } from "@app/hooks/api/users/types"; type Props = { - isOpen?: boolean; - onToggle: (isOpen: boolean) => void; - members?: TWorkspaceUser[]; - projectSlug: string; - editValues?: TAccessApprovalPolicy; + isOpen?: boolean; + onToggle: (isOpen: boolean) => void; + members?: TWorkspaceUser[]; + projectSlug: string; + editValues?: TAccessApprovalPolicy; }; const formSchema = z -.object({ - environment: z.string(), - name: z.string().optional(), - secretPath: z.string().optional(), - approvals: z.number().min(1), - approvers: z.string().array().min(1), - policyType: z.nativeEnum(PolicyType), - enforcementLevel: z.nativeEnum(EnforcementLevel) -}) -.refine((data) => data.approvals <= data.approvers.length, { - path: ["approvals"], - message: "The number of approvals should be lower than the number of approvers." -}); + .object({ + environment: z.string(), + name: z.string().optional(), + secretPath: z.string().optional(), + approvals: z.number().min(1), + approverUserIds: z.string().array().min(1), + policyType: z.nativeEnum(PolicyType), + enforcementLevel: z.nativeEnum(EnforcementLevel) + }) + .refine((data) => data.approvals <= data.approverUserIds.length, { + path: ["approvals"], + message: "The number of approvals should be lower than the number of approvers." + }); type TFormSchema = z.infer; export const AccessPolicyForm = ({ - isOpen, - onToggle, - members = [], - projectSlug, - editValues + isOpen, + onToggle, + members = [], + projectSlug, + editValues }: Props) => { - const { - control, - handleSubmit, - reset, - watch, - formState: { isSubmitting } - } = useForm({ - resolver: zodResolver(formSchema), - values: editValues ? { - ...editValues, - environment: editValues.environment.slug, - approvers: editValues?.userApprovers?.map((user) => user.userId) || editValues?.approvers - } : undefined - }); - const { currentWorkspace } = useWorkspace(); - - const environments = currentWorkspace?.environments || []; - const isEditMode = Boolean(editValues); - - useEffect(() => { - if (!isOpen || !isEditMode) reset({}); - }, [isOpen, isEditMode]); - - const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy(); - const { mutateAsync: updateAccessApprovalPolicy } = useUpdateAccessApprovalPolicy(); - - const { mutateAsync: createSecretApprovalPolicy } = useCreateSecretApprovalPolicy(); - const { mutateAsync: updateSecretApprovalPolicy } = useUpdateSecretApprovalPolicy(); + const { + control, + handleSubmit, + reset, + watch, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(formSchema), + values: editValues + ? { + ...editValues, + environment: editValues.environment.slug, + approverUserIds: + editValues?.userApprovers?.map((user) => user.userId) || editValues?.approvers + } + : undefined + }); + const { currentWorkspace } = useWorkspace(); - const policyName = policyDetails[watch("policyType")]?.name || "Policy"; - - const handleCreatePolicy = async (data: TFormSchema) => { - if (!projectSlug) return; - - try { - if (data.policyType === PolicyType.ChangePolicy) { - await createSecretApprovalPolicy({ - ...data, - workspaceId: currentWorkspace?.id || "" - }); - } else { - await createAccessApprovalPolicy({ - ...data, - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully created policy" - }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "Failed to create policy" - }); - } - }; - - const handleUpdatePolicy = async (data: TFormSchema) => { - if (!projectSlug) return; - if (!editValues?.id) return; - - try { - if (data.policyType === PolicyType.ChangePolicy) { - await updateSecretApprovalPolicy({ - id: editValues?.id, - ...data, - workspaceId: currentWorkspace?.id || "" - }); - } else { - await updateAccessApprovalPolicy({ - id: editValues?.id, - ...data, - projectSlug - }); - } - createNotification({ - type: "success", - text: "Successfully updated policy" - }); - onToggle(false); - } catch (err) { - console.log(err); - createNotification({ - type: "error", - text: "failed to update policy" - }); - } - }; - - const handleFormSubmit = async (data: TFormSchema) => { - if (isEditMode) { - await handleUpdatePolicy(data); - } else { - await handleCreatePolicy(data); - } - }; + const environments = currentWorkspace?.environments || []; + const isEditMode = Boolean(editValues); - const formatEnforcementLevel = (level: EnforcementLevel) => { - if (level === EnforcementLevel.Hard) return "Hard"; - if (level === EnforcementLevel.Soft) return "Soft"; - return level; - }; - - return ( - - -
-
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - )} - /> - ( - - - - - - - - Select members that are allowed to approve requests - - {members.map(({ id, user }) => { - const userId = watch("policyType") === PolicyType.ChangePolicy ? user.id : id; - const isChecked = value?.includes(userId); - return ( - { - evt.preventDefault(); - onChange( - isChecked ? value?.filter((el: string) => el !== userId) : [...(value || []), userId] - ); - }} - key={`create-policy-members-${userId}`} - iconPos="right" - icon={isChecked && } - > - {user.username} - - ); - })} - - - - )} - /> - ( - - field.onChange(parseInt(el.target.value, 10))} - /> - - )} - /> - ( - - - - )} - /> -
- - -
- -
-
-
- ); + useEffect(() => { + if (!isOpen || !isEditMode) reset({}); + }, [isOpen, isEditMode]); + + const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy(); + const { mutateAsync: updateAccessApprovalPolicy } = useUpdateAccessApprovalPolicy(); + + const { mutateAsync: createSecretApprovalPolicy } = useCreateSecretApprovalPolicy(); + const { mutateAsync: updateSecretApprovalPolicy } = useUpdateSecretApprovalPolicy(); + + const policyName = policyDetails[watch("policyType")]?.name || "Policy"; + + const handleCreatePolicy = async (data: TFormSchema) => { + if (!projectSlug) return; + + try { + if (data.policyType === PolicyType.ChangePolicy) { + await createSecretApprovalPolicy({ + ...data, + workspaceId: currentWorkspace?.id || "" + }); + } else { + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + console.log(data); + await createAccessApprovalPolicy({ + ...data, + projectSlug + }); + } + createNotification({ + type: "success", + text: "Successfully created policy" + }); + onToggle(false); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "Failed to create policy" + }); + } + }; + + const handleUpdatePolicy = async (data: TFormSchema) => { + if (!projectSlug) return; + if (!editValues?.id) return; + + try { + if (data.policyType === PolicyType.ChangePolicy) { + await updateSecretApprovalPolicy({ + id: editValues?.id, + ...data, + workspaceId: currentWorkspace?.id || "" + }); + } else { + await updateAccessApprovalPolicy({ + id: editValues?.id, + ...data, + projectSlug + }); + } + createNotification({ + type: "success", + text: "Successfully updated policy" + }); + onToggle(false); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "failed to update policy" + }); + } + }; + + const handleFormSubmit = async (data: TFormSchema) => { + if (isEditMode) { + await handleUpdatePolicy(data); + } else { + await handleCreatePolicy(data); + } + }; + + const formatEnforcementLevel = (level: EnforcementLevel) => { + if (level === EnforcementLevel.Hard) return "Hard"; + if (level === EnforcementLevel.Soft) return "Soft"; + return level; + }; + + return ( + + +
+
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + + + + + Select members that are allowed to approve requests + + {members.map(({ user }) => { + const { id: userId } = user; + const isChecked = value?.includes(userId); + return ( + { + evt.preventDefault(); + onChange( + isChecked + ? value?.filter((el: string) => el !== userId) + : [...(value || []), userId] + ); + }} + key={`create-policy-members-${userId}`} + iconPos="right" + icon={isChecked && } + > + {user.username} + + ); + })} + + + + )} + /> + ( + + field.onChange(parseInt(el.target.value, 10))} + /> + + )} + /> + ( + + + + )} + /> +
+ + +
+ +
+
+
+ ); }; -