Finishes functional design for unit 0 (back-end changes before front-end work)

This commit is contained in:
2026-06-18 21:40:45 +02:00
parent 9e49489f7e
commit 53a307cdbd
14 changed files with 1065 additions and 4 deletions
@@ -0,0 +1,60 @@
# Unit of Work Dependencies — CMS Frontend
## Dependency Matrix
| Unit | Depends On | Blocks |
|------|-----------|--------|
| **Unit 0** — Backend Prerequisites | (none — standalone .NET change) | Unit 1 (CORS must work before frontend can call API) |
| **Unit 1** — Project Scaffold | Unit 0 | Units 2, 3, 4, 5, 6 |
| **Unit 2** — Auth Pages | Unit 1 (ApiClient, AuthContext) | Units 3, 4, 5, 6 (auth layer required) |
| **Unit 3** — Layout & Navigation | Unit 2 (ProtectedRoute, RoleGuard) | Units 4, 5, 6 (authenticated shell) |
| **Unit 4** — Dashboard | Unit 3 (AppLayout) | Unit 5, 6 (patterns established) |
| **Unit 5** — User Management | Unit 3 (RoleGuard), Unit 4 (patterns) | Unit 6 |
| **Unit 6** — Remaining Pages & Docs | Unit 3 (AppLayout, RoleGuard) | (none — final unit) |
## Dependency Diagram
```mermaid
graph TD
U0["Unit 0\nBackend Prerequisites"]
U1["Unit 1\nProject Scaffold"]
U2["Unit 2\nAuth Pages"]
U3["Unit 3\nLayout & Navigation"]
U4["Unit 4\nDashboard"]
U5["Unit 5\nUser Management"]
U6["Unit 6\nRemaining Pages"]
U0 -->|"CORS + cookie\nenabled"| U1
U1 -->|"ApiClient\nAuthContext"| U2
U2 -->|"ProtectedRoute\nRoleGuard\nInitGuard"| U3
U3 -->|"AppLayout\nSidebar"| U4
U3 -->|"AppLayout\nRoleGuard"| U5
U3 -->|"AppLayout\nRoleGuard"| U6
U4 -->|"patterns\nestablished"| U5
style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff
style U1 fill:#FFC107,stroke:#F57F17,color:#000
style U2 fill:#FF5722,stroke:#BF360C,color:#fff
style U3 fill:#9C27B0,stroke:#4A148C,color:#fff
style U4 fill:#2196F3,stroke:#0D47A1,color:#fff
style U5 fill:#009688,stroke:#004D40,color:#fff
style U6 fill:#607D8B,stroke:#263238,color:#fff
```
## Shared Components Across Units
| Component | Created In | Used By |
|-----------|-----------|---------|
| `ApiClient` | Unit 1 | Units 2, 4, 5, 6 |
| `AuthContext` / `useAuth` | Unit 1 | Units 2, 3, 4, 5, 6 |
| `types.ts` (TS interfaces) | Unit 1 | All units |
| `ProtectedRoute` | Unit 2 | Units 3, 4, 5, 6 (via layout route) |
| `RoleGuard` | Unit 2 | Units 3, 5, 6 |
| `InitGuard` | Unit 2 | All authenticated routes (via `__root.tsx`) |
| `AppLayout` | Unit 3 | Units 4, 5, 6 |
| `Sidebar` | Unit 3 | Units 4, 5, 6 |
| `useAvailabilityStatus` | Unit 4 | Unit 6 (`SettingsPage`) |
| `AvailabilityStatusBadge` | Unit 4 | Unit 6 (`SettingsPage`) |
| `useValidateInvitation` | Unit 5 | Unit 2 (`InviteCompletePage`) — note: hook defined in Unit 5, used in Unit 2 route |
> **Note on cross-unit hook usage**: `useValidateInvitation` and `useCompleteSetup` are logically invitation hooks (Unit 5) but are used in `InviteCompletePage` (Unit 2). In practice, these hooks can be created in Unit 2 and moved/refactored in Unit 5, or created directly in Unit 5 and `InviteCompletePage` completed then. Implementation order to follow: create stubs in Unit 2, full implementation in Unit 5.
@@ -0,0 +1,100 @@
# Unit of Work Story Map — CMS Frontend
## Story-to-Unit Mapping
All 20 user stories from `stories.md` are assigned to units. Every story is covered.
| Story | Title | Unit | Rationale |
|-------|-------|------|-----------|
| US-06 | Initialize system as first Owner | Unit 0 + Unit 2 | Backend endpoint already exists; frontend SetupPage in Unit 2 |
| US-07 | Redirect to setup when not initialized | Unit 0 + Unit 2 | Backend returns `initialized: false`; InitGuard in Unit 2 |
| US-01 | Login with email and password | Unit 2 | LoginPage + AuthContext.login() |
| US-02 | Session persistence via refresh token | Unit 2 | AuthContext restore-on-mount + httpOnly cookie (Unit 0 backend) |
| US-03 | Logout | Unit 2 | AuthContext.logout() + AuthController.Revoke() |
| US-04 | Redirect to login when not authenticated | Unit 2 | ProtectedRoute |
| US-05 | Auto token refresh on expiry | Unit 2 | ApiClient 401 interceptor → AuthContext.refresh() |
| US-13 | Complete account setup via invitation link | Unit 2 | InviteCompletePage (stubs for invitation hooks; full hooks in Unit 5) |
| US-14 | Handle expired/invalid invitation token | Unit 2 | InviteCompletePage error states |
| US-18 | Role-appropriate sidebar navigation | Unit 3 | Sidebar with role-filtered nav items |
| US-19 | Toggle dark/light theme | Unit 3 | ThemeProvider + theme toggle in Sidebar |
| US-08 | View dashboard after login | Unit 4 | DashboardPage |
| US-09 | View availability status on dashboard | Unit 4 | AvailabilityStatusBadge + useAvailabilityStatus |
| US-10 | View list of users | Unit 5 | UsersPage + useUsers |
| US-11 | Invite a new user | Unit 5 | InviteUserDialog + useInviteUser |
| US-12 | Share invite link | Unit 5 | InviteUserDialog (step 2 — share link) |
| US-15 | View own profile | Unit 6 | ProfilePage |
| US-16 | View availability status in settings | Unit 6 | SettingsPage + useAvailabilityStatus (reused from Unit 4) |
| US-17 | Access denied to System Settings for non-Owners | Unit 6 | RoleGuard on `/settings` |
| US-20 | View CMS management placeholder | Unit 6 | CmsPage (Owner only) |
---
## Coverage Verification
| Unit | Stories | Count |
|------|---------|-------|
| Unit 0 (Backend) | US-02 (partial), US-06 (partial), US-07 (partial) | 3 |
| Unit 2 (Auth Pages) | US-01, US-02, US-03, US-04, US-05, US-06, US-07, US-13, US-14 | 9 |
| Unit 3 (Layout) | US-18, US-19 | 2 |
| Unit 4 (Dashboard) | US-08, US-09 | 2 |
| Unit 5 (User Management) | US-10, US-11, US-12 | 3 |
| Unit 6 (Remaining Pages) | US-15, US-16, US-17, US-20 | 4 |
**Total: 20/20 stories assigned**
---
## Story Map Visualization
```mermaid
graph TD
subgraph U0["Unit 0 — Backend"]
s06b["US-06 partial\nsetup endpoint"]
s07b["US-07 partial\nstatus endpoint"]
s02b["US-02 partial\ncookie support"]
end
subgraph U2["Unit 2 — Auth Pages"]
s01["US-01 Login"]
s02["US-02 Session restore"]
s03["US-03 Logout"]
s04["US-04 Auth redirect"]
s05["US-05 Token refresh"]
s06["US-06 Setup page"]
s07["US-07 Init redirect"]
s13["US-13 Invite complete"]
s14["US-14 Invalid token"]
end
subgraph U3["Unit 3 — Layout"]
s18["US-18 Sidebar nav"]
s19["US-19 Theme toggle"]
end
subgraph U4["Unit 4 — Dashboard"]
s08["US-08 Dashboard"]
s09["US-09 Availability"]
end
subgraph U5["Unit 5 — User Mgmt"]
s10["US-10 User list"]
s11["US-11 Invite user"]
s12["US-12 Share link"]
end
subgraph U6["Unit 6 — Remaining"]
s15["US-15 Profile"]
s16["US-16 Settings"]
s17["US-17 Access denied"]
s20["US-20 CMS placeholder"]
end
U0 --> U2 --> U3 --> U4 --> U5 --> U6
style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff
style U2 fill:#FF5722,stroke:#BF360C,color:#fff
style U3 fill:#9C27B0,stroke:#4A148C,color:#fff
style U4 fill:#2196F3,stroke:#0D47A1,color:#fff
style U5 fill:#009688,stroke:#004D40,color:#fff
style U6 fill:#607D8B,stroke:#263238,color:#fff
```
@@ -0,0 +1,198 @@
# Units of Work — CMS Frontend
## Overview
The CMS frontend is decomposed into **7 sequential units** (Unit 06). Each unit builds on the previous; dependencies flow strictly forward. All units together produce a single deployable SPA + updated backend API.
```mermaid
graph LR
U0["Unit 0\nBackend Prerequisites"]
U1["Unit 1\nProject Scaffold"]
U2["Unit 2\nAuth Pages"]
U3["Unit 3\nLayout & Nav"]
U4["Unit 4\nDashboard"]
U5["Unit 5\nUser Management"]
U6["Unit 6\nRemaining Pages"]
U0 --> U1 --> U2 --> U3 --> U4 --> U5 --> U6
style U0 fill:#4CAF50,stroke:#2E7D32,color:#fff
style U1 fill:#FFC107,stroke:#F57F17,color:#000
style U2 fill:#FF5722,stroke:#BF360C,color:#fff
style U3 fill:#9C27B0,stroke:#4A148C,color:#fff
style U4 fill:#2196F3,stroke:#0D47A1,color:#fff
style U5 fill:#009688,stroke:#004D40,color:#fff
style U6 fill:#607D8B,stroke:#263238,color:#fff
```
---
## Unit 0 — Backend Prerequisites
**Type**: Backend (.NET) — blocking prerequisite for all frontend units
**Priority**: Must complete before Unit 1
### Deliverables
- `ServiceCollectionExtensions.cs` — add `AddCorsFrontendPolicy()` reading `Cors:AllowedOrigins[]` from config
- `Program.cs` — add `app.UseCors("FrontendPolicy")` before `UseAuthentication`
- `AuthController.cs` — update `Login`, `Refresh`, `Revoke` to use httpOnly cookie
- `RefreshTokenRequest.cs`**remove** (no backward compatibility needed; refresh now uses httpOnly cookie)
- `TokenResponse.cs` — add `Name` property (display name from `UserName` or `Email`)
- `appsettings.json` — add `Cors` section (production placeholder)
- `appsettings.Development.json` — add `Cors:AllowedOrigins: ["http://localhost:5173"]`
- Tests — update `AuthController` tests for cookie-based flow
### Key Decisions
- Cookie name: `refreshToken`, Path: `/api/v1/auth`, HttpOnly, Secure, SameSite=Strict
- CORS: `AllowCredentials()` required for cookie flow
- `name` field in `TokenResponse` maps from `user.UserName ?? user.Email`
---
## Unit 1 — Project Scaffold & Infrastructure
**Type**: Frontend — foundation for all subsequent units
**Depends on**: Unit 0 (API must be reachable with CORS)
### Deliverables
```
frontend/
├── src/
│ ├── api/
│ │ ├── client.ts # ApiClient: fetch + auth header + 401 interceptor
│ │ └── types.ts # Shared TS interfaces (User, AuthResponse, etc.)
│ ├── auth/
│ │ ├── AuthContext.tsx # AuthProvider: in-memory token + user state
│ │ └── useAuth.ts # useAuth hook
│ ├── components/
│ │ ├── layout/
│ │ │ └── ThemeProvider.tsx
│ │ └── error/
│ │ └── ErrorBoundary.tsx
│ ├── main.tsx # Entry: QueryClientProvider + AuthProvider + RouterProvider
│ └── app.tsx # App root
├── .env.example # VITE_API_BASE_URL=http://localhost:5000
├── .env # gitignored
├── vite.config.ts
├── tailwind.config.ts # Primary color #ac0000
├── tsconfig.json
└── package.json # pnpm workspace
```
### Key Decisions
- TanStack Router file-based routing initialized (empty route tree)
- shadcn/ui configured with `#ac0000` primary
- `ApiClient` created; `AuthContext` wired up; `QueryClient` configured
---
## Unit 2 — Authentication Pages
**Type**: Frontend — public + session management
**Depends on**: Unit 1 (ApiClient, AuthContext)
### Deliverables
```
frontend/src/
├── auth/
│ ├── ProtectedRoute.tsx # Redirects unauthenticated → /login
│ ├── RoleGuard.tsx # Redirects wrong role → /403
│ └── InitGuard.tsx # Redirects uninitialized → /setup
├── api/
│ └── useSetup.ts # useSetupStatus, useCreateOwner
└── routes/
├── __root.tsx # Root route: InitGuard + global ErrorBoundary
├── login.tsx # LoginPage
├── setup.tsx # SetupPage
└── invite.complete.tsx # InviteCompletePage
```
### Key Decisions
- `react-hook-form` + `zod` for all forms; password schema enforces exact backend rules
- `InitGuard` in `__root.tsx` blocks all routes until setup status confirmed
- `ProtectedRoute` uses `beforeLoad` in TanStack Router
---
## Unit 3 — Layout & Navigation
**Type**: Frontend — authenticated shell
**Depends on**: Unit 2 (ProtectedRoute, RoleGuard, AuthContext)
### Deliverables
```
frontend/src/
├── components/layout/
│ ├── AppLayout.tsx # Shell: Sidebar + <Outlet />
│ └── Sidebar.tsx # Role-filtered nav, logout, theme toggle
└── routes/
└── _authenticated.tsx # Authenticated layout route wrapping all protected pages
```
### Key Decisions
- `_authenticated.tsx` is a TanStack Router layout route — all child routes inherit `ProtectedRoute`
- Sidebar filters nav items by `user.role` from `AuthContext`
- Responsive: hamburger menu on mobile
---
## Unit 4 — Dashboard
**Type**: Frontend — first authenticated page
**Depends on**: Unit 3 (AppLayout, AuthContext)
### Deliverables
```
frontend/src/
├── api/
│ └── useAvailability.ts # useAvailabilityStatus (staleTime: 30s)
├── components/shared/
│ └── AvailabilityStatusBadge.tsx
└── routes/
└── index.tsx # DashboardPage: welcome + availability widget
```
---
## Unit 5 — User Management
**Type**: Frontend — Owner/Admin feature
**Depends on**: Unit 3 (AppLayout, RoleGuard), Unit 4 (patterns established)
### Deliverables
```
frontend/src/
├── api/
│ ├── useUsers.ts # useUsers, useInviteUser
│ └── useInvitation.ts # useValidateInvitation, useCompleteSetup
├── components/users/
│ └── InviteUserDialog.tsx # Two-step: invite form → share link
└── routes/
└── users.tsx # UsersPage: user table + invite dialog
```
---
## Unit 6 — Remaining Pages & Documentation
**Type**: Frontend + Documentation
**Depends on**: Unit 3 (AppLayout, RoleGuard)
### Deliverables
```
frontend/src/routes/
├── profile.tsx # ProfilePage: read-only user info
├── settings.tsx # SettingsPage: Owner-only, availability + placeholders
├── cms.tsx # CmsPage: Owner-only placeholder
├── 403.tsx # AccessDeniedPage
└── $404.tsx # NotFoundPage
```
**Documentation**:
- `README.md` — add **Frontend Development** section:
- Prerequisites (Node.js, pnpm)
- `pnpm install` in `frontend/`
- `.env` setup (copy `.env.example`)
- `pnpm dev` to start dev server
- `pnpm build` for production build
- Security headers note for production deployment
@@ -0,0 +1,27 @@
# Unit of Work Plan — CMS Frontend
## Context
Unit decomposition is derived directly from the Workflow Planning and Application Design stages. No additional questions are needed — all decomposition decisions were already made:
- **Decomposition approach**: Sequential units ordered by dependency (infrastructure → auth → layout → features)
- **Team alignment**: Single developer — no cross-team boundaries needed
- **Technical considerations**: All units deploy as one SPA + one backend API
- **Code organization**: Monorepo-style, `frontend/` at solution root
## Planning Checklist
- [x] Context analyzed (execution-plan.md + application-design.md)
- [x] Unit boundaries confirmed by user (via Workflow Planning approval)
- [x] Story-to-unit mapping defined
- [x] unit-of-work.md generated
- [x] unit-of-work-dependency.md generated
- [x] unit-of-work-story-map.md generated
## Unit Definitions
| Unit | Name | Scope |
|------|------|-------|
| 0 | Backend Prerequisites | .NET backend changes: CORS, httpOnly cookie, TokenResponse DTO with `name` |
| 1 | Project Scaffold & Infrastructure | Vite init, TanStack Router, shadcn/ui, Tailwind v4, ApiClient, AuthContext, ThemeProvider, ErrorBoundary, .env |
| 2 | Authentication Pages | LoginPage, SetupPage, InviteCompletePage, ProtectedRoute, RoleGuard, InitGuard |
| 3 | Layout & Navigation | AppLayout, Sidebar (role-filtered), responsive layout |
| 4 | Dashboard | DashboardPage, AvailabilityStatusBadge, useAvailabilityStatus |
| 5 | User Management | UsersPage, InviteUserDialog, useUsers, useInviteUser, useValidateInvitation, useCompleteSetup |
| 6 | Remaining Pages & Docs | ProfilePage, SettingsPage, CmsPage, NotFoundPage, AccessDeniedPage, README.md frontend section |