Gift-card rolling expiry, SvelteDate→Date purge, strict DST tests, UTC scan-location + settings legal floor

Gift-card rolling expiry (setting-driven, was dead config):
- GetGiftCardExpiryMonths(): single source of truth (business_settings
  gift_card_expiry_months, fallback 24) shared by payment handlers and the
  CleanupExpiredGiftCards job (was hardcoded 24).
- expiry_date now maintained on ALL 9 gift-card write sites (buy, topup,
  transfer, redeem, terminal payment, refund credit, till) so the refund-time
  guard at refunds.go actually fires. Schema default 12->24 + migration note;
  test-DB seed aligned. Stale "expiry_date IS NULL" test rewritten; new
  expired-card-rejected regression test.

Frontend SvelteDate purge (docs' stated convention, wide):
- All 180+ raw `new SvelteDate(...)` uses across routes/components replaced
  with parseWallClockDate (backend UTC ISO) or new Date (wall-clock
  constructors). SvelteDate imports removed. timeSlots.ts getDayWithOrdinal
  fixed. Zero SvelteDate references remain; svelte-check clean.

Strict timezone/DST testing + QA fixes:
- 8 new hermetic boundary tests: clock.DST transitions (both 2026 folds),
  closing-hours GMT vs BST, booking date-window midnight, refund-tier
  elapsed-time independence, deposit-window UTC-instant, scheduling
  LondonDateString midnight, today AT TIME ZONE window + UTC round-trip.
- today.go summary date labels fixed to London wall-clock (were showing the
  previous UTC day during BST) + regression test.
- pgx ScanLocation fixed to UTC via AfterConnect (was host-local -> JSON
  offsets depended on deployment TZ, contradicting the documented UTC
  invariant) + regression test. Registered as a new *Type to avoid a data
  race on the shared type map (caught by -race).

Admin Business Settings (setting now functional => legal floor):
- gift_card_expiry_months validation floor raised 1 -> 12 months (CMA/
  Consumer Rights Act 2015 unfair-contract-term guidance) in endpoint + UI,
  with rolling-expiry semantics shown in both display and edit form.
- 3 new expiry validation tests; 2 pre-existing message assertions updated.

Full suite 25/25 + race clean via run-tests.sh lockfile; svelte-check 0
errors/warnings; production build succeeds.
This commit is contained in:
2026-08-22 00:34:49 +01:00
parent 7f1c649f1e
commit 197d4c4b9b
54 changed files with 1204 additions and 275 deletions
@@ -1,6 +1,6 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { SvelteDate, SvelteMap } from 'svelte/reactivity';
import { SvelteMap } from 'svelte/reactivity';
import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
@@ -16,7 +16,7 @@
getLunchProtectionForSlots,
timeToMinutes
} from '$lib/lunchProtection';
import { formatLocalDateTime, getLondonTodayCalendarDate } from '$lib/utils/timeSlots';
import { formatLocalDateTime, getLondonTodayCalendarDate, parseWallClockDate } from '$lib/utils/timeSlots';
import ClockIcon from '@lucide/svelte/icons/clock';
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
@@ -71,7 +71,7 @@
// ─── Date constants ─────────────────────────────────────
const todayCalendarDate = getLondonTodayCalendarDate();
const minDate = todayCalendarDate;
const maxDate = new SvelteDate(
const maxDate = new Date(
todayCalendarDate.year,
todayCalendarDate.month - 1,
todayCalendarDate.day
@@ -85,7 +85,7 @@
let placeholderDate = $state<CalendarDate>(minDate);
const hoursUntilAppointment = $derived(
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
(parseWallClockDate(booking.start_time).getTime() - new Date().getTime()) / (1000 * 60 * 60)
);
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
const noticePeriodBlocked = $derived(
@@ -420,7 +420,7 @@
$effect(() => {
if (editMode === 'services' && !servicesHoursFetched) {
servicesHoursFetched = true;
const bookingDate = new SvelteDate(booking.start_time);
const bookingDate = parseWallClockDate(booking.start_time);
const calDate = new CalendarDate(
bookingDate.getFullYear(),
bookingDate.getMonth() + 1,
@@ -445,8 +445,8 @@
return;
editRequestAutoSelectDone = true;
const currentDate = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new SvelteDate(
const currentDate = new Date(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new Date(
maxCalendarDate.year,
maxCalendarDate.month - 1,
maxCalendarDate.day
@@ -458,7 +458,7 @@
const daysToCheck = Math.min(daysDifference, 180);
for (let i = 0; i <= daysToCheck; i++) {
const nextDate = new SvelteDate(currentDate);
const nextDate = new Date(currentDate);
nextDate.setDate(currentDate.getDate() + i);
const dateStr = nextDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
@@ -637,7 +637,7 @@
function calculateRemainingTime(): number {
if (!workingHours || !availableHours) return 0;
const bookingDate = new SvelteDate(booking.start_time);
const bookingDate = parseWallClockDate(booking.start_time);
const dateStr = bookingDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const dayWH = workingHours[dateStr];
@@ -4,7 +4,6 @@
import { authStore } from '$lib/stores/auth.svelte';
import { apiFetch } from '$lib/utils/api';
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import * as Modal from '$lib/components/ui/dialog';
@@ -56,7 +55,7 @@
);
const isFutureBooking = $derived(
selectedBooking ? new SvelteDate(selectedBooking.start_time) > new SvelteDate() : false
selectedBooking ? parseWallClockDate(selectedBooking.start_time) > new Date() : false
);
const hasPayments = $derived(
@@ -112,7 +111,7 @@
const hoursUntilAppointment = $derived(
selectedBooking
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
? (parseWallClockDate(selectedBooking.start_time).getTime() - new Date().getTime()) /
(1000 * 60 * 60)
: Infinity
);
@@ -555,7 +554,7 @@ ${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}
</div>
{#if selectedBooking}
{@const isPastBooking = new SvelteDate(selectedBooking.start_time) < new SvelteDate()}
{@const isPastBooking = parseWallClockDate(selectedBooking.start_time) < new Date()}
{@const isUnpaid = selectedBooking.amount_due > 0}
{@const showChip = !isPastBooking || isUnpaid}
{@const isConfirmedOrLater = ['confirmed', 'in_progress', 'completed'].includes(
@@ -849,7 +848,7 @@ ${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}
</div>
{/if}
<div class="mt-1 text-xs text-gray-400">
{new SvelteDate(payment.created_at).toLocaleString()}
{parseWallClockDate(payment.created_at).toLocaleString()}
</div>
</div>
<div class="text-right font-semibold">
@@ -875,7 +874,7 @@ ${hasVAT ? `<p class="warning">VAT is included at ${biz?.default_vat_rate ?? 20}
{refund.reason || 'Refund processed'}
</div>
<div class="mt-1 text-xs text-gray-400">
{new SvelteDate(refund.created_at).toLocaleString()}
{parseWallClockDate(refund.created_at).toLocaleString()}
</div>
</div>
<div class="text-right font-semibold text-red-600">
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { apiFetch } from '$lib/utils/api';
import { toast } from 'svelte-sonner';
import { extractErrorMessage, sanitizeText } from '$lib/utils/toast-safe';
@@ -161,12 +160,12 @@
function findOldestBooking(): { id: string; isCurrent: boolean } | null {
if (overlappingBookings.length === 0) return null;
const currentCreatedAt = new SvelteDate(booking.created_at).getTime();
const currentCreatedAt = parseWallClockDate(booking.created_at).getTime();
let oldestId = booking.id;
let oldestTime = currentCreatedAt;
for (const ob of overlappingBookings) {
const obTime = new SvelteDate(ob.created_at).getTime();
const obTime = parseWallClockDate(ob.created_at).getTime();
if (obTime < oldestTime) {
oldestTime = obTime;
oldestId = ob.id;
@@ -450,7 +449,7 @@
{ob.status}
</span>
<div class="mt-1 text-xs text-gray-400">
{new SvelteDate(ob.created_at).toLocaleDateString('en-GB', {
{parseWallClockDate(ob.created_at).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
hour: 'numeric',
@@ -2,7 +2,7 @@
import { apiFetch } from '$lib/utils/api';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { SvelteDate, SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
import { SvelteSet, SvelteURLSearchParams } from 'svelte/reactivity';
import { CalendarDate, getLocalTimeZone, type DateValue } from '@internationalized/date';
import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
import { formatUserName } from '$lib/utils/nameDisplay';
@@ -217,7 +217,7 @@
// Date Boundaries
const today = getLondonTodayCalendarDate();
const minDate = today;
const maxDate = new SvelteDate(today.year, today.month - 1, today.day);
const maxDate = new Date(today.year, today.month - 1, today.day);
maxDate.setMonth(today.month - 1 + 6);
const maxCalendarDate = new CalendarDate(
maxDate.getFullYear(),
@@ -397,8 +397,8 @@
!bookingCreateAutoSelectDone
) {
bookingCreateAutoSelectDone = true;
const now = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new SvelteDate(
const now = new Date(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new Date(
maxCalendarDate.year,
maxCalendarDate.month - 1,
maxCalendarDate.day
@@ -408,7 +408,7 @@
);
const daysToCheck = Math.min(daysDifference, 180);
for (let i = 0; i <= daysToCheck; i++) {
const checkDate = new SvelteDate(now);
const checkDate = new Date(now);
checkDate.setDate(now.getDate() + i);
const dateStr = checkDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const calDate = new CalendarDate(
@@ -757,7 +757,7 @@
return;
}
const now = new SvelteDate();
const now = new Date();
const diff = reservationExpiresAt.getTime() - now.getTime();
if (diff <= 0) {
@@ -3,7 +3,7 @@
import { apiFetch } from '$lib/utils/api';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { SvelteDate } from 'svelte/reactivity';
import { parseWallClockDate } from '$lib/utils/timeSlots';
import * as Modal from '$lib/components/ui/dialog';
import { Button } from '$lib/components/ui/button';
import { Checkbox } from '$lib/components/ui/checkbox';
@@ -48,7 +48,7 @@
const balanceDue = $derived(selectedBooking ? computeBalanceDue(selectedBooking) : 0);
const hoursUntilAppt = $derived(
selectedBooking
? (new SvelteDate(selectedBooking.start_time).getTime() - new SvelteDate().getTime()) /
? (parseWallClockDate(selectedBooking.start_time).getTime() - new Date().getTime()) /
(1000 * 60 * 60)
: Infinity
);
@@ -1,6 +1,7 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
import { SvelteURLSearchParams } from 'svelte/reactivity';
import { parseWallClockDate } from '$lib/utils/timeSlots';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
@@ -117,10 +118,10 @@
// Format booking date/time
function formatBookingDateTime(startTime: string): string {
const date = new SvelteDate(startTime);
const now = new SvelteDate();
const today = new SvelteDate(now.getFullYear(), now.getMonth(), now.getDate());
const bookingDate = new SvelteDate(date.getFullYear(), date.getMonth(), date.getDate());
const date = parseWallClockDate(startTime);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const bookingDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
const daysDiff = Math.floor((bookingDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
@@ -143,7 +143,9 @@
if (value == null || value === '') return 'Required';
const num = Number(value);
if (isNaN(num)) return 'Must be a number';
if (num < 1) return 'Must be at least 1';
// Legal floor: CMA guidance flags sub-12-month expiry windows as an
// unfair contract term under the Consumer Rights Act 2015.
if (num < 12) return 'Must be at least 12 months';
return '';
}
@@ -385,6 +387,10 @@
<div>
<span class="text-xs text-muted-foreground">Expiry Period</span>
<p class="text-sm font-medium">{settings.gift_card_expiry_months} months</p>
<p class="text-xs text-muted-foreground">
Rolling from last use — each balance check, top-up, redemption or payment
resets the timer.
</p>
</div>
<div>
<span class="text-xs text-muted-foreground">Voucher Type</span>
@@ -609,11 +615,16 @@
<Input
id="gift_card_expiry_months"
type="number"
min="1"
min="12"
bind:value={form.gift_card_expiry_months}
oninput={() => validateField('gift_card_expiry_months')}
onblur={() => validateField('gift_card_expiry_months')}
/>
<p class="text-xs text-muted-foreground">
Rolling expiry: the timer resets on every use (balance check, top-up, redeem,
payment). 24 months is the recommended default; CMA guidance flags sub-12-month
windows as an unfair contract term.
</p>
{#if formErrors.gift_card_expiry_months}
<p class="text-xs text-destructive">{formErrors.gift_card_expiry_months}</p>
{/if}
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { apiFetch } from '$lib/utils/api';
import { range } from '$lib/utils/format';
import { toast } from 'svelte-sonner';
@@ -92,7 +91,7 @@
if (form.discount_percent <= 0 || form.discount_percent > 100) return false;
if (form.campaign_type === 'time_based') {
if (!form.start_date || !form.end_date) return false;
if (new SvelteDate(form.end_date) < new SvelteDate(form.start_date)) return false;
if (new Date(form.end_date) < new Date(form.start_date)) return false;
}
if (form.campaign_type === 'milestone') {
if (form.milestone_value <= 0) return false;
@@ -1,6 +1,5 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { formatDuration } from '$lib/utils/format';
@@ -280,10 +279,10 @@
const maxAvailableDuration = $derived.by(() => {
if (!booking?.start_time || !nextAppointmentStart) return null;
const bookingStart = new SvelteDate(booking.start_time).getTime();
const bookingStart = parseWallClockDate(booking.start_time).getTime();
const currentDurationMs = totalDuration * 60 * 1000;
const bookingEnd = bookingStart + currentDurationMs;
const nextStart = new SvelteDate(nextAppointmentStart).getTime();
const nextStart = parseWallClockDate(nextAppointmentStart).getTime();
const availableMs = nextStart - bookingEnd;
return Math.max(0, Math.floor(availableMs / (60 * 1000)));
@@ -600,7 +599,7 @@
{/if}
</div>
<div class="mt-1 text-xs text-gray-400">
{new SvelteDate(payment.created_at).toLocaleDateString('en-GB', {
{parseWallClockDate(payment.created_at).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric'
@@ -13,7 +13,8 @@
import { isSquareConfigured } from '$lib/square/square';
import { range } from '$lib/utils/format';
import { formatUserName } from '$lib/utils/nameDisplay';
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
import { parseWallClockDate } from '$lib/utils/timeSlots';
import { SvelteURLSearchParams } from 'svelte/reactivity';
interface GiftCard {
id: string;
@@ -726,7 +727,7 @@
}
function formatDate(dateStr: string): string {
return new SvelteDate(dateStr).toLocaleDateString('en-GB', {
return parseWallClockDate(dateStr).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric',
@@ -736,14 +737,14 @@
function getExpiryDate(lastUsedAt?: string): Date | null {
if (!lastUsedAt) return null;
const date = new SvelteDate(lastUsedAt);
const date = parseWallClockDate(lastUsedAt);
date.setMonth(date.getMonth() + 24);
return date;
}
function isExpired(lastUsedAt?: string): boolean {
const expiry = getExpiryDate(lastUsedAt);
return expiry !== null && expiry < new SvelteDate();
return expiry !== null && expiry < new Date();
}
// =============== Sorting ===============
@@ -802,7 +803,7 @@
return (a.amount_remaining - b.amount_remaining) * mul;
case 'created':
return (
(new SvelteDate(a.created_at).getTime() - new SvelteDate(b.created_at).getTime()) * mul
(parseWallClockDate(a.created_at).getTime() - parseWallClockDate(b.created_at).getTime()) * mul
);
case 'status': {
const aVal = a.redeemed_by ? 2 : a.amount_remaining === 0 ? 1 : 0;
@@ -833,7 +834,7 @@
return (a.balance - b.balance) * mul;
case 'updated':
return (
(new SvelteDate(a.updated_at).getTime() - new SvelteDate(b.updated_at).getTime()) * mul
(parseWallClockDate(a.updated_at).getTime() - parseWallClockDate(b.updated_at).getTime()) * mul
);
default:
return 0;
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage, sanitizeText } from '$lib/utils/toast-safe';
import { apiFetch } from '$lib/utils/api';
@@ -178,9 +177,9 @@
}
function addWeeksToException(fromISO: string, toISO: string, dest: string[]) {
const from = new SvelteDate(fromISO + 'T00:00:00Z');
const to = new SvelteDate(toISO + 'T00:00:00Z');
const first = new SvelteDate(from);
const from = new Date(fromISO + 'T00:00:00Z');
const to = new Date(toISO + 'T00:00:00Z');
const first = new Date(from);
const day = first.getDay();
const daysToMonday = day === 0 ? -6 : 1 - day;
@@ -188,8 +187,8 @@
first.setDate(first.getDate() + daysToMonday);
// Add all Mondays in the range
for (let d = new SvelteDate(first); d <= to; d.setDate(d.getDate() + 7)) {
dest.push(isoDateOf(new SvelteDate(d)));
for (let d = new Date(first); d <= to; d.setDate(d.getDate() + 7)) {
dest.push(isoDateOf(new Date(d)));
}
}
@@ -487,7 +486,7 @@
{g.weekStarts
?.slice(0, 3)
.map((w) =>
new SvelteDate(w).toLocaleDateString('en-GB', {
new Date(w).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short'
})
@@ -869,7 +868,7 @@
<div class="grid grid-cols-2 gap-2 md:grid-cols-3">
{#each viewingException.weekStarts as week (week)}
<div class="text-sm">
Week of {new SvelteDate(week).toLocaleDateString('en-GB', {
Week of {new Date(week).toLocaleDateString('en-GB', {
day: 'numeric',
month: 'short',
year: 'numeric'
@@ -1,5 +1,5 @@
<script lang="ts">
import { SvelteDate, SvelteSet } from 'svelte/reactivity';
import { SvelteSet } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { apiFetch } from '$lib/utils/api';
@@ -58,7 +58,7 @@
const today = getLondonTodayCalendarDate();
const minDate = today;
const maxDate = new SvelteDate(today.year, today.month - 1, today.day);
const maxDate = new Date(today.year, today.month - 1, today.day);
maxDate.setMonth(today.month - 1 + 6);
const maxCalendarDate = new CalendarDate(
maxDate.getFullYear(),
@@ -67,7 +67,7 @@
);
const hoursUntilAppointment = $derived(
(new SvelteDate(booking.start_time).getTime() - new SvelteDate().getTime()) / (1000 * 60 * 60)
(parseWallClockDate(booking.start_time).getTime() - new Date().getTime()) / (1000 * 60 * 60)
);
const hasPayments = $derived((booking.amount_paid ?? 0) > 0);
const showNoticeWarning = $derived(
@@ -380,8 +380,8 @@
!rescheduleAutoSelectDone
) {
rescheduleAutoSelectDone = true;
const now = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new SvelteDate(
const now = new Date(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new Date(
maxCalendarDate.year,
maxCalendarDate.month - 1,
maxCalendarDate.day
@@ -391,7 +391,7 @@
);
const daysToCheck = Math.min(daysDifference, 180);
for (let i = 1; i <= daysToCheck; i++) {
const checkDate = new SvelteDate(now);
const checkDate = new Date(now);
checkDate.setDate(now.getDate() + i);
const dateStr = checkDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const calDate = new CalendarDate(
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { apiFetch } from '$lib/utils/api';
import { CalendarDate } from '@internationalized/date';
import { toast } from 'svelte-sonner';
@@ -123,7 +122,7 @@
function getWorkingHoursForDate(dateStr: string): WorkingHourRow | null {
if (!dateStr || defaultHours.length === 0) return null;
const d = new SvelteDate(dateStr + 'T00:00:00Z');
const d = new Date(dateStr + 'T00:00:00Z');
const jsDay = d.getDay();
const weekday = jsDay === 0 ? 6 : jsDay - 1;
return defaultHours.find((h) => h.weekday === weekday) ?? null;
@@ -186,8 +185,8 @@
});
function formatRelativeTime(iso: string): string {
const d = new SvelteDate(iso);
const now = new SvelteDate();
const d = parseWallClockDate(iso);
const now = new Date();
const diffMs = d.getTime() - now.getTime();
if (diffMs < 0) {
const absMin = Math.floor(Math.abs(diffMs) / 60000);
@@ -205,7 +204,7 @@
const sortedBlockers = $derived.by(() => {
return [...blockers].sort(
(a, b) => new SvelteDate(a.start_time).getTime() - new SvelteDate(b.start_time).getTime()
(a, b) => parseWallClockDate(a.start_time).getTime() - parseWallClockDate(b.start_time).getTime()
);
});
@@ -297,8 +296,8 @@
return;
}
const blockerStart = new SvelteDate(startIso);
const blockerEnd = new SvelteDate(endIso);
const blockerStart = new Date(startIso);
const blockerEnd = new Date(endIso);
if (blockerEnd.getTime() <= blockerStart.getTime()) {
overlappingBookings = [];
hasOverlap = false;
@@ -322,8 +321,8 @@
const allBookings: OverlappingBooking[] = data.bookings || [];
// Client-side filter: only bookings that overlap with the proposed blocker timespan
const filtered = allBookings.filter((b) => {
const bStart = new SvelteDate(b.start_time);
const bEnd = new SvelteDate(bStart.getTime() + b.duration_minutes * 60000);
const bStart = parseWallClockDate(b.start_time);
const bEnd = new Date(bStart.getTime() + b.duration_minutes * 60000);
return bStart < blockerEnd && bEnd > blockerStart;
});
overlappingBookings = filtered;
@@ -774,7 +773,7 @@
<p class="text-sm text-gray-400">Select a date first</p>
{:else if !selectedWorkingHours.isOpen}
<p class="text-sm text-red-500">
Closed on {new SvelteDate(newStartDate + 'T00:00:00Z').toLocaleDateString('en-GB', {
Closed on {new Date(newStartDate + 'T00:00:00Z').toLocaleDateString('en-GB', {
weekday: 'long'
})}
</p>
@@ -1,5 +1,5 @@
<script lang="ts">
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
import { SvelteURLSearchParams } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { apiFetch } from '$lib/utils/api';
@@ -325,13 +325,13 @@
<div class="font-medium">
{#if selectedUser.dateOfBirth}
{(() => {
const dob = new SvelteDate(selectedUser.dateOfBirth);
const dob = new Date(selectedUser.dateOfBirth);
const dateStr = dob.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
const today = new SvelteDate();
const today = new Date();
let age = today.getFullYear() - dob.getFullYear();
const m = today.getMonth() - dob.getMonth();
if (m < 0 || (m === 0 && today.getDate() < dob.getDate())) age--;
@@ -346,7 +346,7 @@
<div class="text-xs text-gray-500">First Visit</div>
<div class="font-medium">
{#if customerRelationship?.firstVisitDate}
{new SvelteDate(customerRelationship.firstVisitDate).toLocaleDateString('en-US', {
{new Date(customerRelationship.firstVisitDate).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
@@ -360,7 +360,7 @@
<div class="text-xs text-gray-500">Last Visit</div>
<div class="font-medium">
{#if customerRelationship?.lastVisitDate}
{new SvelteDate(customerRelationship.lastVisitDate).toLocaleDateString('en-US', {
{new Date(customerRelationship.lastVisitDate).toLocaleDateString('en-US', {
weekday: 'short',
day: 'numeric',
month: 'short',
@@ -3,7 +3,6 @@
import WalkInCreateModal from '$lib/components/admin/WalkInCreateModal.svelte';
import { CalendarDate } from '@internationalized/date';
import { apiFetch } from '$lib/utils/api';
import { SvelteDate } from 'svelte/reactivity';
import { onMount, onDestroy } from 'svelte';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
@@ -28,7 +27,7 @@
} | null>(null);
let loading = $state(true);
let noSlotsToday = $state(false);
let currentTime = new SvelteDate();
let currentTime = new Date();
let _reservationId = $state<string | null>(null);
let reservationExpiresAt = $state<Date | null>(null);
@@ -58,7 +57,7 @@
fetchShortestService();
const interval = setInterval(() => {
currentTime = new SvelteDate();
currentTime = new Date();
}, 60000);
return () => clearInterval(interval);
@@ -176,7 +175,7 @@
noSlotsToday = false;
try {
const now = new SvelteDate();
const now = new Date();
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const [y, m, d] = londonDateStr.split('-').map(Number);
const today = new CalendarDate(y, m, d);
@@ -275,7 +274,7 @@
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const [y, m, d] = londonDateStr.split('-').map(Number);
const [hours, minutes] = startTime.split(':').map(Number);
const start = new SvelteDate(y, m - 1, d, hours, minutes, 0, 0);
const start = new Date(y, m - 1, d, hours, minutes, 0, 0);
const startTimeISO = formatLocalDateTime(start);
const response = await apiFetch('/api/admin/bookings/reserve', {
@@ -329,7 +328,7 @@
return;
}
const now = new SvelteDate();
const now = new Date();
const diff = reservationExpiresAt.getTime() - now.getTime();
if (diff <= 0) {
@@ -369,7 +368,7 @@
const liveRemaining = getLiveRemainingMinutes() ?? 0;
if (liveRemaining > RESERVATION_TTL) {
// Available now with >15min remaining — reserve from now to slot end
const now = new SvelteDate();
const now = new Date();
const currentMin = now.getHours() * 60 + now.getMinutes();
reserveTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
reserveDuration = slotInfo.slotEndMinutes! - currentMin;
@@ -3,7 +3,7 @@
import { apiFetch } from '$lib/utils/api';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { SvelteDate, SvelteURLSearchParams } from 'svelte/reactivity';
import { SvelteURLSearchParams } from 'svelte/reactivity';
import { isValidUKPhone, toE164UK } from '$lib/utils/phone';
import { formatUserName } from '$lib/utils/nameDisplay';
import { formatLocalDateTime } from '$lib/utils/timeSlots';
@@ -243,7 +243,7 @@
return;
}
const now = new SvelteDate();
const now = new Date();
const diff = reservationExpiresAt.getTime() - now.getTime();
if (diff <= 0) {
@@ -479,7 +479,7 @@
const [hours, minutes] = availableStartTime.split(':').map(Number);
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const [y, m, d] = londonDateStr.split('-').map(Number);
start = new SvelteDate(y, m - 1, d, hours, minutes, 0, 0);
start = new Date(y, m - 1, d, hours, minutes, 0, 0);
} else {
// Fallback: Calculate immediate start time (rounded to next 15 min)
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
@@ -491,8 +491,8 @@
});
const [y, m, d] = londonDateStr.split('-').map(Number);
const [h, min] = londonTimeStr.split(':').map(Number);
const now = new SvelteDate(y, m - 1, d, h, min, 0, 0);
start = new SvelteDate(now);
const now = new Date(y, m - 1, d, h, min, 0, 0);
start = new Date(now);
const minutes = start.getMinutes();
const remainder = 15 - (minutes % 15);
if (remainder !== 15 && remainder !== 0) {
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { browser } from '$app/environment';
@@ -229,7 +228,7 @@
}
function getDefaultEffectiveDate(): string {
const d = new SvelteDate();
const d = new Date();
d.setDate(d.getDate() + 1);
return d.toISOString().slice(0, 10);
}
@@ -27,7 +27,6 @@
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { onDestroy } from 'svelte';
import { SvelteDate } from 'svelte/reactivity';
// Components
import BookingActions from '$lib/components/booking/BookingActions.svelte';
@@ -41,7 +40,7 @@
import { POLICY } from '$lib/constants/policy';
import UserPaymentModal from '$lib/components/payments/UserPaymentModal.svelte';
import { extractBookedSlots, getLunchProtectionForSlots } from '$lib/lunchProtection';
import { formatLocalDateTime, getLondonTodayCalendarDate } from '$lib/utils/timeSlots';
import { formatLocalDateTime, getLondonTodayCalendarDate, parseWallClockDate } from '$lib/utils/timeSlots';
import { generateUUID } from '$lib/utils/uuid';
import type {
@@ -575,7 +574,7 @@
return;
}
const now = new SvelteDate();
const now = new Date();
const diff = reservationExpiresAt.getTime() - now.getTime();
if (diff <= 0) {
@@ -685,7 +684,7 @@
// Initialize date boundaries
const today = getLondonTodayCalendarDate();
const minDate = today;
const maxDate = new SvelteDate(today.year, today.month - 1, today.day);
const maxDate = new Date(today.year, today.month - 1, today.day);
maxDate.setMonth(today.month - 1 + 6);
const maxCalendarDate = new CalendarDate(
maxDate.getFullYear(),
@@ -756,8 +755,8 @@
) {
bookingFlowAutoSelectDone = true;
const currentDate = new SvelteDate(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new SvelteDate(
const currentDate = new Date(getLondonTodayCalendarDate().toString() + 'T00:00:00');
const maxDateJs = new Date(
maxCalendarDate.year,
maxCalendarDate.month - 1,
maxCalendarDate.day
@@ -769,7 +768,7 @@
const daysToCheck = Math.min(daysDifference, 180);
for (let i = 1; i <= daysToCheck; i++) {
const nextDate = new SvelteDate(currentDate);
const nextDate = new Date(currentDate);
nextDate.setDate(currentDate.getDate() + i);
const dateStr = nextDate.toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
@@ -992,7 +991,7 @@
// =============== Time Slot Generation ===============
function calculateEndTime(startTime: string, durationMinutes: number): string {
const [hours, minutes] = startTime.split(':').map(Number);
const date = new SvelteDate();
const date = new Date();
date.setHours(hours, minutes, 0, 0);
date.setMinutes(date.getMinutes() + durationMinutes);
const endHours = date.getHours().toString().padStart(2, '0');
@@ -1253,7 +1252,7 @@
15,
false
);
const now = new SvelteDate();
const now = new Date();
const validSlots = availableSlots.filter((t) => {
if (lunchProtection.get(t)?.isBlocked) return false;
if (userDepositsRequired > 0) {
@@ -1410,7 +1409,7 @@
}
function getDayWithOrdinal(date: CalendarDate): string {
const monthName = new SvelteDate(date.year, date.month - 1, date.day).toLocaleDateString(
const monthName = new Date(date.year, date.month - 1, date.day).toLocaleDateString(
'en-GB',
{
month: 'long'
@@ -2131,7 +2130,7 @@
{#if currentStep === finalStep}
{#if confirmedBooking}
{@const isRequested = confirmedBooking.notes && confirmedBooking.notes.length > 0}
{@const bookingDate = new SvelteDate(confirmedBooking.start_time)}
{@const bookingDate = parseWallClockDate(confirmedBooking.start_time)}
{@const dateStr = bookingDate.toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
@@ -2446,8 +2445,8 @@
deposit_paid: bk.deposit_paid,
payments: bk.payments,
duration_minutes: bk.duration_minutes,
created_at: new SvelteDate().toISOString(),
updated_at: new SvelteDate().toISOString()
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}}
onClose={() => (showPayEarlyModal = false)}
onComplete={() => {
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { onMount, onDestroy } from 'svelte';
import { formatTime } from '$lib/utils/timeSlots';
@@ -81,7 +80,7 @@
const today = getLondonDate();
const dayOfWeek = today.getDay(); // 0=Sun
const offset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
const monday = new SvelteDate(today);
const monday = new Date(today);
monday.setDate(today.getDate() + offset);
return fmtDate(monday);
}
@@ -2,9 +2,9 @@
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
import { extractErrorMessage } from '$lib/utils/toast-safe';
import { SvelteDate } from 'svelte/reactivity';
import { authStore } from '$lib/stores/auth.svelte';
import { apiFetch } from '$lib/utils/api';
import { parseWallClockDate } from '$lib/utils/timeSlots';
import CardSelection from '$lib/components/payments/CardSelection.svelte';
import { savedCardsStore, type SavedCard } from '$lib/stores/savedCards.svelte';
import { Button } from '$lib/components/ui/button';
@@ -110,7 +110,7 @@
});
function formatDate(dateStr: string): string {
const date = new SvelteDate(dateStr);
const date = parseWallClockDate(dateStr);
return date.toLocaleDateString('en-GB', {
weekday: 'long',
day: 'numeric',
@@ -127,7 +127,7 @@
services: BookingService[],
fallbackDuration: number
): string {
const start = new SvelteDate(startStr);
const start = parseWallClockDate(startStr);
const totalMinutes =
services?.reduce(
(sum, s) => sum + (s.override_duration_minutes ?? s.duration_minutes ?? 0),
@@ -135,7 +135,7 @@
) ??
fallbackDuration ??
0;
const end = new SvelteDate(start.getTime() + totalMinutes * 60000);
const end = new Date(start.getTime() + totalMinutes * 60000);
const formatOpt: Intl.DateTimeFormatOptions = {
hour: 'numeric',
@@ -1,10 +1,10 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { ensureBusinessInfo, getBusinessInfo } from '$lib/stores/businessInfo.svelte';
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import { formatDuration } from '$lib/utils/format';
import { formatUserName } from '$lib/utils/nameDisplay';
import { parseWallClockDate } from '$lib/utils/timeSlots';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
import { Badge } from '$lib/components/ui/badge';
@@ -106,19 +106,19 @@
const minutesUntilClosing = $derived.by(() => {
if (!closingTime) return 0;
const [ch, cm] = closingTime.split(':').map(Number);
const now = new SvelteDate();
const closing = new SvelteDate(now.getFullYear(), now.getMonth(), now.getDate(), ch, cm);
const now = new Date();
const closing = new Date(now.getFullYear(), now.getMonth(), now.getDate(), ch, cm);
return Math.max(0, Math.floor((closing.getTime() - now.getTime()) / 60000));
});
function calculateTimes() {
const now = new SvelteDate();
const now = new Date();
if (currentAppointment) {
isInProgress = currentAppointment.status === 'in_progress';
const startTime = new SvelteDate(currentAppointment.start_time);
const startTime = parseWallClockDate(currentAppointment.start_time);
const endTime = new SvelteDate(
const endTime = new Date(
startTime.getTime() + currentAppointment.duration_minutes * 60 * 1000
);
@@ -137,15 +137,15 @@
let rawFreeMinutes = 0;
if (nextAppointment) {
const nextStart = new SvelteDate(nextAppointment.start_time);
const nextStart = parseWallClockDate(nextAppointment.start_time);
const gapMs = nextStart.getTime() - endTime.getTime();
rawFreeMinutes = Math.max(0, Math.floor(gapMs / 60000));
}
if (closingTime) {
const [ch, cm] = closingTime.split(':').map(Number);
const today = new SvelteDate();
const closing = new SvelteDate(
const today = new Date();
const closing = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate(),
@@ -325,14 +325,14 @@
</Card.Title>
{#if activeAppointment}
<Card.Description class="text-base">
{new SvelteDate(activeAppointment.start_time).toLocaleTimeString('en-US', {
{parseWallClockDate(activeAppointment.start_time).toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
})}
-
{new SvelteDate(
new SvelteDate(activeAppointment.start_time).getTime() +
{new Date(
parseWallClockDate(activeAppointment.start_time).getTime() +
activeAppointment.duration_minutes * 60 * 1000
).toLocaleTimeString('en-US', {
hour: 'numeric',
@@ -439,13 +439,13 @@
{#if summary.summary_scope === 'week'}
<div class="text-xs text-gray-500">
Summary: {new SvelteDate(summary.summary_start_date).toLocaleDateString('en-US', {
Summary: {parseWallClockDate(summary.summary_start_date).toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
})}
&ndash;
{new SvelteDate(summary.summary_end_date).toLocaleDateString('en-US', {
{parseWallClockDate(summary.summary_end_date).toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
@@ -552,7 +552,7 @@
<div>
<h4 class="mb-2 text-sm font-semibold text-gray-700">
New bookings made since{summary.summary_scope === 'week'
? ` opening ${new SvelteDate(summary.summary_start_date).toLocaleDateString('en-US', { weekday: 'long' })}`
? ` opening ${parseWallClockDate(summary.summary_start_date).toLocaleDateString('en-US', { weekday: 'long' })}`
: ' closing yesterday'}
</h4>
<div class="flex flex-wrap gap-2">
@@ -573,13 +573,13 @@
<div>
<h4 class="text-sm font-semibold text-gray-700">This week so far</h4>
<div class="text-xs text-gray-500">
{new SvelteDate(weekSummary.summary_start_date).toLocaleDateString('en-US', {
{parseWallClockDate(weekSummary.summary_start_date).toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
})}
&ndash;
{new SvelteDate(weekSummary.summary_end_date).toLocaleDateString('en-US', {
{parseWallClockDate(weekSummary.summary_end_date).toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
@@ -1,6 +1,5 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { SvelteDate } from 'svelte/reactivity';
import { toast } from 'svelte-sonner';
import * as Card from '$lib/components/ui/card';
import { Button } from '$lib/components/ui/button';
@@ -10,6 +9,7 @@
import EditRequestModal from '$lib/components/admin/EditRequestModal.svelte';
import { range } from '$lib/utils/format';
import { formatUserName } from '$lib/utils/nameDisplay';
import { parseWallClockDate } from '$lib/utils/timeSlots';
interface Props {
openBookingModal?: (bookingId: string) => void;
@@ -105,7 +105,7 @@
// Helper function to format date nicely
function formatDateTime(dateTimeString: string): string {
const date = new SvelteDate(dateTimeString);
const date = parseWallClockDate(dateTimeString);
const dateStr = date.toLocaleDateString('en-US', {
weekday: 'long',
day: 'numeric',
@@ -120,8 +120,8 @@
}
function formatRelativeTime(iso: string): string {
const d = new SvelteDate(iso);
const now = new SvelteDate();
const d = parseWallClockDate(iso);
const now = new Date();
const diffMs = now.getTime() - d.getTime();
const diffMin = Math.floor(diffMs / 60000);
@@ -138,7 +138,7 @@
const servicesChanged = areEditServicesChanged(er);
if (timeChanged) {
const d = new SvelteDate(er.proposed.start_time!);
const d = parseWallClockDate(er.proposed.start_time!);
const dateStr = d.toLocaleDateString('en-US', {
weekday: 'long',
day: 'numeric',
@@ -187,7 +187,8 @@
const data = await response.json();
const newApprovals = (data.approvals || []).sort(
(a: PendingApproval, b: PendingApproval) =>
new SvelteDate(a.created_at).getTime() - new SvelteDate(b.created_at).getTime()
parseWallClockDate(a.created_at).getTime() -
parseWallClockDate(b.created_at).getTime()
);
const newJson = JSON.stringify(newApprovals);
if (newJson !== prevApprovalsJson) {
@@ -218,7 +219,8 @@
const data = await response.json();
const newEditRequests = (data.edit_requests || []).sort(
(a: EditRequest, b: EditRequest) =>
new SvelteDate(a.requested_at).getTime() - new SvelteDate(b.requested_at).getTime()
parseWallClockDate(a.requested_at).getTime() -
parseWallClockDate(b.requested_at).getTime()
);
const newJson = JSON.stringify(newEditRequests);
if (newJson !== prevEditRequestsJson) {
@@ -1,6 +1,5 @@
<script lang="ts">
import { apiFetch } from '$lib/utils/api';
import { SvelteDate } from 'svelte/reactivity';
import { CalendarDate } from '@internationalized/date';
import { toast } from 'svelte-sonner';
import { extractErrorMessage, sanitizeText } from '$lib/utils/toast-safe';
@@ -22,7 +21,7 @@
timeToMinutes
} from '$lib/lunchProtection';
import { formatDuration, formatDateISO } from '$lib/utils/format';
import { formatLocalDateTime } from '$lib/utils/timeSlots';
import { formatLocalDateTime, parseWallClockDate } from '$lib/utils/timeSlots';
interface Props {
openBookingModal: (bookingId: string) => void;
@@ -83,8 +82,8 @@
};
function isPastAppointment(startTime: string, durationMinutes: number): boolean {
const start = new SvelteDate(startTime);
const end = new SvelteDate(start.getTime() + durationMinutes * 60_000);
const start = parseWallClockDate(startTime);
const end = new Date(start.getTime() + durationMinutes * 60_000);
return end.getTime() < Date.now();
}
@@ -118,7 +117,7 @@
const weekStartStr = $derived.by(() => {
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const d = new SvelteDate(londonDateStr + 'T00:00:00Z');
const d = new Date(londonDateStr + 'T00:00:00Z');
const day = d.getDay();
const diff = day === 0 ? 6 : day - 1;
d.setDate(d.getDate() - diff);
@@ -126,7 +125,7 @@
});
const weekEndStr = $derived.by(() => {
const d = new SvelteDate(weekStartStr + 'T00:00:00Z');
const d = new Date(weekStartStr + 'T00:00:00Z');
d.setDate(d.getDate() + 6);
return formatDateISO(d);
});
@@ -286,7 +285,7 @@
const items: TimelineItem[] = [];
for (const apt of appointments) {
const start = new SvelteDate(apt.start_time);
const start = parseWallClockDate(apt.start_time);
const startM = start.getHours() * 60 + start.getMinutes();
items.push({
id: `apt-${apt.id}`,
@@ -298,7 +297,7 @@
}
for (const b of blockers) {
const start = new SvelteDate(b.start_time);
const start = parseWallClockDate(b.start_time);
const startM = start.getHours() * 60 + start.getMinutes();
items.push({
id: `blk-${b.id}`,
@@ -440,8 +439,8 @@
hasOverlap = false;
return;
}
const blockerStart = new SvelteDate(startIso);
const blockerEnd = new SvelteDate(endIso);
const blockerStart = new Date(startIso);
const blockerEnd = new Date(endIso);
if (blockerEnd.getTime() <= blockerStart.getTime()) {
overlappingBookings = [];
hasOverlap = false;
@@ -459,8 +458,8 @@
const data = await response.json();
const allBookings: OverlappingBooking[] = data.bookings || [];
const filtered = allBookings.filter((b) => {
const bStart = new SvelteDate(b.start_time);
const bEnd = new SvelteDate(bStart.getTime() + b.duration_minutes * 60000);
const bStart = parseWallClockDate(b.start_time);
const bEnd = new Date(bStart.getTime() + b.duration_minutes * 60000);
return bStart < blockerEnd && bEnd > blockerStart;
});
overlappingBookings = filtered;
@@ -627,7 +626,7 @@
}
function formatTime(dateString: string): string {
const date = new SvelteDate(dateString);
const date = parseWallClockDate(dateString);
return date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
@@ -1011,7 +1010,7 @@
)}
</div>
<div class="text-xs text-gray-500">
{new SvelteDate(booking.start_time).toLocaleTimeString('en-GB', {
{parseWallClockDate(booking.start_time).toLocaleTimeString('en-GB', {
hour: 'numeric',
minute: '2-digit',
hour12: true
@@ -1,5 +1,4 @@
<script lang="ts">
import { SvelteDate } from 'svelte/reactivity';
import { apiFetch } from '$lib/utils/api';
import { Skeleton } from '$lib/components/ui/skeleton';
import * as Card from '$lib/components/ui/card';
@@ -51,11 +50,11 @@
async function findLastWorkingDayClose(): Promise<{ cutoff: string; spansClosed: boolean }> {
const londonDateStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Europe/London' });
const now = new SvelteDate(londonDateStr + 'T00:00:00Z');
const now = new Date(londonDateStr + 'T00:00:00Z');
let spansClosed = false;
for (let i = 1; i <= 14; i++) {
const d = new SvelteDate(now);
const d = new Date(now);
d.setDate(d.getDate() - i);
const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
@@ -72,7 +71,7 @@
if (day?.isOpen) {
const closeTime = day.endTime;
const [h, m] = closeTime.split(':').map(Number);
const closeDate = new SvelteDate(d);
const closeDate = new Date(d);
closeDate.setHours(h, m, 0, 0);
return { cutoff: formatLocalDateTime(closeDate), spansClosed };
} else {
@@ -81,7 +80,7 @@
}
}
const fallback = new SvelteDate(now);
const fallback = new Date(now);
fallback.setDate(fallback.getDate() - 1);
fallback.setHours(17, 0, 0, 0);
return { cutoff: formatLocalDateTime(fallback), spansClosed };
@@ -144,7 +143,7 @@
}
const { cutoff, spansClosed } = await findLastWorkingDayClose();
const nowISO = formatLocalDateTime(new SvelteDate());
const nowISO = formatLocalDateTime(new Date());
let bookingsMade = 0;
try {
const bmRes = await apiFetch(
+1 -2
View File
@@ -1,4 +1,3 @@
import { SvelteDate } from 'svelte/reactivity';
import { CalendarDate } from '@internationalized/date';
import {
extractBookedSlots,
@@ -96,7 +95,7 @@ export function timeToMinutes(time: string): number {
}
export function getDayWithOrdinal(date: CalendarDate): string {
const monthName = new SvelteDate(date.year, date.month - 1, date.day).toLocaleDateString(
const monthName = new Date(date.year, date.month - 1, date.day).toLocaleDateString(
'en-GB',
{ month: 'long' }
);