Improvement: High Performance MobX Integration for Pages ✈︎ (#3397)
* fix: removed parameters `workspace`, `project` & `id` from the patch calls * feat: modified components to work with new pages hooks * feat: modified stores * feat: modified initial component * feat: component implementation changes * feat: store implementation * refactor pages store * feat: updated page store to perform async operations faster * fix: added types for archive and restore pages * feat: implemented archive and restore pages * fix: page creating twice when form submit * feat: updated create-page-modal * feat: updated page form and delete page modal * fix: create page modal not updating isSubmitted prop * feat: list items and list view refactored for pages * feat: refactored project-page-store for inserting computed pagesids * chore: renamed project pages hook * feat: added favourite pages implementation * fix: implemented store for archived pages * fix: project page store for recent pages * fix: issue suggestions breaking pages * fix: issue embeds and suggestions breaking * feat: implemented page store and project page store in page editor * chore: lock file changes * fix: modified page details header to catch mobx updates instead of swr calls * fix: modified usePage hook to fetch page details when reloaded directly on page * fix: fixed deleting pages * fix: removed render on props changed * feat: implemented page store inside page details * fix: role change in pages archives * fix: rerending of pages on tab change * fix: reimplementation of peek overview inside pages * chore: typo fixes * fix: issue suggestion widget selecting wrong issues on click * feat: added labels in pages * fix: deepsource errors fixed * fix: build errors * fix: review comments * fix: removed swr hooks from the `usePage` store hook and refactored `issueEmbed` hook * fix: resolved reviewed comments --------- Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
This commit is contained in:
parent
d3dedc8e51
commit
06a7bdffd7
32 changed files with 960 additions and 1100 deletions
|
|
@ -1,23 +1,17 @@
|
|||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { FileText, Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
import { useApplication, usePage, useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// fetch-keys
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
export interface IPagesHeaderProps {
|
||||
showButton?: boolean;
|
||||
}
|
||||
const pageService = new PageService();
|
||||
|
||||
export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
const { showButton = false } = props;
|
||||
|
|
@ -28,12 +22,7 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
|||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: pageDetails } = useSWR(
|
||||
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,
|
||||
workspaceSlug && currentProjectDetails?.id
|
||||
? () => pageService.getPageDetails(workspaceSlug as string, currentProjectDetails.id, pageId as string)
|
||||
: null
|
||||
);
|
||||
const pageDetails = usePage(pageId as string);
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
|
|
|
|||
|
|
@ -2,132 +2,56 @@ import React, { FC } from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// hooks
|
||||
import { useApplication, usePage, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { PageForm } from "./page-form";
|
||||
// types
|
||||
import { IPage } from "@plane/types";
|
||||
import { useProjectPages } from "hooks/store/use-project-page";
|
||||
import { IPageStore } from "store/page.store";
|
||||
|
||||
type Props = {
|
||||
data?: IPage | null;
|
||||
// data?: IPage | null;
|
||||
pageStore?: IPageStore;
|
||||
handleClose: () => void;
|
||||
isOpen: boolean;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const CreateUpdatePageModal: FC<Props> = (props) => {
|
||||
const { isOpen, handleClose, data, projectId } = props;
|
||||
const { isOpen, handleClose, projectId, pageStore } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { createPage } = useProjectPages();
|
||||
// store hooks
|
||||
const {
|
||||
eventTracker: { postHogEventTracker },
|
||||
} = useApplication();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { createPage, updatePage } = usePage();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const onClose = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const createProjectPage = async (payload: IPage) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
// await createPage(workspaceSlug.toString(), projectId, payload)
|
||||
// .then((res) => {
|
||||
// router.push(`/${workspaceSlug}/projects/${projectId}/pages/${res.id}`);
|
||||
// onClose();
|
||||
// setToastAlert({
|
||||
// type: "success",
|
||||
// title: "Success!",
|
||||
// message: "Page created successfully.",
|
||||
// });
|
||||
// postHogEventTracker(
|
||||
// "PAGE_CREATED",
|
||||
// {
|
||||
// ...res,
|
||||
// state: "SUCCESS",
|
||||
// },
|
||||
// {
|
||||
// isGrouping: true,
|
||||
// groupType: "Workspace_metrics",
|
||||
// groupId: currentWorkspace?.id!,
|
||||
// }
|
||||
// );
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// setToastAlert({
|
||||
// type: "error",
|
||||
// title: "Error!",
|
||||
// message: err.detail ?? "Page could not be created. Please try again.",
|
||||
// });
|
||||
// postHogEventTracker(
|
||||
// "PAGE_CREATED",
|
||||
// {
|
||||
// state: "FAILED",
|
||||
// },
|
||||
// {
|
||||
// isGrouping: true,
|
||||
// groupType: "Workspace_metrics",
|
||||
// groupId: currentWorkspace?.id!,
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
};
|
||||
|
||||
const updateProjectPage = async (payload: IPage) => {
|
||||
if (!data || !workspaceSlug) return;
|
||||
|
||||
// await updatePage(workspaceSlug.toString(), projectId, data.id, payload)
|
||||
// .then((res) => {
|
||||
// onClose();
|
||||
// setToastAlert({
|
||||
// type: "success",
|
||||
// title: "Success!",
|
||||
// message: "Page updated successfully.",
|
||||
// });
|
||||
// postHogEventTracker(
|
||||
// "PAGE_UPDATED",
|
||||
// {
|
||||
// ...res,
|
||||
// state: "SUCCESS",
|
||||
// },
|
||||
// {
|
||||
// isGrouping: true,
|
||||
// groupType: "Workspace_metrics",
|
||||
// groupId: currentWorkspace?.id!,
|
||||
// }
|
||||
// );
|
||||
// })
|
||||
// .catch((err) => {
|
||||
// setToastAlert({
|
||||
// type: "error",
|
||||
// title: "Error!",
|
||||
// message: err.detail ?? "Page could not be updated. Please try again.",
|
||||
// });
|
||||
// postHogEventTracker(
|
||||
// "PAGE_UPDATED",
|
||||
// {
|
||||
// state: "FAILED",
|
||||
// },
|
||||
// {
|
||||
// isGrouping: true,
|
||||
// groupType: "Workspace_metrics",
|
||||
// groupId: currentWorkspace?.id!,
|
||||
// }
|
||||
// );
|
||||
// });
|
||||
await createPage(workspaceSlug.toString(), projectId, payload);
|
||||
};
|
||||
|
||||
const handleFormSubmit = async (formData: IPage) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
if (!data) await createProjectPage(formData);
|
||||
else await updateProjectPage(formData);
|
||||
try {
|
||||
if (pageStore) {
|
||||
if (pageStore.name !== formData.name) {
|
||||
await pageStore.updateName(formData.name);
|
||||
}
|
||||
if (pageStore.access !== formData.access) {
|
||||
formData.access === 1 ? await pageStore.makePrivate() : await pageStore.makePublic();
|
||||
}
|
||||
} else {
|
||||
await createProjectPage(formData);
|
||||
}
|
||||
handleClose();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -157,7 +81,7 @@ export const CreateUpdatePageModal: FC<Props> = (props) => {
|
|||
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<Dialog.Panel className="relative transform rounded-lg bg-custom-background-100 p-5 px-4 text-left shadow-custom-shadow-md transition-all sm:w-full sm:max-w-2xl">
|
||||
<PageForm handleFormSubmit={handleFormSubmit} handleClose={handleClose} data={data} />
|
||||
<PageForm handleFormSubmit={handleFormSubmit} handleClose={handleClose} pageStore={pageStore} />
|
||||
</Dialog.Panel>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -9,37 +9,45 @@ import useToast from "hooks/use-toast";
|
|||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import type { IPage } from "@plane/types";
|
||||
import { useProjectPages } from "hooks/store/use-project-page";
|
||||
|
||||
type TConfirmPageDeletionProps = {
|
||||
data?: IPage | null;
|
||||
pageId: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((props) => {
|
||||
const { data, isOpen, onClose } = props;
|
||||
const { pageId, isOpen, onClose } = props;
|
||||
|
||||
// states
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { deletePage } = usePage();
|
||||
const { deletePage } = useProjectPages();
|
||||
const pageStore = usePage(pageId);
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
if (!pageStore) return null;
|
||||
|
||||
const { name } = pageStore;
|
||||
|
||||
const handleClose = () => {
|
||||
setIsDeleting(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!data || !workspaceSlug || !projectId) return;
|
||||
if (!pageId || !workspaceSlug || !projectId) return;
|
||||
|
||||
setIsDeleting(true);
|
||||
|
||||
await deletePage(workspaceSlug.toString(), data.project, data.id)
|
||||
// Delete Page will only delete the page from the archive page map, at this point only archived pages can be deleted
|
||||
await deletePage(workspaceSlug.toString(), projectId as string, pageId)
|
||||
.then(() => {
|
||||
handleClose();
|
||||
setToastAlert({
|
||||
|
|
@ -99,8 +107,8 @@ export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((pr
|
|||
<div className="mt-2">
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete page-{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">{data?.name}</span>? The Page
|
||||
will be deleted permanently. This action cannot be undone.
|
||||
<span className="break-words font-medium text-custom-text-100">{name}</span>? The Page will be
|
||||
deleted permanently. This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import { Button, Input, Tooltip } from "@plane/ui";
|
|||
import { IPage } from "@plane/types";
|
||||
// constants
|
||||
import { PAGE_ACCESS_SPECIFIERS } from "constants/page";
|
||||
import { IPageStore } from "store/page.store";
|
||||
|
||||
type Props = {
|
||||
handleFormSubmit: (values: IPage) => Promise<void>;
|
||||
handleClose: () => void;
|
||||
data?: IPage | null;
|
||||
pageStore?: IPageStore;
|
||||
};
|
||||
|
||||
const defaultValues = {
|
||||
|
|
@ -19,24 +20,24 @@ const defaultValues = {
|
|||
};
|
||||
|
||||
export const PageForm: React.FC<Props> = (props) => {
|
||||
const { handleFormSubmit, handleClose, data } = props;
|
||||
const { handleFormSubmit, handleClose, pageStore } = props;
|
||||
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
control,
|
||||
} = useForm<IPage>({
|
||||
defaultValues: { ...defaultValues, ...data },
|
||||
defaultValues: pageStore
|
||||
? { name: pageStore.name, description: pageStore.description, access: pageStore.access }
|
||||
: defaultValues,
|
||||
});
|
||||
|
||||
const handleCreateUpdatePage = async (formData: IPage) => {
|
||||
await handleFormSubmit(formData);
|
||||
};
|
||||
const handleCreateUpdatePage = (formData: IPage) => handleFormSubmit(formData);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleCreateUpdatePage)}>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium leading-6 text-custom-text-100">{data ? "Update" : "Create"} Page</h3>
|
||||
<h3 className="text-lg font-medium leading-6 text-custom-text-100">{pageStore ? "Update" : "Create"} Page</h3>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<Controller
|
||||
|
|
@ -104,7 +105,7 @@ export const PageForm: React.FC<Props> = (props) => {
|
|||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={5}>
|
||||
{data ? (isSubmitting ? "Updating..." : "Update page") : isSubmitting ? "Creating..." : "Create Page"}
|
||||
{pageStore ? (isSubmitting ? "Updating..." : "Update page") : isSubmitting ? "Creating..." : "Create Page"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { usePage } from "hooks/store";
|
||||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const AllPagesList: FC = observer(() => {
|
||||
// store
|
||||
const { projectPageIds } = usePage();
|
||||
const pageStores = useProjectPages();
|
||||
// subscribing to the projectPageStore
|
||||
const { projectPageIds } = pageStores;
|
||||
|
||||
if (!projectPageIds)
|
||||
if (!projectPageIds) {
|
||||
return (
|
||||
<Loader className="space-y-4">
|
||||
<Loader.Item height="40px" />
|
||||
|
|
@ -19,6 +19,6 @@ export const AllPagesList: FC = observer(() => {
|
|||
<Loader.Item height="40px" />
|
||||
</Loader>
|
||||
);
|
||||
|
||||
}
|
||||
return <PagesListView pageIds={projectPageIds} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
// hooks
|
||||
import { usePage } from "hooks/store";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const ArchivedPagesList: FC = observer(() => {
|
||||
const { archivedProjectPageIds } = usePage();
|
||||
const projectPageStore = useProjectPages();
|
||||
const { archivedPageIds } = projectPageStore;
|
||||
|
||||
if (!archivedProjectPageIds)
|
||||
if (!archivedPageIds)
|
||||
return (
|
||||
<Loader className="space-y-4">
|
||||
<Loader.Item height="40px" />
|
||||
|
|
@ -19,5 +20,5 @@ export const ArchivedPagesList: FC = observer(() => {
|
|||
</Loader>
|
||||
);
|
||||
|
||||
return <PagesListView pageIds={archivedProjectPageIds} />;
|
||||
return <PagesListView pageIds={archivedPageIds} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
// hooks
|
||||
import { usePage } from "hooks/store";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const FavoritePagesList: FC = observer(() => {
|
||||
const { favoriteProjectPageIds } = usePage();
|
||||
const projectPageStore = useProjectPages();
|
||||
const { favoriteProjectPageIds } = projectPageStore;
|
||||
|
||||
if (!favoriteProjectPageIds)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ import {
|
|||
Star,
|
||||
Trash2,
|
||||
} from "lucide-react";
|
||||
// hooks
|
||||
import { useMember, usePage, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// helpers
|
||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||
import { renderFormattedTime, renderFormattedDate } from "helpers/date-time.helper";
|
||||
// ui
|
||||
|
|
@ -25,142 +21,120 @@ import { CustomMenu, Tooltip } from "@plane/ui";
|
|||
import { CreateUpdatePageModal, DeletePageModal } from "components/pages";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { useRouter } from "next/router";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
import { useMember, usePage, useUser } from "hooks/store";
|
||||
import { IIssueLabel } from "@plane/types";
|
||||
|
||||
export interface IPagesListItem {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
pageId: string;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
||||
const { workspaceSlug, projectId, pageId } = props;
|
||||
export const PagesListItem: FC<IPagesListItem> = observer(({ pageId, projectId }: IPagesListItem) => {
|
||||
const projectPageStore = useProjectPages();
|
||||
// Now, I am observing only the projectPages, out of the projectPageStore.
|
||||
const { archivePage, restorePage } = projectPageStore;
|
||||
|
||||
const pageStore = usePage(pageId);
|
||||
|
||||
// states
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
const [createUpdatePageModal, setCreateUpdatePageModal] = useState(false);
|
||||
|
||||
const [deletePageModal, setDeletePageModal] = useState(false);
|
||||
// store hooks
|
||||
|
||||
const {
|
||||
currentUser,
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const {
|
||||
getArchivedPageById,
|
||||
getUnArchivedPageById,
|
||||
archivePage,
|
||||
removeFromFavorites,
|
||||
addToFavorites,
|
||||
makePrivate,
|
||||
makePublic,
|
||||
restorePage,
|
||||
} = usePage();
|
||||
|
||||
const {
|
||||
project: { getProjectMemberDetails },
|
||||
} = useMember();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// derived values
|
||||
const pageDetails = getUnArchivedPageById(pageId) ?? getArchivedPageById(pageId);
|
||||
|
||||
const handleCopyUrl = (e: any) => {
|
||||
if (!pageStore) return null;
|
||||
|
||||
const {
|
||||
archived_at,
|
||||
label_details,
|
||||
access,
|
||||
is_favorite,
|
||||
owned_by,
|
||||
name,
|
||||
created_at,
|
||||
updated_at,
|
||||
makePublic,
|
||||
makePrivate,
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
} = pageStore;
|
||||
|
||||
const handleCopyUrl = async (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/pages/${pageId}`).then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Link Copied!",
|
||||
message: "Page link copied to clipboard.",
|
||||
});
|
||||
});
|
||||
await copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/pages/${pageId}`);
|
||||
};
|
||||
|
||||
const handleAddToFavorites = (e: any) => {
|
||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
addToFavorites();
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
addToFavorites(workspaceSlug, projectId, pageId)
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Successfully added the page to favorites.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't add the page to favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
removeFromFavorites();
|
||||
};
|
||||
|
||||
const handleRemoveFromFavorites = (e: any) => {
|
||||
const handleMakePublic = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
removeFromFavorites(workspaceSlug, projectId, pageId)
|
||||
.then(() => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Successfully removed the page from favorites.",
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Couldn't remove the page from favorites. Please try again.",
|
||||
});
|
||||
});
|
||||
makePublic();
|
||||
};
|
||||
|
||||
const handleMakePublic = (e: any) => {
|
||||
const handleMakePrivate = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
makePublic(workspaceSlug, projectId, pageId);
|
||||
makePrivate();
|
||||
};
|
||||
|
||||
const handleMakePrivate = (e: any) => {
|
||||
const handleArchivePage = async (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
makePrivate(workspaceSlug, projectId, pageId);
|
||||
await archivePage(workspaceSlug as string, projectId as string, pageId as string);
|
||||
};
|
||||
|
||||
const handleArchivePage = (e: any) => {
|
||||
const handleRestorePage = async (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
archivePage(workspaceSlug, projectId, pageId);
|
||||
await restorePage(workspaceSlug as string, projectId as string, pageId as string);
|
||||
};
|
||||
|
||||
const handleRestorePage = (e: any) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
restorePage(workspaceSlug, projectId, pageId);
|
||||
};
|
||||
|
||||
const handleDeletePage = (e: any) => {
|
||||
const handleDeletePage = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
setDeletePageModal(true);
|
||||
};
|
||||
|
||||
const handleEditPage = (e: any) => {
|
||||
const handleEditPage = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
setCreateUpdatePageModal(true);
|
||||
};
|
||||
|
||||
if (!pageDetails) return null;
|
||||
|
||||
const ownerDetails = getProjectMemberDetails(pageDetails.owned_by);
|
||||
const isCurrentUserOwner = pageDetails.owned_by === currentUser?.id;
|
||||
const ownerDetails = getProjectMemberDetails(owned_by);
|
||||
const isCurrentUserOwner = owned_by === currentUser?.id;
|
||||
|
||||
const userCanEdit =
|
||||
isCurrentUserOwner ||
|
||||
|
|
@ -173,22 +147,21 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
return (
|
||||
<>
|
||||
<CreateUpdatePageModal
|
||||
pageStore={pageStore}
|
||||
isOpen={createUpdatePageModal}
|
||||
handleClose={() => setCreateUpdatePageModal(false)}
|
||||
data={pageDetails}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<DeletePageModal isOpen={deletePageModal} onClose={() => setDeletePageModal(false)} data={pageDetails} />
|
||||
<DeletePageModal isOpen={deletePageModal} onClose={() => setDeletePageModal(false)} pageId={pageId} />
|
||||
<li>
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/pages/${pageDetails.id}`}>
|
||||
<Link href={`/${workspaceSlug}/projects/${projectId}/pages/${pageId}`}>
|
||||
<div className="relative rounded p-4 text-custom-text-200 hover:bg-custom-background-80">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
<FileText className="h-4 w-4 shrink-0" />
|
||||
<p className="mr-2 truncate text-sm text-custom-text-100">{pageDetails.name}</p>
|
||||
{/* FIXME: replace any with proper type */}
|
||||
{pageDetails.label_details.length > 0 &&
|
||||
pageDetails.label_details.map((label: any) => (
|
||||
<p className="mr-2 truncate text-sm text-custom-text-100">{name}</p>
|
||||
{label_details.length > 0 &&
|
||||
label_details.map((label: IIssueLabel) => (
|
||||
<div
|
||||
key={label.id}
|
||||
className="group flex items-center gap-1 rounded-2xl border border-custom-border-200 px-2 py-0.5 text-xs"
|
||||
|
|
@ -207,26 +180,26 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2.5">
|
||||
{pageDetails.archived_at ? (
|
||||
{archived_at ? (
|
||||
<Tooltip
|
||||
tooltipContent={`Archived at ${renderFormattedTime(
|
||||
pageDetails.archived_at
|
||||
)} on ${renderFormattedDate(pageDetails.archived_at)}`}
|
||||
tooltipContent={`Archived at ${renderFormattedTime(archived_at)} on ${renderFormattedDate(
|
||||
archived_at
|
||||
)}`}
|
||||
>
|
||||
<p className="text-sm text-custom-text-200">{renderFormattedTime(pageDetails.archived_at)}</p>
|
||||
<p className="text-sm text-custom-text-200">{renderFormattedTime(archived_at)}</p>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip
|
||||
tooltipContent={`Last updated at ${renderFormattedTime(
|
||||
pageDetails.updated_at
|
||||
)} on ${renderFormattedDate(pageDetails.updated_at)}`}
|
||||
tooltipContent={`Last updated at ${renderFormattedTime(updated_at)} on ${renderFormattedDate(
|
||||
updated_at
|
||||
)}`}
|
||||
>
|
||||
<p className="text-sm text-custom-text-200">{renderFormattedTime(pageDetails.updated_at)}</p>
|
||||
<p className="text-sm text-custom-text-200">{renderFormattedTime(updated_at)}</p>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isEditingAllowed && (
|
||||
<Tooltip tooltipContent={`${pageDetails.is_favorite ? "Remove from favorites" : "Mark as favorite"}`}>
|
||||
{pageDetails.is_favorite ? (
|
||||
<Tooltip tooltipContent={`${is_favorite ? "Remove from favorites" : "Mark as favorite"}`}>
|
||||
{is_favorite ? (
|
||||
<button type="button" onClick={handleRemoveFromFavorites}>
|
||||
<Star className="h-3.5 w-3.5 fill-orange-400 text-orange-400" />
|
||||
</button>
|
||||
|
|
@ -240,12 +213,10 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
{userCanChangeAccess && (
|
||||
<Tooltip
|
||||
tooltipContent={`${
|
||||
pageDetails.access
|
||||
? "This page is only visible to you"
|
||||
: "This page can be viewed by anyone in the project"
|
||||
access ? "This page is only visible to you" : "This page can be viewed by anyone in the project"
|
||||
}`}
|
||||
>
|
||||
{pageDetails.access ? (
|
||||
{access ? (
|
||||
<button type="button" onClick={handleMakePublic}>
|
||||
<Lock className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -259,13 +230,13 @@ export const PagesListItem: FC<IPagesListItem> = observer((props) => {
|
|||
<Tooltip
|
||||
position="top-right"
|
||||
tooltipContent={`Created by ${ownerDetails?.member.display_name} on ${renderFormattedDate(
|
||||
pageDetails.created_at
|
||||
created_at
|
||||
)}`}
|
||||
>
|
||||
<AlertCircle className="h-3.5 w-3.5" />
|
||||
</Tooltip>
|
||||
<CustomMenu width="auto" placement="bottom-end" className="!-m-1" verticalEllipsis>
|
||||
{pageDetails.archived_at ? (
|
||||
{archived_at ? (
|
||||
<>
|
||||
{userCanArchive && (
|
||||
<CustomMenu.MenuItem onClick={handleRestorePage}>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// components
|
||||
import { PagesListItem } from "./list-item";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
|
|
@ -13,14 +11,17 @@ import { Loader } from "@plane/ui";
|
|||
import emptyPage from "public/empty-state/empty_page.png";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { PagesListItem } from "./list-item";
|
||||
|
||||
type IPagesListView = {
|
||||
pageIds: string[];
|
||||
};
|
||||
|
||||
export const PagesListView: FC<IPagesListView> = observer((props) => {
|
||||
const { pageIds } = props;
|
||||
export const PagesListView: FC<IPagesListView> = (props) => {
|
||||
const { pageIds: projectPageIds } = props;
|
||||
// store hooks
|
||||
// trace(true);
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCreatePageModal },
|
||||
} = useApplication();
|
||||
|
|
@ -31,21 +32,18 @@ export const PagesListView: FC<IPagesListView> = observer((props) => {
|
|||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// here we are only observing the projectPageStore, so that we can re-render the component when the projectPageStore changes
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
{pageIds && workspaceSlug && projectId ? (
|
||||
{projectPageIds && workspaceSlug && projectId ? (
|
||||
<div className="h-full space-y-4 overflow-y-auto">
|
||||
{pageIds.length > 0 ? (
|
||||
{projectPageIds.length > 0 ? (
|
||||
<ul role="list" className="divide-y divide-custom-border-200">
|
||||
{pageIds.map((pageId) => (
|
||||
<PagesListItem
|
||||
key={pageId}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={projectId.toString()}
|
||||
pageId={pageId}
|
||||
/>
|
||||
{projectPageIds.map((pageId: string) => (
|
||||
<PagesListItem key={pageId} pageId={pageId} projectId={projectId.toString()} />
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
|
|
@ -77,4 +75,4 @@ export const PagesListView: FC<IPagesListView> = observer((props) => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { usePage } from "hooks/store";
|
||||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const PrivatePagesList: FC = observer(() => {
|
||||
const { privateProjectPageIds } = usePage();
|
||||
const projectPageStore = useProjectPages();
|
||||
const { privateProjectPageIds } = projectPageStore;
|
||||
|
||||
if (!privateProjectPageIds)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { FC } from "react";
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, usePage, useUser } from "hooks/store";
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
|
|
@ -14,6 +14,7 @@ import emptyPage from "public/empty-state/empty_page.png";
|
|||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
// constants
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const RecentPagesList: FC = observer(() => {
|
||||
// store hooks
|
||||
|
|
@ -21,7 +22,7 @@ export const RecentPagesList: FC = observer(() => {
|
|||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { recentProjectPages } = usePage();
|
||||
const { recentProjectPages } = useProjectPages();
|
||||
|
||||
// FIXME: replace any with proper type
|
||||
const isEmpty = recentProjectPages && Object.values(recentProjectPages).every((value: any) => value.length === 0);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { PagesListView } from "components/pages/pages-list";
|
||||
// hooks
|
||||
import { usePage } from "hooks/store";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
import { useProjectPages } from "hooks/store/use-project-specific-pages";
|
||||
|
||||
export const SharedPagesList: FC = observer(() => {
|
||||
const { publicProjectPageIds } = usePage();
|
||||
const projectPageStore = useProjectPages();
|
||||
const { publicProjectPageIds } = projectPageStore;
|
||||
|
||||
if (!publicProjectPageIds)
|
||||
return (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue