Olympic Combat
Olympic Combat teaches martial arts to kids in Dubai, and its growth depends on one flow: a parent finds the school, books a free trial, and turns up. That funnel already existed when ELO inherited it from the London agency that built it — along with a two-page handover document that was, in substance, a list of links to the Vercel, Firebase, Google Cloud and Bitbucket docs. No environment manifest, no account inventory, no data shapes, no cutover plan. We reconstructed what the system actually did from the code, kept shipping the bookings and calendar work, and moved the whole estate onto the client's own accounts — 2,605 lead records included. The client's job was to grant access and attach cards; everything else stayed on our side of the line.
What a lackluster handover actually costs
The handover document listed the stack — Mailchimp, Vercel, Firebase, Google Cloud, Bitbucket, Next.js — and then, under each migration heading, restated what the vendor's own documentation says: export your environment settings, transfer the project, reconfigure domains. Useful if you already know the system. Useless as a description of *this* system.
So the first job was archaeology rather than engineering. Getting the inherited app running locally surfaced what the document had not: a dependency the previous team had installed but never declared in package.json, react-spring animations that fired in a production build and silently did nothing in dev, and two required environment variables — the Mailchimp audience ID and the Google Maps key — that appeared nowhere in the notes.
None of that became the client's problem. Each finding was resolved against the codebase and confirmed in a single consolidated question rather than escalated as a series of blockers, which is the whole difference between a takeover and a stalled takeover.
The booking flow
- Five gated steps. Parent details → phone verification → child age selection → trial slot → confirmation, each step a route in the Next.js App Router with its own validation.
- Phone OTP as the trust gate. Firebase Auth with reCAPTCHA and SMS sign-in, six-digit code — a school selling free trials to parents needs a real contact number, not an email that bounces.
- Age-routed classes. The child’s age decides which class the trial is for, so the slot the parent picks is a slot that fits their kid.
- Calendly as the slot authority. The trial calendar is embedded rather than reimplemented, with event listeners flipping the lead record to booked the moment the parent schedules — one source of truth for availability, no drifting second calendar.
- Provenance on every lead. UTM parameters and device/OS captured onto the record at signup, so the school can tell which campaign produced which trial.
- Mailchimp on signup. Subscription wired into the same step, with the failure case surfaced to the parent as a plain "check your email spelling" rather than a silent drop.
Timetable and the admin side
Alongside the funnel sits the public class timetable — what runs, when, for which ages — and a separate admin console for the people who work the leads. That console is a Firebase-authenticated dashboard over the same Firestore collection: a filterable table of leads with CSV export, date-range filtering, and Recharts analytics on top, with Redux holding the table and chart state.
The lead record is the interesting part. It carries not just contact details but the whole follow-up state — contacted, responded to follow-up, completed the trial session, signed up in full, plus a submissions history and free-text comments. The funnel and the console are two views of one object, which is why the sales side could actually be measured.
The migration
- Vercel. Environment variables, build settings and domain/SSL configuration carried across, with project ownership reassigned to the client’s account.
- Firebase and Google Cloud. A new project under the client’s own Google Cloud, Firestore data and auth users exported and imported, API keys and auth configuration updated, billing account and IAM roles moved to the client’s team.
- Bitbucket. Repositories transferred into the client’s workspace, so the source stopped living in the previous agency’s org.
- Mailchimp. Audience and marketing automation reconnected to the migrated funnel.
- 2,605 records, reshaped. The lead export came out with 22 columns of CRM state; the Firebase import needed 11, in a different shape. The transform between them was the actual data migration, and the record count came out the same on both sides.
Why this one is worth reading
Inheriting a live system from another agency is one of the least glamorous and most common jobs in software, and it is almost never described honestly. The failure mode is not technical: it is the client becoming the message bus between the outgoing and incoming teams, answering questions neither team should be asking them.
What ELO delivered here was the absence of that. The client provided what only they could — account access, API key changes, payment cards on the new accounts — and the reconstruction, the migration sequencing, and the continued feature work stayed with us. Olympic Combat has since rebranded as United Kickboxing Academy — olympiccombat.io now redirects there — but the estate it runs on today is the one ELO migrated: in the client's own hands, not the outgoing agency's.
High-calibre architecture decisions.
Technical Highlights
- Five-step Next.js App Router booking funnel gated by Firebase phone OTP (reCAPTCHA + SMS).
- Calendly embedded as the slot authority, with event listeners writing booking state back to Firestore.
- Age-based class routing so the trial matches the child.
- UTM and device provenance captured onto every lead record at signup.
- Mailchimp subscription in the signup path with a user-legible failure case.
- Firebase-authenticated admin console: filterable lead table, CSV export, date ranges, Recharts analytics over Redux state.
- Four-platform migration (Vercel, Firebase/Google Cloud, Bitbucket, Mailchimp) onto client-owned accounts.
- 2,605-record export → 22-column to 11-column transform → Firestore import, count-verified.
Key Decisions & Rationale
A martial-arts school already manages availability in a calendar tool. Rebuilding scheduling would have created a second source of truth to keep in sync; embedding it and listening for the scheduled event keeps availability in one place and still records the booking against the lead.
Free-trial funnels attract junk. Verifying the number before a slot is consumed protects real availability and gives the school a contact that works — the cost is one extra step, paid before anything valuable is spent.
The handover document was inadequate and the outgoing agency was gone. Treating the code as the specification — and batching the genuinely unanswerable questions into one ask — kept the client out of a relay role they had already paid to avoid.
The original estate lived in the agency’s Vercel, Google Cloud and Bitbucket orgs, which is exactly why the handover was painful. Putting ownership with the client makes the next transition — including away from us — a transfer rather than an excavation.
By the numbers.
If we worked together.
I bring high-leverage product engineering and absolute operational discipline: shipping weekly, pressure-testing user inputs, and automating delivery pipelines.
See it in the wild.
This case study covers how Olympic Combat was built — these links open what ships today: the live product, repo, demos, and supporting material.
Axis
Axis is the ELO product that never shipped, and the one whose idea I still think is right. It is field-workforce tracking — packages, jobs, deadlines, live location, minute-by-minute trails — but built on a multi-tenant premise that most such tools refuse: every logged individual is their own entity, and they can be engaged by several organisations at once. Cloudsight's SSO defines what each org can see, and vehicles and people are shared across companies by time window. Imagine a truck driver or a delivery rider working for three businesses in the hours that suit them, with each business seeing only its own scope. The worker earns more; the businesses stop paying for idle capacity.