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:
NeilClack
2026-09-10 09:38:01 -05:00
parent f4e736c92d
commit 91d162c746
127 changed files with 7182 additions and 9785 deletions
+5 -68
View File
@@ -1,74 +1,11 @@
<script lang="ts">
import { page } from '$app/state';
import { canAccessAdmin } from '$lib/svelte-admin/roles';
import { getAllModelConfigs } from '$lib/svelte-admin/registry';
import type { LayoutData } from './$types';
import type { Snippet } from 'svelte';
let { children, data } = $props<{ children: any; data: LayoutData }>();
const isAdmin = $derived(canAccessAdmin(data.userRoleId));
const adminModels = getAllModelConfigs();
const navLinks = [
{ href: '/dashboard', label: 'Dashboard' },
{ href: '/account', label: 'Account' },
{ href: '/proclamations', label: 'Proclamations' },
{ href: '/license-keys', label: 'License Keys' }
];
function isActive(href: string): boolean {
return page.url.pathname === href || page.url.pathname.startsWith(href + '/');
}
let { children }: { children: Snippet } = $props();
</script>
<div class="flex min-h-0 flex-1">
<!-- Sidebar -->
<nav class="mt-12 mr-12 flex flex-col gap-1">
{#each navLinks as link}
<a
href={link.href}
class="rounded px-3 py-1.5 text-sm font-medium transition-colors {isActive(link.href)
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
: 'text-gray-600 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
>
{link.label}
</a>
{/each}
{#if isAdmin}
<div class="mt-4 flex flex-col gap-1">
<a
href="/admin"
class="rounded px-3 py-1.5 text-sm font-medium transition-colors {isActive('/admin')
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
>
Admin Panel
</a>
<div class="ml-3 flex flex-col gap-0.5">
{#each adminModels as model}
<a
href="/admin/{model.slug}"
class="rounded px-3 py-1 text-sm transition-colors {isActive('/admin/' + model.slug)
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
>
{model.displayName}
</a>
{/each}
<a
href="/admin/api-keys"
class="rounded px-3 py-1 text-sm transition-colors {isActive('/admin/api-keys')
? 'bg-blue-50 text-blue-700 dark:bg-gray-800 dark:text-gray-100'
: 'text-blue-600 hover:bg-gray-50 hover:text-blue-800 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-gray-100'}"
>
API Keys
</a>
</div>
</div>
{/if}
</nav>
<!-- Page content -->
<div class="min-h-0 flex-1 overflow-auto">
<div class="min-h-screen bg-slate-50 text-slate-900">
<main class="mx-auto w-full max-w-6xl px-4 py-6 sm:px-6 sm:py-8">
{@render children()}
</div>
</main>
</div>