Project · Mar 2025 -
Schott Classic
The full platform behind Cincinnati's biggest wiffle ball weekend. Registration, payments, brackets, live scoring, umpire dispatch, sponsor CRM, and 200+ admin API endpoints, all for a volunteer-run tournament that started in a basement.
12 views
Source mark / 01
195.97²
At a glance
- Status
- Live at schottclassic.com. 2026 tournament completed July 11–12.
- Role
- Solo build: platform, backend, admin tooling, and operations
- Stack
- Next.js, TypeScript, PostgreSQL, Drizzle ORM, Square Payments, Replit
- Live at
- schottclassic.com
By the numbers
- Teams
- 105
- Players
- 487
- Fields
- 12
- Sponsors
- 67
- Games on record
- 700
- Years
- 6
Why I built it#
The Schott Classic started during COVID in a neighbor's basement. A handful of west-side Cincinnati families who needed something to look forward to, a wiffle ball, and a strike zone taped to the wall. It's named for William C. Schott and his lifelong love of the game.
The first Classic in 2021 drew 28 teams. Six years later it fills a full July weekend: 105 teams and 487 rostered players across three tournaments (adult co-ed, women's, and a Jr. Classic that brings out kids from 23 schools across Greater Cincinnati and Northern Kentucky). It's still run entirely by volunteers, and all proceeds support St. Antoninus Athletic Club youth sports.
Running a tournament of that size by hand is a spreadsheet nightmare. Registration, payments, waivers, bracket generation, live scoring, umpire assignments, sponsor tracking. Every piece was a manual bottleneck that grew with the field. So I built the platform that runs all of it.
What it does#
The public site is the front door: teams register online and pay through Square, digital waivers are collected at registration, and division placement, roster limits, and waitlists are handled automatically. When a division fills, it flips to a waitlist without anyone having to watch a spreadsheet.
On tournament day, pool-play results seed into single-elimination brackets, and scores update live on the web. The bracket page is where the weekend watches itself happen: captains check their next game from the Biergarten, parents follow along from the parking lot, and the Hall of Champions gets its next entry before the trophies are even handed out.
But the public site is half the platform. The other half is the operations layer that the volunteer crew runs from the field:
- Scorekeeper app: A mobile-first scoring app that umpires run from their phones. Large one-handed controls, undo and score correction, and offline capability with local save and auto-sync when service returns. A practice mode lets volunteers rehearse before game day.
- Umpire dispatch: Games are pushed to umpire phones from central command. Umpires sign in with email-link auth, see their next assignment in a My Games portal, and tap to accept. No passwords, no paper assignments.
- Volunteer scheduling: Four volunteer roles (umpire, scorekeeper, field marshal, event support) with online signup and shift claiming. The coordinator sees who's claimed what and fills gaps before tournament day.
- Central command booth: An admin interface that dispatches games to umpires, confirms scores through a Pending Review workflow, resolves flagged rulings, and monitors the full tournament across 12 fields in real time.
- Sponsorship platform: Tiered packages (Grand Salami, Home Run, Triple) with live availability, a public sponsor wall, and automated tracking of 67 partner businesses across six years.
- By the Numbers: A data dashboard that turns six years of tournament data into a pitch deck: 310 all-time team entries, 700 games on record, 305 kids on 2026 youth rosters across 23 schools and 13 ZIP codes.
- Schotty: A Telegram bot that pipes real-time tournament updates into the volunteer working group chat. Score confirmations, game dispatches, bracket updates, and incident alerts, plus registration status, outstanding waivers, bounced emails, and failed logins, all land in one thread so the crew can troubleshoot and address issues as they happen instead of refreshing a dashboard.
Schotty
Tournament Operations Bot
Score confirmations · dispatches · registration · waivers · bounced emails · failed logins
How it was built#
Nearly 2,000 source files across a single Next.js app. It uses TypeScript, PostgreSQL with Drizzle ORM, and is developed and deployed on Replit. Square handles payments end-to-end: checkout, receipts, and refunds all flow through their API.
The database schema spans 19 tables: tournaments, teams, players, games, brackets, fields, pools, sponsors, volunteers, shifts, financials, email, content, surveys, notifications, OAuth, waitlist, and users, with database views for tournament stats, team rosters, and financial summaries. Role-based access controls four user types: admins, team captains, umpires, and volunteers.
Having all of that in one connected dataset is what changed everything. Registration feeds team building, team building feeds pool assignment, pools feed brackets, and brackets feed live scoring and the Hall of Champions. No CSV exports, no manual imports, no "let me check the other spreadsheet." Every stage reads from the last, and every stage is queryable in real time.
The admin dashboard is the other half of the platform. Over 200 API endpoints power:
- Bracket operations and seeding
- Sponsor CRM with company discovery and auto-logo fetching
- Email marketing with template designer and AI-assisted drafting
- Content management for every public page
- Scheduling and auto-schedule tools
- Financial tracking
- Check-in with kiosk mode
- A banner planner and social studio
- API key management with audit logs
- A data-quality engine
- Cron jobs for abandoned-cart recovery, waitlist expirations, volunteer reminders, daily digests, and tournament-day checks
The scorekeeper app is a separate route within the same app, optimized for mobile and built to degrade gracefully when cell service drops at the field. Events save locally and sync through a pending queue when the connection comes back. Central command runs from the same dashboard, dispatching games, confirming scores through a Pending Review workflow, and resolving flagged rulings in real time.
Dataflow / 01
Connected data
Stack / 01
Architecture
Frontend
Platform
Data
Operations
| Rank# | Team | W-L | RD | RS |
|---|---|---|---|---|
| 1 | Backyard Bombers | 3-0 | +15 | 21 |
| 2 | Scuff City | 2-1 | +4 | 15 |
| 3 | Mercy Rule | 1-2 | -5 | 9 |
| 4 | The Ringers | 0-3 | -14 | 4 |
| Rank# | Team | W-L-T | RD | RS |
|---|---|---|---|---|
| 1 | Bat Attitudes | 2-0-1 | +9 | 17 |
| 2 | The Whiffleblowers | 2-1-0 | +10 | 19 |
| 3 | Dad Bod Dynasty | 1-1-1 | 0 | 12 |
| 4 | Fully Loaded | 0-3-0 | -19 | 5 |
The components became a registry#
The platform also produced this site's component registry. 17 of the 20 published components were extracted from the Schott Classic codebase, not designed as a library but rewritten from production code that ran an actual tournament:
- Tournament Bracket: pure-CSS connectors, no measuring, no libraries. The bracket used to be a whiteboard.
- Score Panel: the live two-team score card from a scorekeeper's phone, with +/− steppers or read-only mode.
- Count Display: balls, strikes, and outs as broadcast-style dot rows. Max counts are configurable because wiffle formats disagree about everything.
- Base Diamond: runners on base at a glance, built for umpires tracking ghost runners between pitches.
- Play-by-Play: the event feed, minus the offline-sync machinery.
- Scorekeeper Layout: the game-day app shell, with a pull-up sheet on phones.
- Pool Standings: tiebreaks by run differential, then runs scored, because that's the actual order on the actual rules sheet.
- Bracket Pick'em: tap to advance teams; changing an early pick voids everything downstream.
- Double Elimination Bracket: winners bracket, losers bracket, and a grand finals match.
- Scoreboard: a ballpark line score, monospace and hairline borders.
- Field Schedule, Roster Check-In, Run of Show, Event Countdown, Final Score Summary, Leaderboard Table, Game Setup Panel: the rest of the operations and scorekeeping family.
Extraction meant rewriting, not copying: every component takes plain props, styles itself with theme tokens, and carries zero dependencies. The Zustand store didn't survive. The offline-sync queue didn't survive. What survived was the domain knowledge: the tiebreak order, the scoreboard convention, the shape of a bracket connector. That's the stuff you can't get from imagining use cases, and it's the only part worth publishing. I wrote about this when the registry launched.
The tournament has grown 3.8× since 2021, and the platform has grown with it. Every feature exists because a volunteer asked "can we stop doing this by hand?" and the answer was usually a weekend of work that saved forty hours of tournament-day chaos.
buildBracketRounds(): first round + results in, later rounds derived from winners. Hover a team to trace their run.
schedulePlacement="between" for tournament day: one game live, one upcoming, final TBD
Where it stands#
The 2026 Schott Classic ran July 11–12 at the William C. Schott Memorial Fields. 105 teams, 487 players, 12 fields, and the platform held. But "held" is doing some work. Tournament weekend exposed real seams:
> Youth registration loop: a self-referential content value sent youth parents into an infinite redirect instead of the waitlist flow. Fixed mid-tournament, but not before parents hit a dead end. > > Bracket page blanking: a transient fetch failure on the public bracket page was silently treated as "no data," wiping an already-rendered bracket. The compat DB client swallowed the error instead of surfacing it. > > Cold-DB publish failures: Replit's healthcheck probes the homepage, which makes DB calls. When Neon was cold at startup, the probe timed out and the deploy failed despite a clean build. Shipped a static 200 fast-path for loopback probes. > > Stale connection pools: additive schema changes on the live Neon DB didn't reach the running app because pooled connections held stale prepared-statement plans. Required a re-publish to recycle connections.
Each one got patched in the moment, and each one is now documented as a memory in the repo so it doesn't happen the same way twice. But they're the kind of bugs you only find when 487 players and their families are counting on the system on a Saturday afternoon.
What's next for 2027#
The focus narrows to the umpire app, the piece that runs the game itself:
- Roster verification: umpires confirm they have the right teams and the right players before first pitch, right from the scorekeeper app. No more paper check-in sheets that don't match what's actually at the field.
- Lineup setting: umpires build and lock the batting order on their phone, with late additions and substitutions tracked in real time. The lineup feeds directly into the live scoring flow, so there's no gap between "who's batting" and "who just scored."
- Live scoring, hardened: the scoring app works, but 2026 exposed the edges: transient fetch failures that blanked a rendered bracket, offline sync that needs to be more resilient, and score confirmation that shouldn't require central command to intervene on every game. The goal is an umpire who can start, score, and finish a game end-to-end without anyone at a laptop backing them up.
The broader infrastructure (captain re-engagement, sponsor retention, the cron jobs) is in place. The umpire experience is where the next round of work pays off, because that's the app that's open on a phone at the field when 487 players are counting on it.
The tournament is a 501(c)(3) under St. Antoninus Athletic Club, and every dollar goes back into field upgrades, equipment, and kids playing ball. Follow along at @SchottClassic or see the full weekend at schottclassic.com.
Weekend / 01
July 11-12
Fri 5:00 PM
Setup & Check-in
Fields marked, tents up, kiosk mode active for walk-up registrations
Sat 8:00 AM
Youth Division Pool Play
Grades K-3, 4-6, 7-8 across 12 fields, 305 kids on 23 school teams
Sat 1:00 PM
Youth Bracket Rounds
Pool winners advance to single-elimination, umpires dispatched via Schotty
Sat 5:00 PM
Youth Championships
Three division titles decided on Field 1, 2, 3
Sat 7:00 PM
Adult Division Check-in
105 adult teams confirmed, waivers verified, rosters locked
Sun 8:30 AM
Adult Pool Play
16 pools across 12 fields, round-robin into bracket seeding
Sun 12:30 PM
Adult Bracket Play
Single-elimination rounds, live scoring on every field
Sun 5:00 PM
Championship Sunday
Final games on Field 1, Hall of Champions updated in real time
What is inside
Registration & payments
Teams register online, pay entry fees through Square, and sign digital waivers. No spreadsheets, no paper, no chase. The system handles team rosters, division placement, and waitlists when divisions fill up.
Brackets & live scoring
Pool play seeds into single-elimination brackets, with live scores updating on the web throughout tournament day. Every team is guaranteed four-plus games, and the bracket page is where the weekend watches itself happen.
Sponsorship platform
Tiered sponsor packages (Grand Salami, Home Run, Triple) with live availability, a public sponsor wall, and automated tracking of 67 partner businesses across six years of the tournament.
Scorekeeper app
A mobile-first scoring app that umpires run from their phones at the field. Offline-capable with local save and auto-sync, one-handed controls, undo and score correction, game lineups, and a practice mode so volunteers can rehearse before game day.
Umpire dispatch & volunteer management
A dispatch board that pushes game assignments to umpire phones with email-link auth, a My Games portal, bulk assignment grid, and shift claiming for four volunteer roles: umpire, scorekeeper, field marshal, and event support.
Central command booth
A real-time admin booth that dispatches games to umpires, confirms scores through a Pending Review workflow, resolves flagged rulings, monitors the full tournament across 12 fields, and runs check-in with a kiosk mode.
Admin dashboard & API
A full admin panel with 200+ API endpoints covering bracket operations, sponsor CRM, email marketing with templates and AI assist, content management, scheduling, financial tracking, user management, audit logs, API keys, and a data-quality engine.
Email & communications
A built-in email platform with template designer, AI-assisted drafting, audience segmentation, bulk sends, Gmail integration for inbox triage, captain outreach, abandoned-cart recovery, and drip campaigns, all tracked with event analytics.
Sponsor CRM
A sponsor management system with company discovery, auto-logo fetching, tiered packages, a kanban board, lapsed-sponsor tracking, impression analytics, and bulk logo management across 67 partner businesses.
Schotty (Telegram bot)
A Telegram bot that pipes real-time tournament updates into the volunteer working group chat. Score confirmations, game dispatches, bracket updates, and incident alerts, plus registration status, outstanding waivers, bounced emails, and failed logins, all land in one thread so the crew can troubleshoot and address issues as they happen instead of refreshing a dashboard.
By the Numbers
A data dashboard page that visualizes six years of growth: 310 all-time team entries, 700 games on record, 305 kids on 2026 youth rosters across 23 schools and 13 ZIP codes. Built for press, partners, and sponsor pitches.
Hall of Champions
A permanent record of every division champion since 2021, with team names, years, and division breakdowns. Seventeen champions crowned and counting.
Component registry
17 of the 20 components in this site's public registry were extracted from the Schott Classic platform: tournament brackets, pool standings, a scorekeeper app shell, count display, base diamond, play-by-play feed, scoreboard, field schedule, roster check-in, and more. Installable via shadcn in any React project.