--- description: 'Use when building or modifying admin panel pages for The Collective Hub. Covers admin route structure, auth guards, form action patterns, layout conventions, and admin Svelte component patterns.' applyTo: 'src/routes/admin/**/*.svelte', 'src/routes/admin/**/*.server.ts' --- # Admin Panel ## Route Structure Admin routes live under `src/routes/admin/`: ``` src/routes/admin/ +layout.server.ts ← Auth guard (redirects to /login if unauthenticated) +layout.svelte ← Admin shell with navigation sidebar +page.svelte ← Admin dashboard (overview) settings/ +page.server.ts ← Loads/saves site settings +page.svelte branding/ +page.server.ts ← Logo, colors, theme +page.svelte homepage/ +page.server.ts ← Hero text, about, CTA +page.svelte links/ +page.server.ts ← Nav links + social links +page.svelte events/ +page.server.ts ← Event CRUD +page.svelte assets/ +page.server.ts ← Asset library +page.svelte ``` ## Auth Guard Pattern Every admin server load must check authentication. This is centralized in the admin layout: ```ts // src/routes/admin/+layout.server.ts import { redirect } from '@sveltejs/kit'; import type { LayoutServerLoad } from './$types'; export const load: LayoutServerLoad = async ({ locals }) => { if (!locals.user) { redirect(302, '/login'); } // Optional: check minimum role for admin access // const roleLevel = { owner: 3, admin: 2, editor: 1 }; // if ((roleLevel[locals.membership?.role ?? ''] ?? 0) < 2) { // redirect(302, '/'); // } return { user: locals.user, membership: locals.membership, settings: locals.siteSettings, }; }; ``` ## Form Action Patterns Admin pages use SvelteKit form actions for data mutations. API routes (`+server.ts`) are reserved for asset uploads and external integrations. ### Standard Form Action ```ts // +page.server.ts import { fail, redirect } from '@sveltejs/kit'; import { db } from '$lib/server/db'; import { siteSettings } from '$lib/server/db/schema'; import { eq } from 'drizzle-orm'; import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ locals }) => { if (!locals.user) redirect(302, '/login'); return { settings: locals.siteSettings, }; }; export const actions: Actions = { default: async ({ locals, request }) => { if (!locals.user) return fail(401, { error: 'Unauthorized' }); const form = await request.formData(); const siteName = form.get('siteName') as string; if (!siteName || siteName.trim().length === 0) { return fail(400, { error: 'Site name is required' }); } const updatedSettings = { ...locals.siteSettings, branding: { ...locals.siteSettings?.branding, siteName: siteName.trim(), }, }; await db .update(siteSettings) .set({ settings: updatedSettings }) .where(eq(siteSettings.siteId, locals.site.id)); return { success: true, message: 'Settings saved' }; }, }; ``` ### Form Component ```svelte
{#if form?.error}

{form.error}

{/if} {#if form?.success}

{form.message}

{/if}
``` ## Admin Layout Shell The admin layout provides consistent navigation: ```svelte
{@render children()}
``` ## Admin Page Conventions 1. **Server load fetches current data** — always load from DB, don't rely on cached `locals` 2. **Form actions validate and save** — never trust client-side data without server validation 3. **Success/error feedback** — return `{ success: true, message: '...' }` or `fail(statusCode, { error: '...' })` 4. **Role-aware UI** — conditionally show/hide controls based on `membership.role` 5. **Optimistic updates** — not required for V1; simple form submit + reload is fine 6. **No client-side state management** — rely on `form` and `data` from SvelteKit's form actions