feat(frontend): add email check and gift card support to booking flow
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import CharCounter from '$lib/components/ui/CharCounter.svelte';
|
||||
import { PhoneInput } from '$lib/components/ui/phone-input/index.js';
|
||||
|
||||
// Booking Components
|
||||
import BookingActions from '$lib/components/booking/BookingActions.svelte';
|
||||
@@ -155,7 +156,9 @@
|
||||
);
|
||||
|
||||
const canProceedStep1 = $derived(
|
||||
userType === 'member' ? !!selectedUserId : !!(guestName.trim() && guestPhone.trim())
|
||||
userType === 'member'
|
||||
? !!selectedUserId
|
||||
: !!(guestName.trim() && guestPhone.trim() && isValidUKPhone(guestPhone))
|
||||
);
|
||||
const canProceedStep2 = $derived(selectedServices.length > 0);
|
||||
const canProceedStep3 = $derived(true); // Overrides are optional
|
||||
@@ -939,26 +942,12 @@
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="guest-phone">Phone Number *</Label>
|
||||
<input
|
||||
<PhoneInput
|
||||
id="guest-phone"
|
||||
type="tel"
|
||||
class="flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none {guestPhoneError
|
||||
? 'border-red-500'
|
||||
: 'border-input'}"
|
||||
bind:value={guestPhone}
|
||||
bind:error={guestPhoneError}
|
||||
placeholder="07700 900000"
|
||||
value={guestPhone}
|
||||
oninput={(e) => {
|
||||
guestPhone = e.currentTarget.value;
|
||||
}}
|
||||
onblur={() => {
|
||||
if (guestPhone && !isValidUKPhone(guestPhone))
|
||||
guestPhoneError = 'Invalid UK phone number';
|
||||
else guestPhoneError = '';
|
||||
}}
|
||||
/>
|
||||
{#if guestPhoneError}
|
||||
<p class="mt-1 text-xs text-red-600">{guestPhoneError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="rounded-lg bg-yellow-50 p-3 text-sm text-yellow-800">
|
||||
Booking as a guest creates a temporary record. Encourage them to sign up for
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
bookings = data.bookings as Booking[];
|
||||
totalBookings = data.total || 0;
|
||||
totalPages = data.total_pages || 1;
|
||||
totalPages = data.totalPages ?? 1;
|
||||
currentPage = data.page || 1;
|
||||
} else {
|
||||
const text = await response.text();
|
||||
|
||||
@@ -92,6 +92,7 @@
|
||||
let hasEligiblePatchTests = $state(false);
|
||||
let customerRelationship = $state<CustomerRelationship | null>(null);
|
||||
let loadingRelationship = $state(false);
|
||||
let giftCardBalance = $state<number | null>(null);
|
||||
|
||||
async function fetchUserDetails() {
|
||||
if (!userId) return;
|
||||
@@ -217,11 +218,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchGiftCardBalance() {
|
||||
if (!userId) return;
|
||||
try {
|
||||
const res = await fetch(`/api/admin/users/${userId}/giftcard-balance`, {
|
||||
headers: { Authorization: `Bearer ${authStore.currentToken}` }
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
giftCardBalance = data.balance;
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (open && userId) {
|
||||
fetchUserDetails();
|
||||
fetchUserBookings();
|
||||
fetchCustomerRelationship();
|
||||
fetchGiftCardBalance();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -519,6 +534,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if giftCardBalance !== null && giftCardBalance > 0}
|
||||
<div class="mt-3 border-t pt-3">
|
||||
<div class="text-xs text-gray-500">Gift Card Balance</div>
|
||||
<div class="text-2xl font-bold text-green-600">
|
||||
£{giftCardBalance.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if customerRelationship.topServices && customerRelationship.topServices.length > 0}
|
||||
<div class="mt-4">
|
||||
<div class="mb-2 text-xs font-semibold text-gray-600">Most Booked Services</div>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { Separator } from '$lib/components/ui/separator';
|
||||
import { Skeleton } from '$lib/components/ui/skeleton';
|
||||
import CharCounter from '$lib/components/ui/CharCounter.svelte';
|
||||
import { PhoneInput } from '$lib/components/ui/phone-input/index.js';
|
||||
|
||||
// Booking Components
|
||||
import BookingActions from '$lib/components/booking/BookingActions.svelte';
|
||||
@@ -110,7 +111,11 @@
|
||||
|
||||
const isOverDuration = $derived(getTotalDuration() > maxSlotDuration);
|
||||
|
||||
const canProceedStep1 = $derived(userType === 'member' ? !!selectedUserId : !!guestName.trim());
|
||||
const canProceedStep1 = $derived(
|
||||
userType === 'member'
|
||||
? !!selectedUserId
|
||||
: !!(guestName.trim() && guestPhone.trim() && isValidUKPhone(guestPhone))
|
||||
);
|
||||
const canProceedStep2 = $derived(selectedServices.length > 0 && !isOverDuration);
|
||||
|
||||
// =============== Effects ===============
|
||||
@@ -622,26 +627,13 @@
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="guest-phone">Phone Number</Label>
|
||||
<input
|
||||
<PhoneInput
|
||||
id="guest-phone"
|
||||
type="tel"
|
||||
class="flex h-10 w-full rounded-md border bg-background px-3 py-2 text-sm ring-offset-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-none {guestPhoneError
|
||||
? 'border-red-500'
|
||||
: 'border-input'}"
|
||||
bind:value={guestPhone}
|
||||
bind:error={guestPhoneError}
|
||||
placeholder="07700 900000"
|
||||
value={guestPhone}
|
||||
oninput={(e) => {
|
||||
guestPhone = e.currentTarget.value;
|
||||
}}
|
||||
onblur={() => {
|
||||
if (guestPhone && !isValidUKPhone(guestPhone))
|
||||
guestPhoneError = 'Invalid UK phone number';
|
||||
else guestPhoneError = '';
|
||||
}}
|
||||
required={false}
|
||||
/>
|
||||
{#if guestPhoneError}
|
||||
<p class="mt-1 text-xs text-red-600">{guestPhoneError}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="rounded-lg bg-yellow-50 p-3 text-sm text-yellow-800">
|
||||
Booking as a guest creates a temporary record. Encourage them to sign up for
|
||||
|
||||
Reference in New Issue
Block a user