2
0
forked from github/plane
plane/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx
Aaryan Khandelwal c8c89007c0
style: revamped page details UI ()
* style: revamp page details UI

* chore: updated the info popover date format

* fix: page actions mutation

* style: made the page content responsive
2023-11-22 12:32:49 +05:30

287 lines
9.2 KiB
TypeScript

import React, { useEffect, useRef, useState, ReactElement } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Controller, useForm } from "react-hook-form";
// services
import { PageService } from "services/page.service";
import { FileService } from "services/file.service";
// hooks
import useUser from "hooks/use-user";
import { useDebouncedCallback } from "use-debounce";
// layouts
import { AppLayout } from "layouts/app-layout";
// components
import { PageDetailsHeader } from "components/headers/page-details";
import { EmptyState } from "components/common";
// ui
import { DocumentEditorWithRef, DocumentReadOnlyEditorWithRef } from "@plane/document-editor";
import { Loader } from "@plane/ui";
// assets
import emptyPage from "public/empty-state/page.svg";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
// types
import { NextPageWithLayout } from "types/app";
import { IPage } from "types";
// fetch-keys
import { PAGE_DETAILS } from "constants/fetch-keys";
// services
const fileService = new FileService();
const pageService = new PageService();
const PageDetailsPage: NextPageWithLayout = () => {
const editorRef = useRef<any>(null);
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
const router = useRouter();
const { workspaceSlug, projectId, pageId } = router.query;
const { user } = useUser();
const { handleSubmit, reset, getValues, control } = useForm<IPage>({
defaultValues: { name: "" },
});
// =================== Fetching Page Details ======================
const {
data: pageDetails,
mutate: mutatePageDetails,
error,
} = useSWR(
workspaceSlug && projectId && pageId ? PAGE_DETAILS(pageId.toString()) : null,
workspaceSlug && projectId && pageId
? () => pageService.getPageDetails(workspaceSlug.toString(), projectId.toString(), pageId.toString())
: null
);
const updatePage = async (formData: IPage) => {
if (!workspaceSlug || !projectId || !pageId) return;
if (!formData.name || formData.name.length === 0 || formData.name === "") return;
await pageService
.patchPage(workspaceSlug.toString(), projectId.toString(), pageId.toString(), formData)
.then(() => {
mutatePageDetails(
(prevData) => ({
...prevData,
...formData,
}),
false
);
});
};
const createPage = async (payload: Partial<IPage>) => {
if (!workspaceSlug || !projectId) return;
await pageService.createPage(workspaceSlug.toString(), projectId.toString(), payload);
};
// ================ Page Menu Actions ==================
const duplicate_page = async () => {
const currentPageValues = getValues();
const formData: Partial<IPage> = {
name: "Copy of " + currentPageValues.name,
description_html: currentPageValues.description_html,
};
await createPage(formData);
};
const archivePage = async () => {
if (!workspaceSlug || !projectId || !pageId) return;
try {
mutatePageDetails((prevData) => {
if (!prevData) return;
return {
...prevData,
archived_at: renderDateFormat(new Date()),
};
}, true);
await pageService.archivePage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
} catch (e) {
mutatePageDetails();
}
};
const unArchivePage = async () => {
if (!workspaceSlug || !projectId || !pageId) return;
try {
mutatePageDetails((prevData) => {
if (!prevData) return;
return {
...prevData,
archived_at: null,
};
}, false);
await pageService.restorePage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
} catch (e) {
mutatePageDetails();
}
};
// ========================= Page Lock ==========================
const lockPage = async () => {
if (!workspaceSlug || !projectId || !pageId) return;
try {
mutatePageDetails((prevData) => {
if (!prevData) return;
return {
...prevData,
is_locked: true,
};
}, false);
await pageService.lockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
} catch (e) {
mutatePageDetails();
}
};
const unlockPage = async () => {
if (!workspaceSlug || !projectId || !pageId) return;
try {
mutatePageDetails((prevData) => {
if (!prevData) return;
return {
...prevData,
is_locked: false,
};
}, false);
await pageService.unlockPage(workspaceSlug.toString(), projectId.toString(), pageId.toString());
} catch (e) {
mutatePageDetails();
}
};
useEffect(() => {
if (!pageDetails) return;
reset({
...pageDetails,
});
}, [reset, pageDetails]);
const debouncedFormSave = useDebouncedCallback(async () => {
handleSubmit(updatePage)().finally(() => setIsSubmitting("submitted"));
}, 1500);
return (
<>
{error ? (
<EmptyState
image={emptyPage}
title="Page does not exist"
description="The page you are looking for does not exist or has been deleted."
primaryButton={{
text: "View other pages",
onClick: () => router.push(`/${workspaceSlug}/projects/${projectId}/pages`),
}}
/>
) : pageDetails ? (
<div className="flex h-full flex-col justify-between">
<div className="h-full w-full overflow-hidden">
{pageDetails.is_locked || pageDetails.archived_at ? (
<DocumentReadOnlyEditorWithRef
ref={editorRef}
value={pageDetails.description_html}
customClassName={"tracking-tight self-center w-full max-w-full px-0"}
borderOnFocus={false}
noBorder={true}
documentDetails={{
title: pageDetails.name,
created_by: pageDetails.created_by,
created_on: pageDetails.created_at,
last_updated_at: pageDetails.updated_at,
last_updated_by: pageDetails.updated_by,
}}
pageLockConfig={
!pageDetails.archived_at && user && pageDetails.owned_by === user.id
? { action: unlockPage, is_locked: pageDetails.is_locked }
: undefined
}
pageArchiveConfig={
user && pageDetails.owned_by === user.id
? {
action: pageDetails.archived_at ? unArchivePage : archivePage,
is_archived: pageDetails.archived_at ? true : false,
archived_at: pageDetails.archived_at ? new Date(pageDetails.archived_at) : undefined,
}
: undefined
}
/>
) : (
<Controller
name="description_html"
control={control}
render={({ field: { value, onChange } }) => (
<DocumentEditorWithRef
documentDetails={{
title: pageDetails.name,
created_by: pageDetails.created_by,
created_on: pageDetails.created_at,
last_updated_at: pageDetails.updated_at,
last_updated_by: pageDetails.updated_by,
}}
uploadFile={fileService.getUploadFileFunction(workspaceSlug as string)}
deleteFile={fileService.deleteImage}
ref={editorRef}
debouncedUpdatesEnabled={false}
setIsSubmitting={setIsSubmitting}
value={!value || value === "" ? "<p></p>" : value}
customClassName="tracking-tight self-center px-0 h-full w-full"
onChange={(_description_json: Object, description_html: string) => {
onChange(description_html);
setIsSubmitting("submitting");
debouncedFormSave();
}}
duplicationConfig={{ action: duplicate_page }}
pageArchiveConfig={
user && pageDetails.owned_by === user.id
? {
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
}
/>
)}
/>
)}
</div>
</div>
) : (
<Loader className="p-8">
<Loader.Item height="200px" />
</Loader>
)}
</>
);
};
PageDetailsPage.getLayout = function getLayout(page: ReactElement) {
return (
<AppLayout header={<PageDetailsHeader />} withProjectWrapper>
{page}
</AppLayout>
);
};
export default PageDetailsPage;