diff --git a/src/lib/components/admin/ProjectListItem.svelte b/src/lib/components/admin/ProjectListItem.svelte
index aa9be5e..409aca9 100644
--- a/src/lib/components/admin/ProjectListItem.svelte
+++ b/src/lib/components/admin/ProjectListItem.svelte
@@ -3,32 +3,18 @@
import { createEventDispatcher, onMount } from 'svelte'
import AdminByline from './AdminByline.svelte'
- interface Project {
- id: number
- title: string
- subtitle: string | null
- year: number
- client: string | null
- status: string
- projectType: string
- logoUrl: string | null
- backgroundColor: string | null
- highlightColor: string | null
- publishedAt: string | null
- createdAt: string
- updatedAt: string
- }
+ import type { AdminProject } from '$lib/types/admin'
interface Props {
- project: Project
+ project: AdminProject
}
let { project }: Props = $props()
const dispatch = createEventDispatcher<{
- edit: { project: Project }
- togglePublish: { project: Project }
- delete: { project: Project }
+ edit: { project: AdminProject }
+ togglePublish: { project: AdminProject }
+ delete: { project: AdminProject }
}>()
let isDropdownOpen = $state(false)
@@ -114,7 +100,12 @@
-
+
{/if}
diff --git a/src/lib/types/admin.ts b/src/lib/types/admin.ts
new file mode 100644
index 0000000..d391ed9
--- /dev/null
+++ b/src/lib/types/admin.ts
@@ -0,0 +1,15 @@
+export interface AdminProject {
+ id: number
+ title: string
+ subtitle: string | null
+ year: number
+ client: string | null
+ status: string
+ projectType: string
+ logoUrl: string | null
+ backgroundColor: string | null
+ highlightColor: string | null
+ publishedAt: string | null
+ createdAt: string
+ updatedAt: string
+}
diff --git a/src/routes/admin/projects/+page.server.ts b/src/routes/admin/projects/+page.server.ts
new file mode 100644
index 0000000..4e2a2d5
--- /dev/null
+++ b/src/routes/admin/projects/+page.server.ts
@@ -0,0 +1,85 @@
+import { fail } from '@sveltejs/kit'
+import type { Actions, PageServerLoad } from './$types'
+import { adminFetch, adminFetchJson } from '$lib/server/admin/authenticated-fetch'
+import type { AdminProject } from '$lib/types/admin'
+
+interface ProjectsResponse {
+ projects: AdminProject[]
+}
+
+function toStatusCounts(projects: AdminProject[]) {
+ return projects.reduce(
+ (counts, project) => {
+ counts.all += 1
+ counts[project.status as 'draft' | 'published'] += 1
+ return counts
+ },
+ { all: 0, published: 0, draft: 0 }
+ )
+}
+
+function toTypeCounts(projects: AdminProject[]) {
+ return projects.reduce(
+ (counts, project) => {
+ counts.all += 1
+ if (project.projectType === 'work') counts.work += 1
+ if (project.projectType === 'labs') counts.labs += 1
+ return counts
+ },
+ { all: 0, work: 0, labs: 0 }
+ )
+}
+
+export const load = (async (event) => {
+ event.depends('admin:projects')
+
+ const { projects } = await adminFetchJson(event, '/api/projects')
+
+ return {
+ items: projects,
+ filters: {
+ statusCounts: toStatusCounts(projects),
+ typeCounts: toTypeCounts(projects)
+ }
+ }
+}) satisfies PageServerLoad
+
+export const actions = {
+ toggleStatus: async (event) => {
+ const formData = await event.request.formData()
+ const id = Number(formData.get('id'))
+ const status = formData.get('status')
+ const updatedAt = formData.get('updatedAt')
+
+ if (!Number.isFinite(id) || typeof status !== 'string' || typeof updatedAt !== 'string') {
+ return fail(400, { message: 'Invalid toggle request' })
+ }
+
+ await adminFetch(event, `/api/projects/${id}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ status,
+ updatedAt
+ })
+ })
+
+ return { success: true }
+ },
+ delete: async (event) => {
+ const formData = await event.request.formData()
+ const id = Number(formData.get('id'))
+
+ if (!Number.isFinite(id)) {
+ return fail(400, { message: 'Invalid project id' })
+ }
+
+ await adminFetch(event, `/api/projects/${id}`, {
+ method: 'DELETE'
+ })
+
+ return { success: true }
+ }
+} satisfies Actions
diff --git a/src/routes/admin/projects/+page.svelte b/src/routes/admin/projects/+page.svelte
index baa9fb3..7273f05 100644
--- a/src/routes/admin/projects/+page.svelte
+++ b/src/routes/admin/projects/+page.svelte
@@ -1,7 +1,6 @@
- Projects - Admin @jedmund
+ Work - Admin @jedmund
-
+
{#snippet actions()}
- New Project
+ goto('/admin/projects/new')}
+ >
+ New project
+
{/snippet}
- {#if error}
- {error}
- {:else}
-
-
- {#snippet left()}
-
-
- {/snippet}
- {#snippet right()}
-
- {/snippet}
-
+
+ {#snippet left()}
+
+
+ {/snippet}
+ {#snippet right()}
+
+ {/snippet}
+
-
- {#if isLoading}
-
-
-
Loading projects...
-
- {:else if filteredProjects.length === 0}
-
-
- {#if selectedStatusFilter === 'all' && selectedTypeFilter === 'all'}
- No projects found. Create your first project!
- {:else}
- No projects found matching the current filters. Try adjusting your filters or create a
- new project.
- {/if}
-
-
- {:else}
-
- {#each filteredProjects as project}
-
- {/each}
-
- {/if}
+ {#if actionError}
+ {actionError}
+ {/if}
+
+ {#if filteredProjects.length === 0}
+
+
No projects found
+
+ {#if selectedTypeFilter === 'all' && selectedStatusFilter === 'all'}
+ Create your first project to get started!
+ {:else}
+ No projects found matching the current filters. Try adjusting your filters or create a new
+ project.
+ {/if}
+
+
+ {:else}
+
+ {#each filteredProjects as project (project.id)}
+
+ {/each}
+
{/if}
+
+
+
+