forked from github/plane
[WEB-1019] chore: error state for unauthorized pages (#4219)
* chore: private page access * chore: add error state for private pages --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
1080094b01
commit
1880eb7704
@ -169,9 +169,15 @@ class PageViewSet(BaseViewSet):
|
|||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
page = self.get_queryset().filter(pk=pk).first()
|
page = self.get_queryset().filter(pk=pk).first()
|
||||||
return Response(
|
if page is None:
|
||||||
PageDetailSerializer(page).data, status=status.HTTP_200_OK
|
return Response(
|
||||||
)
|
{"error": "Page not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
PageDetailSerializer(page).data, status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
def lock(self, request, slug, project_id, pk):
|
def lock(self, request, slug, project_id, pk):
|
||||||
page = Page.objects.filter(
|
page = Page.objects.filter(
|
||||||
|
@ -20,7 +20,17 @@ export const PageQuickActions: React.FC<Props> = observer((props) => {
|
|||||||
// states
|
// states
|
||||||
const [deletePageModal, setDeletePageModal] = useState(false);
|
const [deletePageModal, setDeletePageModal] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { access, archive, archived_at, makePublic, makePrivate, restore } = usePage(pageId);
|
const {
|
||||||
|
access,
|
||||||
|
archive,
|
||||||
|
archived_at,
|
||||||
|
makePublic,
|
||||||
|
makePrivate,
|
||||||
|
restore,
|
||||||
|
canCurrentUserArchivePage,
|
||||||
|
canCurrentUserChangeAccess,
|
||||||
|
canCurrentUserDeletePage,
|
||||||
|
} = usePage(pageId);
|
||||||
|
|
||||||
const pageLink = `${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
|
const pageLink = `${workspaceSlug}/projects/${projectId}/pages/${pageId}`;
|
||||||
const handleCopyText = () =>
|
const handleCopyText = () =>
|
||||||
@ -31,8 +41,53 @@ export const PageQuickActions: React.FC<Props> = observer((props) => {
|
|||||||
message: "Page link copied to clipboard.",
|
message: "Page link copied to clipboard.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleOpenInNewTab = () => window.open(`/${pageLink}`, "_blank");
|
const handleOpenInNewTab = () => window.open(`/${pageLink}`, "_blank");
|
||||||
|
|
||||||
|
const MENU_ITEMS: {
|
||||||
|
key: string;
|
||||||
|
action: () => void;
|
||||||
|
label: string;
|
||||||
|
icon: React.FC<any>;
|
||||||
|
shouldRender: boolean;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
key: "copy-link",
|
||||||
|
action: handleCopyText,
|
||||||
|
label: "Copy link",
|
||||||
|
icon: Link,
|
||||||
|
shouldRender: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "open-new-tab",
|
||||||
|
action: handleOpenInNewTab,
|
||||||
|
label: "Open in new tab",
|
||||||
|
icon: ExternalLink,
|
||||||
|
shouldRender: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "archive-restore",
|
||||||
|
action: archived_at ? restore : archive,
|
||||||
|
label: archived_at ? "Restore" : "Archive",
|
||||||
|
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
||||||
|
shouldRender: canCurrentUserArchivePage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "make-public-private",
|
||||||
|
action: access === 0 ? makePrivate : makePublic,
|
||||||
|
label: access === 0 ? "Make private" : "Make public",
|
||||||
|
icon: access === 0 ? Lock : UsersRound,
|
||||||
|
shouldRender: canCurrentUserChangeAccess && !archived_at,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "delete",
|
||||||
|
action: () => setDeletePageModal(true),
|
||||||
|
label: "Delete",
|
||||||
|
icon: Trash2,
|
||||||
|
shouldRender: canCurrentUserDeletePage && !!archived_at,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DeletePageModal
|
<DeletePageModal
|
||||||
@ -42,89 +97,23 @@ export const PageQuickActions: React.FC<Props> = observer((props) => {
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
/>
|
/>
|
||||||
<CustomMenu placement="bottom-end" ellipsis closeOnSelect>
|
<CustomMenu placement="bottom-end" ellipsis closeOnSelect>
|
||||||
<CustomMenu.MenuItem
|
{MENU_ITEMS.map((item) => {
|
||||||
onClick={(e) => {
|
if (!item.shouldRender) return null;
|
||||||
e.preventDefault();
|
return (
|
||||||
e.stopPropagation();
|
<CustomMenu.MenuItem
|
||||||
handleCopyText();
|
key={item.key}
|
||||||
}}
|
onClick={(e) => {
|
||||||
>
|
e.preventDefault();
|
||||||
<span className="flex items-center gap-2">
|
e.stopPropagation();
|
||||||
<Link className="h-3 w-3" />
|
item.action();
|
||||||
Copy link
|
}}
|
||||||
</span>
|
className="flex items-center gap-2"
|
||||||
</CustomMenu.MenuItem>
|
>
|
||||||
<CustomMenu.MenuItem
|
<item.icon className="h-3 w-3" />
|
||||||
onClick={(e) => {
|
{item.label}
|
||||||
e.preventDefault();
|
</CustomMenu.MenuItem>
|
||||||
e.stopPropagation();
|
);
|
||||||
handleOpenInNewTab();
|
})}
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<ExternalLink className="h-3 w-3" />
|
|
||||||
Open in new tab
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
if (archived_at) restore();
|
|
||||||
else archive();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
{archived_at ? (
|
|
||||||
<>
|
|
||||||
<ArchiveRestoreIcon className="h-3 w-3" />
|
|
||||||
Restore
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ArchiveIcon className="h-3 w-3" />
|
|
||||||
Archive
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
{!archived_at && (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
access === 0 ? makePrivate() : makePublic();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
{access === 0 ? (
|
|
||||||
<>
|
|
||||||
<Lock className="h-3 w-3" />
|
|
||||||
Make private
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<UsersRound className="h-3 w-3" />
|
|
||||||
Make public
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
{archived_at && (
|
|
||||||
<CustomMenu.MenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
setDeletePageModal(true);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="flex items-center gap-2">
|
|
||||||
<Trash2 className="h-3 w-3" />
|
|
||||||
Delete
|
|
||||||
</span>
|
|
||||||
</CustomMenu.MenuItem>
|
|
||||||
)}
|
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Clipboard, Copy, Link, Lock } from "lucide-react";
|
import { ArchiveRestoreIcon, Clipboard, Copy, Link, Lock, LockOpen } from "lucide-react";
|
||||||
// document editor
|
// document editor
|
||||||
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/document-editor";
|
import { EditorReadOnlyRefApi, EditorRefApi } from "@plane/document-editor";
|
||||||
// ui
|
// ui
|
||||||
@ -21,6 +21,9 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
|||||||
const { editorRef, handleDuplicatePage, pageStore } = props;
|
const { editorRef, handleDuplicatePage, pageStore } = props;
|
||||||
// store values
|
// store values
|
||||||
const {
|
const {
|
||||||
|
archived_at,
|
||||||
|
is_locked,
|
||||||
|
id,
|
||||||
archive,
|
archive,
|
||||||
lock,
|
lock,
|
||||||
unlock,
|
unlock,
|
||||||
@ -99,7 +102,7 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
|||||||
{
|
{
|
||||||
key: "copy-page-link",
|
key: "copy-page-link",
|
||||||
action: () => {
|
action: () => {
|
||||||
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/pages/${pageStore.id}`).then(() =>
|
copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/pages/${id}`).then(() =>
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Successful!",
|
title: "Successful!",
|
||||||
@ -119,32 +122,18 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
|||||||
shouldRender: canCurrentUserDuplicatePage,
|
shouldRender: canCurrentUserDuplicatePage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "lock-page",
|
key: "lock-unlock-page",
|
||||||
action: handleLockPage,
|
action: is_locked ? handleUnlockPage : handleLockPage,
|
||||||
label: "Lock page",
|
label: is_locked ? "Unlock page" : "Lock page",
|
||||||
icon: Lock,
|
icon: is_locked ? LockOpen : Lock,
|
||||||
shouldRender: !pageStore.is_locked && canCurrentUserLockPage,
|
shouldRender: canCurrentUserLockPage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "unlock-page",
|
key: "archive-restore-page",
|
||||||
action: handleUnlockPage,
|
action: archived_at ? handleRestorePage : handleArchivePage,
|
||||||
label: "Unlock page",
|
label: archived_at ? "Restore page" : "Archive page",
|
||||||
icon: Lock,
|
icon: archived_at ? ArchiveRestoreIcon : ArchiveIcon,
|
||||||
shouldRender: pageStore.is_locked && canCurrentUserLockPage,
|
shouldRender: canCurrentUserArchivePage,
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "archive-page",
|
|
||||||
action: handleArchivePage,
|
|
||||||
label: "Archive page",
|
|
||||||
icon: ArchiveIcon,
|
|
||||||
shouldRender: !pageStore.archived_at && canCurrentUserArchivePage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "restore-page",
|
|
||||||
action: handleRestorePage,
|
|
||||||
label: "Restore page",
|
|
||||||
icon: ArchiveIcon,
|
|
||||||
shouldRender: !!pageStore.archived_at && canCurrentUserArchivePage,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -166,7 +155,7 @@ export const PageOptionsDropdown: React.FC<Props> = observer((props) => {
|
|||||||
return (
|
return (
|
||||||
<CustomMenu.MenuItem key={item.key} onClick={item.action} className="flex items-center gap-2">
|
<CustomMenu.MenuItem key={item.key} onClick={item.action} className="flex items-center gap-2">
|
||||||
<item.icon className="h-3 w-3" />
|
<item.icon className="h-3 w-3" />
|
||||||
<div className="text-custom-text-300">{item.label}</div>
|
{item.label}
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ReactElement, useEffect, useRef, useState } from "react";
|
import { ReactElement, useEffect, useRef, useState } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
@ -8,12 +9,14 @@ import { EditorRefApi, useEditorMarkings } from "@plane/document-editor";
|
|||||||
// types
|
// types
|
||||||
import { TPage } from "@plane/types";
|
import { TPage } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Spinner, TOAST_TYPE, getButtonStyling, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { PageDetailsHeader } from "@/components/headers";
|
import { PageDetailsHeader } from "@/components/headers";
|
||||||
import { IssuePeekOverview } from "@/components/issues";
|
import { IssuePeekOverview } from "@/components/issues";
|
||||||
import { PageEditorBody, PageEditorHeaderRoot } from "@/components/pages";
|
import { PageEditorBody, PageEditorHeaderRoot } from "@/components/pages";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "@/helpers/common.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePage, useProjectPages } from "@/hooks/store";
|
import { usePage, useProjectPages } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
@ -46,15 +49,16 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// fetching page details
|
// fetching page details
|
||||||
const { data: swrPageDetails, isValidating } = useSWR(
|
const {
|
||||||
pageId ? `PAGE_DETAILS_${pageId}` : null,
|
data: swrPageDetails,
|
||||||
pageId ? () => getPageById(pageId.toString()) : null,
|
isValidating,
|
||||||
{
|
error: pageDetailsError,
|
||||||
revalidateIfStale: false,
|
} = useSWR(pageId ? `PAGE_DETAILS_${pageId}` : null, pageId ? () => getPageById(pageId.toString()) : null, {
|
||||||
revalidateOnFocus: true,
|
revalidateIfStale: false,
|
||||||
revalidateOnReconnect: true,
|
revalidateOnFocus: true,
|
||||||
}
|
revalidateOnReconnect: true,
|
||||||
);
|
});
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() => () => {
|
() => () => {
|
||||||
if (pageStore.cleanup) pageStore.cleanup();
|
if (pageStore.cleanup) pageStore.cleanup();
|
||||||
@ -62,17 +66,29 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
[pageStore]
|
[pageStore]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditorReady = (value: boolean) => setEditorReady(value);
|
if ((!pageStore || !pageStore.id) && !pageDetailsError)
|
||||||
|
|
||||||
const handleReadOnlyEditorReady = () => setReadOnlyEditorReady(true);
|
|
||||||
|
|
||||||
if (!pageStore || !pageStore.id)
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full grid place-items-center">
|
<div className="h-full w-full grid place-items-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (pageDetailsError)
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full flex flex-col items-center justify-center">
|
||||||
|
<h3 className="text-lg font-semibold text-center">Page not found</h3>
|
||||||
|
<p className="text-sm text-custom-text-200 text-center mt-3">
|
||||||
|
The page you are trying to access doesn{"'"}t exist or you don{"'"}t have permission to view it.
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href={`/${workspaceSlug}/projects/${projectId}/pages`}
|
||||||
|
className={cn(getButtonStyling("neutral-primary", "md"), "mt-5")}
|
||||||
|
>
|
||||||
|
View other Pages
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
// we need to get the values of title and description from the page store but we don't have to subscribe to those values
|
// we need to get the values of title and description from the page store but we don't have to subscribe to those values
|
||||||
const pageTitle = pageStore?.name;
|
const pageTitle = pageStore?.name;
|
||||||
|
|
||||||
@ -132,9 +148,9 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
|
|||||||
swrPageDetails={swrPageDetails}
|
swrPageDetails={swrPageDetails}
|
||||||
control={control}
|
control={control}
|
||||||
editorRef={editorRef}
|
editorRef={editorRef}
|
||||||
handleEditorReady={handleEditorReady}
|
handleEditorReady={(val) => setEditorReady(val)}
|
||||||
readOnlyEditorRef={readOnlyEditorRef}
|
readOnlyEditorRef={readOnlyEditorRef}
|
||||||
handleReadOnlyEditorReady={handleReadOnlyEditorReady}
|
handleReadOnlyEditorReady={() => setReadOnlyEditorReady(true)}
|
||||||
handleSubmit={() => handleSubmit(handleUpdatePage)()}
|
handleSubmit={() => handleSubmit(handleUpdatePage)()}
|
||||||
markings={markings}
|
markings={markings}
|
||||||
pageStore={pageStore}
|
pageStore={pageStore}
|
||||||
|
@ -21,6 +21,7 @@ export interface IPageStore extends TPage {
|
|||||||
canCurrentUserEditPage: boolean; // it will give the user permission to read the page or write the page
|
canCurrentUserEditPage: boolean; // it will give the user permission to read the page or write the page
|
||||||
canCurrentUserDuplicatePage: boolean;
|
canCurrentUserDuplicatePage: boolean;
|
||||||
canCurrentUserLockPage: boolean;
|
canCurrentUserLockPage: boolean;
|
||||||
|
canCurrentUserChangeAccess: boolean;
|
||||||
canCurrentUserArchivePage: boolean;
|
canCurrentUserArchivePage: boolean;
|
||||||
canCurrentUserDeletePage: boolean;
|
canCurrentUserDeletePage: boolean;
|
||||||
isContentEditable: boolean;
|
isContentEditable: boolean;
|
||||||
@ -72,7 +73,10 @@ export class PageStore implements IPageStore {
|
|||||||
// service
|
// service
|
||||||
pageService: PageService;
|
pageService: PageService;
|
||||||
|
|
||||||
constructor(private store: RootStore, page: TPage) {
|
constructor(
|
||||||
|
private store: RootStore,
|
||||||
|
page: TPage
|
||||||
|
) {
|
||||||
this.id = page?.id || undefined;
|
this.id = page?.id || undefined;
|
||||||
this.name = page?.name || undefined;
|
this.name = page?.name || undefined;
|
||||||
this.description_html = page?.description_html || undefined;
|
this.description_html = page?.description_html || undefined;
|
||||||
@ -122,6 +126,7 @@ export class PageStore implements IPageStore {
|
|||||||
canCurrentUserEditPage: computed,
|
canCurrentUserEditPage: computed,
|
||||||
canCurrentUserDuplicatePage: computed,
|
canCurrentUserDuplicatePage: computed,
|
||||||
canCurrentUserLockPage: computed,
|
canCurrentUserLockPage: computed,
|
||||||
|
canCurrentUserChangeAccess: computed,
|
||||||
canCurrentUserArchivePage: computed,
|
canCurrentUserArchivePage: computed,
|
||||||
canCurrentUserDeletePage: computed,
|
canCurrentUserDeletePage: computed,
|
||||||
isContentEditable: computed,
|
isContentEditable: computed,
|
||||||
@ -245,12 +250,19 @@ export class PageStore implements IPageStore {
|
|||||||
return this.isCurrentUserOwner || (!!currentUserProjectRole && currentUserProjectRole >= EUserProjectRoles.MEMBER);
|
return this.isCurrentUserOwner || (!!currentUserProjectRole && currentUserProjectRole >= EUserProjectRoles.MEMBER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description returns true if the current logged in user can change the access of the page
|
||||||
|
*/
|
||||||
|
get canCurrentUserChangeAccess() {
|
||||||
|
return this.isCurrentUserOwner;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description returns true if the current logged in user can archive the page
|
* @description returns true if the current logged in user can archive the page
|
||||||
*/
|
*/
|
||||||
get canCurrentUserArchivePage() {
|
get canCurrentUserArchivePage() {
|
||||||
const currentUserProjectRole = this.store.user.membership.currentProjectRole;
|
const currentUserProjectRole = this.store.user.membership.currentProjectRole;
|
||||||
return this.isCurrentUserOwner || (!!currentUserProjectRole && currentUserProjectRole >= EUserProjectRoles.MEMBER);
|
return this.isCurrentUserOwner || currentUserProjectRole === EUserProjectRoles.ADMIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -315,13 +327,14 @@ export class PageStore implements IPageStore {
|
|||||||
set(this, key, currentPageResponse?.[currentPageKey] || undefined);
|
set(this, key, currentPageResponse?.[currentPageKey] || undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
Object.keys(pageData).forEach((key) => {
|
Object.keys(pageData).forEach((key) => {
|
||||||
const currentPageKey = key as keyof TPage;
|
const currentPageKey = key as keyof TPage;
|
||||||
set(this, key, currentPage?.[currentPageKey] || undefined);
|
set(this, key, currentPage?.[currentPageKey] || undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -349,10 +362,11 @@ export class PageStore implements IPageStore {
|
|||||||
...updatedProps,
|
...updatedProps,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.view_props = currentViewProps;
|
this.view_props = currentViewProps;
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -366,13 +380,16 @@ export class PageStore implements IPageStore {
|
|||||||
const pageAccess = this.access;
|
const pageAccess = this.access;
|
||||||
runInAction(() => (this.access = EPageAccess.PUBLIC));
|
runInAction(() => (this.access = EPageAccess.PUBLIC));
|
||||||
|
|
||||||
await this.pageService
|
try {
|
||||||
.update(workspaceSlug, projectId, this.id, {
|
await this.pageService.update(workspaceSlug, projectId, this.id, {
|
||||||
access: EPageAccess.PUBLIC,
|
access: EPageAccess.PUBLIC,
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
runInAction(() => (this.access = pageAccess));
|
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.access = pageAccess;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -385,13 +402,16 @@ export class PageStore implements IPageStore {
|
|||||||
const pageAccess = this.access;
|
const pageAccess = this.access;
|
||||||
runInAction(() => (this.access = EPageAccess.PRIVATE));
|
runInAction(() => (this.access = EPageAccess.PRIVATE));
|
||||||
|
|
||||||
await this.pageService
|
try {
|
||||||
.update(workspaceSlug, projectId, this.id, {
|
await this.pageService.update(workspaceSlug, projectId, this.id, {
|
||||||
access: EPageAccess.PRIVATE,
|
access: EPageAccess.PRIVATE,
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
runInAction(() => (this.access = pageAccess));
|
|
||||||
});
|
});
|
||||||
|
} catch (error) {
|
||||||
|
runInAction(() => {
|
||||||
|
this.access = pageAccess;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -404,36 +424,11 @@ export class PageStore implements IPageStore {
|
|||||||
const pageIsLocked = this.is_locked;
|
const pageIsLocked = this.is_locked;
|
||||||
runInAction(() => (this.is_locked = true));
|
runInAction(() => (this.is_locked = true));
|
||||||
|
|
||||||
await this.pageService.lock(workspaceSlug, projectId, this.id).catch(() => {
|
await this.pageService.lock(workspaceSlug, projectId, this.id).catch((error) => {
|
||||||
runInAction(() => (this.is_locked = pageIsLocked));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description archive the page
|
|
||||||
*/
|
|
||||||
archive = async () => {
|
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
|
||||||
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
|
||||||
|
|
||||||
await this.pageService.archive(workspaceSlug, projectId, this.id).then((res) => {
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.archived_at = res.archived_at;
|
this.is_locked = pageIsLocked;
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description restore the page
|
|
||||||
*/
|
|
||||||
restore = async () => {
|
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
|
||||||
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
|
||||||
|
|
||||||
await this.pageService.restore(workspaceSlug, projectId, this.id).then(() => {
|
|
||||||
runInAction(() => {
|
|
||||||
this.archived_at = null;
|
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -447,11 +442,48 @@ export class PageStore implements IPageStore {
|
|||||||
const pageIsLocked = this.is_locked;
|
const pageIsLocked = this.is_locked;
|
||||||
runInAction(() => (this.is_locked = false));
|
runInAction(() => (this.is_locked = false));
|
||||||
|
|
||||||
await this.pageService.unlock(workspaceSlug, projectId, this.id).catch(() => {
|
await this.pageService.unlock(workspaceSlug, projectId, this.id).catch((error) => {
|
||||||
runInAction(() => (this.is_locked = pageIsLocked));
|
runInAction(() => {
|
||||||
|
this.is_locked = pageIsLocked;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description archive the page
|
||||||
|
*/
|
||||||
|
archive = async () => {
|
||||||
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
|
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.pageService.archive(workspaceSlug, projectId, this.id);
|
||||||
|
runInAction(() => {
|
||||||
|
this.archived_at = response.archived_at;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description restore the page
|
||||||
|
*/
|
||||||
|
restore = async () => {
|
||||||
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
|
if (!workspaceSlug || !projectId || !this.id) return undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.pageService.restore(workspaceSlug, projectId, this.id);
|
||||||
|
runInAction(() => {
|
||||||
|
this.archived_at = null;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description add the page to favorites
|
* @description add the page to favorites
|
||||||
*/
|
*/
|
||||||
@ -464,8 +496,11 @@ export class PageStore implements IPageStore {
|
|||||||
this.is_favorite = true;
|
this.is_favorite = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.pageService.addToFavorites(workspaceSlug, projectId, this.id).catch(() => {
|
await this.pageService.addToFavorites(workspaceSlug, projectId, this.id).catch((error) => {
|
||||||
runInAction(() => (this.is_favorite = pageIsFavorite));
|
runInAction(() => {
|
||||||
|
this.is_favorite = pageIsFavorite;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -481,8 +516,11 @@ export class PageStore implements IPageStore {
|
|||||||
this.is_favorite = false;
|
this.is_favorite = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.pageService.removeFromFavorites(workspaceSlug, projectId, this.id).catch(() => {
|
await this.pageService.removeFromFavorites(workspaceSlug, projectId, this.id).catch((error) => {
|
||||||
runInAction(() => (this.is_favorite = pageIsFavorite));
|
runInAction(() => {
|
||||||
|
this.is_favorite = pageIsFavorite;
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -148,7 +148,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return pages;
|
return pages;
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
@ -156,6 +156,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
description: "Failed to fetch the pages, Please try again later.",
|
description: "Failed to fetch the pages, Please try again later.",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -181,7 +182,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
@ -189,6 +190,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
description: "Failed to fetch the page, Please try again later.",
|
description: "Failed to fetch the page, Please try again later.",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -213,7 +215,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return page;
|
return page;
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
@ -221,6 +223,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
description: "Failed to create a page, Please try again later.",
|
description: "Failed to create a page, Please try again later.",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -235,7 +238,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
|
|
||||||
await this.service.remove(workspaceSlug, projectId, pageId);
|
await this.service.remove(workspaceSlug, projectId, pageId);
|
||||||
runInAction(() => unset(this.data, [pageId]));
|
runInAction(() => unset(this.data, [pageId]));
|
||||||
} catch {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.loader = undefined;
|
this.loader = undefined;
|
||||||
this.error = {
|
this.error = {
|
||||||
@ -243,6 +246,7 @@ export class ProjectPageStore implements IProjectPageStore {
|
|||||||
description: "Failed to delete a page, Please try again later.",
|
description: "Failed to delete a page, Please try again later.",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2747,7 +2747,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
|
|
||||||
"@types/react@*", "@types/react@^18.2.42":
|
"@types/react@*", "@types/react@18.2.42", "@types/react@^18.2.42":
|
||||||
version "18.2.42"
|
version "18.2.42"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.42.tgz#6f6b11a904f6d96dda3c2920328a97011a00aba7"
|
||||||
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
integrity sha512-c1zEr96MjakLYus/wPnuWDo1/zErfdU9rNsIGmE+NV71nx88FG9Ttgo5dqorXTu/LImX2f63WBP986gJkMPNbA==
|
||||||
|
Loading…
Reference in New Issue
Block a user