improvement: add select polices modal

This commit is contained in:
Scott Wilson
2025-05-07 13:50:27 -07:00
parent 0610416677
commit 68c549f1c6
8 changed files with 309 additions and 377 deletions

View File

@@ -6,15 +6,15 @@ import { twMerge } from "tailwind-merge";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent, ModalTrigger } from "@app/components/v2";
import { Button, FormControl, Input } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { isCustomProjectRole } from "@app/helpers/roles";
import { usePopUp } from "@app/hooks";
import { TProjectTemplate, useUpdateProjectTemplate } from "@app/hooks/api/projectTemplates";
import { slugSchema } from "@app/lib/schemas";
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
import { NewPermissionRule } from "@app/pages/project/RoleDetailsBySlugPage/components/NewPermissionRule";
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import {
formRolePermission2API,
PROJECT_PERMISSION_OBJECT,
@@ -44,7 +44,7 @@ export const ProjectTemplateEditRoleForm = ({
role,
isDisabled
}: Props) => {
const { popUp, handlePopUpToggle } = usePopUp(["createPolicy"] as const);
const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const);
const formMethods = useForm<TFormSchema>({
values: role ? { ...role, permissions: rolePermission2Form(role.permissions) } : undefined,
@@ -119,34 +119,29 @@ export const ProjectTemplateEditRoleForm = ({
<Button
variant="outline_bg"
type="submit"
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
className={twMerge(
"h-10 rounded-r-none border border-primary",
isDirty && "bg-primary text-black"
)}
isDisabled={isSubmitting || !isDirty || isDisabled}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={faSave} />}
>
Save
</Button>
<Modal
isOpen={popUp.createPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("createPolicy", isOpen)}
<Button
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={isDisabled}
onClick={() => handlePopUpToggle("addPolicy")}
>
<ModalTrigger asChild>
<Button
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={isDisabled}
>
New Policy
</Button>
</ModalTrigger>
<ModalContent
title="New Policy"
subTitle="Policies grant additional permissions."
>
<NewPermissionRule onClose={() => handlePopUpToggle("createPolicy")} />
</ModalContent>
</Modal>
Add Policies
</Button>
<PolicySelectionModal
isOpen={popUp.addPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addPolicy", isOpen)}
/>
</div>
</div>
)}

View File

@@ -1,3 +1,4 @@
import { useMemo } from "react";
import { useRouterState } from "@tanstack/react-router";
import { ProjectType } from "@app/hooks/api/workspace/types";
@@ -5,9 +6,17 @@ import { ProjectType } from "@app/hooks/api/workspace/types";
export const useGetProjectTypeFromRoute = () => {
const { location } = useRouterState();
const segment = location.pathname.split("/")[2];
return useMemo(() => {
const segments = location.pathname.split("/");
if (!Object.values(ProjectType).includes(segment as ProjectType)) return undefined;
let type: ProjectType | undefined;
return segment as ProjectType;
// location of project type can vary in router path, so we need to check all possible values
segments.forEach((segment) => {
if (Object.values(ProjectType).includes(segment as ProjectType))
type = segment as ProjectType;
});
return type;
}, [location]);
};

View File

@@ -18,10 +18,6 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
FormControl,
FormLabel,
Input,
@@ -37,6 +33,7 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import {
useCreateIdentityProjectAdditionalPrivilege,
useGetIdentityProjectPrivilegeDetails,
@@ -45,12 +42,11 @@ import {
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/identityProjectAdditionalPrivilege/types";
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import {
formRolePermission2API,
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -101,6 +97,7 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
ProjectPermissionIdentityActions.Edit,
subject(ProjectPermissionSub.Identity, { identityId })
);
const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const);
const form = useForm<TFormSchema>({
values: privilegeDetails
@@ -195,30 +192,6 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
}
}
const onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
if (rootPolicyValue && isConditionalSubjects(selectedSubject)) {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[...rootPolicyValue, {}],
{ shouldDirty: true, shouldTouch: true }
);
} else {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[{}],
{
shouldDirty: true,
shouldTouch: true
}
);
}
};
return (
<form
onSubmit={handleSubmit(onSubmit)}
@@ -250,52 +223,25 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
<Button
variant="outline_bg"
type="submit"
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
className={twMerge(
"h-10 rounded-r-none border border-primary",
isDirty && "bg-primary text-black"
)}
isDisabled={isSubmitting || !isDirty || isDisabled}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={faSave} />}
>
Save
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
New policy
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(sub) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
sub as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase()
.localeCompare(
PROJECT_PERMISSION_OBJECT[
b as keyof typeof PROJECT_PERMISSION_OBJECT
].title.toLowerCase()
)
)
.map((permissionSubject) => (
<DropdownMenuItem
key={`permission-create-${permissionSubject}`}
className="py-3"
onClick={() => onNewPolicy(permissionSubject as ProjectPermissionSub)}
>
{PROJECT_PERMISSION_OBJECT[permissionSubject as ProjectPermissionSub].title}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpToggle("addPolicy")}
>
Add Policies
</Button>
</div>
</div>
</div>
@@ -436,6 +382,10 @@ export const IdentityProjectAdditionalPrivilegeModifySection = ({
)
)}
</div>
<PolicySelectionModal
isOpen={popUp.addPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addPolicy", isOpen)}
/>
</FormProvider>
</form>
);

View File

@@ -17,10 +17,6 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
FormControl,
FormLabel,
Input,
@@ -36,6 +32,7 @@ import {
useProjectPermission,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import {
useCreateProjectUserAdditionalPrivilege,
useGetProjectUserPrivilegeDetails,
@@ -44,12 +41,11 @@ import {
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import {
formRolePermission2API,
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
rolePermission2Form
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
@@ -87,6 +83,8 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
projectMembershipId,
isDisabled
}: Props) => {
const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const);
const isCreate = !privilegeId;
const { currentWorkspace } = useWorkspace();
const projectId = currentWorkspace?.id || "";
@@ -167,30 +165,6 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
}
};
const onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
if (rootPolicyValue && isConditionalSubjects(selectedSubject)) {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[...rootPolicyValue, {}],
{ shouldDirty: true, shouldTouch: true }
);
} else {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[{}],
{
shouldDirty: true,
shouldTouch: true
}
);
}
};
const privilegeTemporaryAccess = form.watch("temporaryAccess");
const isTemporary = privilegeTemporaryAccess?.isTemporary;
const isExpired =
@@ -246,52 +220,25 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
<Button
variant="outline_bg"
type="submit"
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
className={twMerge(
"h-10 rounded-r-none border border-primary",
isDirty && "bg-primary text-black"
)}
isDisabled={isSubmitting || !isDirty || isDisabled}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={faSave} />}
>
Save
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
New policy
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
.toLowerCase()
.localeCompare(
PROJECT_PERMISSION_OBJECT[
b as keyof typeof PROJECT_PERMISSION_OBJECT
].title.toLowerCase()
)
)
.map((subject) => (
<DropdownMenuItem
key={`permission-create-${subject}`}
className="py-3"
onClick={() => onNewPolicy(subject as ProjectPermissionSub)}
>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpToggle("addPolicy")}
>
Add Policies
</Button>
</div>
</div>
</div>
@@ -430,6 +377,10 @@ export const MembershipProjectAdditionalPrivilegeModifySection = ({
</GeneralPermissionPolicies>
))}
</div>
<PolicySelectionModal
isOpen={popUp.addPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addPolicy", isOpen)}
/>
</FormProvider>
</form>
);

View File

@@ -1,136 +0,0 @@
import { Controller, useForm, useFormContext } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
Checkbox,
FormControl,
FormLabel,
ModalClose,
Select,
SelectItem
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
projectRoleFormSchema,
ProjectTypePermissionSubjects,
TFormSchema
} from "./ProjectRoleModifySection.utils";
type Props = {
onClose: () => void;
};
export const NewPermissionRule = ({ onClose }: Props) => {
const rootForm = useFormContext<TFormSchema>();
const form = useForm<{
type: ProjectPermissionSub;
permissions: NonNullable<TFormSchema["permissions"]>;
}>({
resolver: zodResolver(
projectRoleFormSchema
.pick({ permissions: true })
.extend({ type: z.nativeEnum(ProjectPermissionSub) })
),
defaultValues: {
type: ProjectPermissionSub.Project
}
});
const selectedSubject = form.watch("type");
const projectType = useGetProjectTypeFromRoute();
return (
<div>
<Controller
control={form.control}
name="type"
defaultValue={ProjectPermissionSub.Project}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Subject" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[projectType ?? ProjectType.SecretManager][
subject as ProjectPermissionSub
]
)
.map((subject) => (
<SelectItem value={subject} key={`permission-create-${subject}`}>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<FormLabel label="Actions" className="my-2" />
<div className="flex flex-grow flex-wrap justify-start gap-8">
{PROJECT_PERMISSION_OBJECT?.[selectedSubject]?.actions?.map(({ label, value }) => (
<Controller
key={`create-permission-${selectedSubject}-${label}`}
name={`permissions.${selectedSubject}.0.${value as any}` as any}
control={form.control}
defaultValue={false}
render={({ field }) => (
<div className="flex items-center justify-center">
<Checkbox
isChecked={field.value}
onCheckedChange={field.onChange}
id={`new-permissions.${selectedSubject}.0.${String(value)}`}
>
{label}
</Checkbox>
</div>
)}
/>
))}
</div>
<div className="mt-8 flex space-x-4">
<Button
onClick={form.handleSubmit((el) => {
const rootPolicyValue = rootForm.getValues("permissions")?.[el.type];
if (rootPolicyValue && isConditionalSubjects(selectedSubject)) {
rootForm.setValue(
`permissions.${el.type}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[...rootPolicyValue, ...(el?.permissions[el.type] || [])],
{ shouldDirty: true, shouldTouch: true }
);
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
rootForm.setValue(`permissions.${el.type}`, el?.permissions?.[el.type], {
shouldDirty: true,
shouldTouch: true
});
}
onClose();
})}
>
Create
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</div>
);
};

View File

@@ -0,0 +1,214 @@
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { faCheck, faSearch, faXmark, faXmarkCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button,
EmptyState,
IconButton,
Input,
Modal,
ModalClose,
ModalContent,
Table,
TableContainer,
TBody,
Td,
Tooltip,
Tr
} from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { useGetProjectTypeFromRoute } from "@app/hooks";
import { ProjectType } from "@app/hooks/api/workspace/types";
import {
isConditionalSubjects,
PROJECT_PERMISSION_OBJECT,
ProjectTypePermissionSubjects,
TFormSchema
} from "./ProjectRoleModifySection.utils";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
};
type ContentProps = {
onClose: () => void;
};
type TForm = { permissions: Record<ProjectPermissionSub, boolean> };
const Content = ({ onClose }: ContentProps) => {
const rootForm = useFormContext<TFormSchema>();
const [search, setSearch] = useState("");
const {
control,
handleSubmit,
formState: { isDirty },
setValue,
reset
} = useForm<TForm>({
defaultValues: {
permissions: Object.fromEntries(
Object.values(ProjectPermissionSub).map((subject) => [subject, false])
)
}
});
const projectType = useGetProjectTypeFromRoute();
const filteredPolicies = Object.entries(PROJECT_PERMISSION_OBJECT)
.filter(
([subject, { title }]) =>
ProjectTypePermissionSubjects[projectType ?? ProjectType.SecretManager][
subject as ProjectPermissionSub
] && (search ? title.toLowerCase().includes(search.toLowerCase()) : true)
)
.sort((a, b) => a[1].title.localeCompare(b[1].title))
.map(([subject]) => subject);
const onSubmit = () =>
handleSubmit((form) => {
Object.entries(form.permissions).forEach(([subject, add]) => {
if (!add) return;
const type = subject as ProjectPermissionSub;
const rootPolicyValue = rootForm.getValues("permissions")?.[type];
if (rootPolicyValue && isConditionalSubjects(subject as ProjectPermissionSub)) {
rootForm.setValue(
`permissions.${type}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[...rootPolicyValue, {}],
{ shouldDirty: true, shouldTouch: true }
);
} else {
rootForm.setValue(
`permissions.${type}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[{}],
{
shouldDirty: true,
shouldTouch: true
}
);
}
});
onClose();
})();
return (
<>
<Input
placeholder="Search policies..."
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faSearch} />}
rightIcon={
search ? (
<IconButton ariaLabel="Clear search" variant="plain" onClick={() => setSearch("")}>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
) : null
}
/>
<TableContainer className="thin-scrollbar mt-4 max-h-[28rem]">
<div className="sticky top-0 z-30 flex justify-between border-b border-b-mineshaft-600 bg-mineshaft-800 py-3 pl-5 pr-4 font-inter text-sm font-medium text-bunker-300">
<span>Resource</span>
<div className="flex gap-2">
<Button
variant="plain"
className="p-0 text-mineshaft-400"
size="xs"
colorSchema="secondary"
onClick={() => {
setValue(
"permissions",
Object.fromEntries(
Object.values(ProjectPermissionSub).map((subject) => [subject, true])
) as TForm["permissions"],
{ shouldDirty: true }
);
}}
>
Select All
</Button>
<Tooltip content="Clear selection">
<IconButton
ariaLabel="Clear selection"
onClick={() => reset()}
variant="plain"
size="xs"
className={`text-mineshaft-400 ${!isDirty ? "pointer-events-none opacity-50" : ""} hover:text-red`}
isDisabled={!isDirty}
>
<FontAwesomeIcon icon={faXmarkCircle} />
</IconButton>
</Tooltip>
</div>
</div>
<Table>
<TBody>
{filteredPolicies.map((subject) => (
<Controller
control={control}
key={`permission-add-${subject}`}
render={({ field: { value, onChange } }) => (
<Tr
className={`${value ? "bg-mineshaft-600/30" : ""} cursor-pointer hover:bg-mineshaft-700`}
onClick={() => onChange(!value)}
>
<Td className={`${value ? "text-mineshaft-100" : "text-mineshaft-300"} w-full`}>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</Td>
<Td>
{value ? <FontAwesomeIcon className="text-green" icon={faCheck} /> : null}
</Td>
</Tr>
)}
name={`permissions.${subject as ProjectPermissionSub}`}
/>
))}
</TBody>
</Table>
{!filteredPolicies.length && (
<EmptyState
iconSize="2x"
icon={faSearch}
className="!pb-4 !pt-8"
title="No polices match search"
/>
)}
</TableContainer>
<div className="mt-8 flex space-x-4">
<Button isDisabled={!isDirty} onClick={onSubmit}>
Add Policies
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</>
);
};
export const PolicySelectionModal = ({ isOpen, onOpenChange }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
title="Add Policies"
subTitle="Select one or more policies to add to this role."
className="max-w-3xl"
>
<Content onClose={() => onOpenChange(false)} />
</ModalContent>
</Modal>
);
};

View File

@@ -913,7 +913,7 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
actions: [{ label: "Modify", value: "edit" }]
},
[ProjectPermissionSub.Integrations]: {
title: "Integrations",
title: "Native Integrations",
actions: [
{ label: "Read", value: "read" },
{ label: "Create", value: "create" },

View File

@@ -8,19 +8,15 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications";
import { AccessTree } from "@app/components/permissions";
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { Button } from "@app/components/v2";
import { ProjectPermissionSub, useWorkspace } from "@app/context";
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
import { evaluatePermissionsAbility } from "@app/helpers/permissions";
import { usePopUp } from "@app/hooks";
import { useGetProjectRoleBySlug, useUpdateProjectRole } from "@app/hooks/api";
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
import { ProjectType } from "@app/hooks/api/workspace/types";
import { PolicySelectionModal } from "@app/pages/project/RoleDetailsBySlugPage/components/PolicySelectionModal";
import { DynamicSecretPermissionConditions } from "./DynamicSecretPermissionConditions";
import { GeneralPermissionConditions } from "./GeneralPermissionConditions";
@@ -90,6 +86,8 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
const { mutateAsync: updateRole } = useUpdateProjectRole();
const { popUp, handlePopUpToggle } = usePopUp(["addPolicy"] as const);
const onSubmit = async (el: TFormSchema) => {
try {
if (!projectId || !role?.id) return;
@@ -110,30 +108,6 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
(role?.slug ?? "") as ProjectMembershipRole
);
const onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
if (rootPolicyValue && isConditionalSubjects(selectedSubject)) {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[...rootPolicyValue, {}],
{ shouldDirty: true, shouldTouch: true }
);
} else {
form.setValue(
`permissions.${selectedSubject}`,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
[{}],
{
shouldDirty: true,
shouldTouch: true
}
);
}
};
const permissions = form.watch("permissions");
const formattedPermissions = useMemo(
@@ -176,54 +150,25 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
<Button
variant="outline_bg"
type="submit"
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
className={twMerge(
"h-10 rounded-r-none border border-primary",
isDirty && "bg-primary text-black"
)}
isDisabled={isSubmitting || !isDirty}
isLoading={isSubmitting}
leftIcon={<FontAwesomeIcon icon={faSave} />}
>
Save
</Button>
<DropdownMenu>
<DropdownMenuTrigger>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
New policy
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
{Object.keys(PROJECT_PERMISSION_OBJECT)
.filter(
(subject) =>
ProjectTypePermissionSubjects[currentWorkspace.type][
subject as ProjectPermissionSub
]
)
.sort((a, b) =>
PROJECT_PERMISSION_OBJECT[
a as keyof typeof PROJECT_PERMISSION_OBJECT
].title
.toLowerCase()
.localeCompare(
PROJECT_PERMISSION_OBJECT[
b as keyof typeof PROJECT_PERMISSION_OBJECT
].title.toLowerCase()
)
)
.map((subject) => (
<DropdownMenuItem
key={`permission-create-${subject}`}
className="py-3"
onClick={() => onNewPolicy(subject as ProjectPermissionSub)}
>
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
<Button
isDisabled={isDisabled}
className="h-10 rounded-l-none"
variant="outline_bg"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpToggle("addPolicy")}
>
Add Policy
</Button>
</div>
</>
)}
@@ -245,6 +190,10 @@ export const RolePermissionsSection = ({ roleSlug, isDisabled }: Props) => {
</GeneralPermissionPolicies>
))}
</div>
<PolicySelectionModal
isOpen={popUp.addPolicy.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("addPolicy", isOpen)}
/>
</FormProvider>
</form>
</div>