mirror of
https://github.com/docmost/docmost.git
synced 2025-11-24 06:41:22 +10:00
added recycle bin modal, updated api routes
This commit is contained in:
@ -14,6 +14,9 @@ import {
|
||||
movePage,
|
||||
getPageBreadcrumbs,
|
||||
getRecentChanges,
|
||||
getDeletedPages,
|
||||
restorePage,
|
||||
removePage,
|
||||
} from "@/features/page/services/page-service";
|
||||
import {
|
||||
IMovePage,
|
||||
@ -73,6 +76,18 @@ export function useUpdatePageMutation() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useRemovePageMutation() {
|
||||
return useMutation({
|
||||
mutationFn: (pageId: string) => removePage(pageId),
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: "Page deleted successfully" });
|
||||
},
|
||||
onError: (error) => {
|
||||
notifications.show({ message: "Failed to delete page", color: "red" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletePageMutation() {
|
||||
return useMutation({
|
||||
mutationFn: (pageId: string) => deletePage(pageId),
|
||||
@ -91,6 +106,18 @@ export function useMovePageMutation() {
|
||||
});
|
||||
}
|
||||
|
||||
export function useRestorePageMutation() {
|
||||
return useMutation({
|
||||
mutationFn: (pageId: string) => restorePage(pageId),
|
||||
onSuccess: () => {
|
||||
notifications.show({ message: "Page restored successfully" });
|
||||
},
|
||||
onError: (error) => {
|
||||
notifications.show({ message: "Failed to restore page", color: "red" });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useGetSidebarPagesQuery(
|
||||
data: SidebarPagesParams,
|
||||
): UseQueryResult<IPagination<IPage>, Error> {
|
||||
@ -143,3 +170,12 @@ export function useRecentChangesQuery(
|
||||
refetchOnMount: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeletedPagesQuery(
|
||||
spaceId: string,
|
||||
): UseQueryResult<IPagination<IPage>, Error> {
|
||||
return useQuery({
|
||||
queryKey: ["deleted-pages", spaceId],
|
||||
queryFn: () => getDeletedPages(spaceId),
|
||||
});
|
||||
}
|
||||
|
||||
@ -26,10 +26,19 @@ export async function updatePage(data: Partial<IPageInput>): Promise<IPage> {
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function removePage(pageId: string): Promise<void> {
|
||||
await api.post("/pages/remove", { pageId });
|
||||
}
|
||||
|
||||
export async function deletePage(pageId: string): Promise<void> {
|
||||
await api.post("/pages/delete", { pageId });
|
||||
}
|
||||
|
||||
export async function getDeletedPages(spaceId: string): Promise<IPagination<IPage>> {
|
||||
const req = await api.post("/pages/deleted", { spaceId });
|
||||
return req.data;
|
||||
}
|
||||
|
||||
export async function restorePage(pageId: string): Promise<void> {
|
||||
await api.post("/pages/restore", { pageId });
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ import { IMovePage, IPage } from "@/features/page/types/page.types.ts";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import {
|
||||
useCreatePageMutation,
|
||||
useDeletePageMutation,
|
||||
useRemovePageMutation,
|
||||
useMovePageMutation,
|
||||
useUpdatePageMutation,
|
||||
} from "@/features/page/queries/page-query.ts";
|
||||
@ -27,7 +27,7 @@ export function useTreeMutation<T>(spaceId: string) {
|
||||
const tree = useMemo(() => new SimpleTree<SpaceTreeNode>(data), [data]);
|
||||
const createPageMutation = useCreatePageMutation();
|
||||
const updatePageMutation = useUpdatePageMutation();
|
||||
const deletePageMutation = useDeletePageMutation();
|
||||
const deletePageMutation = useRemovePageMutation();
|
||||
const movePageMutation = useMovePageMutation();
|
||||
const navigate = useNavigate();
|
||||
const { spaceSlug } = useParams();
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { Modal, ScrollArea, rem } from "@mantine/core";
|
||||
import React, { useMemo } from "react";
|
||||
import { useSpaceQuery } from "@/features/space/queries/space-query.ts";
|
||||
import { useSpaceAbility } from "@/features/space/permissions/use-space-ability.ts";
|
||||
import RecycledPagesList from "@/features/space/components/recycled-pages.tsx"
|
||||
|
||||
interface RecycleBinModalProps {
|
||||
spaceId: string;
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export default function RecycleBinModal({
|
||||
spaceId,
|
||||
opened,
|
||||
onClose,
|
||||
}: RecycleBinModalProps) {
|
||||
const { data: space, isLoading } = useSpaceQuery(spaceId);
|
||||
|
||||
const spaceRules = space?.membership?.permissions;
|
||||
const spaceAbility = useMemo(() => useSpaceAbility(spaceRules), [spaceRules]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Root
|
||||
opened={opened}
|
||||
onClose={onClose}
|
||||
size={600}
|
||||
padding="xl"
|
||||
yOffset="10vh"
|
||||
xOffset={0}
|
||||
mah={400}
|
||||
>
|
||||
<Modal.Overlay />
|
||||
<Modal.Content style={{ overflow: "hidden" }}>
|
||||
<Modal.Header py={0}>
|
||||
<Modal.Title fw={500}>{space?.name}</Modal.Title>
|
||||
<Modal.CloseButton />
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div style={{ height: rem("600px") }}>
|
||||
<ScrollArea h="600" w="100%" scrollbarSize={5}>
|
||||
<RecycledPagesList spaceId={space.id} />
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
</Modal.Content>
|
||||
</Modal.Root>
|
||||
</>
|
||||
)
|
||||
}
|
||||
106
apps/client/src/features/space/components/recycled-pages.tsx
Normal file
106
apps/client/src/features/space/components/recycled-pages.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useDeletedPagesQuery, useRestorePageMutation, useDeletePageMutation } from "@/features/page/queries/page-query.ts";
|
||||
import { modals } from "@mantine/modals";
|
||||
import { ActionIcon, Menu, Table, Text } from "@mantine/core";
|
||||
import { IconDots } from "@tabler/icons-react";
|
||||
|
||||
interface RecycledPagesProps {
|
||||
spaceId: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export default function RecycledPagesList({
|
||||
spaceId,
|
||||
}: RecycledPagesProps) {
|
||||
const { data, isLoading } = useDeletedPagesQuery(spaceId);
|
||||
const restorePageMutation = useRestorePageMutation();
|
||||
const removePageMutation = useDeletePageMutation();
|
||||
|
||||
const handleRestorePage = async (pageId: string) => {
|
||||
await restorePageMutation.mutateAsync(pageId);
|
||||
};
|
||||
|
||||
const handleRemovePage = async (pageId: string) => {
|
||||
await removePageMutation.mutateAsync(pageId);
|
||||
};
|
||||
|
||||
const openRemovePageModal = (pageId: string) =>
|
||||
modals.openConfirmModal({
|
||||
title: "Delete page permanently",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
Are you sure you want to permanently delete this page ?
|
||||
</Text>
|
||||
),
|
||||
centered: true,
|
||||
labels: { confirm: "Delete", cancel: "Cancel" },
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => handleRemovePage(pageId),
|
||||
});
|
||||
|
||||
const openRestorePageModal = (pageId: string) =>
|
||||
modals.openConfirmModal({
|
||||
title: "Restore page",
|
||||
children: (
|
||||
<Text size="sm">
|
||||
"Restore this page ?"
|
||||
</Text>
|
||||
),
|
||||
centered: true,
|
||||
labels: { confirm: "Restore", cancel: "Cancel" },
|
||||
confirmProps: { color: "blue" },
|
||||
onConfirm: () => handleRestorePage(pageId)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{data && (
|
||||
<Table>
|
||||
<Table.Thead>
|
||||
<Table.Th>Deleted Pages</Table.Th>
|
||||
</Table.Thead>
|
||||
|
||||
<Table.Tbody>
|
||||
{data?.items.map((page) => (
|
||||
<Table.Tr>
|
||||
<Table.Td>
|
||||
<div>
|
||||
<Text fz="sm" fw={500}>
|
||||
{page?.title}
|
||||
</Text>
|
||||
</div>
|
||||
</Table.Td>
|
||||
|
||||
<Table.Td>
|
||||
{(
|
||||
<Menu>
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="subtle" c="gray">
|
||||
<IconDots size={20} stroke={2} />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
openRestorePageModal(page.id)
|
||||
}>
|
||||
Restore Page
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={() =>
|
||||
openRemovePageModal
|
||||
}>
|
||||
Delete Page permanently
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
)}
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -14,6 +14,7 @@ import {
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
IconTrash,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import classes from './space-sidebar.module.css';
|
||||
@ -25,6 +26,7 @@ import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import SpaceSettingsModal from '@/features/space/components/settings-modal.tsx';
|
||||
import RecycleBinModal from "@/features/space/components/recycle-bin-modal.tsx";
|
||||
import { useGetSpaceBySlugQuery } from '@/features/space/queries/space-query.ts';
|
||||
import { getSpaceUrl } from '@/lib/config.ts';
|
||||
import SpaceTree from '@/features/page/tree/components/space-tree.tsx';
|
||||
@ -41,6 +43,8 @@ export function SpaceSidebar() {
|
||||
const location = useLocation();
|
||||
const [opened, { open: openSettings, close: closeSettings }] =
|
||||
useDisclosure(false);
|
||||
const [openedRecycleBin, { open: openRecycleBin, close: closeRecycleBin }] =
|
||||
useDisclosure(false);
|
||||
const { spaceSlug } = useParams();
|
||||
const { data: space, isLoading, isError } = useGetSpaceBySlugQuery(spaceSlug);
|
||||
|
||||
@ -113,6 +117,17 @@ export function SpaceSidebar() {
|
||||
</div>
|
||||
</UnstyledButton>
|
||||
|
||||
<UnstyledButton className={classes.menu} onClick={openRecycleBin}>
|
||||
<div className={classes.menuItemInner}>
|
||||
<IconTrash
|
||||
size={18}
|
||||
className={classes.menuItemIcon}
|
||||
stroke={2}
|
||||
/>
|
||||
<span>Recycle Bin</span>
|
||||
</div>
|
||||
</UnstyledButton>
|
||||
|
||||
{spaceAbility.can(
|
||||
SpaceCaslAction.Manage,
|
||||
SpaceCaslSubject.Page
|
||||
@ -179,6 +194,12 @@ export function SpaceSidebar() {
|
||||
spaceId={space?.slug}
|
||||
/>
|
||||
|
||||
<RecycleBinModal
|
||||
opened={openedRecycleBin}
|
||||
onClose={closeRecycleBin}
|
||||
spaceId={space?.slug}
|
||||
/>
|
||||
|
||||
<SearchSpotlight spaceId={space.id} />
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user