diff --git a/web/components/headers/pages.tsx b/web/components/headers/pages.tsx index 7762458f2b..ec4df01a9b 100644 --- a/web/components/headers/pages.tsx +++ b/web/components/headers/pages.tsx @@ -1,4 +1,3 @@ -import { FC } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import { FileText, Plus } from "lucide-react"; @@ -8,18 +7,22 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { Breadcrumbs, Button } from "@plane/ui"; // helper import { renderEmoji } from "helpers/emoji.helper"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; -export interface IPagesHeaderProps { - showButton?: boolean; -} - -export const PagesHeader: FC = observer((props) => { - const { showButton = false } = props; +export const PagesHeader = observer(() => { + // router const router = useRouter(); const { workspaceSlug } = router.query; + // mobx store + const { + user: { currentProjectRole }, + project: { currentProjectDetails }, + commandPalette: { toggleCreatePageModal }, + } = useMobxStore(); - const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore(); - const { currentProjectDetails } = projectStore; + const canUserCreatePage = + currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole); return (
@@ -50,14 +53,9 @@ export const PagesHeader: FC = observer((props) => {
- {showButton && ( + {canUserCreatePage && (
-
diff --git a/web/components/pages/page-form.tsx b/web/components/pages/page-form.tsx index 594390255d..f7c4b219af 100644 --- a/web/components/pages/page-form.tsx +++ b/web/components/pages/page-form.tsx @@ -102,13 +102,7 @@ export const PageForm: React.FC = (props) => { Cancel diff --git a/web/components/pages/pages-list/list-item.tsx b/web/components/pages/pages-list/list-item.tsx index 9bc194cb96..c5d6087454 100644 --- a/web/components/pages/pages-list/list-item.tsx +++ b/web/components/pages/pages-list/list-item.tsx @@ -1,7 +1,6 @@ import { FC, useState } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; -// icons import { AlertCircle, Archive, @@ -14,12 +13,13 @@ import { Star, Trash2, } 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"; // helpers import { copyUrlToClipboard } from "helpers/string.helper"; -import { renderShortDate, render24HourFormatTime, renderLongDateFormat } from "helpers/date-time.helper"; +import { render24HourFormatTime, renderFormattedDate } from "helpers/date-time.helper"; // ui import { CustomMenu, Tooltip } from "@plane/ui"; // components @@ -39,10 +39,10 @@ export const PagesListItem: FC = observer((props) => { // states const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false); const [deletePageModal, setDeletePageModal] = useState(false); - // store + // mobx store const { page: { archivePage, removeFromFavorites, addToFavorites, makePublic, makePrivate, restorePage }, - user: { currentProjectRole }, + user: { currentUser, currentProjectRole }, projectMember: { projectMembers }, } = useMobxStore(); // hooks @@ -145,7 +145,15 @@ export const PagesListItem: FC = observer((props) => { setCreateUpdatePageModal(true); }; - const userCanEdit = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; + const ownerDetails = projectMembers?.find((projectMember) => projectMember.member.id === page.owned_by)?.member; + const isCurrentUserOwner = page.owned_by === currentUser?.id; + + const userCanEdit = + isCurrentUserOwner || + (currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole)); + const userCanChangeAccess = isCurrentUserOwner; + const userCanArchive = isCurrentUserOwner || currentProjectRole === EUserWorkspaceRoles.ADMIN; + const userCanDelete = isCurrentUserOwner || currentProjectRole === EUserWorkspaceRoles.ADMIN; return ( <> @@ -185,7 +193,7 @@ export const PagesListItem: FC = observer((props) => {
{page.archived_at ? ( @@ -193,27 +201,25 @@ export const PagesListItem: FC = observer((props) => { ) : (

{render24HourFormatTime(page.updated_at)}

)} - {!page.archived_at && userCanEdit && ( - - {page.is_favorite ? ( - - ) : ( - - )} - - )} - {!page.archived_at && userCanEdit && ( + + {page.is_favorite ? ( + + ) : ( + + )} + + {userCanChangeAccess && ( = observer((props) => { )} projectMember.member.id === page.created_by)?.member - .display_name ?? "" - } on ${renderLongDateFormat(`${page.created_at}`)}`} + tooltipContent={`Created by ${ownerDetails?.display_name} on ${renderFormattedDate(page.created_at)}`} > - {page.archived_at ? ( - - {userCanEdit && ( - <> + + {page.archived_at ? ( + <> + {userCanArchive && (
Restore page
+ )} + {userCanDelete && (
Delete page
- - )} - -
- - Copy page link -
-
-
- ) : ( - - {userCanEdit && ( - <> + )} + + ) : ( + <> + {userCanEdit && (
Edit page
+ )} + {userCanArchive && (
Archive page
- - )} - -
- - Copy page link -
-
-
- )} + )} + + )} + +
+ + Copy page link +
+
+
diff --git a/web/components/pages/pages-list/list-view.tsx b/web/components/pages/pages-list/list-view.tsx index d87e7dc574..f87262d38b 100644 --- a/web/components/pages/pages-list/list-view.tsx +++ b/web/components/pages/pages-list/list-view.tsx @@ -10,9 +10,11 @@ import { NewEmptyState } from "components/common/new-empty-state"; // ui import { Loader } from "@plane/ui"; // images -import emptyPage from "public/empty-state/empty_page.webp"; +import emptyPage from "public/empty-state/empty_page.png"; // types import { IPage } from "types"; +// constants +import { EUserWorkspaceRoles } from "constants/workspace"; type IPagesListView = { pages: IPage[]; @@ -20,11 +22,27 @@ type IPagesListView = { export const PagesListView: FC = observer(({ pages }) => { // store - const { commandPalette: commandPaletteStore } = useMobxStore(); + const { + user: { currentProjectRole }, + commandPalette: { toggleCreatePageModal }, + } = useMobxStore(); // router const router = useRouter(); const { workspaceSlug, projectId } = router.query; + const canUserCreatePage = + currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole); + + const emptyStatePrimaryButton = canUserCreatePage + ? { + primaryButton: { + icon: , + text: "Create your first page", + onClick: () => toggleCreatePageModal(true), + }, + } + : {}; + return ( <> {pages && workspaceSlug && projectId ? ( @@ -51,11 +69,7 @@ export const PagesListView: FC = observer(({ pages }) => { "We wrote Parth and Meera’s love story. You could write your project’s mission, goals, and eventual vision.", direction: "right", }} - primaryButton={{ - icon: , - text: "Create your first page", - onClick: () => commandPaletteStore.toggleCreatePageModal(true), - }} + {...emptyStatePrimaryButton} /> )} diff --git a/web/components/pages/pages-list/recent-pages-list.tsx b/web/components/pages/pages-list/recent-pages-list.tsx index 475cb5db29..206690c2f6 100644 --- a/web/components/pages/pages-list/recent-pages-list.tsx +++ b/web/components/pages/pages-list/recent-pages-list.tsx @@ -9,7 +9,7 @@ import { NewEmptyState } from "components/common/new-empty-state"; // ui import { Loader } from "@plane/ui"; // assets -import emptyPage from "public/empty-state/empty_page.webp"; +import emptyPage from "public/empty-state/empty_page.png"; // helpers import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper"; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index fcc88cbdf3..b963207db1 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -29,6 +29,7 @@ import { NextPageWithLayout } from "types/app"; import { IPage } from "types"; // fetch-keys import { PAGE_DETAILS } from "constants/fetch-keys"; +import { EUserWorkspaceRoles } from "constants/workspace"; // services const fileService = new FileService(); @@ -42,6 +43,7 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { // store const { appConfig: { envConfig }, + user: { currentProjectRole }, } = useMobxStore(); // router const router = useRouter(); @@ -217,12 +219,24 @@ const PageDetailsPage: NextPageWithLayout = observer(() => { /> ); + const isPageReadOnly = + pageDetails?.is_locked || + (currentProjectRole && [EUserWorkspaceRoles.VIEWER, EUserWorkspaceRoles.GUEST].includes(currentProjectRole)); + + const isCurrentUserOwner = pageDetails?.owned_by === user?.id; + + const userCanDuplicate = + currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole); + const userCanArchive = isCurrentUserOwner || currentProjectRole === EUserWorkspaceRoles.ADMIN; + const userCanLock = + currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole); + return ( <> {pageDetails ? (
- {pageDetails.is_locked || pageDetails.archived_at ? ( + {isPageReadOnly ? ( { setIsSubmitting("submitting"); debouncedFormSave(); }} - duplicationConfig={{ action: duplicate_page }} + duplicationConfig={userCanDuplicate ? { action: duplicate_page } : undefined} pageArchiveConfig={ - user && pageDetails.owned_by === user.id + userCanArchive ? { is_archived: pageDetails.archived_at ? true : false, action: pageDetails.archived_at ? unArchivePage : archivePage, } : undefined } - pageLockConfig={ - user && pageDetails.owned_by === user.id ? { is_locked: false, action: lockPage } : undefined - } + pageLockConfig={userCanLock ? { is_locked: false, action: lockPage } : undefined} /> )} /> diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index edc2ef7d95..fd9a291031 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -162,7 +162,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { ProjectPagesPage.getLayout = function getLayout(page: ReactElement) { return ( - } withProjectWrapper> + } withProjectWrapper> {page} ); diff --git a/web/public/empty-state/empty_page.png b/web/public/empty-state/empty_page.png new file mode 100644 index 0000000000..993774c700 Binary files /dev/null and b/web/public/empty-state/empty_page.png differ diff --git a/web/public/empty-state/empty_page.webp b/web/public/empty-state/empty_page.webp deleted file mode 100644 index a0d1863b53..0000000000 Binary files a/web/public/empty-state/empty_page.webp and /dev/null differ