chore: apply project-wide formatting and UI library updates

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-05-29 20:23:14 +01:00
co-authored by Sisyphus
parent ef650e013a
commit 6e901690fd
131 changed files with 2437 additions and 1571 deletions
@@ -74,7 +74,11 @@
// Step 4: Date & Time
let placeholder = $state<CalendarDate>(
new CalendarDate(new SvelteDate().getFullYear(), new SvelteDate().getMonth() + 1, new SvelteDate().getDate())
new CalendarDate(
new SvelteDate().getFullYear(),
new SvelteDate().getMonth() + 1,
new SvelteDate().getDate()
)
);
let selectedDate = $state<CalendarDate | undefined>(undefined);
let selectedTime = $state<string | null>(null);
@@ -129,7 +133,7 @@
// =============== Lunch Protection ===============
const lunchProtection = $derived(
(selectedDate && selectedServices.length > 0)
selectedDate && selectedServices.length > 0
? buildLunchProtection(selectedDate, workingHours, availableHours, getTotalDuration(), true)
: new Map()
);
@@ -197,7 +201,16 @@
// Auto-select first available date once data loads (timing-safe, data-driven)
let bookingCreateAutoSelectDone = $state(false);
$effect(() => {
if (open && currentStep === 4 && workingHours && availableHours && !selectedDate && selectedServices.length > 0 && !userNavigatedCalendar && !bookingCreateAutoSelectDone) {
if (
open &&
currentStep === 4 &&
workingHours &&
availableHours &&
!selectedDate &&
selectedServices.length > 0 &&
!userNavigatedCalendar &&
!bookingCreateAutoSelectDone
) {
bookingCreateAutoSelectDone = true;
const now = new SvelteDate();
const maxDateJs = new SvelteDate(
@@ -213,7 +226,11 @@
const checkDate = new SvelteDate(now);
checkDate.setDate(now.getDate() + i);
const dateStr = checkDate.toISOString().split('T')[0];
const calDate = new CalendarDate(checkDate.getFullYear(), checkDate.getMonth() + 1, checkDate.getDate());
const calDate = new CalendarDate(
checkDate.getFullYear(),
checkDate.getMonth() + 1,
checkDate.getDate()
);
if (workingHours[dateStr]?.isOpen && !isDateUnavailable(calDate)) {
selectedDate = calDate;
if (!userNavigatedCalendar) {
@@ -342,7 +359,9 @@
const whMap: Record<string, any> = {};
const ahMap: Record<string, any> = {};
whData.forEach((d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime }));
whData.forEach(
(d) => (whMap[d.date] = { isOpen: d.isOpen, startTime: d.startTime, endTime: d.endTime })
);
ahData.forEach((d) => (ahMap[d.date] = { isOpen: d.isOpen, slots: d.slots }));
if (gen !== hoursRangeGeneration) return; // Stale response, discard
@@ -435,7 +454,7 @@
} finally {
loadingWorkingHours = false;
loadingAvailableHours = false;
loadingMonthKeys = new Set([...loadingMonthKeys].filter(k => k !== monthKey));
loadingMonthKeys = new Set([...loadingMonthKeys].filter((k) => k !== monthKey));
}
}
@@ -575,7 +594,13 @@
const groupedTimeSlots = $derived(
currentStep === 4 && selectedServices.length > 0 && selectedDate
? generateGroupedTimeSlots(selectedDate, workingHours, availableHours, getTotalDuration(), lunchProtection)
? generateGroupedTimeSlots(
selectedDate,
workingHours,
availableHours,
getTotalDuration(),
lunchProtection
)
: []
);
@@ -599,8 +624,20 @@
if (duration <= 0) return true;
// Build lunch protection specifically for the date being checked
const dayProtection = buildLunchProtection(date as CalendarDate, workingHours, availableHours, duration, true);
const slots = generateAvailableTimeSlots(date as CalendarDate, workingHours, availableHours, duration, dayProtection);
const dayProtection = buildLunchProtection(
date as CalendarDate,
workingHours,
availableHours,
duration,
true
);
const slots = generateAvailableTimeSlots(
date as CalendarDate,
workingHours,
availableHours,
duration,
dayProtection
);
if (slots.length === 0) return true;
return false;
@@ -620,32 +657,32 @@
try {
let finalUserId = selectedUserId;
if (userType === 'guest') {
const phone = guestPhone.trim() || '+447700900000';
const createRes = await fetch('/api/users/guest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authStore.currentToken}`
},
body: JSON.stringify({
firstName: guestName.trim().split(' ')[0] || 'Guest',
lastName: guestName.trim().split(' ').slice(1).join(' ') || 'Customer',
phone: phone,
email: `callin-${Date.now()}@guest.invalid`
})
});
if (userType === 'guest') {
const phone = guestPhone.trim() || '+447700900000';
const createRes = await fetch('/api/users/guest', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authStore.currentToken}`
},
body: JSON.stringify({
firstName: guestName.trim().split(' ')[0] || 'Guest',
lastName: guestName.trim().split(' ').slice(1).join(' ') || 'Customer',
phone: phone,
email: `callin-${Date.now()}@guest.invalid`
})
});
if (!createRes.ok) {
toast.error('Failed to create guest user');
submitting = false;
return;
if (!createRes.ok) {
toast.error('Failed to create guest user');
submitting = false;
return;
}
const guestUser = await createRes.json();
finalUserId = guestUser.id;
}
const guestUser = await createRes.json();
finalUserId = guestUser.id;
}
if (!finalUserId) throw new Error('User ID required');
if (!selectedDate || !selectedTime) throw new Error('Date and time required');
@@ -734,7 +771,9 @@
</script>
<Modal.Root bind:open>
<Modal.Content class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl md:max-w-4xl">
<Modal.Content
class="max-h-[90vh] max-w-[calc(100%-2rem)] overflow-y-auto sm:max-w-2xl md:max-w-4xl"
>
<Modal.Header>
<Modal.Title>Create Admin Booking</Modal.Title>
<Modal.Description>
@@ -1096,17 +1135,30 @@
<Card.Content class="space-y-4 p-0">
<!-- Reservation Countdown Banner -->
{#if reservationId && reservationExpiresAt}
<div class="mx-6 mt-4 rounded-lg bg-green-50 border border-green-200 p-3">
<div class="mx-6 mt-4 rounded-lg border border-green-200 bg-green-50 p-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<svg class="h-5 w-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
<svg
class="h-5 w-5 text-green-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 13l4 4L19 7"
></path>
</svg>
<span class="text-sm font-medium text-green-800">
Slot reserved until {reservationExpiresAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
Slot reserved until {reservationExpiresAt.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
})}
</span>
</div>
<span class="text-sm font-mono font-semibold text-green-700">
<span class="font-mono text-sm font-semibold text-green-700">
({reservationCountdown} remaining)
</span>
</div>
@@ -1123,11 +1175,11 @@
selectedDate = newDate;
selectedTime = null;
}}
onPlaceholderChange={(newPlaceholder) => {
placeholder = newPlaceholder;
userNavigatedCalendar = true;
fetchHoursForMonth(newPlaceholder);
}}
onPlaceholderChange={(newPlaceholder) => {
placeholder = newPlaceholder;
userNavigatedCalendar = true;
fetchHoursForMonth(newPlaceholder);
}}
/>
</div>
@@ -1139,14 +1191,18 @@
<div class="border-t">
<div class="max-h-64 overflow-y-auto p-6">
{#if formattedSelectedDate}
<div class="mb-3 grid justify-center gap-2 text-sm font-medium">{formattedSelectedDate}</div>
<div class="mb-3 grid justify-center gap-2 text-sm font-medium">
{formattedSelectedDate}
</div>
{/if}
<TimeSlotList
slots={groupedTimeSlots}
{selectedTime}
duration={getTotalDuration()}
protection={lunchProtection}
onSelect={(time) => { selectedTime = time; }}
onSelect={(time) => {
selectedTime = time;
}}
/>
</div>
</div>
@@ -1177,7 +1233,11 @@
onclick={submitBooking}
class="bg-primary text-primary-foreground"
>
{isReserving ? 'Reserving Slot...' : submitting ? 'Creating Booking...' : 'Create Booking'}
{isReserving
? 'Reserving Slot...'
: submitting
? 'Creating Booking...'
: 'Create Booking'}
</Button>
</Card.Footer>
</Card.Root>