diff --git a/frontend/src/lib/components/account/EditRequestModal.svelte b/frontend/src/lib/components/account/EditRequestModal.svelte index 05984b5..23aeb3d 100644 --- a/frontend/src/lib/components/account/EditRequestModal.svelte +++ b/frontend/src/lib/components/account/EditRequestModal.svelte @@ -8,7 +8,8 @@ import * as Textarea from '$lib/components/ui/textarea'; import * as Label from '$lib/components/ui/label'; import DatePicker from '$lib/components/booking/DatePicker.svelte'; - import type { Booking, Service, WorkingHoursDay, AvailableHoursDay } from '$lib/types/booking'; + import PolicyPopover from '$lib/components/ui/policyPopover.svelte'; + import type { Booking, BookingDiscount, Service, WorkingHoursDay, AvailableHoursDay } from '$lib/types/booking'; import { extractBookedSlots, getLunchProtectionForSlots, @@ -77,6 +78,28 @@ ); let placeholderDate = $state(minDate); + let hoursUntilAppointment = $derived( + (new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60) + ); + let hasPayments = $derived( + (booking.amount_paid ?? 0) > 0 + ); + let noticePeriodBlocked = $derived( + hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24 + ); + let noticePeriodWarning = $derived( + !hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0 + ); + let noticeBlockedMessage = $derived( + hasPayments + ? 'This booking has payments and is too close to the original appointment to reschedule online.' + : 'This booking is too close to the original appointment time to reschedule online.' + ); + + let discountTotal = $derived( + booking.discounts?.reduce((sum, d) => sum + d.discount_amount, 0) ?? 0 + ); + let bookingTotalDuration = $derived( booking.services?.reduce( (sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0), @@ -110,7 +133,7 @@ let modalTitle = $derived(() => { switch (editMode) { case 'select': - return 'Edit Request'; + return 'Edit/Reschedule'; case 'time': return 'Change Time'; case 'services': @@ -379,9 +402,10 @@ } }); + let servicesHoursFetched = $state(false); $effect(() => { - if (editMode === 'services') { - // Fetch hours for the booking's current date to calculate remaining time + if (editMode === 'services' && !servicesHoursFetched) { + servicesHoursFetched = true; const bookingDate = new SvelteDate(booking.start_time); const calDate = new CalendarDate( bookingDate.getFullYear(), @@ -390,6 +414,9 @@ ); fetchHoursForMonth(calDate); } + if (editMode !== 'services') { + servicesHoursFetched = false; + } }); // ─── Auto-select ──────────────────────────────────────── @@ -681,12 +708,16 @@ }); if (response.ok) { - toast.success("Edit request sent — we'll confirm shortly"); + const noShowWarning = response.headers.get('X-No-Show-Warning'); + if (noShowWarning) { + toast.warning(noShowWarning, { duration: 8000 }); + } + toast.success("Edit/reschedule request sent — we'll confirm shortly"); open = false; onSubmitted(); } else { const text = await response.text(); - toast.error(text || 'Failed to submit edit request'); + toast.error(text || 'Failed to submit edit/reschedule request'); } } catch { toast.error('Network error'); @@ -734,10 +765,54 @@

What would you like to change?

+ {#if noticePeriodBlocked} +
+

Cannot Reschedule Online

+

{noticeBlockedMessage}

+

+ Contact us to discuss options, + or + and rebook — note that cancellation fees may apply based on our + + {#snippet trigger()} + deposit policy + {/snippet} + . +

+
+ {:else if noticePeriodWarning} +
+

Rescheduling Within 24h

+

+ Rescheduling within 24h counts as a no-show towards your deposit obligations. Two no-shows + within 6 months will require deposits on future bookings. + + {#snippet trigger()} + Full policy + {/snippet} + +

+
+ {/if} + + {#if booking.discounts && booking.discounts.length > 0} +
+

Discounts Applied to This Booking

+

+ Your booking has £{discountTotal.toFixed(2)} in savings from loyalty stamps or + promotional offers. A time change requires admin approval. If denied, you can cancel + (standard refund policy applies) and rebook at full price. +

+
+ {/if} +