All work

Jungleland

Timed-slot booking and deposits for a children's play centre

Visit junglelandbooking.com
  • Real customers, real payments
  • Multi-step flow that survives a refresh
  • Staff-managed dates and capacity

Jungleland is a children's play centre in Telford. Until 2021 it had no booking system at all — families simply turned up. Social distancing ended that: entry had to be capped per session, so the whole thing had to be built from nothing. Timed slots with hard capacity limits, a deposit taken at the point of booking, and a way for staff to open and close dates themselves without calling me.

It has been running and taking bookings ever since.

The booking flow

A parent booking a party is usually doing it one-handed. The flow is deliberately linear — pick a date, pick a slot, pay a deposit, get a confirmation — and each step is its own route:

/book
  → /book/[date]
    → /book/[date]/[timeslot]
      → /deposit/[id]
        → /confirmation/[id]

Step one. Only dates staff have opened are selectable — everything else is greyed out before a parent can get it wrong.

Booking state lives in a Pinia store persisted to localStorage, so a half-finished booking survives a refresh, a backgrounded tab, or a phone that decides to lock mid-payment. That single decision removed the most common support complaint from the first version.

The details step. The £2 deposit is stated plainly before anyone reaches a card field.

Capacity is the hard part

Slots for a Saturday, with the SEN session first. Green, red and grey are the only states that matter: available, sold out, gone.

Everything else follows from not overselling a slot. The data model is deliberately narrow — Date → TimeSlot → Booking in AppSync/GraphQL over DynamoDB — and capacity is two limits on the timeslot rather than one: a group limit and a space limit, because a party of eight is one group and eight spaces. A slot has to be under both to be bookable.

What has actually been consumed is summed from the slot's bookings at read time, skipping anything cancelled or not yet confirmed. That keeps the write path free of counters to keep in step, at the cost of a number that is only ever as fresh as the last read — fine at this volume, and worth revisiting if two parents were regularly racing for the last place in a session.

Abandoned bookings expire on their own through DynamoDB TTLs rather than needing a cleanup job, so a parent who bails at the payment step releases their slot without anyone intervening.

Payments and mail

Deposits go through Stripe, with the checkout session created server-side in a Nitro route so no key is ever exposed to the client. Confirmations are sent through AWS SES. Both are boring, on purpose — this is somebody's actual revenue.

Ten minutes to pay or the slot goes back. The countdown is the user-facing half of the expiry.

Deposit paid, balance on arrival, and the one instruction that actually gets forgotten.

Admin

Staff get a JWT-authenticated admin area to create bookable dates, set slot capacity, and see what has been booked. It is intentionally plain: the people using it are running a play centre, not learning software.

Pick a date, pick a slot, see who is coming. Groups and spaces are counted separately because a party of eight is one group and eight spaces.

Capacity is two numbers, not one, and staff can change both per slot without involving me — which was the whole point.

Both limits, editable on the slot itself.

What I'd change

The repo has no automated tests and no linting — it grew from something built quickly under a reopening deadline and never had that debt paid down. If I picked it up again today, capacity logic is the first thing I'd put under test, because it is the part that costs real money when it is wrong.