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.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user