1"use client";
2
3import { useEffect } from "react";
4import { DndContext, PointerSensor, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core";
5import { Sidebar } from "./sidebar";
6import { Topbar } from "./topbar";
7import { CommandPalette } from "./command-palette";
8import { OfflineBanner } from "@/components/collaboration/OfflineBanner";
9import { SearchDialog } from "@/components/search/search-dialog";
10import { AiPanel } from "@/components/ai/ai-panel";
11import { GeneratePageDialog } from "@/components/ai/generate-page-dialog";
12import { AssistantDialog } from "@/components/ai/assistant-dialog";
13import { MoveToDialog } from "@/components/dialogs/move-to-dialog";
14import { BulkActionsDialog } from "@/components/dialogs/bulk-actions-dialog";
15import { initOutbox } from "@/stores/offlineQueue";
16import { useUiStore } from "@/stores/ui-store";
17import { authStore, useAuthStore } from "@/lib/auth-store";
18import { realtime } from "@/lib/realtime";
19
20
25export function WorkspaceShell({ children }: { children: React.ReactNode }) {
26 const setSearchDialog = useUiStore((s) => s.setSearchDialog);
27
28
29
30 useEffect(() => {
31 initOutbox();
32 }, []);
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 const accessToken = useAuthStore((s) => s.accessToken);
50 useEffect(() => {
51 if (!accessToken) {
52
53
54
55 realtime.disconnect();
56 return;
57 }
58 realtime.connect(() => authStore.accessToken);
59 }, [accessToken]);
60
61
62
63
64
65 useEffect(() => {
66 const onKey = (e: KeyboardEvent) => {
67 if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
68 e.preventDefault();
69 setSearchDialog(!useUiStore.getState().searchOpen);
70 }
71 };
72 window.addEventListener("keydown", onKey);
73 return () => window.removeEventListener("keydown", onKey);
74 }, [setSearchDialog]);
75
76 const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }));
77
78
79
80
81 const onDragEnd = (_event: DragEndEvent) => {
82
83 };
84
85 return (
86 <DndContext sensors={sensors} onDragEnd={onDragEnd}>
87 <div className="flex h-screen overflow-hidden bg-background">
88 <Sidebar />
89 <div className="flex min-w-0 flex-1 flex-col">
90 <Topbar />
91 {}
93 <OfflineBanner />
94 <main id="workspace-main" className="min-h-0 flex-1 overflow-y-auto">
95 {children}
96 </main>
97 </div>
98 </div>
99 <CommandPalette />
100 <SearchDialog />
101 <AiPanel />
102 <GeneratePageDialog />
103 <AssistantDialog />
104 <MoveToDialog />
105 <BulkActionsDialog />
106 </DndContext>
107 );
108}
109