style: fix prefer-const and prettier formatting issues
Backend CI / Tests (push) Failing after 1m41s
Backend CI / Lint & vulns (push) Failing after 2m26s
Backend CI / Race detector (push) Failing after 3m45s

This commit is contained in:
2026-06-25 13:48:03 +01:00
parent e0236144c7
commit 4af2b8dfb4
64 changed files with 234 additions and 229 deletions
@@ -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
+2 -2
View File
@@ -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);
} }
+1 -1
View File
@@ -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');
+16 -16
View File
@@ -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);
+1 -1
View File
@@ -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')) {
+1 -1
View File
@@ -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
); );
+2 -2
View File
@@ -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
); );
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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
); );