frontend-ui.md 32 KB

Frontend UI: App Shell, Routing, Auth/RBAC, Explorer, Search, Date Range Filter, Recent Search History, Navigation Polish, Profile Password Change, User Management, Role Refinement, Must-Change-Password Gate, and User Management UX Hardening (RHL-019 / RHL-020 / RHL-021 / RHL-022 / RHL-023 / RHL-024 / RHL-025 / RHL-037 / RHL-032 / RHL-009 / RHL-012 / RHL-041 / RHL-044 / RHL-042 / RHL-043)

This document describes the frontend routing scaffold, the application shell layout, and the core UI modules for the RHL Lieferscheine app.

Timeline:

  • RHL-019: Public vs protected route scaffold + AppShell + placeholder pages.
  • RHL-020: Real login flow + session handling and redirects.
  • RHL-021: UI-side RBAC guard + consistent Forbidden / NotFound UX.
  • RHL-022: Explorer v2 (Year → Month → Day → Files) + shadcn Breadcrumbs with dropdowns.
  • RHL-023: Explorer file action “Open PDF” using the binary PDF endpoint (opens in a new tab).
  • RHL-024: Search UI v1 (URL-driven q, scopes, cursor pagination, open PDF + jump to day).
  • RHL-025: Search date range filter (from/to) with a calendar popover, presets, local validation, and URL sync.
  • RHL-042: Search history dropdown (recent queries, per-user localStorage, LRU behavior, and first-page-only restore links).
  • RHL-037: Search scope UX improvements (TopNav deep-link branch switching + Single combobox + Multi selection UX + deterministic URL state).
  • RHL-032: Navigation UX polish (TopNav branding, theme toggle, user menu, tooltips, session indicator without content flicker, favicon, active states, and debounced loading UI).
  • RHL-009: Profile password change UI (Change Password form + toasts).
  • RHL-041: Role refinement (superadmin) + capability separation (branch access vs user management).
  • RHL-012: User management UI (list/create/update/delete users) + guarded admin endpoints.
  • RHL-044: Must-change-password enforcement (central protected-route gate, /profile redirect, and post-change resume flow).
  • RHL-043: User management UX hardening + layout cleanup:
    • safer delete flow (typed username confirmation),
    • branch input hardening (NL + numeric draft) + branch existence validation with fail-open note,
    • loaded-count + users sorting toolbar,
    • temporary-password controls (reset/show/copy + tooltips) in table and edit dialog,
    • sticky icon-only action column polish,
    • AppShell cleanup (remove sidebar placeholder, desktop width to 75%).

Language policy

  • Developer coordination can be German.
  • Code, comments, tests, and documentation are English.
  • All user-facing UI strings are German (labels, button text, alerts, hints).

1. Scope

1.1 Implemented (as of RHL-012 + RHL-041 + RHL-037 + RHL-025 + RHL-042 + RHL-032 + RHL-009 + RHL-044 + RHL-043)

  • Public /login route with a functional login form (shadcn/ui primitives).

  • Protected application shell for all authenticated routes:

    • Top navigation (branding, quick navigation, actions)
    • Centered main content container
    • Main content area

Width policy:

  • mobile/tablet: full width
  • desktop (lg+): centered w-3/4 content for a wider but bounded workspace

  • Session guard for protected routes:

    • Session identity is checked via GET /api/auth/me.
    • When unauthenticated: redirect to /login?reason=expired&next=<original-url>.
  • In-shell auth gating (UX improvement):

    • Auth loading/error/redirect states render inside the AppShell main content.
    • AppShell (TopNav + main content frame) remains stable; no “blank spinner screens”.
  • Logout:

    • Logout action calls GET /api/auth/logout and redirects to /login?reason=logged-out.
  • User dropdown menu (RHL-032):

    • Profil / (optional) Benutzerverwaltung / Support / Abmelden
  • Profile password change (RHL-009):

    • Route: /profile (protected)
    • Profile shows read-only account/session metadata (role, branch, email).
    • Password change form calls apiClient.changePassword({ currentPassword, newPassword }).
    • Uses inline validation for actionable form errors.
    • Uses toast notifications (Sonner) for success/error feedback.
  • Must-change-password enforcement (RHL-044):

    • Session identity (GET /api/auth/me) includes mustChangePassword.
    • AuthGate centrally enforces:
    • if mustChangePassword=true, redirect any protected non-profile route to /profile.
    • keep users on /profile until password is changed.
    • Redirect state is preserved with:
    • /profile?mustChangePasswordGate=1&next=<safe-internal-url>
    • After successful password change:
    • backend clears mustChangePassword and refreshes the session cookie.
    • frontend revalidates auth state and automatically resumes next when available.
    • /profile shows:
    • required-action alert (red) while mustChangePassword=true
    • success alert (green) after successful password change once the gate is lifted.
  • Global toast notifications (Sonner):

    • Sonner Toaster is mounted once in the root layout.
    • UI code uses a small wrapper (lib/frontend/ui/toast.js) to keep copy and behavior consistent.
  • Role model (RHL-041):

    • Backend roles: branch | admin | superadmin | dev.
    • UI treats branch access and user management as separate capabilities.
    • UI introduces a pure role helper:
    • lib/frontend/auth/roles.js
      • isAdminLike(role)admin | superadmin | dev
      • canManageUsers(role)superadmin | dev
  • User management UI (RHL-012 + RHL-043 hardening):

    • Route: /admin/users (protected)
    • Permission gating:
    • allowed: superadmin, dev
    • forbidden: admin, branch

    • Features:

    • list users with filters (username/email search, role, branchId)

    • loaded-count indicator (X Benutzer geladen) in the users toolbar

    • sort toolbar (Standard, Rolle (Rechte), Niederlassung (NL))

    • create user dialog (initial password + must-change-password flag on create)

    • create/edit branch input hardening (NL prefix + numeric input draft)

    • create/edit branch existence checks against /api/branches with fail-open note

    • update user (role/branchId consistency enforced)

    • delete user with typed-username confirmation gate

    • temporary password controls (reset, reveal/hide, copy) in table + edit dialog

    • sticky right action column with icon-only actions (edit/delete)

    • cursor-based pagination (“Mehr laden”), consistent with selected sort mode

  • UI RBAC (branch-level):

    • BranchGuard prevents branch users from accessing other branches’ URLs.
    • Admin-like users (admin/superadmin/dev) can access multiple branches.
    • Admin-like branch existence validation uses GET /api/branches.
    • Fail-open policy on validation failures (do not lock the UI on temporary API errors).
  • Route param validation (syntactic):

    • branch: NL + digits (syntactic validity; existence is validated by BranchGuard for admin-like)
    • year: YYYY
    • month: 01–12
    • day: 01–31
    • Invalid params trigger notFound() early in layouts.
  • Explorer v2 (Branch → Year → Month → Day → Files):

    • /:branch → years
    • /:branch/:year → months
    • /:branch/:year/:month → days
    • /:branch/:year/:month/:day → files
  • Breadcrumb navigation:

    • shadcn/ui Breadcrumb + dropdowns for year/month/day when options are available.
    • Dropdown options are derived from real API results (only show segments that exist).
  • Consistent states across Explorer levels:

    • Loading states (Skeleton)
    • Empty states
    • Error states with retry
    • FS_NOT_FOUND mapped to an Explorer “path no longer exists” card
  • Explorer leaf action: Open PDF (RHL-023)

    • The file list on /:branch/:year/:month/:day provides an “Öffnen” action.
    • Clicking “Öffnen” opens the selected PDF in a new browser tab.
    • URL construction is centralized in lib/frontend/explorer/pdfUrl.js.
  • Search UI (RHL-024) + Scope UX improvements (RHL-037)

    • Route: /:branch/search (protected).
    • URL-driven state for shareability: q, scope, branches, limit, from, to.
    • Cursor-based pagination (nextCursor) is not stored in the URL.
  • Search date range filter (RHL-025)

    • A calendar popover allows selecting from and to.
    • Presets (“Heute”, “Letzte 7 Tage”, …) set common ranges quickly.
    • The active filter is shown as a compact chip and can be cleared.
    • The filter is validated locally (fast feedback) and is also validated by the backend.
  • Search history (recent queries, RHL-042)

    • A recent-search dropdown is available from the query box clock button.
    • The clock trigger is icon-only, keyboard accessible, and has a tooltip.
    • Entries are stored in browser localStorage per authenticated user:
    • key: rhl.searchHistory.v1.<userId>
    • max items: 10 (LRU: reused query moves to top)
    • Stored entry fields:
    • routeBranch, q, scope, branches, limit, from, to, createdAt
    • cursor is intentionally never stored.
    • Write trigger policy:
    • entries are stored only when first-page search loading succeeds.
    • Selection behavior:
    • selecting a recent item navigates to a canonical first-page search URL (including route branch switching when needed, e.g. /NL32/search -> /NL17/search).
    • Clear behavior:
    • users can clear all recent-search entries from the dropdown (Verlauf löschen).
  • TopNav / navigation polish (RHL-032)

    • Solid header (no blur/transparency) for crisp borders.
    • Branding logo (light + dark assets).
    • Icon-only theme toggle.
    • User dropdown menu (Profil / Benutzerverwaltung* / Support / Abmelden).
    • Consistent tooltips across navigation.
    • Session check indicator in TopNav (debounced) to avoid content flicker.
    • Clear active states for Explorer and Search.
    • Safe handling of invalid branch routes (admin-like): warning + one-click recovery.

Implementation note:

  • For small static brand assets (logos), Next Image optimization is disabled (unoptimized) to avoid browser-specific “pending” indicators caused by aborted /_next/image optimization requests.

  • The “Benutzerverwaltung” entry is only shown when the user has the user-management capability.


1.2 Still out of scope / planned

  • Email-based password reset/recovery:

    • Planned as a separate follow-up ticket/phase.
    • Rationale: higher security surface (token handling and non-leakage), external SMTP/IT dependencies, and separate rate limiting ticket (RHL-031) to avoid scope creep.
  • Optional Search UX improvements:

    • grouping results by date and/or branch
    • debounced “typeahead” search (current v1 is explicit submit)
    • optional date-only search mode (allow searches with from/to even when q is empty) if desired later
  • Optional Explorer improvements:

    • add a dedicated “Herunterladen” UI action (download variant)
    • optional in-app PDF viewer experience (instead of a new tab)
  • Optional user management enhancements (not implemented in RHL-043):

    • additional branch list filtering in branch pickers (create/edit)
    • one-click “copy username” helper

2. Route Groups & URL Structure

The app uses Next.js App Router Route Groups to separate public and protected UI.

2.1 Route groups

  • Public: app/(public)

    • Routes that do not show the authenticated AppShell.
    • Current route: /login
  • Protected: app/(protected)

    • Routes that render inside the AppShell.
    • Protected routes are guarded by:
    • Session check (AuthProvider)
    • Must-change-password gate (AuthGate, RHL-044)
    • UI RBAC check for branch routes (BranchGuard)

2.2 URL map

URL Purpose Notes
/login Login page Supports reason and next query params
/ Protected entry placeholder Rendered only when authenticated
/profile Profile Password change is implemented here (RHL-009)
/admin/users Benutzerverwaltung Only superadmin/dev (RHL-012)
/:branch Explorer: years Example: /NL01
/:branch/:year Explorer: months Example: /NL01/2025
/:branch/:year/:month Explorer: days Example: /NL01/2025/12
/:branch/:year/:month/:day Explorer: files Example: /NL01/2025/12/31
/:branch/search Search UI (v1) Explicit segment so search is not interpreted as :year
/forbidden Forbidden page wrapper Optional wrapper; Forbidden is typically rendered inline

Important:

  • There is no standalone /search route. Visiting /search matches /:branch with branch = "search".

3. Layouts

3.1 Root layout

File: app/layout.jsx

Responsibilities:

  • Global CSS imports (app/globals.css).
  • Theme provider setup (shadcn/ui + next-themes wrapper).
  • Base HTML/body structure.
  • Mount global UI providers:
    • theme provider
    • Sonner Toaster (global notifications)

3.2 Public layout

File: app/(public)/layout.jsx

Responsibilities:

  • Minimal centered layout for public routes.
  • Intended for /login (and potential future public routes).

3.3 Protected layout

File: app/(protected)/layout.jsx

Responsibilities:

  • Wrap all protected pages with:
    • AuthProvider (session check + redirect)
    • AppShell (stable frame)
    • AuthGate (renders auth loading/error/redirect UI inside the shell)

UX rationale:

  • Keep the AppShell frame visible while auth/session checks run.
  • Avoid full-screen “blank spinners” on slow connections.

4. App Shell & Top Navigation

4.1 AppShell framing

File: components/app-shell/AppShell.jsx

AppShell is the stable frame for all protected pages:

  • TopNav is always visible.
  • Route pages render inside the main content area (no sidebar rail).

4.1.1 Width policy

Current strategy (implemented in AppShell.jsx + TopNav.jsx):

  • keep content full-width on smaller screens,
  • switch to a centered lg:w-3/4 container on desktop for both:
    • TopNav inner row,
    • protected page content area.

This keeps header and content visually aligned and avoids horizontal “jump” between navigation and page body.

4.2 TopNav structure (RHL-032)

File: components/app-shell/TopNav.jsx

TopNav is a sticky header and is the primary navigation surface.

Layout groups (left → right):

  • Brand: logo link to / (light + dark variants).
  • Primary navigation: QuickNav (branch selector + Explorer/Search buttons).
  • Actions: theme toggle + session indicator.
  • User menu: dropdown trigger (Profil / Benutzerverwaltung / Support / Abmelden).

Design note:

  • The header is intentionally rendered as a solid background (no backdrop blur) to keep borders/buttons crisp.

4.3 Branding (logo assets)

Asset convention:

  • Store brand assets under public/brand/.
  • Use two assets when needed (light/dark) and toggle them via Tailwind dark: classes.

Implementation note:

  • For small static logo assets, Next Image optimization is disabled (unoptimized) to avoid repeated aborted /_next/image requests on some browsers.

4.4 Theme toggle

File: components/app-shell/ThemeToggleButton.jsx

  • Icon-only toggle (sun/moon) using next-themes.
  • Preference respects system theme by default.

4.5 Session indicator (no content flicker)

File: components/app-shell/SessionIndicator.jsx

Goal:

  • Avoid “session checking” cards flashing in the main content area.
  • Surface transient checks as a small indicator in the TopNav.

Policy:

  • The indicator is debounced to avoid flicker on fast connections.
  • If the session check completes before the delay, nothing is shown.

4.6 Tooltips policy (RHL-032)

  • Use shadcn/Radix tooltips for TopNav actions.
  • Prefer tooltips over native title attributes to avoid double tooltips.
  • A single TooltipProvider is mounted at TopNav scope so all triggers share the same delay configuration.

4.7 User menu

File: components/app-shell/UserStatus.jsx

Menu items (German):

  • Profil/profile (account info + password change)
  • Benutzerverwaltung/admin/users (only superadmin / dev)
  • Support → opens a mailto: link to support.
  • Abmelden → calls logout and redirects to login.

Role label mapping:

  • branch → “Niederlassung”
  • admin → “Admin”
  • superadmin → “Superadmin”
  • dev → “Entwicklung”

5. Quick navigation (quality-of-life)

File: components/app-shell/QuickNav.jsx

Purpose:

  • Provide direct links to:

    • Explorer (/:branch)
    • Search (/:branch/search)
  • For admin-like users: enable quick branch switching while preserving the current “context”.

Behavior:

  • Branch users:

    • QuickNav is effectively fixed to their branchId.
  • Admin-like users (admin/superadmin/dev):

    • Loads branches via GET /api/branches.
    • Stores the last selected branch in localStorage (rhl_last_branch) for convenience.
    • Keeps selectedBranch stable and avoids update loops (guarded initialization).

Branch switching rule (RHL-037):

  • Selecting a branch navigates to the same section while replacing the first path segment.

    • Explorer deep paths preserved:
    • /NL32/2025/12/31/NL20/2025/12/31

    • Search route preserved and shareable params preserved:

    • /NL32/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=...&to=.../NL20/search?q=x&scope=multi&branches=NL06,NL20&limit=200&from=...&to=...

Implementation notes:

  • Deep-path branch switching logic is centralized in lib/frontend/quickNav/branchSwitch.js.
  • Avoid using useSearchParams() inside QuickNav for “current query string” access.
    • Use window.location.search at click-time instead (client-only).

Invalid branch routes (admin-like) (RHL-032):

  • If the user manually navigates to a syntactically valid but non-existent branch (e.g. /NL200):
    • QuickNav shows a warning indicator.
    • The dropdown shows a warning explanation.
    • A one-click recovery item is available: “Zur letzten gültigen Niederlassung”.

6. Authentication UX (RHL-020)

6.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 protected UI
  3. If { user: null }:

    • redirect to /login?reason=expired&next=<current-url>

Notes:

  • GET /api/auth/me returns minimal identity data for the UI:

    • userId, role, branchId, email, mustChangePassword.
  • Role is one of: branch | admin | superadmin | dev.

6.2 AuthGate (in-shell gating)

File: components/auth/AuthGate.jsx

Behavior:

  • While session is loading: render a minimal in-shell state.
  • On auth errors: show an in-shell error card + retry.
  • On unauthenticated: show a short “redirecting” message while redirect happens.
  • On authenticated + mustChangePassword=true:
    • redirect to /profile unless already on /profile.
    • preserve a safe resume target via next.
  • On authenticated + mustChangePassword=false and gate marker present:
    • resume to next automatically (if present and safe).

RHL-032 note:

  • Prefer surfacing transient “session checking” feedback in the TopNav (SessionIndicator) to avoid content flicker.

6.3 Login page (reason / next)

Files:

  • app/(public)/login/page.jsx (Server Component)
  • components/auth/LoginForm.jsx (Client Component)

Flow:

  1. Login page parses query params using parseLoginParams(...).

  2. If reason is present:

    • expired → show “session expired” banner (German)
    • logged-out → show “logged out” banner (German)
  3. On submit, the form calls apiClient.login({ username, password }).

  4. On success:

    • redirect to next if present
    • otherwise redirect to /
  5. On failure:

    • show a safe, user-friendly error message (German)

Username policy:

  • Backend stores usernames in lowercase and performs normalization during login.
  • UI enforces the same policy:
    • username input is normalized to lowercase
    • autoCapitalize="none" to prevent mobile auto-caps

7. UI RBAC, Forbidden, and NotFound (RHL-021 / RHL-041)

7.1 Goals

UI-side RBAC exists for UX (backend RBAC remains authoritative):

  • Branch users must not access other branches’ URLs.
  • Admin-like users may access any existing branch.
  • Invalid route parameters (year/month/day) should surface as NotFound.

7.2 BranchGuard (UI-side RBAC)

Files:

  • components/auth/BranchGuard.jsx

Pure logic:

  • lib/frontend/rbac/branchAccess.js
  • lib/frontend/rbac/branchUiDecision.js

Responsibilities:

  • Read user and status from AuthContext.
  • Enforce branch rules:
    • role branch → allowed only when :branch === user.branchId
    • admin-like (admin/superadmin/dev) → allowed for any branch that exists

Admin-like branch existence validation:

  • BranchGuard fetches GET /api/branches and verifies the route branch exists.
  • Fail-open policy:
    • If fetching the list fails, do not block rendering.
    • Backend RBAC and subsequent API calls remain authoritative.

7.3 Param validation (year/month/day)

Files:

  • lib/frontend/params.js

Layout enforcement (server-side notFound()):

  • app/(protected)/[branch]/layout.jsx (branch syntax)
  • app/(protected)/[branch]/[year]/layout.jsx
  • app/(protected)/[branch]/[year]/[month]/layout.jsx
  • app/(protected)/[branch]/[year]/[month]/[day]/layout.jsx

8. Explorer v2 (RHL-022) + PDF Open (RHL-023)

8.1 UI goal

Provide a simple “file explorer” drill-down:

  • Year → Month → Day → Files

8.2 Explorer pages

Routes and components:

  • /:branchcomponents/explorer/levels/YearsExplorer.jsx
  • /:branch/:yearcomponents/explorer/levels/MonthsExplorer.jsx
  • /:branch/:year/:monthcomponents/explorer/levels/DaysExplorer.jsx
  • /:branch/:year/:month/:daycomponents/explorer/levels/FilesExplorer.jsx

8.3 Data fetching strategy

  • All Explorer pages are Client Components.
  • All JSON API calls go through lib/frontend/apiClient.js.
  • A small hook provides consistent query state:
    • lib/frontend/hooks/useExplorerQuery.js

8.4 Breadcrumbs (with dropdowns)

Files:

  • UI component:

    • components/explorer/breadcrumbs/ExplorerBreadcrumbs.jsx
    • components/explorer/breadcrumbs/SegmentDropdown.jsx
  • Pure helpers:

    • lib/frontend/explorer/breadcrumbDropdowns.js
    • lib/frontend/explorer/formatters.js (German month labels)
    • lib/frontend/explorer/sorters.js

8.5 Files list (leaf route) and “Open PDF”

Leaf route:

  • /:branch/:year/:month/:day

Files list behavior:

  • Uses shadcn/ui Table.
  • Shows:
    • file name
    • relative path (desktop column + mobile secondary line)

Primary file action:

  • “Öffnen” opens the PDF in a new browser tab via the binary PDF endpoint:
    • GET /api/files/:branch/:year/:month/:day/:filename

Implementation notes:

  • URL construction is centralized in:
    • lib/frontend/explorer/pdfUrl.js

9. Search UI (RHL-024 / RHL-037) + Date Range Filter (RHL-025) + Recent Search History (RHL-042)

9.1 Route and state model

Route:

  • /:branch/search

Single Source of Truth rule (RHL-037):

  • The path segment /:branch/search is the source of truth for the current branch context.
  • Single-scope search uses the route branch.
  • Shareable URLs for search do not need to carry branch= for Single.

URL-driven state policy:

  • Search state is URL-driven to support shareable links.
  • Cursor-based pagination state is not shareable and is kept in client state.

Admin-like scope availability:

  • Admin-like users (admin/superadmin/dev) can switch between:
    • Single (route branch)
    • Multi (selected branches)
    • All (global)

Branch users:

  • Are forced to Single on their own branch.

Shareable params (first page identity):

  • q (string)

  • Scope params:

    • Single: no scope param required; route branch defines the branch.
    • Multi: scope=multi&branches=NL06,NL20 (deterministic)
    • All: scope=all
  • limit:

    • allowed values: 50 | 100 | 200
    • default: 100
  • from/to (RHL-025):

    • ISO date filter in YYYY-MM-DD
    • preserved in URLs and across branch switching

9.2 Recent search history (RHL-042)

Search history is a frontend-only convenience feature built on top of the URL-driven first-page state model.

Storage model:

  • Per-user browser key:
    • rhl.searchHistory.v1.<userId>
  • Max entries:
    • 10
  • Deduplication:
    • stable identity over routeBranch, q, scope, branches, limit, from, to
    • reusing an existing search moves it to the top (LRU)

Write trigger policy:

  • Entries are persisted only when the first-page request has succeeded.
  • Keystrokes/draft updates are never persisted.

Restore behavior:

  • Restoring a recent item navigates to a canonical internal href under /:branch/search.
  • The route branch is restored from the stored entry (routeBranch) when needed.
  • The URL contains shareable first-page params only (cursor remains excluded).

UI behavior:

  • Recent history is opened via the clock icon button in the query row.
  • The trigger has a tooltip (“Letzte Suchen anzeigen”).
  • The dropdown supports keyboard navigation (Command list).
  • A global clear action is available: “Verlauf löschen”.

9.3 Debounced loading UI (RHL-032)

Goal:

  • Avoid visible “skeleton flashes” for fast requests.

Approach:

  • Loading UI is shown only after a small delay.
  • This is implemented with useDebouncedVisibility(...) and centralized timing constants.

10. Toast notifications (Sonner)

The project uses Sonner (shadcn/ui integration) for toast notifications.

  • The Toaster is mounted once in app/layout.jsx (root layout) and respects the current theme.
  • UI code should use the wrapper in lib/frontend/ui/toast.js.

11. User management UI (RHL-012 + RHL-043)

11.1 Route

  • /admin/users (protected)

11.2 Authorization

  • UI gating uses canManageUsers(role) from lib/frontend/auth/roles.js.
  • Backend is authoritative:
    • all user management APIs require requireUserManagement(session).

11.3 UI components

Files:

  • components/admin/users/AdminUsersPage.jsx (gating)
  • components/admin/users/AdminUsersClient.jsx (list + filters + actions)
  • components/admin/users/AdminUsersTableToolbar.jsx (loaded count + sort dropdown)
  • components/admin/users/UsersTable.jsx (table)
  • components/admin/users/UserTemporaryPasswordField.jsx (temporary password controls)
  • components/admin/users/DeleteUserDialog.jsx (delete safety dialog)
  • components/admin/users/BranchNumberInput.jsx (fixed NL prefix + numeric input)
  • components/admin/users/CreateUserDialog.jsx + components/admin/users/create-user/* (create flow)
  • components/admin/users/EditUserDialog.jsx + components/admin/users/edit-user/* (edit flow)

The UI uses the API client wrappers from lib/frontend/apiClient.js:

  • adminListUsers
  • adminCreateUser
  • adminUpdateUser
  • adminDeleteUser
  • adminResetUserPassword

11.4 UX hardening rules (RHL-043)

  • Delete flow requires explicit typed confirmation:

    • dialog shows the target user details,
    • user must type the username,
    • delete button stays disabled until the normalized value matches.
  • Branch-role input hardening (role=branch) in create and edit:

    • fixed prefix NL,
    • numeric input draft for the branch number,
    • normalization output examples:
    • 1 -> NL01
    • 32 -> NL32
    • 200 -> NL200
    • typing stability: drafts like 01 remain visible while typing.
  • Branch existence validation:

    • if /api/branches is available and selected branch does not exist:
    • inline error is shown,
    • submit is blocked.
    • if /api/branches cannot be loaded:
    • submit is not hard-blocked (fail-open),
    • a small informational note is shown.

11.5 Table UX details

  • Users toolbar (above table):

    • loaded count badge (X Benutzer geladen)
    • sorting dropdown:
    • Standard
    • Rolle (Rechte)
    • Niederlassung (NL)
  • Table layout:

    • fixed/sticky right Aktion column for edit/delete actions,
    • icon-only edit/delete actions to reduce horizontal space.
  • Temporary password column:

    • masked display by default (••••••),
    • controls:
    • reset temporary password,
    • reveal/hide,
    • copy to clipboard,
    • copy feedback:
    • success checkmark state after copy,
    • fallback copy path for browsers/environments where navigator.clipboard is not usable.
  • Tooltips:

    • temporary-password control buttons use tooltips for discoverability and consistent icon-only UX.

11.6 Sorting and pagination behavior

  • Sorting is applied server-side (/api/admin/users?sort=...) and remains stable across Mehr laden.
  • Cursor payload carries sort context to prevent cross-mode cursor reuse.
  • Frontend query state includes sort so changing sort resets the list cleanly.

12. Profile: Password change (RHL-009)

12.1 Route

  • /profile (protected)

12.2 Components

  • components/profile/ProfilePage.jsx

    • Shows read-only account/session info (role, branch, email).
    • Shows required-action alert when mustChangePassword=true.
    • Shows success alert after a successful forced password change.
  • components/profile/ChangePasswordCard.jsx

    • Calls apiClient.changePassword({ currentPassword, newPassword }).
    • Maps password policy errors into user-friendly German hints.
    • Triggers auth revalidation after success (retry() from AuthContext).

13. UI primitives (shadcn/ui)

The Explorer + auth + search UI uses shadcn/ui primitives from components/ui/*.


14. File Naming Convention (.js vs .jsx)

To keep the project consistent:

  • Use .jsx for files that contain JSX.
  • Use .js for non-JSX files.

15. Tests

15.1 Unit tests

Role helper tests (RHL-041):

  • lib/frontend/auth/roles.test.js

Existing tests remain as documented in the project.

15.2 Running tests

From the repo root:

npx vitest run

Optional build check:

npm run build

16. Manual verification checklist

16.1 Local (Docker)

  • Login and verify TopNav role label per account.
  • Branch switching for admin-like users.
  • Explorer drill-down.
  • Search scopes (admin-like) + Search restrictions (branch).
  • Search history:
    • execute several searches with different scopes/date filters/limits
    • open recent-search dropdown via clock button
    • restore recent entries across same and different branches
    • clear history via Verlauf löschen
  • Open PDFs.
  • User management UI (superadmin/dev):
    • list/create/update/delete
    • delete typed confirmation
    • branch input (NL + number) and branch existence warning/blocking
    • temporary password reset/reveal/copy controls
    • sorting (Standard, Rolle (Rechte), Niederlassung (NL))
    • sticky action column behavior on horizontal overflow

16.2 Server

  • Repeat the local checks on the real server.
  • Validate RBAC behavior:
    • branch user → forbidden on foreign branches
    • admin-like → notfound on non-existent branches
    • admin/branch → forbidden on /admin/users
  • Validate must-change-password behavior:
    • new user with mustChangePassword=true is forced to /profile
    • gate lifts immediately after password change (no logout/login required)
    • user resumes the previously blocked protected route when next is available

17. Planned follow-ups

  • Optional user-management polish:
    • additional branch-list filtering in the create/edit branch picker UX
    • optional one-click username copy helper