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]

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.

Capacity is the hard part

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.


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.

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

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.