Files
root-org/src/routes/admin/+page.svelte

1208 lines
32 KiB
Svelte

<script lang="ts">
import { enhance } from "$app/forms";
import { invalidateAll } from "$app/navigation";
import {
Button,
Badge,
Avatar,
StatCard,
TabBar,
Input,
Select,
Modal,
} from "$lib/components/ui";
let { data } = $props();
let activeTab = $state("overview");
let orgSearch = $state("");
let userSearch = $state("");
let eventSearch = $state("");
// Modal state
let confirmModal = $state<{
type: string;
id: string;
name: string;
} | null>(null);
let editOrgModal = $state<any>(null);
let editUserModal = $state<any>(null);
let editEventModal = $state<any>(null);
let actionLoading = $state(false);
const filteredOrgs = $derived(
orgSearch
? data.organizations.filter(
(o: any) =>
o.name
?.toLowerCase()
.includes(orgSearch.toLowerCase()) ||
o.slug?.toLowerCase().includes(orgSearch.toLowerCase()),
)
: data.organizations,
);
const filteredUsers = $derived(
userSearch
? data.profiles.filter(
(p: any) =>
p.email
?.toLowerCase()
.includes(userSearch.toLowerCase()) ||
p.full_name
?.toLowerCase()
.includes(userSearch.toLowerCase()),
)
: data.profiles,
);
const filteredEvents = $derived(
eventSearch
? data.events.filter(
(e: any) =>
e.name
?.toLowerCase()
.includes(eventSearch.toLowerCase()) ||
e.slug
?.toLowerCase()
.includes(eventSearch.toLowerCase()),
)
: data.events,
);
function formatDate(dateStr: string | null) {
if (!dateStr) return "-";
return new Date(dateStr).toLocaleDateString("en-US", {
month: "short",
day: "numeric",
year: "numeric",
});
}
function formatDateInput(dateStr: string | null) {
if (!dateStr) return "";
return new Date(dateStr).toISOString().split("T")[0];
}
function timeAgo(dateStr: string | null) {
if (!dateStr) return "-";
const diff = Date.now() - new Date(dateStr).getTime();
const mins = Math.floor(diff / 60000);
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
if (days < 30) return `${days}d ago`;
return formatDate(dateStr);
}
const statusColors: Record<string, string> = {
planning: "text-amber-400 bg-amber-400/10",
active: "text-emerald-400 bg-emerald-400/10",
completed: "text-blue-400 bg-blue-400/10",
archived: "text-light/40 bg-light/5",
draft: "text-light/40 bg-light/5",
};
const eventStatuses = ["planning", "active", "completed", "archived"];
const orgMap = $derived(
Object.fromEntries(data.organizations.map((o: any) => [o.id, o])),
);
// Count orgs per user
const userOrgCounts = $derived(() => {
const counts: Record<string, number> = {};
for (const m of data.orgMembers) {
counts[m.user_id] = (counts[m.user_id] || 0) + 1;
}
return counts;
});
function enhanceAction() {
return ({ result }: any) => {
actionLoading = false;
if (result.type === "success") {
confirmModal = null;
editOrgModal = null;
editUserModal = null;
editEventModal = null;
invalidateAll();
}
};
}
</script>
<svelte:head>
<title>Platform Admin | root</title>
</svelte:head>
<div class="min-h-screen bg-background">
<!-- Header -->
<header class="border-b border-light/5 bg-dark/30">
<div
class="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between"
>
<div class="flex items-center gap-3">
<a
href="/"
class="p-1.5 text-light/40 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
>
<span
class="material-symbols-rounded"
style="font-size: 20px;">arrow_back</span
>
</a>
<div class="flex items-center gap-2">
<span
class="material-symbols-rounded text-primary"
style="font-size: 24px; font-variation-settings: 'FILL' 1;"
>admin_panel_settings</span
>
<span class="font-heading text-body text-white"
>Platform Admin</span
>
</div>
<Badge variant="error" size="sm">Admin Only</Badge>
</div>
<div class="flex items-center gap-2 text-light/40 text-body-sm">
<span class="material-symbols-rounded" style="font-size: 16px;"
>schedule</span
>
{new Date().toLocaleDateString("en-US", {
weekday: "long",
month: "long",
day: "numeric",
year: "numeric",
})}
</div>
</div>
</header>
<div class="max-w-7xl mx-auto px-6 py-6 space-y-6">
<!-- Stats Overview -->
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-3">
<StatCard
label="Total Users"
value={data.stats.totalUsers}
icon="group"
/>
<StatCard
label="Organizations"
value={data.stats.totalOrgs}
icon="business"
/>
<StatCard
label="Total Events"
value={data.stats.totalEvents}
icon="event"
/>
<StatCard
label="New Users (7d)"
value={data.stats.newUsersLast7d}
icon="person_add"
/>
<StatCard
label="New Users (30d)"
value={data.stats.newUsersLast30d}
icon="trending_up"
/>
<StatCard
label="Active Events"
value={data.stats.activeEvents}
icon="play_circle"
/>
<StatCard
label="Planning"
value={data.stats.planningEvents}
icon="edit_calendar"
/>
</div>
<!-- Tab Navigation -->
<TabBar
tabs={[
{ value: "overview", label: "Overview", icon: "dashboard" },
{
value: "organizations",
label: "Organizations",
icon: "business",
},
{ value: "users", label: "Users", icon: "group" },
{ value: "events", label: "Events", icon: "event" },
]}
active={activeTab}
onchange={(v) => (activeTab = v)}
/>
<!-- Tab Content -->
{#if activeTab === "overview"}
<div class="grid lg:grid-cols-2 gap-6">
<!-- Recent Organizations -->
<div class="bg-dark/30 border border-light/5 rounded-xl p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="font-heading text-body text-white">
Recent Organizations
</h3>
<button
type="button"
class="text-[11px] text-primary hover:text-primary/80 transition-colors"
onclick={() => (activeTab = "organizations")}
>View all →</button
>
</div>
<div class="space-y-2">
{#each data.organizations.slice(0, 5) as org}
<div
class="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-dark/50 transition-colors"
>
<div class="flex items-center gap-3 min-w-0">
<Avatar
name={org.name ?? "Org"}
size="sm"
/>
<div class="min-w-0">
<p
class="text-body-sm text-white truncate"
>
{org.name}
</p>
<p class="text-[10px] text-light/30">
/{org.slug}
</p>
</div>
</div>
<div
class="flex items-center gap-3 shrink-0 text-[10px] text-light/40"
>
<span>{org.memberCount} members</span>
<span>{org.eventCount} events</span>
</div>
</div>
{/each}
{#if data.organizations.length === 0}
<p
class="text-body-sm text-light/30 text-center py-4"
>
No organizations yet
</p>
{/if}
</div>
</div>
<!-- Recent Users -->
<div class="bg-dark/30 border border-light/5 rounded-xl p-5">
<div class="flex items-center justify-between mb-4">
<h3 class="font-heading text-body text-white">
Recent Users
</h3>
<button
type="button"
class="text-[11px] text-primary hover:text-primary/80 transition-colors"
onclick={() => (activeTab = "users")}
>View all →</button
>
</div>
<div class="space-y-2">
{#each data.profiles.slice(0, 5) as profile}
<div
class="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-dark/50 transition-colors"
>
<div class="flex items-center gap-3 min-w-0">
<Avatar
name={profile.full_name ??
profile.email}
size="sm"
src={profile.avatar_url}
/>
<div class="min-w-0">
<p
class="text-body-sm text-white truncate"
>
{profile.full_name ?? "No name"}
</p>
<p class="text-[10px] text-light/30">
{profile.email}
</p>
</div>
</div>
<div class="flex items-center gap-2 shrink-0">
{#if profile.is_platform_admin}
<Badge variant="error" size="sm"
>Admin</Badge
>
{/if}
<span class="text-[10px] text-light/30"
>{timeAgo(profile.created_at)}</span
>
</div>
</div>
{/each}
{#if data.profiles.length === 0}
<p
class="text-body-sm text-light/30 text-center py-4"
>
No users yet
</p>
{/if}
</div>
</div>
<!-- Recent Events -->
<div
class="bg-dark/30 border border-light/5 rounded-xl p-5 lg:col-span-2"
>
<div class="flex items-center justify-between mb-4">
<h3 class="font-heading text-body text-white">
Recent Events
</h3>
<button
type="button"
class="text-[11px] text-primary hover:text-primary/80 transition-colors"
onclick={() => (activeTab = "events")}
>View all →</button
>
</div>
{#if data.events.length > 0}
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead>
<tr class="border-b border-light/5">
<th
class="text-[10px] text-light/40 font-body pb-2 pr-4"
>Event</th
>
<th
class="text-[10px] text-light/40 font-body pb-2 pr-4"
>Organization</th
>
<th
class="text-[10px] text-light/40 font-body pb-2 pr-4"
>Status</th
>
<th
class="text-[10px] text-light/40 font-body pb-2 pr-4"
>Dates</th
>
<th
class="text-[10px] text-light/40 font-body pb-2"
>Created</th
>
</tr>
</thead>
<tbody>
{#each data.events.slice(0, 8) as event}
<tr
class="border-b border-light/5 last:border-0 hover:bg-dark/30"
>
<td class="py-2.5 pr-4">
<p
class="text-body-sm text-white"
>
{event.name}
</p>
<p
class="text-[10px] text-light/30"
>
/{event.slug}
</p>
</td>
<td
class="py-2.5 pr-4 text-body-sm text-light/50"
>
{orgMap[event.org_id]?.name ??
"-"}
</td>
<td class="py-2.5 pr-4">
<span
class="text-[10px] px-2 py-0.5 rounded-full capitalize {statusColors[
event.status
] ??
'text-light/40 bg-light/5'}"
>
{event.status}
</span>
</td>
<td
class="py-2.5 pr-4 text-[11px] text-light/40"
>
{formatDate(event.start_date)} -
{formatDate(event.end_date)}
</td>
<td
class="py-2.5 text-[11px] text-light/30"
>{timeAgo(event.created_at)}</td
>
</tr>
{/each}
</tbody>
</table>
</div>
{:else}
<p class="text-body-sm text-light/30 text-center py-4">
No events yet
</p>
{/if}
</div>
<!-- Event Status Legend -->
<div
class="bg-dark/30 border border-light/5 rounded-xl p-5 lg:col-span-2"
>
<h3 class="font-heading text-body text-white mb-3">
Event Status Guide
</h3>
<div class="grid md:grid-cols-4 gap-4">
<div class="flex items-start gap-3">
<span
class="text-[10px] px-2 py-0.5 rounded-full shrink-0 text-amber-400 bg-amber-400/10"
>planning</span
>
<p class="text-[11px] text-light/40">
Event is being prepared. Team is setting up
departments, tasks, and logistics.
</p>
</div>
<div class="flex items-start gap-3">
<span
class="text-[10px] px-2 py-0.5 rounded-full shrink-0 text-emerald-400 bg-emerald-400/10"
>active</span
>
<p class="text-[11px] text-light/40">
Event is currently live/happening. All systems
go.
</p>
</div>
<div class="flex items-start gap-3">
<span
class="text-[10px] px-2 py-0.5 rounded-full shrink-0 text-blue-400 bg-blue-400/10"
>completed</span
>
<p class="text-[11px] text-light/40">
Event has finished. Data is preserved for
review.
</p>
</div>
<div class="flex items-start gap-3">
<span
class="text-[10px] px-2 py-0.5 rounded-full shrink-0 text-light/40 bg-light/5"
>archived</span
>
<p class="text-[11px] text-light/40">
Event is hidden from normal views. Can be
restored.
</p>
</div>
</div>
</div>
</div>
{:else if activeTab === "organizations"}
<div class="space-y-4">
<div class="max-w-sm">
<Input
placeholder="Search organizations..."
icon="search"
bind:value={orgSearch}
/>
</div>
<div
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden"
>
<table class="w-full text-left">
<thead>
<tr class="border-b border-light/5 bg-dark/20">
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Organization</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Slug</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Members</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Events</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Created</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4 text-right"
>Actions</th
>
</tr>
</thead>
<tbody>
{#each filteredOrgs as org}
<tr
class="border-b border-light/5 last:border-0 hover:bg-dark/30 transition-colors"
>
<td class="py-3 px-4">
<div class="flex items-center gap-3">
<Avatar
name={org.name ?? "Org"}
size="sm"
/>
<span
class="text-body-sm text-white"
>{org.name}</span
>
</div>
</td>
<td
class="py-3 px-4 text-body-sm text-light/40"
>/{org.slug}</td
>
<td class="py-3 px-4">
<Badge variant="default" size="sm"
>{org.memberCount}</Badge
>
</td>
<td class="py-3 px-4">
<Badge variant="primary" size="sm"
>{org.eventCount}</Badge
>
</td>
<td
class="py-3 px-4 text-[11px] text-light/30"
>{formatDate(org.created_at)}</td
>
<td class="py-3 px-4">
<div
class="flex items-center justify-end gap-1"
>
<button
type="button"
class="p-1.5 text-light/30 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
title="Edit organization"
onclick={() =>
(editOrgModal = {
id: org.id,
name: org.name,
slug: org.slug,
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>edit</span
>
</button>
<button
type="button"
class="p-1.5 text-light/30 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors"
title="Delete organization"
onclick={() =>
(confirmModal = {
type: "deleteOrg",
id: org.id,
name: org.name,
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>delete</span
>
</button>
</div>
</td>
</tr>
{/each}
{#if filteredOrgs.length === 0}
<tr>
<td
colspan="6"
class="py-8 text-center text-body-sm text-light/30"
>
{orgSearch
? "No organizations match your search"
: "No organizations yet"}
</td>
</tr>
{/if}
</tbody>
</table>
</div>
<p class="text-[10px] text-light/30">
{filteredOrgs.length} of {data.organizations.length} organizations
</p>
</div>
{:else if activeTab === "users"}
<div class="space-y-4">
<div class="max-w-sm">
<Input
placeholder="Search users..."
icon="search"
bind:value={userSearch}
/>
</div>
<div
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden"
>
<table class="w-full text-left">
<thead>
<tr class="border-b border-light/5 bg-dark/20">
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>User</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Email</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Organizations</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Role</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Joined</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4 text-right"
>Actions</th
>
</tr>
</thead>
<tbody>
{#each filteredUsers as profile}
<tr
class="border-b border-light/5 last:border-0 hover:bg-dark/30 transition-colors"
>
<td class="py-3 px-4">
<div class="flex items-center gap-3">
<Avatar
name={profile.full_name ??
profile.email}
size="sm"
src={profile.avatar_url}
/>
<span
class="text-body-sm text-white"
>{profile.full_name ??
"No name"}</span
>
</div>
</td>
<td
class="py-3 px-4 text-body-sm text-light/40"
>{profile.email}</td
>
<td class="py-3 px-4">
<Badge variant="default" size="sm"
>{userOrgCounts()[profile.id] ??
0}</Badge
>
</td>
<td class="py-3 px-4">
{#if profile.is_platform_admin}
<Badge variant="error" size="sm"
>Platform Admin</Badge
>
{:else}
<Badge variant="default" size="sm"
>User</Badge
>
{/if}
</td>
<td
class="py-3 px-4 text-[11px] text-light/30"
>{formatDate(profile.created_at)}</td
>
<td class="py-3 px-4">
<div
class="flex items-center justify-end gap-1"
>
<button
type="button"
class="p-1.5 text-light/30 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
title="Edit user"
onclick={() =>
(editUserModal = {
id: profile.id,
full_name:
profile.full_name ??
"",
email: profile.email,
is_platform_admin:
profile.is_platform_admin,
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>edit</span
>
</button>
{#if profile.is_platform_admin && profile.id !== data.currentUserId}
<form
method="POST"
action="?/toggleAdmin"
use:enhance={enhanceAction}
>
<input
type="hidden"
name="id"
value={profile.id}
/>
<input
type="hidden"
name="is_admin"
value="false"
/>
<button
type="submit"
class="p-1.5 text-amber-400 hover:text-amber-300 hover:bg-amber-400/10 rounded-lg transition-colors"
title="Remove admin"
>
<span
class="material-symbols-rounded"
style="font-size: 16px; font-variation-settings: 'FILL' 1;"
>shield</span
>
</button>
</form>
{:else if !profile.is_platform_admin}
<form
method="POST"
action="?/toggleAdmin"
use:enhance={enhanceAction}
>
<input
type="hidden"
name="id"
value={profile.id}
/>
<input
type="hidden"
name="is_admin"
value="true"
/>
<button
type="submit"
class="p-1.5 text-light/30 hover:text-amber-400 hover:bg-amber-400/10 rounded-lg transition-colors"
title="Make admin"
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>shield</span
>
</button>
</form>
{/if}
{#if profile.id !== data.currentUserId}
<button
type="button"
class="p-1.5 text-light/30 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors"
title="Delete user"
onclick={() =>
(confirmModal = {
type: "deleteUser",
id: profile.id,
name:
profile.full_name ??
profile.email,
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>delete</span
>
</button>
{/if}
</div>
</td>
</tr>
{/each}
{#if filteredUsers.length === 0}
<tr>
<td
colspan="6"
class="py-8 text-center text-body-sm text-light/30"
>
{userSearch
? "No users match your search"
: "No users yet"}
</td>
</tr>
{/if}
</tbody>
</table>
</div>
<p class="text-[10px] text-light/30">
{filteredUsers.length} of {data.profiles.length} users
</p>
</div>
{:else if activeTab === "events"}
<div class="space-y-4">
<div class="max-w-sm">
<Input
placeholder="Search events..."
icon="search"
bind:value={eventSearch}
/>
</div>
<div
class="bg-dark/30 border border-light/5 rounded-xl overflow-hidden"
>
<table class="w-full text-left">
<thead>
<tr class="border-b border-light/5 bg-dark/20">
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Event</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Organization</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Status</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Start</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>End</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4"
>Created</th
>
<th
class="text-[10px] text-light/40 font-body py-3 px-4 text-right"
>Actions</th
>
</tr>
</thead>
<tbody>
{#each filteredEvents as event}
<tr
class="border-b border-light/5 last:border-0 hover:bg-dark/30 transition-colors"
>
<td class="py-3 px-4">
<div>
<p class="text-body-sm text-white">
{event.name}
</p>
<p
class="text-[10px] text-light/30"
>
/{event.slug}
</p>
</div>
</td>
<td
class="py-3 px-4 text-body-sm text-light/40"
>
{orgMap[event.org_id]?.name ?? "-"}
</td>
<td class="py-3 px-4">
<form
method="POST"
action="?/updateEventStatus"
use:enhance={enhanceAction}
class="inline"
>
<input
type="hidden"
name="id"
value={event.id}
/>
<select
name="status"
class="text-[10px] px-2 py-0.5 rounded-full capitalize cursor-pointer border-0 appearance-none bg-transparent {statusColors[
event.status
] ??
'text-light/40 bg-light/5'}"
onchange={(e) =>
e.currentTarget.form?.requestSubmit()}
>
{#each eventStatuses as s}
<option
value={s}
selected={event.status ===
s}
class="bg-surface text-white"
>{s}</option
>
{/each}
</select>
</form>
</td>
<td
class="py-3 px-4 text-[11px] text-light/40"
>{formatDate(event.start_date)}</td
>
<td
class="py-3 px-4 text-[11px] text-light/40"
>{formatDate(event.end_date)}</td
>
<td
class="py-3 px-4 text-[11px] text-light/30"
>{timeAgo(event.created_at)}</td
>
<td class="py-3 px-4">
<div
class="flex items-center justify-end gap-1"
>
<button
type="button"
class="p-1.5 text-light/30 hover:text-white hover:bg-dark/50 rounded-lg transition-colors"
title="Edit event"
onclick={() =>
(editEventModal = {
id: event.id,
name: event.name,
slug: event.slug,
status: event.status,
start_date:
formatDateInput(
event.start_date,
),
end_date:
formatDateInput(
event.end_date,
),
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>edit</span
>
</button>
<button
type="button"
class="p-1.5 text-light/30 hover:text-red-400 hover:bg-red-400/10 rounded-lg transition-colors"
title="Delete event"
onclick={() =>
(confirmModal = {
type: "deleteEvent",
id: event.id,
name: event.name,
})}
>
<span
class="material-symbols-rounded"
style="font-size: 16px;"
>delete</span
>
</button>
</div>
</td>
</tr>
{/each}
{#if filteredEvents.length === 0}
<tr>
<td
colspan="7"
class="py-8 text-center text-body-sm text-light/30"
>
{eventSearch
? "No events match your search"
: "No events yet"}
</td>
</tr>
{/if}
</tbody>
</table>
</div>
<p class="text-[10px] text-light/30">
{filteredEvents.length} of {data.events.length} events
</p>
</div>
{/if}
</div>
</div>
<!-- Delete Confirmation Modal -->
<Modal
isOpen={!!confirmModal}
onClose={() => (confirmModal = null)}
title="Confirm Delete"
>
{#if confirmModal}
<p class="text-light/70 text-body-sm mb-2">
Are you sure you want to delete <strong class="text-white"
>{confirmModal.name}</strong
>?
</p>
<p class="text-red-400/70 text-[11px] mb-4">
This action cannot be undone. All associated data will be
permanently removed.
</p>
<form
method="POST"
action="?/{confirmModal.type}"
use:enhance={() => {
actionLoading = true;
return enhanceAction();
}}
>
<input type="hidden" name="id" value={confirmModal.id} />
<div class="flex gap-3 justify-end">
<Button
variant="secondary"
onclick={() => (confirmModal = null)}>Cancel</Button
>
<Button variant="danger" type="submit" loading={actionLoading}
>Delete</Button
>
</div>
</form>
{/if}
</Modal>
<!-- Edit Organization Modal -->
<Modal
isOpen={!!editOrgModal}
onClose={() => (editOrgModal = null)}
title="Edit Organization"
>
{#if editOrgModal}
<form
method="POST"
action="?/updateOrg"
use:enhance={() => {
actionLoading = true;
return enhanceAction();
}}
class="space-y-4"
>
<input type="hidden" name="id" value={editOrgModal.id} />
<Input label="Name" name="name" bind:value={editOrgModal.name} />
<Input label="Slug" name="slug" bind:value={editOrgModal.slug} />
<div class="flex gap-3 justify-end">
<Button
variant="secondary"
onclick={() => (editOrgModal = null)}>Cancel</Button
>
<Button type="submit" loading={actionLoading}>Save</Button>
</div>
</form>
{/if}
</Modal>
<!-- Edit User Modal -->
<Modal
isOpen={!!editUserModal}
onClose={() => (editUserModal = null)}
title="Edit User"
>
{#if editUserModal}
<form
method="POST"
action="?/updateUser"
use:enhance={() => {
actionLoading = true;
return enhanceAction();
}}
class="space-y-4"
>
<input type="hidden" name="id" value={editUserModal.id} />
<Input
label="Full Name"
name="full_name"
bind:value={editUserModal.full_name}
/>
<Input
label="Email"
name="email"
bind:value={editUserModal.email}
/>
<div class="flex items-center gap-3 py-2">
<label
class="flex items-center gap-2 text-body-sm text-light/60 cursor-pointer"
>
<input
type="checkbox"
bind:checked={editUserModal.is_platform_admin}
class="accent-primary"
/>
Platform Admin
</label>
</div>
<div class="flex gap-3 justify-end">
<Button
variant="secondary"
onclick={() => (editUserModal = null)}>Cancel</Button
>
<Button type="submit" loading={actionLoading}>Save</Button>
</div>
</form>
{/if}
</Modal>
<!-- Edit Event Modal -->
<Modal
isOpen={!!editEventModal}
onClose={() => (editEventModal = null)}
title="Edit Event"
>
{#if editEventModal}
<form
method="POST"
action="?/updateEvent"
use:enhance={() => {
actionLoading = true;
return enhanceAction();
}}
class="space-y-4"
>
<input type="hidden" name="id" value={editEventModal.id} />
<Input label="Name" name="name" bind:value={editEventModal.name} />
<Input label="Slug" name="slug" bind:value={editEventModal.slug} />
<Select
variant="compact"
label="Status"
name="status"
bind:value={editEventModal.status}
placeholder=""
options={eventStatuses.map((s) => ({
value: s,
label: s.charAt(0).toUpperCase() + s.slice(1),
}))}
/>
<div class="grid grid-cols-2 gap-3">
<Input
variant="compact"
type="date"
label="Start Date"
name="start_date"
bind:value={editEventModal.start_date}
/>
<Input
variant="compact"
type="date"
label="End Date"
name="end_date"
bind:value={editEventModal.end_date}
/>
</div>
<div class="flex gap-3 justify-end">
<Button
variant="secondary"
onclick={() => (editEventModal = null)}>Cancel</Button
>
<Button type="submit" loading={actionLoading}>Save</Button>
</div>
</form>
{/if}
</Modal>