fix: Permission levels for project settings (#2978)

* fix add subgroup issue FED-1101

* fix subgroup by None assignee FED-1100

* fix grouping by asignee or labels FED-1096

* fix create view popup FED-1093

* fix subgroup exception in swimlanes

* fix show sub issue filter FED-1102

* use Enums instead of numbers

* fix Estimates setting permission for admin

* disable access to project settings for viewers and guests

* fix project unautorized flicker

* add observer to estimates

* add permissions to member list
This commit is contained in:
rahulramesha 2023-12-04 20:03:23 +05:30 committed by Aaryan Khandelwal
parent 199357560d
commit 657d8e97da
24 changed files with 115 additions and 61 deletions

View File

@ -11,6 +11,7 @@ import { ArchiveRestore } from "lucide-react";
import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
// types // types
import { IProject } from "types"; import { IProject } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
handleChange: (formData: Partial<IProject>) => Promise<void>; handleChange: (formData: Partial<IProject>) => Promise<void>;
@ -28,6 +29,8 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
const projectDetails = projectStore.currentProjectDetails; const projectDetails = projectStore.currentProjectDetails;
const userRole = userStore.currentProjectRole; const userRole = userStore.currentProjectRole;
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
return ( return (
<> <>
<SelectMonthModal <SelectMonthModal
@ -56,7 +59,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 }) projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
} }
size="sm" size="sm"
disabled={userRole !== 20} disabled={!isAdmin}
/> />
</div> </div>
@ -74,7 +77,7 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
}} }}
input input
width="w-full" width="w-full"
disabled={userRole !== 20} disabled={!isAdmin}
> >
<> <>
{PROJECT_AUTOMATION_MONTHS.map((month) => ( {PROJECT_AUTOMATION_MONTHS.map((month) => (

View File

@ -11,6 +11,7 @@ import { ArchiveX } from "lucide-react";
import { IProject } from "types"; import { IProject } from "types";
// fetch keys // fetch keys
import { PROJECT_AUTOMATION_MONTHS } from "constants/project"; import { PROJECT_AUTOMATION_MONTHS } from "constants/project";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
handleChange: (formData: Partial<IProject>) => Promise<void>; handleChange: (formData: Partial<IProject>) => Promise<void>;
@ -53,6 +54,8 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
default_state: defaultState, default_state: defaultState,
}; };
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
return ( return (
<> <>
<SelectMonthModal <SelectMonthModal
@ -83,7 +86,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
: handleChange({ close_in: 0, default_state: null }) : handleChange({ close_in: 0, default_state: null })
} }
size="sm" size="sm"
disabled={userRole !== 20} disabled={!isAdmin}
/> />
</div> </div>
@ -102,7 +105,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
}} }}
input input
width="w-full" width="w-full"
disabled={userRole !== 20} disabled={!isAdmin}
> >
<> <>
{PROJECT_AUTOMATION_MONTHS.map((month) => ( {PROJECT_AUTOMATION_MONTHS.map((month) => (

View File

@ -22,6 +22,7 @@ import { Button } from "@plane/ui";
import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Inbox, Trash2, XCircle } from "lucide-react"; import { CheckCircle2, ChevronDown, ChevronUp, Clock, FileStack, Inbox, Trash2, XCircle } from "lucide-react";
// types // types
import type { TInboxStatus } from "types"; import type { TInboxStatus } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
export const InboxActionsHeader = observer(() => { export const InboxActionsHeader = observer(() => {
const [date, setDate] = useState(new Date()); const [date, setDate] = useState(new Date());
@ -71,7 +72,7 @@ export const InboxActionsHeader = observer(() => {
}, [issue]); }, [issue]);
const issueStatus = issue?.issue_inbox[0].status; const issueStatus = issue?.issue_inbox[0].status;
const isAllowed = userRole === 15 || userRole === 20; const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const today = new Date(); const today = new Date();
const tomorrow = new Date(today); const tomorrow = new Date(today);

View File

@ -16,6 +16,7 @@ import { Loader } from "@plane/ui";
import { renderShortDateWithYearFormat } from "helpers/date-time.helper"; import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
// types // types
import { IInboxIssue, IIssue } from "types"; import { IInboxIssue, IIssue } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
const defaultValues: Partial<IInboxIssue> = { const defaultValues: Partial<IInboxIssue> = {
name: "", name: "",
@ -144,6 +145,8 @@ export const InboxMainContent: React.FC = observer(() => {
</div> </div>
); );
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>
{issueDetails ? ( {issueDetails ? (
@ -222,7 +225,7 @@ export const InboxMainContent: React.FC = observer(() => {
description_html: issueDetails.description_html, description_html: issueDetails.description_html,
}} }}
handleFormSubmit={submitChanges} handleFormSubmit={submitChanges}
isAllowed={userRole === 15 || userRole === 20 || user?.id === issueDetails.created_by} isAllowed={isAllowed || user?.id === issueDetails.created_by}
/> />
</div> </div>

View File

@ -25,6 +25,7 @@ import {
IViewIssuesStore, IViewIssuesStore,
} from "store/issues"; } from "store/issues";
import { TUnGroupedIssues } from "store/issues/types"; import { TUnGroupedIssues } from "store/issues/types";
import { EUserWorkspaceRoles } from "constants/workspace";
interface IBaseGanttRoot { interface IBaseGanttRoot {
issueFiltersStore: issueFiltersStore:
@ -69,7 +70,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
); );
}; };
const isAllowed = currentProjectRole && currentProjectRole >= 15; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>

View File

@ -31,6 +31,7 @@ import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes"; import { KanBanSwimLanes } from "./swimlanes";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IssuePeekOverview } from "components/issues"; import { IssuePeekOverview } from "components/issues";
import { EUserWorkspaceRoles } from "constants/workspace";
export interface IBaseKanBanLayout { export interface IBaseKanBanLayout {
issueStore: issueStore:
@ -93,7 +94,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
} = useMobxStore(); } = useMobxStore();
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const issues = issueStore?.getIssues || {}; const issues = issueStore?.getIssues || {};
const issueIds = issueStore?.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];

View File

@ -25,6 +25,7 @@ import { IIssueResponse } from "store/issues/types";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IssuePeekOverview } from "components/issues"; import { IssuePeekOverview } from "components/issues";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { EUserWorkspaceRoles } from "constants/workspace";
enum EIssueActions { enum EIssueActions {
UPDATE = "update", UPDATE = "update",
@ -83,7 +84,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
} = useMobxStore(); } = useMobxStore();
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const issueIds = issueStore?.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];
const issues = issueStore?.getIssues; const issues = issueStore?.getIssues;

View File

@ -18,6 +18,7 @@ import { observer } from "mobx-react-lite";
import { EFilterType, TUnGroupedIssues } from "store/issues/types"; import { EFilterType, TUnGroupedIssues } from "store/issues/types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { EUserWorkspaceRoles } from "constants/workspace";
interface IBaseSpreadsheetRoot { interface IBaseSpreadsheetRoot {
issueFiltersStore: issueFiltersStore:
@ -49,7 +50,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
} = useMobxStore(); } = useMobxStore();
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
const issuesResponse = issueStore.getIssues; const issuesResponse = issueStore.getIssues;
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues; const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;

View File

@ -14,6 +14,7 @@ import { IIssue } from "types";
// services // services
import { FileService } from "services/file.service"; import { FileService } from "services/file.service";
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { EUserWorkspaceRoles } from "constants/workspace";
const fileService = new FileService(); const fileService = new FileService();
@ -32,7 +33,7 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
// store // store
const { user: userStore } = useMobxStore(); const { user: userStore } = useMobxStore();
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isAllowed = [15, 20].includes(currentProjectRole || 0); const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
// states // states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const [characterLimit, setCharacterLimit] = useState(false); const [characterLimit, setCharacterLimit] = useState(false);

View File

@ -12,6 +12,7 @@ import { IssueView } from "./view";
import { copyUrlToClipboard } from "helpers/string.helper"; import { copyUrlToClipboard } from "helpers/string.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
interface IIssuePeekOverview { interface IIssuePeekOverview {
workspaceSlug: string; workspaceSlug: string;
@ -118,7 +119,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
} }
}; };
const userRole = userStore.currentProjectRole ?? 5; const userRole = userStore.currentProjectRole ?? EUserWorkspaceRoles.GUEST;
return ( return (
<Fragment> <Fragment>

View File

@ -26,6 +26,7 @@ import { MinusCircle } from "lucide-react";
import { IIssue, IIssueComment } from "types"; import { IIssue, IIssueComment } from "types";
// fetch-keys // fetch-keys
import { PROJECT_ISSUES_ACTIVITY, SUB_ISSUES } from "constants/fetch-keys"; import { PROJECT_ISSUES_ACTIVITY, SUB_ISSUES } from "constants/fetch-keys";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
issueDetails: IIssue; issueDetails: IIssue;
@ -100,6 +101,8 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
); );
}; };
const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>
<div className="rounded-lg"> <div className="rounded-lg">
@ -166,7 +169,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug as string} workspaceSlug={workspaceSlug as string}
issue={issueDetails} issue={issueDetails}
handleFormSubmit={submitChanges} handleFormSubmit={submitChanges}
isAllowed={userRole === 20 || userRole === 15 || !uneditable} isAllowed={isAllowed || !uneditable}
/> />
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} /> <IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />

View File

@ -40,6 +40,7 @@ import { copyTextToClipboard } from "helpers/string.helper";
import type { IIssue, IIssueLink, linkDetails } from "types"; import type { IIssue, IIssueLink, linkDetails } from "types";
// fetch-keys // fetch-keys
import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = { type Props = {
control: any; control: any;
@ -245,7 +246,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
setLinkModal(true); setLinkModal(true);
}; };
const isNotAllowed = userRole === 5 || userRole === 10; const isAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>
@ -295,7 +296,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<LinkIcon className="h-3.5 w-3.5" /> <LinkIcon className="h-3.5 w-3.5" />
</button> </button>
)} )}
{!isNotAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && ( {isAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && (
<button <button
type="button" type="button"
className="rounded-md border border-red-500 p-2 text-red-500 shadow-sm duration-300 hover:bg-red-500/20 focus:outline-none" className="rounded-md border border-red-500 p-2 text-red-500 shadow-sm duration-300 hover:bg-red-500/20 focus:outline-none"
@ -325,7 +326,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarStateSelect <SidebarStateSelect
value={value} value={value}
onChange={(val: string) => submitChanges({ state: val })} onChange={(val: string) => submitChanges({ state: val })}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -346,7 +347,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarAssigneeSelect <SidebarAssigneeSelect
value={value} value={value}
onChange={(val: string[]) => submitChanges({ assignees: val })} onChange={(val: string[]) => submitChanges({ assignees: val })}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -367,7 +368,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarPrioritySelect <SidebarPrioritySelect
value={value} value={value}
onChange={(val) => submitChanges({ priority: val })} onChange={(val) => submitChanges({ priority: val })}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -388,7 +389,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarEstimateSelect <SidebarEstimateSelect
value={value} value={value}
onChange={(val: number | null) => submitChanges({ estimate_point: val })} onChange={(val: number | null) => submitChanges({ estimate_point: val })}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -416,7 +417,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
onChange(val); onChange(val);
}} }}
issueDetails={issueDetail} issueDetails={issueDetail}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -441,7 +442,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("blocked")) && (
@ -462,7 +463,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("duplicate")) && (
@ -480,7 +481,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("relates_to")) && (
@ -498,7 +499,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
}} }}
watch={watchIssue} watch={watchIssue}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && ( {(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
@ -522,7 +523,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
} }
className="bg-custom-background-80 border-none" className="bg-custom-background-80 border-none"
maxDate={maxDate ?? undefined} maxDate={maxDate ?? undefined}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -550,7 +551,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
} }
className="bg-custom-background-80 border-none" className="bg-custom-background-80 border-none"
minDate={minDate ?? undefined} minDate={minDate ?? undefined}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
)} )}
/> />
@ -571,7 +572,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarCycleSelect <SidebarCycleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
handleCycleChange={handleCycleChange} handleCycleChange={handleCycleChange}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
</div> </div>
</div> </div>
@ -586,7 +587,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<SidebarModuleSelect <SidebarModuleSelect
issueDetail={issueDetail} issueDetail={issueDetail}
handleModuleChange={handleModuleChange} handleModuleChange={handleModuleChange}
disabled={isNotAllowed || uneditable} disabled={!isAllowed || uneditable}
/> />
</div> </div>
</div> </div>
@ -605,7 +606,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
issueDetails={issueDetail} issueDetails={issueDetail}
labelList={issueDetail?.labels ?? []} labelList={issueDetail?.labels ?? []}
submitChanges={submitChanges} submitChanges={submitChanges}
isNotAllowed={isNotAllowed} isNotAllowed={!isAllowed}
uneditable={uneditable ?? false} uneditable={uneditable ?? false}
/> />
</div> </div>
@ -615,7 +616,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className={`min-h-[116px] py-1 text-xs ${uneditable ? "opacity-60" : ""}`}> <div className={`min-h-[116px] py-1 text-xs ${uneditable ? "opacity-60" : ""}`}>
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<h4>Links</h4> <h4>Links</h4>
{!isNotAllowed && ( {isAllowed && (
<button <button
type="button" type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-background-90 ${ className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-custom-background-90 ${

View File

@ -22,6 +22,7 @@ import { IUser, IIssue, ISearchIssueResponse } from "types";
import { IssueService } from "services/issue"; import { IssueService } from "services/issue";
// fetch keys // fetch keys
import { SUB_ISSUES } from "constants/fetch-keys"; import { SUB_ISSUES } from "constants/fetch-keys";
import { EUserWorkspaceRoles } from "constants/workspace";
export interface ISubIssuesRoot { export interface ISubIssuesRoot {
parentIssue: IIssue; parentIssue: IIssue;
@ -176,7 +177,7 @@ export const SubIssuesRoot: React.FC<ISubIssuesRoot> = observer((props) => {
[updateIssueStructure, projectId, updateIssue, user, workspaceSlug] [updateIssueStructure, projectId, updateIssue, user, workspaceSlug]
); );
const isEditable = userRole === 5 || userRole === 10 ? false : true; const isEditable = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
const mutateSubIssues = (parentIssueId: string | null) => { const mutateSubIssues = (parentIssueId: string | null) => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId)); if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));

View File

@ -33,6 +33,7 @@ import { linkDetails, IModule, ModuleLink } from "types";
import { MODULE_DETAILS } from "constants/fetch-keys"; import { MODULE_DETAILS } from "constants/fetch-keys";
// constant // constant
import { MODULE_STATUS } from "constants/module"; import { MODULE_STATUS } from "constants/module";
import { EUserWorkspaceRoles } from "constants/workspace";
const defaultValues: Partial<IModule> = { const defaultValues: Partial<IModule> = {
lead: "", lead: "",
@ -588,10 +589,10 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
handleEditLink={handleEditLink} handleEditLink={handleEditLink}
handleDeleteLink={handleDeleteLink} handleDeleteLink={handleDeleteLink}
userAuth={{ userAuth={{
isGuest: userRole === 5, isGuest: userRole === EUserWorkspaceRoles.GUEST,
isViewer: userRole === 10, isViewer: userRole === EUserWorkspaceRoles.VIEWER,
isMember: userRole === 15, isMember: userRole === EUserWorkspaceRoles.MEMBER,
isOwner: userRole === 20, isOwner: userRole === EUserWorkspaceRoles.ADMIN,
}} }}
/> />
</> </>

View File

@ -26,6 +26,7 @@ import { CustomMenu, Tooltip } from "@plane/ui";
import { CreateUpdatePageModal, DeletePageModal } from "components/pages"; import { CreateUpdatePageModal, DeletePageModal } from "components/pages";
// types // types
import { IPage } from "types"; import { IPage } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
export interface IPagesListItem { export interface IPagesListItem {
workspaceSlug: string; workspaceSlug: string;
@ -144,7 +145,7 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
setCreateUpdatePageModal(true); setCreateUpdatePageModal(true);
}; };
const userCanEdit = currentProjectRole === 15 || currentProjectRole === 20; const userCanEdit = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
return ( return (
<> <>

View File

@ -13,7 +13,7 @@ import { CustomSelect, Tooltip } from "@plane/ui";
// icons // icons
import { ChevronDown, Dot, XCircle } from "lucide-react"; import { ChevronDown, Dot, XCircle } from "lucide-react";
// constants // constants
import { ROLE } from "constants/workspace"; import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
// types // types
import { IProjectMember, TUserProjectRole } from "types"; import { IProjectMember, TUserProjectRole } from "types";
@ -38,7 +38,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// derived values // derived values
const isAdmin = currentProjectRole === 20; const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
const memberDetails = member.member; const memberDetails = member.member;
const handleRemove = async () => { const handleRemove = async () => {
@ -148,12 +148,13 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
disabled={ disabled={
memberDetails.id === currentUser?.id || memberDetails.id === currentUser?.id ||
!member.member || !member.member ||
(currentProjectRole && currentProjectRole !== 20 && currentProjectRole < member.role) !currentProjectRole ||
currentProjectRole < member.role
} }
placement="bottom-end" placement="bottom-end"
> >
{Object.keys(ROLE).map((key) => { {Object.keys(ROLE).map((key) => {
if (currentProjectRole && currentProjectRole !== 20 && currentProjectRole < parseInt(key)) return null; if (currentProjectRole && !isAdmin && currentProjectRole < parseInt(key)) return null;
return ( return (
<CustomSelect.Option key={key} value={parseInt(key, 10)}> <CustomSelect.Option key={key} value={parseInt(key, 10)}>

View File

@ -15,6 +15,7 @@ import { Loader } from "@plane/ui";
import { IProject, IUserLite, IWorkspace } from "types"; import { IProject, IUserLite, IWorkspace } from "types";
// fetch-keys // fetch-keys
import { PROJECT_MEMBERS } from "constants/fetch-keys"; import { PROJECT_MEMBERS } from "constants/fetch-keys";
import { EUserWorkspaceRoles } from "constants/workspace";
const defaultValues: Partial<IProject> = { const defaultValues: Partial<IProject> = {
project_lead: null, project_lead: null,
@ -29,7 +30,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
const { user: userStore, project: projectStore } = useMobxStore(); const { user: userStore, project: projectStore } = useMobxStore();
const { currentProjectDetails } = projectStore; const { currentProjectDetails } = projectStore;
const { currentProjectRole } = userStore; const { currentProjectRole } = userStore;
const isAdmin = currentProjectRole === 20; const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
// hooks // hooks
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
// form info // form info

View File

@ -15,7 +15,7 @@ import useToast from "hooks/use-toast";
// types // types
import { IProjectMember, TUserProjectRole } from "types"; import { IProjectMember, TUserProjectRole } from "types";
// constants // constants
import { ROLE } from "constants/workspace"; import { EUserWorkspaceRoles, ROLE } from "constants/workspace";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -246,7 +246,8 @@ export const SendProjectInvitationModal: React.FC<Props> = observer((props) => {
width="w-full" width="w-full"
> >
{Object.entries(ROLE).map(([key, label]) => { {Object.entries(ROLE).map(([key, label]) => {
if (parseInt(key) > (currentProjectRole ?? 5)) return null; if (parseInt(key) > (currentProjectRole ?? EUserWorkspaceRoles.GUEST))
return null;
return ( return (
<CustomSelect.Option key={key} value={key}> <CustomSelect.Option key={key} value={key}>

View File

@ -9,6 +9,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// types // types
import { IProject } from "types"; import { IProject } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
type Props = {}; type Props = {};
@ -56,7 +57,7 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
user: { currentUser, currentProjectRole }, user: { currentUser, currentProjectRole },
trackEvent: { setTrackElement, postHogEventTracker }, trackEvent: { setTrackElement, postHogEventTracker },
} = useMobxStore(); } = useMobxStore();
const isAdmin = currentProjectRole === 20; const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
// hooks // hooks
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
@ -97,7 +98,7 @@ export const ProjectFeaturesList: FC<Props> = observer(() => {
project_id: currentProjectDetails?.id, project_id: currentProjectDetails?.id,
project_name: currentProjectDetails?.name, project_name: currentProjectDetails?.name,
project_identifier: currentProjectDetails?.identifier, project_identifier: currentProjectDetails?.identifier,
enabled: !currentProjectDetails?.[feature.property as keyof IProject] enabled: !currentProjectDetails?.[feature.property as keyof IProject],
}); });
handleSubmit({ handleSubmit({
[feature.property]: !currentProjectDetails?.[feature.property as keyof IProject], [feature.property]: !currentProjectDetails?.[feature.property as keyof IProject],

View File

@ -1,12 +1,14 @@
export const getUserRole = (role: number) => { import { EUserWorkspaceRoles } from "constants/workspace";
export const getUserRole = (role: EUserWorkspaceRoles) => {
switch (role) { switch (role) {
case 5: case EUserWorkspaceRoles.GUEST:
return "GUEST"; return "GUEST";
case 10: case EUserWorkspaceRoles.VIEWER:
return "VIEWER"; return "VIEWER";
case 15: case EUserWorkspaceRoles.MEMBER:
return "MEMBER"; return "MEMBER";
case 20: case EUserWorkspaceRoles.ADMIN:
return "ADMIN"; return "ADMIN";
} }
}; };

View File

@ -1,15 +1,27 @@
import { FC, ReactNode } from "react"; import { FC, ReactNode } from "react";
// components // components
import { ProjectSettingsSidebar } from "./sidebar"; import { ProjectSettingsSidebar } from "./sidebar";
import { useMobxStore } from "lib/mobx/store-provider";
import { EUserWorkspaceRoles } from "constants/workspace";
import { NotAuthorizedView } from "components/auth-screens";
import { observer } from "mobx-react-lite";
export interface IProjectSettingLayout { export interface IProjectSettingLayout {
children: ReactNode; children: ReactNode;
} }
export const ProjectSettingLayout: FC<IProjectSettingLayout> = (props) => { export const ProjectSettingLayout: FC<IProjectSettingLayout> = observer((props) => {
const { children } = props; const { children } = props;
return ( const {
user: { currentProjectRole },
} = useMobxStore();
const restrictViewSettings = currentProjectRole && currentProjectRole <= EUserWorkspaceRoles.VIEWER;
return restrictViewSettings ? (
<NotAuthorizedView type="project" />
) : (
<div className="flex gap-2 h-full w-full overflow-x-hidden overflow-y-scroll"> <div className="flex gap-2 h-full w-full overflow-x-hidden overflow-y-scroll">
<div className="w-80 pt-8 overflow-y-hidden flex-shrink-0"> <div className="w-80 pt-8 overflow-y-hidden flex-shrink-0">
<ProjectSettingsSidebar /> <ProjectSettingsSidebar />
@ -17,4 +29,4 @@ export const ProjectSettingLayout: FC<IProjectSettingLayout> = (props) => {
{children} {children}
</div> </div>
); );
}; });

View File

@ -14,6 +14,7 @@ import { ProjectSettingHeader } from "components/headers";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { IProject } from "types"; import { IProject } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
const AutomationSettingsPage: NextPageWithLayout = observer(() => { const AutomationSettingsPage: NextPageWithLayout = observer(() => {
const router = useRouter(); const router = useRouter();
@ -39,7 +40,7 @@ const AutomationSettingsPage: NextPageWithLayout = observer(() => {
}); });
}; };
const isAdmin = currentProjectRole === 20; const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
return ( return (
<section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}> <section className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>

View File

@ -7,12 +7,23 @@ import { ProjectSettingHeader } from "components/headers";
import { EstimatesList } from "components/estimates"; import { EstimatesList } from "components/estimates";
// types // types
import { NextPageWithLayout } from "types/app"; import { NextPageWithLayout } from "types/app";
import { useMobxStore } from "lib/mobx/store-provider";
import { EUserWorkspaceRoles } from "constants/workspace";
import { observer } from "mobx-react-lite";
const EstimatesSettingsPage: NextPageWithLayout = () => ( const EstimatesSettingsPage: NextPageWithLayout = observer(() => {
<div className="pr-9 py-8 w-full overflow-y-auto"> const {
user: { currentProjectRole },
} = useMobxStore();
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
return (
<div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60 pointer-events-none"}`}>
<EstimatesList /> <EstimatesList />
</div> </div>
); );
});
EstimatesSettingsPage.getLayout = function getLayout(page: ReactElement) { EstimatesSettingsPage.getLayout = function getLayout(page: ReactElement) {
return ( return (

View File

@ -5,6 +5,7 @@ import { RootStore } from "../root";
import { InboxService } from "services/inbox.service"; import { InboxService } from "services/inbox.service";
// types // types
import { IInbox, IInboxFilterOptions, IInboxQueryParams } from "types"; import { IInbox, IInboxFilterOptions, IInboxQueryParams } from "types";
import { EUserWorkspaceRoles } from "constants/workspace";
export interface IInboxFiltersStore { export interface IInboxFiltersStore {
// states // states
@ -132,8 +133,8 @@ export class InboxFiltersStore implements IInboxFiltersStore {
}; };
}); });
const userRole = this.rootStore.user?.projectMemberInfo?.[projectId]?.role || 0; const userRole = this.rootStore.user?.currentProjectRole || EUserWorkspaceRoles.GUEST;
if (userRole > 10) { if (userRole > EUserWorkspaceRoles.VIEWER) {
await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps }); await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps });
} }
} catch (error) { } catch (error) {