diff --git a/apiserver/plane/app/views/issue/reaction.py b/apiserver/plane/app/views/issue/reaction.py index 5146118f30..a43f7bda6a 100644 --- a/apiserver/plane/app/views/issue/reaction.py +++ b/apiserver/plane/app/views/issue/reaction.py @@ -37,7 +37,7 @@ class IssueReactionViewSet(BaseViewSet): .distinct() ) - @allow_permission(ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST) + @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def create(self, request, slug, project_id, issue_id): serializer = IssueReactionSerializer(data=request.data) if serializer.is_valid(): @@ -60,7 +60,7 @@ class IssueReactionViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - @allow_permission(ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST) + @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def destroy(self, request, slug, project_id, issue_id, reaction_code): issue_reaction = IssueReaction.objects.get( workspace__slug=slug, diff --git a/web/core/components/cycles/delete-modal.tsx b/web/core/components/cycles/delete-modal.tsx index e31a785b53..f89a1cea77 100644 --- a/web/core/components/cycles/delete-modal.tsx +++ b/web/core/components/cycles/delete-modal.tsx @@ -54,7 +54,7 @@ export const CycleDeleteModal: React.FC = observer((props) => { }); }) .catch((errors) => { - const isPermissionError = errors?.error === "Only admin or owner can delete the cycle"; + const isPermissionError = errors?.error === "You don't have the required permissions."; const currentError = isPermissionError ? PROJECT_ERROR_MESSAGES.permissionError : PROJECT_ERROR_MESSAGES.cycleDeleteError; diff --git a/web/core/components/issues/issue-layouts/list/list-group.tsx b/web/core/components/issues/issue-layouts/list/list-group.tsx index ca0753bef9..7e417e471a 100644 --- a/web/core/components/issues/issue-layouts/list/list-group.tsx +++ b/web/core/components/issues/issue-layouts/list/list-group.tsx @@ -4,6 +4,7 @@ import { MutableRefObject, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { observer } from "mobx-react"; +import { useParams } from "next/navigation"; import { cn } from "@plane/editor"; // plane packages import { @@ -90,6 +91,7 @@ export const ListGroup = observer((props: Props) => { const [isExpanded, setIsExpanded] = useState(true); const groupRef = useRef(null); + const { projectId } = useParams(); const projectState = useProjectState(); const { @@ -216,7 +218,8 @@ export const ListGroup = observer((props: Props) => { ); }, [groupRef?.current, group, orderBy, getGroupIndex, setDragColumnOrientation, setIsDraggingOverColumn]); - const isDragAllowed = !!group_by && DRAG_ALLOWED_GROUPS.includes(group_by); + const isDragAllowed = + !!group_by && DRAG_ALLOWED_GROUPS.includes(group_by) && canEditProperties(projectId?.toString()); const canOverlayBeVisible = orderBy !== "sort_order" || !!group.isDropDisabled; const isGroupByCreatedBy = group_by === "created_by"; diff --git a/web/core/components/issues/issue-layouts/list/roots/project-root.tsx b/web/core/components/issues/issue-layouts/list/roots/project-root.tsx index 4e7ad97c9a..5fe8bceab3 100644 --- a/web/core/components/issues/issue-layouts/list/roots/project-root.tsx +++ b/web/core/components/issues/issue-layouts/list/roots/project-root.tsx @@ -6,12 +6,28 @@ import { ProjectIssueQuickActions } from "@/components/issues"; // components // types // constants +import { useUserPermissions } from "@/hooks/store"; +import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions"; import { BaseListRoot } from "../base-list-root"; export const ListLayout: FC = observer(() => { const { workspaceSlug, projectId } = useParams(); + const { allowPermissions } = useUserPermissions(); if (!workspaceSlug || !projectId) return null; - return ; + const canEditPropertiesBasedOnProject = (projectId: string) => + allowPermissions( + [EUserPermissions.ADMIN, EUserPermissions.MEMBER], + EUserPermissionsLevel.PROJECT, + workspaceSlug.toString(), + projectId + ); + + return ( + + ); }); diff --git a/web/core/components/labels/project-setting-label-group.tsx b/web/core/components/labels/project-setting-label-group.tsx index efa0facc1b..898931b344 100644 --- a/web/core/components/labels/project-setting-label-group.tsx +++ b/web/core/components/labels/project-setting-label-group.tsx @@ -25,10 +25,20 @@ type Props = { droppedLabelId: string | undefined, dropAtEndOfList: boolean ) => void; + isEditable?: boolean; }; export const ProjectSettingLabelGroup: React.FC = observer((props) => { - const { label, labelChildren, handleLabelDelete, isUpdating, setIsUpdating, isLastChild, onDrop } = props; + const { + label, + labelChildren, + handleLabelDelete, + isUpdating, + setIsUpdating, + isLastChild, + onDrop, + isEditable = false, + } = props; // states const [isEditLabelForm, setEditLabelForm] = useState(false); @@ -123,6 +133,7 @@ export const ProjectSettingLabelGroup: React.FC = observer((props) => { isChild isLastChild={index === labelChildren.length - 1} onDrop={onDrop} + isEditable={isEditable} /> diff --git a/web/core/components/labels/project-setting-label-list.tsx b/web/core/components/labels/project-setting-label-list.tsx index b11d8249ae..2fc9c83827 100644 --- a/web/core/components/labels/project-setting-label-list.tsx +++ b/web/core/components/labels/project-setting-label-list.tsx @@ -111,6 +111,7 @@ export const ProjectSettingsLabelList: React.FC = observer(() => { setIsUpdating={setIsUpdating} isLastChild={index === projectLabelsTree.length - 1} onDrop={onDrop} + isEditable={isEditable} /> ); } @@ -123,6 +124,7 @@ export const ProjectSettingsLabelList: React.FC = observer(() => { isChild={false} isLastChild={index === projectLabelsTree.length - 1} onDrop={onDrop} + isEditable={isEditable} /> ); })} diff --git a/web/core/components/modules/delete-module-modal.tsx b/web/core/components/modules/delete-module-modal.tsx index 290b7dc2be..c165c2e4c7 100644 --- a/web/core/components/modules/delete-module-modal.tsx +++ b/web/core/components/modules/delete-module-modal.tsx @@ -56,7 +56,7 @@ export const DeleteModuleModal: React.FC = observer((props) => { }); }) .catch((errors) => { - const isPermissionError = errors?.error === "Only admin or creator can delete the module"; + const isPermissionError = errors?.error === "You don't have the required permissions."; const currentError = isPermissionError ? PROJECT_ERROR_MESSAGES.permissionError : PROJECT_ERROR_MESSAGES.moduleDeleteError; diff --git a/web/core/components/project/leave-project-modal.tsx b/web/core/components/project/leave-project-modal.tsx index 74dd189397..470a5fe61b 100644 --- a/web/core/components/project/leave-project-modal.tsx +++ b/web/core/components/project/leave-project-modal.tsx @@ -60,10 +60,10 @@ export const LeaveProjectModal: FC = observer((props) => { if (data) { if (data.projectName === project?.name) { if (data.confirmLeave === "Leave Project") { + router.push(`/${workspaceSlug}/projects`); return leaveProject(workspaceSlug.toString(), project.id) .then(() => { handleClose(); - router.push(`/${workspaceSlug}/projects`); captureEvent(PROJECT_MEMBER_LEAVE, { state: "SUCCESS", element: "Project settings members page", diff --git a/web/core/components/project/member-list-item.tsx b/web/core/components/project/member-list-item.tsx index 956e95db32..3a7c5ed034 100644 --- a/web/core/components/project/member-list-item.tsx +++ b/web/core/components/project/member-list-item.tsx @@ -38,6 +38,7 @@ export const ProjectMemberListItem: React.FC = observer((props) => { if (!workspaceSlug || !projectId || !memberId) return; if (memberId === currentUser?.id) { + router.push(`/${workspaceSlug}/projects`); await leaveProject(workspaceSlug.toString(), projectId.toString()) .then(async () => { captureEvent(PROJECT_MEMBER_LEAVE, { @@ -45,7 +46,6 @@ export const ProjectMemberListItem: React.FC = observer((props) => { element: "Project settings members page", }); await fetchProjects(workspaceSlug.toString()); - router.push(`/${workspaceSlug}/projects`); }) .catch((err) => setToast({ diff --git a/web/core/components/project/settings/member-columns.tsx b/web/core/components/project/settings/member-columns.tsx index f6699d7118..b5b48d6315 100644 --- a/web/core/components/project/settings/member-columns.tsx +++ b/web/core/components/project/settings/member-columns.tsx @@ -97,9 +97,11 @@ export const AccountTypeColumn: React.FC = observer((props) => // derived values const isCurrentUser = currentUser?.id === rowData.member.id; - const isAdminOrGuest = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(rowData.role); - const userWorkspaceRole = getWorkspaceMemberDetails(rowData.member.id)?.role; - const isRoleNonEditable = isCurrentUser || (isAdminOrGuest && userWorkspaceRole !== EUserPermissions.MEMBER); + const isProjectAdminOrGuest = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(rowData.role); + const isWorkspaceMember = [EUserPermissions.MEMBER].includes( + Number(getWorkspaceMemberDetails(rowData.member.id)?.role) ?? EUserPermissions.GUEST + ); + const isRoleNonEditable = isCurrentUser || (isProjectAdminOrGuest && !isWorkspaceMember); const checkCurrentOptionWorkspaceRole = (value: string) => { const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role as EUserPermissions | undefined; diff --git a/web/core/components/workspace/settings/member-columns.tsx b/web/core/components/workspace/settings/member-columns.tsx index 70491351dd..c2639177d1 100644 --- a/web/core/components/workspace/settings/member-columns.tsx +++ b/web/core/components/workspace/settings/member-columns.tsx @@ -114,9 +114,7 @@ export const AccountTypeColumn: React.FC = observer((props) => { - console.log({ value, workspaceSlug }, "onChange"); if (!workspaceSlug) return; - updateMember(workspaceSlug.toString(), rowData.member.id, { role: value as unknown as EUserPermissions, // Cast value to unknown first, then to EUserPermissions }).catch((err) => {