diff --git a/apps/client/src/features/page/components/header/page-header-menu.tsx b/apps/client/src/features/page/components/header/page-header-menu.tsx index cf61ac39..cd247c57 100644 --- a/apps/client/src/features/page/components/header/page-header-menu.tsx +++ b/apps/client/src/features/page/components/header/page-header-menu.tsx @@ -9,20 +9,14 @@ import { IconList, IconMessage, IconPrinter, - IconSearch, IconTrash, IconWifiOff, } from "@tabler/icons-react"; -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import useToggleAside from "@/hooks/use-toggle-aside.tsx"; -import { useAtom } from "jotai"; +import { useAtom, useAtomValue } from "jotai"; import { historyAtoms } from "@/features/page-history/atoms/history-atoms.ts"; -import { - getHotkeyHandler, - useClipboard, - useDisclosure, - useHotkeys, -} from "@mantine/hooks"; +import { useClipboard, useDisclosure, useHotkeys } from "@mantine/hooks"; import { useParams } from "react-router-dom"; import { usePageQuery } from "@/features/page/queries/page-query.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts"; @@ -38,8 +32,7 @@ import { pageEditorAtom, yjsConnectionStatusAtom, } from "@/features/editor/atoms/editor-atoms.ts"; -import { searchAndReplaceStateAtom } from "@/features/editor/components/search-and-replace/atoms/search-and-replace-state-atom.ts"; -import { formattedDate, timeAgo } from "@/lib/time.ts"; +import { formattedDate } from "@/lib/time.ts"; import { PageStateSegmentedControl } from "@/features/user/components/page-state-pref.tsx"; import MovePageModal from "@/features/page/components/move-page-modal.tsx"; import { useTimeAgo } from "@/hooks/use-time-ago.tsx"; @@ -51,7 +44,6 @@ interface PageHeaderMenuProps { export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) { const { t } = useTranslation(); const toggleAside = useToggleAside(); - const [yjsConnectionStatus] = useAtom(yjsConnectionStatusAtom); useHotkeys( [ @@ -75,17 +67,7 @@ export default function PageHeaderMenu({ readOnly }: PageHeaderMenuProps) { return ( <> - {yjsConnectionStatus === "disconnected" && ( - - - - - - )} + {!readOnly && } @@ -290,3 +272,49 @@ function PageActionMenu({ readOnly }: PageActionMenuProps) { ); } + +function ConnectionWarning() { + const { t } = useTranslation(); + const yjsConnectionStatus = useAtomValue(yjsConnectionStatusAtom); + const [showWarning, setShowWarning] = useState(false); + const timeoutRef = useRef | null>(null); + + useEffect(() => { + const isDisconnected = ["disconnected", "connecting"].includes(yjsConnectionStatus); + + if (isDisconnected) { + if (!timeoutRef.current) { + timeoutRef.current = setTimeout(() => setShowWarning(true), 5000); + } + } else { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + setShowWarning(false); + } + }, [yjsConnectionStatus]); + + // Cleanup only on unmount + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + if (!showWarning) return null; + + return ( + + + + + + ); +} diff --git a/package.json b/package.json index 8ccd7926..c739b1da 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "server:start": "nx run server:start:prod", "email:dev": "nx run server:email:dev", "dev": "pnpm concurrently -n \"frontend,backend\" -c \"cyan,green\" \"pnpm run client:dev\" \"pnpm run server:dev\"", - "clean": "rm -rf apps/*/dist packages/*/dist apps/*/node_modules/.vite" + "clean": "rm -rf apps/*/dist packages/*/dist apps/client/node_modules/.vite" }, "dependencies": { "@braintree/sanitize-url": "^7.1.0",