style: fix prefer-const and prettier formatting issues
This commit is contained in:
@@ -89,57 +89,57 @@
|
|||||||
);
|
);
|
||||||
let placeholderDate = $state<CalendarDate>(minDate);
|
let placeholderDate = $state<CalendarDate>(minDate);
|
||||||
|
|
||||||
let hoursUntilAppointment = $derived(
|
const hoursUntilAppointment = $derived(
|
||||||
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
||||||
);
|
);
|
||||||
let hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||||
let noticePeriodBlocked = $derived(
|
const noticePeriodBlocked = $derived(
|
||||||
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
||||||
);
|
);
|
||||||
let noticePeriodWarning = $derived(
|
const noticePeriodWarning = $derived(
|
||||||
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
||||||
);
|
);
|
||||||
let noticeBlockedMessage = $derived(
|
const noticeBlockedMessage = $derived(
|
||||||
hasPayments
|
hasPayments
|
||||||
? 'This booking has payments and is too close to the original appointment to reschedule online.'
|
? '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.'
|
: 'This booking is too close to the original appointment time to reschedule online.'
|
||||||
);
|
);
|
||||||
|
|
||||||
let discountTotal = $derived(
|
const discountTotal = $derived(
|
||||||
booking.discounts?.reduce((sum, d) => sum + d.discount_amount, 0) ?? 0
|
booking.discounts?.reduce((sum, d) => sum + d.discount_amount, 0) ?? 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let bookingTotalDuration = $derived(
|
const bookingTotalDuration = $derived(
|
||||||
booking.services?.reduce(
|
booking.services?.reduce(
|
||||||
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
||||||
0
|
0
|
||||||
) || 0
|
) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let selectedServicesDuration = $derived(
|
const selectedServicesDuration = $derived(
|
||||||
selectedServices.reduce((sum, s) => sum + (s.duration_minutes ?? 0), 0)
|
selectedServices.reduce((sum, s) => sum + (s.duration_minutes ?? 0), 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let hasOverrides = $derived(
|
const hasOverrides = $derived(
|
||||||
booking?.services?.some(
|
booking?.services?.some(
|
||||||
(s) => s.override_price != null || s.override_duration_minutes != null
|
(s) => s.override_price != null || s.override_duration_minutes != null
|
||||||
) ?? false
|
) ?? false
|
||||||
);
|
);
|
||||||
|
|
||||||
let slotDuration = $derived(
|
const slotDuration = $derived(
|
||||||
editMode === 'time' ? bookingTotalDuration : selectedServicesDuration
|
editMode === 'time' ? bookingTotalDuration : selectedServicesDuration
|
||||||
);
|
);
|
||||||
|
|
||||||
let canSubmit = $derived(
|
const canSubmit = $derived(
|
||||||
submitting === false &&
|
submitting === false &&
|
||||||
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
|
((editMode === 'time' && !!newDate && newTime.length >= 4) ||
|
||||||
(editMode === 'services' && selectedServices.length > 0) ||
|
(editMode === 'services' && selectedServices.length > 0) ||
|
||||||
(editMode === 'both-time' && !!newDate && newTime.length >= 4))
|
(editMode === 'both-time' && !!newDate && newTime.length >= 4))
|
||||||
);
|
);
|
||||||
|
|
||||||
let notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
const notesChanged = $derived(notes.trim() !== originalNotes.trim());
|
||||||
|
|
||||||
let modalTitle = $derived(() => {
|
const modalTitle = $derived(() => {
|
||||||
switch (editMode) {
|
switch (editMode) {
|
||||||
case 'select':
|
case 'select':
|
||||||
return 'Edit/Reschedule';
|
return 'Edit/Reschedule';
|
||||||
@@ -193,7 +193,7 @@
|
|||||||
|
|
||||||
function calculateEndTime(startTime: string, durationMinutes: number): string {
|
function calculateEndTime(startTime: string, durationMinutes: number): string {
|
||||||
const [hours, minutes] = startTime.split(':').map(Number);
|
const [hours, minutes] = startTime.split(':').map(Number);
|
||||||
let total = hours * 60 + minutes + durationMinutes;
|
const total = hours * 60 + minutes + durationMinutes;
|
||||||
const h = Math.floor(total / 60);
|
const h = Math.floor(total / 60);
|
||||||
const m = total % 60;
|
const m = total % 60;
|
||||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||||
@@ -201,7 +201,7 @@
|
|||||||
|
|
||||||
function calculatePreviousTime(time: string): string {
|
function calculatePreviousTime(time: string): string {
|
||||||
const [h, m] = time.split(':').map(Number);
|
const [h, m] = time.split(':').map(Number);
|
||||||
let total = h * 60 + m - 15;
|
const total = h * 60 + m - 15;
|
||||||
return `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(total % 60).padStart(2, '0')}`;
|
return `${String(Math.floor(total / 60)).padStart(2, '0')}:${String(total % 60).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -654,7 +654,7 @@
|
|||||||
return Math.max(0, Math.min(nextBookingStart, workingEndMinutes) - bookingEndMinutes);
|
return Math.max(0, Math.min(nextBookingStart, workingEndMinutes) - bookingEndMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableAdditionalServices = $derived(() => {
|
const availableAdditionalServices = $derived(() => {
|
||||||
if (editMode !== 'services') return [];
|
if (editMode !== 'services') return [];
|
||||||
const remaining = calculateRemainingTime();
|
const remaining = calculateRemainingTime();
|
||||||
if (remaining <= 0) return [];
|
if (remaining <= 0) return [];
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
let { open = $bindable(), bookingId }: Props = $props();
|
let { open = $bindable(), bookingId }: Props = $props();
|
||||||
|
|
||||||
let selectedBooking = $state<Booking | null>(null);
|
let selectedBooking = $state<Booking | null>(null);
|
||||||
let businessSettings = $derived(getBusinessInfo());
|
const businessSettings = $derived(getBusinessInfo());
|
||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
let hasPendingEditRequest = $state(false);
|
let hasPendingEditRequest = $state(false);
|
||||||
let pendingEditRequest = $state<{
|
let pendingEditRequest = $state<{
|
||||||
@@ -44,80 +44,80 @@
|
|||||||
|
|
||||||
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
||||||
// customised at booking time. Showing base values misleads users about what was booked.
|
// customised at booking time. Showing base values misleads users about what was booked.
|
||||||
let totalDuration = $derived(
|
const totalDuration = $derived(
|
||||||
selectedBooking?.services?.reduce(
|
selectedBooking?.services?.reduce(
|
||||||
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
||||||
0
|
0
|
||||||
) || 0
|
) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let isFutureBooking = $derived(
|
const isFutureBooking = $derived(
|
||||||
selectedBooking ? new SvelteDate(selectedBooking.start_time) > new SvelteDate() : false
|
selectedBooking ? new SvelteDate(selectedBooking.start_time) > new SvelteDate() : false
|
||||||
);
|
);
|
||||||
|
|
||||||
let hasPayments = $derived(
|
const hasPayments = $derived(
|
||||||
selectedBooking && selectedBooking.payments && selectedBooking.payments.length > 0
|
selectedBooking && selectedBooking.payments && selectedBooking.payments.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let isCancellable = $derived(
|
const isCancellable = $derived(
|
||||||
selectedBooking && isFutureBooking && ['pending', 'confirmed'].includes(selectedBooking.status)
|
selectedBooking && isFutureBooking && ['pending', 'confirmed'].includes(selectedBooking.status)
|
||||||
);
|
);
|
||||||
|
|
||||||
let canEditBooking = $derived(isCancellable);
|
const canEditBooking = $derived(isCancellable);
|
||||||
|
|
||||||
let totalPaid = $derived(
|
const totalPaid = $derived(
|
||||||
selectedBooking?.payments
|
selectedBooking?.payments
|
||||||
?.filter((p) => p.status === 'completed')
|
?.filter((p) => p.status === 'completed')
|
||||||
.reduce((sum, p) => sum + p.amount, 0) || 0
|
.reduce((sum, p) => sum + p.amount, 0) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalRefunds = $derived(
|
const totalRefunds = $derived(
|
||||||
(selectedBooking?.refunds ?? [])
|
(selectedBooking?.refunds ?? [])
|
||||||
.filter((r) => r.status === 'completed')
|
.filter((r) => r.status === 'completed')
|
||||||
.reduce((sum, r) => sum + r.amount, 0)
|
.reduce((sum, r) => sum + r.amount, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||||
|
|
||||||
let totalVAT = $derived(
|
const totalVAT = $derived(
|
||||||
selectedBooking?.payments
|
selectedBooking?.payments
|
||||||
?.filter((p) => p.status === 'completed' && p.vat_amount)
|
?.filter((p) => p.status === 'completed' && p.vat_amount)
|
||||||
.reduce((sum, p) => sum + (p.vat_amount ?? 0), 0) || 0
|
.reduce((sum, p) => sum + (p.vat_amount ?? 0), 0) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalNet = $derived(
|
const totalNet = $derived(
|
||||||
selectedBooking?.payments
|
selectedBooking?.payments
|
||||||
?.filter((p) => p.status === 'completed' && p.net_amount)
|
?.filter((p) => p.status === 'completed' && p.net_amount)
|
||||||
.reduce((sum, p) => sum + (p.net_amount ?? 0), 0) || 0
|
.reduce((sum, p) => sum + (p.net_amount ?? 0), 0) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let hasVAT = $derived(totalVAT > 0);
|
const hasVAT = $derived(totalVAT > 0);
|
||||||
|
|
||||||
let depositOutstanding = $derived(
|
const depositOutstanding = $derived(
|
||||||
selectedBooking?.deposit_required && !selectedBooking?.deposit_paid
|
selectedBooking?.deposit_required && !selectedBooking?.deposit_paid
|
||||||
);
|
);
|
||||||
|
|
||||||
let canPayEarly = $derived(
|
const canPayEarly = $derived(
|
||||||
selectedBooking &&
|
selectedBooking &&
|
||||||
!depositOutstanding &&
|
!depositOutstanding &&
|
||||||
totalPaid < selectedBooking.total_amount &&
|
totalPaid < selectedBooking.total_amount &&
|
||||||
['confirmed', 'in_progress'].includes(selectedBooking.status)
|
['confirmed', 'in_progress'].includes(selectedBooking.status)
|
||||||
);
|
);
|
||||||
|
|
||||||
let isCompleted = $derived(selectedBooking?.status === 'completed');
|
const isCompleted = $derived(selectedBooking?.status === 'completed');
|
||||||
|
|
||||||
let hoursUntilAppointment = $derived(
|
const hoursUntilAppointment = $derived(
|
||||||
selectedBooking
|
selectedBooking
|
||||||
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
||||||
(1000 * 60 * 60)
|
(1000 * 60 * 60)
|
||||||
: Infinity
|
: Infinity
|
||||||
);
|
);
|
||||||
let protectedDeposit = $derived(
|
const protectedDeposit = $derived(
|
||||||
selectedBooking
|
selectedBooking
|
||||||
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
let estimatedRefund = $derived(
|
const estimatedRefund = $derived(
|
||||||
hoursUntilAppointment > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
hoursUntilAppointment > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
||||||
? totalPaid
|
? totalPaid
|
||||||
: hoursUntilAppointment >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS
|
: hoursUntilAppointment >= POLICY.PARTIAL_REFUND_THRESHOLD_HOURS
|
||||||
@@ -133,11 +133,11 @@
|
|||||||
let customTipInput = $state('');
|
let customTipInput = $state('');
|
||||||
let tipProcessing = $state(false);
|
let tipProcessing = $state(false);
|
||||||
|
|
||||||
let canSaveCards = $derived(
|
const canSaveCards = $derived(
|
||||||
authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
|
authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
|
||||||
);
|
);
|
||||||
|
|
||||||
let tipPresets = $derived(
|
const tipPresets = $derived(
|
||||||
selectedBooking
|
selectedBooking
|
||||||
? [
|
? [
|
||||||
{ pct: 10, amount: Math.round(selectedBooking.total_amount * 0.1 * 100) / 100 },
|
{ pct: 10, amount: Math.round(selectedBooking.total_amount * 0.1 * 100) / 100 },
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let isCustomFormValid = $derived(
|
const isCustomFormValid = $derived(
|
||||||
(newCustomService.name ?? '').trim() !== '' &&
|
(newCustomService.name ?? '').trim() !== '' &&
|
||||||
!customServiceErrors.name &&
|
!customServiceErrors.name &&
|
||||||
!customServiceErrors.price &&
|
!customServiceErrors.price &&
|
||||||
|
|||||||
@@ -32,29 +32,29 @@
|
|||||||
let cancelling = $state(false);
|
let cancelling = $state(false);
|
||||||
|
|
||||||
// Derived values for cancel confirmation
|
// Derived values for cancel confirmation
|
||||||
let totalPaid = $derived(
|
const totalPaid = $derived(
|
||||||
(selectedBooking?.payments ?? [])
|
(selectedBooking?.payments ?? [])
|
||||||
.filter((p) => p.payment_method !== 'discount' && p.status === 'completed')
|
.filter((p) => p.payment_method !== 'discount' && p.status === 'completed')
|
||||||
.reduce((sum, p) => sum + p.amount, 0)
|
.reduce((sum, p) => sum + p.amount, 0)
|
||||||
);
|
);
|
||||||
let totalRefunds = $derived(
|
const totalRefunds = $derived(
|
||||||
(selectedBooking?.refunds ?? [])
|
(selectedBooking?.refunds ?? [])
|
||||||
.filter((r) => r.status === 'completed')
|
.filter((r) => r.status === 'completed')
|
||||||
.reduce((sum, r) => sum + r.amount, 0)
|
.reduce((sum, r) => sum + r.amount, 0)
|
||||||
);
|
);
|
||||||
let balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
|
||||||
let hoursUntilAppt = $derived(
|
const hoursUntilAppt = $derived(
|
||||||
selectedBooking
|
selectedBooking
|
||||||
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
|
||||||
(1000 * 60 * 60)
|
(1000 * 60 * 60)
|
||||||
: Infinity
|
: Infinity
|
||||||
);
|
);
|
||||||
let protectedDeposit = $derived(
|
const protectedDeposit = $derived(
|
||||||
selectedBooking
|
selectedBooking
|
||||||
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
? Math.min(totalPaid, selectedBooking.total_amount * POLICY.PROTECTED_DEPOSIT_MAX_PCT)
|
||||||
: 0
|
: 0
|
||||||
);
|
);
|
||||||
let estimatedRefund = $derived(
|
const estimatedRefund = $derived(
|
||||||
forgiveFeesCancel
|
forgiveFeesCancel
|
||||||
? totalPaid
|
? totalPaid
|
||||||
: hoursUntilAppt > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
: hoursUntilAppt > POLICY.FULL_REFUND_THRESHOLD_HOURS
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
// IMPORTANT: Use override_duration_minutes when present — services may have been
|
||||||
// customised at booking time (discounts, extended sessions). Showing base values
|
// customised at booking time (discounts, extended sessions). Showing base values
|
||||||
// misleads admins about what was actually booked.
|
// misleads admins about what was actually booked.
|
||||||
let totalDuration = $derived(
|
const totalDuration = $derived(
|
||||||
selectedBooking?.services?.reduce(
|
selectedBooking?.services?.reduce(
|
||||||
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
(sum, service) => sum + (service.override_duration_minutes ?? service.duration_minutes ?? 0),
|
||||||
0
|
0
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
let { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
|
const { openBookingModal }: { openBookingModal: (bookingId: string) => void } = $props();
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let bookings = $state<Booking[]>([]);
|
let bookings = $state<Booking[]>([]);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
let services = $state<CustomService[]>([]);
|
let services = $state<CustomService[]>([]);
|
||||||
let total = $state(0);
|
let total = $state(0);
|
||||||
let page = $state(1);
|
let page = $state(1);
|
||||||
let perPage = $state(10);
|
const perPage = $state(10);
|
||||||
let searchQuery = $state('');
|
let searchQuery = $state('');
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let creating = $state(false);
|
let creating = $state(false);
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
serviceErrors.minimum_age_required = validateMinimumAge(newService.minimum_age_required);
|
serviceErrors.minimum_age_required = validateMinimumAge(newService.minimum_age_required);
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFormValid = $derived(
|
const isFormValid = $derived(
|
||||||
(newService.name ?? '').trim() !== '' &&
|
(newService.name ?? '').trim() !== '' &&
|
||||||
!serviceErrors.name &&
|
!serviceErrors.name &&
|
||||||
!serviceErrors.price &&
|
!serviceErrors.price &&
|
||||||
|
|||||||
@@ -92,7 +92,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let isFormValid = $derived.by(() => {
|
const isFormValid = $derived.by(() => {
|
||||||
if (!form.name.trim()) return false;
|
if (!form.name.trim()) return false;
|
||||||
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
|
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
|
||||||
if (form.campaign_type === 'time_based') {
|
if (form.campaign_type === 'time_based') {
|
||||||
|
|||||||
@@ -149,11 +149,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalDuration = $derived(
|
const totalDuration = $derived(
|
||||||
services.reduce((sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0), 0)
|
services.reduce((sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0), 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalPrice = $derived(
|
const totalPrice = $derived(
|
||||||
services.reduce((sum, s) => sum + (s.override_price ?? s.price ?? 0), 0)
|
services.reduce((sum, s) => sum + (s.override_price ?? s.price ?? 0), 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@
|
|||||||
return services.some((s) => s.service_id === serviceId);
|
return services.some((s) => s.service_id === serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
let maxAvailableDuration = $derived.by(() => {
|
const maxAvailableDuration = $derived.by(() => {
|
||||||
if (!booking?.start_time || !nextAppointmentStart) return null;
|
if (!booking?.start_time || !nextAppointmentStart) return null;
|
||||||
|
|
||||||
const bookingStart = new SvelteDate(booking.start_time).getTime();
|
const bookingStart = new SvelteDate(booking.start_time).getTime();
|
||||||
@@ -292,7 +292,7 @@
|
|||||||
return Math.max(0, Math.floor(availableMs / (60 * 1000)));
|
return Math.max(0, Math.floor(availableMs / (60 * 1000)));
|
||||||
});
|
});
|
||||||
|
|
||||||
let filteredServices = $derived.by(() => {
|
const filteredServices = $derived.by(() => {
|
||||||
return availableServices.filter((s) => {
|
return availableServices.filter((s) => {
|
||||||
if (isServiceAdded(s.id)) return false;
|
if (isServiceAdded(s.id)) return false;
|
||||||
if (maxAvailableDuration !== null && s.duration_minutes > maxAvailableDuration) return false;
|
if (maxAvailableDuration !== null && s.duration_minutes > maxAvailableDuration) return false;
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let serviceDiff = $derived.by(() => {
|
const serviceDiff = $derived.by(() => {
|
||||||
const orig = editRequest.original.services;
|
const orig = editRequest.original.services;
|
||||||
const prop = editRequest.proposed.services;
|
const prop = editRequest.proposed.services;
|
||||||
const origIds = new Set(orig.map((s) => s.id));
|
const origIds = new Set(orig.map((s) => s.id));
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
user_balances: UserBalance[];
|
user_balances: UserBalance[];
|
||||||
}
|
}
|
||||||
|
|
||||||
let summary = $state<GiftCardSummary>({
|
const summary = $state<GiftCardSummary>({
|
||||||
total_unclaimed: 0,
|
total_unclaimed: 0,
|
||||||
total_user_balances: 0,
|
total_user_balances: 0,
|
||||||
gift_cards: [],
|
gift_cards: [],
|
||||||
@@ -196,32 +196,32 @@
|
|||||||
let topUpMode = $state<'giveaway' | 'purchase'>('giveaway');
|
let topUpMode = $state<'giveaway' | 'purchase'>('giveaway');
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
let generateError = $derived(
|
const generateError = $derived(
|
||||||
generateAmount && (isNaN(Number(generateAmount)) || Number(generateAmount) <= 0)
|
generateAmount && (isNaN(Number(generateAmount)) || Number(generateAmount) <= 0)
|
||||||
? 'Must be a valid positive number'
|
? 'Must be a valid positive number'
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
let topUpError = $derived(
|
const topUpError = $derived(
|
||||||
topUpAmount && (isNaN(Number(topUpAmount)) || Number(topUpAmount) <= 0)
|
topUpAmount && (isNaN(Number(topUpAmount)) || Number(topUpAmount) <= 0)
|
||||||
? 'Must be a valid positive number'
|
? 'Must be a valid positive number'
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
let transferAmountError = $derived(
|
const transferAmountError = $derived(
|
||||||
transferAmount && (isNaN(Number(transferAmount)) || Number(transferAmount) <= 0)
|
transferAmount && (isNaN(Number(transferAmount)) || Number(transferAmount) <= 0)
|
||||||
? 'Must be a valid positive number'
|
? 'Must be a valid positive number'
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
let transferCodeError = $derived(
|
const transferCodeError = $derived(
|
||||||
transferToCode && transferToCode.replace(/[^a-zA-Z0-9]/g, '').length !== 12
|
transferToCode && transferToCode.replace(/[^a-zA-Z0-9]/g, '').length !== 12
|
||||||
? 'Code must be exactly 12 characters'
|
? 'Code must be exactly 12 characters'
|
||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
|
|
||||||
let isAmountValid = $derived(
|
const isAmountValid = $derived(
|
||||||
generateType === 'stock' ? true : !!(generateAmount && !generateError)
|
generateType === 'stock' ? true : !!(generateAmount && !generateError)
|
||||||
);
|
);
|
||||||
|
|
||||||
let isEmailValid = $derived.by(() => {
|
const isEmailValid = $derived.by(() => {
|
||||||
if (generateType !== 'code') return true;
|
if (generateType !== 'code') return true;
|
||||||
const trimmed = generateEmail.trim();
|
const trimmed = generateEmail.trim();
|
||||||
if (isGuestSelected) {
|
if (isGuestSelected) {
|
||||||
@@ -233,20 +233,20 @@
|
|||||||
return trimmed.includes('@') && trimmed.includes('.');
|
return trimmed.includes('@') && trimmed.includes('.');
|
||||||
});
|
});
|
||||||
|
|
||||||
let isGenerateValid = $derived(generateType === 'stock' ? true : isAmountValid && isEmailValid);
|
const isGenerateValid = $derived(generateType === 'stock' ? true : isAmountValid && isEmailValid);
|
||||||
|
|
||||||
let isTopUpValid = $derived(topUpAmount && !topUpError);
|
const isTopUpValid = $derived(topUpAmount && !topUpError);
|
||||||
let isTransferValid = $derived(
|
const isTransferValid = $derived(
|
||||||
transferAmount && !transferAmountError && transferToCode && !transferCodeError
|
transferAmount && !transferAmountError && transferToCode && !transferCodeError
|
||||||
);
|
);
|
||||||
|
|
||||||
// TillPayment state
|
// TillPayment state
|
||||||
let showTillPayment = $state(false);
|
const showTillPayment = $state(false);
|
||||||
let tillAmount = $state(0);
|
const tillAmount = $state(0);
|
||||||
let tillAction = $state<'create' | 'topup'>('create');
|
const tillAction = $state<'create' | 'topup'>('create');
|
||||||
let tillGiftCardId = $state<string | undefined>(undefined);
|
const tillGiftCardId = $state<string | undefined>(undefined);
|
||||||
let tillUserId = $state<string | undefined>(undefined);
|
const tillUserId = $state<string | undefined>(undefined);
|
||||||
let tillDelivery = $state<'account' | 'code'>('code');
|
const tillDelivery = $state<'account' | 'code'>('code');
|
||||||
|
|
||||||
async function fetchGiftCards(page: number = 1, search: string = '') {
|
async function fetchGiftCards(page: number = 1, search: string = '') {
|
||||||
loading = true;
|
loading = true;
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
|
|
||||||
// =============== Embedded Payment Handlers ===============
|
// =============== Embedded Payment Handlers ===============
|
||||||
|
|
||||||
let isEphemeralCardValid = $derived(
|
const isEphemeralCardValid = $derived(
|
||||||
ephemeralCardNumber.replace(/\s/g, '').length >= 13 &&
|
ephemeralCardNumber.replace(/\s/g, '').length >= 13 &&
|
||||||
ephemeralCardExpiry.includes('/') &&
|
ephemeralCardExpiry.includes('/') &&
|
||||||
ephemeralCardExpiry.length === 5 &&
|
ephemeralCardExpiry.length === 5 &&
|
||||||
@@ -811,8 +811,11 @@
|
|||||||
| 'balance'
|
| 'balance'
|
||||||
| 'updated';
|
| 'updated';
|
||||||
|
|
||||||
let cardSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'created', dir: 'desc' });
|
const cardSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'created', dir: 'desc' });
|
||||||
let balanceSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({ key: 'updated', dir: 'desc' });
|
const balanceSort = $state<{ key: SortKey; dir: 'asc' | 'desc' }>({
|
||||||
|
key: 'updated',
|
||||||
|
dir: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
function toggleCardSort(key: SortKey) {
|
function toggleCardSort(key: SortKey) {
|
||||||
if (cardSort.key === key) {
|
if (cardSort.key === key) {
|
||||||
@@ -832,7 +835,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortedCards = $derived.by(() => {
|
const sortedCards = $derived.by(() => {
|
||||||
let filtered = [...cards];
|
let filtered = [...cards];
|
||||||
if (activeSection === 'cards') {
|
if (activeSection === 'cards') {
|
||||||
filtered = cards.filter((gc) => !isExpired(gc.last_used_at));
|
filtered = cards.filter((gc) => !isExpired(gc.last_used_at));
|
||||||
@@ -864,10 +867,10 @@
|
|||||||
return sorted;
|
return sorted;
|
||||||
});
|
});
|
||||||
|
|
||||||
let activeCardsCount = $derived(cards.filter((gc) => !isExpired(gc.last_used_at)).length);
|
const activeCardsCount = $derived(cards.filter((gc) => !isExpired(gc.last_used_at)).length);
|
||||||
let expiredCardsCount = $derived(cards.filter((gc) => isExpired(gc.last_used_at)).length);
|
const expiredCardsCount = $derived(cards.filter((gc) => isExpired(gc.last_used_at)).length);
|
||||||
|
|
||||||
let sortedBalances = $derived.by(() => {
|
const sortedBalances = $derived.by(() => {
|
||||||
const bals = [...balances];
|
const bals = [...balances];
|
||||||
const { key, dir } = balanceSort;
|
const { key, dir } = balanceSort;
|
||||||
const mul = dir === 'asc' ? 1 : -1;
|
const mul = dir === 'asc' ? 1 : -1;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
let exceptionGroupsLoading = $state(true);
|
let exceptionGroupsLoading = $state(true);
|
||||||
let savingHours = $state(false);
|
let savingHours = $state(false);
|
||||||
|
|
||||||
let formErrors = $state({
|
const formErrors = $state({
|
||||||
name: '',
|
name: '',
|
||||||
weeks: ''
|
weeks: ''
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
let isFormValid = $derived(
|
const isFormValid = $derived(
|
||||||
exceptionDraft.name.trim() !== '' && exceptionDraft.weekStarts.length > 0
|
exceptionDraft.name.trim() !== '' && exceptionDraft.weekStarts.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
return file.size > MAX_FILE_SIZE;
|
return file.size > MAX_FILE_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
const hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
|
||||||
|
|
||||||
function isHeicFile(file: File): boolean {
|
function isHeicFile(file: File): boolean {
|
||||||
const name = file.name.toLowerCase();
|
const name = file.name.toLowerCase();
|
||||||
|
|||||||
@@ -83,7 +83,7 @@
|
|||||||
validateExpiryField();
|
validateExpiryField();
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFormValid = $derived(
|
const isFormValid = $derived(
|
||||||
formData.name.trim() !== '' &&
|
formData.name.trim() !== '' &&
|
||||||
!formErrors.name &&
|
!formErrors.name &&
|
||||||
!formErrors.notice_duration &&
|
!formErrors.notice_duration &&
|
||||||
|
|||||||
@@ -65,20 +65,20 @@
|
|||||||
maxDate.getDate()
|
maxDate.getDate()
|
||||||
);
|
);
|
||||||
|
|
||||||
let hoursUntilAppointment = $derived(
|
const hoursUntilAppointment = $derived(
|
||||||
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
|
||||||
);
|
);
|
||||||
let hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
|
||||||
let showNoticeWarning = $derived(
|
const showNoticeWarning = $derived(
|
||||||
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
hasPayments ? hoursUntilAppointment < 72 : hoursUntilAppointment < 24
|
||||||
);
|
);
|
||||||
let showNoShowWarning = $derived(
|
const showNoShowWarning = $derived(
|
||||||
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
!hasPayments && hoursUntilAppointment < 24 && hoursUntilAppointment >= 0
|
||||||
);
|
);
|
||||||
let forgiveFees = $state(false);
|
let forgiveFees = $state(false);
|
||||||
let forgiveNoShow = $state(false);
|
let forgiveNoShow = $state(false);
|
||||||
|
|
||||||
let bookingDuration = $derived(
|
const bookingDuration = $derived(
|
||||||
booking.services?.reduce(
|
booking.services?.reduce(
|
||||||
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
|
||||||
0
|
0
|
||||||
|
|||||||
@@ -36,9 +36,9 @@
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
minimum_age_required: 0
|
minimum_age_required: 0
|
||||||
});
|
});
|
||||||
let editingService = $state<Service | null>(null);
|
const editingService = $state<Service | null>(null);
|
||||||
let servicesLoading = $state(true);
|
let servicesLoading = $state(true);
|
||||||
let servicesUpdating = $state<Record<string, boolean>>({});
|
const servicesUpdating = $state<Record<string, boolean>>({});
|
||||||
let showServiceModal = $state(false);
|
let showServiceModal = $state(false);
|
||||||
let creatingService = $state(false);
|
let creatingService = $state(false);
|
||||||
let serviceErrors = $state<Record<string, string>>({});
|
let serviceErrors = $state<Record<string, string>>({});
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let isFormValid = $derived(
|
const isFormValid = $derived(
|
||||||
newService.name !== '' &&
|
newService.name !== '' &&
|
||||||
!serviceErrors.name &&
|
!serviceErrors.name &&
|
||||||
!serviceErrors.price &&
|
!serviceErrors.price &&
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
let giftCardAmount = $state('25');
|
let giftCardAmount = $state('25');
|
||||||
let showGiftCardInput = $state(false);
|
let showGiftCardInput = $state(false);
|
||||||
|
|
||||||
let subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));
|
const subtotal = $derived(cart.reduce((sum, item) => sum + item.price * item.qty, 0));
|
||||||
let itemCount = $derived(cart.reduce((sum, item) => sum + item.qty, 0));
|
const itemCount = $derived(cart.reduce((sum, item) => sum + item.qty, 0));
|
||||||
|
|
||||||
function formatCurrency(n: number): string {
|
function formatCurrency(n: number): string {
|
||||||
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(n);
|
return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP' }).format(n);
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
rescheduleVersion?: number;
|
rescheduleVersion?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { openUserModal, openBookingModal, rescheduleVersion = 0 }: Props = $props();
|
const { openUserModal, openBookingModal, rescheduleVersion = 0 }: Props = $props();
|
||||||
|
|
||||||
const PAGE_SIZE = 5;
|
const PAGE_SIZE = 5;
|
||||||
|
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
let overlappingBookings = $state<OverlappingBooking[]>([]);
|
let overlappingBookings = $state<OverlappingBooking[]>([]);
|
||||||
let hasOverlap = $state(false);
|
let hasOverlap = $state(false);
|
||||||
let hasDayConflicts = $derived(overlappingBookings.length > 0);
|
const hasDayConflicts = $derived(overlappingBookings.length > 0);
|
||||||
|
|
||||||
let showDeleteAlert = $state(false);
|
let showDeleteAlert = $state(false);
|
||||||
let blockerToDelete = $state<TimeBlocker | null>(null);
|
let blockerToDelete = $state<TimeBlocker | null>(null);
|
||||||
@@ -128,9 +128,9 @@
|
|||||||
return `${row.startTime}-${row.endTime}-${row.isOpen}`;
|
return `${row.startTime}-${row.endTime}-${row.isOpen}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedWorkingHours = $derived.by(() => getWorkingHoursForDate(newStartDate));
|
const selectedWorkingHours = $derived.by(() => getWorkingHoursForDate(newStartDate));
|
||||||
|
|
||||||
let availableStartOptions = $derived.by(() => {
|
const availableStartOptions = $derived.by(() => {
|
||||||
const wh = selectedWorkingHours;
|
const wh = selectedWorkingHours;
|
||||||
if (!wh || !wh.isOpen) return [];
|
if (!wh || !wh.isOpen) return [];
|
||||||
const startMin = timeToMinutes(wh.startTime);
|
const startMin = timeToMinutes(wh.startTime);
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
|
|
||||||
let availableEndOptions = $derived.by(() => {
|
const availableEndOptions = $derived.by(() => {
|
||||||
const wh = selectedWorkingHours;
|
const wh = selectedWorkingHours;
|
||||||
if (!wh || !wh.isOpen) return [];
|
if (!wh || !wh.isOpen) return [];
|
||||||
const startMin = timeToMinutes(wh.startTime);
|
const startMin = timeToMinutes(wh.startTime);
|
||||||
@@ -197,14 +197,14 @@
|
|||||||
return `in ${Math.floor(hr / 24)}d`;
|
return `in ${Math.floor(hr / 24)}d`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let sortedBlockers = $derived.by(() => {
|
const sortedBlockers = $derived.by(() => {
|
||||||
return [...blockers].sort(
|
return [...blockers].sort(
|
||||||
(a, b) => new SvelteDate(a.start_time).getTime() - new SvelteDate(b.start_time).getTime()
|
(a, b) => new SvelteDate(a.start_time).getTime() - new SvelteDate(b.start_time).getTime()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
const totalPages = $derived.by(() => Math.max(1, Math.ceil(sortedBlockers.length / PAGE_SIZE)));
|
||||||
let pagedBlockers = $derived.by(() => {
|
const pagedBlockers = $derived.by(() => {
|
||||||
const start = (currentPage - 1) * PAGE_SIZE;
|
const start = (currentPage - 1) * PAGE_SIZE;
|
||||||
return sortedBlockers.slice(start, start + PAGE_SIZE);
|
return sortedBlockers.slice(start, start + PAGE_SIZE);
|
||||||
});
|
});
|
||||||
@@ -275,13 +275,13 @@
|
|||||||
return formatLocalDateTime(dt);
|
return formatLocalDateTime(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
let formComplete = $derived.by(() => {
|
const formComplete = $derived.by(() => {
|
||||||
return (
|
return (
|
||||||
newDescription.trim() !== '' && newStartDate !== '' && startHour !== '' && endHour !== ''
|
newDescription.trim() !== '' && newStartDate !== '' && startHour !== '' && endHour !== ''
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let canCreate = $derived.by(() => {
|
const canCreate = $derived.by(() => {
|
||||||
return formComplete && !hasOverlap && !hasDayConflicts && !checkingOverlap;
|
return formComplete && !hasOverlap && !hasDayConflicts && !checkingOverlap;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
openUserModal: (userId: string) => void;
|
openUserModal: (userId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { openUserModal }: Props = $props();
|
const { openUserModal }: Props = $props();
|
||||||
|
|
||||||
type UserListItem = {
|
type UserListItem = {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -322,13 +322,13 @@
|
|||||||
window.__walkInCountdownInterval = setInterval(updateCountdown, 1000);
|
window.__walkInCountdownInterval = setInterval(updateCountdown, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableMinutes = $derived.by(() => {
|
const availableMinutes = $derived.by(() => {
|
||||||
if (!slotInfo) return null;
|
if (!slotInfo) return null;
|
||||||
if (slotInfo.isAvailableNow) return getLiveRemainingMinutes();
|
if (slotInfo.isAvailableNow) return getLiveRemainingMinutes();
|
||||||
return slotInfo.durationMinutes;
|
return slotInfo.durationMinutes;
|
||||||
});
|
});
|
||||||
|
|
||||||
let tooShortForService = $derived.by(() => {
|
const tooShortForService = $derived.by(() => {
|
||||||
if (shortestServiceMinutes === null || availableMinutes === null) return false;
|
if (shortestServiceMinutes === null || availableMinutes === null) return false;
|
||||||
return availableMinutes < shortestServiceMinutes;
|
return availableMinutes < shortestServiceMinutes;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
minimum_age_required: validateCsMinimumAge(newCustomService.minimum_age_required)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let isCustomFormValid = $derived(
|
const isCustomFormValid = $derived(
|
||||||
(newCustomService.name ?? '').trim() !== '' &&
|
(newCustomService.name ?? '').trim() !== '' &&
|
||||||
!customServiceErrors.name &&
|
!customServiceErrors.name &&
|
||||||
!customServiceErrors.price &&
|
!customServiceErrors.price &&
|
||||||
|
|||||||
@@ -72,7 +72,7 @@
|
|||||||
let selectedServices = $state<Service[]>([]);
|
let selectedServices = $state<Service[]>([]);
|
||||||
let selectedDate = $state<CalendarDate | undefined>(undefined);
|
let selectedDate = $state<CalendarDate | undefined>(undefined);
|
||||||
let selectedTime = $state<string | null>(null);
|
let selectedTime = $state<string | null>(null);
|
||||||
let customerInfo = $state<CustomerInfo>({
|
const customerInfo = $state<CustomerInfo>({
|
||||||
firstName: '',
|
firstName: '',
|
||||||
lastName: '',
|
lastName: '',
|
||||||
email: '',
|
email: '',
|
||||||
@@ -98,13 +98,13 @@
|
|||||||
let newCardNumber = $state('');
|
let newCardNumber = $state('');
|
||||||
let newCardExpiry = $state('');
|
let newCardExpiry = $state('');
|
||||||
let newCardCVC = $state('');
|
let newCardCVC = $state('');
|
||||||
let saveCardForFuture = $state(false);
|
const saveCardForFuture = $state(false);
|
||||||
|
|
||||||
// Payment flow state
|
// Payment flow state
|
||||||
let depositPaid = $state(false);
|
let depositPaid = $state(false);
|
||||||
let showPaymentForm = $state(false);
|
let showPaymentForm = $state(false);
|
||||||
|
|
||||||
let depositCardFormValid = $derived(
|
const depositCardFormValid = $derived(
|
||||||
selectedPaymentMethod !== null ||
|
selectedPaymentMethod !== null ||
|
||||||
(showNewCardForm &&
|
(showNewCardForm &&
|
||||||
newCardNumber.replace(/\s/g, '').length >= 13 &&
|
newCardNumber.replace(/\s/g, '').length >= 13 &&
|
||||||
@@ -113,17 +113,17 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
// VAT registration status from public business info (via shared store)
|
// VAT registration status from public business info (via shared store)
|
||||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
const vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||||
|
|
||||||
// Email existence check (guest flow only)
|
// Email existence check (guest flow only)
|
||||||
let emailChecking = $state(false);
|
let emailChecking = $state(false);
|
||||||
let emailSuggestion = $state<string | null>(null);
|
let emailSuggestion = $state<string | null>(null);
|
||||||
let emailError = $state('');
|
let emailError = $state('');
|
||||||
let emailFormatValid = $derived(
|
const emailFormatValid = $derived(
|
||||||
!customerInfo.email ||
|
!customerInfo.email ||
|
||||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(customerInfo.email)
|
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(customerInfo.email)
|
||||||
);
|
);
|
||||||
let allGuestFieldsValid = $derived(
|
const allGuestFieldsValid = $derived(
|
||||||
customerInfo.firstName &&
|
customerInfo.firstName &&
|
||||||
customerInfo.lastName &&
|
customerInfo.lastName &&
|
||||||
customerInfo.email &&
|
customerInfo.email &&
|
||||||
@@ -571,12 +571,12 @@
|
|||||||
let loadingWorkingHours = $state<boolean>(false);
|
let loadingWorkingHours = $state<boolean>(false);
|
||||||
let loadingAvailableHours = $state<boolean>(false);
|
let loadingAvailableHours = $state<boolean>(false);
|
||||||
|
|
||||||
let workingHoursCache: Record<
|
const workingHoursCache: Record<
|
||||||
string,
|
string,
|
||||||
Record<string, { isOpen: boolean; startTime: string; endTime: string }>
|
Record<string, { isOpen: boolean; startTime: string; endTime: string }>
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
let availableHoursCache: Record<
|
const availableHoursCache: Record<
|
||||||
string,
|
string,
|
||||||
Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }>
|
Record<string, { isOpen: boolean; slots: Array<{ startTime: string; endTime: string }> }>
|
||||||
> = {};
|
> = {};
|
||||||
@@ -602,7 +602,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Track which months are currently being fetched (prevents duplicate requests)
|
// Track which months are currently being fetched (prevents duplicate requests)
|
||||||
let loadingMonths: Record<string, boolean> = {};
|
const loadingMonths: Record<string, boolean> = {};
|
||||||
|
|
||||||
// Preload current + next month on first render; subsequent months fetched individually
|
// Preload current + next month on first render; subsequent months fetched individually
|
||||||
let initialLoadDone = $state(false);
|
let initialLoadDone = $state(false);
|
||||||
@@ -1304,13 +1304,13 @@
|
|||||||
selectedDate ? getDayWithOrdinal(selectedDate) : undefined
|
selectedDate ? getDayWithOrdinal(selectedDate) : undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
let depositRequired = $derived(calculateDepositRequired());
|
const depositRequired = $derived(calculateDepositRequired());
|
||||||
let totalSteps = $derived(authStore.isAuthenticated ? 4 : 5);
|
const totalSteps = $derived(authStore.isAuthenticated ? 4 : 5);
|
||||||
// StepIndicator uses displayNumber = startAt + index. currentStep aligns with displayNumber,
|
// StepIndicator uses displayNumber = startAt + index. currentStep aligns with displayNumber,
|
||||||
// not the array index. For auth: startAt=1, totalSteps=4 → last displayNumber=4.
|
// not the array index. For auth: startAt=1, totalSteps=4 → last displayNumber=4.
|
||||||
// For guest: startAt=0, totalSteps=5 → last displayNumber=4. Always evaluates to 4.
|
// For guest: startAt=0, totalSteps=5 → last displayNumber=4. Always evaluates to 4.
|
||||||
let finalStep = $derived(totalSteps - 1 + (authStore.isAuthenticated ? 1 : 0));
|
const finalStep = $derived(totalSteps - 1 + (authStore.isAuthenticated ? 1 : 0));
|
||||||
let stepLabels = $derived(
|
const stepLabels = $derived(
|
||||||
authStore.isAuthenticated
|
authStore.isAuthenticated
|
||||||
? ['Service', 'Date & Time', 'Details', 'Payment']
|
? ['Service', 'Date & Time', 'Details', 'Payment']
|
||||||
: userDepositsRequired > 0
|
: userDepositsRequired > 0
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { getLocalTimeZone } from '@internationalized/date';
|
import { getLocalTimeZone } from '@internationalized/date';
|
||||||
import { Separator } from '$lib/components/ui/separator';
|
import { Separator } from '$lib/components/ui/separator';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
services = [],
|
services = [],
|
||||||
date = undefined,
|
date = undefined,
|
||||||
time = null,
|
time = null,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { CalendarDate, DateValue } from '@internationalized/date';
|
import type { CalendarDate, DateValue } from '@internationalized/date';
|
||||||
import Calendar from '$lib/components/ui/calendar/calendar.svelte';
|
import Calendar from '$lib/components/ui/calendar/calendar.svelte';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
date,
|
date,
|
||||||
placeholder,
|
placeholder,
|
||||||
minValue,
|
minValue,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
outOfHours?: boolean;
|
outOfHours?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
selectedDate,
|
selectedDate,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
endTime,
|
endTime,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import type { Service } from '$lib/types/booking';
|
import type { Service } from '$lib/types/booking';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
service,
|
service,
|
||||||
selected = false,
|
selected = false,
|
||||||
onclick,
|
onclick,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import ServiceCard from './ServiceCard.svelte';
|
import ServiceCard from './ServiceCard.svelte';
|
||||||
import type { Service } from '$lib/types/booking';
|
import type { Service } from '$lib/types/booking';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
services = [],
|
services = [],
|
||||||
selected = [],
|
selected = [],
|
||||||
loading = true,
|
loading = true,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
onSelect: (time: string) => void;
|
onSelect: (time: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { slots, selectedTime, duration, protection, onSelect }: Props = $props();
|
const { slots, selectedTime, duration, protection, onSelect }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if slots.length > 0}
|
{#if slots.length > 0}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { CalendarDate } from '@internationalized/date';
|
import type { CalendarDate } from '@internationalized/date';
|
||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
date,
|
date,
|
||||||
groupedTimeSlots = [],
|
groupedTimeSlots = [],
|
||||||
selectedTime = null,
|
selectedTime = null,
|
||||||
|
|||||||
@@ -52,8 +52,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Derived values for auth state - ensures reactivity
|
// Derived values for auth state - ensures reactivity
|
||||||
let isAuthenticated = $derived(authStore.isAuthenticated);
|
const isAuthenticated = $derived(authStore.isAuthenticated);
|
||||||
let isLoading = $derived(authStore.isLoading);
|
const isLoading = $derived(authStore.isLoading);
|
||||||
|
|
||||||
// Notification bell state
|
// Notification bell state
|
||||||
let unreadCount = $state(0);
|
let unreadCount = $state(0);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
onComplete: (payment: PaymentResult) => void;
|
onComplete: (payment: PaymentResult) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { booking, onClose, onComplete }: Props = $props();
|
const { booking, onClose, onComplete }: Props = $props();
|
||||||
|
|
||||||
type PaymentStatus =
|
type PaymentStatus =
|
||||||
| 'idle'
|
| 'idle'
|
||||||
@@ -47,17 +47,17 @@
|
|||||||
let paymentResult = $state<PaymentResult | null>(null);
|
let paymentResult = $state<PaymentResult | null>(null);
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
|
|
||||||
let stamps = $derived(booking.user?.loyalty_stamps ?? 0);
|
const stamps = $derived(booking.user?.loyalty_stamps ?? 0);
|
||||||
let useLoyalty = $state(false);
|
let useLoyalty = $state(false);
|
||||||
|
|
||||||
let loyaltyEligible = $derived(
|
const loyaltyEligible = $derived(
|
||||||
stamps >= 10 &&
|
stamps >= 10 &&
|
||||||
!(booking.discounts ?? []).some((d: BookingDiscount) => d.discount_source === 'loyalty') &&
|
!(booking.discounts ?? []).some((d: BookingDiscount) => d.discount_source === 'loyalty') &&
|
||||||
booking.total_amount > 0 &&
|
booking.total_amount > 0 &&
|
||||||
booking.amount_paid === 0
|
booking.amount_paid === 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let loyaltyDiscount = $derived(
|
const loyaltyDiscount = $derived(
|
||||||
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -203,13 +203,15 @@
|
|||||||
return service.override_price ?? service.price ?? 0;
|
return service.override_price ?? service.price ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let subtotal = $derived((booking.services ?? []).reduce((sum, s) => sum + getServicePrice(s), 0));
|
const subtotal = $derived(
|
||||||
let discountSum = $derived(
|
(booking.services ?? []).reduce((sum, s) => sum + getServicePrice(s), 0)
|
||||||
|
);
|
||||||
|
const discountSum = $derived(
|
||||||
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
||||||
);
|
);
|
||||||
let netTotal = $derived(Math.max(0, subtotal - discountSum));
|
const netTotal = $derived(Math.max(0, subtotal - discountSum));
|
||||||
|
|
||||||
let tipPercentages = $derived.by(() => {
|
const tipPercentages = $derived.by(() => {
|
||||||
if (netTotal <= 0) return [];
|
if (netTotal <= 0) return [];
|
||||||
return [
|
return [
|
||||||
{ pct: 10, amount: Math.round(netTotal * 0.1 * 100) / 100 },
|
{ pct: 10, amount: Math.round(netTotal * 0.1 * 100) / 100 },
|
||||||
@@ -218,7 +220,7 @@
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let tipMultiplier = $derived(
|
const tipMultiplier = $derived(
|
||||||
selectedTipPercent !== null
|
selectedTipPercent !== null
|
||||||
? 1 + selectedTipPercent / 100
|
? 1 + selectedTipPercent / 100
|
||||||
: customTipAmount && parseFloat(customTipAmount) > 0
|
: customTipAmount && parseFloat(customTipAmount) > 0
|
||||||
@@ -226,8 +228,8 @@
|
|||||||
: 1
|
: 1
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalWithTip = $derived(tipEnabled ? netTotal * tipMultiplier : netTotal);
|
const totalWithTip = $derived(tipEnabled ? netTotal * tipMultiplier : netTotal);
|
||||||
let tipDisplay = $derived(
|
const tipDisplay = $derived(
|
||||||
selectedTipPercent !== null
|
selectedTipPercent !== null
|
||||||
? `${selectedTipPercent}%`
|
? `${selectedTipPercent}%`
|
||||||
: customTipAmount && parseFloat(customTipAmount) > 0
|
: customTipAmount && parseFloat(customTipAmount) > 0
|
||||||
@@ -235,7 +237,7 @@
|
|||||||
: ''
|
: ''
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalDue = $derived(tipEnabled ? totalWithTip : netTotal);
|
const totalDue = $derived(tipEnabled ? totalWithTip : netTotal);
|
||||||
|
|
||||||
function formatCurrency(value: number): string {
|
function formatCurrency(value: number): string {
|
||||||
return new Intl.NumberFormat('en-GB', {
|
return new Intl.NumberFormat('en-GB', {
|
||||||
@@ -378,8 +380,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
let cashAmount = $state<string>('');
|
let cashAmount = $state<string>('');
|
||||||
let cashAmountNum = $derived(cashAmount === '' ? 0 : parseFloat(cashAmount));
|
const cashAmountNum = $derived(cashAmount === '' ? 0 : parseFloat(cashAmount));
|
||||||
let changeDue = $derived(cashAmountNum > totalDue ? cashAmountNum - totalDue : 0);
|
const changeDue = $derived(cashAmountNum > totalDue ? cashAmountNum - totalDue : 0);
|
||||||
let extraAsTip = $state(false);
|
let extraAsTip = $state(false);
|
||||||
|
|
||||||
function handleCashInput(e: Event) {
|
function handleCashInput(e: Event) {
|
||||||
@@ -511,7 +513,7 @@
|
|||||||
giftCardId = formatted;
|
giftCardId = formatted;
|
||||||
}
|
}
|
||||||
|
|
||||||
let giftCardValid = $derived(useAccountBalance || giftCardId.replace(/-/g, '').length === 12);
|
const giftCardValid = $derived(useAccountBalance || giftCardId.replace(/-/g, '').length === 12);
|
||||||
|
|
||||||
async function handleGiftCardPayment() {
|
async function handleGiftCardPayment() {
|
||||||
if (!giftCardValid) {
|
if (!giftCardValid) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
onComplete: (result: TillSaleResult) => void;
|
onComplete: (result: TillSaleResult) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
amount,
|
amount,
|
||||||
itemType,
|
itemType,
|
||||||
action,
|
action,
|
||||||
@@ -100,9 +100,9 @@
|
|||||||
|
|
||||||
// Cash
|
// Cash
|
||||||
let cashAmount = $state('');
|
let cashAmount = $state('');
|
||||||
let cashAmountNum = $derived(Number(cashAmount) || 0);
|
const cashAmountNum = $derived(Number(cashAmount) || 0);
|
||||||
let changeDue = $derived(Math.max(0, cashAmountNum - amount));
|
const changeDue = $derived(Math.max(0, cashAmountNum - amount));
|
||||||
let cashEntryComplete = $derived(cashAmountNum >= amount);
|
const cashEntryComplete = $derived(cashAmountNum >= amount);
|
||||||
// Remember cash received for success screen display
|
// Remember cash received for success screen display
|
||||||
let cashTendered = $state(0);
|
let cashTendered = $state(0);
|
||||||
|
|
||||||
@@ -224,13 +224,13 @@
|
|||||||
ephemeralCardCVC = input.value.replace(/\D/g, '').substring(0, 4);
|
ephemeralCardCVC = input.value.replace(/\D/g, '').substring(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
let isEphemeralCardValid = $derived(
|
const isEphemeralCardValid = $derived(
|
||||||
isValidLuhn(ephemeralCardNumber) &&
|
isValidLuhn(ephemeralCardNumber) &&
|
||||||
/^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
/^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
||||||
ephemeralCardCVC.length >= 3
|
ephemeralCardCVC.length >= 3
|
||||||
);
|
);
|
||||||
|
|
||||||
let ephemeralCardError = $derived(
|
const ephemeralCardError = $derived(
|
||||||
ephemeralCardNumber.length > 0 && !isValidLuhn(ephemeralCardNumber)
|
ephemeralCardNumber.length > 0 && !isValidLuhn(ephemeralCardNumber)
|
||||||
? 'Invalid card number'
|
? 'Invalid card number'
|
||||||
: /^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
: /^\d{2}\/\d{2}$/.test(ephemeralCardExpiry) &&
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
defaultPaymentType?: 'full' | 'partial' | 'deposit';
|
defaultPaymentType?: 'full' | 'partial' | 'deposit';
|
||||||
}
|
}
|
||||||
|
|
||||||
let { booking, onClose, onComplete, canSaveCards = true, defaultPaymentType }: Props = $props();
|
const { booking, onClose, onComplete, canSaveCards = true, defaultPaymentType }: Props = $props();
|
||||||
|
|
||||||
type PaymentStatus = 'idle' | 'processing' | 'polling' | 'success' | 'error';
|
type PaymentStatus = 'idle' | 'processing' | 'polling' | 'success' | 'error';
|
||||||
|
|
||||||
@@ -140,9 +140,9 @@
|
|||||||
return { month, year };
|
return { month, year };
|
||||||
}
|
}
|
||||||
|
|
||||||
let expiryParts = $derived(parseExpiryParts(newCardExpiry));
|
const expiryParts = $derived(parseExpiryParts(newCardExpiry));
|
||||||
|
|
||||||
let isExpiryInPast = $derived(
|
const isExpiryInPast = $derived(
|
||||||
expiryParts !== null &&
|
expiryParts !== null &&
|
||||||
(() => {
|
(() => {
|
||||||
const expiryDate = new SvelteDate(expiryParts.year, expiryParts.month);
|
const expiryDate = new SvelteDate(expiryParts.year, expiryParts.month);
|
||||||
@@ -150,7 +150,7 @@
|
|||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
|
|
||||||
let hasInvalidMonth = $derived(/^\d{2}\/\d{2}$/.test(newCardExpiry) && expiryParts === null);
|
const hasInvalidMonth = $derived(/^\d{2}\/\d{2}$/.test(newCardExpiry) && expiryParts === null);
|
||||||
|
|
||||||
function isValidLuhn(cardNumber: string): boolean {
|
function isValidLuhn(cardNumber: string): boolean {
|
||||||
const s = cardNumber.replace(/\D/g, '');
|
const s = cardNumber.replace(/\D/g, '');
|
||||||
@@ -168,17 +168,17 @@
|
|||||||
return sum % 10 === 0 && s.length >= 13 && s.length <= 19;
|
return sum % 10 === 0 && s.length >= 13 && s.length <= 19;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cardFormValid = $derived(
|
const cardFormValid = $derived(
|
||||||
isValidLuhn(newCardNumber) && expiryParts !== null && newCardCVC.length >= 3 && !isExpiryInPast
|
isValidLuhn(newCardNumber) && expiryParts !== null && newCardCVC.length >= 3 && !isExpiryInPast
|
||||||
);
|
);
|
||||||
|
|
||||||
let cardSelected = $derived(
|
const cardSelected = $derived(
|
||||||
(selectedPaymentMethod !== null && paymentMethods.length > 0) ||
|
(selectedPaymentMethod !== null && paymentMethods.length > 0) ||
|
||||||
(showNewCardForm && cardFormValid) ||
|
(showNewCardForm && cardFormValid) ||
|
||||||
(paymentMethods.length === 0 && cardFormValid)
|
(paymentMethods.length === 0 && cardFormValid)
|
||||||
);
|
);
|
||||||
|
|
||||||
let cardValidationError = $derived(
|
const cardValidationError = $derived(
|
||||||
!cardSelected
|
!cardSelected
|
||||||
? selectedPaymentMethod === null && paymentMethods.length > 0 && !showNewCardForm
|
? selectedPaymentMethod === null && paymentMethods.length > 0 && !showNewCardForm
|
||||||
? 'Please select a card'
|
? 'Please select a card'
|
||||||
@@ -211,12 +211,12 @@
|
|||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
// Derived values
|
// Derived values
|
||||||
let depositOutstanding = $derived(booking.deposit_required && !booking.deposit_paid);
|
const depositOutstanding = $derived(booking.deposit_required && !booking.deposit_paid);
|
||||||
|
|
||||||
// Auto-select a sensible default payment type based on the booking's deposit
|
// Auto-select a sensible default payment type based on the booking's deposit
|
||||||
// state. The backend will split the charge into deposit + non-deposit records
|
// state. The backend will split the charge into deposit + non-deposit records
|
||||||
// when appropriate, so this choice mainly controls the button label and amount.
|
// when appropriate, so this choice mainly controls the button label and amount.
|
||||||
let defaultType = $derived(defaultPaymentType ?? (depositOutstanding ? 'deposit' : 'full'));
|
const defaultType = $derived(defaultPaymentType ?? (depositOutstanding ? 'deposit' : 'full'));
|
||||||
let paymentType = $state<'full' | 'partial' | 'deposit'>('full');
|
let paymentType = $state<'full' | 'partial' | 'deposit'>('full');
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
paymentType = defaultType as 'full' | 'partial' | 'deposit';
|
paymentType = defaultType as 'full' | 'partial' | 'deposit';
|
||||||
@@ -230,35 +230,35 @@
|
|||||||
let lockInterval: ReturnType<typeof setInterval> | null = null;
|
let lockInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
let countdownInterval: ReturnType<typeof setInterval> | null = null;
|
let countdownInterval: ReturnType<typeof setInterval> | null = null;
|
||||||
|
|
||||||
let servicesSubtotal = $derived(
|
const servicesSubtotal = $derived(
|
||||||
(booking.services ?? []).reduce((sum, s) => sum + (s.price || 0), 0)
|
(booking.services ?? []).reduce((sum, s) => sum + (s.price || 0), 0)
|
||||||
);
|
);
|
||||||
let discountSum = $derived(
|
const discountSum = $derived(
|
||||||
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
(booking.discounts ?? []).reduce((sum, d) => sum + d.discount_amount, 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let totalPaid = $derived(
|
const totalPaid = $derived(
|
||||||
booking.payments
|
booking.payments
|
||||||
?.filter((p) => p.status === 'completed')
|
?.filter((p) => p.status === 'completed')
|
||||||
.reduce((sum, p) => sum + p.amount, 0) || 0
|
.reduce((sum, p) => sum + p.amount, 0) || 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let amountRemaining = $derived(booking.total_amount - totalPaid);
|
const amountRemaining = $derived(booking.total_amount - totalPaid);
|
||||||
|
|
||||||
let loyaltyEligible = $derived(
|
const loyaltyEligible = $derived(
|
||||||
stamps >= 10 &&
|
stamps >= 10 &&
|
||||||
!(booking.discounts ?? []).some((d) => d.discount_source === 'loyalty') &&
|
!(booking.discounts ?? []).some((d) => d.discount_source === 'loyalty') &&
|
||||||
booking.total_amount > 0 &&
|
booking.total_amount > 0 &&
|
||||||
booking.amount_paid === 0
|
booking.amount_paid === 0
|
||||||
);
|
);
|
||||||
|
|
||||||
let loyaltyDiscount = $derived(
|
const loyaltyDiscount = $derived(
|
||||||
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
useLoyalty ? Math.round(booking.total_amount * 100 * LOYALTY_DISCOUNT_RATE) : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
// Deposit policy warning text — dynamic based on booking state
|
// Deposit policy warning text — dynamic based on booking state
|
||||||
let expectedDepositPercent = $derived(booking.deposit_required ? 20 : 0);
|
const expectedDepositPercent = $derived(booking.deposit_required ? 20 : 0);
|
||||||
let depositPolicyWarning = $derived<string | null>(
|
const depositPolicyWarning = $derived<string | null>(
|
||||||
{
|
{
|
||||||
get text(): string | null {
|
get text(): string | null {
|
||||||
if (!booking.deposit_required && totalPaid === 0 && booking.amount_due <= 0) return null;
|
if (!booking.deposit_required && totalPaid === 0 && booking.amount_due <= 0) return null;
|
||||||
@@ -273,12 +273,12 @@
|
|||||||
}.text
|
}.text
|
||||||
);
|
);
|
||||||
|
|
||||||
let isScenarioA = $derived(depositOutstanding);
|
const isScenarioA = $derived(depositOutstanding);
|
||||||
|
|
||||||
let isScenarioB = $derived(!depositOutstanding && totalPaid < booking.total_amount);
|
const isScenarioB = $derived(!depositOutstanding && totalPaid < booking.total_amount);
|
||||||
|
|
||||||
let partialAmountNum = $derived(partialAmount === '' ? 0 : parseFloat(partialAmount));
|
const partialAmountNum = $derived(partialAmount === '' ? 0 : parseFloat(partialAmount));
|
||||||
let partialAmountValid = $derived(
|
const partialAmountValid = $derived(
|
||||||
paymentType === 'partial' &&
|
paymentType === 'partial' &&
|
||||||
partialAmount !== '' &&
|
partialAmount !== '' &&
|
||||||
!isNaN(partialAmountNum) &&
|
!isNaN(partialAmountNum) &&
|
||||||
@@ -287,7 +287,7 @@
|
|||||||
/^\d+(\.\d{0,2})?$/.test(partialAmount)
|
/^\d+(\.\d{0,2})?$/.test(partialAmount)
|
||||||
);
|
);
|
||||||
|
|
||||||
let partialValidationError = $derived(
|
const partialValidationError = $derived(
|
||||||
paymentType === 'partial' && !partialAmountValid
|
paymentType === 'partial' && !partialAmountValid
|
||||||
? partialAmount === ''
|
? partialAmount === ''
|
||||||
? 'Enter an amount'
|
? 'Enter an amount'
|
||||||
@@ -301,14 +301,14 @@
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
let payButtonDisabled = $derived(
|
const payButtonDisabled = $derived(
|
||||||
status === 'processing' ||
|
status === 'processing' ||
|
||||||
!cardSelected ||
|
!cardSelected ||
|
||||||
(paymentType === 'partial' && !partialAmountValid) ||
|
(paymentType === 'partial' && !partialAmountValid) ||
|
||||||
(booking.status === 'pending_release' && (lockTimer <= 0 || !lockAcquired))
|
(booking.status === 'pending_release' && (lockTimer <= 0 || !lockAcquired))
|
||||||
);
|
);
|
||||||
|
|
||||||
let payButtonError = $derived(
|
const payButtonError = $derived(
|
||||||
status === 'processing'
|
status === 'processing'
|
||||||
? null
|
? null
|
||||||
: !cardSelected
|
: !cardSelected
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
openUserModal: (userId: string) => void;
|
openUserModal: (userId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { _openBookingModal, openEditBookingModal, openUserModal }: Props = $props();
|
const { _openBookingModal, openEditBookingModal, openUserModal }: Props = $props();
|
||||||
|
|
||||||
type Booking = {
|
type Booking = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -90,7 +90,7 @@
|
|||||||
let weekSummary = $state<DailySummary | null>(null);
|
let weekSummary = $state<DailySummary | null>(null);
|
||||||
|
|
||||||
// VAT registration status from public business info (via shared store)
|
// VAT registration status from public business info (via shared store)
|
||||||
let vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
const vatRegistered = $derived(getBusinessInfo()?.is_vat_registered ?? false);
|
||||||
|
|
||||||
// Data diffing — only update UI when payload actually changes
|
// Data diffing — only update UI when payload actually changes
|
||||||
let prevDataJson = $state('');
|
let prevDataJson = $state('');
|
||||||
|
|||||||
@@ -87,17 +87,17 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let pendingApprovals = $state<PendingApproval[]>([]);
|
let pendingApprovals = $state<PendingApproval[]>([]);
|
||||||
let visibleApprovals = $derived(pendingApprovals.slice(0, 3));
|
const visibleApprovals = $derived(pendingApprovals.slice(0, 3));
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let showApprovalModal = $state(false);
|
let showApprovalModal = $state(false);
|
||||||
let selectedBooking = $state<PendingBooking | null>(null);
|
let selectedBooking = $state<PendingBooking | null>(null);
|
||||||
|
|
||||||
let pendingEditRequests = $state<EditRequest[]>([]);
|
let pendingEditRequests = $state<EditRequest[]>([]);
|
||||||
let visibleEditRequests = $derived(pendingEditRequests.slice(0, 3));
|
const visibleEditRequests = $derived(pendingEditRequests.slice(0, 3));
|
||||||
let showEditRequestModal = $state(false);
|
let showEditRequestModal = $state(false);
|
||||||
let selectedEditRequest = $state<EditRequest | null>(null);
|
let selectedEditRequest = $state<EditRequest | null>(null);
|
||||||
|
|
||||||
let _hasItems = $derived(pendingApprovals.length > 0 || pendingEditRequests.length > 0);
|
const _hasItems = $derived(pendingApprovals.length > 0 || pendingEditRequests.length > 0);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
hasItems = _hasItems;
|
hasItems = _hasItems;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
openUserModal: (userId: string) => void;
|
openUserModal: (userId: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { openBookingModal, openUserModal }: Props = $props();
|
const { openBookingModal, openUserModal }: Props = $props();
|
||||||
|
|
||||||
type TodayAppointment = {
|
type TodayAppointment = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -155,7 +155,7 @@
|
|||||||
return blocker.description?.startsWith('RESERVATION:') ?? false;
|
return blocker.description?.startsWith('RESERVATION:') ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let availableStartOptions = $derived.by(() => {
|
const availableStartOptions = $derived.by(() => {
|
||||||
const wh = workingHours;
|
const wh = workingHours;
|
||||||
if (!wh || !wh.isOpen) return [];
|
if (!wh || !wh.isOpen) return [];
|
||||||
const startMin = timeToMinutes(wh.startTime);
|
const startMin = timeToMinutes(wh.startTime);
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
|
|
||||||
let availableEndOptions = $derived.by(() => {
|
const availableEndOptions = $derived.by(() => {
|
||||||
const wh = workingHours;
|
const wh = workingHours;
|
||||||
if (!wh || !wh.isOpen) return [];
|
if (!wh || !wh.isOpen) return [];
|
||||||
const endMin = timeToMinutes(wh.endTime);
|
const endMin = timeToMinutes(wh.endTime);
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
return options;
|
return options;
|
||||||
});
|
});
|
||||||
|
|
||||||
let suggestedLunch = $derived.by(() => {
|
const suggestedLunch = $derived.by(() => {
|
||||||
if (!workingHours || !availableHours || !workingHours.isOpen) return null;
|
if (!workingHours || !availableHours || !workingHours.isOpen) return null;
|
||||||
const wh = workingHours;
|
const wh = workingHours;
|
||||||
const ah = availableHours;
|
const ah = availableHours;
|
||||||
@@ -284,7 +284,7 @@
|
|||||||
| LunchTimelineItem
|
| LunchTimelineItem
|
||||||
| ClosingTimeTimelineItem;
|
| ClosingTimeTimelineItem;
|
||||||
|
|
||||||
let timeline = $derived.by(() => {
|
const timeline = $derived.by(() => {
|
||||||
const items: TimelineItem[] = [];
|
const items: TimelineItem[] = [];
|
||||||
|
|
||||||
for (const apt of appointments) {
|
for (const apt of appointments) {
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
return formatLocalDateTime(dt);
|
return formatLocalDateTime(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
let canCreate = $derived.by(() => !hasOverlap && !checkingOverlap);
|
const canCreate = $derived.by(() => !hasOverlap && !checkingOverlap);
|
||||||
|
|
||||||
async function createBlocker() {
|
async function createBlocker() {
|
||||||
if (!canCreate) return;
|
if (!canCreate) return;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
maxChars?: number;
|
maxChars?: number;
|
||||||
threshold?: number; // when to start showing counter
|
threshold?: number; // when to start showing counter
|
||||||
};
|
};
|
||||||
let { text, maxChars = 1000000, threshold = 750000 }: Props = $props();
|
const { text, maxChars = 1000000, threshold = 750000 }: Props = $props();
|
||||||
|
|
||||||
const graphemeCount = $derived.by(() => {
|
const graphemeCount = $derived.by(() => {
|
||||||
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
jxl?: string;
|
jxl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
urls,
|
urls,
|
||||||
type = 'thumb',
|
type = 'thumb',
|
||||||
alt = '',
|
alt = '',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
let { status }: { status: string } = $props();
|
const { status }: { status: string } = $props();
|
||||||
|
|
||||||
const variantMap: Record<string, { label: string; variantClass: string }> = {
|
const variantMap: Record<string, { label: string; variantClass: string }> = {
|
||||||
draft: { label: 'Draft', variantClass: 'bg-gray-100 text-gray-800' },
|
draft: { label: 'Draft', variantClass: 'bg-gray-100 text-gray-800' },
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import * as Button from '$lib/components/ui/button/index.js';
|
import * as Button from '$lib/components/ui/button/index.js';
|
||||||
|
|
||||||
let { ...restProps }: Record<string, unknown> = $props();
|
const { ...restProps }: Record<string, unknown> = $props();
|
||||||
let ref = $state<HTMLElement | null>(null);
|
let ref = $state<HTMLElement | null>(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useImageCropperCropper } from './image-cropper.svelte.js';
|
import { useImageCropperCropper } from './image-cropper.svelte.js';
|
||||||
import type { ImageCropperCropperProps } from './types.js';
|
import type { ImageCropperCropperProps } from './types.js';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
cropShape = 'round',
|
cropShape = 'round',
|
||||||
aspect = 1,
|
aspect = 1,
|
||||||
showGrid = false,
|
showGrid = false,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import { useImageCropperDialog } from './image-cropper.svelte.js';
|
import { useImageCropperDialog } from './image-cropper.svelte.js';
|
||||||
import type { ImageCropperDialogProps } from './types';
|
import type { ImageCropperDialogProps } from './types';
|
||||||
|
|
||||||
let { children, class: className, ...rest }: ImageCropperDialogProps = $props();
|
const { children, class: className, ...rest }: ImageCropperDialogProps = $props();
|
||||||
|
|
||||||
const dialogState = useImageCropperDialog();
|
const dialogState = useImageCropperDialog();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
import UploadIcon from '@lucide/svelte/icons/upload';
|
import UploadIcon from '@lucide/svelte/icons/upload';
|
||||||
import { cn } from '$lib/utils.js';
|
import { cn } from '$lib/utils.js';
|
||||||
|
|
||||||
let { child, class: className }: ImageCropperPreviewProps = $props();
|
const { child, class: className }: ImageCropperPreviewProps = $props();
|
||||||
|
|
||||||
const previewState = useImageCropperPreview();
|
const previewState = useImageCropperPreview();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
<script lang="ts" generics="T extends MapArcDatum = MapArcDatum">
|
<script lang="ts" generics="T extends MapArcDatum = MapArcDatum">
|
||||||
import { useMap } from '$lib/hooks/use-map.svelte.js';
|
import { useMap } from '$lib/hooks/use-map.svelte.js';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
data,
|
data,
|
||||||
id: propId,
|
id: propId,
|
||||||
curvature = 0.2,
|
curvature = 0.2,
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
const ARC_HIT_MIN_WIDTH = 12;
|
const ARC_HIT_MIN_WIDTH = 12;
|
||||||
const ARC_HIT_PADDING = 6;
|
const ARC_HIT_PADDING = 6;
|
||||||
|
|
||||||
let autoId = $state(Math.random().toString(36).slice(2));
|
const autoId = $state(Math.random().toString(36).slice(2));
|
||||||
const id = $derived(propId ?? autoId);
|
const id = $derived(propId ?? autoId);
|
||||||
const sourceId = $derived(`arc-source-${id}`);
|
const sourceId = $derived(`arc-source-${id}`);
|
||||||
const layerId = $derived(`arc-layer-${id}`);
|
const layerId = $derived(`arc-layer-${id}`);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
onclusterclick?: (clusterId: number, coordinates: [number, number], pointCount: number) => void;
|
onclusterclick?: (clusterId: number, coordinates: [number, number], pointCount: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
data,
|
data,
|
||||||
clusterMaxZoom = 14,
|
clusterMaxZoom = 14,
|
||||||
clusterRadius = 50,
|
clusterRadius = 50,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
onlocate?: (coords: { longitude: number; latitude: number }) => void;
|
onlocate?: (coords: { longitude: number; latitude: number }) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
position = 'bottom-right',
|
position = 'bottom-right',
|
||||||
showZoom = true,
|
showZoom = true,
|
||||||
showCompass = false,
|
showCompass = false,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
rotationAlignment?: MarkerOptions['rotationAlignment'];
|
rotationAlignment?: MarkerOptions['rotationAlignment'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
longitude,
|
longitude,
|
||||||
latitude,
|
latitude,
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
longitude,
|
longitude,
|
||||||
latitude,
|
latitude,
|
||||||
children,
|
children,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
coordinates,
|
coordinates,
|
||||||
color = '#4285F4',
|
color = '#4285F4',
|
||||||
width = 3,
|
width = 3,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { children, class: className }: Props = $props();
|
const { children, class: className }: Props = $props();
|
||||||
|
|
||||||
const markerCtx = getContext<{
|
const markerCtx = getContext<{
|
||||||
getMarker: () => MapLibreGL.Marker | null;
|
getMarker: () => MapLibreGL.Marker | null;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
position?: 'top' | 'bottom';
|
position?: 'top' | 'bottom';
|
||||||
}
|
}
|
||||||
|
|
||||||
let { children, class: className, position = 'top' }: Props = $props();
|
const { children, class: className, position = 'top' }: Props = $props();
|
||||||
|
|
||||||
const positionClasses = {
|
const positionClasses = {
|
||||||
top: 'bottom-full mb-1',
|
top: 'bottom-full mb-1',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
maxWidth?: string;
|
maxWidth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
children,
|
children,
|
||||||
class: className,
|
class: className,
|
||||||
closeButton = false,
|
closeButton = false,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
anchor?: PopupOptions['anchor'];
|
anchor?: PopupOptions['anchor'];
|
||||||
}
|
}
|
||||||
|
|
||||||
let { children, class: className, offset = 16, anchor }: Props = $props();
|
const { children, class: className, offset = 16, anchor }: Props = $props();
|
||||||
|
|
||||||
const markerCtx = getContext<{
|
const markerCtx = getContext<{
|
||||||
getMarker: () => MapLibreGL.Marker | null;
|
getMarker: () => MapLibreGL.Marker | null;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
let {
|
const {
|
||||||
trigger
|
trigger
|
||||||
}: {
|
}: {
|
||||||
trigger?: Snippet;
|
trigger?: Snippet;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
import { Toaster as Sonner, type ToasterProps as SonnerProps } from 'svelte-sonner';
|
||||||
import { mode } from 'mode-watcher';
|
import { mode } from 'mode-watcher';
|
||||||
|
|
||||||
let { ...restProps }: SonnerProps = $props();
|
const { ...restProps }: SonnerProps = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Sonner
|
<Sonner
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export function timeToMinutes(time: string): number {
|
|||||||
|
|
||||||
export function calculatePreviousTime(time: string): string {
|
export function calculatePreviousTime(time: string): string {
|
||||||
const [hours, minutes] = time.split(':').map(Number);
|
const [hours, minutes] = time.split(':').map(Number);
|
||||||
let totalMinutes = hours * 60 + minutes - 15;
|
const totalMinutes = hours * 60 + minutes - 15;
|
||||||
return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
|
return `${String(Math.floor(totalMinutes / 60)).padStart(2, '0')}:${String(totalMinutes % 60).padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ export function generateAvailableTimeSlots(
|
|||||||
});
|
});
|
||||||
const [londonHours, londonMinutes] = londonTimeStr.split(':').map(Number);
|
const [londonHours, londonMinutes] = londonTimeStr.split(':').map(Number);
|
||||||
const currentMinutes = londonHours * 60 + londonMinutes;
|
const currentMinutes = londonHours * 60 + londonMinutes;
|
||||||
let minimumStart = Math.ceil((currentMinutes + 60) / 15) * 15;
|
const minimumStart = Math.ceil((currentMinutes + 60) / 15) * 15;
|
||||||
startTotalMinutes = Math.max(startTotalMinutes, minimumStart);
|
startTotalMinutes = Math.max(startTotalMinutes, minimumStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
import { authStore } from '$lib/stores/auth.svelte';
|
import { authStore } from '$lib/stores/auth.svelte';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let { children } = $props();
|
const { children } = $props();
|
||||||
|
|
||||||
// Default to 'top-center' (mobile-first approach)
|
// Default to 'top-center' (mobile-first approach)
|
||||||
let toasterPosition = $state<'top-center' | 'bottom-center'>('top-center');
|
let toasterPosition = $state<'top-center' | 'bottom-center'>('top-center');
|
||||||
|
|||||||
@@ -61,7 +61,7 @@
|
|||||||
// =============== Tab State ===============
|
// =============== Tab State ===============
|
||||||
let activeTab = $state<'general' | 'history' | 'referral' | 'cards' | 'admin'>('general');
|
let activeTab = $state<'general' | 'history' | 'referral' | 'cards' | 'admin'>('general');
|
||||||
|
|
||||||
let canSaveCards = $derived(
|
const canSaveCards = $derived(
|
||||||
authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
|
authStore.currentUser?.role === 'verified_email' || authStore.currentUser?.role === 'affiliate'
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -122,9 +122,9 @@
|
|||||||
return path + ' Z';
|
return path + ' Z';
|
||||||
}
|
}
|
||||||
|
|
||||||
let notifPrefs = $state({ emailEnabled: true, smsEnabled: true, browserPushEnabled: true });
|
const notifPrefs = $state({ emailEnabled: true, smsEnabled: true, browserPushEnabled: true });
|
||||||
|
|
||||||
let loadingCards = $state(false);
|
const loadingCards = $state(false);
|
||||||
|
|
||||||
let cardToDelete = $state<SavedCard | null>(null);
|
let cardToDelete = $state<SavedCard | null>(null);
|
||||||
let showDeleteCardDialog = $state(false);
|
let showDeleteCardDialog = $state(false);
|
||||||
@@ -192,19 +192,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Derived validations for Add Saved Card form
|
// Derived validations for Add Saved Card form
|
||||||
let newCardExpiryParts = $derived(parseExpiryParts(newCardExpiry));
|
const newCardExpiryParts = $derived(parseExpiryParts(newCardExpiry));
|
||||||
let isNewCardExpiryInPast = $derived(
|
const isNewCardExpiryInPast = $derived(
|
||||||
newCardExpiryParts !== null &&
|
newCardExpiryParts !== null &&
|
||||||
(() => {
|
(() => {
|
||||||
const expiryDate = new SvelteDate(newCardExpiryParts.year, newCardExpiryParts.month);
|
const expiryDate = new SvelteDate(newCardExpiryParts.year, newCardExpiryParts.month);
|
||||||
return expiryDate < new SvelteDate();
|
return expiryDate < new SvelteDate();
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
let isNewCardExpiryInvalidMonth = $derived(
|
const isNewCardExpiryInvalidMonth = $derived(
|
||||||
/^\d{2}\/\d{2}$/.test(newCardExpiry) && newCardExpiryParts === null
|
/^\d{2}\/\d{2}$/.test(newCardExpiry) && newCardExpiryParts === null
|
||||||
);
|
);
|
||||||
|
|
||||||
let addCardError = $derived(
|
const addCardError = $derived(
|
||||||
newCardNumber.length > 0 && !isValidLuhn(newCardNumber)
|
newCardNumber.length > 0 && !isValidLuhn(newCardNumber)
|
||||||
? 'Invalid card number'
|
? 'Invalid card number'
|
||||||
: isNewCardExpiryInvalidMonth
|
: isNewCardExpiryInvalidMonth
|
||||||
@@ -216,24 +216,24 @@
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
let isAddCardValid = $derived(
|
const isAddCardValid = $derived(
|
||||||
isValidLuhn(newCardNumber) && /^\d{2}\/\d{2}$/.test(newCardExpiry) && newCardCVC.length >= 3
|
isValidLuhn(newCardNumber) && /^\d{2}\/\d{2}$/.test(newCardExpiry) && newCardCVC.length >= 3
|
||||||
);
|
);
|
||||||
|
|
||||||
// Derived validations for Buy Gift Card form
|
// Derived validations for Buy Gift Card form
|
||||||
let buyNewCardExpiryParts = $derived(parseExpiryParts(buyNewCardExpiry));
|
const buyNewCardExpiryParts = $derived(parseExpiryParts(buyNewCardExpiry));
|
||||||
let isBuyNewCardExpiryInPast = $derived(
|
const isBuyNewCardExpiryInPast = $derived(
|
||||||
buyNewCardExpiryParts !== null &&
|
buyNewCardExpiryParts !== null &&
|
||||||
(() => {
|
(() => {
|
||||||
const expiryDate = new SvelteDate(buyNewCardExpiryParts.year, buyNewCardExpiryParts.month);
|
const expiryDate = new SvelteDate(buyNewCardExpiryParts.year, buyNewCardExpiryParts.month);
|
||||||
return expiryDate < new SvelteDate();
|
return expiryDate < new SvelteDate();
|
||||||
})()
|
})()
|
||||||
);
|
);
|
||||||
let isBuyNewCardExpiryInvalidMonth = $derived(
|
const isBuyNewCardExpiryInvalidMonth = $derived(
|
||||||
/^\d{2}\/\d{2}$/.test(buyNewCardExpiry) && buyNewCardExpiryParts === null
|
/^\d{2}\/\d{2}$/.test(buyNewCardExpiry) && buyNewCardExpiryParts === null
|
||||||
);
|
);
|
||||||
|
|
||||||
let buyCardError = $derived(
|
const buyCardError = $derived(
|
||||||
buyNewCardNumber.length > 0 && !isValidLuhn(buyNewCardNumber)
|
buyNewCardNumber.length > 0 && !isValidLuhn(buyNewCardNumber)
|
||||||
? 'Invalid card number'
|
? 'Invalid card number'
|
||||||
: isBuyNewCardExpiryInvalidMonth
|
: isBuyNewCardExpiryInvalidMonth
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
let isBuyCardValid = $derived(
|
const isBuyCardValid = $derived(
|
||||||
buySelectedCard !== '' ||
|
buySelectedCard !== '' ||
|
||||||
(isValidLuhn(buyNewCardNumber) &&
|
(isValidLuhn(buyNewCardNumber) &&
|
||||||
buyNewCardExpiryParts !== null &&
|
buyNewCardExpiryParts !== null &&
|
||||||
@@ -1142,11 +1142,11 @@
|
|||||||
let changingPassword = $state(false);
|
let changingPassword = $state(false);
|
||||||
|
|
||||||
// Password strength using zxcvbn
|
// Password strength using zxcvbn
|
||||||
let newPasswordStrength = $derived(passwordData.new ? zxcvbn(passwordData.new) : null);
|
const newPasswordStrength = $derived(passwordData.new ? zxcvbn(passwordData.new) : null);
|
||||||
let isPasswordStrongEnough = $derived(
|
const isPasswordStrongEnough = $derived(
|
||||||
!passwordData.new || newPasswordStrength === null || newPasswordStrength.score >= 2
|
!passwordData.new || newPasswordStrength === null || newPasswordStrength.score >= 2
|
||||||
);
|
);
|
||||||
let passwordsMatch = $derived(
|
const passwordsMatch = $derived(
|
||||||
passwordData.confirm === '' || passwordData.new === passwordData.confirm
|
passwordData.confirm === '' || passwordData.new === passwordData.confirm
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let error = $state(false);
|
let error = $state(false);
|
||||||
let page = $state(1);
|
let page = $state(1);
|
||||||
let perPage = $state(20);
|
const perPage = $state(20);
|
||||||
let total = $state(0);
|
let total = $state(0);
|
||||||
let includeAcknowledged = $state(false);
|
let includeAcknowledged = $state(false);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ async function proxyRequest(request: Request, path: string) {
|
|||||||
headers.delete('content-length');
|
headers.delete('content-length');
|
||||||
|
|
||||||
let body: BodyInit | undefined;
|
let body: BodyInit | undefined;
|
||||||
let fetchOptions: RequestInit = {};
|
const fetchOptions: RequestInit = {};
|
||||||
if (!['GET', 'HEAD'].includes(request.method)) {
|
if (!['GET', 'HEAD'].includes(request.method)) {
|
||||||
const contentType = request.headers.get('content-type') || '';
|
const contentType = request.headers.get('content-type') || '';
|
||||||
if (contentType.includes('multipart/form-data')) {
|
if (contentType.includes('multipart/form-data')) {
|
||||||
|
|||||||
@@ -295,7 +295,7 @@
|
|||||||
return !!(r?.referred_by || (r?.referred_users && r.referred_users.length > 0));
|
return !!(r?.referred_by || (r?.referred_users && r.referred_users.length > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasVat = $derived(
|
const hasVat = $derived(
|
||||||
gdprData?.payments?.some((p) => p.vat_amount && Number(p.vat_amount) > 0) ?? false
|
gdprData?.payments?.some((p) => p.vat_amount && Number(p.vat_amount) > 0) ?? false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -224,7 +224,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// when password changes, re-compute strength
|
// when password changes, re-compute strength
|
||||||
let passwordStrength = $derived(
|
const passwordStrength = $derived(
|
||||||
formData.password
|
formData.password
|
||||||
? (() => {
|
? (() => {
|
||||||
const result = zxcvbn(formData.password);
|
const result = zxcvbn(formData.password);
|
||||||
@@ -245,7 +245,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
// form completion check
|
// form completion check
|
||||||
let isFormComplete = $derived(
|
const isFormComplete = $derived(
|
||||||
isLogin ||
|
isLogin ||
|
||||||
(formData.firstName.trim() &&
|
(formData.firstName.trim() &&
|
||||||
formData.lastName.trim() &&
|
formData.lastName.trim() &&
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
// Tip selection state
|
// Tip selection state
|
||||||
let selectedTip = $state<number | null>(null);
|
let selectedTip = $state<number | null>(null);
|
||||||
let customTip = $state('');
|
let customTip = $state('');
|
||||||
let tipAmount = $derived(
|
const tipAmount = $derived(
|
||||||
selectedTip !== null ? selectedTip : customTip ? parseFloat(customTip) || 0 : 0
|
selectedTip !== null ? selectedTip : customTip ? parseFloat(customTip) || 0 : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
let loading = $state(false);
|
let loading = $state(false);
|
||||||
let selectedBookingId = $state<string | null>(null);
|
let selectedBookingId = $state<string | null>(null);
|
||||||
let showBookingModal = $state(false);
|
let showBookingModal = $state(false);
|
||||||
let businessSettings = $derived(getBusinessInfo());
|
const businessSettings = $derived(getBusinessInfo());
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
|
|
||||||
let selectedTip = $state<number | null>(null);
|
let selectedTip = $state<number | null>(null);
|
||||||
let customTip = $state('');
|
let customTip = $state('');
|
||||||
let tipAmount = $derived(
|
const tipAmount = $derived(
|
||||||
selectedTip !== null ? selectedTip : customTip ? parseFloat(customTip) || 0 : 0
|
selectedTip !== null ? selectedTip : customTip ? parseFloat(customTip) || 0 : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user