Case Study

Drive Up

Online booking for a driving instructor in Edmonton: students book and prepay a lesson, manage it in their own portal, and the instructor runs the day from one console, in English and Ukrainian. In client review before launch.

Period
– present
Role
Sole engineer; in client review
  • 8 days (13 to 21 September 2026)

    First commit to staging

  • 68

    Commits, all mine

  • 13

    Architecture decision records

  • 21

    Test files

  • 2 (English and Ukrainian)

    Languages

Drive Up's first screen on staging in its navy Night drive look: a headline in English and Ukrainian over a choice between the two languages

Lesson booking for one driving instructor, from first commit to staging in eight days

Problem

One driving instructor teaches students in Edmonton, in English and Ukrainian. Students needed to book a lesson themselves and pay for it in advance, at a time that fits his working hours and leaves a buffer around a first-time driver's lesson, then move or cancel it within his rules. The decisions that are his had to stay his: rescheduling, a late cancellation, a no-show and how much is refunded.

What was built

Drive Up is one Next.js application with PostgreSQL in Docker. A visitor chooses English or Ukrainian on the first screen. A student proves a phone number with a code the app sends by SMS, behind a Cloudflare Turnstile check, then books in four steps: who they are, the lesson type, a day and time computed from the instructor's weekly schedule, blocked time, minimum notice, booking horizon and buffers, and payment. The price plus GST is paid in Stripe's embedded form, and only Stripe's signed payment event turns the 30-minute hold on the slot into a lesson.

In the student portal a student sees every lesson, can reschedule each one once, and can cancel with a full refund before the late-cancellation window. The instructor's console shows the day's agenda, the students with his private notes, and every action on a lesson: reschedule, cancel, mark a no-show and decide the penalty and the refund. Reminders and hold expiry run on a scheduler inside the server process. The instructor also manages the photos and videos behind the public pages and an intro of full-screen slides. Staging is deployed by GitLab CI to my VPS behind its shared Traefik; production has its own Docker Compose layer with Traefik and HTTPS, verified locally.

Outcome

Drive Up is in client review on staging, which sends no SMS and takes no real money; it goes to production when the client approves it. The first staging deploy checked the whole path: health over HTTPS, administrator sign-in, media uploads, and a booking paid with a Stripe test card that appeared on the instructor's day. The same deploy found two bugs, configuration keys missing from the container and cookie security tied to the wrong setting, and both were fixed that day. The source is private.

Shipping Timeline

From zero to production.

  1. 01 · Requirements

    Baseline requirements

  2. 02 · Build

    Night drive, in English and Ukrainian

    The look was chosen from three live prototypes; the language is picked on the first visit.

  3. 03 · Build

    Instructor sign-in, settings and media

  4. 04 · Architecture

    Booking stage planned

    Glossary, decisions and ADRs, and one issue per part.

  5. 05 · Payments

    Booking with Stripe prepayment

    Phone verification, the availability engine, holds and the embedded payment form.

  6. 06 · Build

    Student portal

    One reschedule per lesson, and refunds for cancellations.

  7. 07 · Build

    The instructor's day

    Agenda, student cards, lesson actions and penalties, reminders and SMS templates.

  8. 08 · Payments

    Refunds and payment timing hardened

    A refund Stripe failed is sent again; the time-dependent payment paths are tested.

  9. 09 · Deploy

    Staging on my VPS

    Deployed by GitLab CI behind the shared Traefik and checked end to end with a Stripe test booking.

  10. 10 · Support

    Next: client approval, then production

Engineering

How it is built.

Stack

  • TypeScript
  • React
  • Next.js
  • Node.js
  • PostgreSQL
  • Docker
  • GitLab CI/CD
  • Traefik
  • Encrypted secrets (SOPS, dotenvx)
  • Vitest
  • Stripe

Decisions

Only a signed Stripe event creates a lesson
Choosing a slot places a 30-minute hold; the lesson exists only after Stripe delivers a signed paid event. The handler locks the checkout and the availability together, so a repeated event never makes a second lesson. If a payment arrives after its hold expired and the slot is gone, the whole price and GST are refunded.
Own verification codes over plain SMS
Every code costs money, and a dedicated verification service charges a multiple of a plain message. The app generates, hashes, rate-limits and checks its own codes, sends them as ordinary messages, and puts a free Cloudflare Turnstile check in front of the request so bots cannot spend the SMS budget. Outside production no message is sent and a fixed code works.
A fixed GST line instead of Stripe Tax
The business sells only in Alberta, where the one sales tax is the federal GST. The rate lives in the instructor's settings and the app adds one tax line to each checkout, snapshotted on the lesson, so a later rate change never alters a paid lesson. Stripe Tax would compute the same number for a fee on every transaction.
Reminders run inside the server process
Production is one host with one application instance, and the project allows no services beyond PostgreSQL, the application and the proxy. A scheduler in the Next.js process polls the database and claims each item in a transaction; a reminder is recorded as sent before the message leaves, so a restart cannot send it twice.
Encrypted configuration in the repository
Each environment has one dotenvx-encrypted env file committed with the code, and CI holds one decryption key. Invalid configuration stops the process with a message naming every offending key, and no configured value is ever printed.
  • Booking on Drive Up, step one of four: a returning student continues with their mobile number, a new one starts a first lesson, with the steps About you, Lesson, Date and time, and Payment beside it
  • Drive Up booking in Ukrainian on a phone: step one asks whether the student has driven with the instructor before, with the four booking steps across the top
  • Drive Up student portal: a completed two-hour standard lesson on Wednesday, September 23, with the lesson price, 5% GST and the amount paid