Admins can now create a Profile or License Key for someone who hasn't
registered yet (matched by email). When that person registers, any
unattached profile and license keys are linked to their new account.
Also remove the Proclamations surface from the user dashboard.
- Consolidated navbar with hamburger menu for mobile
- Responsive styling for root/protected layouts and auth pages
- Mobile-friendly account, dashboard, license-key, and setup pages
- Replace per-model admin forms with the shared generic AdminTable/AdminModal CRUD
- Add ProclamationGroup model and admin + API v2 routes (replaces subscription levels)
- Drop subscription levels and flatten proclamation categories
- Update v2 serializers and seed data accordingly
Problem:
The admin createKey action had two issues preventing the license key from
appearing in the delivery email:
1. Wrong variable name: passed 'licenseKey' (camelCase) but the Resend
template expects 'license_key' (snake_case).
2. Wrong template: used the default RESEND_TEMPLATE_ID (a generic
template) instead of the 'license-key-delivery' template that the
automation endpoint uses for license key emails.
Solution:
- Changed template variable from 'licenseKey' to 'license_key'
- Added explicit templateId: 'license-key-delivery' to match the
automation endpoint (api/v1/automations/create/account)
- Updated email subject to 'Your Rhemawave License Key' for consistency
Tests:
Added page.server.spec.ts for the admin license-keys createKey action
covering: unauthenticated rejection, missing email/level validation,
user-not-found error, successful key creation with correct template ID
and license_key variable, fallback to user.name when profile is missing,
and graceful handling when email sending fails.
- Replace raw Resend text emails with sendMail using Resend templates
- automated-user-registration template: firstName, tempPassword,
verifyUrl, loginUrl
- license-key-delivery template: firstName, license_key
- Remove requestPasswordReset call (temp password is the one-time
credential; verification link handles email confirmation)
- Add .catch() to Better Auth email callbacks to prevent unhandled
rejections from missing templates crashing the server
- Change /api/v1/license from GET with query params to POST with JSON body
- Request body uses license_key and email_address
- Response returns key, email_address, subscription_level
- Add active check (reject deactivated license keys)
- Exclude /api/v1/automations/ from API key auth (GHL webhook)
- Add copyableFields prop to AdminTable with clipboard icon
- Enable copy button on license key field in license-keys admin page
- Add ApiKey Prisma model (keyHash, expiresAt, revokedAt)
- Protect /api/v1/* routes with Authorization: Bearer header check in hooks
- create rotateApiKey() utility: normal (48h grace) and emergency (immediate)
- Add Super-only /admin/api-keys page for key rotation with one-time reveal
- Fix roleId check by using parent layout's DB-fetched adminUser
- Add API Keys link to admin sidebar
- Add sortOrder column to ProclamationCategory with migration
- Implement drag-and-drop reorder in AdminTable with persist-to-DB
- Refactor AdminTable to use $derived + dragOverride (no $effect sync)
- Replace window.location.reload with invalidateAll for reorder
- Convert license key create to modal with subscription level dropdown
- Add LicenseKeyForm.svelte following the CategoryForm pattern
- Add replace action (delete + generate new key with same level)
- Remove deactivateOld checkbox; each key has its own level
- Use use:enhance with update() to refresh list after create/replace
- Registry: Remove 'parent.name' from listFields (all shown records
are top-level with no parent), remove 'parent: true' from include
since unused
- +page.server.ts: Custom load filters to parentId=null (top-level
only); custom upsert syncs children (create/update/delete) and
handles P2002 duplicate-name errors; custom delete cascades to
children first to avoid FK constraint violations
- CategoryForm.svelte: Replace parentId select dropdown with dynamic
list of sub-category inputs. Each row has a name field and Remove
button. Pre-populates from record.children when editing existing
categories. Add Sub-category button appends new rows
- +page.svelte: Remove unused relationOptionsData prop from
CategoryForm; rename submit button label from 'Update' to 'Save'
- prisma: Add ProclamationCategorySubscriptionLevel join table
migration and regenerate client
- General: Update TipTap mock, Toast, license-keys page, proclamations
server/route tests, README, AGENTS.md, and other admin forms
Server tests cover load content stripping per role and upsert/delete
role enforcement. Client tests verify field rendering, pre-fill on
edit, content visibility per role, and submit button dirty-state.
Switched selects from bind:value/value to selected attribute pattern
to work around Svelte 5 select value reactivity in test env.
Switch from initialized from prop to -synced
values so form fields always populate when the record changes.
Key the TipTap editor on record.id to force re-render.
- Only roleId <= 1 (SUPER) can create/edit/delete proclamations
- Content field is stripped from records for non-Super users
- Content field is hidden from the form for non-Super users
- Server actions reject upsert/delete from non-Super users
- Add per-route form components: UserRoleForm, UserForm,
ProclamationForm (with category cascade), CategoryForm,
FreqForm, LevelForm
- Update all admin +page.svelte files to use AdminTable + AdminModal
+ their specific form component instead of AdminPage
- Update profiles page to inline fields instead of using AdminForm
- Add custom Proclamation server action with category fallback logic
(categoryId falls back to parentCategoryId when no sub-category)
- Remove cascade/were/fieldLabels logic from generic crud.ts
- Remove outdated cascade/where tests from crud.spec.ts
- Remove fieldLabels from AdminModelConfig type
- Import superValidate from sveltekit-superforms/server instead of default
path to avoid pulling SuperDebug.svelte into SSR
- Upgrade @prisma/client to match prisma CLI version (7.2.0 -> 7.8.0)
so generated client's query_compiler_fast_bg files exist at runtime
- Add regression test verifying superValidate works in server context
- Add regression test verifying generated Prisma client compiler imports
reference real files
- Add Superforms + Zod 4 for all admin form validation
- Replace address isDefault with three separate boolean defaults (Mailing, Billing, Shipping)
- Update profiles page with three-checkbox address form and toast improvements
- Convert all admin page server actions to pass request directly to crud
- Export superFormErrorsToFieldErrors helper for profiles server actions
- Regenerate Prisma client for new schema fields
- Add type (Mailing/Billing/Shipping) and isDefault fields to Address
schema with migration. Unique default enforced server-side.
- Rewrite profiles page with inline address creation form and
editable user name/email within the profile modal.
- Add upsertAddress and updateUser form actions.
- Fix form redirect by adding use:enhance to AdminForm.
- Disable Update button in AdminForm until a field is changed
(prevents accidental submissions).
The :root.dark CSS variable inversion caused contrast problems -
text-gray-900 and bg-gray-950 both became light in dark mode, making
text invisible on backgrounds.
Fix:
- Remove :root.dark { --color-gray-*: ... } block from layout.css
- Change :root.dark selector to .dark for form element styles
- Add standard Tailwind dark: classes throughout every .svelte page
- dark:bg-gray-950 (page backgrounds)
- dark:bg-gray-900 (card/surface backgrounds)
- dark:bg-gray-800 (table headers, elevated surfaces, active nav links)
- dark:bg-gray-700 (primary buttons)
- dark:text-gray-100 (primary text)
- dark:text-gray-300 (secondary text, labels)
- dark:text-gray-400 (muted text, inactive nav links, links)
- dark:border-gray-700 (borders)
- dark:border-gray-600 (input borders)
- dark:hover:bg-gray-800 (sidebar hover)
- dark:hover:bg-gray-600 (button hover)
- dark:prose-invert (rich text content)
- TopBar: full dark mode support (bg, links, toggle button)
- Sidebar: all nav links, active states, admin submenu get dark mode
- Auth pages (login, register, forgot, reset): full dark mode
- Protected pages (dashboard, account, setup, license-keys,
proclamations, categories): full dark mode
- Home page, error page, admin page: dark:text-gray-100
- Skip profile redirect when already on /account/setup so users
who just signed up/logged in land on the page without looping
- Add test for the skip-redirect case
- Register action now returns success instead of redirecting to /account/setup
so users without a session (duplicate email with requireEmailVerification)
see a 'check your email' message instead of crashing
- Protected layout guards against null/undefined locals.user before querying
Prisma (was passing id: undefined)
- Updated register page to show success banner with verification instructions
- Updated test to match new action response
19 new tests across 5 test files covering the protected layout redirect,
account page load, registration action (documenting the redirect bug),
and login/register component rendering. Includes shared test utilities.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add self-referential parent/child relation on AffirmationCategory for
one-level hierarchy. Update AffirmationForm with two-level category and
sub-category dropdowns, server actions with find-or-create hierarchy
logic, AffirmationList with Parent
This page is not behind (protected) because it must not use the (protected)/layout.
This means that this page also needs to handle it's own authentication, because it should not be open to the public either.