Adds unit 2 functional design and code generation plan and gap 4 report
This commit is contained in:
+302
-202
@@ -2,234 +2,334 @@
|
||||
|
||||
## Overview
|
||||
|
||||
Unit 2 implements five distinct business logic flows. Each is technology-agnostic; implementation details (TanStack Router APIs, React Query, etc.) are resolved in Code Generation.
|
||||
Unit 2 implements three core business logic flows:
|
||||
1. **System Initialization** — First Owner setup
|
||||
2. **User Invitation Completion** — New users complete their account
|
||||
3. **Role-Based Access Control** — Guard pages by user role
|
||||
|
||||
All flows depend on the authentication foundation (Unit 1: AuthContext, ApiClient, MSW).
|
||||
|
||||
---
|
||||
|
||||
## Flow 1: App Initialization — InitGuard
|
||||
## System Initialization Flow
|
||||
|
||||
**Trigger**: Every page load / app mount (runs in the root route)
|
||||
**Purpose**: Ensure the system is initialized before rendering any route
|
||||
### Trigger
|
||||
- User visits app when `GET /Setup/status` returns `initialized: false`
|
||||
- `InitGuard` in `__root.tsx` redirects to `/setup` (blocking all other routes)
|
||||
|
||||
### SetupPage Form Collects
|
||||
- **Name** (required string)
|
||||
- **Email** (required, valid email format)
|
||||
- **Password** (required, backend rules: min 8 chars, uppercase, digit, special char)
|
||||
- **Confirm Password** (required, must match password)
|
||||
- **Language Preference** (dropdown: English / Nederlands — determines i18n locale and stored for future use)
|
||||
|
||||
### Business Rules (Setup)
|
||||
- Password must conform to backend validation rules (enforced via Zod schema matching backend)
|
||||
- Email must be a valid email format
|
||||
- Name can be any non-empty string
|
||||
- Form is only shown when system is not initialized
|
||||
- Success redirects to `/login` (user must log in after setup to verify email/password)
|
||||
|
||||
### Endpoint Integration
|
||||
- `POST /Setup` payload: `{ name, email, password, language }` (language stored for future use)
|
||||
- Backend creates first Owner user account
|
||||
- Backend returns setup status (updated to `initialized: true`)
|
||||
|
||||
### System Initialization Flow Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([App mounts]) --> B{"Setup status<br/>already cached?"}
|
||||
B -- Yes --> E
|
||||
B -- No --> C["Fetch GET /Setup/status"]
|
||||
C --> D{"Request<br/>outcome"}
|
||||
D -- Network error --> ERR["Unable to reach server"]
|
||||
D -- Success --> E{"initialized?"}
|
||||
E -- false --> F{"On /setup<br/>already?"}
|
||||
F -- Yes --> G([Render /setup page])
|
||||
F -- No --> H([Redirect to /setup])
|
||||
E -- true --> I{"On /setup<br/>already?"}
|
||||
I -- Yes --> J([Redirect to /login])
|
||||
I -- No --> K([Continue to route])
|
||||
|
||||
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
|
||||
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
|
||||
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
|
||||
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
|
||||
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
|
||||
|
||||
class A start
|
||||
class B,D,E,F,I decision
|
||||
class C action
|
||||
class ERR error
|
||||
class G,H,J,K terminal
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant SetupPage as SetupPage<br/>(Frontend)
|
||||
participant Backend as Backend API<br/>POST /Setup
|
||||
participant LoginPage as LoginPage<br/>(Redirect)
|
||||
|
||||
User->>SetupPage: Opens /setup
|
||||
SetupPage->>SetupPage: Render form (name, email, password, language)
|
||||
User->>SetupPage: Fill form
|
||||
User->>SetupPage: Submit
|
||||
SetupPage->>Backend: POST /Setup {name, email, password, language}
|
||||
alt Setup Success
|
||||
Backend-->>SetupPage: 201 {user: Owner, status: initialized}
|
||||
SetupPage->>SetupPage: Show success message
|
||||
SetupPage->>LoginPage: Redirect to /login
|
||||
LoginPage->>User: User logs in to verify credentials
|
||||
else Setup Fails
|
||||
Backend-->>SetupPage: 400 {detail: error message}
|
||||
SetupPage->>SetupPage: Show error banner
|
||||
User->>SetupPage: Fix and retry
|
||||
end
|
||||
```
|
||||
|
||||
Text alternative: On app mount, fetch setup status (once per session). If not initialized → redirect to /setup (except when already on /setup). If initialized → allow navigation (redirect away from /setup to /login).
|
||||
|
||||
**Caching rule**: The fetch result is held in React state at root level. Once loaded, it never re-fetches within the same session (BR-U2-08).
|
||||
Text alternative: Sequence diagram showing user opening SetupPage, filling form with name/email/password/language, submitting to backend. On success: backend returns Owner user data and redirect to LoginPage. On failure: backend returns error, shown as banner, user can retry.
|
||||
|
||||
---
|
||||
|
||||
## Flow 2: Protected Route Access — ProtectedRoute
|
||||
## User Invitation Completion Flow
|
||||
|
||||
**Trigger**: User navigates to any route inside the authenticated layout
|
||||
**Purpose**: Prevent unauthenticated access to protected pages
|
||||
### Trigger
|
||||
- User clicks invitation link: `/invite/complete?token=xxx`
|
||||
- `InviteCompletePage` validates token on mount (GET request to backend)
|
||||
|
||||
### InviteCompletePage Form Collects (if token valid)
|
||||
- **Email** (read-only, shown from invitation data)
|
||||
- **Name** (required string)
|
||||
- **Password** (required, backend rules same as setup)
|
||||
- **Confirm Password** (required, must match password)
|
||||
|
||||
### Business Rules (Invitation)
|
||||
- Token validation happens on page mount with loading state
|
||||
- If token invalid/expired: show error state directly (no form)
|
||||
- If token valid: show form with email pre-filled (read-only)
|
||||
- Password must conform to backend rules
|
||||
- Name can be any non-empty string
|
||||
- Success redirects to `/login` (user logs in to verify account)
|
||||
|
||||
### Endpoint Integration
|
||||
- `GET /Invitation/validate?token=xxx` — validate token and retrieve email
|
||||
- `POST /Invitation/complete` payload: `{ token, name, password }` — complete invitation
|
||||
- Both endpoints return user data if successful
|
||||
|
||||
### User Invitation Completion Flow Diagram
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([Route navigation]) --> B{"AuthContext:<br/>user present?"}
|
||||
B -- Yes --> C([Render requested page])
|
||||
B -- No --> D{"Restoring<br/>session?"}
|
||||
D -- Yes --> E([Loading spinner])
|
||||
E --> B
|
||||
D -- No --> F["Redirect to /login<br/>with ?redirect=path"]
|
||||
|
||||
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
|
||||
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
|
||||
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
|
||||
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
|
||||
classDef loading fill:#e0e7ff,stroke:#4338ca,stroke-width:1px,color:#1a1a1a
|
||||
|
||||
class A start
|
||||
class B,D decision
|
||||
class F action
|
||||
class C terminal
|
||||
class E loading
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant InvitePage as InviteCompletePage<br/>(Frontend)
|
||||
participant ValidateAPI as Backend API<br/>GET /Invitation/validate
|
||||
participant CompleteAPI as Backend API<br/>POST /Invitation/complete
|
||||
participant LoginPage as LoginPage<br/>(Redirect)
|
||||
|
||||
User->>InvitePage: Click invitation link /invite/complete?token=xxx
|
||||
InvitePage->>InvitePage: Show loading spinner
|
||||
InvitePage->>ValidateAPI: GET /Invitation/validate?token=xxx
|
||||
alt Token Valid
|
||||
ValidateAPI-->>InvitePage: {valid: true, email: user@example.com}
|
||||
InvitePage->>InvitePage: Show form (email read-only, name, password)
|
||||
User->>InvitePage: Fill name & password
|
||||
User->>InvitePage: Submit
|
||||
InvitePage->>CompleteAPI: POST /Invitation/complete {token, name, password}
|
||||
alt Completion Success
|
||||
CompleteAPI-->>InvitePage: 201 {user: User, message: success}
|
||||
InvitePage->>InvitePage: Show success message
|
||||
InvitePage->>LoginPage: Redirect to /login
|
||||
else Completion Fails
|
||||
CompleteAPI-->>InvitePage: 400 {detail: error}
|
||||
InvitePage->>InvitePage: Show error banner
|
||||
User->>InvitePage: Retry
|
||||
end
|
||||
else Token Invalid/Expired
|
||||
ValidateAPI-->>InvitePage: {valid: false, error: Invalid token}
|
||||
InvitePage->>InvitePage: Show error state (no form)
|
||||
InvitePage->>User: Offer link to request new invitation
|
||||
end
|
||||
```
|
||||
|
||||
Text alternative: Check if user is in AuthContext. If restoring session, show spinner. If no user after restore, redirect to /login preserving the original path.
|
||||
Text alternative: Sequence diagram showing user clicking invitation link, InviteCompletePage validating token with loading state. If valid: form appears with email read-only, user fills name/password and submits. On success: redirects to login. On failure: shows error banner. If token invalid: shows error state with option to request new invitation.
|
||||
|
||||
---
|
||||
|
||||
## Flow 3: Role-Restricted Route Access — RoleGuard
|
||||
## Role-Based Access Control Flow
|
||||
|
||||
**Trigger**: Authenticated user navigates to a role-restricted route
|
||||
**Purpose**: Enforce per-route role requirements
|
||||
### Trigger
|
||||
- Authenticated user navigates to a protected route (e.g., `/users`, `/settings`)
|
||||
- `RoleGuard` checks `user.role` from AuthContext
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([Authenticated navigation]) --> B{"Route has<br/>role restriction?"}
|
||||
B -- No restriction --> C([Render page])
|
||||
B -- Owner only --> D{"user.role<br/>= Owner?"}
|
||||
B -- Owner or Admin --> E{"user.role<br/>= Owner or Admin?"}
|
||||
D -- Yes --> C
|
||||
D -- No --> F([Inline Access Denied])
|
||||
E -- Yes --> C
|
||||
E -- No --> F
|
||||
### Role Model
|
||||
- **Owner** — system administrator, can manage users, system settings, CMS
|
||||
- **Admin** — (reserved for future use) may have limited permissions
|
||||
- **User** — standard user, can only view dashboard and own profile
|
||||
|
||||
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
|
||||
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
|
||||
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
|
||||
classDef denied fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
|
||||
### Route Access Rules
|
||||
| Route | Required Role(s) | Behavior if Denied |
|
||||
|-------|------------------|-------------------|
|
||||
| `/setup` | None (public) | N/A |
|
||||
| `/login` | None (public) | N/A |
|
||||
| `/invite/complete` | None (public, token-authenticated) | N/A |
|
||||
| `/dashboard` | Any authenticated | N/A |
|
||||
| `/profile` | Any authenticated | N/A |
|
||||
| `/users` | Owner or Admin | Show inline "Access Denied" message |
|
||||
| `/settings` | Owner only | Show inline "Access Denied" message |
|
||||
| `/cms` | Owner only | Show inline "Access Denied" message |
|
||||
|
||||
class A start
|
||||
class B,D,E decision
|
||||
class C terminal
|
||||
class F denied
|
||||
```
|
||||
### Business Rules (Role Guard)
|
||||
- Redirect logic happens in TanStack Router `beforeLoad` hook (not page-level)
|
||||
- When access denied: show inline "Access Denied" message within the page component (not a separate route)
|
||||
- Toast notifications are NOT used for access denied (inline message only)
|
||||
- All routes under `_authenticated` layout require authentication (ProtectedRoute guard already enforces this)
|
||||
|
||||
Text alternative: If route has no restriction → render. If Owner-only route → check role, render page or show inline "Access Denied". If Owner/Admin route → same check.
|
||||
|
||||
**Inline Access Denied**: Rendered within the normal page shell (sidebar + layout remain visible). The message identifies the required role. No redirect occurs (BR-U2-19).
|
||||
|
||||
---
|
||||
|
||||
## Flow 4: System Setup — SetupPage
|
||||
|
||||
**Trigger**: User visits `/setup` and system is uninitialized (`initialized: false`)
|
||||
**Purpose**: Create the first Owner account
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([User opens /setup]) --> B["Render 5-field form<br/>Name · Email · Password<br/>Confirm Password · Locale"]
|
||||
B --> C{"Locale<br/>changed?"}
|
||||
C -- Yes --> D["Apply i18n.changeLanguage"]
|
||||
D --> B
|
||||
C -- No --> E["User submits form"]
|
||||
E --> F{"Zod<br/>validation"}
|
||||
F -- Invalid --> G["Show inline field errors"]
|
||||
G --> B
|
||||
F -- Valid --> H["Disable submit · loading"]
|
||||
H --> I["POST /Setup"]
|
||||
I --> J{"Response"}
|
||||
J -- 200/201 --> K["Show success message"]
|
||||
K --> L([Redirect to /login])
|
||||
J -- 409 Conflict --> M["Banner: already initialized"]
|
||||
J -- 4xx --> N["Banner: API error"]
|
||||
J -- Network error --> O["Banner: Unable to connect"]
|
||||
M --> B
|
||||
N --> B
|
||||
O --> B
|
||||
|
||||
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
|
||||
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
|
||||
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
|
||||
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
|
||||
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
|
||||
classDef success fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
|
||||
|
||||
class A start
|
||||
class C,F,J decision
|
||||
class B,D,E,H,I action
|
||||
class G,M,N,O error
|
||||
class K success
|
||||
class L terminal
|
||||
```
|
||||
|
||||
Text alternative: Render 5-field form → live locale switching → submit → Zod validation → POST /Setup → success banner + redirect to /login, or error banner on API/network failure.
|
||||
|
||||
---
|
||||
|
||||
## Flow 5: Invitation Completion — InviteCompletePage
|
||||
|
||||
**Trigger**: User clicks an invitation link (`/invite/complete?token=xxx`)
|
||||
**Purpose**: Complete account setup for an invited user
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A([User opens /invite/complete]) --> B{"token in URL?"}
|
||||
B -- No --> C(["Error: invalid link"])
|
||||
B -- Yes --> D(["Loading spinner"])
|
||||
D --> E["GET /Invitation/validate"]
|
||||
E --> F{"Validation<br/>result"}
|
||||
F -- Network error --> G(["Error: unable to connect"])
|
||||
F -- isValid=false --> H(["Error: expired / used / not found"])
|
||||
F -- isValid=true --> I["Show form<br/>email read-only · Name · Password<br/>Confirm Password"]
|
||||
I --> J["User submits form"]
|
||||
J --> K{"Zod<br/>validation"}
|
||||
K -- Invalid --> L["Inline field errors"]
|
||||
L --> I
|
||||
K -- Valid --> M(["Disable submit · loading"])
|
||||
M --> N["POST /Invitation/complete"]
|
||||
N --> O{"Response"}
|
||||
O -- Success --> P["Show success message"]
|
||||
P --> Q([Redirect to /login])
|
||||
O -- 4xx --> R["Banner: API error"]
|
||||
O -- Network error --> S["Banner: Unable to connect"]
|
||||
R --> I
|
||||
S --> I
|
||||
|
||||
classDef start fill:#c7f9e9,stroke:#065f46,stroke-width:2px,color:#1a1a1a
|
||||
classDef decision fill:#fef3c7,stroke:#b45309,stroke-width:1px,color:#1a1a1a
|
||||
classDef action fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
|
||||
classDef error fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
|
||||
classDef terminal fill:#f3e8ff,stroke:#7c3aed,stroke-width:2px,color:#1a1a1a
|
||||
classDef success fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
|
||||
classDef loading fill:#e0e7ff,stroke:#4338ca,stroke-width:1px,color:#1a1a1a
|
||||
|
||||
class A start
|
||||
class B,F,K,O decision
|
||||
class E,I,J,N action
|
||||
class C,G,H,R,S error
|
||||
class P success
|
||||
class Q terminal
|
||||
class D,M loading
|
||||
```
|
||||
|
||||
Text alternative: On mount → check token param → validate with API (loading spinner) → invalid token shows error state; valid token shows form → submit → success banner + redirect to /login, or error banner on failure.
|
||||
|
||||
---
|
||||
|
||||
## Flow Interaction Diagram
|
||||
|
||||
The five flows compose within the TanStack Router tree:
|
||||
### Role-Based Access Control Decision Flow Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
Root["__root · InitGuard"] --> PublicSetup["/setup · SetupPage"]
|
||||
Root --> PublicLogin["/login · LoginPage"]
|
||||
Root --> PublicInvite["/invite/complete · InviteCompletePage"]
|
||||
Root --> AuthLayout["_authenticated · ProtectedRoute"]
|
||||
AuthLayout --> Dashboard["/ · Dashboard"]
|
||||
AuthLayout --> Profile["/profile · ProfilePage"]
|
||||
AuthLayout --> Users["/users · UsersPage<br/>RoleGuard: Owner or Admin"]
|
||||
AuthLayout --> Settings["/settings · SettingsPage<br/>RoleGuard: Owner"]
|
||||
AuthLayout --> CMS["/cms · CmsPage<br/>RoleGuard: Owner"]
|
||||
|
||||
classDef guard fill:#fef3c7,stroke:#b45309,stroke-width:2px,color:#1a1a1a
|
||||
classDef public fill:#dbeafe,stroke:#1d4ed8,stroke-width:1px,color:#1a1a1a
|
||||
classDef protected fill:#dcfce7,stroke:#15803d,stroke-width:1px,color:#1a1a1a
|
||||
classDef restricted fill:#fee2e2,stroke:#b91c1c,stroke-width:1px,color:#1a1a1a
|
||||
|
||||
class Root guard
|
||||
class PublicSetup,PublicLogin,PublicInvite public
|
||||
class Dashboard,Profile protected
|
||||
class AuthLayout,Users,Settings,CMS restricted
|
||||
user["Authenticated User<br/>Navigates to Route"]
|
||||
route["Route Requires Role?"]
|
||||
check["RoleGuard Checks<br/>user.role from AuthContext"]
|
||||
|
||||
match["User Role Matches<br/>Required Role(s)?"]
|
||||
|
||||
allow["✓ Access Allowed<br/>Render Page"]
|
||||
deny["✗ Access Denied<br/>Show Inline Message"]
|
||||
|
||||
msg["Message: You do not have<br/>permission to access this page"]
|
||||
|
||||
user --> route
|
||||
route -->|No role required| allow
|
||||
route -->|Role required<br/>e.g., /users, /settings| check
|
||||
|
||||
check --> match
|
||||
match -->|Yes<br/>Owner or Admin| allow
|
||||
match -->|No<br/>Insufficient role| deny
|
||||
|
||||
deny --> msg
|
||||
msg --> deny
|
||||
|
||||
classDef decision fill:#2196F3,stroke:#0D47A1,color:#fff,stroke-width:2px
|
||||
classDef allowed fill:#4CAF50,stroke:#2E7D32,color:#fff,stroke-width:2px
|
||||
classDef denied fill:#F44336,stroke:#C62828,color:#fff,stroke-width:2px
|
||||
classDef message fill:#FF9800,stroke:#E65100,color:#fff,stroke-width:2px
|
||||
|
||||
class route,check,match decision
|
||||
class allow allowed
|
||||
class deny denied
|
||||
class msg message
|
||||
```
|
||||
|
||||
Text alternative: Root route runs InitGuard. Public routes (/setup, /login, /invite/complete) are accessible without authentication. The _authenticated layout wraps all protected routes and runs ProtectedRoute. Role-restricted routes (/users, /settings, /cms) additionally run RoleGuard.
|
||||
Text alternative: Decision flow diagram for role-based access control. User navigates to route. If route requires no role: access allowed. If role required: RoleGuard checks user.role from AuthContext. If role matches required roles: access allowed and render page. If insufficient role: access denied, show inline message to user.
|
||||
|
||||
---
|
||||
|
||||
## InitGuard Logic
|
||||
|
||||
### Purpose
|
||||
Ensure system initialization is complete before users access authenticated features.
|
||||
|
||||
### Implementation
|
||||
- Placed in `__root.tsx` route, runs before all routes
|
||||
- Calls `GET /Setup/status` on app mount (or when AuthContext is ready)
|
||||
- Caching strategy: **session-level cache** (once loaded, never re-fetch during the session)
|
||||
- Rationale: setup operations redirect back to `/login` anyway, which reloads the app
|
||||
- `staleTime: Infinity` (cache for entire session)
|
||||
|
||||
### Routes That Bypass InitGuard
|
||||
- `/setup` — setup page (accessible even if not initialized)
|
||||
- `/login` — public login (not checked, public route)
|
||||
- `/invite/complete` — public invitation completion (not checked, token-authenticated)
|
||||
- All other routes redirect to `/setup` if `initialized: false`
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Strategy
|
||||
|
||||
### Form Submission Errors
|
||||
- Network errors (e.g., 500, connection failure)
|
||||
- Backend validation errors (e.g., email already exists, password too weak)
|
||||
- Token errors (invalid/expired invitation token)
|
||||
|
||||
### Error Display Pattern (Combination of A + B)
|
||||
1. **Real-time inline validation** — Show errors next to fields as user types (via Zod schema)
|
||||
2. **Form-level banner after submit** — After form submission, show a dismissible error banner at the top of the form with the full error message from the backend
|
||||
- Consistent with LoginPage pattern (already implemented in Unit 1)
|
||||
- Example: "Setup failed: Email already in use"
|
||||
|
||||
### Specific Error Cases
|
||||
- **Invalid token on InviteCompletePage mount** — Show full-page error state with action (e.g., "Request a new invitation link")
|
||||
- **Backend validation errors** — Combine inline (from Zod) + banner (from API response)
|
||||
- **Network errors** — Banner only: "Network error. Please try again."
|
||||
|
||||
---
|
||||
|
||||
## Translation (i18n) Structure
|
||||
|
||||
All new pages use keys added to existing `src/i18n/locales/{en,nl}/translation.json` files.
|
||||
|
||||
### New Translation Keys (extend existing file)
|
||||
```json
|
||||
{
|
||||
"setup": {
|
||||
"title": "Initialize System",
|
||||
"nameLabel": "Name",
|
||||
"emailLabel": "Email",
|
||||
"passwordLabel": "Password",
|
||||
"confirmPasswordLabel": "Confirm Password",
|
||||
"languageLabel": "Language Preference",
|
||||
"submitButton": "Create Owner Account",
|
||||
"successMessage": "Account created. Please log in."
|
||||
},
|
||||
"inviteComplete": {
|
||||
"title": "Complete Your Account",
|
||||
"emailLabel": "Email",
|
||||
"nameLabel": "Name",
|
||||
"passwordLabel": "Password",
|
||||
"confirmPasswordLabel": "Confirm Password",
|
||||
"submitButton": "Complete Setup",
|
||||
"loadingMessage": "Validating invitation...",
|
||||
"invalidTokenMessage": "This invitation link is invalid or has expired.",
|
||||
"requestNewInvitationLink": "Request a new invitation link",
|
||||
"successMessage": "Account created. Please log in."
|
||||
},
|
||||
"errors": {
|
||||
"accessDenied": "You do not have permission to access this page.",
|
||||
"setupRequired": "System setup required. Please initialize the system first.",
|
||||
"invalidInvitationToken": "Invalid or expired invitation token."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Authentication Context Integration
|
||||
|
||||
### AuthContext Usage in Unit 2
|
||||
- `useAuth()` hook provides current `user` (for role checks) and `accessToken`
|
||||
- RoleGuard reads `user.role` to determine route access
|
||||
- SetupPage and InviteCompletePage do NOT call `AuthContext.login()` on success (user must log in manually)
|
||||
- Login operations still go through LoginPage → `AuthContext.login()` (existing Unit 1 flow)
|
||||
|
||||
---
|
||||
|
||||
## MSW Mock Handlers
|
||||
|
||||
### New Mock Handlers for Unit 2
|
||||
|
||||
**Setup Handlers** (`src/mocks/setup/`)
|
||||
```javascript
|
||||
// POST /Setup — create first Owner account
|
||||
// Request: { name, email, password, language }
|
||||
// Response: { status: 201, message: "System initialized", user: {...} }
|
||||
|
||||
// GET /Setup/status — check initialization status
|
||||
// Response: { initialized: true/false, created_at: ISO timestamp }
|
||||
```
|
||||
|
||||
**Invitation Handlers** (`src/mocks/invitation/`)
|
||||
```javascript
|
||||
// GET /Invitation/validate?token=xxx — validate invitation token
|
||||
// Response: { valid: true, email: "user@example.com" } or { valid: false, error: "Invalid token" }
|
||||
|
||||
// POST /Invitation/complete — complete invitation
|
||||
// Request: { token, name, password }
|
||||
// Response: { status: 201, user: { id, name, email, role }, message: "Account created" }
|
||||
```
|
||||
|
||||
All handlers align with the real backend API (no extra `/me` endpoint; user data comes from setup/invitation responses).
|
||||
|
||||
---
|
||||
|
||||
## Password Validation Schema
|
||||
|
||||
**Location**: `src/lib/schemas/auth.ts`
|
||||
|
||||
The schema is shared by:
|
||||
- LoginPage (Unit 1 — already exists, password only)
|
||||
- SetupPage (Unit 2 — new, password + confirm)
|
||||
- InviteCompletePage (Unit 2 — new, password + confirm)
|
||||
|
||||
Backend rules:
|
||||
- Minimum 8 characters
|
||||
- At least 1 uppercase letter
|
||||
- At least 1 digit
|
||||
- At least 1 special character (!@#$%^&*()-_=+[]{}|;:,.<>?)
|
||||
|
||||
Zod schema validates on both fields + cross-field `confirm password` match.
|
||||
|
||||
Reference in New Issue
Block a user