Adds unit 1 functional design and answered nfr requirements questions and plan
This commit is contained in:
+9
-9
@@ -10,7 +10,7 @@
|
|||||||
- Provide `ApiClient` and `AuthContext` foundations aligned with cookie-based auth (httpOnly refresh cookie, access token in memory)
|
- Provide `ApiClient` and `AuthContext` foundations aligned with cookie-based auth (httpOnly refresh cookie, access token in memory)
|
||||||
|
|
||||||
## Plan Checklist
|
## Plan Checklist
|
||||||
- [ ] Confirm scaffold strategy and tools via questions below
|
- [x] Confirm scaffold strategy and tools via questions below
|
||||||
- [ ] Generate folder structure (`src/api`, `src/auth`, `src/components`, `src/routes`)
|
- [ ] Generate folder structure (`src/api`, `src/auth`, `src/components`, `src/routes`)
|
||||||
- [ ] Install and configure dependencies (TanStack Router, @tanstack/react-query, Tailwind v4, shadcn/ui)
|
- [ ] Install and configure dependencies (TanStack Router, @tanstack/react-query, Tailwind v4, shadcn/ui)
|
||||||
- [ ] Configure Tailwind theme with primary color `#ac0000`
|
- [ ] Configure Tailwind theme with primary color `#ac0000`
|
||||||
@@ -30,49 +30,49 @@ B) npm
|
|||||||
C) yarn
|
C) yarn
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 2: Router choice
|
## Question 2: Router choice
|
||||||
A) TanStack Router (required by NFR-01)
|
A) TanStack Router (required by NFR-01)
|
||||||
B) React Router
|
B) React Router
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 3: UI foundation
|
## Question 3: UI foundation
|
||||||
A) Tailwind v4 + shadcn/ui (recommended)
|
A) Tailwind v4 + shadcn/ui (recommended)
|
||||||
B) Tailwind v4 only (no shadcn/ui)
|
B) Tailwind v4 only (no shadcn/ui)
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 4: Primary color integration (`#ac0000`)
|
## Question 4: Primary color integration (`#ac0000`)
|
||||||
A) Tailwind theme config (extend colors + use via utility classes)
|
A) Tailwind theme config (extend colors + use via utility classes)
|
||||||
B) CSS variables (root-level var, consumed by components)
|
B) CSS variables (root-level var, consumed by components)
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 5: HTTP client layer
|
## Question 5: HTTP client layer
|
||||||
A) Fetch wrapper (`ApiClient`) with `credentials: 'include'` + JSON helpers (recommended)
|
A) Fetch wrapper (`ApiClient`) with `credentials: 'include'` + JSON helpers (recommended)
|
||||||
B) Axios instance with `withCredentials: true`
|
B) Axios instance with `withCredentials: true`
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 6: API base URL source
|
## Question 6: API base URL source
|
||||||
A) `import.meta.env.VITE_API_BASE_URL` (from `.env`)
|
A) `import.meta.env.VITE_API_BASE_URL` (from `.env`)
|
||||||
B) Derive from `window.location.origin` + `/api`
|
B) Derive from `window.location.origin` + `/api`
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 7: Session bootstrap behavior
|
## Question 7: Session bootstrap behavior
|
||||||
A) On app mount, attempt silent refresh (uses httpOnly cookie) to hydrate `AuthContext` (recommended)
|
A) On app mount, attempt silent refresh (uses httpOnly cookie) to hydrate `AuthContext` (recommended)
|
||||||
B) Do not auto-refresh on mount; rely on login flow and 401 handling
|
B) Do not auto-refresh on mount; rely on login flow and 401 handling
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A
|
||||||
|
|
||||||
## Question 8: Linting/formatting baseline
|
## Question 8: Linting/formatting baseline
|
||||||
A) ESLint (recommended) + Prettier with sensible defaults
|
A) ESLint (recommended) + Prettier with sensible defaults
|
||||||
@@ -80,4 +80,4 @@ B) ESLint only
|
|||||||
C) None for now
|
C) None for now
|
||||||
X) Other (please describe after the [Answer]: tag below)
|
X) Other (please describe after the [Answer]: tag below)
|
||||||
|
|
||||||
[Answer]:
|
[Answer]: A, but indentation with 4 spaces
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# NFR Requirements Plan — Unit 1: Project Scaffold & Infrastructure
|
||||||
|
|
||||||
|
**Status**: Awaiting user answers to the questions below.
|
||||||
|
|
||||||
|
## Plan Checklist
|
||||||
|
- [ ] Analyze functional design artifacts (domain-entities.md, business-rules.md, business-logic-model.md)
|
||||||
|
- [ ] Identify NFR categories requiring clarification (performance, security, accessibility, maintainability, observability)
|
||||||
|
- [ ] Generate context-appropriate multiple-choice questions per question-format-guide.md
|
||||||
|
- [ ] Store this plan file
|
||||||
|
- [ ] Wait for user completion of [Answer] tags
|
||||||
|
- [ ] Validate answers, resolve any ambiguities
|
||||||
|
- [ ] Generate nfr-requirements.md and tech-stack-decisions.md artifacts
|
||||||
|
|
||||||
|
## NFR Questions
|
||||||
|
|
||||||
|
## Question 1
|
||||||
|
What is the target initial JavaScript bundle size budget (gzipped) for the production build of the CMS frontend?
|
||||||
|
|
||||||
|
A) < 200 KB (strict, requires aggressive code-splitting and tree-shaking)
|
||||||
|
B) < 300 KB (balanced for shadcn/ui + TanStack Router + React 18)
|
||||||
|
C) < 500 KB (lenient, acceptable for admin panel with multiple pages)
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: X, as small as possible to reduce initial bundle size, but no exact number.
|
||||||
|
|
||||||
|
## Question 2
|
||||||
|
What accessibility (a11y) compliance level must the admin UI components and pages meet?
|
||||||
|
|
||||||
|
A) WCAG 2.2 AA (standard for most admin interfaces; keyboard navigation + screen reader support)
|
||||||
|
B) WCAG 2.2 AAA (highest level; includes enhanced contrast, cognitive accessibility)
|
||||||
|
C) Basic keyboard navigation and focus management only (minimal viable for internal tool)
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: C
|
||||||
|
|
||||||
|
## Question 3
|
||||||
|
Should the scaffold include a client-side error monitoring / observability solution from day one?
|
||||||
|
|
||||||
|
A) No observability in scaffold (console + toast only; add later)
|
||||||
|
B) Sentry (error tracking + performance monitoring)
|
||||||
|
C) OpenTelemetry / custom lightweight solution
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: A
|
||||||
|
|
||||||
|
## Question 4
|
||||||
|
Should Vitest + React Testing Library + MSW (mock service worker) be included in the initial project scaffold for component and integration tests?
|
||||||
|
|
||||||
|
A) Yes — full test setup with example tests for login flow and auth guard
|
||||||
|
B) No — only Playwright E2E later; keep scaffold minimal
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: A
|
||||||
|
|
||||||
|
## Question 5
|
||||||
|
Is internationalization (i18n) support required in the initial scaffold, or can labels remain hard-coded for now?
|
||||||
|
|
||||||
|
A) Yes — add react-i18next + language switcher (English + Dutch)
|
||||||
|
B) No — English only for v1; Dutch translations can be added later without i18n framework
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: A
|
||||||
|
|
||||||
|
## Question 6
|
||||||
|
Should pre-commit hooks (husky + lint-staged) enforcing ESLint + Prettier + TypeScript checks be part of the scaffold?
|
||||||
|
|
||||||
|
A) Yes — enforce formatting and type safety on every commit
|
||||||
|
B) No — rely on editor integration and CI only
|
||||||
|
X) Other (please describe after [Answer]: tag below)
|
||||||
|
|
||||||
|
[Answer]: B
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
# Business Logic Model — Unit 1: Project Scaffold & Infrastructure
|
||||||
|
|
||||||
|
This model describes the core application flows for the frontend scaffold based on your choices:
|
||||||
|
- Package manager: pnpm
|
||||||
|
- Router: TanStack Router
|
||||||
|
- UI: Tailwind v4 + shadcn/ui (primary color #ac0000)
|
||||||
|
- HTTP: Fetch-based ApiClient with `credentials: 'include'`
|
||||||
|
- Session: Silent refresh on app mount (httpOnly cookie), access token in memory
|
||||||
|
- Lint/format: ESLint + Prettier (4 spaces indentation)
|
||||||
|
|
||||||
|
## 1) App Initialization + Silent Refresh (Session Bootstrap)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant A as App (main.tsx)
|
||||||
|
participant R as Router (TanStack)
|
||||||
|
participant C as AuthContext
|
||||||
|
participant H as ApiClient (fetch)
|
||||||
|
participant B as Backend API
|
||||||
|
|
||||||
|
A->>R: Create Router + Providers
|
||||||
|
A->>H: silentRefresh() with credentials: include
|
||||||
|
H->>B: POST /api/v1/auth/refresh (cookie only)
|
||||||
|
alt 200 OK
|
||||||
|
B-->>H: { accessToken, expiresAt, user }
|
||||||
|
H-->>C: setAuth(user, accessToken, expiresAt)
|
||||||
|
C-->>R: set state: authenticated
|
||||||
|
else 401 Unauthorized
|
||||||
|
B-->>H: 401
|
||||||
|
H-->>C: clearAuth()
|
||||||
|
C-->>R: set state: guest
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Text alternative: Bij het starten vraagt de app een silent refresh via cookie; bij succes wordt de gebruiker/auth-state gezet, anders blijft de app in guest-modus.
|
||||||
|
|
||||||
|
## 2) Protected Request Flow with 401 Intercept + Retry
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
UI[UI Component<br/>useAuth/useQuery] -->|call ApiClient| API[ApiClient fetch<br/>Authorization: Bearer]
|
||||||
|
API -->|200 OK| OK[Resolve Promise]
|
||||||
|
API -->|401| RFR[Try Refresh via Cookie]
|
||||||
|
RFR -->|200 OK| TOK[Update in-memory token]
|
||||||
|
TOK --> RETRY[Retry original request]
|
||||||
|
RFR -->|401| OUT[Clear auth + Redirect /login]
|
||||||
|
|
||||||
|
classDef ui fill:#c6f6d5,stroke:#22543d,stroke-width:2px,color:#22543d;
|
||||||
|
classDef infra fill:#cbd5e0,stroke:#1a202c,stroke-width:2px,color:#1a202c;
|
||||||
|
classDef ok fill:#9ae6b4,stroke:#22543d,stroke-width:2px,color:#22543d;
|
||||||
|
classDef warn fill:#fde68a,stroke:#92400e,stroke-width:2px,color:#92400e;
|
||||||
|
classDef err fill:#feb2b2,stroke:#742a2a,stroke-width:2px,color:#742a2a;
|
||||||
|
|
||||||
|
class UI ui;
|
||||||
|
class API infra;
|
||||||
|
class OK ok;
|
||||||
|
class RFR warn;
|
||||||
|
class TOK ok;
|
||||||
|
class RETRY infra;
|
||||||
|
class OUT err;
|
||||||
|
```
|
||||||
|
|
||||||
|
Text alternative: Een beveiligd verzoek gebruikt het in-memory access token; bij 401 wordt een refresh via cookie geprobeerd en vervolgens herhaald; als dat faalt volgt een logout/redirect.
|
||||||
|
|
||||||
|
## 3) Routing Shell (Layouts + Guards)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Root[__root.tsx<br/>Providers: QueryClient + AuthContext] --> Guard[Init/Silent Refresh]
|
||||||
|
Guard -->|guest| Pub[Public routes: /login, /setup]
|
||||||
|
Guard -->|auth| Auth[_authenticated.tsx<br/>AppLayout + Sidebar]
|
||||||
|
Auth --> Dash[Dashboard]
|
||||||
|
Auth --> Users[Users]
|
||||||
|
Auth --> Profile[Profile]
|
||||||
|
|
||||||
|
|
||||||
|
classDef rootElement fill:#e9d5ff,stroke:#6b21a8,stroke-width:2px,color:#6b21a8;
|
||||||
|
classDef guard fill:#fde68a,stroke:#92400e,stroke-width:2px,color:#92400e;
|
||||||
|
classDef route fill:#c6f6d5,stroke:#22543d,stroke-width:2px,color:#22543d;
|
||||||
|
|
||||||
|
class Root rootElement;
|
||||||
|
class Guard guard;
|
||||||
|
class Pub,Auth,Dash,Users,Profile route;
|
||||||
|
```
|
||||||
|
|
||||||
|
Text alternative: De root route initialiseert providers; een guard bepaalt guest vs authenticated en leidt naar publieke of beschermde routes (AppLayout met Sidebar).
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
# Business Rules — Unit 1: Project Scaffold & Infrastructure
|
||||||
|
|
||||||
|
These rules define the expected behavior and guardrails for the frontend scaffold. They are binding for subsequent design and implementation steps.
|
||||||
|
|
||||||
|
## BR-U1-01: Session Bootstrap
|
||||||
|
- On app mount, attempt a silent refresh using the httpOnly cookie
|
||||||
|
- If successful, hydrate `AuthContext` with `{ user, accessToken, expiresAt }`
|
||||||
|
- On failure (401), ensure `guest` state
|
||||||
|
|
||||||
|
## BR-U1-02: Access Token Handling
|
||||||
|
- Store the `accessToken` only in memory (React state/context)
|
||||||
|
- Never persist it in `localStorage`, `sessionStorage`, or cookies
|
||||||
|
|
||||||
|
## BR-U1-03: HTTP Defaults
|
||||||
|
- All API calls use `fetch` with `credentials: 'include'`
|
||||||
|
- `Content-Type: application/json` when a body is present
|
||||||
|
|
||||||
|
## BR-U1-04: 401 Intercept + Retry
|
||||||
|
- On a `401 Unauthorized`, the `ApiClient` must first try `POST /api/v1/auth/refresh` (cookie-based)
|
||||||
|
- If it succeeds, update the in-memory token and retry the original request once
|
||||||
|
- If refresh fails, clear auth and redirect to `/login`
|
||||||
|
|
||||||
|
## BR-U1-05: Router & Guards
|
||||||
|
- Use TanStack Router
|
||||||
|
- Protected routes live under `_authenticated.tsx` layout
|
||||||
|
- Use a `beforeLoad` guard (or equivalent hook) to block unauthorized access and redirect to `/login`
|
||||||
|
|
||||||
|
## BR-U1-06: Public Routes
|
||||||
|
- `/login`, `/setup`, and invitation completion pages remain publicly accessible
|
||||||
|
- When authenticated users hit `/login`, redirect to the dashboard
|
||||||
|
|
||||||
|
## BR-U1-07: Theme & UI
|
||||||
|
- Configure Tailwind v4 with primary color `#ac0000`
|
||||||
|
- shadcn/ui components must use this primary color in the theme configuration
|
||||||
|
|
||||||
|
## BR-U1-08: Error Handling
|
||||||
|
- Parse API errors as RFC 9457 `ProblemDetails`
|
||||||
|
- Show a user-friendly toast/banner
|
||||||
|
- Do not display raw stack traces
|
||||||
|
|
||||||
|
## BR-U1-09: API Base URL
|
||||||
|
- Read the API base URL from `import.meta.env.VITE_API_BASE_URL`
|
||||||
|
- Provide `.env.example` and document setup in README
|
||||||
|
|
||||||
|
## BR-U1-10: Typing & Naming
|
||||||
|
- Use TypeScript types that align with backend payloads
|
||||||
|
- The user property is named `name` (not `naam`)
|
||||||
|
|
||||||
|
## BR-U1-11: Testing Readiness
|
||||||
|
- Add stable `data-testid` attributes to interactive elements (e.g., `login-form-submit-button`) to support automation
|
||||||
|
|
||||||
|
## BR-U1-12: Formatting
|
||||||
|
- Use ESLint + Prettier
|
||||||
|
- Enforce 4-space indentation (printWidth and other defaults as sensible)
|
||||||
|
|
||||||
|
## BR-U1-13: Security
|
||||||
|
- Never echo refresh tokens
|
||||||
|
- Ensure all cross-origin calls rely on CORS allowlist and credentialed requests only
|
||||||
|
|
||||||
|
## BR-U1-14: State Consistency
|
||||||
|
- When auth state changes (login, refresh, logout), notify dependents via context
|
||||||
|
- UI updates immediately without hard reloads
|
||||||
|
|
||||||
|
## BR-U1-15: Network Robustness
|
||||||
|
- The `ApiClient` must gracefully handle network timeouts and show non-blocking toasts
|
||||||
|
- Background refetches should not break the UI
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
# Domain Entities — Unit 1: Project Scaffold & Infrastructure
|
||||||
|
|
||||||
|
TypeScript interfaces to be used in the frontend scaffold. Indentation shown with 4 spaces to match ESLint/Prettier baseline.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/api/types.ts
|
||||||
|
|
||||||
|
// Roles aligned with backend authorization
|
||||||
|
export type UserRole = 'Owner' | 'Administrator' | 'User';
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
id: string; // UUID
|
||||||
|
email: string;
|
||||||
|
name: string;
|
||||||
|
role: UserRole;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthResponse {
|
||||||
|
accessToken: string; // JWT access token
|
||||||
|
expiresAt: string; // ISO timestamp
|
||||||
|
user: User;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC 9457 ProblemDetails for standardized error handling
|
||||||
|
export interface ProblemDetails {
|
||||||
|
type?: string;
|
||||||
|
title?: string;
|
||||||
|
status?: number;
|
||||||
|
detail?: string;
|
||||||
|
instance?: string;
|
||||||
|
[extension: string]: unknown; // includes traceId
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience wrapper for API results (optional usage)
|
||||||
|
export type ApiResult<T> = {
|
||||||
|
ok: true;
|
||||||
|
data: T;
|
||||||
|
} | {
|
||||||
|
ok: false;
|
||||||
|
error: ProblemDetails;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup status (used by guards during bootstrap)
|
||||||
|
export interface SetupStatus {
|
||||||
|
initialized: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- Names align with backend payloads: property is `name` (English), never `naam`.
|
||||||
|
- `accessToken` lives only in memory (AuthContext), not persisted.
|
||||||
|
- `ProblemDetails` maps 1:1 to backend RFC 9457 responses and includes `traceId` as an extension.
|
||||||
Reference in New Issue
Block a user