# Business Logic Model — Unit 2: Authentication Pages
## 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.
---
## Flow 1: App Initialization — InitGuard
**Trigger**: Every page load / app mount (runs in the root route)
**Purpose**: Ensure the system is initialized before rendering any route
```mermaid
flowchart TD
A([App mounts]) --> B{"Setup status
already cached?"}
B -- Yes --> E
B -- No --> C["Fetch GET /Setup/status"]
C --> D{"Request
outcome"}
D -- Network error --> ERR["Unable to reach server"]
D -- Success --> E{"initialized?"}
E -- false --> F{"On /setup
already?"}
F -- Yes --> G([Render /setup page])
F -- No --> H([Redirect to /setup])
E -- true --> I{"On /setup
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
```
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).
---
## Flow 2: Protected Route Access — ProtectedRoute
**Trigger**: User navigates to any route inside the authenticated layout
**Purpose**: Prevent unauthenticated access to protected pages
```mermaid
flowchart TD
A([Route navigation]) --> B{"AuthContext:
user present?"}
B -- Yes --> C([Render requested page])
B -- No --> D{"Restoring
session?"}
D -- Yes --> E([Loading spinner])
E --> B
D -- No --> F["Redirect to /login
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
```
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.
---
## Flow 3: Role-Restricted Route Access — RoleGuard
**Trigger**: Authenticated user navigates to a role-restricted route
**Purpose**: Enforce per-route role requirements
```mermaid
flowchart TD
A([Authenticated navigation]) --> B{"Route has
role restriction?"}
B -- No restriction --> C([Render page])
B -- Owner only --> D{"user.role
= Owner?"}
B -- Owner or Admin --> E{"user.role
= Owner or Admin?"}
D -- Yes --> C
D -- No --> F([Inline Access Denied])
E -- Yes --> C
E -- No --> F
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
class A start
class B,D,E decision
class C terminal
class F denied
```
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
Name · Email · Password
Confirm Password · Locale"]
B --> C{"Locale
changed?"}
C -- Yes --> D["Apply i18n.changeLanguage"]
D --> B
C -- No --> E["User submits form"]
E --> F{"Zod
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
result"}
F -- Network error --> G(["Error: unable to connect"])
F -- isValid=false --> H(["Error: expired / used / not found"])
F -- isValid=true --> I["Show form
email read-only · Name · Password
Confirm Password"]
I --> J["User submits form"]
J --> K{"Zod
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:
```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
RoleGuard: Owner or Admin"]
AuthLayout --> Settings["/settings · SettingsPage
RoleGuard: Owner"]
AuthLayout --> CMS["/cms · CmsPage
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
```
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.