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 index 9694bf0..487dda4 100644 --- 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 @@ -1,6 +1,6 @@ # Functional Design Plan — Unit 3: Layout & Navigation -**Status**: ✅ Complete +**Status**: 📋 Awaiting answers ## Unit Context - **Unit**: Unit 3 — Layout & Navigation @@ -8,30 +8,67 @@ - **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. +Please answer the following questions by filling in the letter after each `[Answer]:` tag. -### 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). +## Question 1: Topbar +The current Topbar contains only the LanguageSwitcher and UserMenu. -### Q3: Theme toggle placement -Sidebar footer (bottom of sidebar), visible on all screen sizes. -**Decision**: Sidebar footer. +A) Remove the Topbar entirely — move LanguageSwitcher and UserMenu to the sidebar footer (recommended — sidebar becomes the only chrome, full width for page content) +B) Keep the Topbar — move LanguageSwitcher and UserMenu to the sidebar footer, leave Topbar as an empty placeholder for future use +C) Keep the Topbar with its current content, only add the same items also to the sidebar -### Q4: Theme initialisation (no flash) -Blocking inline script in `index.html` applies theme class before React hydrates. -**Decision**: Inline script in `index.html`. +[Answer]: -### 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 +## Question 2: Mobile navigation +On small screens (< 768px) the sidebar is currently hidden. How should navigation work on mobile? + +A) Slide-over overlay — hamburger button in a slim top bar opens the full sidebar as an overlay (recommended) +B) Bottom navigation bar — fixed bar at the bottom of the screen with icons only +C) Icon-only sidebar — sidebar collapses to icon-only width on mobile, expands on hover/click + +[Answer]: + +--- + +## Question 3: Theme toggle (dark/light mode) +US-19 requires a theme toggle. Where should it live? + +A) Sidebar footer — next to LanguageSwitcher and UserMenu (recommended — keeps all UI controls in one place) +B) Top of the sidebar — visible without scrolling +C) Only accessible from the Profile page (not in the sidebar) + +[Answer]: + +--- + +## Question 4: Theme — no-flash initialisation +To avoid a flash of the wrong theme on page load, the theme needs to be applied before React hydrates. + +A) Inline script in `index.html` — runs synchronously before React loads (recommended — standard approach, zero dependencies) +B) CSS-only — use `prefers-color-scheme` media query only, no localStorage persistence +C) Skip no-flash for now — accept a brief flash on load + +[Answer]: + +--- + +## Question 5: Role-filtered sidebar +The sidebar should only show nav items the current user is allowed to access (US-18). Items per role: + +| Item | Owner | Admin | User | +|---|---|---|---| +| Dashboard | ✅ | ✅ | ✅ | +| User Management | ✅ | ✅ | ❌ | +| System Settings | ✅ | ❌ | ❌ | +| CMS Management | ✅ | ❌ | ❌ | +| Profile | ✅ | ✅ | ✅ | + +A) Filter sidebar items client-side based on `user.role` from AuthContext (recommended — clean UX; backend remains authoritative for access control) +B) Show all items to all roles, grey out inaccessible ones +C) Show all items, let the route guard handle the redirect on click + +[Answer]: 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 deleted file mode 100644 index d17a5d3..0000000 --- a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-logic-model.md +++ /dev/null @@ -1,56 +0,0 @@ -# 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 deleted file mode 100644 index 4394ad3..0000000 --- a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/business-rules.md +++ /dev/null @@ -1,65 +0,0 @@ -# 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 deleted file mode 100644 index 85a8a60..0000000 --- a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/domain-entities.md +++ /dev/null @@ -1,37 +0,0 @@ -# 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 deleted file mode 100644 index b4c4534..0000000 --- a/aidlc-docs/features/cms-frontend/construction/unit-3/functional-design/frontend-components.md +++ /dev/null @@ -1,152 +0,0 @@ -# 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` |