From 5f275bfcc73460343f9d6e7d43beba96dfa9160b Mon Sep 17 00:00:00 2001 From: Bas van Rossem Date: Thu, 19 Feb 2026 16:20:22 +0100 Subject: [PATCH] feat(web): set up React app shell with routing and layout Adds react-router-dom with routes for ship list, dashboard, and rules pages. Includes TopBar with navigation and mobile-first dark theme CSS. Co-Authored-By: Claude Opus 4.6 --- web/src/App.tsx | 18 ++++- web/src/components/layout/PageContainer.tsx | 5 ++ web/src/components/layout/TopBar.tsx | 32 ++++++++ web/src/index.css | 89 +++++++++++++++++++++ web/src/pages/RulesPage.tsx | 13 +++ web/src/pages/ShipDashboardPage.tsx | 16 ++++ web/src/pages/ShipListPage.tsx | 13 +++ 7 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 web/src/components/layout/PageContainer.tsx create mode 100644 web/src/components/layout/TopBar.tsx create mode 100644 web/src/pages/RulesPage.tsx create mode 100644 web/src/pages/ShipDashboardPage.tsx create mode 100644 web/src/pages/ShipListPage.tsx diff --git a/web/src/App.tsx b/web/src/App.tsx index a426bb8..b0415b3 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,9 +1,19 @@ +import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import ShipListPage from './pages/ShipListPage'; +import ShipDashboardPage from './pages/ShipDashboardPage'; +import RulesPage from './pages/RulesPage'; + function App() { return ( -
-

Spelljammer Ship Tracker

-

App is running. Routes coming soon.

-
+ +
+ + } /> + } /> + } /> + +
+
); } diff --git a/web/src/components/layout/PageContainer.tsx b/web/src/components/layout/PageContainer.tsx new file mode 100644 index 0000000..13b08c3 --- /dev/null +++ b/web/src/components/layout/PageContainer.tsx @@ -0,0 +1,5 @@ +import type { ReactNode } from 'react'; + +export default function PageContainer({ children }: { children: ReactNode }) { + return
{children}
; +} diff --git a/web/src/components/layout/TopBar.tsx b/web/src/components/layout/TopBar.tsx new file mode 100644 index 0000000..8cc7284 --- /dev/null +++ b/web/src/components/layout/TopBar.tsx @@ -0,0 +1,32 @@ +import { useNavigate, useLocation } from 'react-router-dom'; + +export default function TopBar({ title }: { title?: string }) { + const navigate = useNavigate(); + const location = useLocation(); + const isHome = location.pathname === '/'; + + return ( +
+
+ {!isHome && ( + + )} +

{title || 'Spelljammer'}

+
+ +
+ ); +} diff --git a/web/src/index.css b/web/src/index.css index a2b7754..67071c0 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -88,3 +88,92 @@ a { a:hover { text-decoration: underline; } + +/* Top Bar */ +.top-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--spacing-sm) 0; + margin-bottom: var(--spacing-md); + border-bottom: 1px solid var(--color-border); +} + +.top-bar-left { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.top-bar-title { + font-size: 1.2rem; + font-weight: 600; +} + +.top-bar-back { + background: none; + color: var(--color-primary); + font-size: 1.2rem; + padding: var(--spacing-xs) var(--spacing-sm); +} + +.top-bar-right { + display: flex; + gap: var(--spacing-sm); +} + +.top-bar-link { + background: var(--color-accent); + color: var(--color-text); + font-size: 0.85rem; + padding: var(--spacing-xs) var(--spacing-sm); +} + +.top-bar-link:hover { + background: var(--color-surface-hover); +} + +/* Page Container */ +.page-container { + padding-bottom: var(--spacing-xl); +} + +/* Buttons */ +.btn-primary { + background: var(--color-primary); + color: #fff; +} + +.btn-primary:hover { + background: var(--color-primary-hover); +} + +.btn-danger { + background: var(--color-danger); + color: #fff; +} + +.btn-danger:hover { + background: #d32f2f; +} + +.btn-secondary { + background: var(--color-accent); + color: var(--color-text); +} + +.btn-secondary:hover { + background: var(--color-surface-hover); +} + +.btn-icon { + background: none; + padding: var(--spacing-xs); + font-size: 1.2rem; + line-height: 1; + color: var(--color-text-muted); +} + +.btn-icon:hover { + color: var(--color-text); +} diff --git a/web/src/pages/RulesPage.tsx b/web/src/pages/RulesPage.tsx new file mode 100644 index 0000000..1963ccd --- /dev/null +++ b/web/src/pages/RulesPage.tsx @@ -0,0 +1,13 @@ +import TopBar from '../components/layout/TopBar'; +import PageContainer from '../components/layout/PageContainer'; + +export default function RulesPage() { + return ( + <> + + +

Rules reference coming soon...

+
+ + ); +} diff --git a/web/src/pages/ShipDashboardPage.tsx b/web/src/pages/ShipDashboardPage.tsx new file mode 100644 index 0000000..0c715b6 --- /dev/null +++ b/web/src/pages/ShipDashboardPage.tsx @@ -0,0 +1,16 @@ +import { useParams } from 'react-router-dom'; +import TopBar from '../components/layout/TopBar'; +import PageContainer from '../components/layout/PageContainer'; + +export default function ShipDashboardPage() { + const { id } = useParams<{ id: string }>(); + + return ( + <> + + +

Dashboard for ship {id} coming soon...

+
+ + ); +} diff --git a/web/src/pages/ShipListPage.tsx b/web/src/pages/ShipListPage.tsx new file mode 100644 index 0000000..ee11243 --- /dev/null +++ b/web/src/pages/ShipListPage.tsx @@ -0,0 +1,13 @@ +import TopBar from '../components/layout/TopBar'; +import PageContainer from '../components/layout/PageContainer'; + +export default function ShipListPage() { + return ( + <> + + +

Ship list coming soon...

+
+ + ); +}