Create Admin Booking Book an appointment for a member or guest with flexible pricing and timing
{#if currentStep === 1} Select Customer Choose an existing member or create a guest booking
{#if userType === 'member'}
{ userQuery = e.currentTarget.value; }} onkeydown={(e) => { if (e.key === 'Enter') { e.preventDefault(); fetchUsers(); } }} />
{#if loadingUsers}
{#each range(3) as i (i)} {/each}
{:else if users.length === 0}
{userQuery ? 'No users found. Try a different search.' : 'Search for a user above to get started.'}
{:else}
    {#each users.slice(0, 4) as user (user.id)}
  • {/each}
{/if}
{:else}
(guestName = e.currentTarget.value)} />

Booking as a guest creates a temporary record. Encourage them to sign up for loyalty benefits.

{/if}
currentStep++} />
{/if} {#if currentStep === 2} Choose Services Select one or more treatments for this appointment {#if loadingServices}
{#each range(4) as i (i)} {/each}
{:else if services.length === 0}

No services available.

{:else} {/if}

Or book a custom service

{ if (e.key === 'Enter') fetchCustomServices(); }} class="flex-1" />
{#if loadingCustomServices}
{#each range(3) as i (i)} {/each}
{:else if customServices.length > 0}
{#each customServices as cs (cs.id)} {/each}
{/if}
(customServiceErrors.name = validateCsName(newCustomService.name))} onblur={() => (customServiceErrors.name = validateCsName(newCustomService.name))} placeholder="e.g., Bridal Party French Tips" class={customServiceErrors.name ? 'border-red-500' : ''} /> {#if customServiceErrors.name}

{customServiceErrors.name}

{/if}
(customServiceErrors.price = validateCsPrice(newCustomService.price))} onblur={() => (customServiceErrors.price = validateCsPrice(newCustomService.price))} placeholder="0.00" class={customServiceErrors.price ? 'border-red-500' : ''} /> {#if customServiceErrors.price}

{customServiceErrors.price}

{/if}
{#if customServiceErrors.duration_minutes}

{customServiceErrors.duration_minutes}

{/if}
(customServiceErrors.minimum_age_required = validateCsMinimumAge( newCustomService.minimum_age_required ))} class="w-full {customServiceErrors.minimum_age_required ? 'border-red-500' : ''}" /> {#if customServiceErrors.minimum_age_required}

{customServiceErrors.minimum_age_required}

{/if}

0 for no age restriction

{#if selectedServices.length > 0}

Selected Services

{#each selectedServices as service (service.id)}
{service.name} {service.duration_minutes} mins • £{service.price}
{/each}
Estimated Duration: {formattedTotalDuration}
Total Cost: £{getTotalPrice()}
{/if}
{/if} {#if currentStep === 3} Customize Services Adjust pricing or duration if needed, and add appointment notes

Service Details

Override default pricing or duration for special cases (discounts, extended sessions, etc.)

{#each selectedServices as service (service.id)} {#if serviceOverrides[service.id]}
{service.name}
handlePriceInput(service.id, e.currentTarget.value)} />
handleDurationInput(service.id, e.currentTarget.value)} />
{#if serviceOverrides[service.id] && (Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01 || parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration)}
{#if Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01} Price modified from £{serviceOverrides[ service.id ].originalPrice.toFixed(2)} {/if} {#if Math.abs(parseFloat(serviceOverrides[service.id].price) - serviceOverrides[service.id].originalPrice) > 0.01 && parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration} • {/if} {#if parseInt(serviceOverrides[service.id].duration) !== serviceOverrides[service.id].originalDuration} Duration modified from {serviceOverrides[service.id].originalDuration} mins {/if}
{/if}
{/if} {/each}
Total Duration: {formattedTotalDuration}
Total Cost: £{getTotalPrice().toFixed(2)}
{/if} {#if currentStep === 4} Choose Date & Time {selectedServices.map((s) => s.name).join(', ')} • {formattedTotalDuration} total • £{getTotalPrice().toFixed( 2 )} {#if reservationId && reservationExpiresAt}
Slot reserved until {parseWallClockDate( reservationExpiresAt.toISOString() ).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
({reservationCountdown} remaining)
{/if}
{ selectedDate = newDate; selectedTime = null; }} onPlaceholderChange={(newPlaceholder) => { placeholder = newPlaceholder; userNavigatedCalendar = true; fetchHoursForMonth(newPlaceholder); }} />
{#if loadingAvailableHours && selectedDate}

Loading times...

{:else if selectedDate}
{#if formattedSelectedDate}
{formattedSelectedDate}
{/if} { selectedTime = time; }} />
{#if selectedDate && selectedTime}
{/if} {:else}

Select a date to see available times

{/if}
{/if}