import type { HocuspocusProvider, onStatusParameters, onUnsyncedChangesParameters, } from "@hocuspocus/provider"; import { useEffect, useState } from "react"; const CollaborationStatus = (props: { provider: HocuspocusProvider; }) => { const { provider } = props; const [unsyncedChanges, setUnsyncedChanges] = useState(0); const [socketStatus, setSocketStatus] = useState(null); const [isAttached, _setAttached] = useState(false); useEffect(() => { setSocketStatus(provider.configuration.websocketProvider.status); _setAttached(provider.isAttached); const handleUnsyncedChanges = (changes: onUnsyncedChangesParameters) => { setUnsyncedChanges(changes.number); }; const handleSocketStatus = (status: onStatusParameters) => { setSocketStatus(status.status); }; provider.on("unsyncedChanges", handleUnsyncedChanges); provider.configuration.websocketProvider.on("status", handleSocketStatus); return () => { provider.off("unsyncedChanges", handleUnsyncedChanges); provider.configuration.websocketProvider.off( "status", handleSocketStatus, ); }; }, [provider]); const updateAttached = (attach: boolean) => { if (attach) { provider.attach(); } else { provider.detach(); } _setAttached(provider.isAttached); }; const getStatusColor = (status: string | null) => { switch (status) { case "connected": return "bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400"; case "connecting": return "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400"; case "disconnected": return "bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400"; default: return "bg-slate-100 text-slate-800 dark:bg-slate-800 dark:text-slate-400"; } }; const UNSYNCED_THRESHOLD = 10; return (
{/* Alert banner when unsynced changes grow — indicates connection issues */} {unsyncedChanges >= UNSYNCED_THRESHOLD && (

Connection issue detected

{unsyncedChanges} changes are waiting to sync. Your edits are saved locally but not yet on the server.

)} {/* Status indicators */}

Connection Status

{socketStatus || "Unknown"}
{isAttached ? "Attached" : "Detached"}
= UNSYNCED_THRESHOLD ? "bg-red-500 animate-pulse" : "bg-orange-500 animate-pulse"}`} >
= UNSYNCED_THRESHOLD ? "bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400" : "bg-orange-100 text-orange-800 dark:bg-orange-900/20 dark:text-orange-400"}`} > {unsyncedChanges} unsynced
{/* Control buttons */}

WebSocket Controls

Provider Controls

); }; export default CollaborationStatus;