feat(api): add endpoint to fetch guest list

* implement ListGuests method in store
* create handleGuestList function in API
* add GuestListEntry and GuestList interfaces in UI
* create guest list page to display RSVPs
This commit is contained in:
2026-08-12 19:21:14 +02:00
parent 2c28b18098
commit df5d8efeb1
5 changed files with 152 additions and 6 deletions
+18
View File
@@ -28,6 +28,18 @@ export interface PhotoUploadPayload {
files: FileList;
}
export interface GuestListEntry {
name: string;
isChild: boolean;
}
export interface GuestList {
guests: GuestListEntry[];
total: number;
adults: number;
children: number;
}
export class ApiError extends Error {
constructor(
message: string,
@@ -62,6 +74,12 @@ export async function submitRsvp(payload: RsvpPayload): Promise<void> {
if (!response.ok) throw new ApiError(await parseError(response), response.status);
}
export async function fetchGuests(fetchImpl: typeof fetch = fetch): Promise<GuestList> {
const response = await fetchImpl('/api/guests');
if (!response.ok) throw new ApiError(await parseError(response), response.status);
return response.json();
}
export async function uploadPhotos(payload: PhotoUploadPayload): Promise<void> {
const form = new FormData();
form.set('name', payload.name);
+20 -6
View File
@@ -24,12 +24,12 @@
</script>
<svelte:head>
<title>Zoé &amp; Shaldon are getting married</title>
<link rel="icon" href={favicon} />
<title>Zoé &amp; Shaldon are getting married</title>
<link rel="icon" href={favicon} />
</svelte:head>
<div
class="bg-surface-50 dark:bg-surface-950 flex min-h-screen flex-col bg-[url('/patterns/damask.svg')] bg-repeat [background-size:160px_160px]"
class="bg-surface-50 dark:bg-surface-950 flex min-h-screen flex-col bg-[url('/patterns/damask.svg')] bg-repeat [background-size:160px_160px]"
>
<div class="sticky top-0 z-20 flex flex-col">
<nav
@@ -65,18 +65,32 @@
onclick={() => (menuOpen = !menuOpen)}
>
{#if menuOpen}
<svg viewBox="0 0 24 24" class="size-6" fill="none" stroke="currentColor" stroke-width="2">
<svg
viewBox="0 0 24 24"
class="size-6"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" d="M6 6l12 12M18 6L6 18" />
</svg>
{:else}
<svg viewBox="0 0 24 24" class="size-6" fill="none" stroke="currentColor" stroke-width="2">
<svg
viewBox="0 0 24 24"
class="size-6"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path stroke-linecap="round" d="M4 7h16M4 12h16M4 17h16" />
</svg>
{/if}
</button>
</nav>
{#if menuOpen}
<div class="bg-primary-950 border-primary-800 flex flex-col gap-1 border-t px-4 py-3 sm:hidden">
<div
class="bg-primary-950 border-primary-800 flex flex-col gap-1 border-t px-4 py-3 sm:hidden"
>
{#each links as link (link.href)}
<a
href={link.href}
+57
View File
@@ -0,0 +1,57 @@
<script lang="ts">
import { browser } from '$app/environment';
import { fetchGuests, ApiError, type GuestList } from '$lib/api';
let status = $state<'loading' | 'done' | 'error'>('loading');
let errorMessage = $state('');
let guestList = $state<GuestList>({ guests: [], total: 0, adults: 0, children: 0 });
$effect(() => {
if (!browser) return;
fetchGuests()
.then((list) => {
guestList = list;
status = 'done';
})
.catch((err) => {
status = 'error';
errorMessage = err instanceof ApiError ? err.message : 'Something went wrong. Please try again.';
});
});
</script>
<section class="flex flex-1 items-center justify-center px-4 py-16">
<div class="w-full max-w-md">
<h1 class="font-serif text-4xl">Who's coming</h1>
<p class="text-surface-600-400 mt-2 mb-8 text-2xl">
{#if status === 'done'}
{guestList.total}
{guestList.total === 1 ? 'guest has' : 'guests have'} RSVP'd
{#if guestList.children > 0}
&mdash; {guestList.adults} adults, {guestList.children} children
{/if}
{:else}
Everyone who has RSVP'd so far.
{/if}
</p>
{#if status === 'loading'}
<p class="text-surface-600-400 text-2xl">Loading…</p>
{:else if status === 'error'}
<p class="text-error-500 text-2xl">{errorMessage}</p>
{:else if guestList.total === 0}
<p class="text-surface-600-400 text-2xl">No RSVPs yet.</p>
{:else}
<ul class="flex flex-col gap-2">
{#each guestList.guests as guest, i (i)}
<li class="border-surface-300 dark:border-surface-700 flex items-center justify-between border-b py-2">
<span class="text-2xl">{guest.name}</span>
{#if guest.isChild}
<span class="preset-tonal-secondary rounded-base px-2 text-lg font-medium">Child</span>
{/if}
</li>
{/each}
</ul>
{/if}
</div>
</section>