Adds ability to expand photos, and upload both from camera and from gallery

This commit is contained in:
2026-05-18 13:44:23 -05:00
parent 2ba8b60063
commit 67fe81dc41
2 changed files with 145 additions and 23 deletions

View File

@@ -41,7 +41,9 @@
let itemPhotos = $state<PhotoResponse[]>([])
let photosLoading = $state(false)
let submitting = $state(false)
let expandedPhoto = $state<PhotoResponse | null>(null)
let fileInput = $state<HTMLInputElement | undefined>()
let cameraInput = $state<HTMLInputElement | undefined>()
let _uploading = false
let _errorTimer: ReturnType<typeof setTimeout> | null = null
@@ -263,6 +265,40 @@
{/if}
</div>
<!-- Photo lightbox -->
{#if expandedPhoto}
<div
class="fixed inset-0 z-60 bg-black/90 flex flex-col"
role="dialog"
aria-modal="true"
>
<div class="flex items-center justify-between px-4 py-3 shrink-0">
<div>
<p class="font-semibold text-white">{expandedPhoto.hunterName}</p>
<p class="text-xs text-white/40">{new Date(expandedPhoto.photoUploadDateTime).toLocaleString()}</p>
</div>
<div class="flex items-center gap-3">
<StatusBadge status={expandedPhoto.photoStatus} />
<button class="btn btn-ghost btn-sm btn-circle text-white" onclick={() => expandedPhoto = null}>✕</button>
</div>
</div>
<div class="flex-1 min-h-0 flex items-center justify-center px-4">
<AuthImage photoId={expandedPhoto.id} version="LARGE" alt={expandedPhoto.hunterName} class="max-h-full max-w-full object-contain rounded-lg" />
</div>
{#if expandedPhoto.photoStatus === 'SUBMITTED' || expandedPhoto.photoStatus === 'REJECTED'}
<div class="px-4 py-4 shrink-0">
<button class="btn btn-ghost btn-sm w-full text-white/40" onclick={() => { removePhoto(expandedPhoto!); expandedPhoto = null }}>
Remove
</button>
</div>
{:else}
<div class="pb-4"></div>
{/if}
</div>
{/if}
<!-- Item detail bottom sheet -->
{#if selectedItem}
<div class="fixed inset-0 z-40" onclick={closeSheet} role="presentation"></div>
@@ -275,28 +311,47 @@
<button class="btn btn-ghost btn-sm btn-circle" onclick={closeSheet}>✕</button>
</div>
<div class="p-4 flex flex-col gap-4">
<button
type="button"
class="btn btn-primary w-full gap-2"
class:opacity-50={submitting}
disabled={submitting}
onclick={() => fileInput?.click()}
>
{#if submitting}
{#if submitting}
<button type="button" class="btn btn-primary w-full gap-2 opacity-50" disabled>
<span class="loading loading-spinner loading-sm"></span>
Uploading…
{:else}
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
Take / Upload Photo
{/if}
</button>
</button>
{:else}
<div class="flex gap-2">
<button
type="button"
class="btn btn-primary flex-1 gap-2"
onclick={() => cameraInput?.click()}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 5a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2V7a2 2 0 00-2-2h-1.586a1 1 0 01-.707-.293l-1.121-1.121A2 2 0 0011.172 3H8.828a2 2 0 00-1.414.586L6.293 4.707A1 1 0 015.586 5H4zm6 9a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd" />
</svg>
Camera
</button>
<button
type="button"
class="btn btn-outline flex-1 gap-2"
onclick={() => fileInput?.click()}
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z" clip-rule="evenodd" />
</svg>
Gallery
</button>
</div>
{/if}
<input
bind:this={cameraInput}
type="file"
accept="image/*"
capture="environment"
class="hidden"
onchange={handleFileChange}
/>
<input
bind:this={fileInput}
type="file"
accept="image/*"
capture="environment"
class="hidden"
onchange={handleFileChange}
/>
@@ -307,13 +362,15 @@
<div class="flex flex-col gap-3">
{#each itemPhotos.filter(p => p.photoStatus !== 'REMOVED') as photo (photo.id)}
<div class="card bg-base-200 overflow-hidden">
<AuthImage photoId={photo.id} version="LARGE" alt={photo.hunterName} class="w-full h-48 object-cover" />
<button class="w-full cursor-zoom-in" onclick={() => expandedPhoto = photo}>
<AuthImage photoId={photo.id} version="SMALL" alt={photo.hunterName} class="w-full h-48 object-cover" />
</button>
<div class="p-3 flex items-center justify-between">
<div>
<p class="text-sm font-medium">{photo.hunterName}</p>
<StatusBadge status={photo.photoStatus} />
</div>
{#if photo.photoStatus === 'SUBMITTED'}
{#if photo.photoStatus === 'SUBMITTED' || photo.photoStatus === 'REJECTED'}
<button class="btn btn-ghost btn-xs text-error" onclick={() => removePhoto(photo)}>Remove</button>
{/if}
</div>