## Goal

Decompose `+page.svelte` into focused, testable components with clear data flow and minimal shared state.

---

## High‑level structure

```
/routes/book/+page.svelte
/lib/components/booking/
  BookingFlow.svelte
  StepIndicator.svelte
  ServiceSelector.svelte
  ServiceCard.svelte
  DatePicker.svelte
  TimeSlotPicker.svelte
  CustomerDetailsForm.svelte
  BookingSummary.svelte
  BookingActions.svelte
/lib/stores/booking.ts
/lib/types/booking.ts
```

---

## State ownership (important)

**Single source of truth:** `BookingFlow.svelte`

* currentStep
* selectedServices
* selectedDate
* selectedTime
* customerInfo
* pricing / totals

Everything else receives props + dispatches events. No hidden global coupling. 🧠

---

## Components

### 1. `BookingFlow.svelte`

**Role:** Orchestrator

* Holds booking state
* Validates transitions between steps
* Calls API + handles toasts

**Props:** none
**Emits:** none

This is the only component allowed to know *everything*.

---

### 2. `StepIndicator.svelte`

**Role:** Visual progress

**Props**

```ts
currentStep: number
totalSteps: number
```

Pure UI. Zero logic.

---

### 3. `ServiceSelector.svelte`

**Role:** Choose services

**Props**

```ts
services: Service[]
selected: Service[]
```

**Emits**

```ts
select(service)
deselect(service)
```

Internally renders multiple `ServiceCard`s.

---

### 4. `ServiceCard.svelte`

**Role:** One service tile

**Props**

```ts
service: Service
selected: boolean
```

No awareness of booking steps or pricing totals.

---

### 5. `DatePicker.svelte`

**Role:** Calendar selection

Wraps your existing `Calendar` usage.

**Props**

```ts
date: CalendarDate | undefined
```

**Emits**

```ts
change(date)
```

---

### 6. `TimeSlotPicker.svelte`

**Role:** Pick available time

**Props**

```ts
date: CalendarDate
selectedTime: string | null
availability: TimeSlot[]
```

**Emits**

```ts
select(time)
```

---

### 7. `CustomerDetailsForm.svelte`

**Role:** Collect user info

**Props**

```ts
value: CustomerInfo
```

**Emits**

```ts
update(value)
```

No submit button here. Forms shouldn’t decide flow control.

---

### 8. `BookingSummary.svelte`

**Role:** Read‑only confirmation

**Props**

```ts
services: Service[]
date: CalendarDate
time: string
customer: CustomerInfo
total: number
```

Zero mutation. Snapshot only.

---

### 9. `BookingActions.svelte`

**Role:** Navigation + submit

**Props**

```ts
canBack: boolean
canNext: boolean
isSubmitting: boolean
```

**Emits**

```ts
back()
next()
submit()
```

---

## Shared types

Move all interfaces out of `+page.svelte`:

```ts
// lib/types/booking.ts
export interface Service { ... }
export interface CustomerInfo { ... }
export interface TimeSlot { ... }
```

---

## Optional store (recommended)

```ts
// lib/stores/booking.ts
export const bookingStore = $state({ ... })
```

Use only if the flow must persist across routes or reloads. Otherwise keep it local.

---

## Result

* Smaller files
* Predictable data flow
* Each component explainable in one sentence
* Easier testing and future changes

Entropy reduced. ✂️🧩

---

## Step 1 — Extract the brain (no store yet)

We start by **wrapping the existing logic**, not rewriting it.

### 1. Create `BookingFlow.svelte`

Move *all* booking‑related state and logic out of `+page.svelte` into this file.

```svelte
<script lang="ts">
  import StepIndicator from './StepIndicator.svelte';
  import ServiceSelector from './ServiceSelector.svelte';
  import DatePicker from './DatePicker.svelte';
  import TimeSlotPicker from './TimeSlotPicker.svelte';
  import CustomerDetailsForm from './CustomerDetailsForm.svelte';
  import BookingSummary from './BookingSummary.svelte';
  import BookingActions from './BookingActions.svelte';

  import type { Service, CustomerInfo, TimeSlot } from '$lib/types/booking';

  // --- State (copied verbatim from +page.svelte) ---
  let currentStep = 1;
  let selectedServices: Service[] = [];
  let selectedDate;
  let selectedTime: string | null = null;
  let customerInfo: CustomerInfo = { /* unchanged */ };

  // pricing, derived values, API calls stay here
</script>

<StepIndicator {currentStep} totalSteps={4} />

{#if currentStep === 1}
  <ServiceSelector
    services={services}
    selected={selectedServices}
    on:select={(e) => selectedServices.push(e.detail)}
    on:deselect={(e) => selectedServices = selectedServices.filter(s => s.id !== e.detail.id)}
  />
{:else if currentStep === 2}
  <DatePicker bind:date={selectedDate} />
  <TimeSlotPicker
    date={selectedDate}
    availability={availability}
    bind:selectedTime
  />
{:else if currentStep === 3}
  <CustomerDetailsForm bind:value={customerInfo} />
{:else if currentStep === 4}
  <BookingSummary
    services={selectedServices}
    date={selectedDate}
    time={selectedTime}
    customer={customerInfo}
    total={total}
  />
{/if}

<BookingActions
  canBack={currentStep > 1}
  canNext={currentStep < 4}
  on:back={() => currentStep--}
  on:next={() => currentStep++}
  on:submit={submitBooking}
/>
```

Nothing clever yet. This is a **containerization step**, not a refactor.

---

### 2. Reduce `+page.svelte` to glue

```svelte
<script lang="ts">
  import BookingFlow from '$lib/components/booking/BookingFlow.svelte';
</script>

<BookingFlow />
```

At this point:

* Behaviour is identical
* No store introduced
* You have a single, explicit "brain"

---

## Step 2 — Extract `ServiceCard` (low risk, high reward)

This is the safest cut: **pure UI, minimal state, zero flow control**.

---

### 2.1 Identify the slice

In `ServiceSelector`, find the repeated markup that:

* Displays service name / price / duration
* Highlights selected state
* Handles click / toggle

If it *renders one service*, it becomes a card.

---

### 2.2 Create `ServiceCard.svelte`

```svelte
<script lang="ts">
  import type { Service } from '$lib/types/booking';
  import { createEventDispatcher } from 'svelte';

  export let service: Service;
  export let selected = false;

  const dispatch = createEventDispatcher();

  function toggle() {
    dispatch(selected ? 'deselect' : 'select', service);
  }
</script>

<button
  class:selected
  on:click={toggle}
>
  <h3>{service.name}</h3>
  <p>{service.duration} min</p>
  <p>£{service.price}</p>
</button>

<style>
  button { /* existing styles */ }
  .selected { /* existing selected styles */ }
</style>
```

No booking logic. No totals. No step awareness.

---

### 2.3 Simplify `ServiceSelector.svelte`

```svelte
<script lang="ts">
  import ServiceCard from './ServiceCard.svelte';
  import type { Service } from '$lib/types/booking';

  export let services: Service[] = [];
  export let selected: Service[] = [];
</script>

<div class="grid">
  {#each services as service (service.id)}
    <ServiceCard
      {service}
      selected={selected.some(s => s.id === service.id)}
      on:select
      on:deselect
    />
  {/each}
</div>
```

Selection state still lives *above*. This is critical.

---

## Validation checkpoint

At this point:

* `ServiceCard` is dumb
* `ServiceSelector` coordinates cards
* `BookingFlow` owns truth

If this feels boring, good. Boring code is stable code.

---

## Step 3 — Extract `CustomerDetailsForm` (controlled input boundary)

This step removes a classic source of entropy: **forms that secretly control flow**.

The rule here is strict:

> Forms collect data. Parents decide what to do with it.

---

### 3.1 Identify the form logic

In `BookingFlow`, locate:

* Name / email / phone inputs
* Validation messages
* `on:input` handlers

Anything that mutates `customerInfo` belongs in the form *except* submission.

---

### 3.2 Create `CustomerDetailsForm.svelte`

```svelte
<script lang="ts">
  import type { CustomerInfo } from '$lib/types/booking';
  import { createEventDispatcher } from 'svelte';

  export let value: CustomerInfo;

  const dispatch = createEventDispatcher();

  function update<K extends keyof CustomerInfo>(key: K, val: CustomerInfo[K]) {
    dispatch('update', { ...value, [key]: val });
  }
</script>

<div class="space-y-4">
  <input
    type="text"
    placeholder="Name"
    value={value.name}
    on:input={(e) => update('name', e.currentTarget.value)}
  />

  <input
    type="email"
    placeholder="Email"
    value={value.email}
    on:input={(e) => update('email', e.currentTarget.value)}
  />

  <input
    type="tel"
    placeholder="Phone"
    value={value.phone}
    on:input={(e) => update('phone', e.currentTarget.value)}
  />
</div>
```

No submit button. No step logic. No API calls.

---

### 3.3 Wire it back into `BookingFlow`

Replace inline inputs with:

```svelte
<CustomerDetailsForm
  value={customerInfo}
  on:update={(e) => customerInfo = e.detail}
/>
```

Validation still lives in `BookingFlow`:

* Can we go to the next step?
* Is submit enabled?

---

## Validation checkpoint

You should now observe:

* The form is reusable
* BookingFlow got smaller
* Step logic is easier to read

If you *can’t* explain where a rule lives in one sentence, it’s in the wrong place.

---

## Step 4 — Split `DatePicker` and `TimeSlotPicker` (explicit dependency)

This is the first step where **ordering matters**. Time is meaningless without a date. We make that dependency obvious and one‑directional.

Rule of the step:

> Date flows down. Time flows up.

---

### 4.1 Extract `DatePicker.svelte`

This component selects *only* a date. No availability logic. No time awareness.

```svelte
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  import type { CalendarDate } from '@internationalized/date';

  export let date: CalendarDate | undefined;

  const dispatch = createEventDispatcher();
</script>

<Calendar
  value={date}
  on:change={(e) => dispatch('change', e.detail)}
/>
```

It emits intent. That’s it.

---

### 4.2 Extract `TimeSlotPicker.svelte`

Time slots depend on *inputs*, never globals.

```svelte
<script lang="ts">
  import { createEventDispatcher } from 'svelte';
  import type { TimeSlot } from '$lib/types/booking';

  export let date; // required
  export let availability: TimeSlot[] = [];
  export let selectedTime: string | null = null;

  const dispatch = createEventDispatcher();
</script>

{#if !date}
  <p class="text-muted">Select a date first</p>
{:else}
  <div class="grid">
    {#each availability as slot (slot.time)}
      <button
        class:selected={slot.time === selectedTime}
        disabled={!slot.available}
        on:click={() => dispatch('select', slot.time)}
      >
        {slot.time}
      </button>
    {/each}
  </div>
{/if}
```

No fetching. No step logic. Just rendering.

---

### 4.3 Wire in `BookingFlow`

Here is the **complete and correct wiring**, including guards and reset logic:

```svelte
<DatePicker
  date={selectedDate}
  on:change={(e) => {
    const newDate = e.detail;
    selectedDate = newDate;

    // changing date invalidates time
    selectedTime = null;

    // fetch / recompute availability here
    loadAvailability(newDate);
  }}
/>

<TimeSlotPicker
  date={selectedDate}
  availability={availability}
  selectedTime={selectedTime}
  on:select={(e) => {
    selectedTime = e.detail;
  }}
/>
```

All temporal logic lives here. Children stay honest.