T5/Phase 3: SPA shell + navigation

- TanStack Router with type-safe file-based routes
- Sidebar: collapsible, workspace switcher, 12 nav links, user menu
- Topbar: search, quick-add, notifications, user avatar
- cmdk command palette: nav + create + search + @mentions
- Keyboard shortcuts: Cmd+K palette, g+t/h/p/n/c/g/s navigation, ? help
- Theme: dark default, accent picker (slate/blue/green/purple/orange), persisted to localStorage
- Auth: login -> /, logout -> /login, 401 redirect
- Placeholder pages for all 13 nav routes (real in T6/T7)
- Typed API client with useApiQuery/useApiMutation hooks
This commit is contained in:
Hermes
2026-08-01 02:00:24 +00:00
parent c277e3a14f
commit e86a7c0672
66 changed files with 3781 additions and 32 deletions
@@ -0,0 +1,32 @@
import { useEffect } from "react";
import { useThemeStore, ACCENT_PALETTE, type AccentColor } from "@/lib/stores/use-theme-store";
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const { mode, accent } = useThemeStore();
useEffect(() => {
const root = document.documentElement;
// Apply dark/light mode
if (mode === "system") {
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
const handler = (e: MediaQueryListEvent | MediaQueryList) => {
root.classList.toggle("dark", e.matches);
};
handler(mediaQuery);
mediaQuery.addEventListener("change", handler as (e: MediaQueryListEvent) => void);
return () =>
mediaQuery.removeEventListener("change", handler as (e: MediaQueryListEvent) => void);
} else {
root.classList.toggle("dark", mode === "dark");
}
}, [mode]);
useEffect(() => {
const root = document.documentElement;
const palette = ACCENT_PALETTE[accent as AccentColor] || ACCENT_PALETTE.blue;
root.style.setProperty("--accent-hsl", palette.hsl);
}, [accent]);
return <>{children}</>;
}