diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx index a6e472976..f0388a2f4 100644 --- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx +++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies.tsx @@ -1,8 +1,9 @@ -import { cloneElement } from "react"; +import { cloneElement, useState } from "react"; import { Controller, useFieldArray, useFormContext } from "react-hook-form"; import { faChevronDown, faChevronRight, + faGripVertical, faInfoCircle, faPlus, faTrash @@ -36,14 +37,44 @@ export const GeneralPermissionPolicies = ) => { const { control, watch } = useFormContext(); - const items = useFieldArray({ + const { fields, remove, insert, move } = useFieldArray({ control, name: `permissions.${subject}` }); const [isOpen, setIsOpen] = useToggle(); - // const [hideFullReadAccess, setHideFullReadAccess] = useState(false); + const [draggedItem, setDraggedItem] = useState(null); + const [dragOverItem, setDragOverItem] = useState(null); - if (!items.fields.length) return
; + if (!fields.length) return
; + + const handleDragStart = (_: React.DragEvent, index: number) => { + setDraggedItem(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + setDragOverItem(index); + }; + + const handleDrop = (e: React.DragEvent) => { + e.preventDefault(); + + if (draggedItem === null || dragOverItem === null || draggedItem === dragOverItem) { + setDraggedItem(null); + setDragOverItem(null); + return; + } + + move(draggedItem, dragOverItem); + + setDraggedItem(null); + setDragOverItem(null); + }; + + const handleDragEnd = () => { + setDraggedItem(null); + setDragOverItem(null); + }; return (
@@ -62,17 +93,17 @@ export const GeneralPermissionPolicies =
{title}
- {items.fields.length > 1 && ( + {fields.length > 1 && (
- {items.fields.length} rules + {fields.length} rules
)}
{isOpen && (
- {items.fields.map((el, rootIndex) => { + {fields.map((el, rootIndex) => { let isFullReadAccessEnabled = false; if (subject === ProjectPermissionSub.Secrets) { @@ -82,47 +113,72 @@ export const GeneralPermissionPolicies = - {isConditionalSubjects(subject) && ( -
-
Permission
-
- ( - - )} - /> -
-
- -

- Whether to allow or forbid the selected actions when the following - conditions (if any) are met. -

-

Forbid rules must come after allow rules.

- - } - > - -
-
-
+ className={twMerge( + "relative bg-mineshaft-800 p-5 first:rounded-t-md last:rounded-b-md", + dragOverItem === rootIndex ? "border-2 border-blue-400" : "", + draggedItem === rootIndex ? "opacity-50" : "" )} + onDragOver={(e) => handleDragOver(e, rootIndex)} + onDrop={handleDrop} + > + {!isDisabled && ( + +
handleDragStart(e, rootIndex)} + onDragEnd={handleDragEnd} + className="absolute right-3 top-2 cursor-move rounded-md bg-mineshaft-700 p-2 text-gray-400 hover:text-gray-200" + > + +
+
+ )} + +
+ {isConditionalSubjects(subject) && ( +
+
Permission
+
+ ( + + )} + /> +
+
+ +

+ Whether to allow or forbid the selected actions when the following + conditions (if any) are met. +

+

Forbid rules must come after allow rules.

+ + } + > + +
+
+
+ )} +
Actions
@@ -179,7 +235,7 @@ export const GeneralPermissionPolicies = { - items.insert(rootIndex + 1, [ + insert(rootIndex + 1, [ { read: false, edit: false, create: false, delete: false } as any ]); }} @@ -194,7 +250,7 @@ export const GeneralPermissionPolicies = items.remove(rootIndex)} + onClick={() => remove(rootIndex)} isDisabled={isDisabled} > Remove policy