feat(16-04): add TanStack Router file-based routes and app shell layout
Create route structure with __root, index (redirect to /initiatives), initiatives/index (dashboard stub), initiatives/$id (detail stub with type-safe params), and inbox (stub). Add AppLayout with persistent nav header matching wireframe: title, New Initiative button, navigation tabs with active highlighting. Disabled tabs for Agents/Tasks/Settings (out of scope). Replace temporary health-check App with RouterProvider. Route tree auto-generated by TanStack Router Vite plugin.
This commit is contained in:
@@ -1,16 +1,8 @@
|
||||
import { trpc } from './lib/trpc';
|
||||
import { RouterProvider } from '@tanstack/react-router'
|
||||
import { router } from './router'
|
||||
|
||||
function App() {
|
||||
const health = trpc.health.useQuery();
|
||||
|
||||
return (
|
||||
<div className="p-8">
|
||||
<h1 className="text-2xl font-bold">Codewalk District</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
Server: {health.data?.status ?? 'connecting...'}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
return <RouterProvider router={router} />
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
||||
57
packages/web/src/layouts/AppLayout.tsx
Normal file
57
packages/web/src/layouts/AppLayout.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
const navItems = [
|
||||
{ label: 'Initiatives', to: '/initiatives', enabled: true },
|
||||
{ label: 'Inbox', to: '/inbox', enabled: true },
|
||||
{ label: 'Agents', to: '#', enabled: false },
|
||||
{ label: 'Tasks', to: '#', enabled: false },
|
||||
{ label: 'Settings', to: '#', enabled: false },
|
||||
] as const
|
||||
|
||||
export function AppLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-50 border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-6">
|
||||
<Link to="/initiatives" className="text-lg font-bold tracking-tight">
|
||||
Codewalk District
|
||||
</Link>
|
||||
<Button size="sm" onClick={() => { /* placeholder - Phase 17 */ }}>
|
||||
+ New Initiative
|
||||
</Button>
|
||||
</div>
|
||||
{/* Navigation */}
|
||||
<nav className="mx-auto flex max-w-7xl gap-1 px-6 pb-2">
|
||||
{navItems.map((item) =>
|
||||
item.enabled ? (
|
||||
<Link
|
||||
key={item.label}
|
||||
to={item.to}
|
||||
className="rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:text-foreground"
|
||||
activeProps={{
|
||||
className: 'rounded-md px-3 py-1.5 text-sm font-medium text-foreground bg-muted',
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
) : (
|
||||
<span
|
||||
key={item.label}
|
||||
className="cursor-not-allowed rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground/50"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
{/* Page content */}
|
||||
<main className="mx-auto max-w-7xl px-6 py-6">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
113
packages/web/src/routeTree.gen.ts
Normal file
113
packages/web/src/routeTree.gen.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as InboxRouteImport } from './routes/inbox'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as InitiativesIndexRouteImport } from './routes/initiatives/index'
|
||||
import { Route as InitiativesIdRouteImport } from './routes/initiatives/$id'
|
||||
|
||||
const InboxRoute = InboxRouteImport.update({
|
||||
id: '/inbox',
|
||||
path: '/inbox',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const InitiativesIndexRoute = InitiativesIndexRouteImport.update({
|
||||
id: '/initiatives/',
|
||||
path: '/initiatives/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const InitiativesIdRoute = InitiativesIdRouteImport.update({
|
||||
id: '/initiatives/$id',
|
||||
path: '/initiatives/$id',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/inbox': typeof InboxRoute
|
||||
'/initiatives/$id': typeof InitiativesIdRoute
|
||||
'/initiatives/': typeof InitiativesIndexRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/inbox': typeof InboxRoute
|
||||
'/initiatives/$id': typeof InitiativesIdRoute
|
||||
'/initiatives': typeof InitiativesIndexRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/inbox': typeof InboxRoute
|
||||
'/initiatives/$id': typeof InitiativesIdRoute
|
||||
'/initiatives/': typeof InitiativesIndexRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/inbox' | '/initiatives/$id' | '/initiatives/'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/inbox' | '/initiatives/$id' | '/initiatives'
|
||||
id: '__root__' | '/' | '/inbox' | '/initiatives/$id' | '/initiatives/'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
InboxRoute: typeof InboxRoute
|
||||
InitiativesIdRoute: typeof InitiativesIdRoute
|
||||
InitiativesIndexRoute: typeof InitiativesIndexRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/inbox': {
|
||||
id: '/inbox'
|
||||
path: '/inbox'
|
||||
fullPath: '/inbox'
|
||||
preLoaderRoute: typeof InboxRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/initiatives/': {
|
||||
id: '/initiatives/'
|
||||
path: '/initiatives'
|
||||
fullPath: '/initiatives/'
|
||||
preLoaderRoute: typeof InitiativesIndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/initiatives/$id': {
|
||||
id: '/initiatives/$id'
|
||||
path: '/initiatives/$id'
|
||||
fullPath: '/initiatives/$id'
|
||||
preLoaderRoute: typeof InitiativesIdRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
InboxRoute: InboxRoute,
|
||||
InitiativesIdRoute: InitiativesIdRoute,
|
||||
InitiativesIndexRoute: InitiativesIndexRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
10
packages/web/src/router.tsx
Normal file
10
packages/web/src/router.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createRouter } from '@tanstack/react-router'
|
||||
import { routeTree } from './routeTree.gen'
|
||||
|
||||
export const router = createRouter({ routeTree })
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
}
|
||||
}
|
||||
16
packages/web/src/routes/__root.tsx
Normal file
16
packages/web/src/routes/__root.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router'
|
||||
import { AppLayout } from '../layouts/AppLayout'
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<AppLayout>
|
||||
<Outlet />
|
||||
</AppLayout>
|
||||
),
|
||||
notFoundComponent: () => (
|
||||
<div className="p-8 text-center">
|
||||
<h1 className="text-2xl font-bold">Page not found</h1>
|
||||
<p className="text-muted-foreground mt-2">The page you are looking for does not exist.</p>
|
||||
</div>
|
||||
),
|
||||
})
|
||||
14
packages/web/src/routes/inbox.tsx
Normal file
14
packages/web/src/routes/inbox.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/inbox')({
|
||||
component: InboxPage,
|
||||
})
|
||||
|
||||
function InboxPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Agent Inbox</h1>
|
||||
<p className="text-muted-foreground mt-1">Content coming in Phase 19</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
7
packages/web/src/routes/index.tsx
Normal file
7
packages/web/src/routes/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({ to: '/initiatives' })
|
||||
},
|
||||
})
|
||||
16
packages/web/src/routes/initiatives/$id.tsx
Normal file
16
packages/web/src/routes/initiatives/$id.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/initiatives/$id')({
|
||||
component: InitiativeDetailPage,
|
||||
})
|
||||
|
||||
function InitiativeDetailPage() {
|
||||
const { id } = Route.useParams()
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Initiative Detail</h1>
|
||||
<p className="text-muted-foreground mt-2">Initiative ID: {id}</p>
|
||||
<p className="text-muted-foreground mt-1">Content coming in Phase 18</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
19
packages/web/src/routes/initiatives/index.tsx
Normal file
19
packages/web/src/routes/initiatives/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { trpc } from '../../lib/trpc'
|
||||
|
||||
export const Route = createFileRoute('/initiatives/')({
|
||||
component: DashboardPage,
|
||||
})
|
||||
|
||||
function DashboardPage() {
|
||||
const health = trpc.health.useQuery()
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Initiative Dashboard</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
Server: {health.data?.status ?? 'connecting...'}
|
||||
</p>
|
||||
<p className="text-muted-foreground mt-1">Content coming in Phase 17</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user