style: fix no-unused-vars -- prefix unused catch bindings, remove dead code, suppress template-use false positives
This commit is contained in:
@@ -11,7 +11,6 @@
|
||||
import PolicyPopover from '$lib/components/ui/policyPopover.svelte';
|
||||
import type {
|
||||
Booking,
|
||||
BookingDiscount,
|
||||
Service,
|
||||
WorkingHoursDay,
|
||||
AvailableHoursDay
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { sanitizeText } from '$lib/utils/toast-safe';
|
||||
import { formatDateTime, formatDuration, calculateAge } from '$lib/utils/format';
|
||||
import { formatDateTime, formatDuration } from '$lib/utils/format';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
import { parseWallClockDate } from '$lib/utils/timeSlots';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
@@ -151,7 +151,7 @@
|
||||
const data = await response.json();
|
||||
overlappingBookings = data.bookings || [];
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// Silently handle - overlapping bookings couldn't be fetched
|
||||
} finally {
|
||||
loadingOverlaps = false;
|
||||
@@ -339,7 +339,7 @@
|
||||
const text = await response.text();
|
||||
toast.error('Failed to confirm: ' + sanitizeText(text), { id: loadingToast });
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error confirming booking', { id: loadingToast });
|
||||
} finally {
|
||||
submitting = false;
|
||||
@@ -368,7 +368,7 @@
|
||||
const text = await response.text();
|
||||
toast.error('Failed to decline: ' + sanitizeText(text), { id: loadingToast });
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error declining booking', { id: loadingToast });
|
||||
} finally {
|
||||
submitting = false;
|
||||
|
||||
@@ -146,8 +146,6 @@
|
||||
!customServiceErrors.minimum_age_required
|
||||
);
|
||||
|
||||
function toggleCustomForm(show: boolean) {
|
||||
showCustomCreateForm = show;
|
||||
if (show) {
|
||||
newCustomService = {
|
||||
name: '',
|
||||
@@ -326,7 +324,6 @@
|
||||
: !!(guestName.trim() && guestPhone.trim() && isValidUKPhone(guestPhone))
|
||||
);
|
||||
const canProceedStep2 = $derived(selectedServices.length > 0);
|
||||
const canProceedStep3 = $derived(true); // Overrides are optional
|
||||
const canProceedStep4 = $derived(!!(selectedDate && selectedTime));
|
||||
|
||||
/** Whether the currently selected time slot is out-of-hours */
|
||||
@@ -487,7 +484,7 @@
|
||||
(user: { account_role: string }) => !excludedRoles.includes(user.account_role)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to load users');
|
||||
} finally {
|
||||
loadingUsers = false;
|
||||
@@ -508,7 +505,7 @@
|
||||
if (response.ok) {
|
||||
services = await response.json();
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to load services');
|
||||
} finally {
|
||||
loadingServices = false;
|
||||
@@ -582,7 +579,7 @@
|
||||
workingHours = { ...workingHours, ...whMap };
|
||||
availableHours = { ...availableHours, ...ahMap };
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to load availability');
|
||||
} finally {
|
||||
_loadingWorkingHours = false;
|
||||
@@ -652,7 +649,7 @@
|
||||
workingHours = { ...workingHours, ...whMap };
|
||||
availableHours = { ...availableHours, ...ahMap };
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to load availability');
|
||||
} finally {
|
||||
_loadingWorkingHours = false;
|
||||
@@ -742,7 +739,7 @@
|
||||
toast.error(`Failed to reserve slot: ${errorText}`);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to reserve slot');
|
||||
return false;
|
||||
} finally {
|
||||
@@ -1042,7 +1039,7 @@
|
||||
const errorText = await res.text();
|
||||
toast.error(`Failed to create booking: ${errorText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('An error occurred while creating booking');
|
||||
} finally {
|
||||
submitting = false;
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
const text = await response.text();
|
||||
toast.error('Failed to cancel: ' + text);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error');
|
||||
} finally {
|
||||
cancelling = false;
|
||||
@@ -213,7 +213,7 @@
|
||||
const text = await response.text();
|
||||
toast.error('Failed to load booking details: ' + text);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Error fetching booking details:', err);
|
||||
toast.error('Network error loading booking details');
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import type { Booking } from '$lib/types/booking';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import * as Textarea from '$lib/components/ui/textarea';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import StatusBadge from '$lib/components/ui/StatusBadge.svelte';
|
||||
|
||||
type Campaign = {
|
||||
@@ -49,11 +48,6 @@
|
||||
anniversary: 'Anniversary'
|
||||
};
|
||||
|
||||
const SCOPE_LABELS: Record<string, string> = {
|
||||
all_bookings: 'All bookings',
|
||||
first_booking_only: 'First booking only',
|
||||
new_customers_only: 'New customers only'
|
||||
};
|
||||
|
||||
let campaigns = $state<Campaign[]>([]);
|
||||
let loading = $state(true);
|
||||
@@ -139,24 +133,6 @@
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
function openEditModal(c: Campaign) {
|
||||
editingCampaign = c;
|
||||
form = {
|
||||
name: c.name,
|
||||
description: c.description || '',
|
||||
campaign_type: c.campaign_type,
|
||||
discount_percent: c.discount_percent,
|
||||
scope: c.scope || 'all_bookings',
|
||||
start_date: c.start_date ? c.start_date.slice(0, 10) : '',
|
||||
end_date: c.end_date ? c.end_date.slice(0, 10) : '',
|
||||
milestone_type: c.milestone_type || 'per_user_booking_count',
|
||||
milestone_value: c.milestone_value || 0,
|
||||
milestone_unit: c.milestone_unit || 'bookings',
|
||||
max_redemptions: c.max_redemptions || 0
|
||||
};
|
||||
errors = {};
|
||||
showModal = true;
|
||||
}
|
||||
|
||||
async function submitForm() {
|
||||
errors = {};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import * as Modal from '$lib/components/ui/dialog';
|
||||
|
||||
@@ -48,7 +48,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
let cardQuery = $state('');
|
||||
let cards = $state<GiftCard[]>([]);
|
||||
let balances = $state<UserBalance[]>([]);
|
||||
let totalCards = $state(0);
|
||||
let _totalCards = $state(0);
|
||||
let totalBalanceRecords = $state(0);
|
||||
let currentPage = $state(1);
|
||||
let totalPages = $state(1);
|
||||
@@ -137,17 +137,17 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
// Page 3: Recipient email input
|
||||
let generateEmail = $state('');
|
||||
|
||||
let tillGuestEmail = $state<string | undefined>(undefined);
|
||||
let _tillGuestEmail = $state<string | undefined>(undefined);
|
||||
|
||||
// Payment Processing States
|
||||
let cashAmount = $state('');
|
||||
let cashTendered = $state(0);
|
||||
let extraAsTip = $state(false);
|
||||
let _cashTendered = $state(0);
|
||||
let _extraAsTip = $state(false);
|
||||
|
||||
let ephemeralCardNumber = $state('');
|
||||
let ephemeralCardExpiry = $state('');
|
||||
let ephemeralCardCVC = $state('');
|
||||
let ephemeralCardError = $state('');
|
||||
let _ephemeralCardError = $state('');
|
||||
|
||||
let paymentError = $state('');
|
||||
let paymentResult = $state<{
|
||||
@@ -157,7 +157,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
status?: string;
|
||||
} | null>(null);
|
||||
let cardMachineItemID = $state<string | null>(null);
|
||||
let checkoutId = $state<string | null>(null);
|
||||
let _checkoutId = $state<string | null>(null);
|
||||
let processingMessage = $state('Processing payment...');
|
||||
|
||||
// Idempotency Key
|
||||
@@ -242,12 +242,12 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
);
|
||||
|
||||
// TillPayment state
|
||||
const showTillPayment = $state(false);
|
||||
const tillAmount = $state(0);
|
||||
const tillAction = $state<'create' | 'topup'>('create');
|
||||
const tillGiftCardId = $state<string | undefined>(undefined);
|
||||
const tillUserId = $state<string | undefined>(undefined);
|
||||
const tillDelivery = $state<'account' | 'code'>('code');
|
||||
const _showTillPayment = $state(false);
|
||||
const _tillAmount = $state(0);
|
||||
const _tillAction = $state<'create' | 'topup'>('create');
|
||||
const _tillGiftCardId = $state<string | undefined>(undefined);
|
||||
const _tillUserId = $state<string | undefined>(undefined);
|
||||
const _tillDelivery = $state<'account' | 'code'>('code');
|
||||
|
||||
async function fetchGiftCards(page: number = 1, search: string = '') {
|
||||
loading = true;
|
||||
@@ -270,14 +270,14 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
summary.total_user_balances = data.total_user_balances;
|
||||
cards = data.gift_cards;
|
||||
balances = data.user_balances;
|
||||
totalCards = data.total;
|
||||
_totalCards = data.total;
|
||||
totalBalanceRecords = data.ub_total ?? data.user_balances?.length ?? 0;
|
||||
currentPage = data.page;
|
||||
totalPages = data.totalPages;
|
||||
} else {
|
||||
toast.error('Failed to fetch gift cards');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error fetching gift cards');
|
||||
} finally {
|
||||
loading = false;
|
||||
@@ -304,7 +304,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
} else {
|
||||
toast.error('Failed to fetch expired balances');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error fetching expired balances');
|
||||
} finally {
|
||||
loadingExpired = false;
|
||||
@@ -329,7 +329,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
const err = await res.json();
|
||||
toast.error(err.error || 'Failed to claim balance');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error claiming balance');
|
||||
} finally {
|
||||
claimingId = null;
|
||||
@@ -427,7 +427,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
const errText = await res.text();
|
||||
toast.error(errText || 'Failed to create inventory card');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error');
|
||||
} finally {
|
||||
creating = false;
|
||||
@@ -470,7 +470,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
const errText = await res.text();
|
||||
toast.error(errText || 'Failed to transfer balance');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error transferring balance');
|
||||
} finally {
|
||||
transferring = false;
|
||||
@@ -488,7 +488,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
generateEmail = '';
|
||||
generateUserQuery = '';
|
||||
generateUsers = [];
|
||||
tillGuestEmail = undefined;
|
||||
_tillGuestEmail = undefined;
|
||||
}
|
||||
|
||||
function resetTopUpModal() {
|
||||
@@ -498,16 +498,16 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
|
||||
// Reset payment
|
||||
cashAmount = '';
|
||||
cashTendered = 0;
|
||||
extraAsTip = false;
|
||||
_cashTendered = 0;
|
||||
_extraAsTip = false;
|
||||
ephemeralCardNumber = '';
|
||||
ephemeralCardExpiry = '';
|
||||
ephemeralCardCVC = '';
|
||||
ephemeralCardError = '';
|
||||
_ephemeralCardError = '';
|
||||
paymentError = '';
|
||||
paymentResult = null;
|
||||
cardMachineItemID = null;
|
||||
checkoutId = null;
|
||||
_checkoutId = null;
|
||||
idempotencyKey = '';
|
||||
}
|
||||
|
||||
@@ -622,7 +622,7 @@ import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
const data = await res.json();
|
||||
cardMachineItemID = data.item_id || null;
|
||||
if (data.status === 'pending' && data.checkout_id) {
|
||||
checkoutId = data.checkout_id;
|
||||
_checkoutId = data.checkout_id;
|
||||
setModalStep(actionType, 'processing');
|
||||
pollEmbeddedCheckout(data.checkout_id, amt, actionType);
|
||||
} else {
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
}
|
||||
|
||||
let availableTags = $state<string[]>([]);
|
||||
let loadingTags = $state(true);
|
||||
let _loadingTags = $state(true);
|
||||
let isMobile = $state(false);
|
||||
|
||||
async function fetchTags() {
|
||||
@@ -374,7 +374,7 @@
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch tags:', e);
|
||||
} finally {
|
||||
loadingTags = false;
|
||||
_loadingTags = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import { SvelteMap } from 'svelte/reactivity';
|
||||
is_active: true,
|
||||
minimum_age_required: 0
|
||||
});
|
||||
const editingService = $state<Service | null>(null);
|
||||
let servicesLoading = $state(true);
|
||||
const servicesUpdating = $state<Record<string, boolean>>({});
|
||||
let showServiceModal = $state(false);
|
||||
|
||||
@@ -29,9 +29,9 @@
|
||||
let noSlotsToday = $state(false);
|
||||
let currentTime = new SvelteDate();
|
||||
|
||||
let reservationId = $state<string | null>(null);
|
||||
let _reservationId = $state<string | null>(null);
|
||||
let reservationExpiresAt = $state<Date | null>(null);
|
||||
let reservationCountdown = $state<string>('');
|
||||
let _reservationCountdown = $state<string>('');
|
||||
let isReserving = $state(false);
|
||||
let reservedDuration = $state(0);
|
||||
let reservedStartTime = $state<string | null>(null);
|
||||
@@ -50,7 +50,7 @@
|
||||
shortestServiceMinutes = Math.min(...durations);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// Silently handled - services list remains empty
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@
|
||||
const dayEndTimeVal = minutesToTime(dayEndMinutesVal);
|
||||
|
||||
if (shouldApplyLunchProtection(dayStartTimeVal, dayEndTimeVal)) {
|
||||
const { windowStart, windowEnd } = calculateMiddleWindow(dayStartTimeVal, dayEndTimeVal);
|
||||
const { windowStart, windowEnd: _windowEnd } = calculateMiddleWindow(dayStartTimeVal, dayEndTimeVal);
|
||||
const lunchWalkerBlocker = {
|
||||
startTime: minutesToTime(windowStart),
|
||||
endTime: minutesToTime(windowStart + 60)
|
||||
@@ -220,7 +220,7 @@
|
||||
}
|
||||
|
||||
noSlotsToday = true;
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to calculate slot availability', err);
|
||||
noSlotsToday = true;
|
||||
} finally {
|
||||
@@ -264,7 +264,7 @@
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
reservationId = data.id;
|
||||
_reservationId = data.id;
|
||||
reservedDuration = data.duration_minutes;
|
||||
reservationExpiresAt = new Date(data.expires_at);
|
||||
startWalkInCountdown();
|
||||
@@ -278,7 +278,7 @@
|
||||
toast.error(`Failed to reserve slot: ${errorText}`);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Reservation error:', err);
|
||||
toast.error('Failed to reserve slot');
|
||||
return false;
|
||||
@@ -294,7 +294,7 @@
|
||||
|
||||
const updateCountdown = () => {
|
||||
if (!reservationExpiresAt) {
|
||||
reservationCountdown = '';
|
||||
_reservationCountdown = '';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -302,8 +302,8 @@
|
||||
const diff = reservationExpiresAt.getTime() - now.getTime();
|
||||
|
||||
if (diff <= 0) {
|
||||
reservationCountdown = 'Expired';
|
||||
reservationId = null;
|
||||
_reservationCountdown = 'Expired';
|
||||
_reservationId = null;
|
||||
reservationExpiresAt = null;
|
||||
toast.error('Slot released — please re-check availability');
|
||||
if (window.__walkInCountdownInterval) {
|
||||
@@ -315,7 +315,7 @@
|
||||
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const seconds = Math.floor((diff % 60000) / 1000);
|
||||
reservationCountdown = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
_reservationCountdown = `${minutes}:${seconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
updateCountdown();
|
||||
@@ -372,9 +372,9 @@
|
||||
|
||||
function handleModalClose() {
|
||||
showCreateModal = false;
|
||||
reservationId = null;
|
||||
_reservationId = null;
|
||||
reservationExpiresAt = null;
|
||||
reservationCountdown = '';
|
||||
_reservationCountdown = '';
|
||||
reservedDuration = 0;
|
||||
reservedStartTime = null;
|
||||
if (window.__walkInCountdownInterval) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { generateUUID } from '$lib/utils/uuid';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
|
||||
import { getLocalTimeZone } from '@internationalized/date';
|
||||
import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
|
||||
import { formatUserName } from '$lib/utils/nameDisplay';
|
||||
import { formatLocalDateTime } from '$lib/utils/timeSlots';
|
||||
@@ -33,7 +32,7 @@
|
||||
availableStartTime?: string;
|
||||
reservationExpiresAt?: Date | null;
|
||||
onBookingCreated?: () => void;
|
||||
onclose?: () => void;
|
||||
_onclose?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -42,7 +41,7 @@
|
||||
availableStartTime,
|
||||
reservationExpiresAt,
|
||||
onBookingCreated,
|
||||
onclose
|
||||
_onclose
|
||||
}: Props = $props();
|
||||
|
||||
// =============== State ===============
|
||||
@@ -297,7 +296,7 @@
|
||||
(user: { account_role: string }) => !excludedRoles.includes(user.account_role)
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to fetch users', err);
|
||||
toast.error('Failed to load users');
|
||||
} finally {
|
||||
@@ -319,7 +318,7 @@
|
||||
if (response.ok) {
|
||||
services = await response.json();
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Failed to load services');
|
||||
} finally {
|
||||
loadingServices = false;
|
||||
@@ -569,7 +568,7 @@
|
||||
const errorText = await res.text();
|
||||
toast.error(`Failed to create booking: ${errorText}`);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('An error occurred while creating booking');
|
||||
} finally {
|
||||
submitting = false;
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
import { Textarea } from '$lib/components/ui/textarea/index.js';
|
||||
import CharCounter from '$lib/components/ui/CharCounter.svelte';
|
||||
import { Separator } from '$lib/components/ui/separator/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { PhoneInput } from '$lib/components/ui/phone-input/index.js';
|
||||
import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
|
||||
// INTENTIONAL: We use the browser's local timezone (getLocalTimeZone) because Crussell is a UK-only
|
||||
@@ -26,7 +25,6 @@
|
||||
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Components
|
||||
import BookingActions from '$lib/components/booking/BookingActions.svelte';
|
||||
@@ -42,7 +40,6 @@
|
||||
import {
|
||||
extractBookedSlots,
|
||||
getLunchProtectionForSlots,
|
||||
type TimeSlot
|
||||
} from '$lib/lunchProtection';
|
||||
import { formatLocalDateTime, getLondonTodayCalendarDate } from '$lib/utils/timeSlots';
|
||||
|
||||
@@ -79,13 +76,13 @@
|
||||
phone: '',
|
||||
specialRequests: ''
|
||||
});
|
||||
let isSubmitting = $state(false);
|
||||
let _isSubmitting = $state(false);
|
||||
let idempotencyKey = $state<string>('');
|
||||
|
||||
// =============== Payment State ===============
|
||||
let userDepositsRequired = $state<number>(0);
|
||||
let hasActiveBooking = $state<boolean>(false);
|
||||
let activeBookingCheckDone = $state<boolean>(false);
|
||||
let _activeBookingCheckDone = $state<boolean>(false);
|
||||
let paymentMethods = $state<
|
||||
Array<{ id: string; brand: string; last4: string; expiry_month: number; expiry_year: number }>
|
||||
>([]);
|
||||
@@ -98,11 +95,11 @@
|
||||
let newCardNumber = $state('');
|
||||
let newCardExpiry = $state('');
|
||||
let newCardCVC = $state('');
|
||||
const saveCardForFuture = $state(false);
|
||||
const _saveCardForFuture = $state(false);
|
||||
|
||||
// Payment flow state
|
||||
let depositPaid = $state(false);
|
||||
let showPaymentForm = $state(false);
|
||||
let _showPaymentForm = $state(false);
|
||||
|
||||
const depositCardFormValid = $derived(
|
||||
selectedPaymentMethod !== null ||
|
||||
@@ -213,7 +210,7 @@
|
||||
const user = await response.json();
|
||||
userDepositsRequired = user.deposits_required ?? 0;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
userDepositsRequired = 0;
|
||||
}
|
||||
}
|
||||
@@ -221,11 +218,11 @@
|
||||
async function fetchActiveBookingStatus() {
|
||||
if (!authStore.isAuthenticated) {
|
||||
hasActiveBooking = false;
|
||||
activeBookingCheckDone = true;
|
||||
_activeBookingCheckDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
activeBookingCheckDone = false;
|
||||
_activeBookingCheckDone = false;
|
||||
try {
|
||||
// Check for pending bookings
|
||||
const pendingResp = await fetch('/api/bookings?status=pending&perPage=1', {
|
||||
@@ -235,7 +232,7 @@
|
||||
const data = await pendingResp.json();
|
||||
if (data.bookings && data.bookings.length > 0) {
|
||||
hasActiveBooking = true;
|
||||
activeBookingCheckDone = true;
|
||||
_activeBookingCheckDone = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -248,42 +245,13 @@
|
||||
const data = await confirmedResp.json();
|
||||
hasActiveBooking = data.bookings && data.bookings.length > 0;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
hasActiveBooking = false;
|
||||
} finally {
|
||||
activeBookingCheckDone = true;
|
||||
_activeBookingCheckDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchPaymentMethods() {
|
||||
if (!authStore.isAuthenticated) {
|
||||
paymentMethods = [];
|
||||
return;
|
||||
}
|
||||
|
||||
paymentMethodsLoading = true;
|
||||
try {
|
||||
const response = await fetch('/api/user/payment-methods', {
|
||||
headers: authStore.currentToken ? { Authorization: `Bearer ${authStore.currentToken}` } : {}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
paymentMethods = data.payment_methods ?? [];
|
||||
if (paymentMethods.length > 0 && !selectedPaymentMethod) {
|
||||
const defaultCard =
|
||||
paymentMethods.find((m) => 'is_default' in m && m.is_default) ?? paymentMethods[0];
|
||||
selectedPaymentMethod = defaultCard.id;
|
||||
}
|
||||
} else {
|
||||
paymentMethods = [];
|
||||
}
|
||||
} catch (err) {
|
||||
paymentMethods = [];
|
||||
} finally {
|
||||
paymentMethodsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function calculateDepositRequired(): boolean {
|
||||
if (!selectedDate || !selectedTime) return false;
|
||||
@@ -311,7 +279,7 @@
|
||||
discountPreview = data;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to fetch discount preview:', err);
|
||||
}
|
||||
}
|
||||
@@ -364,7 +332,7 @@
|
||||
const text = await response.text();
|
||||
toast.warning(text || 'Payment failed — you can pay again from your booking details.');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error(
|
||||
'An error occurred. Your booking may still be confirmed — check your appointments.'
|
||||
);
|
||||
@@ -375,35 +343,14 @@
|
||||
|
||||
let paymentAttempted = $state(false);
|
||||
|
||||
function handlePayNow() {
|
||||
showPaymentForm = true;
|
||||
if (authStore.isAuthenticated) {
|
||||
fetchPaymentMethods();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSkipPayment() {
|
||||
depositPaid = false;
|
||||
nextStep();
|
||||
}
|
||||
|
||||
|
||||
function formatCardExpiry(month: number, year: number): string {
|
||||
return `${String(month).padStart(2, '0')}/${year.toString().slice(-2)}`;
|
||||
}
|
||||
|
||||
function formatDepositCardNumber(value: string): string {
|
||||
const digits = value.replace(/\D/g, '').substring(0, 16);
|
||||
const groups = digits.match(/.{1,4}/g);
|
||||
return groups ? groups.join(' ') : digits;
|
||||
}
|
||||
|
||||
function formatDepositExpiry(value: string): string {
|
||||
const digits = value.replace(/\D/g, '').substring(0, 4);
|
||||
if (digits.length >= 3) {
|
||||
return digits.substring(0, 2) + '/' + digits.substring(2);
|
||||
}
|
||||
return digits;
|
||||
}
|
||||
|
||||
// Fetch user deposit and active booking status when step 1 is reached
|
||||
$effect(() => {
|
||||
@@ -414,15 +361,15 @@
|
||||
});
|
||||
|
||||
// =============== Slot Reservation System ===============
|
||||
let reservationId = $state<string | null>(null);
|
||||
let _reservationId = $state<string | null>(null);
|
||||
let reservationExpiresAt = $state<Date | null>(null);
|
||||
let reservationCountdown = $state<string>('');
|
||||
let reservationExpired = $state(false);
|
||||
let isReserving = $state(false);
|
||||
let _isReserving = $state(false);
|
||||
|
||||
// =============== Slot Reservation Functions ===============
|
||||
async function reserveSlot() {
|
||||
isReserving = true;
|
||||
_isReserving = true;
|
||||
try {
|
||||
if (!selectedDate || !selectedTime) {
|
||||
toast.error('Please select a date and time');
|
||||
@@ -442,7 +389,7 @@
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
const _errorText = await response.text();
|
||||
if (response.status === 429) {
|
||||
toast.error('Too many active reservations. Please wait or log in.');
|
||||
} else if (response.status === 409) {
|
||||
@@ -455,16 +402,16 @@
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
reservationId = data.id;
|
||||
_reservationId = data.id;
|
||||
reservationExpiresAt = new Date(data.expires_at);
|
||||
reservationExpired = false;
|
||||
startCountdown();
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
toast.error('Network error while reserving slot.');
|
||||
return false;
|
||||
} finally {
|
||||
isReserving = false;
|
||||
_isReserving = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +430,7 @@
|
||||
if (diff <= 0) {
|
||||
reservationCountdown = '00:00';
|
||||
reservationExpired = true;
|
||||
reservationId = null;
|
||||
_reservationId = null;
|
||||
reservationExpiresAt = null;
|
||||
toast.error('Your reservation has expired. Please select a new time slot.');
|
||||
return;
|
||||
@@ -550,7 +497,7 @@
|
||||
} else {
|
||||
toast.error('Failed to load services');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
toast.error('Network error loading services');
|
||||
} finally {
|
||||
servicesLoading = false;
|
||||
@@ -760,7 +707,7 @@
|
||||
// MERGE instead of replace — preserves data from previously loaded months
|
||||
workingHours = { ...workingHours, ...whMap };
|
||||
availableHours = { ...availableHours, ...ahMap };
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Clean up loadingMonths for the range
|
||||
for (let i = 0; i < months; i++) {
|
||||
let mYear = startDate.year;
|
||||
@@ -860,7 +807,7 @@
|
||||
|
||||
availableHoursCache[monthKey] = availableHoursMap;
|
||||
availableHours = { ...availableHours, ...availableHoursMap };
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
if (!selectedDate) {
|
||||
selectedDate = minDate;
|
||||
}
|
||||
@@ -1362,7 +1309,7 @@
|
||||
}
|
||||
|
||||
async function submitAndProceed() {
|
||||
isSubmitting = true;
|
||||
_isSubmitting = true;
|
||||
try {
|
||||
if (!idempotencyKey) {
|
||||
// Generate UUID v4 manually for environments where crypto.randomUUID() is unavailable
|
||||
@@ -1385,7 +1332,7 @@
|
||||
|
||||
if (!selectedDate || !selectedTime) {
|
||||
toast.error('Please select a date and time');
|
||||
isSubmitting = false;
|
||||
_isSubmitting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1410,13 +1357,13 @@
|
||||
});
|
||||
|
||||
if (!guestResponse.ok) {
|
||||
const errorText = await guestResponse.text();
|
||||
const _errorText = await guestResponse.text();
|
||||
if (guestResponse.status === 409) {
|
||||
toast.error('Email already registered — please log in to book.');
|
||||
} else {
|
||||
toast.error('Failed to create guest account. Please try again.');
|
||||
}
|
||||
isSubmitting = false;
|
||||
_isSubmitting = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1507,10 +1454,10 @@
|
||||
toast.error('Failed to submit booking: ' + errorMessage);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
toast.error('Network error. Please check your connection and try again.');
|
||||
} finally {
|
||||
isSubmitting = false;
|
||||
_isSubmitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1550,7 +1497,7 @@
|
||||
)) &&
|
||||
!reservationExpired
|
||||
);
|
||||
const canProceedStep4 = $derived(true);
|
||||
const _canProceedStep4 = $derived(true);
|
||||
</script>
|
||||
|
||||
<div class="mx-auto max-w-4xl p-6">
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
);
|
||||
|
||||
let customerBalance = $state(0);
|
||||
let loadingCustomerBalance = $state(false);
|
||||
let _loadingCustomerBalance = $state(false);
|
||||
let giftCardPaymentAmount = $state('');
|
||||
let savedCardList = $state<
|
||||
Array<{
|
||||
@@ -73,12 +73,12 @@
|
||||
cardholder_name?: string;
|
||||
}>
|
||||
>([]);
|
||||
let loadingSavedCardList = $state(false);
|
||||
let _loadingSavedCardList = $state(false);
|
||||
|
||||
async function fetchCustomerGiftCardBalance() {
|
||||
const targetUserId = booking.user_id ?? booking.user?.id;
|
||||
if (!targetUserId) return;
|
||||
loadingCustomerBalance = true;
|
||||
_loadingCustomerBalance = true;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${targetUserId}/giftcard-balance`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
@@ -94,14 +94,14 @@
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
loadingCustomerBalance = false;
|
||||
_loadingCustomerBalance = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSavedCardList() {
|
||||
const targetUserId = booking.user_id ?? booking.user?.id;
|
||||
if (!targetUserId) return;
|
||||
loadingSavedCardList = true;
|
||||
_loadingSavedCardList = true;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${targetUserId}/payment-methods`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
@@ -112,7 +112,7 @@
|
||||
} catch {
|
||||
// ignore
|
||||
} finally {
|
||||
loadingSavedCardList = false;
|
||||
_loadingSavedCardList = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
checkoutId = data.checkout_id;
|
||||
status = 'card-polling';
|
||||
startPolling();
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
error = err instanceof Error ? err.message : 'Failed to initiate payment';
|
||||
toast.error(error ?? 'Unknown error');
|
||||
@@ -344,7 +344,7 @@
|
||||
error = data.error_message || 'Payment failed';
|
||||
toast.error(error as string);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
stopPolling();
|
||||
status = 'error';
|
||||
error = 'Failed to check payment status';
|
||||
@@ -450,7 +450,7 @@
|
||||
};
|
||||
toast.success('Cash payment recorded');
|
||||
onComplete(paymentResult);
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
error = err instanceof Error ? err.message : 'Failed to process payment';
|
||||
toast.error(error ?? 'Unknown error');
|
||||
@@ -575,7 +575,7 @@
|
||||
};
|
||||
toast.success('Gift card payment recorded');
|
||||
onComplete(paymentResult);
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
error = err instanceof Error ? err.message : 'Failed to process gift card';
|
||||
toast.error(error ?? 'Unknown error');
|
||||
@@ -657,7 +657,7 @@
|
||||
};
|
||||
toast.success('Saved card payment successful');
|
||||
onComplete(paymentResult);
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
error = err instanceof Error ? err.message : 'Failed to process saved card payment';
|
||||
toast.error(error ?? 'Unknown error');
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
let cashTendered = $state(0);
|
||||
|
||||
// Card Machine
|
||||
let checkoutId = $state<string | null>(null);
|
||||
let _checkoutId = $state<string | null>(null);
|
||||
// Preserve the item_id from the card machine initial response (polling overwrites result)
|
||||
let cardMachineItemID = $state<string | null>(null);
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
step = 'customer-selection';
|
||||
error = null;
|
||||
cashAmount = '';
|
||||
checkoutId = null;
|
||||
_checkoutId = null;
|
||||
cardMachineItemID = null;
|
||||
selectedCustomer = null;
|
||||
isGuest = false;
|
||||
@@ -477,7 +477,7 @@
|
||||
error = await res.text();
|
||||
step = 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
error = 'Network error processing cash payment';
|
||||
step = 'error';
|
||||
}
|
||||
@@ -509,7 +509,7 @@
|
||||
const data = await res.json();
|
||||
cardMachineItemID = data.item_id || null;
|
||||
if (data.status === 'pending' && data.checkout_id) {
|
||||
checkoutId = data.checkout_id;
|
||||
_checkoutId = data.checkout_id;
|
||||
step = 'card-polling';
|
||||
pollCheckout(data.checkout_id);
|
||||
} else {
|
||||
@@ -520,7 +520,7 @@
|
||||
error = await res.text();
|
||||
step = 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
error = 'Network error initiating card machine payment';
|
||||
step = 'error';
|
||||
}
|
||||
@@ -603,7 +603,7 @@
|
||||
error = await res.text();
|
||||
step = 'error';
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
error = 'Network error processing card payment';
|
||||
step = 'error';
|
||||
} finally {
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
return groups ? groups.join(' ') : digits;
|
||||
}
|
||||
|
||||
function handleCardNumberInput(e: Event) {
|
||||
function _handleCardNumberInput(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const formatted = formatAndPreserveCursor(input, formatCardNumber);
|
||||
newCardNumber = formatted;
|
||||
@@ -117,13 +117,13 @@
|
||||
return digits;
|
||||
}
|
||||
|
||||
function handleExpiryInput(e: Event) {
|
||||
function _handleExpiryInput(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const formatted = formatAndPreserveCursor(input, formatExpiryDate);
|
||||
newCardExpiry = formatted;
|
||||
}
|
||||
|
||||
function handleCvcInput(e: Event) {
|
||||
function _handleCvcInput(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const formatted = formatAndPreserveCursor(input, (val) =>
|
||||
val.replace(/\D/g, '').substring(0, 4)
|
||||
@@ -305,7 +305,7 @@
|
||||
(booking.status === 'pending_release' && (lockTimer <= 0 || !lockAcquired))
|
||||
);
|
||||
|
||||
const payButtonError = $derived(
|
||||
const _payButtonError = $derived(
|
||||
status === 'processing'
|
||||
? null
|
||||
: !cardSelected
|
||||
@@ -357,7 +357,7 @@
|
||||
lockAcquired = true;
|
||||
lockTimer = 300;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to acquire payment lock:', err);
|
||||
}
|
||||
}
|
||||
@@ -372,7 +372,7 @@
|
||||
Authorization: `Bearer ${authStore.currentToken}`
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to release payment lock:', err);
|
||||
}
|
||||
}
|
||||
@@ -396,7 +396,7 @@
|
||||
lockTimer = 300;
|
||||
lockAcquired = true;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to renew payment lock:', err);
|
||||
}
|
||||
}, 60000);
|
||||
@@ -432,7 +432,7 @@
|
||||
if (response.ok) {
|
||||
paymentMethods = await response.json();
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to fetch payment methods:', err);
|
||||
} finally {
|
||||
paymentMethodsLoading = false;
|
||||
@@ -451,7 +451,7 @@
|
||||
const data = await response.json();
|
||||
stamps = data.stamps ?? 0;
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to fetch loyalty data:', err);
|
||||
}
|
||||
}
|
||||
@@ -505,7 +505,7 @@
|
||||
const errData = await redemptionResponse.text();
|
||||
throw new Error(errData || 'Failed to apply loyalty discount');
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
const msg = err instanceof Error ? err.message : 'Failed to apply loyalty discount';
|
||||
error = msg;
|
||||
@@ -568,7 +568,7 @@
|
||||
fetchPaymentMethods();
|
||||
onComplete();
|
||||
releaseLock();
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
status = 'error';
|
||||
const msg = err instanceof Error ? err.message : 'Payment declined';
|
||||
error = msg;
|
||||
@@ -642,7 +642,7 @@
|
||||
if (resp.ok) {
|
||||
discountPreview = await resp.json();
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
console.error('Failed to fetch discount preview:', err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
value = $bindable(''),
|
||||
error = $bindable(''),
|
||||
placeholder = '07123 456789 or +44 7123 456789',
|
||||
required = false,
|
||||
_required = false,
|
||||
disabled = false,
|
||||
class: className = '',
|
||||
id = 'phone',
|
||||
@@ -29,7 +29,7 @@
|
||||
let touched = $state(false);
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const _target = e.target as HTMLInputElement;
|
||||
const allowedKeys = [
|
||||
'Backspace',
|
||||
'Delete',
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
|
||||
function handleInput(e: Event) {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const _target = e.target as HTMLInputElement;
|
||||
const formatted = formatPhoneDisplay(target.value);
|
||||
value = formatted;
|
||||
onvaluechange?.(formatted);
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { SvelteDate } from 'svelte/reactivity';
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
import { cubicOut } from 'svelte/easing';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { authStore } from '$lib/stores/auth.svelte';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
|
||||
Reference in New Issue
Block a user