Files
home-automation/frontend/src/App.tsx
T

119 lines
3.9 KiB
TypeScript
Raw Normal View History

/**
* App — top-level provider stack and route tree.
*
* Provider order (outermost first):
* MantineProvider → QueryClientProvider → BrowserRouter → SessionProvider → routes
*
* Route tree:
* /login → LoginPage (public, T07 will build the real form)
* / → ProtectedRoute → AppLayout → HomePage (T09)
* /config → ProtectedRoute → AppLayout → ConfigPage (T08)
*
* AppLayout renders a minimal shell with a gear-icon nav entry for /config (§5#10).
* T07T10 slot their real pages in without touching the provider/router setup.
*/
import { MantineProvider } from '@mantine/core'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { BrowserRouter, Routes, Route, Link, Outlet } from 'react-router-dom'
// Mantine requires its CSS to be imported once.
import '@mantine/core/styles.css'
import { SessionProvider } from './auth/SessionProvider'
import { ProtectedRoute } from './auth/ProtectedRoute'
import { LoginPage } from './pages/LoginPage'
import { HomePage } from './pages/HomePage'
import { ConfigPage } from './pages/ConfigPage'
// ---------------------------------------------------------------------------
// TanStack Query client (singleton, created outside render to avoid re-creation)
// ---------------------------------------------------------------------------
const queryClient = new QueryClient({
defaultOptions: {
queries: {
// Don't retry on 4xx — we handle 401 in the middleware
retry: (failureCount, error) => {
if (error instanceof Error && 'status' in error) {
const status = (error as unknown as { status: number }).status
if (status >= 400 && status < 500) return false
}
return failureCount < 2
},
},
},
})
// ---------------------------------------------------------------------------
// App shell layout (used by all protected pages)
// ---------------------------------------------------------------------------
function AppLayout() {
return (
<div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
{/* Minimal top nav — T07T10 can enhance this with Mantine AppShell */}
<nav
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0.5rem 1rem',
borderBottom: '1px solid #eee',
}}
>
<Link to="/" style={{ fontWeight: 600, textDecoration: 'none' }}>
Home Automation
</Link>
{/* Gear icon nav slot — links to config page (§5#10) */}
<Link
to="/config"
aria-label="Configuration"
style={{ fontSize: '1.25rem', textDecoration: 'none' }}
title="Configuration"
>
</Link>
</nav>
{/* Page content */}
<main style={{ flex: 1 }}>
<Outlet />
</main>
</div>
)
}
// ---------------------------------------------------------------------------
// Root app
// ---------------------------------------------------------------------------
export default function App() {
return (
<MantineProvider>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<SessionProvider>
<Routes>
{/* Public routes */}
<Route path="/login" element={<LoginPage />} />
{/* Protected routes — all nested under AppLayout */}
<Route
element={
<ProtectedRoute>
<AppLayout />
</ProtectedRoute>
}
>
<Route index element={<HomePage />} />
<Route path="/config" element={<ConfigPage />} />
</Route>
</Routes>
</SessionProvider>
</BrowserRouter>
</QueryClientProvider>
</MantineProvider>
)
}