# 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.