feat: add email verification, profile pictures, deposits, and calendar

export
Backend:
- Add email verification code generation and verification endpoints
- Add profile picture upload with S3 storage and image processing
- Add deposit_required field to users with 48h advance booking
  requirement
- Add loyalty stamps that accumulate on completed bookings
- Auto-transition bookings: confirmed → in_progress → completed
- Add booking cancellation handler with no-show detection
- Add ICS calendar file download endpoint for bookings
- Sync bookings to CalDAV on confirmation
  Frontend:
- Add schedule page route
- Add avatar and image-cropper UI components
- Update shadcn-svelte components (button, dialog)
- Add "Add to Calendar" button in booking modal
  Database:
- Add verification_codes table
- Add profile_pic_url, loyalty_stamps, deposits_required to users
- Various schema updates
This commit is contained in:
2026-02-21 18:48:29 +00:00
parent 88d8469180
commit 970cc5554d
48 changed files with 1984 additions and 175 deletions
@@ -131,7 +131,7 @@
interval = setInterval(() => {
calculateTimes();
}, 60000); // Update every minute
}, 15000); // Update every 15 seconds
return () => {
if (interval) clearInterval(interval);
@@ -193,18 +193,22 @@
{#if activeAppointment}
{#if isInProgress}
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
<span class="relative mr-2 flex h-2 w-2">
<span
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75"
></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-blue-600"></span>
</span>
In Progress • {timeRemaining} min remaining
{#if freeTimeAfter > 0}
{freeTimeAfter} min free
<div class="flex flex-col gap-2 items-end">
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
<span class="relative mr-2 flex h-2 w-2">
<span
class="absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75"
></span>
<span class="relative inline-flex h-2 w-2 rounded-full bg-blue-600"></span>
</span>
In Progress • {timeRemaining} min remaining
</Badge>
{#if freeTimeAfter > 0 && timeRemaining > 29}
<Badge class="bg-blue-100 px-3 py-1 text-sm text-blue-800">
{freeTimeAfter} min free afterwards
</Badge>
{/if}
</Badge>
</div>
{:else}
<Badge class="bg-amber-100 px-3 py-1 text-sm text-amber-800">
Starts in {timeRemaining} min
@@ -259,10 +263,11 @@
class="h-20 w-20 rounded-full object-cover ring-4 ring-blue-200"
/>
{:else}
{@const initials = activeAppointment.user?.full_name?.split(' ').map(n => n[0]).join('') || '?'}
<div
class="flex h-20 w-20 items-center justify-center rounded-full bg-gray-200 text-2xl font-bold text-gray-600 ring-4 ring-blue-200"
>
{activeAppointment.user?.full_name?.charAt(0) || '?'}
{initials}
</div>
{/if}
<div>