From 31cc45d79bacc644b313385d1de3e236c2dabfa3 Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Mon, 22 Jun 2026 11:44:24 +0200 Subject: [PATCH] docs(unit-3): Functional design for Layout & Navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Role-filtered sidebar (Owner/Admin/User visibility rules BR-U3-01–06) - Topbar removed; UserMenu + LanguageSwitcher move to sidebar footer - Mobile slide-over sidebar with hamburger button - Theme toggle (light/dark) with localStorage persistence and no-flash init - New components: MobileBar, SidebarOverlay, ThemeToggle, useTheme - Unit 2 marked complete in aidlc-state Co-Authored-By: Claude Haiku 4.5 --- .../features/cms-frontend/aidlc-state.md | 4 +- .../plans/unit-3-functional-design-plan.md | 37 +++++ .../functional-design/business-logic-model.md | 56 +++++++ .../functional-design/business-rules.md | 65 ++++++++ .../functional-design/domain-entities.md | 37 +++++ .../functional-design/frontend-components.md | 152 ++++++++++++++++++ 6 files changed, 349 insertions(+), 2 deletions(-) create mode 100644 aidlc-docs/features/cms-frontend/construction/plans/unit-3-functional-design-plan.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-logic-model.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-rules.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/domain-entities.md create mode 100644 aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md diff --git a/aidlc-docs/features/cms-frontend/aidlc-state.md b/aidlc-docs/features/cms-frontend/aidlc-state.md index 1159ebe..11d0a7d 100644 --- a/aidlc-docs/features/cms-frontend/aidlc-state.md +++ b/aidlc-docs/features/cms-frontend/aidlc-state.md @@ -5,7 +5,7 @@ - **Feature Slug**: cms-frontend - **Project Type**: Brownfield - **Start Date**: 2026-06-16T20:27:00Z -- **Current Stage**: CONSTRUCTION - Unit 2: Code Generation — Part 2 Complete (awaiting README + verification) +- **Current Stage**: CONSTRUCTION - Unit 3: Functional Design - **Branch**: unknown ## Workspace State @@ -48,7 +48,7 @@ ### 🟢 CONSTRUCTION PHASE (per unit) - [x] Unit 0 — Backend Prerequisites (CORS + httpOnly cookie) — COMPLETED 2026-06-19T11:45:00Z - [x] Unit 1 — Project Scaffold & Infrastructure — COMPLETED 2026-06-20T17:30:00Z -- [x] Unit 2 — Authentication Pages — Code Generation complete 2026-06-22 (86% tests pass, build ✅) +- [x] Unit 2 — Authentication Pages — COMPLETED 2026-06-22 (29/29 tests pass, build ✅, runtime bugs fixed) - [ ] Unit 3 — Layout & Navigation - [ ] Unit 4 — Dashboard - [ ] Unit 5 — User Management diff --git a/aidlc-docs/features/cms-frontend/construction/plans/unit-3-functional-design-plan.md b/aidlc-docs/features/cms-frontend/construction/plans/unit-3-functional-design-plan.md new file mode 100644 index 0000000..9694bf0 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/plans/unit-3-functional-design-plan.md @@ -0,0 +1,37 @@ +# Functional Design Plan — Unit 3: Layout & Navigation + +**Status**: ✅ Complete + +## Unit Context +- **Unit**: Unit 3 — Layout & Navigation +- **Type**: Frontend (React/TypeScript with TanStack Router) +- **Depends on**: Unit 2 (AuthContext, RoleGuard, useAuth) +- **Stories Covered**: US-08 (layout shell), US-18 (role-filtered sidebar), US-19 (theme toggle) + +## Design Decisions + +### Q1: Topbar +Remove the Topbar entirely on desktop. A slim mobile-only bar replaces it. +**Decision**: Remove — all chrome moves into the sidebar. + +### Q2: Mobile navigation +Use a slide-over sidebar overlay triggered by a hamburger button in the mobile bar. +**Decision**: Sidebar overlay (not bottom nav, not icon-only collapse). + +### Q3: Theme toggle placement +Sidebar footer (bottom of sidebar), visible on all screen sizes. +**Decision**: Sidebar footer. + +### Q4: Theme initialisation (no flash) +Blocking inline script in `index.html` applies theme class before React hydrates. +**Decision**: Inline script in `index.html`. + +### Q5: Language switcher placement +Move from Topbar to sidebar footer alongside theme toggle and UserMenu. +**Decision**: Sidebar footer. + +## Artefacts Produced +- `unit-3/functional-design/business-rules.md` — BR-U3-01 through BR-U3-15 +- `unit-3/functional-design/domain-entities.md` — NavItem, Role, Theme, SupportedLanguage +- `unit-3/functional-design/business-logic-model.md` — component hierarchy, theme init, nav filtering, mobile state +- `unit-3/functional-design/frontend-components.md` — all modified/new/removed components with testIds diff --git a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-logic-model.md b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-logic-model.md new file mode 100644 index 0000000..d17a5d3 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-logic-model.md @@ -0,0 +1,56 @@ +# Business Logic Model — Unit 3: Layout & Navigation + +## Component Hierarchy + +``` +AppLayout +├── Sidebar (desktop: always visible) +│ ├── Logo / AppName +│ ├── NavList (role-filtered NavItems) +│ │ └── NavItem (Link with activeProps) +│ └── SidebarFooter +│ ├── LanguageSwitcher +│ ├── ThemeToggle +│ └── UserMenu (name, email, logout) +├── MobileBar (mobile only — hamburger + app name) +│ └── opens → SidebarOverlay (Sidebar rendered in overlay) +└──
+ └── +``` + +## Theme Initialisation (no flash) + +Theme is applied in a blocking inline script in `index.html` — before React hydrates — to prevent a flash of the wrong theme: + +```html + +``` + +React's `useTheme` hook reads from `localStorage` on mount and keeps the toggle in sync. + +## NavItem Filtering Logic + +```ts +const visibleItems = NAV_ITEMS.filter(item => + !item.roles || item.roles.includes(user.role) +); +``` + +`item.roles` being `undefined` means "visible to everyone". This is evaluated at render time whenever `user.role` changes. + +## Mobile Sidebar State + +Local `useState` in `AppLayout` (or `MobileBar`). The sidebar overlay uses a `` or a Tailwind-animated `translate-x` panel. Closes on: +- Backdrop click (`onBackdropClick`) +- Close button click +- TanStack Router navigation (via `useEffect` watching `location.pathname`) + +## Logout Flow (unchanged from Unit 1/2) + +`UserMenu` in sidebar footer calls `useAuth().logout()` then `navigate({ to: '/login' })`. diff --git a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-rules.md b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-rules.md new file mode 100644 index 0000000..4394ad3 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-rules.md @@ -0,0 +1,65 @@ +# Business Rules — Unit 3: Layout & Navigation + +## Navigation Visibility Rules (BR-U3-01 through BR-U3-06) + +| Route / Item | Owner | Administrator | User | Rule ID | +|---|---|---|---|---| +| Dashboard (`/dashboard`) | ✅ visible | ✅ visible | ✅ visible | BR-U3-01 | +| User Management (`/users`) | ✅ visible | ✅ visible | ❌ hidden | BR-U3-02 | +| System Settings (`/settings`) | ✅ visible | ❌ hidden | ❌ hidden | BR-U3-03 | +| CMS Management (`/cms`) | ✅ visible | ❌ hidden | ❌ hidden | BR-U3-04 | +| Profile (`/profile`) | ✅ visible | ✅ visible | ✅ visible | BR-U3-05 | +| Logout | ✅ visible | ✅ visible | ✅ visible | BR-U3-06 | + +Sidebar items are filtered client-side based on `user.role` from `AuthContext`. The backend is the authoritative enforcement point; sidebar filtering is defence-in-depth UX only. + +--- + +## Active Route Highlighting (BR-U3-07) + +- The sidebar highlights the nav item whose `to` path matches the current route (TanStack Router `activeProps`). +- Exact match for leaf routes (`/dashboard`, `/profile`); prefix match for section roots (`/users`, `/cms`, `/settings`). + +--- + +## Responsive Behaviour (BR-U3-08) + +- On screens **≥ 768px (md)**: sidebar is always visible at fixed width (256px / `w-64`). +- On screens **< 768px**: sidebar is hidden by default; a hamburger button in a slim top bar opens it as a slide-over overlay. +- The mobile overlay closes when: the user taps outside it, taps the close button, or navigates to a new route. + +--- + +## Theme Toggle (BR-U3-09 through BR-U3-12) + +| Rule | Description | +|---|---| +| BR-U3-09 | Toggle switches between `light` and `dark` class on `` immediately | +| BR-U3-10 | Chosen theme is persisted in `localStorage` under key `cms-theme` | +| BR-U3-11 | On app load, persisted preference is applied before first render (no flash of unstyled content) | +| BR-U3-12 | If no preference is stored, OS preference (`prefers-color-scheme`) is used as default | + +Only `cms-theme` is stored in localStorage — no auth data. + +--- + +## Language Switcher (BR-U3-13) + +- The language switcher (`LanguageSwitcher`) is moved from the Topbar into the sidebar footer. +- Behaviour (NL/EN toggle, i18next `changeLanguage`) is unchanged from Unit 1. + +--- + +## Topbar Removal (BR-U3-14) + +- The `Topbar` component is removed from `AppLayout`. +- A slim mobile-only bar replaces it (hamburger button + app name) — only rendered on `< md` screens. +- All functionality previously in Topbar (`LanguageSwitcher`, `UserMenu`) moves into the sidebar footer section. + +--- + +## Layout Shell (BR-U3-15) + +- `AppLayout` renders: `` + `
` (full remaining width/height). +- No persistent top bar on desktop — the sidebar is the only chrome. +- `
` has padding (`p-6`) and fills remaining viewport height. diff --git a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/domain-entities.md b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/domain-entities.md new file mode 100644 index 0000000..85a8a60 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/domain-entities.md @@ -0,0 +1,37 @@ +# Domain Entities — Unit 3: Layout & Navigation + +## NavItem + +Represents a single entry in the sidebar navigation. Defined as a constant array in `Sidebar.tsx`. + +| Field | Type | Description | +|---|---|---| +| `to` | `string` | TanStack Router route path | +| `labelKey` | `string` | i18n key for the display label | +| `icon` | `LucideIcon` | Icon component from lucide-react | +| `testId` | `string` | `data-testid` value | +| `roles` | `Role[]` | Roles that can see this item (`undefined` = all roles) | + +## Role (from AuthContext) + +```ts +type Role = 'Owner' | 'Administrator' | 'User'; +``` + +Sourced from `useAuth().user.role`. Used by sidebar to filter `NavItem[]`. + +## Theme + +```ts +type Theme = 'light' | 'dark'; +``` + +Persisted in `localStorage` under key `cms-theme`. Applied as class `light` or `dark` on ``. + +## SupportedLanguage (existing, Unit 1) + +```ts +type SupportedLanguage = 'en' | 'nl'; +``` + +No changes from Unit 1 — `changeLanguage` from `@/i18n/config` is reused. diff --git a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md new file mode 100644 index 0000000..b4c4534 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md @@ -0,0 +1,152 @@ +# Frontend Components — Unit 3: Layout & Navigation + +## Modified Components + +### `AppLayout.tsx` (modify existing) +**Path**: `frontend/src/components/layout/AppLayout.tsx` + +Remove `Topbar` import and render. Add `MobileBar`. Layout becomes: +```tsx +
+ {/* desktop only */} + {/* mobile only */} +
+ +
+
+``` + +Mobile open/close state lives here (or in `MobileBar`). + +--- + +### `Sidebar.tsx` (modify existing) +**Path**: `frontend/src/components/layout/Sidebar.tsx` + +Changes: +- Add `roles?: Role[]` to `NavItem` interface +- Filter `NAV_ITEMS` by `user.role` before rendering +- Add `SidebarFooter` section at bottom with `LanguageSwitcher`, `ThemeToggle`, `UserMenu` +- Accept optional `onClose?: () => void` prop for mobile overlay close button +- `data-testid="app-sidebar"` retained + +Nav items: +| Label key | Route | Roles | Icon | +|---|---|---|---| +| `nav.dashboard` | `/dashboard` | all | `LayoutDashboard` | +| `nav.users` | `/users` | Owner, Administrator | `Users` | +| `nav.settings` | `/settings` | Owner | `Settings` | +| `nav.cms` | `/cms` | Owner | `FileText` | +| `nav.profile` | `/profile` | all | `User` | + +--- + +## New Components + +### `MobileBar.tsx` +**Path**: `frontend/src/components/layout/MobileBar.tsx` + +Slim bar, visible only on `< md`. Contains hamburger button (`Menu` icon) and app name. +```tsx +
+ + {t('common.appName')} +
+``` + +Props: `onMenuOpen: () => void` + +--- + +### `SidebarOverlay.tsx` +**Path**: `frontend/src/components/layout/SidebarOverlay.tsx` + +Wraps `` in a backdrop overlay for mobile: +```tsx +
+
+
+ +
+
+``` + +Only rendered when mobile menu is open. + +--- + +### `ThemeToggle.tsx` +**Path**: `frontend/src/components/layout/ThemeToggle.tsx` + +Button that reads/writes `cms-theme` in localStorage and toggles `dark` class on ``. + +```tsx + +``` + +Uses `useTheme()` hook (see below). + +--- + +### `useTheme.ts` +**Path**: `frontend/src/hooks/useTheme.ts` + +```ts +function useTheme(): { theme: Theme; toggleTheme: () => void } +``` + +- Reads initial value from `localStorage['cms-theme']` or `prefers-color-scheme` +- On toggle: flips theme, writes to localStorage, applies class to `document.documentElement` + +--- + +## Removed Components + +### `Topbar.tsx` +Deleted. Functionality moved to Sidebar footer (`LanguageSwitcher`, `UserMenu`). +`data-testid="app-topbar"` removed from the DOM. + +--- + +## i18n Keys (additions to `translation.json`) + +```json +{ + "nav": { + "settings": "Instellingen" + }, + "theme": { + "switchToLight": "Overschakelen naar licht thema", + "switchToDark": "Overschakelen naar donker thema" + } +} +``` + +(English equivalents added to `en/translation.json`) + +--- + +## Test IDs Summary + +| Element | `data-testid` | +|---|---| +| Sidebar (desktop) | `app-sidebar` | +| Mobile bar | `app-mobile-bar` | +| Hamburger button | `mobile-menu-button` | +| Sidebar backdrop | `sidebar-backdrop` | +| Theme toggle button | `theme-toggle` | +| Nav: dashboard | `nav-dashboard` | +| Nav: users | `nav-users` | +| Nav: settings | `nav-settings` | +| Nav: cms | `nav-cms` | +| Nav: profile | `nav-profile` | +| Main content area | `app-main` |