Files
cli-e2e-mqo775am/apps/kanban/src/routes/__root.tsx
2026-06-21 21:47:10 +02:00

112 lines
3.5 KiB
TypeScript

import { createRootRoute, HeadContent, Link, Outlet, Scripts } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { defaultLocale, localeDir } from '../i18n/config'
import { classifyHealth, sortCards, type Card } from '../lib/kanban'
import { formatApiError, makeApi } from '../lib/api'
import '../i18n/config'
import '../index.css'
export interface BoardData {
cards: Card[]
error: string | null
}
// Root route renders the full HTML document for SSR (owns <html lang dir> so the
// tree mirrors for RTL locales) and the shared board chrome. Its loader fetches
// the cards once on the server; child routes read this same data via
// RootRoute.useLoaderData(), and mutations call router.invalidate() to re-run it.
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'Kanban Board' },
],
}),
loader: async (): Promise<BoardData> => {
try {
const { cards } = await makeApi().invoke('listCards', {})
return { cards: sortCards(cards as Card[]), error: null }
} catch (thrown: unknown) {
let message: string
try {
message = await formatApiError(thrown)
} catch {
message = 'Request failed'
}
return { cards: [], error: message }
}
},
component: RootDocument,
})
function RootDocument() {
const locale = defaultLocale
const { t } = useTranslation()
const { cards, error } = Route.useLoaderData()
const health = classifyHealth(cards)
return (
<html lang={locale} dir={localeDir(locale)}>
<head>
<HeadContent />
</head>
<body>
<div className="app-shell">
<header className="hero">
<div className="hero-copy">
<div className="eyebrow">{t('shell.eyebrow')}</div>
<h1>{t('shell.title')}</h1>
<p>{t('shell.subtitle')}</p>
</div>
<div className="hero-status">
<div className="hero-stat">
<span>{t('shell.stats.boardHealth')}</span>
<strong data-tone={health}>{t(`health.${health}`)}</strong>
</div>
<div className="hero-stat">
<span>{t('shell.stats.cardsTracked')}</span>
<strong>{cards.length}</strong>
</div>
<div className="hero-stat">
<span>{t('shell.stats.runtime')}</span>
<strong>{error ? t('shell.runtimeDegraded') : t('shell.runtimeConnected')}</strong>
</div>
</div>
</header>
<nav className="top-nav">
<Link
to="/"
className="nav-link"
activeOptions={{ exact: true }}
activeProps={{ className: 'nav-link is-active' }}
>
{t('shell.nav.overview')}
</Link>
<Link
to="/board"
className="nav-link"
activeProps={{ className: 'nav-link is-active' }}
>
{t('shell.nav.board')}
</Link>
<Link
to="/automations"
className="nav-link"
activeProps={{ className: 'nav-link is-active' }}
>
{t('shell.nav.automations')}
</Link>
</nav>
<main className="page-content">
<Outlet />
</main>
</div>
<Scripts />
</body>
</html>
)
}