Files
fosterflow/src/routes/admin/events/[id]/+page.svelte
T
justinandClaude Opus 4.6 1ae4c1be2c feat(task-36): admin Content section — content blocks, newsletter, happy tails, events, messages
CMS content block editor grouped by page, newsletter subscriber management,
happy tails CRUD, events CRUD, and contact message inbox with read/unread tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:42:50 -05:00

119 lines
5.6 KiB
Svelte

<script lang="ts">
import { enhance } from '$app/forms';
import { goto } from '$app/navigation';
let { data, form } = $props();
const event = $derived(data.event);
let submitting = $state<string | null>(null);
let showSuccess = $state(false);
let confirmDelete = $state(false);
function toDatetimeLocal(d: string | Date | null): string {
if (!d) return '';
const dt = new Date(d);
const pad = (n: number) => n.toString().padStart(2, '0');
return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())}T${pad(dt.getHours())}:${pad(dt.getMinutes())}`;
}
function handleEnhance(action: string) {
submitting = action;
return async ({ update, result }: { update: (opts?: { reset?: boolean }) => Promise<void>; result: { type: string; data?: Record<string, unknown> } }) => {
submitting = null;
if (result.type === 'success' && result.data?.deleted) {
goto('/admin/events');
return;
}
await update({ reset: false });
if (result.type === 'success') { showSuccess = true; setTimeout(() => showSuccess = false, 3000); }
};
}
const inputClass = 'w-full px-3 py-2.5 rounded-lg border border-gray-soft bg-white text-gray-brand placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-teal-brand focus:border-teal-brand text-sm';
const labelClass = 'block text-sm font-medium text-gray-700 mb-1';
</script>
<svelte:head><title>{event.title} — Admin</title></svelte:head>
<div class="max-w-2xl">
<div class="flex items-center gap-4 mb-6">
<a href="/admin/events" class="text-gray-400 hover:text-gray-600 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" /></svg>
</a>
<h1 class="text-2xl font-bold text-gray-900">{event.title}</h1>
</div>
{#if form?.error}
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl text-sm text-red-700">{form.error}</div>
{/if}
{#if showSuccess}
<div class="mb-6 p-4 bg-green-50 border border-green-200 rounded-xl text-sm text-green-700 flex items-center gap-2">
<svg class="w-4 h-4 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /></svg>
Event saved.
</div>
{/if}
<div class="bg-white rounded-xl shadow-sm border border-gray-soft p-6 mb-6">
<form method="POST" action="?/update" use:enhance={() => handleEnhance('update')}>
<div class="space-y-4">
<div>
<label for="title" class={labelClass}>Title</label>
<input id="title" name="title" type="text" required value={event.title} class={inputClass} />
</div>
<div>
<label for="description" class={labelClass}>Description</label>
<textarea id="description" name="description" rows="4" class={inputClass}>{event.description ?? ''}</textarea>
</div>
<div>
<label for="location" class={labelClass}>Location</label>
<input id="location" name="location" type="text" value={event.location ?? ''} class={inputClass} />
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label for="startDate" class={labelClass}>Start Date & Time</label>
<input id="startDate" name="startDate" type="datetime-local" required value={toDatetimeLocal(event.startDate)} class={inputClass} />
</div>
<div>
<label for="endDate" class={labelClass}>End Date & Time</label>
<input id="endDate" name="endDate" type="datetime-local" value={toDatetimeLocal(event.endDate)} class={inputClass} />
</div>
</div>
<div>
<label for="imageUrl" class={labelClass}>Image URL</label>
<input id="imageUrl" name="imageUrl" type="url" value={event.imageUrl ?? ''} class={inputClass} />
</div>
<div>
<label for="facebookEventId" class={labelClass}>Facebook Event ID</label>
<input id="facebookEventId" name="facebookEventId" type="text" value={event.facebookEventId ?? ''} class={inputClass} />
</div>
<label class="flex items-center gap-2">
<input name="published" type="checkbox" checked={event.published} class="rounded border-gray-soft text-teal-brand focus:ring-teal-brand" />
<span class="text-sm text-gray-700">Published</span>
</label>
</div>
<div class="mt-6">
<button type="submit" disabled={submitting === 'update'} class="px-6 py-2.5 text-sm font-medium rounded-lg bg-teal-brand text-white hover:bg-teal-dark transition-colors disabled:opacity-50 cursor-pointer">
{submitting === 'update' ? 'Saving...' : 'Save Changes'}
</button>
</div>
</form>
</div>
<div class="bg-white rounded-xl shadow-sm border border-red-200 p-6">
<h2 class="text-lg font-semibold text-gray-900 mb-2">Danger Zone</h2>
{#if confirmDelete}
<p class="text-sm text-red-600 mb-3">Are you sure? This cannot be undone.</p>
<form method="POST" action="?/delete" use:enhance={() => handleEnhance('delete')}>
<div class="flex gap-3">
<button type="submit" disabled={submitting === 'delete'} class="px-4 py-2 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 disabled:opacity-50 cursor-pointer">
{submitting === 'delete' ? 'Deleting...' : 'Yes, Delete'}
</button>
<button type="button" onclick={() => confirmDelete = false} class="px-4 py-2 text-sm rounded-lg border border-gray-soft text-gray-700 hover:bg-gray-50 cursor-pointer">Cancel</button>
</div>
</form>
{:else}
<button onclick={() => confirmDelete = true} class="px-4 py-2 text-sm font-medium rounded-lg border border-red-300 text-red-600 hover:bg-red-50 cursor-pointer">Delete Event</button>
{/if}
</div>
</div>