A social + match-tracking app for amateur tennis players — find a hit, set up the match, record the score, build your season. Now live with real players on real courts.
My day job is planning supply chains — spreadsheets and purchase orders, not code. I can't write React and I don't pretend to. But I play tennis, and I kept hitting the same wall as everyone I play with: organizing a hit lives in group chats, and scores live in nobody's memory. This project is what happens when a non-coder takes that itch seriously in the AI era — design the product yourself, direct AI to build it, ship it to real friends. Four steps, honestly told.
Every hit I organized was scattered across group chats; every score lived only in my head. I wanted one place to find a partner and keep the history — built for myself first, not for a market.
Before any code existed I drew the whole product — 34 frames, every screen and state, plus a delivery spec with acceptance criteria. The real work was deciding what not to build.
Claude writes the React and SQL; I make every call around it — what ships next, how a screen should feel, which trade-off wins. Every production data change is still reviewed and run by my own hand.
Not a demo: friends signed up, their feedback reshaped the roadmap within days, and the product proved itself on a Saturday-morning court. Shipped beats perfect.
Amateur players keep hitting the same two walls: "no one to hit with today" and "what was the score last time we played?" Tennis Buddy folds finding a partner and tracking your matches into one loop — and now adds the social layer that makes results worth sharing.
Direct invites or open games anyone can join. Accept or decline, chat before the match, drop it into your calendar — then enter the score set by set. Winner is derived automatically and the match lands in your history.
Five real East Auckland clubs with their actual court counts, players filtered by NTRP level, open games near you. Visitors can browse the live homepage before ever creating an account.
A real feed: posts with attached match results, comments and likes, follows, and player pages with season stats and head-to-head records. Shipped as Phase 3 — live in production, not a mockup.
The first version that shipped in May was a simplified cut. In July I went back to the original 34-frame design and rebuilt it phase by phase — shipping to production at every step, with real friends signing up mid-build.
Back to the original design: a five-phase build plan with one rule — ship as soon as the match loop works, don't wait for the full roadmap.
Phases 1–2 in production: the data foundation plus the full match lifecycle — invites, open games, pre-match chat, calendar files, set-by-set scoring.
Friends signed up and mock data retired the same night. A three-step onboarding wizard went in, and the homepage opened up so visitors can browse without an account.
The app moved to tennis.gimbo.co.nz with transactional email on its own subdomain — sign-in links went from 2 per hour to 30, landing in the inbox instead of spam.
One post in the group chat: four signups that evening, zero lost sign-in links, and players adding home clubs I'd never listed. Analytics switched on.
Phase 3 — the social feed — shipped the same day invite emails started sending straight from the database. And the milestone that actually matters: a friend's invite → accepted → pre-match chat → Saturday morning on court → score recorded in the app.
React 19 + Vite + MUI v6 on the front, with a design language borrowed from Roland-Garros and grounded in Material 3 shapes. Supabase handles data, auth, security — and even sends the emails from inside Postgres. Vercel deploys every push. No servers to babysit.
Solo, end to end — design, frontend, backend, deploy, and the ops of real users. Everything below is live in production, not on a roadmap.
Data foundation, the full match loop, and the social layer are live. Discovery and notifications remain — though invite emails already jumped the queue.
Schema, row-level security, onboarding, the public homepage, the social graph, in-database invite email — every production change reviewed and run by hand.
Friends, not test accounts — signed up via magic links, filling real profiles with real home clubs, including clubs I'd never listed myself.
One match went invite → accept → chat → court → recorded score, entirely inside the product. The single number that matters most.
The process was more interesting than the result — these are the ideas I'm keeping from shipping a real product to real people in a week of evenings.
Phases 1–2 went to production before social or discovery existed. Real users arrived mid-build — and their feedback reshaped the next phase within days.
Direct invites, open games, pending requests and completed matches all live in one matches table. Fewer joins, fewer sync bugs, one source of truth for the whole loop.
Rules live in Postgres: visitors browse real open games while the email column stays revoked. Even a bypassed frontend hits a wall at the database.
Expired unfilled games are only visible to their host — so the host's next app-open cleans them up. Full coverage, zero scheduled jobs, zero new infrastructure.
Signup never asked for a name — found on day one. A score stored flipped reads correctly to the submitter and wrong to the opponent — only visible from the other side.
Shared SMTP allowed 2 sign-in emails per hour — the third friend that hour never got in. An owned sender domain fixed growth in a way no code could.
Tennis Buddy is live. Sign in with any email — a magic link lands in seconds — or just browse the open games without an account.
Open Tennis Buddy →No password is ever stored. Login uses email-only magic links via Supabase Auth. Your email is used solely for sign-in links and the match invites you ask for.