mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
empty states
This commit is contained in:
@@ -357,6 +357,9 @@
|
||||
"Multiple": "Multiple",
|
||||
"Turn into": "Turn into",
|
||||
"Text align": "Text align",
|
||||
"This page may have been deleted, moved, or you may not have access.": "This page may have been deleted, moved, or you may not have access.",
|
||||
"Go to homepage": "Go to homepage",
|
||||
"Pages you create will show up here.": "Pages you create will show up here.",
|
||||
"Heading {{level}}": "Heading {{level}}",
|
||||
"Toggle title": "Toggle title",
|
||||
"Write anything. Enter \"/\" for commands": "Write anything. Enter \"/\" for commands",
|
||||
|
||||
@@ -11,7 +11,8 @@ import PageListSkeleton from "@/components/ui/page-list-skeleton.tsx";
|
||||
import { buildPageUrl } from "@/features/page/page.utils.ts";
|
||||
import { formattedDate } from "@/lib/time.ts";
|
||||
import { useRecentChangesQuery } from "@/features/page/queries/page-query.ts";
|
||||
import { IconFileDescription } from "@tabler/icons-react";
|
||||
import { IconFileDescription, IconFiles } from "@tabler/icons-react";
|
||||
import { EmptyState } from "@/components/ui/empty-state.tsx";
|
||||
import { getSpaceUrl } from "@/lib/config.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getInitialsColor } from "@/lib/get-initials-color.ts";
|
||||
@@ -85,8 +86,10 @@ export default function RecentChanges({ spaceId }: Props) {
|
||||
</Table>
|
||||
</Table.ScrollContainer>
|
||||
) : (
|
||||
<Text size="md" ta="center">
|
||||
{t("No pages yet")}
|
||||
</Text>
|
||||
<EmptyState
|
||||
icon={IconFiles}
|
||||
title={t("No pages yet")}
|
||||
description={t("Pages you create will show up here.")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Stack, Text } from "@mantine/core";
|
||||
import { type TablerIcon } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
import classes from "./empty-state.module.css";
|
||||
|
||||
type EmptyStateProps = {
|
||||
icon: TablerIcon;
|
||||
title: string;
|
||||
description?: string;
|
||||
action?: ReactNode;
|
||||
};
|
||||
|
||||
export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) {
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Icon size={40} stroke={1.5} color="var(--mantine-color-dimmed)" />
|
||||
<Text size="lg" fw={500}>
|
||||
{title}
|
||||
</Text>
|
||||
{description && (
|
||||
<Text size="sm" c="dimmed" maw={350}>
|
||||
{description}
|
||||
</Text>
|
||||
)}
|
||||
{action}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
import classes from "@/features/page/tree/styles/tree.module.css";
|
||||
import { ActionIcon, Box, Menu, rem } from "@mantine/core";
|
||||
import { ActionIcon, Box, Menu, rem, Text } from "@mantine/core";
|
||||
import {
|
||||
IconArrowRight,
|
||||
IconChevronDown,
|
||||
@@ -82,6 +82,7 @@ interface SpaceTreeProps {
|
||||
const openTreeNodesAtom = atom<OpenMap>({});
|
||||
|
||||
export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
||||
const { t } = useTranslation();
|
||||
const { pageSlug } = useParams();
|
||||
const { data, setData, controllers } =
|
||||
useTreeMutation<TreeApi<SpaceTreeNode>>(spaceId);
|
||||
@@ -231,11 +232,18 @@ export default function SpaceTree({ spaceId, readOnly }: SpaceTreeProps) {
|
||||
};
|
||||
}, [setTreeApi]);
|
||||
|
||||
const filteredData = data.filter((node) => node?.spaceId === spaceId);
|
||||
|
||||
return (
|
||||
<div ref={mergedRef} className={classes.treeContainer}>
|
||||
{isDataLoaded && filteredData.length === 0 && (
|
||||
<Text size="xs" c="dimmed" py="xs" px="sm">
|
||||
{t("No pages yet")}
|
||||
</Text>
|
||||
)}
|
||||
{isRootReady && rootElement.current && (
|
||||
<Tree
|
||||
data={data.filter((node) => node?.spaceId === spaceId)}
|
||||
data={filteredData}
|
||||
disableDrag={readOnly}
|
||||
disableDrop={readOnly}
|
||||
disableEdit={readOnly}
|
||||
|
||||
@@ -13,6 +13,10 @@ import {
|
||||
} from "@/features/space/permissions/permissions.type.ts";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React from "react";
|
||||
import { EmptyState } from "@/components/ui/empty-state.tsx";
|
||||
import { IconFileOff } from "@tabler/icons-react";
|
||||
import { Button } from "@mantine/core";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const MemoizedFullEditor = React.memo(FullEditor);
|
||||
const MemoizedPageHeader = React.memo(PageHeader);
|
||||
@@ -39,9 +43,27 @@ export default function Page() {
|
||||
|
||||
if (isError || !page) {
|
||||
if ([401, 403, 404].includes(error?.["status"])) {
|
||||
return <div>{t("Page not found")}</div>;
|
||||
return (
|
||||
<EmptyState
|
||||
icon={IconFileOff}
|
||||
title={t("Page not found")}
|
||||
description={t(
|
||||
"This page may have been deleted, moved, or you may not have access.",
|
||||
)}
|
||||
action={
|
||||
<Button component={Link} to="/home" variant="default" size="sm" mt="xs">
|
||||
{t("Go to homepage")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <div>{t("Error fetching page data.")}</div>;
|
||||
return (
|
||||
<EmptyState
|
||||
icon={IconFileOff}
|
||||
title={t("Error fetching page data.")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!space) {
|
||||
|
||||
Reference in New Issue
Block a user