Просмотр исходного кода

RHL-020 refactor(docs): update frontend-ui.md to reflect login flow and session management enhancements

Code_Uwe 1 месяц назад
Родитель
Сommit
f29415e345
1 измененных файлов с 209 добавлено и 117 удалено
  1. 209 117
      Docs/frontend-ui.md

+ 209 - 117
Docs/frontend-ui.md

@@ -8,32 +8,44 @@
 
 <!-- --------------------------------------------------------------------------- -->
 
-# Frontend UI: App Shell & Routing Scaffold (RHL-019)
+# Frontend UI: App Shell, Routing, and Login Flow (RHL-019 / RHL-020)
 
 This document describes the **frontend routing scaffold** and the **application shell layout** for the RHL Lieferscheine app.
 
-Scope (RHL-019):
+It started as a pure scaffold in **RHL-019** (public vs protected routes + AppShell + placeholder pages) and was extended in **RHL-020** with a **real login flow**, a **session guard** for protected routes, and a minimal **logout + user status UX**.
 
-- Public `/login` route.
+---
+
+## 1. Scope
+
+### 1.1 Implemented (as of RHL-020)
+
+- Public `/login` route with a functional login form (shadcn/ui primitives).
 - Protected application shell for all other routes.
-- Placeholder pages for branch/year/month/day routes and branch search.
-- Minimal session-awareness placeholders (no real auth guard yet).
-- Minimal tests to validate scaffold stability.
+- Minimal session guard for the protected area:
 
-Non-goals (out of scope for RHL-019):
+  - checks session via `GET /api/auth/me`
+  - redirects to `/login?reason=expired&next=<original-url>` when unauthenticated
 
-- Real login form, auth guard, and session-based redirects.
-- Explorer navigation UI (years/months/days list).
+- Logout button wired to `GET /api/auth/logout`.
+- Minimal `UserStatus` that displays session state (role + branch).
+- Centralized helper utilities for auth redirect behavior (`reason` / `next`) and error-to-message mapping.
+
+### 1.2 Still out of scope (planned)
+
+- Full RBAC UI guard (branch users should be prevented from navigating to other branches in the UI).
+- Explorer navigation UI (years/months/days lists in sidebar).
 - Search UI and filters.
 - PDF viewer / file open.
+- HTTPS / reverse proxy (handled in a separate ticket).
 
 ---
 
-## 1. Route Groups & URL Structure
+## 2. Route Groups & URL Structure
 
 The app uses Next.js App Router **Route Groups** to separate public and protected UI.
 
-### 1.1 Route groups
+### 2.1 Route groups
 
 - **Public**: `app/(public)`
 
@@ -43,19 +55,19 @@ The app uses Next.js App Router **Route Groups** to separate public and protecte
 - **Protected**: `app/(protected)`
 
   - Routes that render inside the **AppShell**.
-  - RHL-019 intentionally does **not** enforce authentication yet.
+  - As of RHL-020, protected routes are guarded by a session check.
 
-### 1.2 URL map (scaffold)
+### 2.2 URL map
 
-| URL                          | Purpose                     | Notes                                                  |
-| ---------------------------- | --------------------------- | ------------------------------------------------------ |
-| `/login`                     | Login placeholder           | Public layout (no AppShell)                            |
-| `/`                          | Protected entry placeholder | Later redirects based on session                       |
-| `/:branch`                   | Branch placeholder          | Example: `/NL01`                                       |
-| `/:branch/:year`             | Year placeholder            | Example: `/NL01/2025`                                  |
-| `/:branch/:year/:month`      | Month placeholder           | Example: `/NL01/2025/12`                               |
-| `/:branch/:year/:month/:day` | Day placeholder             | Example: `/NL01/2025/12/31`                            |
-| `/:branch/search`            | Search placeholder          | Explicit segment so `search` is not treated as `:year` |
+| URL                          | Purpose                     | Notes                                                      |
+| ---------------------------- | --------------------------- | ---------------------------------------------------------- |
+| `/login`                     | Login page                  | Supports `reason` and `next` query params                  |
+| `/`                          | Protected entry placeholder | Only rendered when authenticated                           |
+| `/:branch`                   | Branch placeholder          | Example: `/NL01`                                           |
+| `/:branch/:year`             | Year placeholder            | Example: `/NL01/2025`                                      |
+| `/:branch/:year/:month`      | Month placeholder           | Example: `/NL01/2025/12`                                   |
+| `/:branch/:year/:month/:day` | Day placeholder             | Example: `/NL01/2025/12/31`                                |
+| `/:branch/search`            | Search placeholder          | Explicit segment so `search` is not interpreted as `:year` |
 
 Important:
 
@@ -63,9 +75,9 @@ Important:
 
 ---
 
-## 2. Layouts
+## 3. Layouts
 
-### 2.1 Root layout
+### 3.1 Root layout
 
 File: `app/layout.jsx`
 
@@ -75,7 +87,7 @@ Responsibilities:
 - Theme provider setup (shadcn/ui + next-themes wrapper).
 - Base HTML/body structure.
 
-### 2.2 Public layout
+### 3.2 Public layout
 
 File: `app/(public)/layout.jsx`
 
@@ -84,80 +96,163 @@ Responsibilities:
 - Minimal centered layout for public routes.
 - Intended for `/login` (and potential future public routes).
 
-### 2.3 Protected layout
+### 3.3 Protected layout (with session guard)
 
 File: `app/(protected)/layout.jsx`
 
 Responsibilities:
 
 - Wraps all protected pages with the **AppShell**.
-- Intentionally contains **no auth guard** in RHL-019.
+- Wraps the auth provider in a **`<Suspense>` boundary**.
+- Adds the **session guard** via `components/auth/AuthProvider.jsx`.
+
+Why the Suspense boundary is required:
+
+- The session guard uses Next.js navigation hooks like `useSearchParams()`.
+- When a route is statically prerendered during production builds, `useSearchParams()` causes a CSR bailout unless wrapped by a Suspense boundary.
+- The Suspense fallback ensures the build stays valid while the client hydrates.
 
 ---
 
-## 3. AppShell
+## 4. Authentication UX (RHL-020)
+
+### 4.1 Session check for protected routes
+
+File: `components/auth/AuthProvider.jsx`
+
+Behavior:
+
+1. On mount, call `apiClient.getMe()`.
+2. If `{ user: { ... } }`:
+
+   - set auth state to `authenticated`
+   - render the protected UI
+
+3. If `{ user: null }`:
+
+   - redirect to `/login?reason=expired&next=<current-url>`
 
-The AppShell is the stable frame for authenticated UI.
+The `next` parameter:
 
-Folder: `components/app-shell/*`
+- includes the original `pathname` and query string
+- is sanitized to avoid open redirects (only internal paths are allowed)
 
-- `AppShell.jsx`
+### 4.2 Login page (reason / next)
 
-  - Overall layout container: **TopNav** + content area.
-  - Uses `min-h-screen flex flex-col` so the content area can fill remaining height.
+Files:
 
-- `TopNav.jsx`
+- `app/(public)/login/page.jsx` (Server Component)
+- `components/auth/LoginForm.jsx` (Client Component)
 
-  - Branding.
-  - User status placeholder.
-  - Placeholder buttons for theme and logout (not wired yet).
+Flow:
 
-- `SidebarPlaceholder.jsx`
+1. Login page parses query params using `parseLoginParams(...)`.
+2. If `reason` is present:
 
-  - Reserved space for future navigation/filter UI.
-  - Later additions:
+   - `expired` → show “Session expired” banner
+   - `logged-out` → show “Logged out” banner
 
-    - Admin/dev branch selector
-    - Explorer navigation (year/month/day)
-    - Search filters and shortcuts
+3. On submit, the form calls `apiClient.login({ username, password })`.
+4. On success:
 
-- `UserStatus.jsx`
+   - redirect to `next` if present
+   - otherwise redirect to `/`
 
-  - Placeholder only.
-  - Later reads session state (via `apiClient.getMe()`).
+5. On failure:
 
-### 3.1 Responsive behavior
+   - show a safe, user-friendly error message
 
-- Sidebar is hidden on small viewports (`md:block`).
-- Main content remains usable on mobile sizes.
+Username policy:
+
+- The backend stores usernames in lowercase and performs normalization during login.
+- The UI enforces this policy as well:
+
+  - username input is normalized to lowercase
+  - `autoCapitalize="none"` to prevent mobile auto-caps
+
+### 4.3 Logout
+
+File: `components/auth/LogoutButton.jsx`
+
+Flow:
+
+1. Calls `apiClient.logout()`.
+2. Redirects to `/login?reason=logged-out`.
+
+### 4.4 User status
+
+Files:
+
+- `components/auth/authContext.jsx`
+- `components/app-shell/UserStatus.jsx`
+
+Behavior:
+
+- AuthProvider provides a minimal auth context (`status`, `user`).
+- `UserStatus` renders a short indicator:
+
+  - loading → `Loading...`
+  - authenticated → `<role> (<branchId>)` when available
+  - unauthenticated/error → fallback text
 
 ---
 
-## 4. Placeholder Pages
+## 5. Frontend helper modules
+
+### 5.1 API client
+
+File: `lib/frontend/apiClient.js`
+
+- All UI code must call the backend through this client.
+- Defaults:
 
-Placeholder pages validate:
+  - `credentials: "include"`
+  - `cache: "no-store"`
 
-- URL structure
-- Layout composition
-- Dynamic route parameter handling
+- Throws `ApiClientError` for standardized backend errors.
+- RHL-020 uses:
 
-They render via:
+  - `login({ username, password })`
+  - `logout()`
+  - `getMe()`
 
-- `components/placeholders/PlaceholderPage.jsx`
+### 5.2 Auth redirect helpers (`reason` / `next`)
 
-### 4.1 Dynamic route params (Next.js App Router)
+File: `lib/frontend/authRedirect.js`
 
-In this project setup, dynamic route `params` can behave like an async value.
+Provides:
 
-Rule of thumb:
+- `sanitizeNext(next)` to prevent open redirects.
+- `buildLoginUrl({ reason, next })`.
+- `parseLoginParams(searchParams)`.
 
-- In dynamic routes (`[branch]`, `[year]`, `[month]`, `[day]`), unwrap `params` before using properties.
+### 5.3 Auth message mapping
+
+File: `lib/frontend/authMessages.js`
+
+- Centralized mapping from error codes to user-facing strings.
+- Centralized banner copy for `reason=expired` and `reason=logged-out`.
 
 ---
 
-## 5. File Naming Convention (.js vs .jsx)
+## 6. UI primitives (shadcn/ui)
+
+The login UI uses shadcn/ui primitives from `components/ui/*`.
+
+Required components for RHL-020:
+
+- `card`
+- `input`
+- `label`
+- `alert`
+
+These are added to the repository via the shadcn CLI.
+
+---
 
-To keep the project consistent and avoid test/tooling issues:
+## 7. File Naming Convention (.js vs .jsx)
+
+To keep the project consistent and avoid tooling issues:
 
 - Use **`.jsx`** for files that contain JSX:
 
@@ -171,44 +266,23 @@ To keep the project consistent and avoid test/tooling issues:
   - `models/**`
   - tests that do not contain JSX
 
----
-
-## 6. Frontend Route Helpers
-
-File: `lib/frontend/routes.js`
-
-Purpose:
-
-- Centralize URL building so UI code does not scatter hardcoded strings.
-- Encode dynamic segments defensively.
-- Keep navigation consistent across components.
-
-Provided helpers:
+Note:
 
-- `homePath()` → `/`
-- `loginPath()` → `/login`
-- `branchPath(branch)` → `/:branch`
-- `yearPath(branch, year)` → `/:branch/:year`
-- `monthPath(branch, year, month)` → `/:branch/:year/:month`
-- `dayPath(branch, year, month, day)` → `/:branch/:year/:month/:day`
-- `searchPath(branch)` → `/:branch/search`
+- `components/auth/authContext` must be `.jsx` because it renders a JSX Provider.
 
 ---
 
-## 7. Tests
+## 8. Tests
 
-### 7.1 Unit tests
+### 8.1 Unit tests
 
-- `lib/frontend/routes.test.js`
+- `lib/frontend/routes.test.js` (route builder)
+- `lib/frontend/apiClient.test.js` (client defaults + error mapping)
+- `lib/frontend/authRedirect.test.js` (reason/next parsing + sanitization)
+- `lib/frontend/authMessages.test.js` (UI message mappings)
+- `components/app-shell/AppShell.test.js` (SSR smoke test)
 
-  - Validates URL builder behavior.
-
-- `components/app-shell/AppShell.test.js`
-
-  - Smoke test: server-renders AppShell and asserts key text is present.
-  - `next/link` is mocked to avoid Next runtime dependency.
-
-### 7.2 Running tests
+### 8.2 Running tests
 
 From the repo root:
 
@@ -216,7 +290,7 @@ From the repo root:
 npx vitest run
 ```
 
-Optional (recommended) build check:
+Optional build check:
 
 ```bash
 npm run build
@@ -224,43 +298,61 @@ npm run build
 
 ---
 
-## 8. Manual Verification Checklist
-
-### 8.1 Local (Docker)
+## 9. Manual verification checklist (RHL-020)
 
-Follow `Docs/runbook.md`.
+### 9.1 Local (Docker)
 
-Typical command:
+Start:
 
 ```bash
 docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build
 ```
 
-Verify routes:
+Verify flows in the browser:
+
+- Open a protected route while logged out (e.g. `/NL01/2025/12`)
+
+  - Expect redirect to `/login?reason=expired&next=/NL01/2025/12`
+
+- Invalid login
+
+  - Expect “Invalid username or password.”
 
-- `/login` (public layout)
-- `/` (protected app shell)
-- `/:branch` (e.g. `/NL01`)
-- `/:branch/:year/:month/:day` (placeholder)
-- `/:branch/search`
+- Valid login
 
-### 8.2 Server
+  - Expect redirect into the protected route
 
-Follow `Docs/runbook.md`.
+- Logout
 
-Verify:
+  - Expect redirect to `/login?reason=logged-out`
+
+### 9.2 Server (direct HTTP)
+
+The current server deployment is accessed via **direct HTTP**:
+
+- `http://<server-ip>:3000`
+
+Important cookie note:
+
+- Browsers reject `Secure` cookies over HTTP.
+- Therefore the server `.env.server` must set:
+
+```env
+SESSION_COOKIE_SECURE=false
+```
 
-- `curl -s http://127.0.0.1:3000/api/health`
-- Browser:
+Verify flows on the server URL:
 
-  - `http(s)://<server>:3000/login`
-  - `http(s)://<server>:3000/NL01/...` (or a real branch)
+- Unauthenticated redirect + `next`
+- Valid login sets cookie and redirects back to `next`
+- Logout clears session and shows `reason=logged-out`
 
 ---
 
-## 9. Planned Follow-ups
+## 10. Planned follow-ups
 
-- Add real **auth guard** and session-based redirects (`/` → `/login` or `/:branch`).
-- Replace placeholders with Explorer pages (years/months/days + files).
-- Add Search UI and filters.
-- Add PDF open/view experience.
+- HTTPS / reverse proxy deployment (separate ticket)
+- UI-level RBAC guards (branch users cannot navigate to other branches)
+- Replace placeholders with Explorer pages (years/months/days + files)
+- Add Search UI and filters
+- Add PDF open/view experience