- {workspaceUserInfo && !isWorkspaceAdmin ? (
+ {workspaceUserInfo && !isAuthorized ? (
) : (
<>
diff --git a/web/ce/constants/project/settings/tabs.ts b/web/ce/constants/project/settings/tabs.ts
index 50bff11151e..15869c186cd 100644
--- a/web/ce/constants/project/settings/tabs.ts
+++ b/web/ce/constants/project/settings/tabs.ts
@@ -18,7 +18,7 @@ export const PROJECT_SETTINGS = {
key: "members",
i18n_label: "members",
href: `/settings/members`,
- access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER],
+ access: [EUserPermissions.ADMIN, EUserPermissions.MEMBER, EUserPermissions.GUEST],
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members/`,
Icon: SettingIcon,
},
diff --git a/web/core/components/project/settings/member-columns.tsx b/web/core/components/project/settings/member-columns.tsx
index f3b6ec956c7..d9af617eae0 100644
--- a/web/core/components/project/settings/member-columns.tsx
+++ b/web/core/components/project/settings/member-columns.tsx
@@ -13,7 +13,7 @@ import { CustomSelect, PopoverMenu, TOAST_TYPE, setToast } from "@plane/ui";
// helpers
import { getFileURL } from "@/helpers/file.helper";
// hooks
-import { useMember, useUser } from "@/hooks/store";
+import { useMember, useUser, useUserPermissions } from "@/hooks/store";
export interface RowData {
member: IWorkspaceMember;
@@ -91,7 +91,7 @@ export const NameColumn: React.FC
= (props) => {
};
export const AccountTypeColumn: React.FC = observer((props) => {
- const { rowData, currentProjectRole, projectId, workspaceSlug } = props;
+ const { rowData, projectId, workspaceSlug } = props;
// form info
const {
control,
@@ -99,48 +99,56 @@ export const AccountTypeColumn: React.FC = observer((props) =>
} = useForm();
// store hooks
const {
- project: { updateMember, getProjectMemberDetails },
+ project: { updateMember },
workspace: { getWorkspaceMemberDetails },
} = useMember();
const { data: currentUser } = useUser();
+ const { projectUserInfo } = useUserPermissions();
// derived values
const isCurrentUser = currentUser?.id === rowData.member.id;
- const isProjectAdminOrGuest = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(rowData.role);
- const isWorkspaceMember = [EUserPermissions.MEMBER].includes(
+ const isRowDataWorkspaceAdmin = [EUserPermissions.ADMIN].includes(
Number(getWorkspaceMemberDetails(rowData.member.id)?.role) ?? EUserPermissions.GUEST
);
- const isCurrentUserProjectMember = currentUser
- ? getProjectMemberDetails(currentUser.id, projectId)?.role === EUserPermissions.MEMBER
+ const isCurrentUserWorkspaceAdmin = currentUser
+ ? [EUserPermissions.ADMIN].includes(
+ Number(getWorkspaceMemberDetails(currentUser.id)?.role) ?? EUserPermissions.GUEST
+ )
: false;
- const isRoleNonEditable =
- isCurrentUser || (isProjectAdminOrGuest && !isWorkspaceMember) || isCurrentUserProjectMember;
+ const currentProjectRole = projectUserInfo?.[workspaceSlug.toString()]?.[projectId.toString()]
+ ?.role as unknown as EUserPermissions;
+ const isCurrentUserProjectAdmin = currentProjectRole
+ ? ![EUserPermissions.MEMBER, EUserPermissions.GUEST].includes(Number(currentProjectRole) ?? EUserPermissions.GUEST)
+ : false;
+
+ // logic
+ // Workspace admin can change his own role
+ // Project admin can change any role except his own and workspace admin's role
+ const isRoleEditable =
+ (isCurrentUserWorkspaceAdmin && isCurrentUser) ||
+ (isCurrentUserProjectAdmin && !isRowDataWorkspaceAdmin && !isCurrentUser);
const checkCurrentOptionWorkspaceRole = (value: string) => {
const currentMemberWorkspaceRole = getWorkspaceMemberDetails(value)?.role as EUserPermissions | undefined;
if (!value || !currentMemberWorkspaceRole) return ROLE;
- const isGuestOROwner = [EUserPermissions.ADMIN, EUserPermissions.GUEST].includes(currentMemberWorkspaceRole);
+ const isGuest = [EUserPermissions.GUEST].includes(currentMemberWorkspaceRole);
return Object.fromEntries(
- Object.entries(ROLE).filter(([key]) => !isGuestOROwner || [currentMemberWorkspaceRole].includes(parseInt(key)))
+ Object.entries(ROLE).filter(([key]) => !isGuest || parseInt(key) === EUserPermissions.GUEST)
);
};
return (
<>
- {isRoleNonEditable ? (
-
- {ROLE[rowData.role]}
-
- ) : (
+ {isRoleEditable ? (
(
+ render={() => (
{
if (!workspaceSlug) return;
@@ -168,17 +176,18 @@ export const AccountTypeColumn: React.FC = observer((props) =>
optionsClassName="w-full"
input
>
- {Object.entries(checkCurrentOptionWorkspaceRole(rowData.member.id)).map(([key, label]) => {
- if (parseInt(key) > (currentProjectRole ?? EUserPermissions.GUEST)) return null;
- return (
-
- {label}
-
- );
- })}
+ {Object.entries(checkCurrentOptionWorkspaceRole(rowData.member.id)).map(([key, label]) => (
+
+ {label}
+
+ ))}
)}
/>
+ ) : (
+
+ {ROLE[rowData.role]}
+
)}
>
);
diff --git a/web/core/components/workspace/sidebar/dropdown-item.tsx b/web/core/components/workspace/sidebar/dropdown-item.tsx
index 2259f95d202..9eef151d15f 100644
--- a/web/core/components/workspace/sidebar/dropdown-item.tsx
+++ b/web/core/components/workspace/sidebar/dropdown-item.tsx
@@ -86,8 +86,8 @@ const SidebarDropdownItem = observer((props: TProps) => {
{workspace.id === activeWorkspace?.id && (
<>
- {workspace?.role === EUserPermissions.ADMIN && (
-
+
+ {[EUserPermissions.ADMIN, EUserPermissions.MEMBER].includes(workspace?.role) && (
{
{t("settings")}
+ )}
+ {[EUserPermissions.ADMIN].includes(workspace?.role) && (
{
{t("project_settings.members.invite_members.title")}
-
- )}
+ )}
+
>
)}
diff --git a/web/core/store/member/project-member.store.ts b/web/core/store/member/project-member.store.ts
index 5c67f61b1b7..e97e5ab320d 100644
--- a/web/core/store/member/project-member.store.ts
+++ b/web/core/store/member/project-member.store.ts
@@ -69,6 +69,7 @@ export class ProjectMemberStore implements IProjectMemberStore {
userStore: IUserStore;
memberRoot: IMemberRootStore;
projectRoot: IProjectStore;
+ rootStore: CoreRootStore;
// services
projectMemberService;
@@ -86,6 +87,7 @@ export class ProjectMemberStore implements IProjectMemberStore {
});
// root store
+ this.rootStore = _rootStore;
this.routerStore = _rootStore.router;
this.userStore = _rootStore.user;
this.memberRoot = _memberRoot;
@@ -199,10 +201,13 @@ export class ProjectMemberStore implements IProjectMemberStore {
const memberDetails = this.getProjectMemberDetails(userId, projectId);
if (!memberDetails) throw new Error("Member not found");
// original data to revert back in case of error
- const originalProjectMemberData = this.projectMemberMap?.[projectId]?.[userId];
+ const originalProjectMemberData = this.projectMemberMap?.[projectId]?.[userId]?.role;
+ const isCurrentUser = this.rootStore.user.data?.id === userId;
try {
runInAction(() => {
set(this.projectMemberMap, [projectId, userId, "role"], data.role);
+ if (isCurrentUser)
+ set(this.rootStore.user.permission.projectUserInfo, [workspaceSlug, projectId, "role"], data.role);
});
const response = await this.projectMemberService.updateProjectMember(
workspaceSlug,
@@ -214,7 +219,13 @@ export class ProjectMemberStore implements IProjectMemberStore {
} catch (error) {
// revert back to original members in case of error
runInAction(() => {
- set(this.projectMemberMap, [projectId, userId], originalProjectMemberData);
+ set(this.projectMemberMap, [projectId, userId, "role"], originalProjectMemberData);
+ if (isCurrentUser)
+ set(
+ this.rootStore.user.permission.projectUserInfo,
+ [workspaceSlug, projectId, "role"],
+ originalProjectMemberData
+ );
});
throw error;
}