Files
solelog/docs/superpowers/specs/2026-06-17-phase-3a-admin-panel-design.md
2026-06-17 18:46:11 +02:00

5.8 KiB

Phase 3a — Admin Panel (MVP) Design

  • Created: 2026-06-17
  • Status: Approved (brainstorming) — ready for implementation plan
  • Phase: 3a (first of two slices of roadmap Phase 3; see docs/roadmap.md §9)
  • Tracker: Plane (workspace solelog, project SoleLog)

Goal

A working apps/admin desktop web app where an admin logs in, watches who is working live, and manages handelingen (activities). Everything consumes existing backend endpoints plus one tiny backend touch (role on /api/me). Reports/export, user management, and manual session entry/edit are explicitly deferred to Phase 3b.

Done when: an admin can sign in to the admin app, see who is working right now (auto- refreshing), and add/edit/delete activities. A worker who signs in with valid credentials is rejected with "Geen toegang".

Scope decisions (confirmed during brainstorming, 2026-06-17)

  1. Slice → MVP-first, two cycles. 3a = shell + admin login + live view + activity management (existing endpoints). 3b = reports/export + user management + manual entry/edit.
  2. Auth → reuse the worker's bearer-token flow (POST /api/auth/sign-in/email → capture set-auth-token → localStorage → Authorization: Bearer). Gate admin access by reading role from /api/me; non-admins are signed out with "Geen toegang".
  3. Layout → fixed left sidebar + content area (scales as 3b adds sections).
  4. Live refresh → react-query refetchInterval: 5000 (5s); elapsed time ticks client-side every second.
  5. Live view is read-only in 3a — no stop/fix button (admin-stopping another worker's session needs a new backend endpoint → 3b).

Architecture

apps/admin/  (new Vite + React 18 + TS SPA, dev port 5174)
  src/
    lib/        api.ts (apiFetch + signIn), auth-storage.ts   ← copied from worker
    auth/       AuthContext.tsx  (signIn + admin-role gate via /api/me)
    api/        admin-sessions.ts (useActiveSessions), activities.ts (CRUD hooks)
    screens/    Login.tsx, Live.tsx, Activities.tsx
    components/ Sidebar.tsx
    lib/        elapsed.ts (HH:MM:SS formatter, ported from worker stopwatch)
    App.tsx, main.tsx, styles.css

The admin app is a client only — it talks to the existing backend over HTTP with a bearer token. No DB access. It mirrors apps/worker's toolchain and conventions exactly so the build can copy proven patterns.

Backend change (the only one in 3a)

  • packages/shared/src/index.ts: add role: Role to PublicUser (so MeResponse.user carries it).
  • apps/api/src/routes/me.ts: include role in the response (read from the session user, default 'worker'). The worker app ignores the extra field — no worker change needed.

Components

  • lib/api.ts / lib/auth-storage.ts — copied verbatim from worker; bearer token in localStorage, apiFetch<T> adds the Authorization header.
  • auth/AuthContext.tsxsignIn(email,password) calls the worker signIn, then fetches /api/me; if role !== 'admin' it clears the token and throws so Login shows "Geen toegang — alleen beheerders." isAuthed reflects a present token; an admin check on mount (re-fetch /api/me) guards against a stale worker token.
  • screens/Login.tsx — Dutch email+password form (no self-signup), error line.
  • components/Sidebar.tsx — app name + signed-in email + logout (⏻); nav items Live and Handelingen; a muted/disabled "binnenkort" group hints 3b (Rapporten / Gebruikers / Handmatig).
  • screens/Live.tsx — consumes GET /api/admin/sessions/active (useActiveSessions, refetchInterval: 5000). One card per session: worker name, activity name, insole-type pill, pair count, ticking elapsed timer (from start_time). Empty state "Niemand is nu aan het werk."; header "Actief nu (N)".
  • screens/Activities.tsx — port of the legacy worker Settings.tsx (git decb158): add form (name + insole-type toggles), list with inline edit, delete-with-confirm — desktop-styled. Uses api/activities.ts hooks against /api/activities.

Data flow

  1. Admin enters credentials → signIn → token stored → /api/me confirms role==='admin'.
  2. Live screen polls /api/admin/sessions/active every 5s; each card computes elapsed from start_time with a 1s client tick.
  3. Activity CRUD hits /api/activities (GET open to any authed user; POST/PUT/DELETE are admin-gated server-side — the admin bearer satisfies them).

Error handling

  • Login: 401/no-token → "Inloggen mislukt"; authenticated non-admin → "Geen toegang — alleen beheerders" (token cleared).
  • Live/Activities: react-query error → inline "Kon gegevens niet laden." with the data hidden; loading → "Laden…".
  • Delete activity: window.confirm warns that the activity's sessions are also deleted (matches backend cascade in activities.ts).

Testing

Mirror the worker's vitest + testing-library setup, mocking apiFetch:

  • Backend: /api/me includes role for a worker and an admin (extend me.test.ts).
  • Auth/login: admin signs in; worker credentials are rejected with "Geen toegang".
  • Shell: sidebar renders nav; logout clears the token.
  • Live: renders a card per active session (name/activity/type); empty state when none.
  • Activities: add/edit/delete invoke the correct endpoints (mocked).

Out of scope (→ Phase 3b)

  • All-users filtered CSV export (current /api/export is self-scoped).
  • User management via better-auth /api/auth/admin/*.
  • Manual session entry/edit + admin stop/fix of another worker's session (needs new backend endpoints).

Build approach

spec → writing-plansone Workflow that executes the plan task-by-task (TDD, commit per task, final lint/typecheck/test/build + live verify), matching the Phase 2 pattern and keeping this session's context clean.