# PROGRESS.md

Running log of work on the admin portal. Newest context is in the Current status
section at the bottom. Every phase or significant change appends an entry here
(what changed, commit hash, verification) as part of that work.

Design spec: `docs/superpowers/specs/2026-07-13-admin-portal-design.md`.
Plan and checklist: `PLAN.md`.

## Planning and design

- Commits: `dac7d1d` (design spec plus PLAN.md), `8d6f6f4` (added the
  must_change_password flow to the spec and plan).
- Decisions: login by email only; DB-backed lockout (5 fails per email plus IP
  in 15 minutes); one-time self-disabling `/setup`; placeholder dashboard;
  profile is name and email only; deactivate-only staff (no hard delete); admin
  sets a temporary password shown once and the user must change it on first
  login.

## Phase 1: Scaffold and Tailwind pipeline

- Commit: `e7e4e47`.
- Built: folder structure per spec; `package.json` with `build:css` and
  `watch:css`; `tailwind.config.js` scanning the PHP templates; `input.css` with
  the `@tailwind` directives and a shared components layer. Tailwind v3 (the
  brief requires tailwind.config.js and `@tailwind` directives).
- Verified: `npm run build:css` produced `assets/css/dist/output.css`; a smoke
  test confirmed PHP class scanning picks up utilities, custom brand colors, and
  the @apply component classes.

## Phase 2: Helpers, layout, and components

- Commit: `a173d24`.
- Built: helpers (bootstrap, db with PDO wrappers, auth, csrf, flash,
  validation, response, password, pagination, view); layout wrapper with
  collapsible sidebar, header with user menu, and footer, plus a bare shell;
  reusable components (button, alert, form_field, table, modal, card,
  pagination); vanilla JS split one concern per file.
- Verified: a throwaway harness rendered the full layout and every component
  once with HTTP 200 and no PHP notices, then was removed. No inline CSS or JS,
  no em-dashes.

## Phase 3: Front controller, auth, guard, lockout, schema

- Commit: `2bbe049`.
- Built: `index.php` front controller and route map; central guard (CSRF on
  every POST, auth, forced-change, admin RBAC); email login with the DB-backed
  lockout (prune each attempt, clear on success, generic message so accounts
  cannot be enumerated); self-disabling `/setup`; logout; placeholder dashboard;
  `sql/schema.sql` (users and login_attempts) written but not executed.
- Verified: over HTTP with a session seeder, confirmed 404 on unknown route,
  login 200, logged-out dashboard redirect, POST without CSRF returns 419 before
  any DB call, staff to admin route 403, and the forced-change redirect.

## Phase 4: Database details and schema execution

- No code commit. The owner supplied credentials in the git-ignored
  `config/config.php` and the schema was run against the live database
  `heffl-crm-lead`.
- Verified: `SHOW TABLES` showed the two new tables (`users`, `login_attempts`)
  alongside the untouched live tables. `lead` stayed at 53 rows and
  `heffl_webhook_logs` at 14 rows before and after. The schema is idempotent
  (CREATE TABLE IF NOT EXISTS) and names only the two portal tables. See the
  shared-database constraint in CLAUDE.md.

## Phases 5 to 7: Staff management, account, change password, final pass

- Commit: `9e7ca4d` (plus `cb0234e` removing redundant gitkeep files, and
  `cb952a1` adding the README).
- Built: staff management (paginated list, add with admin-set or generated temp
  password shown once, edit name/email/role, activate/deactivate) with guards
  blocking self-deactivation and removing the last active admin; account
  settings; change password (verifies current, blocks reuse, clears the flag);
  `select_field` component, `users` helper with last-admin checks, and a typed
  paginated query wrapper (`db_page`).
- Verified: full end-to-end run against the live database with a seeded admin
  session and a real staff login. RBAC 403 for unflagged staff, forced-change
  flow, profile update, and password change all passed. Live tables untouched.
  All test data removed. No em-dash, no inline CSS or JS, 31 db_* calls all
  parameterized, 0 raw query/exec.

## UI/UX redesign pass

- Commit: `da8fc40`.
- Built: a design system in the Tailwind config and input.css @layer (slate
  neutrals, one brand accent, shadow scale, shared button/input/card/table/badge
  and typography classes); a centered max-width container with a consistent page
  header (title, subtitle, actions); a collapsible sidebar with inline-SVG icons,
  hover and focus tooltips when collapsed, and cookie-persisted state read
  server-side (no flash); redesigned login, setup, and forced-change screens;
  merged account settings and change password into one Account page with Profile
  and Password cards, and removed the separate Change password nav item. The
  forced first-login change stays a separate minimal gate a flagged user cannot
  leave.
- Verified: hard-rule scan clean; RBAC and the forced-change redirect still hold;
  a full real-DB run (create staff, temp login, forced change, profile update,
  password change, re-login) passed. Live tables untouched. output.css rebuilt.

## Clean URL migration

- Commit: `f9199f8`.
- Built: a committed `.htaccess` that rewrites any non-file request to
  `index.php` while serving real files directly, working at the document root or
  a subfolder; front-controller routing from the request path (base stripped,
  query and trailing slash ignored); `url()` as the single source of truth for
  clean paths; conversion of every link, nav item, form action, and redirect
  (zero index.php?page= remain).
- Verified: clean routing for `/`, `/login`, `/dashboard`, `/dashboard/` (same as
  `/dashboard`); 404 for `/nonsense` and `/staff/nonsense` through the front
  controller; static assets served directly (output.css 200 with text/css);
  guards and RBAC intact and pointing at clean URLs; base-path logic unit-tested
  for docroot, subfolder, and root. No em-dash, no inline CSS or JS.

## Current status

The portal is complete and live-ready. Phases 1 through 7 are done, plus the
UI/UX redesign and the clean-URL migration. The schema is loaded in the live
database, an admin account exists (created by the owner via `/setup`), and the
two production tables (`heffl_webhook_logs`, `lead`) have never been touched.
Latest commit at the time of writing: `f9199f8`.

## Next and backlog

Deferred, not yet needed:

- Optional cron to prune `login_attempts` older than the lockout window. The app
  already prunes on each login attempt, so this is only a belt-and-suspenders
  cleanup: `DELETE FROM login_attempts WHERE attempted_at < NOW() - INTERVAL 15 MINUTE`.
- Integration into the wider lead pipeline once those parts exist: Phases 1 to 3
  (ingestion), the sync worker, and the multi-app helper. The portal is the
  Phase 4 admin layer and will surface pipeline views when they are built.
- Possible future portal features when a real need appears: profile avatar,
  admin-initiated password reset for a staff member, and email sending.
