## 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}
		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} bind:selectedTime />
{:else if currentStep === 3}
	<CustomerDetailsForm bind:value={customerInfo} />
{:else if currentStep === 4}
	<BookingSummary
		services={selectedServices}
		date={selectedDate}
		time={selectedTime}
		customer={customerInfo}
		{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}
	{selectedTime}
	on:select={(e) => {
		selectedTime = e.detail;
	}}
/>
```

All temporal logic lives here. Children stay honest.
