[PE-238] refactor: page store hooks (#6409)

* refactor: page store hooks

* fix: page details instances

* fix: build errors

* refactor: page store hooks

* fix: minor bug
This commit is contained in:
Aaryan Khandelwal 2025-02-19 18:02:14 +05:30 committed by GitHub
parent dd11ebf335
commit 827f47809b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 216 additions and 103 deletions

View file

@ -19,7 +19,9 @@ import { IssuePeekOverview } from "@/components/issues";
import { PageRoot, TPageRootConfig, TPageRootHandlers } from "@/components/pages"; import { PageRoot, TPageRootConfig, TPageRootHandlers } from "@/components/pages";
// hooks // hooks
import { useEditorConfig } from "@/hooks/editor"; import { useEditorConfig } from "@/hooks/editor";
import { useEditorAsset, useProjectPage, useProjectPages, useWorkspace } from "@/hooks/store"; import { useEditorAsset, useWorkspace } from "@/hooks/store";
// plane web hooks
import { EPageStoreType, usePage, usePageStore } from "@/plane-web/hooks/store";
// plane web services // plane web services
import { WorkspaceService } from "@/plane-web/services"; import { WorkspaceService } from "@/plane-web/services";
// services // services
@ -31,13 +33,16 @@ const projectPageVersionService = new ProjectPageVersionService();
const PageDetailsPage = observer(() => { const PageDetailsPage = observer(() => {
const { workspaceSlug, projectId, pageId } = useParams(); const { workspaceSlug, projectId, pageId } = useParams();
// store hooks // store hooks
const { createPage, getPageById } = useProjectPages(); const { createPage, fetchPageDetails } = usePageStore(EPageStoreType.PROJECT);
const page = useProjectPage(pageId?.toString() ?? ""); const page = usePage({
pageId: pageId?.toString() ?? "",
storeType: EPageStoreType.PROJECT,
});
const { getWorkspaceBySlug } = useWorkspace(); const { getWorkspaceBySlug } = useWorkspace();
const { uploadEditorAsset } = useEditorAsset(); const { uploadEditorAsset } = useEditorAsset();
// derived values // derived values
const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : ""; const workspaceId = workspaceSlug ? (getWorkspaceBySlug(workspaceSlug.toString())?.id ?? "") : "";
const { canCurrentUserAccessPage, id, name, updateDescription } = page; const { canCurrentUserAccessPage, id, name, updateDescription } = page ?? {};
// entity search handler // entity search handler
const fetchEntityCallback = useCallback( const fetchEntityCallback = useCallback(
async (payload: TSearchEntityRequestPayload) => async (payload: TSearchEntityRequestPayload) =>
@ -53,7 +58,7 @@ const PageDetailsPage = observer(() => {
const { error: pageDetailsError } = useSWR( const { error: pageDetailsError } = useSWR(
workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null, workspaceSlug && projectId && pageId ? `PAGE_DETAILS_${pageId}` : null,
workspaceSlug && projectId && pageId workspaceSlug && projectId && pageId
? () => getPageById(workspaceSlug?.toString(), projectId?.toString(), pageId.toString()) ? () => fetchPageDetails(workspaceSlug?.toString(), projectId?.toString(), pageId.toString())
: null, : null,
{ {
revalidateIfStale: true, revalidateIfStale: true,
@ -70,8 +75,8 @@ const PageDetailsPage = observer(() => {
return await projectPageVersionService.fetchAllVersions(workspaceSlug.toString(), projectId.toString(), pageId); return await projectPageVersionService.fetchAllVersions(workspaceSlug.toString(), projectId.toString(), pageId);
}, },
fetchDescriptionBinary: async () => { fetchDescriptionBinary: async () => {
if (!workspaceSlug || !projectId || !page.id) return; if (!workspaceSlug || !projectId || !id) return;
return await projectPageService.fetchDescriptionBinary(workspaceSlug.toString(), projectId.toString(), page.id); return await projectPageService.fetchDescriptionBinary(workspaceSlug.toString(), projectId.toString(), id);
}, },
fetchEntity: fetchEntityCallback, fetchEntity: fetchEntityCallback,
fetchVersionDetails: async (pageId, versionId) => { fetchVersionDetails: async (pageId, versionId) => {
@ -84,9 +89,9 @@ const PageDetailsPage = observer(() => {
); );
}, },
getRedirectionLink: (pageId) => `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`, getRedirectionLink: (pageId) => `/${workspaceSlug}/projects/${projectId}/pages/${pageId}`,
updateDescription, updateDescription: updateDescription ?? (async () => {}),
}), }),
[createPage, fetchEntityCallback, page.id, projectId, updateDescription, workspaceSlug] [createPage, fetchEntityCallback, id, projectId, updateDescription, workspaceSlug]
); );
// page root config // page root config
const pageRootConfig: TPageRootConfig = useMemo( const pageRootConfig: TPageRootConfig = useMemo(
@ -145,6 +150,8 @@ const PageDetailsPage = observer(() => {
</div> </div>
); );
if (!page) return null;
return ( return (
<> <>
<PageHead title={name} /> <PageHead title={name} />
@ -154,6 +161,7 @@ const PageDetailsPage = observer(() => {
config={pageRootConfig} config={pageRootConfig}
handlers={pageRootHandlers} handlers={pageRootHandlers}
page={page} page={page}
storeType={EPageStoreType.PROJECT}
webhookConnectionParams={webhookConnectionParams} webhookConnectionParams={webhookConnectionParams}
workspaceSlug={workspaceSlug?.toString() ?? ""} workspaceSlug={workspaceSlug?.toString() ?? ""}
/> />

View file

@ -15,11 +15,13 @@ import { PageEditInformationPopover } from "@/components/pages";
import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper"; import { convertHexEmojiToDecimal } from "@/helpers/emoji.helper";
import { getPageName } from "@/helpers/page.helper"; import { getPageName } from "@/helpers/page.helper";
// hooks // hooks
import { useProjectPage, useProject } from "@/hooks/store"; import { useProject } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components // plane web components
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
import { PageDetailsHeaderExtraActions } from "@/plane-web/components/pages"; import { PageDetailsHeaderExtraActions } from "@/plane-web/components/pages";
// plane web hooks
import { EPageStoreType, usePage } from "@/plane-web/hooks/store";
export interface IPagesHeaderProps { export interface IPagesHeaderProps {
showButton?: boolean; showButton?: boolean;
@ -32,7 +34,12 @@ export const PageDetailsHeader = observer(() => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
// store hooks // store hooks
const { currentProjectDetails, loader } = useProject(); const { currentProjectDetails, loader } = useProject();
const page = useProjectPage(pageId?.toString() ?? ""); const page = usePage({
pageId: pageId?.toString() ?? "",
storeType: EPageStoreType.PROJECT,
});
if (!page) return null;
// derived values
const { name, logo_props, updatePageLogo, isContentEditable } = page; const { name, logo_props, updatePageLogo, isContentEditable } = page;
// use platform // use platform
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();

View file

@ -13,9 +13,11 @@ import { Breadcrumbs, Button, Header, setToast, TOAST_TYPE } from "@plane/ui";
// helpers // helpers
import { BreadcrumbLink } from "@/components/common"; import { BreadcrumbLink } from "@/components/common";
// hooks // hooks
import { useEventTracker, useProject, useProjectPages } from "@/hooks/store"; import { useEventTracker, useProject } from "@/hooks/store";
// plane web // plane web
import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs"; import { ProjectBreadcrumb } from "@/plane-web/components/breadcrumbs";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
export const PagesListHeader = observer(() => { export const PagesListHeader = observer(() => {
// states // states
@ -27,7 +29,7 @@ export const PagesListHeader = observer(() => {
const pageType = searchParams.get("type"); const pageType = searchParams.get("type");
// store hooks // store hooks
const { currentProjectDetails, loader } = useProject(); const { currentProjectDetails, loader } = useProject();
const { canCurrentUserCreatePage, createPage } = useProjectPages(); const { canCurrentUserCreatePage, createPage } = usePageStore(EPageStoreType.PROJECT);
const { setTrackElement } = useEventTracker(); const { setTrackElement } = useEventTracker();
// handle page create // handle page create
const handleCreatePage = async () => { const handleCreatePage = async () => {

View file

@ -14,6 +14,8 @@ import { PagesListRoot, PagesListView } from "@/components/pages";
import { useProject, useUserPermissions } from "@/hooks/store"; import { useProject, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
const ProjectPagesPage = observer(() => { const ProjectPagesPage = observer(() => {
// router // router
@ -63,11 +65,12 @@ const ProjectPagesPage = observer(() => {
<> <>
<PageHead title={pageTitle} /> <PageHead title={pageTitle} />
<PagesListView <PagesListView
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
pageType={currentPageType()} pageType={currentPageType()}
projectId={projectId.toString()}
storeType={EPageStoreType.PROJECT}
workspaceSlug={workspaceSlug.toString()}
> >
<PagesListRoot pageType={currentPageType()} /> <PagesListRoot pageType={currentPageType()} storeType={EPageStoreType.PROJECT} />
</PagesListView> </PagesListView>
</> </>
); );

View file

@ -6,6 +6,8 @@ import { CreatePageModal } from "@/components/pages";
import { CreateUpdateProjectViewModal } from "@/components/views"; import { CreateUpdateProjectViewModal } from "@/components/views";
// hooks // hooks
import { useCommandPalette } from "@/hooks/store"; import { useCommandPalette } from "@/hooks/store";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
export type TProjectLevelModalsProps = { export type TProjectLevelModalsProps = {
workspaceSlug: string; workspaceSlug: string;
@ -53,6 +55,7 @@ export const ProjectLevelModals = observer((props: TProjectLevelModalsProps) =>
pageAccess={createPageModal.pageAccess} pageAccess={createPageModal.pageAccess}
handleModalClose={() => toggleCreatePageModal({ isOpen: false })} handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
redirectionEnabled redirectionEnabled
storeType={EPageStoreType.PROJECT}
/> />
</> </>
); );

View file

@ -0,0 +1,2 @@
export * from "./use-page-store";
export * from "./use-page";

View file

@ -0,0 +1,24 @@
import { useContext } from "react";
// context
import { StoreContext } from "@/lib/store-context";
// mobx store
import { IProjectPageStore } from "@/store/pages/project-page.store";
export enum EPageStoreType {
PROJECT = "PROJECT_PAGE",
}
export type TReturnType = {
[EPageStoreType.PROJECT]: IProjectPageStore;
};
export const usePageStore = <T extends EPageStoreType>(storeType: T): TReturnType[T] => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("usePageStore must be used within StoreProvider");
if (storeType === EPageStoreType.PROJECT) {
return context.projectPages;
}
throw new Error(`Invalid store type: ${storeType}`);
};

View file

@ -0,0 +1,23 @@
import { useContext } from "react";
// mobx store
import { StoreContext } from "@/lib/store-context";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
export type TArgs = {
pageId: string;
storeType: EPageStoreType;
};
export const usePage = (args: TArgs) => {
const { pageId, storeType } = args;
// context
const context = useContext(StoreContext);
// store hooks
const pageStore = usePageStore(storeType);
if (context === undefined) throw new Error("usePage must be used within StoreProvider");
if (!pageId) throw new Error("pageId is required");
return pageStore.getPageById(pageId);
};

View file

@ -30,6 +30,7 @@ import { usePageOperations } from "@/hooks/use-page-operations";
// plane web components // plane web components
import { MovePageModal } from "@/plane-web/components/pages"; import { MovePageModal } from "@/plane-web/components/pages";
// plane web hooks // plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
import { usePageFlag } from "@/plane-web/hooks/use-page-flag"; import { usePageFlag } from "@/plane-web/hooks/use-page-flag";
// store types // store types
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
@ -55,10 +56,11 @@ type Props = {
optionsOrder: TPageActions[]; optionsOrder: TPageActions[];
page: TPageInstance; page: TPageInstance;
parentRef?: React.RefObject<HTMLElement>; parentRef?: React.RefObject<HTMLElement>;
storeType: EPageStoreType;
}; };
export const PageActions: React.FC<Props> = observer((props) => { export const PageActions: React.FC<Props> = observer((props) => {
const { editorRef, extraOptions, optionsOrder, page, parentRef } = props; const { editorRef, extraOptions, optionsOrder, page, parentRef, storeType } = props;
// states // states
const [deletePageModal, setDeletePageModal] = useState(false); const [deletePageModal, setDeletePageModal] = useState(false);
const [movePageModal, setMovePageModal] = useState(false); const [movePageModal, setMovePageModal] = useState(false);
@ -175,7 +177,12 @@ export const PageActions: React.FC<Props> = observer((props) => {
return ( return (
<> <>
<MovePageModal isOpen={movePageModal} onClose={() => setMovePageModal(false)} page={page} /> <MovePageModal isOpen={movePageModal} onClose={() => setMovePageModal(false)} page={page} />
<DeletePageModal isOpen={deletePageModal} onClose={() => setDeletePageModal(false)} page={page} /> <DeletePageModal
isOpen={deletePageModal}
onClose={() => setDeletePageModal(false)}
page={page}
storeType={storeType}
/>
{parentRef && <ContextMenu parentRef={parentRef} items={arrangedOptions} />} {parentRef && <ContextMenu parentRef={parentRef} items={arrangedOptions} />}
<CustomMenu placement="bottom-end" optionsClassName="max-h-[90vh]" ellipsis closeOnSelect> <CustomMenu placement="bottom-end" optionsClassName="max-h-[90vh]" ellipsis closeOnSelect>
{arrangedOptions.map((item) => { {arrangedOptions.map((item) => {

View file

@ -12,16 +12,19 @@ import { PageInfoPopover, PageOptionsDropdown } from "@/components/pages";
import { renderFormattedDate } from "@/helpers/date-time.helper"; import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks // hooks
import useOnlineStatus from "@/hooks/use-online-status"; import useOnlineStatus from "@/hooks/use-online-status";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store // store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
type Props = { type Props = {
editorRef: EditorRefApi; editorRef: EditorRefApi;
page: TPageInstance; page: TPageInstance;
storeType: EPageStoreType;
}; };
export const PageExtraOptions: React.FC<Props> = observer((props) => { export const PageExtraOptions: React.FC<Props> = observer((props) => {
const { editorRef, page } = props; const { editorRef, page, storeType } = props;
// derived values // derived values
const { const {
archived_at, archived_at,
@ -84,7 +87,7 @@ export const PageExtraOptions: React.FC<Props> = observer((props) => {
/> />
)} )}
<PageInfoPopover editorRef={editorRef} page={page} /> <PageInfoPopover editorRef={editorRef} page={page} />
<PageOptionsDropdown editorRef={editorRef} page={page} /> <PageOptionsDropdown editorRef={editorRef} page={page} storeType={storeType} />
</div> </div>
); );
}); });

View file

@ -5,6 +5,8 @@ import { Header, EHeaderVariant } from "@plane/ui";
import { PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages"; import { PageExtraOptions, PageSummaryPopover, PageToolbar } from "@/components/pages";
// hooks // hooks
import { usePageFilters } from "@/hooks/use-page-filters"; import { usePageFilters } from "@/hooks/use-page-filters";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store // store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
@ -13,10 +15,11 @@ type Props = {
page: TPageInstance; page: TPageInstance;
setSidePeekVisible: (sidePeekState: boolean) => void; setSidePeekVisible: (sidePeekState: boolean) => void;
sidePeekVisible: boolean; sidePeekVisible: boolean;
storeType: EPageStoreType;
}; };
export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => { export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
const { editorRef, page, setSidePeekVisible, sidePeekVisible } = props; const { editorRef, page, setSidePeekVisible, sidePeekVisible, storeType } = props;
// derived values // derived values
const { isContentEditable } = page; const { isContentEditable } = page;
// page filters // page filters
@ -33,7 +36,7 @@ export const PageEditorMobileHeaderRoot: React.FC<Props> = observer((props) => {
setSidePeekVisible={setSidePeekVisible} setSidePeekVisible={setSidePeekVisible}
/> />
</div> </div>
<PageExtraOptions editorRef={editorRef} page={page} /> <PageExtraOptions editorRef={editorRef} page={page} storeType={storeType} />
</Header> </Header>
<Header variant={EHeaderVariant.TERNARY}> <Header variant={EHeaderVariant.TERNARY}>
{isContentEditable && editorRef && <PageToolbar editorRef={editorRef} />} {isContentEditable && editorRef && <PageToolbar editorRef={editorRef} />}

View file

@ -15,16 +15,19 @@ import { copyTextToClipboard } from "@/helpers/string.helper";
// hooks // hooks
import { usePageFilters } from "@/hooks/use-page-filters"; import { usePageFilters } from "@/hooks/use-page-filters";
import { useQueryParams } from "@/hooks/use-query-params"; import { useQueryParams } from "@/hooks/use-query-params";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store // store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
type Props = { type Props = {
editorRef: EditorRefApi | null; editorRef: EditorRefApi | null;
page: TPageInstance; page: TPageInstance;
storeType: EPageStoreType;
}; };
export const PageOptionsDropdown: React.FC<Props> = observer((props) => { export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
const { editorRef, page } = props; const { editorRef, page, storeType } = props;
// states // states
const [isExportModalOpen, setIsExportModalOpen] = useState(false); const [isExportModalOpen, setIsExportModalOpen] = useState(false);
// router // router
@ -136,6 +139,7 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
"export", "export",
]} ]}
page={page} page={page}
storeType={storeType}
/> />
</> </>
); );

View file

@ -7,6 +7,8 @@ import { PageEditorMobileHeaderRoot, PageExtraOptions, PageSummaryPopover, PageT
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
// hooks // hooks
import { usePageFilters } from "@/hooks/use-page-filters"; import { usePageFilters } from "@/hooks/use-page-filters";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store // store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
@ -16,10 +18,11 @@ type Props = {
page: TPageInstance; page: TPageInstance;
setSidePeekVisible: (sidePeekState: boolean) => void; setSidePeekVisible: (sidePeekState: boolean) => void;
sidePeekVisible: boolean; sidePeekVisible: boolean;
storeType: EPageStoreType;
}; };
export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => { export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
const { editorReady, editorRef, page, setSidePeekVisible, sidePeekVisible } = props; const { editorReady, editorRef, page, setSidePeekVisible, sidePeekVisible, storeType } = props;
// derived values // derived values
const { isContentEditable } = page; const { isContentEditable } = page;
// page filters // page filters
@ -52,7 +55,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
<PageToolbar editorRef={editorRef?.current} /> <PageToolbar editorRef={editorRef?.current} />
)} )}
</Header.LeftItem> </Header.LeftItem>
<PageExtraOptions editorRef={resolvedEditorRef} page={page} /> <PageExtraOptions editorRef={resolvedEditorRef} page={page} storeType={storeType} />
</Header> </Header>
<div className="md:hidden"> <div className="md:hidden">
<PageEditorMobileHeaderRoot <PageEditorMobileHeaderRoot
@ -60,6 +63,7 @@ export const PageEditorHeaderRoot: React.FC<Props> = observer((props) => {
page={page} page={page}
sidePeekVisible={sidePeekVisible} sidePeekVisible={sidePeekVisible}
setSidePeekVisible={setSidePeekVisible} setSidePeekVisible={setSidePeekVisible}
storeType={storeType}
/> />
</div> </div>
</> </>

View file

@ -18,6 +18,8 @@ import {
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { usePageFallback } from "@/hooks/use-page-fallback"; import { usePageFallback } from "@/hooks/use-page-fallback";
import { useQueryParams } from "@/hooks/use-query-params"; import { useQueryParams } from "@/hooks/use-query-params";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store // store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
@ -36,12 +38,13 @@ type TPageRootProps = {
config: TPageRootConfig; config: TPageRootConfig;
handlers: TPageRootHandlers; handlers: TPageRootHandlers;
page: TPageInstance; page: TPageInstance;
storeType: EPageStoreType;
webhookConnectionParams: TWebhookConnectionQueryParams; webhookConnectionParams: TWebhookConnectionQueryParams;
workspaceSlug: string; workspaceSlug: string;
}; };
export const PageRoot = observer((props: TPageRootProps) => { export const PageRoot = observer((props: TPageRootProps) => {
const { config, handlers, page, webhookConnectionParams, workspaceSlug } = props; const { config, handlers, page, storeType, webhookConnectionParams, workspaceSlug } = props;
// states // states
const [editorReady, setEditorReady] = useState(false); const [editorReady, setEditorReady] = useState(false);
const [hasConnectionFailed, setHasConnectionFailed] = useState(false); const [hasConnectionFailed, setHasConnectionFailed] = useState(false);
@ -107,6 +110,7 @@ export const PageRoot = observer((props: TPageRootProps) => {
page={page} page={page}
setSidePeekVisible={(state) => setSidePeekVisible(state)} setSidePeekVisible={(state) => setSidePeekVisible(state)}
sidePeekVisible={sidePeekVisible} sidePeekVisible={sidePeekVisible}
storeType={storeType}
/> />
<PageEditorBody <PageEditorBody
config={config} config={config}

View file

@ -16,19 +16,22 @@ import {
// helpers // helpers
import { calculateTotalFilters } from "@/helpers/filter.helper"; import { calculateTotalFilters } from "@/helpers/filter.helper";
// hooks // hooks
import { useMember, useProjectPages } from "@/hooks/store"; import { useMember } from "@/hooks/store";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
type Props = { type Props = {
pageType: TPageNavigationTabs; pageType: TPageNavigationTabs;
projectId: string; projectId: string;
storeType: EPageStoreType;
workspaceSlug: string; workspaceSlug: string;
}; };
export const PagesListHeaderRoot: React.FC<Props> = observer((props) => { export const PagesListHeaderRoot: React.FC<Props> = observer((props) => {
const { pageType, projectId, workspaceSlug } = props; const { pageType, projectId, storeType, workspaceSlug } = props;
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { filters, updateFilters, clearAllFilters } = useProjectPages(); const { filters, updateFilters, clearAllFilters } = usePageStore(storeType);
const { const {
workspace: { workspaceMemberIds }, workspace: { workspaceMemberIds },
} = useMember(); } = useMember();

View file

@ -13,15 +13,19 @@ import { getFileURL } from "@/helpers/file.helper";
// hooks // hooks
import { useMember } from "@/hooks/store"; import { useMember } from "@/hooks/store";
import { usePageOperations } from "@/hooks/use-page-operations"; import { usePageOperations } from "@/hooks/use-page-operations";
// plane web hooks
import { EPageStoreType } from "@/plane-web/hooks/store";
// store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
type Props = { type Props = {
page: TPageInstance; page: TPageInstance;
parentRef: React.RefObject<HTMLElement>; parentRef: React.RefObject<HTMLElement>;
storeType: EPageStoreType;
}; };
export const BlockItemAction: FC<Props> = observer((props) => { export const BlockItemAction: FC<Props> = observer((props) => {
const { page, parentRef } = props; const { page, parentRef, storeType } = props;
// store hooks // store hooks
const { getUserDetails } = useMember(); const { getUserDetails } = useMember();
// page operations // page operations
@ -80,6 +84,7 @@ export const BlockItemAction: FC<Props> = observer((props) => {
]} ]}
page={page} page={page}
parentRef={parentRef} parentRef={parentRef}
storeType={storeType}
/> />
</> </>
); );

View file

@ -11,20 +11,26 @@ import { BlockItemAction } from "@/components/pages/list";
import { getPageName } from "@/helpers/page.helper"; import { getPageName } from "@/helpers/page.helper";
// hooks // hooks
import { usePlatformOS } from "@/hooks/use-platform-os"; import { usePlatformOS } from "@/hooks/use-platform-os";
import { TUsePage } from "@/store/pages/base-page"; // plane web hooks
import { EPageStoreType, usePage } from "@/plane-web/hooks/store";
type TPageListBlock = { type TPageListBlock = {
pageId: string; pageId: string;
usePage: TUsePage; storeType: EPageStoreType;
}; };
export const PageListBlock: FC<TPageListBlock> = observer((props) => { export const PageListBlock: FC<TPageListBlock> = observer((props) => {
const { pageId, usePage } = props; const { pageId, storeType } = props;
// refs // refs
const parentRef = useRef(null); const parentRef = useRef(null);
// hooks // hooks
const page = usePage(pageId); const page = usePage({
pageId,
storeType,
});
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
// handle page check
if (!page) return null;
// derived values // derived values
const { name, logo_props, getRedirectionLink } = page; const { name, logo_props, getRedirectionLink } = page;
@ -41,7 +47,7 @@ export const PageListBlock: FC<TPageListBlock> = observer((props) => {
} }
title={getPageName(name)} title={getPageName(name)}
itemLink={getRedirectionLink()} itemLink={getRedirectionLink()}
actionableItems={<BlockItemAction page={page} parentRef={parentRef} />} actionableItems={<BlockItemAction page={page} parentRef={parentRef} storeType={storeType} />}
isMobile={isMobile} isMobile={isMobile}
parentRef={parentRef} parentRef={parentRef}
/> />

View file

@ -4,19 +4,20 @@ import { observer } from "mobx-react";
import { TPageNavigationTabs } from "@plane/types"; import { TPageNavigationTabs } from "@plane/types";
// components // components
import { ListLayout } from "@/components/core/list"; import { ListLayout } from "@/components/core/list";
// hooks // plane web hooks
import { useProjectPage, useProjectPages } from "@/hooks/store"; import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
// components // components
import { PageListBlock } from "./"; import { PageListBlock } from "./";
type TPagesListRoot = { type TPagesListRoot = {
pageType: TPageNavigationTabs; pageType: TPageNavigationTabs;
storeType: EPageStoreType;
}; };
export const PagesListRoot: FC<TPagesListRoot> = observer((props) => { export const PagesListRoot: FC<TPagesListRoot> = observer((props) => {
const { pageType } = props; const { pageType, storeType } = props;
// store hooks // store hooks
const { getCurrentProjectFilteredPageIds } = useProjectPages(); const { getCurrentProjectFilteredPageIds } = usePageStore(storeType);
// derived values // derived values
const filteredPageIds = getCurrentProjectFilteredPageIds(pageType); const filteredPageIds = getCurrentProjectFilteredPageIds(pageType);
@ -24,7 +25,7 @@ export const PagesListRoot: FC<TPagesListRoot> = observer((props) => {
return ( return (
<ListLayout> <ListLayout>
{filteredPageIds.map((pageId) => ( {filteredPageIds.map((pageId) => (
<PageListBlock key={pageId} pageId={pageId} usePage={useProjectPage} /> <PageListBlock key={pageId} pageId={pageId} storeType={storeType} />
))} ))}
</ListLayout> </ListLayout>
); );

View file

@ -7,8 +7,10 @@ import { EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
// components // components
import { PageForm } from "@/components/pages"; import { PageForm } from "@/components/pages";
// hooks // hooks
import { useProjectPages, useEventTracker } from "@/hooks/store"; import { useEventTracker } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
type Props = { type Props = {
workspaceSlug: string; workspaceSlug: string;
@ -17,10 +19,19 @@ type Props = {
pageAccess?: EPageAccess; pageAccess?: EPageAccess;
handleModalClose: () => void; handleModalClose: () => void;
redirectionEnabled?: boolean; redirectionEnabled?: boolean;
storeType: EPageStoreType;
}; };
export const CreatePageModal: FC<Props> = (props) => { export const CreatePageModal: FC<Props> = (props) => {
const { workspaceSlug, projectId, isModalOpen, pageAccess, handleModalClose, redirectionEnabled = false } = props; const {
workspaceSlug,
projectId,
isModalOpen,
pageAccess,
handleModalClose,
redirectionEnabled = false,
storeType,
} = props;
// states // states
const [pageFormData, setPageFormData] = useState<Partial<TPage>>({ const [pageFormData, setPageFormData] = useState<Partial<TPage>>({
id: undefined, id: undefined,
@ -30,7 +41,7 @@ export const CreatePageModal: FC<Props> = (props) => {
// router // router
const router = useAppRouter(); const router = useAppRouter();
// store hooks // store hooks
const { createPage } = useProjectPages(); const { createPage } = usePageStore(storeType);
const { capturePageEvent } = useEventTracker(); const { capturePageEvent } = useEventTracker();
const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) => const handlePageFormData = <T extends keyof TPage>(key: T, value: TPage[T]) =>
setPageFormData((prev) => ({ ...prev, [key]: value })); setPageFormData((prev) => ({ ...prev, [key]: value }));

View file

@ -7,21 +7,25 @@ import { PAGE_DELETED } from "@plane/constants";
import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui"; import { AlertModalCore, TOAST_TYPE, setToast } from "@plane/ui";
// constants // constants
// hooks // hooks
import { useEventTracker, useProjectPages } from "@/hooks/store"; import { useEventTracker } from "@/hooks/store";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
// store
import { TPageInstance } from "@/store/pages/base-page"; import { TPageInstance } from "@/store/pages/base-page";
type TConfirmPageDeletionProps = { type TConfirmPageDeletionProps = {
page: TPageInstance;
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
page: TPageInstance;
storeType: EPageStoreType;
}; };
export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((props) => { export const DeletePageModal: React.FC<TConfirmPageDeletionProps> = observer((props) => {
const { page, isOpen, onClose } = props; const { isOpen, onClose, page, storeType } = props;
// states // states
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
// store hooks // store hooks
const { removePage } = useProjectPages(); const { removePage } = usePageStore(storeType);
const { capturePageEvent } = useEventTracker(); const { capturePageEvent } = useEventTracker();
if (!page || !page.id) return null; if (!page || !page.id) return null;
// derived values // derived values

View file

@ -7,8 +7,10 @@ import { TPageNavigationTabs } from "@plane/types";
// components // components
import { DetailedEmptyState } from "@/components/empty-state"; import { DetailedEmptyState } from "@/components/empty-state";
import { PageLoader } from "@/components/pages"; import { PageLoader } from "@/components/pages";
import { useCommandPalette, useProjectPages, useUserPermissions } from "@/hooks/store"; import { useCommandPalette, useUserPermissions } from "@/hooks/store";
import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path"; import { useResolvedAssetPath } from "@/hooks/use-resolved-asset-path";
// plane web hooks
import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
// assets // assets
import AllFiltersImage from "@/public/empty-state/pages/all-filters.svg"; import AllFiltersImage from "@/public/empty-state/pages/all-filters.svg";
import NameFilterImage from "@/public/empty-state/pages/name-filter.svg"; import NameFilterImage from "@/public/empty-state/pages/name-filter.svg";
@ -16,15 +18,16 @@ import NameFilterImage from "@/public/empty-state/pages/name-filter.svg";
type Props = { type Props = {
children: React.ReactNode; children: React.ReactNode;
pageType: TPageNavigationTabs; pageType: TPageNavigationTabs;
storeType: EPageStoreType;
}; };
export const PagesListMainContent: React.FC<Props> = observer((props) => { export const PagesListMainContent: React.FC<Props> = observer((props) => {
const { children, pageType } = props; const { children, pageType, storeType } = props;
// plane hooks // plane hooks
const { t } = useTranslation(); const { t } = useTranslation();
// store hooks // store hooks
const { loader, isAnyPageAvailable, getCurrentProjectFilteredPageIds, getCurrentProjectPageIds, filters } = const { loader, isAnyPageAvailable, getCurrentProjectFilteredPageIds, getCurrentProjectPageIds, filters } =
useProjectPages(); usePageStore(storeType);
const { toggleCreatePageModal } = useCommandPalette(); const { toggleCreatePageModal } = useCommandPalette();
const { allowPermissions } = useUserPermissions(); const { allowPermissions } = useUserPermissions();
// derived values // derived values

View file

@ -3,24 +3,25 @@ import useSWR from "swr";
import { TPageNavigationTabs } from "@plane/types"; import { TPageNavigationTabs } from "@plane/types";
// components // components
import { PagesListHeaderRoot, PagesListMainContent } from "@/components/pages"; import { PagesListHeaderRoot, PagesListMainContent } from "@/components/pages";
// hooks // plane web hooks
import { useProjectPages } from "@/hooks/store"; import { EPageStoreType, usePageStore } from "@/plane-web/hooks/store";
type TPageView = { type TPageView = {
workspaceSlug: string;
projectId: string;
pageType: TPageNavigationTabs;
children: React.ReactNode; children: React.ReactNode;
pageType: TPageNavigationTabs;
projectId: string;
storeType: EPageStoreType;
workspaceSlug: string;
}; };
export const PagesListView: React.FC<TPageView> = observer((props) => { export const PagesListView: React.FC<TPageView> = observer((props) => {
const { workspaceSlug, projectId, pageType, children } = props; const { children, pageType, projectId, storeType, workspaceSlug } = props;
// store hooks // store hooks
const { isAnyPageAvailable, getAllPages } = useProjectPages(); const { isAnyPageAvailable, fetchPagesList } = usePageStore(storeType);
// fetching pages list // fetching pages list
useSWR( useSWR(
workspaceSlug && projectId && pageType ? `PROJECT_PAGES_${projectId}` : null, workspaceSlug && projectId && pageType ? `PROJECT_PAGES_${projectId}` : null,
workspaceSlug && projectId && pageType ? () => getAllPages(workspaceSlug, projectId, pageType) : null workspaceSlug && projectId && pageType ? () => fetchPagesList(workspaceSlug, projectId, pageType) : null
); );
// pages loader // pages loader
@ -28,9 +29,16 @@ export const PagesListView: React.FC<TPageView> = observer((props) => {
<div className="relative w-full h-full overflow-hidden flex flex-col"> <div className="relative w-full h-full overflow-hidden flex flex-col">
{/* tab header */} {/* tab header */}
{isAnyPageAvailable && ( {isAnyPageAvailable && (
<PagesListHeaderRoot pageType={pageType} projectId={projectId} workspaceSlug={workspaceSlug} /> <PagesListHeaderRoot
pageType={pageType}
projectId={projectId}
storeType={storeType}
workspaceSlug={workspaceSlug}
/>
)} )}
<PagesListMainContent pageType={pageType}>{children}</PagesListMainContent> <PagesListMainContent pageType={pageType} storeType={storeType}>
{children}
</PagesListMainContent>
</div> </div>
); );
}); });

View file

@ -1,6 +1,5 @@
export * from "./estimates"; export * from "./estimates";
export * from "./notifications"; export * from "./notifications";
export * from "./pages";
export * from "./use-app-theme"; export * from "./use-app-theme";
export * from "./use-calendar-view"; export * from "./use-calendar-view";
export * from "./use-command-palette"; export * from "./use-command-palette";

View file

@ -1,2 +0,0 @@
export * from "./use-page";
export * from "./use-project-page";

View file

@ -1,15 +0,0 @@
import { useContext } from "react";
// mobx store
import { StoreContext } from "@/lib/store-context";
import { TUsePage } from "@/store/pages/base-page";
// store
import { TProjectPage } from "@/store/pages/project-page";
export const useProjectPage: TUsePage = (pageId: string | undefined): TProjectPage => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("usePage must be used within StoreProvider");
if (!pageId) return {} as TProjectPage;
return context.projectPages.data?.[pageId] ?? {};
};

View file

@ -1,11 +0,0 @@
import { useContext } from "react";
// context
import { StoreContext } from "@/lib/store-context";
// mobx store
import { IProjectPageStore } from "@/store/pages/project-page.store";
export const useProjectPages = (): IProjectPageStore => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useProjectPage must be used within StoreProvider");
return context.projectPages;
};

View file

@ -8,7 +8,9 @@ import {
// helpers // helpers
import { getPageName } from "@/helpers/page.helper"; import { getPageName } from "@/helpers/page.helper";
// hooks // hooks
import { useProject, useProjectPage, useProjectView, useCycle, useModule } from "@/hooks/store"; import { useProject, useProjectView, useCycle, useModule } from "@/hooks/store";
// plane web hooks
import { EPageStoreType, usePage } from "@/plane-web/hooks/store";
export const useFavoriteItemDetails = (workspaceSlug: string, favorite: IFavorite) => { export const useFavoriteItemDetails = (workspaceSlug: string, favorite: IFavorite) => {
const favoriteItemId = favorite?.entity_data?.id; const favoriteItemId = favorite?.entity_data?.id;
@ -23,7 +25,10 @@ export const useFavoriteItemDetails = (workspaceSlug: string, favorite: IFavorit
const { getModuleById } = useModule(); const { getModuleById } = useModule();
// derived values // derived values
const pageDetail = useProjectPage(favoriteItemId ?? ""); const pageDetail = usePage({
pageId: favoriteItemId ?? "",
storeType: EPageStoreType.PROJECT,
});
const viewDetails = getViewById(favoriteItemId ?? ""); const viewDetails = getViewById(favoriteItemId ?? "");
const cycleDetail = getCycleById(favoriteItemId ?? ""); const cycleDetail = getCycleById(favoriteItemId ?? "");
const moduleDetail = getModuleById(favoriteItemId ?? ""); const moduleDetail = getModuleById(favoriteItemId ?? "");
@ -40,7 +45,7 @@ export const useFavoriteItemDetails = (workspaceSlug: string, favorite: IFavorit
itemIcon = getFavoriteItemIcon("project", currentProjectDetails?.logo_props || favoriteItemLogoProps); itemIcon = getFavoriteItemIcon("project", currentProjectDetails?.logo_props || favoriteItemLogoProps);
break; break;
case "page": case "page":
itemTitle = getPageName(pageDetail.name || favoriteItemName); itemTitle = getPageName(pageDetail?.name || favoriteItemName);
itemIcon = getFavoriteItemIcon("page", pageDetail?.logo_props || favoriteItemLogoProps); itemIcon = getFavoriteItemIcon("page", pageDetail?.logo_props || favoriteItemLogoProps);
break; break;
case "view": case "view":

View file

@ -64,8 +64,6 @@ export type TPageInstance = TBasePage &
getRedirectionLink: () => string; getRedirectionLink: () => string;
}; };
export type TUsePage = (pageId: string | undefined) => TPageInstance;
export class BasePage implements TBasePage { export class BasePage implements TBasePage {
// loaders // loaders
isSubmitting: TNameDescriptionLoader = "saved"; isSubmitting: TNameDescriptionLoader = "saved";

View file

@ -39,16 +39,16 @@ export interface IProjectPageStore {
// helper actions // helper actions
getCurrentProjectPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; getCurrentProjectPageIds: (pageType: TPageNavigationTabs) => string[] | undefined;
getCurrentProjectFilteredPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; getCurrentProjectFilteredPageIds: (pageType: TPageNavigationTabs) => string[] | undefined;
pageById: (pageId: string) => TProjectPage | undefined; getPageById: (pageId: string) => TProjectPage | undefined;
updateFilters: <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => void; updateFilters: <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => void;
clearAllFilters: () => void; clearAllFilters: () => void;
// actions // actions
getAllPages: ( fetchPagesList: (
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string,
pageType: TPageNavigationTabs pageType: TPageNavigationTabs
) => Promise<TPage[] | undefined>; ) => Promise<TPage[] | undefined>;
getPageById: (workspaceSlug: string, projectId: string, pageId: string) => Promise<TPage | undefined>; fetchPageDetails: (workspaceSlug: string, projectId: string, pageId: string) => Promise<TPage | undefined>;
createPage: (pageData: Partial<TPage>) => Promise<TPage | undefined>; createPage: (pageData: Partial<TPage>) => Promise<TPage | undefined>;
removePage: (pageId: string) => Promise<void>; removePage: (pageId: string) => Promise<void>;
movePage: (workspaceSlug: string, projectId: string, pageId: string, newProjectId: string) => Promise<void>; movePage: (workspaceSlug: string, projectId: string, pageId: string, newProjectId: string) => Promise<void>;
@ -82,8 +82,8 @@ export class ProjectPageStore implements IProjectPageStore {
updateFilters: action, updateFilters: action,
clearAllFilters: action, clearAllFilters: action,
// actions // actions
getAllPages: action, fetchPagesList: action,
getPageById: action, fetchPageDetails: action,
createPage: action, createPage: action,
removePage: action, removePage: action,
movePage: action, movePage: action,
@ -164,7 +164,7 @@ export class ProjectPageStore implements IProjectPageStore {
* @description get the page store by id * @description get the page store by id
* @param {string} pageId * @param {string} pageId
*/ */
pageById = computedFn((pageId: string) => this.data?.[pageId] || undefined); getPageById = computedFn((pageId: string) => this.data?.[pageId] || undefined);
updateFilters = <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => { updateFilters = <T extends keyof TPageFilters>(filterKey: T, filterValue: TPageFilters[T]) => {
runInAction(() => { runInAction(() => {
@ -183,7 +183,7 @@ export class ProjectPageStore implements IProjectPageStore {
/** /**
* @description fetch all the pages * @description fetch all the pages
*/ */
getAllPages = async (workspaceSlug: string, projectId: string, pageType: TPageNavigationTabs) => { fetchPagesList = async (workspaceSlug: string, projectId: string, pageType: TPageNavigationTabs) => {
try { try {
if (!workspaceSlug || !projectId) return undefined; if (!workspaceSlug || !projectId) return undefined;
@ -216,11 +216,11 @@ export class ProjectPageStore implements IProjectPageStore {
* @description fetch the details of a page * @description fetch the details of a page
* @param {string} pageId * @param {string} pageId
*/ */
getPageById = async (workspaceSlug: string, projectId: string, pageId: string) => { fetchPageDetails = async (workspaceSlug: string, projectId: string, pageId: string) => {
try { try {
if (!workspaceSlug || !projectId || !pageId) return undefined; if (!workspaceSlug || !projectId || !pageId) return undefined;
const currentPageId = this.pageById(pageId); const currentPageId = this.getPageById(pageId);
runInAction(() => { runInAction(() => {
this.loader = currentPageId ? `mutation-loader` : `init-loader`; this.loader = currentPageId ? `mutation-loader` : `init-loader`;
this.error = undefined; this.error = undefined;

View file

@ -0,0 +1 @@
export * from "ce/hooks/store";