refactor: use common TagChip, switch to SSR for admin pages, better error & logger handling

This commit is contained in:
2026-01-14 11:44:46 -06:00
parent cba0cbb704
commit 08c5dcda3b
18 changed files with 831 additions and 408 deletions
@@ -0,0 +1,22 @@
import type { PageServerLoad } from "./$types";
import { apiFetch } from "$lib/api.server";
import { addIconsToTags } from "$lib/server/tag-icons";
import type { AdminProject, AdminTagWithCount } from "$lib/admin-types";
export const load: PageServerLoad = async ({ params, fetch }) => {
const { id } = params;
// Fetch project and tags in parallel
const [project, tagsWithCounts] = await Promise.all([
apiFetch<AdminProject>(`/api/projects/${id}`, { fetch }).catch(() => null),
apiFetch<AdminTagWithCount[]>("/api/tags", { fetch }),
]);
// Add icons to tags
const availableTags = await addIconsToTags(tagsWithCounts);
return {
project,
availableTags,
};
};
+18 -47
View File
@@ -1,56 +1,29 @@
<script lang="ts">
import { page } from "$app/stores";
import { goto } from "$app/navigation";
import { resolve } from "$app/paths";
import ProjectForm from "$lib/components/admin/ProjectForm.svelte";
import { getAdminProject, getAdminTags, updateAdminProject } from "$lib/api";
import { updateAdminProject } from "$lib/api";
import type {
AdminProject,
AdminTag,
AdminTagWithCount,
CreateProjectData,
UpdateProjectData,
CreateProjectData,
TagWithIcon,
} from "$lib/admin-types";
const projectId = $derived(($page.params as { id: string }).id);
let project = $state<AdminProject | null>(null);
let tags = $state<AdminTag[]>([]);
let loading = $state(true);
async function loadData() {
try {
const [projectData, tagsWithCounts] = await Promise.all([
getAdminProject(projectId),
getAdminTags(),
]);
project = projectData;
tags = tagsWithCounts.map(
(t: AdminTagWithCount): AdminTag => ({
id: t.id,
slug: t.slug,
name: t.name,
createdAt: t.createdAt,
}),
);
} catch (error) {
console.error("Failed to load data:", error);
alert("Failed to load project");
goto(resolve("/admin/projects"));
} finally {
loading = false;
}
interface Props {
data: {
project: import("$lib/admin-types").AdminProject | null;
availableTags: TagWithIcon[];
};
}
$effect(() => {
loadData();
});
let { data }: Props = $props();
async function handleSubmit(formData: CreateProjectData) {
if (!data.project) return;
async function handleSubmit(data: CreateProjectData) {
const updateData: UpdateProjectData = {
...data,
id: projectId,
...formData,
id: data.project.id,
};
await updateAdminProject(updateData);
goto(resolve("/admin/projects"));
@@ -71,23 +44,21 @@
</div>
<!-- Form -->
{#if loading}
<div class="text-center py-12 text-admin-text-muted">Loading...</div>
{:else if !project}
{#if !data.project}
<div class="text-center py-12">
<p class="text-admin-text-muted mb-4">Project not found</p>
<a
href={resolve("/admin/projects")}
class="text-admin-accent hover:text-admin-accent-hover"
>
Back to projects
Back to projects
</a>
</div>
{:else}
<div class="rounded-lg border border-admin-border bg-admin-surface p-6">
<ProjectForm
{project}
availableTags={tags}
project={data.project}
availableTags={data.availableTags}
onsubmit={handleSubmit}
submitLabel="Update Project"
/>