Files
portal/docs/ADMIN_OVERHAUL.md
T
NeilClack 91d162c746 Rebuild admin pages around DataTable/AdminModal; restructure categories/groups and v2 groups API
Remove the generic $lib/svelte-admin CRUD layer and AdminTable.svelte; each
admin route now owns its own form/modal logic. Add DataTable.svelte,
superform-errors.ts, and profile-specific modal/field components.

Flatten proclamation categories/groups and drop subscription levels; add the
v2 groups API; update v1/v2 endpoints, serializers, auth, and regenerated
Prisma client with the new migrations.

Document the overhaul plan in docs/ADMIN_OVERHAUL.md.
2026-09-10 09:38:01 -05:00

185 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Admin Overhaul & Bug Fix Plan
Status: **IN PROGRESS** — checkboxes are ticked as work is completed and committed.
## Context
The admin section is mid-refactor. The generic `$lib/svelte-admin/*` CRUD layer and
`AdminTable.svelte` were deleted; the **Profiles** page was rebuilt by hand with
`DataTable.svelte`, `AdminModal.svelte`, and route-specific form component(s). The
remaining admin pages still import the deleted modules, so the app does not compile.
This plan:
1. Fixes the reported Profiles-page bugs (stale buttons, unified User/Profile identity,
in-modal status messages, centered modal with backdrop).
2. Rebuilds every other admin page to match the Profiles page look, style, and
functionality — KISS, no clever abstractions.
3. Removes the **Admin dashboard** — the nav links are the navigation.
4. Uses `DataTable` / `AdminModal` / `Toast` as dumb containers; each page owns its
own form logic.
Roles: `SUPER = 1`, `ADMIN = 2`, `STAFF = 3`, `USER = 4`. Admin area requires `roleId <= 3`.
## Decisions Locked In
- **Dashboard**: removed. `/admin` redirects to `/admin/profiles`.
- **Profile email is the identity.** The User dropdown is removed from the Profiles
form. One Email field:
- Linked profile (`userId` set): field shows/edits the User's email; saving
syncs the change to the `User` record.
- Unlinked profile: email lives on the Profile only.
- On save, if an existing `User` matches the entered email, the profile is linked
to it automatically.
- **Users page** is where Users are edited (Name, Email, Role) — not the Profiles page.
- **Status messages live inside modals**, at the top, not in a page-level toast.
- **API Keys**: pending operator review. Applied: compile fix (remove `$lib/svelte-admin/roles`
dependency) + consistent styling. Modal-based rotation flow is deferred (see appendix).
---
## Checklist
### Phase 0 — Documentation & scaffolding
- [x] Document this plan at `docs/ADMIN_OVERHAUL.md` (this file)
- [x] Remove Admin dashboard: `/admin` redirects to `/admin/profiles`
- [x] Delete `(protected)/admin/+page.svelte`
- [x] Rewrite `(protected)/admin/+page.server.ts` as a `redirect(307, '/admin/profiles')`
### Phase 1 — Shared components
- [x] Rewrite `src/lib/components/admin/AdminModal.svelte`
- [x] Fixed overlay, centered panel (`fixed inset-0 z-50`)
- [x] Dark translucent backdrop (`bg-slate-900/50`), click/`Esc` to close
- [x] Panel: `max-w-2xl`, `max-h-[90vh]`, scrollable body
- [x] Header: title, Delete (when `canDelete && recordId`), close `×`
- [x] Extend `src/lib/components/DataTable.svelte`
- [x] Optional `copyableFields` → copy button per matching cell (`stopPropagation`)
- [x] Optional `draggable` + `onReorder` → drag-handle reorder column
- [x] Restyle `src/lib/components/admin/Toast.svelte` (fixed, dark chip, countdown/dismiss)
### Phase 2 — Profiles page (reference implementation)
- [x] `+page.server.ts`
- [x] Remove user-options relation data / `updateUser` action
- [x] New schema: `email` (required), `firstName`, `lastName`, `phoneNumber`
- [x] Upsert: create links to matching `User` by email; update syncs linked `User.email`;
guard `P2002` with friendly message
- [x] `ProfileFields.svelte`
- [x] Single Email field (bound from `user.email ?? record.email`); remove User select
- [x] `CreateProfileModal.svelte`
- [x] Fix skip-first `lastForm` effect bug
- [x] In-modal success banner ("Profile created") then auto-close
- [x] In-modal error banner + field errors
- [x] `EditProfileModal.svelte`
- [x] Fix skip-first `lastForm` effect bug
- [x] Reset sub-state per record (addresses, toggles, errors) on record change
- [x] Remove the "Edit User" / "Save User" section
- [x] In-modal success/error banners; keep open on successful edit
- [x] Clear & close Add-Address form on success; append returned address live
- [x] `+page.svelte`
- [x] Email column shows `user.email ?? email`; remove `users` prop plumbing
- [x] `{#key editRecord?.id}` so the edit modal remounts per record
- [x] Remove page-level `Toast` for modal feedback
- [x] New spec: `profiles/+page.server.spec.ts`
- [x] Create links by matched email; unlinked stores email only
- [x] Update syncs linked `User.email`; rejects email owned by another user
- [x] Validation failures and `P2002` handling
### Phase 3 — Users page
- [x] `+page.server.ts` — load users + role options (role-filtered for non-super);
`upsert` name/email/roleId; `delete`; `P2002` guard
- [x] `+page.svelte` — `DataTable` (Name, Email, Role, Created) + edit `AdminModal`
(Name, Email, Role select, Delete)
- [x] Replace `UserForm.svelte` usage (or convert to inline form) — no `svelte-admin`
### Phase 4 — User Roles page
- [x] `+page.server.ts` — direct `prisma.userRole` load/upsert/delete (P2003 guard)
- [x] `+page.svelte` — `DataTable` (Id, Name) + modal
### Phase 5 — Solfeggio Frequencies page
- [x] `+page.server.ts` — direct `prisma.solfeggioFrequency` load/upsert/delete (P2003 guard)
- [x] `+page.svelte` — `DataTable` (Name, Value) + modal
### Phase 6 — Proclamation Categories page
- [x] `+page.server.ts` — direct `prisma.proclamationCategory` load/upsert/delete/reorder
(P2002/P2003 guards, maxSort for new rows)
- [x] `+page.svelte` — draggable `DataTable` + modal (Name), reorder via fetch + invalidateAll
### Phase 7 — Proclamation Groups page
- [x] `+page.server.ts` — direct `prisma.proclamationGroup` load/upsert/delete/reorder
(P2002/P2003 guards)
- [x] `+page.svelte` — draggable `DataTable` (Name, Sort) + modal
### Phase 8 — License Keys page
- [x] `+page.server.ts` — direct `prisma.licenseKey` load (with user) + `createKey` + `replace`
- [x] `+page.svelte` — `DataTable` with copyable Key column; New-by-email modal; row → Replace modal
- [x] In-modal success/error banners for both flows
### Phase 9 — Proclamations page
- [x] `+page.server.ts` — direct load (strip content for non-super) + upsert (Super only,
P2002) + delete (Super only, P2003); category/group/frequency option data
- [x] `+page.svelte` — `DataTable` (Name, Category, Group, Frequency) + modal with
TipTap content editor for Super users
- [x] Update `page.server.spec.ts` and `page.svelte.spec.ts` (drop `svelte-admin` mocks)
### Phase 10 — API Keys page (operator page)
- [x] `+page.server.ts` — remove `ROLES` import (`$lib/svelte-admin/roles` deleted); use literal
- [x] `+page.svelte` — consistent styling; keep single-page rotate flow (modal conversion deferred)
- [x] Document deferred modal conversion in appendix
### Phase 11 — Tests & validation
- [x] Update `license-keys/page.server.spec.ts` (drop `svelte-admin` mocks)
- [x] `npm run check`
- [x] `npm run lint` (pre-existing `no-explicit-any` spec debt remains — see Appendix C)
- [x] `npm run test`
- [x] `npm run build` (needs env vars exported during `vite build`)
- [x] Final commit; mark every checkbox complete
---
## Appendix A — Profiles bug root causes
1. **Skip-first `lastForm` bug.** Both modals guard against the initial `null` form
with `if (lastForm === null) { lastForm = form; return; }`, which also swallows the
_first real submit result_ — the first "Add Address" save creates the DB row but the
UI never reacts (no close, no toast, no appended row). Fix: `if (!form || form === lastForm) return;`.
2. **Stale modal state across records.** `{#if editRecord}` keeps the modal mounted
between different rows, while the `seeded` flag never resets — the previous row's
addresses/user data leak in, and `record` stays stale (the reported
"my name didn't update"). Fix: `{#key editRecord?.id}` remount + per-record reseed.
3. **User data is only a snapshot.** The modal rendered `record.user.name / email`
directly. With the unified email model this display goes away; the form fields are
controlled by local `$state` and the server keeps Profile + User in sync.
## Appendix B — API Keys decision note
The API Keys page is an operator page (key rotation), not a CRUD table. It does not use
`DataTable`. Recommended future change: present the rotation confirmation and the new
key in shared `AdminModal` panels with in-modal status. That conversion is **deferred**
pending review. What was done now: compile fix (replaced the deleted `ROLES` import)
and consistent styling (simple slate card layout, indigo action buttons, red emergency
button) so the page compiles and matches the site visually.
## Appendix C — Validation caveats
1. **`npm run check`** required renaming `src/routes/(protected)/admin/profiles/+page.server.spec.ts`
to `page.server.spec.ts`: SvelteKit reserves files whose names start with `+`. The `+`-prefix
broke `svelte-kit sync` and thus the whole `check`/`build` pipeline. Renamed.
2. **`npm run lint`** still reports 142 `no-explicit-any` errors across 14 pre-existing spec files
(user/account, dashboard, license-keys, login/register/reset, etc.). These predate this overhaul
and are untouched here; fixing them is a separate cleanup.
3. **`npm run build`** needs `BETTER_AUTH_SECRET`/`BETTER_AUTH_URL`/`DATABASE_URL` exported in the
shell, because `src/lib/auth.ts` reads `$env/dynamic/private` (empty at `vite build` time) and
Better Auth rejects the default secret. Export the vars before building.