All work
Client · in production

C2J Auto Spa

Online booking and a back office for a mobile detailing business that used to book by phone and text.

Role
Lead engineer at G2, with Nick Giacchi
Year
2026
Stack
React 19, Vite, Express on Vercel Functions, Firebase Auth and Firestore, Google Calendar API, Stripe (built, off), Twilio (built, off), GitHub Actions
c2jautospa.com
Demo dataThe real booking flow, run locally with the default catalog. The taken 3:00 PM slot is demo data.
paying client takes bookings on itRésumé · source
1
admin tabs with 14 management panelsCounted in the repo · source
9
merged pull requestsCounted in the repo · source
38

The problem

The owner took every booking by phone and text. Prices depend on the vehicle size and the add-ons, and one person's calendar has to hold the whole business.

What I built

  1. 01Double booking prevented inside a Firestore transaction: the server re-reads the day's bookings and blocked slots, checks interval overlap, and returns 409 if the slot was just taken.
  2. 02One shared time module for the browser and the server, which fixes the classic bug where a date string parses as UTC midnight and shifts every booking a day in Eastern time.
  3. 03A Stripe webhook that stamps a sent marker before sending, so a retry never messages a customer twice, and awaits every send because a serverless function freezes when it responds.
  4. 04Per-channel notification outcomes saved on each booking, so 'the client never got a confirmation' can be diagnosed from the console.
  5. 05Catalog version history: every admin change snapshots first, bursts of drag reorders merge into one snapshot, and a restore can itself be undone.
  6. 06Security rules deployed by CI in separate steps, so a Storage failure can never block the Firestore rules.

More screens

Recorded on the live site.
Demo dataSwipe rail, cart drawer, checkout, live price.
Services shown as a sideways swipe rail on a phone with the next card peeking in
Demo dataThe owner can switch the service list to a swipe rail from the admin.
c2jautospa.com
Full Detail selected with a red border while every other main service is faded and locked; add-ons stay available
Demo dataOne main service per booking. The rule lives in pure cart functions shared by every page.
c2jautospa.com
Vehicle size cards with Large SUV selected and a pet hair add-on set to Moderate, subtotal $430
Demo dataVehicle size and add-on severity reprice the order as you choose.
c2jautospa.com
October calendar with the 8th selected and four time slots, 3:00 PM struck through as taken
Demo dataSlots come from the same overlap code the server uses to refuse a double booking.
c2jautospa.com
Contact form filled with fictional details and a separate checkbox to receive texts about the appointment
Demo dataText messages need their own opt-in box. Details shown are fictional.
Laptop showing the C2J booking page
Generated hardware

Decisions

Every integration is optional

No Stripe means request-only booking. No Twilio means SMS is a no-op. The site degrades instead of crashing, which let it go live before every account was approved.

SMS gated on explicit consent

Customer texts require a consent checkbox to satisfy the TCPA and A2P 10DLC. Older bookings with no answer are treated as no consent.

Moved the AI key server-side

An inherited Gemini key had been inlined into the browser bundle. The AI endpoint now accepts a fixed set of operations with admin authentication, not an open proxy.

Not claimed

  • Stripe deposits and SMS confirmations are built and switched off until the client enables them.
  • The AI marketing hub is built and switched off.
  • The admin console is not shown because it holds customer data.
  • The laptop image is a generated render with a real screen capture composited in.

Links