* implement handleListPhotos to retrieve uploaded photos * create PhotoFileRecord struct for photo data * add fetchGuestPhotos function in the UI * display guest photos in the photo upload page * remove index.html as it is no longer needed * add calendar functionality to download wedding event
146 lines
4.5 KiB
Svelte
146 lines
4.5 KiB
Svelte
<script lang="ts">
|
|
import { browser } from '$app/environment';
|
|
import { uploadPhotos, fetchGuestPhotos, ApiError, type GuestPhoto } from '$lib/api';
|
|
import PhotoGallery from '$lib/components/PhotoGallery.svelte';
|
|
|
|
const MAX_FILES = 10;
|
|
const MAX_SIZE_MB = 15;
|
|
|
|
let name = $state('');
|
|
let message = $state('');
|
|
let files: FileList | undefined = $state();
|
|
|
|
let status = $state<'idle' | 'submitting' | 'done' | 'error'>('idle');
|
|
let errorMessage = $state('');
|
|
|
|
let guestPhotos = $state<GuestPhoto[]>([]);
|
|
let guestPhotosStatus = $state<'loading' | 'done' | 'error'>('loading');
|
|
|
|
function loadGuestPhotos() {
|
|
fetchGuestPhotos()
|
|
.then((photos) => {
|
|
guestPhotos = photos;
|
|
guestPhotosStatus = 'done';
|
|
})
|
|
.catch(() => {
|
|
guestPhotosStatus = 'error';
|
|
});
|
|
}
|
|
|
|
$effect(() => {
|
|
if (!browser) return;
|
|
loadGuestPhotos();
|
|
});
|
|
|
|
async function onSubmit(event: SubmitEvent) {
|
|
event.preventDefault();
|
|
if (!files || files.length === 0) {
|
|
status = 'error';
|
|
errorMessage = 'Please choose at least one photo.';
|
|
return;
|
|
}
|
|
if (files.length > MAX_FILES) {
|
|
status = 'error';
|
|
errorMessage = `Please upload at most ${MAX_FILES} photos at a time.`;
|
|
return;
|
|
}
|
|
for (const file of files) {
|
|
if (file.size > MAX_SIZE_MB * 1024 * 1024) {
|
|
status = 'error';
|
|
errorMessage = `"${file.name}" is larger than ${MAX_SIZE_MB}MB.`;
|
|
return;
|
|
}
|
|
}
|
|
|
|
status = 'submitting';
|
|
errorMessage = '';
|
|
try {
|
|
await uploadPhotos({
|
|
name,
|
|
message: message || undefined,
|
|
files
|
|
});
|
|
status = 'done';
|
|
loadGuestPhotos();
|
|
} 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">
|
|
<PhotoGallery />
|
|
|
|
{#if status === 'done'}
|
|
<div class="preset-filled-primary-500 rounded-base p-4 text-center">
|
|
Thank you — your photos have been uploaded!
|
|
</div>
|
|
{:else}
|
|
<h1 class="font-serif text-4xl">Share your photos</h1>
|
|
<p class="text-surface-600-400 mt-2 mb-8 text-2xl">
|
|
Upload your photos of Zoé & Shaldon — up to {MAX_FILES} at a time, {MAX_SIZE_MB}MB each.
|
|
</p>
|
|
|
|
<form class="flex flex-col gap-4" onsubmit={onSubmit}>
|
|
<label class="label">
|
|
<span class="label-text text-2xl">Name *</span>
|
|
<input class="input border border-surface-300 focus:border-primary-500 dark:border-surface-700 text-2xl" type="text" required bind:value={name} disabled={status === 'submitting'} />
|
|
</label>
|
|
|
|
<label class="label">
|
|
<span class="label-text text-2xl">Message to the couple (optional)</span>
|
|
<textarea class="textarea border border-surface-300 focus:border-primary-500 dark:border-surface-700 text-2xl" rows="3" bind:value={message} disabled={status === 'submitting'}
|
|
></textarea>
|
|
</label>
|
|
|
|
<label class="label">
|
|
<span class="label-text text-2xl">Photos *</span>
|
|
<input
|
|
class="input border border-surface-300 focus:border-primary-500 dark:border-surface-700 text-2xl"
|
|
type="file"
|
|
accept="image/*"
|
|
multiple
|
|
required
|
|
bind:files
|
|
disabled={status === 'submitting'}
|
|
/>
|
|
</label>
|
|
|
|
{#if status === 'error'}
|
|
<p class="text-error-500 text-2xl">{errorMessage}</p>
|
|
{/if}
|
|
|
|
<button class="btn preset-filled-secondary-500 mt-2 text-2xl" type="submit" disabled={status === 'submitting'}>
|
|
{status === 'submitting' ? 'Uploading…' : 'Upload photos'}
|
|
</button>
|
|
</form>
|
|
{/if}
|
|
|
|
<div class="border-surface-300 dark:border-surface-700 mt-10 border-t pt-6">
|
|
<h2 class="font-serif text-center text-3xl">Photos From Our Guests</h2>
|
|
{#if guestPhotosStatus === 'loading'}
|
|
<p class="text-surface-600-400 mt-4 text-center text-2xl">Loading…</p>
|
|
{:else if guestPhotosStatus === 'error'}
|
|
<p class="text-error-500 mt-4 text-center text-2xl">Could not load guest photos.</p>
|
|
{:else if guestPhotos.length === 0}
|
|
<p class="text-surface-600-400 mt-4 text-center text-2xl">No photos yet — be the first to share!</p>
|
|
{:else}
|
|
<div class="mt-4 grid grid-cols-3 gap-2">
|
|
{#each guestPhotos as photo (photo.url)}
|
|
<a href={photo.url} target="_blank" rel="noopener noreferrer">
|
|
<img
|
|
src={photo.url}
|
|
alt="Shared by {photo.uploaderName}"
|
|
class="border-surface-300 dark:border-surface-700 aspect-square w-full rounded-base border object-cover"
|
|
loading="lazy"
|
|
/>
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</section>
|