diff --git a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx index afb5b3b6b..ef03d3a8e 100644 --- a/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx +++ b/web/components/issues/issue-layouts/gantt/base-gantt-root.tsx @@ -25,7 +25,6 @@ import { IViewIssuesFilterStore, IViewIssuesStore, } from "store/issues"; -import { EUserWorkspaceRoles } from "layouts/settings-layout/workspace/sidebar"; import { TUnGroupedIssues } from "store/issues/types"; interface IBaseGanttRoot { @@ -46,6 +45,10 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan const { projectDetails } = useProjectDetails(); + const { + user: { currentProjectRole }, + } = useMobxStore(); + const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters; const issuesResponse = issueStore.getIssues; @@ -69,7 +72,7 @@ export const BaseGanttRoot: React.FC = observer((props: IBaseGan ); }; - const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER; + const isAllowed = currentProjectRole && currentProjectRole >= 15; return ( <> diff --git a/web/components/workspace/send-workspace-invitation-modal.tsx b/web/components/workspace/send-workspace-invitation-modal.tsx index b044ec5a2..77cc5c381 100644 --- a/web/components/workspace/send-workspace-invitation-modal.tsx +++ b/web/components/workspace/send-workspace-invitation-modal.tsx @@ -1,10 +1,12 @@ import React, { useEffect } from "react"; +import { observer } from "mobx-react-lite"; import { Controller, useFieldArray, useForm } from "react-hook-form"; import { Dialog, Transition } from "@headlessui/react"; +import { Plus, X } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // ui import { Button, CustomSelect, Input } from "@plane/ui"; -// icons -import { Plus, X } from "lucide-react"; // types import { IWorkspaceBulkInviteFormData, TUserWorkspaceRole } from "types"; // constants @@ -34,9 +36,12 @@ const defaultValues: FormValues = { ], }; -export const SendWorkspaceInvitationModal: React.FC = (props) => { +export const SendWorkspaceInvitationModal: React.FC = observer((props) => { const { isOpen, onClose, onSubmit } = props; - + // mobx store + const { + user: { currentWorkspaceRole }, + } = useMobxStore(); // form info const { control, @@ -59,30 +64,6 @@ export const SendWorkspaceInvitationModal: React.FC = (props) => { }, 350); }; - // const onSubmit = async (formData: FormValues) => { - // if (!workspaceSlug) return; - - // return workspaceService - // .inviteWorkspace(workspaceSlug, formData, user) - - // .then(async () => { - // if (onSuccess) await onSuccess(); - // handleClose(); - // setToastAlert({ - // type: "success", - // title: "Success!", - // message: "Invitations sent successfully.", - // }); - // }) - // .catch((err) => - // setToastAlert({ - // type: "error", - // title: "Error!", - // message: `${err.error ?? "Something went wrong. Please try again."}`, - // }) - // ); - // }; - const appendField = () => { append({ email: "", role: 15 }); }; @@ -181,11 +162,14 @@ export const SendWorkspaceInvitationModal: React.FC = (props) => { width="w-full" input > - {Object.entries(ROLE).map(([key, value]) => ( - - {value} - - ))} + {Object.entries(ROLE).map(([key, value]) => { + if (currentWorkspaceRole && currentWorkspaceRole >= parseInt(key)) + return ( + + {value} + + ); + })} )} /> @@ -230,4 +214,4 @@ export const SendWorkspaceInvitationModal: React.FC = (props) => { ); -}; +}); diff --git a/web/components/workspace/settings/members-list-item.tsx b/web/components/workspace/settings/members-list-item.tsx index 5384977f8..ec3c9db69 100644 --- a/web/components/workspace/settings/members-list-item.tsx +++ b/web/components/workspace/settings/members-list-item.tsx @@ -1,7 +1,9 @@ import { useState, FC } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; import { mutate } from "swr"; +import { ChevronDown, Dot, XCircle } from "lucide-react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // hooks @@ -10,12 +12,10 @@ import useToast from "hooks/use-toast"; import { ConfirmWorkspaceMemberRemove } from "components/workspace"; // ui import { CustomSelect, Tooltip } from "@plane/ui"; -// icons -import { ChevronDown, Dot, XCircle } from "lucide-react"; // types import { TUserWorkspaceRole } from "types"; // constants -import { ROLE } from "constants/workspace"; +import { EUserWorkspaceRoles, ROLE } from "constants/workspace"; type Props = { member: { @@ -33,7 +33,7 @@ type Props = { }; }; -export const WorkspaceMembersListItem: FC = (props) => { +export const WorkspaceMembersListItem: FC = observer((props) => { const { member } = props; // router const router = useRouter(); @@ -43,7 +43,6 @@ export const WorkspaceMembersListItem: FC = (props) => { workspaceMember: { removeMember, updateMember, deleteWorkspaceInvitation }, user: { currentWorkspaceMemberInfo, currentWorkspaceRole, currentUser, currentUserSettings, leaveWorkspace }, } = useMobxStore(); - const isAdmin = currentWorkspaceRole === 20; // states const [removeMemberModal, setRemoveMemberModal] = useState(false); // hooks @@ -53,10 +52,7 @@ export const WorkspaceMembersListItem: FC = (props) => { if (!workspaceSlug || !currentUserSettings) return; await leaveWorkspace(workspaceSlug.toString()) - .then(() => { - if (currentUserSettings.workspace?.invites > 0) router.push("/invitations"); - else router.push("/create-workspace"); - }) + .then(() => router.push("/profile")) .catch((err) => setToastAlert({ type: "error", @@ -114,6 +110,20 @@ export const WorkspaceMembersListItem: FC = (props) => { } else await handleRemoveInvitation(); }; + // is the member current logged in user + const isCurrentUser = member.memberId === currentWorkspaceMemberInfo?.member; + // is the current logged in user admin + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; + // role change access- + // 1. user cannot change their own role + // 2. only admin or member can change role + // 3. user cannot change role of higher role + const hasRoleChangeAccess = + currentWorkspaceRole && + !isCurrentUser && + [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole) && + member.role <= currentWorkspaceRole; + if (!currentWorkspaceMemberInfo) return null; return ( @@ -180,12 +190,12 @@ export const WorkspaceMembersListItem: FC = (props) => {
{ROLE[member.role as keyof typeof ROLE]} - {member.memberId !== currentWorkspaceMemberInfo.member && ( + {hasRoleChangeAccess && ( @@ -206,11 +216,7 @@ export const WorkspaceMembersListItem: FC = (props) => { }); }); }} - disabled={ - member.memberId === currentWorkspaceMemberInfo.member || - !member.status || - Boolean(currentWorkspaceRole && currentWorkspaceRole !== 20 && currentWorkspaceRole < member.role) - } + disabled={!hasRoleChangeAccess} placement="bottom-end" > {Object.keys(ROLE).map((key) => { @@ -224,23 +230,24 @@ export const WorkspaceMembersListItem: FC = (props) => { ); })} - {isAdmin && ( - + - - )} + + +
); -}; +}); diff --git a/web/components/workspace/settings/members-list.tsx b/web/components/workspace/settings/members-list.tsx index 854c3b069..e95418e16 100644 --- a/web/components/workspace/settings/members-list.tsx +++ b/web/components/workspace/settings/members-list.tsx @@ -9,18 +9,14 @@ import { WorkspaceMembersListItem } from "components/workspace"; // ui import { Loader } from "@plane/ui"; -export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ searchQuery }) => { +export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer((props) => { + const { searchQuery } = props; + // router const router = useRouter(); const { workspaceSlug } = router.query; // store const { - workspaceMember: { - workspaceMembers, - workspaceMembersWithInvitations, - workspaceMemberInvitations, - fetchWorkspaceMemberInvitations, - }, - user: { currentWorkspaceMemberInfo }, + workspaceMember: { workspaceMembersWithInvitations, fetchWorkspaceMemberInvitations }, } = useMobxStore(); // fetching workspace invitations useSWR( @@ -36,12 +32,7 @@ export const WorkspaceMembersList: FC<{ searchQuery: string }> = observer(({ sea return `${email}${displayName}${fullName}`.includes(searchQuery.toLowerCase()); }); - if ( - !workspaceMembers || - !workspaceMemberInvitations || - !workspaceMembersWithInvitations || - !currentWorkspaceMemberInfo - ) + if (!workspaceMembersWithInvitations) return ( diff --git a/web/components/workspace/settings/workspace-details.tsx b/web/components/workspace/settings/workspace-details.tsx index 318cc9fe0..8ec94a6be 100644 --- a/web/components/workspace/settings/workspace-details.tsx +++ b/web/components/workspace/settings/workspace-details.tsx @@ -14,12 +14,12 @@ import { DeleteWorkspaceModal } from "components/workspace"; import { WorkspaceImageUploadModal } from "components/core"; // ui import { Button, CustomSelect, Input, Spinner } from "@plane/ui"; +// helpers +import { copyUrlToClipboard } from "helpers/string.helper"; // types import { IWorkspace } from "types"; // constants -import { ORGANIZATION_SIZE } from "constants/workspace"; -import { trackEvent } from "helpers/event-tracker.helper"; -import { copyUrlToClipboard } from "helpers/string.helper"; +import { EUserWorkspaceRoles, ORGANIZATION_SIZE } from "constants/workspace"; const defaultValues: Partial = { name: "", @@ -40,9 +40,9 @@ export const WorkspaceDetails: FC = observer(() => { const { workspace: { currentWorkspace, updateWorkspace }, user: { currentWorkspaceRole }, - trackEvent: { postHogEventTracker } + trackEvent: { postHogEventTracker }, } = useMobxStore(); - const isAdmin = currentWorkspaceRole === 20; + // hooks const { setToastAlert } = useToast(); // form info @@ -67,28 +67,22 @@ export const WorkspaceDetails: FC = observer(() => { await updateWorkspace(currentWorkspace.slug, payload) .then((res) => { - postHogEventTracker( - 'WORKSPACE_UPDATE', - { - ...res, - state: "SUCCESS" - } - ) + postHogEventTracker("WORKSPACE_UPDATE", { + ...res, + state: "SUCCESS", + }); setToastAlert({ title: "Success", type: "success", message: "Workspace updated successfully", }); - }).catch((err) => { - postHogEventTracker( - 'WORKSPACE_UPDATE', - { - state: "FAILED" - } - ); - console.error(err) - } - ); + }) + .catch((err) => { + postHogEventTracker("WORKSPACE_UPDATE", { + state: "FAILED", + }); + console.error(err); + }); }; const handleRemoveLogo = () => { @@ -136,6 +130,8 @@ export const WorkspaceDetails: FC = observer(() => { if (currentWorkspace) reset({ ...currentWorkspace }); }, [currentWorkspace, reset]); + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; + if (!currentWorkspace) return (
@@ -192,11 +188,10 @@ export const WorkspaceDetails: FC = observer(() => { -
+ {isAdmin && ( -
+ )}
-
-

Workspace Name

+
+

Workspace name

{
-

Company Size

+

Company size

{ id="url" name="url" type="url" - value={`${typeof window !== "undefined" && + value={`${ + typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "") - }/${currentWorkspace.slug}`} + }/${currentWorkspace.slug}`} onChange={onChange} ref={ref} hasError={Boolean(errors.url)} @@ -291,11 +287,13 @@ export const WorkspaceDetails: FC = observer(() => {
-
- -
+ {isAdmin && ( +
+ +
+ )}
{isAdmin && ( diff --git a/web/constants/workspace.ts b/web/constants/workspace.ts index 1151402a1..42c38e606 100644 --- a/web/constants/workspace.ts +++ b/web/constants/workspace.ts @@ -4,8 +4,16 @@ import JiraLogo from "public/services/jira.svg"; import CSVLogo from "public/services/csv.svg"; import ExcelLogo from "public/services/excel.svg"; import JSONLogo from "public/services/json.svg"; +// types import { TStaticViewTypes } from "types"; +export enum EUserWorkspaceRoles { + GUEST = 5, + VIEWER = 10, + MEMBER = 15, + ADMIN = 20, +} + export const ROLE = { 5: "Guest", 10: "Viewer", @@ -105,3 +113,50 @@ export const RESTRICTED_URLS = [ "spaces", "workspace-invitations", ]; + +export const WORKSPACE_SETTINGS_LINKS: { + label: string; + href: string; + access: EUserWorkspaceRoles; +}[] = [ + { + label: "General", + href: `/settings`, + access: EUserWorkspaceRoles.GUEST, + }, + { + label: "Members", + href: `/settings/members`, + access: EUserWorkspaceRoles.GUEST, + }, + { + label: "Billing and plans", + href: `/settings/billing`, + access: EUserWorkspaceRoles.ADMIN, + }, + { + label: "Integrations", + href: `/settings/integrations`, + access: EUserWorkspaceRoles.ADMIN, + }, + { + label: "Imports", + href: `/settings/imports`, + access: EUserWorkspaceRoles.ADMIN, + }, + { + label: "Exports", + href: `/settings/exports`, + access: EUserWorkspaceRoles.MEMBER, + }, + { + label: "Webhooks", + href: `/settings/webhooks`, + access: EUserWorkspaceRoles.ADMIN, + }, + { + label: "API tokens", + href: `/settings/api-tokens`, + access: EUserWorkspaceRoles.ADMIN, + }, +]; diff --git a/web/layouts/settings-layout/workspace/sidebar.tsx b/web/layouts/settings-layout/workspace/sidebar.tsx index 766aca710..ec34abd8e 100644 --- a/web/layouts/settings-layout/workspace/sidebar.tsx +++ b/web/layouts/settings-layout/workspace/sidebar.tsx @@ -1,82 +1,35 @@ import React from "react"; import { useRouter } from "next/router"; import Link from "next/link"; -import { RootStore } from "store/root"; +// mobx store import { useMobxStore } from "lib/mobx/store-provider"; - -export enum EUserWorkspaceRoles { - GUEST = 5, - MEMBER = 15, - ADMIN = 20, -} +// constants +import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "constants/workspace"; export const WorkspaceSettingsSidebar = () => { + // router const router = useRouter(); const { workspaceSlug } = router.query; - const { user: userStore }: RootStore = useMobxStore(); + // mobx store + const { + user: { currentWorkspaceRole }, + } = useMobxStore(); - const workspaceMemberInfo = userStore.currentWorkspaceRole || EUserWorkspaceRoles.GUEST; - - const workspaceLinks: Array<{ - label: string; - href: string; - access: EUserWorkspaceRoles; - }> = [ - { - label: "General", - href: `/${workspaceSlug}/settings`, - access: EUserWorkspaceRoles.GUEST, - }, - { - label: "Members", - href: `/${workspaceSlug}/settings/members`, - access: EUserWorkspaceRoles.GUEST, - }, - { - label: "Billing and plans", - href: `/${workspaceSlug}/settings/billing`, - access: EUserWorkspaceRoles.ADMIN, - }, - { - label: "Integrations", - href: `/${workspaceSlug}/settings/integrations`, - access: EUserWorkspaceRoles.ADMIN, - }, - { - label: "Imports", - href: `/${workspaceSlug}/settings/imports`, - access: EUserWorkspaceRoles.ADMIN, - }, - { - label: "Exports", - href: `/${workspaceSlug}/settings/exports`, - access: EUserWorkspaceRoles.MEMBER, - }, - { - label: "Webhooks", - href: `/${workspaceSlug}/settings/webhooks`, - access: EUserWorkspaceRoles.ADMIN, - }, - { - label: "API tokens", - href: `/${workspaceSlug}/settings/api-tokens`, - access: EUserWorkspaceRoles.ADMIN, - }, - ]; + const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST; return (
SETTINGS
- {workspaceLinks.map( + {WORKSPACE_SETTINGS_LINKS.map( (link) => workspaceMemberInfo >= link.access && ( - +
{ user: { currentWorkspaceRole }, } = useMobxStore(); - const isAdmin = currentWorkspaceRole === 20; + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; const { data: tokens } = useSWR(workspaceSlug && isAdmin ? API_TOKENS_LIST(workspaceSlug.toString()) : null, () => workspaceSlug && isAdmin ? apiTokenService.getApiTokens(workspaceSlug.toString()) : null diff --git a/web/pages/[workspaceSlug]/settings/billing.tsx b/web/pages/[workspaceSlug]/settings/billing.tsx index bd7fe3f2f..9d4d85e4c 100644 --- a/web/pages/[workspaceSlug]/settings/billing.tsx +++ b/web/pages/[workspaceSlug]/settings/billing.tsx @@ -1,4 +1,6 @@ -import { ReactElement } from "react"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; @@ -8,27 +10,44 @@ import { WorkspaceSettingHeader } from "components/headers"; import { Button } from "@plane/ui"; // types import { NextPageWithLayout } from "types/app"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; -const BillingSettingsPage: NextPageWithLayout = () => ( -
-
-); +
+
+

Current plan

+

You are currently using the free plan

+ + + +
+
+ + ); +}); -BillingSettingsPage.getLayout = function getLayout(page: ReactElement) { +BillingSettingsPage.getLayout = function getLayout(page: React.ReactElement) { return ( }> {page} diff --git a/web/pages/[workspaceSlug]/settings/exports.tsx b/web/pages/[workspaceSlug]/settings/exports.tsx index 07e76c86f..9eaea6de0 100644 --- a/web/pages/[workspaceSlug]/settings/exports.tsx +++ b/web/pages/[workspaceSlug]/settings/exports.tsx @@ -1,4 +1,6 @@ -import { ReactElement } from "react"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // layout import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; @@ -7,17 +9,35 @@ import { WorkspaceSettingHeader } from "components/headers"; import ExportGuide from "components/exporter/guide"; // types import { NextPageWithLayout } from "types/app"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; -const ExportsPage: NextPageWithLayout = () => ( -
-
-

Exports

+const ExportsPage: NextPageWithLayout = observer(() => { + const { + user: { currentWorkspaceRole }, + } = useMobxStore(); + + const hasPageAccess = + currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole); + + if (!hasPageAccess) + return ( +
+

You are not authorized to access this page.

+
+ ); + + return ( +
+
+

Exports

+
+
- -
-); + ); +}); -ExportsPage.getLayout = function getLayout(page: ReactElement) { +ExportsPage.getLayout = function getLayout(page: React.ReactElement) { return ( }> {page} diff --git a/web/pages/[workspaceSlug]/settings/imports.tsx b/web/pages/[workspaceSlug]/settings/imports.tsx index 932c939f6..d7d37e8f4 100644 --- a/web/pages/[workspaceSlug]/settings/imports.tsx +++ b/web/pages/[workspaceSlug]/settings/imports.tsx @@ -1,4 +1,6 @@ -import { ReactElement } from "react"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { WorkspaceSettingLayout } from "layouts/settings-layout"; import { AppLayout } from "layouts/app-layout"; @@ -7,17 +9,34 @@ import IntegrationGuide from "components/integration/guide"; import { WorkspaceSettingHeader } from "components/headers"; // types import { NextPageWithLayout } from "types/app"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; -const ImportsPage: NextPageWithLayout = () => ( -
-
-

Imports

-
- -
-); +const ImportsPage: NextPageWithLayout = observer(() => { + const { + user: { currentWorkspaceRole }, + } = useMobxStore(); -ImportsPage.getLayout = function getLayout(page: ReactElement) { + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; + + if (!isAdmin) + return ( +
+

You are not authorized to access this page.

+
+ ); + + return ( +
+
+

Imports

+
+ +
+ ); +}); + +ImportsPage.getLayout = function getLayout(page: React.ReactElement) { return ( }> {page} diff --git a/web/pages/[workspaceSlug]/settings/integrations.tsx b/web/pages/[workspaceSlug]/settings/integrations.tsx index c16330e29..485c44db8 100644 --- a/web/pages/[workspaceSlug]/settings/integrations.tsx +++ b/web/pages/[workspaceSlug]/settings/integrations.tsx @@ -1,6 +1,9 @@ import { ReactElement } from "react"; import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; import useSWR from "swr"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // services import { IntegrationService } from "services/integrations"; // layouts @@ -16,16 +19,31 @@ import { Loader } from "@plane/ui"; import { NextPageWithLayout } from "types/app"; // fetch-keys import { APP_INTEGRATIONS } from "constants/fetch-keys"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; -// services const integrationService = new IntegrationService(); -const WorkspaceIntegrationsPage: NextPageWithLayout = () => { +const WorkspaceIntegrationsPage: NextPageWithLayout = observer(() => { + // router const router = useRouter(); const { workspaceSlug } = router.query; + // mobx store + const { + user: { currentWorkspaceRole }, + } = useMobxStore(); - const { data: appIntegrations } = useSWR(workspaceSlug ? APP_INTEGRATIONS : null, () => - workspaceSlug ? integrationService.getAppIntegrationsList() : null + const isAdmin = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; + + if (!isAdmin) + return ( +
+

You are not authorized to access this page.

+
+ ); + + const { data: appIntegrations } = useSWR(workspaceSlug && isAdmin ? APP_INTEGRATIONS : null, () => + workspaceSlug && isAdmin ? integrationService.getAppIntegrationsList() : null ); return ( @@ -43,7 +61,7 @@ const WorkspaceIntegrationsPage: NextPageWithLayout = () => {
); -}; +}); WorkspaceIntegrationsPage.getLayout = function getLayout(page: ReactElement) { return ( diff --git a/web/pages/[workspaceSlug]/settings/members.tsx b/web/pages/[workspaceSlug]/settings/members.tsx index a96af5309..797c7bb3e 100644 --- a/web/pages/[workspaceSlug]/settings/members.tsx +++ b/web/pages/[workspaceSlug]/settings/members.tsx @@ -1,9 +1,11 @@ import { useState, ReactElement } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; +import { Search } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; // hooks import useToast from "hooks/use-toast"; -import { useMobxStore } from "lib/mobx/store-provider"; // layouts import { AppLayout } from "layouts/app-layout"; import { WorkspaceSettingLayout } from "layouts/settings-layout"; @@ -12,21 +14,20 @@ import { WorkspaceSettingHeader } from "components/headers"; import { SendWorkspaceInvitationModal, WorkspaceMembersList } from "components/workspace"; // ui import { Button } from "@plane/ui"; -// icons -import { Search } from "lucide-react"; -// helpers -import { trackEvent } from "helpers/event-tracker.helper"; // types import { NextPageWithLayout } from "types/app"; import { IWorkspaceBulkInviteFormData } from "types"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { const router = useRouter(); const { workspaceSlug } = router.query; // store const { + user: { currentWorkspaceRole }, workspaceMember: { inviteMembersToWorkspace }, - trackEvent: { postHogEventTracker, setTrackElement } + trackEvent: { postHogEventTracker, setTrackElement }, } = useMobxStore(); // states const [inviteModal, setInviteModal] = useState(false); @@ -57,15 +58,16 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { }); }; + const hasAddMemberPermission = + currentWorkspaceRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentWorkspaceRole); + return ( <> - {workspaceSlug && ( - setInviteModal(false)} - onSubmit={handleWorkspaceInvite} - /> - )} + setInviteModal(false)} + onSubmit={handleWorkspaceInvite} + />

Members

@@ -79,13 +81,18 @@ const WorkspaceMembersSettingsPage: NextPageWithLayout = observer(() => { onChange={(e) => setSearchQuery(e.target.value)} />
- + {hasAddMemberPermission && ( + + )}
diff --git a/web/store/workspace/workspace-member.store.ts b/web/store/workspace/workspace-member.store.ts index f8885716f..cc70f78cf 100644 --- a/web/store/workspace/workspace-member.store.ts +++ b/web/store/workspace/workspace-member.store.ts @@ -88,7 +88,8 @@ export class WorkspaceMemberStore implements IWorkspaceMemberStore { * computed value provides the members information including the invitations. */ get workspaceMembersWithInvitations() { - if (!this.workspaceMembers || !this.workspaceMemberInvitations) return null; + if (!this.workspaceMembers) return null; + return [ ...(this.workspaceMemberInvitations?.map((item) => ({ id: item.id, diff --git a/web/types/workspace.d.ts b/web/types/workspace.d.ts index 1b9b924f4..fb2aca722 100644 --- a/web/types/workspace.d.ts +++ b/web/types/workspace.d.ts @@ -1,4 +1,4 @@ -import type { IProjectMember, IUser, IUserLite, IUserMemberLite, IWorkspaceViewProps } from "types"; +import type { IProjectMember, IUser, IUserLite, IWorkspaceViewProps } from "types"; export type TUserWorkspaceRole = 5 | 10 | 15 | 20;