Files
slp-modular-cms/aidlc-docs/features/cms-frontend/construction/unit-2/functional-design/business-rules.md
T

9.5 KiB
Raw Blame History

Business Rules — Unit 2: Authentication Pages

Setup Form Validation Rules

Field-Level Rules

Field Type Rules Error Message
Name string Required, 1255 chars "Name is required"
Email string Required, valid email format "Enter a valid email address"
Password string Zod schema matching backend (8+ chars, 1 uppercase, 1 digit, 1 special) Backend-specific error (e.g., "Password must contain uppercase")
Confirm Password string Required, must match Password field "Passwords do not match"
Language enum "en" or "nl" (dropdown, always valid)

Cross-Field Rules

  • Confirm Password must equal Password (checked on blur and form validation)

Invitation Completion Form Validation Rules

Field-Level Rules

Field Type Rules Error Message
Email string Read-only (from token validation) N/A
Name string Required, 1255 chars "Name is required"
Password string Zod schema matching backend Backend-specific error
Confirm Password string Required, must match Password field "Passwords do not match"

Token Validation Rules

  • Token comes from query param (?token=xxx)
  • Validated on page mount via GET /Invitation/validate?token=xxx
  • If valid: backend returns { valid: true, email: "..." }
  • If invalid/expired: backend returns { valid: false, error: "..." }
  • Loading state shown while validating
  • Error state shown if validation fails (no form rendered)

Initialization Status Rules

InitGuard Behavior

Scenario Action
App loads, GET /Setup/status returns { initialized: true } Allow all routes; proceed normally
App loads, GET /Setup/status returns { initialized: false } Redirect to /setup (InitGuard blocks other routes)
User navigates to /setup directly Show setup form (accessible regardless of init status)
User navigates to any other route while not initialized Redirect to /setup (InitGuard blocks)
User completes setup (POST /Setup succeeds) App redirects to /login, session resets, init status re-fetched on next app load
Network error while checking init status Treat as "not initialized" and redirect to /setup (fail-safe)

Caching Strategy

  • staleTime: Infinity (session-level cache, never re-fetch after first successful load)
  • Rationale: Setup operation ends with redirect to /login, which reloads the app
  • No manual re-fetching needed during the session

Role-Based Access Control Rules

Role Definition

  • Owner: Created during system initialization (POST /Setup)
  • Admin: Reserved for future use (not used in Unit 26)
  • User: Created via user invitations (POST /Invitation/complete)

Route Access Rules

Route Required Role(s) Behavior if Denied Guard Component
/setup None (public, no auth required) N/A None
/login None (public, no auth required) N/A None
/invite/complete None (public, token-authenticated) N/A None
/dashboard Any authenticated N/A ProtectedRoute (Unit 1)
/profile Any authenticated N/A ProtectedRoute (Unit 1)
/users Owner or Admin Inline "Access Denied" message RoleGuard (this unit)
/settings Owner only Inline "Access Denied" message RoleGuard (this unit)
/cms Owner only Inline "Access Denied" message RoleGuard (this unit)

Access Denied Behavior

  • When user lacks required role for a route:
    • DO: Show inline "Access Denied" message within the page (e.g., in a styled container)
    • DO NOT: Use toast notifications
    • DO NOT: Redirect to separate /403 route
    • DO: Make it clear the page exists but access is denied
    • Example: "You do not have permission to access this page. Contact your administrator."

Guard Implementation

  • RoleGuard implemented as TanStack Router beforeLoad hook in route definitions
  • Evaluates user.role from AuthContext at route load time
  • Returns redirect-to-self if denied (allows inline error display via page component state)
  • OR: page component checks role and displays error inline

Error Handling Rules

Form Submission Error Display

Two-part error handling (combining inline validation + banner):

Part 1: Real-Time Inline Validation

  • Zod schema validates as user types/blurs on field
  • Show error message below the field in red text
  • Example: <span class="text-red-600 text-sm">{error}</span>
  • Clears when user fixes the error

Part 2: Form-Level Banner (After Submit)

  • After form submission, if backend returns error:
    • Show dismissible banner at the top of the form with full error message
    • Banner includes close button
    • Banner persists until user dismisses or form is reset
    • Consistent with LoginPage pattern (Unit 1)
  • If network error (no response):
    • Banner: "Network error. Please try again."

Specific Error Scenarios

Scenario Banner Message Inline Errors
Email already exists on setup "Setup failed: This email is already registered" None (only from backend)
Password too weak "Setup failed: [backend message about password rules]" Pre-validated by Zod
Network timeout on setup "Network error. Please try again." None
Invalid invitation token on load Full-page error (InitGuard redirect to setup OR error page) N/A
Token expired during form fill Error shown after submit: "Invitation link expired" N/A
Invalid form data before submit Inline errors only (Zod validation) Below each field

Translation Key Rules

Locales

  • English (en) — eager load (fallback language)
  • Nederlands (nl) — lazy load (per-route chunk)

Translation Keys Scope

  • All Unit 2 keys added to existing translation.json files (not separate namespace files)
  • Organized by feature: setup, inviteComplete, errors sections
  • Used with i18next pattern: t('setup.title'), t('errors.accessDenied')

Language Preference Storage

  • SetupPage collects language preference at account creation
  • Stored in backend user profile (for future use)
  • Frontend respects this preference on login (future: read from user data)

Password Schema Rules

Location

src/lib/schemas/auth.ts — shared by LoginPage, SetupPage, InviteCompletePage

Validation Rules (Backend-Aligned)

const passwordSchema = z.string()
  .min(8, "At least 8 characters")
  .regex(/[A-Z]/, "At least one uppercase letter")
  .regex(/[0-9]/, "At least one digit")
  .regex(/[!@#$%^&*\(\)\-_=+\[\]{}|;:,.<>?]/, "At least one special character");

const confirmPasswordSchema = z.string()
  .min(1, "Confirm password is required");

const setupFormSchema = z.object({
  name: z.string().min(1).max(255),
  email: z.string().email(),
  password: passwordSchema,
  confirmPassword: confirmPasswordSchema,
  language: z.enum(['en', 'nl'])
}).refine((data) => data.password === data.confirmPassword, {
  message: "Passwords do not match",
  path: ["confirmPassword"]
});

Shared Usage

  • passwordSchema — used by LoginPage, SetupPage, InviteCompletePage
  • confirmPasswordSchema — used only when form collects both password fields (setup, invite)

MSW Mock Handler Rules

Handler Organization

  • Setup handlers: Extend existing src/mocks/setup/ → add POST /Setup handler
  • Invitation handlers: New src/mocks/invitation/ → add GET /Invitation/validate + POST /Invitation/complete handlers

Setup Handler Behaviors

GET /Setup/status

  • Always returns { initialized: true/false }
  • Default: { initialized: false } on app first load
  • After POST /Setup succeeds: automatically return { initialized: true } for subsequent requests

POST /Setup

  • Request: { name, email, password, language }
  • Validates email not already used (mock: always accepts if password valid)
  • Returns { status: 201, message: "System initialized", user: { id, name, email, role: "Owner" } }
  • Error: { status: 400, detail: "Email already in use" } (ProblemDetails format)

Invitation Handler Behaviors

GET /Invitation/validate?token=xxx

  • Mock tokens: valid-token-123, expired-token-456
  • Valid token: returns { valid: true, email: "invited@example.com" }
  • Invalid/expired: returns { valid: false, error: "Invalid or expired token" } (ProblemDetails format)

POST /Invitation/complete

  • Request: { token, name, password }
  • Validates token and password
  • Returns { status: 201, user: { id, name, email, role: "User" }, message: "Account created" }
  • Error: { status: 400, detail: "Invalid token" } (ProblemDetails format)

Consistency Rules

Form Pattern Consistency

  • All forms use react-hook-form + zod (LoginPage pattern from Unit 1)
  • Error display: inline (on blur) + banner (on submit)
  • Submission feedback: disable submit button during request
  • Success: redirect or success message

Navigation Consistency

  • After setup: redirect to /login (user must log in)
  • After invitation completion: redirect to /login (user must log in)
  • After login: redirect to /dashboard (existing pattern from Unit 1)

Component API Consistency

  • All hooks follow React conventions (use* prefix)
  • All components accept standard React props (className, etc.)
  • All pages rendered in src/pages/ directory (consistent with LoginPage)
  • All routes defined in src/router.tsx (code-based routing)