Case study · Shipped

Golf simulator club booking platform

Texas · Midland · Lubbock · Big Spring · Web + Mobile · Sole author

Client type

Premium golf simulator club (physical + digital)

Markets

Midland · Lubbock · Big Spring, Texas

Engagement

Full-stack build — sole author, web + mobile + backend + database

Status

Shipped

Next.js 15 React 19 TypeScript MUI v7 Prisma 6 MySQL NextAuth.js React Native 0.81 Stripe bcryptjs

The problem

The client operated a premium golf simulator club with physical locations in three Texas cities. Each location had a fixed number of simulator bays — physical rooms with hardware — and members needed to book time on them. The existing process was manual: phone calls, spreadsheets, and staff at each location keeping their own booking log. It didn't scale, and it was starting to double-book bays.

The specific complication was the membership model. Rather than a single flat rate, the club sold three tiers — Gold, Platinum, and Vibranium — each with genuinely different booking rules: how far in advance you could book, how many concurrent bookings you could hold, how long each session could be, and whether weekend slots were included. A naive rules table wouldn't cover it — the rules interact (a Gold member on a Sunday triggers different constraints than a Gold member on a Wednesday) and they need to be enforced at booking time, on the waitlist, and on the admin override.

The ask

Ship a full booking platform — web for members and staff, mobile for members on the go — that handles three membership tiers across three physical locations without double-booking a single simulator bay.

The approach

Built as a single codebase for the web app and a separate React Native app for mobile, both talking to one Prisma/MySQL data model. Four moves made it work:

1. Membership rules as a first-class engine

The tier rules aren't sprinkled through the booking code — they live in a dedicated membership.ts module that answers questions like "can this user book this bay at this time for this duration?" The rest of the system just asks that module. Adding a new tier or changing a rule means editing one file; nothing downstream cares.

The rules table looks like this in practice:

Tier Advance booking Concurrent bookings Session length Weekend access
Gold Current week only 1 30 minutes No
Platinum 2 weeks 3 1 hour Yes
Vibranium 2 months 10 3 hours Yes

2. Real-time bay availability with conflict detection

A physical simulator bay is a hard resource — two members cannot be in the same bay at the same time, regardless of what the system thinks. Every booking write goes through a conflict-check against the current state of that bay's schedule, at transaction time, in the database. The API refuses the write if there's a conflict; the client re-fetches availability and offers alternatives.

3. Waitlist as an automatic queue

Popular slots (Vibranium prime-time weekend at Midland, for example) fill up. Rather than tell members "sorry, try again," the system lets them join a waitlist. When a booking is cancelled or a session ends early, the waitlist processes automatically — matches the next eligible member (respecting their tier rules) and books the slot for them, with a notification.

4. One data model, two clients

The web app (Next.js) and the mobile app (React Native) both hit the same Prisma-backed API. The mobile app handles Stripe payments natively; the web app handles admin flows (creating members, running the waitlist manually, resolving disputes). Everything else — bookings, dashboard, membership details — works on both, from one truth.

What shipped

What made the release own-able

Physical-inventory booking systems are unforgiving. A double-booked simulator bay isn't a soft failure that a user retry fixes — it's a real customer standing in a real hallway wondering why the staff have no room for them. The whole system has to be correct, not just usually correct.

Being sole author on the full stack meant no handoff seams where correctness could slip between teams. The membership rules that block a booking at the API also inform the availability UI on the web, the availability UI on the mobile app, the waitlist matching logic, and the admin override warnings. All in one head, all consistent.

Lesson

When the resource being booked is physical, correctness isn't a nice-to-have — it's the whole product. One person holding the full state model beats a five-person team where nobody owns the invariants end-to-end.

Stack details

Web platform

Mobile app

Ops

Have a similar problem?

If you're building a booking or scheduling platform where the resource being booked is physical — hardware, rooms, vehicles, appointments — we've done this. The correctness bar is different from digital-only bookings, and it's what most systems get wrong.

Talk to us →