Files
drift/src/app/workspace/page.tsx
T
2026-02-28 13:41:51 +01:00

156 lines
4.5 KiB
TypeScript

"use client";
import Router from "@/components/Router";
import AppSidebar, { SidebarTrigger } from "@/components/sidebar/AppSidebar";
import { useWorkspace } from "@/hooks/workspace";
import { ChevronRight } from "lucide-react";
import Cookies from "js-cookie";
import { useEffect, useState } from "react";
import { Route } from "@/lib/types";
import { Button } from "@/components/ui/button";
import {
createDefaultWorkspace,
saveWorkspaceToLocalStorage,
} from "@/lib/workspace";
export default function Workspace() {
const {
workspace,
loadWorkspace,
updateEntity,
setWorkspace,
addEntity,
addGroup,
renameEntity,
deleteEntity,
removeNode,
renameGroup,
} = useWorkspace();
const [route, setRoute] = useState<Route>({});
/**
* Initialize workspace from cookie on mount
*/
useEffect(() => {
const routeString = Cookies.get("route");
if (!routeString) return;
const route = JSON.parse(routeString) as Route;
setRoute(route);
}, []);
/**
* Load workspace when currentWorkspace changes.
*
* `loadWorkspace` is intentionally omitted from the dependency array
* because it is recreated on every render by the `useWorkspace` hook.
* Including it would cause this effect to run repeatedly and trigger
* the "maximum update depth exceeded" error. ESLint warning is disabled
* for this case.
*/
useEffect(() => {
loadWorkspace(route.workspace);
if (route.workspace) {
Cookies.set("route", JSON.stringify(route));
} else {
Cookies.remove("route");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [route]);
/**
* Load route (entity) from localStorage when workspace loads
*/
useEffect(() => {
if (!workspace) return;
try {
const savedRoute = localStorage.getItem(`route_${workspace.id}`);
if (savedRoute) {
const parsedRoute: Route = JSON.parse(savedRoute);
setRoute(parsedRoute);
} else {
// Default to first entity if no route saved
const firstEntityId = Object.keys(workspace.entities)[0];
if (firstEntityId) {
setRoute({ workspace: workspace.id, entity: firstEntityId });
}
}
} catch (err) {
console.error("Failed to load route:", err);
const firstEntityId = Object.keys(workspace.entities)[0];
if (firstEntityId) {
setRoute({ workspace: workspace.id, entity: firstEntityId });
}
}
}, [workspace?.id]);
/**
* Save route whenever it changes
*/
useEffect(() => {
if (!workspace || !route.entity) return;
try {
localStorage.setItem(
`route_${workspace.id}`,
JSON.stringify({ workspace: workspace.id, entity: route.entity }),
);
} catch (err) {
console.error("Failed to save route:", err);
}
}, [route, workspace?.id]);
if (!workspace) {
return (
<main
className="flex h-svh items-center justify-center bg-linear-to-br from-blue-50 to-indigo-100 dark:from-slate-900 dark:to-slate-800"
onClick={() => {
const w = createDefaultWorkspace();
saveWorkspaceToLocalStorage(w);
setRoute({ workspace: w.id });
}}
>
<div className="text-center space-y-6">
<h1 className="text-4xl font-bold">Welcome to Drift</h1>
<p className="text-lg text-muted-foreground">
Your personal productivity workspace
</p>
<Button size="lg" className="text-base">
Create a new workspace
</Button>
</div>
</main>
);
}
return (
<AppSidebar
workspace={workspace}
setRoute={setRoute}
setWorkspace={setWorkspace}
addEntity={addEntity}
addGroup={addGroup}
renameEntity={renameEntity}
deleteEntity={deleteEntity}
removeNode={removeNode}
renameGroup={renameGroup}
>
<SidebarTrigger className="w-full px-4">
<div className="px-4">
<header className="w-full border-b pb-3 pt-2 hover:bg-accent/20 flex items-center text-foreground/90 gap-2">
<span className="font-semibold">{workspace.name}</span>
<ChevronRight className="h-5 text-muted-foreground" />
<span className="text-muted-foreground">
{workspace.entities[route.entity || ""]?.name || "Select an item"}
</span>
</header>
</div>
</SidebarTrigger>
<Router workspace={workspace} route={route} updateEntity={updateEntity} />
</AppSidebar>
);
}