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:
@@ -1,9 +1,11 @@
|
||||
import { createRootRoute, Outlet } from "@tanstack/react-router";
|
||||
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<div className="min-h-screen bg-background text-foreground">
|
||||
<Outlet />
|
||||
{import.meta.env.DEV && <TanStackRouterDevtools />}
|
||||
</div>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { createRoute, Outlet } from "@tanstack/react-router";
|
||||
import { Route as rootRoute } from "./__root";
|
||||
import { Sidebar } from "@/components/shell/sidebar";
|
||||
import { Topbar } from "@/components/shell/topbar";
|
||||
import { CommandPalette } from "@/components/shell/command-palette";
|
||||
import { ShortcutsHelp } from "@/components/shell/shortcuts-help";
|
||||
import { useKeyboardShortcuts } from "@/hooks/use-keyboard-shortcuts";
|
||||
|
||||
function AppLayout() {
|
||||
useKeyboardShortcuts();
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen">
|
||||
<Sidebar />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<Topbar />
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex-1 overflow-auto p-4 md:p-6"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
<CommandPalette />
|
||||
<ShortcutsHelp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
id: "_app",
|
||||
component: AppLayout,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function AgentsPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Agent Activity</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — agent activity feed.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/agents",
|
||||
component: AgentsPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../../_app";
|
||||
|
||||
function AgentActivityPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Agent Activity</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — agent activity feed.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/agents/activity",
|
||||
component: AgentActivityPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function AnalyticsPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Analytics</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — analytics dashboard.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/analytics",
|
||||
component: AnalyticsPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function CalendarPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Calendar</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — calendar view.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/calendar",
|
||||
component: CalendarPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function CanvasPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Canvas</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — visual canvas.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/canvas",
|
||||
component: CanvasPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function DailyNotesPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Daily Notes</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — daily journal.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/daily",
|
||||
component: DailyNotesPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function GraphPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Graph</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — knowledge graph.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/graph",
|
||||
component: GraphPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function HabitsPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Habits</h1>
|
||||
<p className="text-muted-foreground">Coming in T6 — habit tracking.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/habits",
|
||||
component: HabitsPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function DashboardPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Dashboard</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — real dashboard widgets.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/",
|
||||
component: DashboardPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function NotesPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Notes</h1>
|
||||
<p className="text-muted-foreground">Coming in T6 — notes and documents.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/notes",
|
||||
component: NotesPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function ProjectsPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Projects</h1>
|
||||
<p className="text-muted-foreground">Coming in T6 — project management.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/projects",
|
||||
component: ProjectsPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function SearchPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Search</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — full-text search.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/search",
|
||||
component: SearchPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function SettingsPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Settings</h1>
|
||||
<p className="text-muted-foreground">Coming in T7 — user preferences.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/settings",
|
||||
component: SettingsPage,
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { createRoute } from "@tanstack/react-router";
|
||||
import { Route as appRoute } from "../_app";
|
||||
|
||||
function TasksPage() {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh]">
|
||||
<h1 className="text-3xl font-bold mb-2">Tasks</h1>
|
||||
<p className="text-muted-foreground">Coming in T6 — full task management.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const Route = createRoute({
|
||||
getParentRoute: () => appRoute,
|
||||
path: "/tasks",
|
||||
component: TasksPage,
|
||||
});
|
||||
@@ -19,7 +19,7 @@ function LoginPage() {
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json();
|
||||
setError(data.message || "Login failed");
|
||||
setError(data.error?.message || data.message || "Login failed");
|
||||
return;
|
||||
}
|
||||
navigate({ to: "/" });
|
||||
|
||||
Reference in New Issue
Block a user