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²

Schott Classic wiffle ballThe Schott Classic wiffle ball icon shown as an animated blueprint drawing.
Schott ClassicVector study

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

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.
11:23
Live/Field 1
Game 24 / Synced
▼ 4of 5
Whiffleblowers
3
Bat Attitudes
At bat
4
Ghost runners
S

Schotty

Tournament Operations Bot

Score confirmed

9:42 AM

Field 3: Mud Dogs 12, Bombers 5 (Final)

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

Next.jsTypeScriptTailwind CSSReact

Platform

ReplitSquare APINeon Postgres

Data

PostgreSQLDrizzle ORM19 tablesDatabase views

Operations

200+ API endpointsCron jobsSchotty botAdmin dashboard
One app, one database, one deploy
Pool A
Rank#TeamW-LRDRS
1Backyard Bombers3-0+1521
2Scuff City2-1+415
3Mercy Rule1-2-59
4The Ringers0-3-144
Pool B
Rank#TeamW-L-TRDRS
1Bat Attitudes2-0-1+917
2The Whiffleblowers2-1-0+1019
3Dad Bod Dynasty1-1-1012
4Fully Loaded0-3-0-195

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:

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.

Round of 16
1Backyard Bombers8
16The Ringers1
Sat 9:00 AMField 1
8Caught Looking3
9Basement Legends4
Sat 9:00 AMField 2
5Scuff City6
12Rip City2
Sat 9:00 AMField 3
4Plastic Fantastic4
13Dad Bod Dynasty5
Sat 9:00 AMField 4
6Pitch Please7
11The Big Sticks5
Sat 10:00 AMField 1
3The Whiffleblowers9
14Off the Garage3
Sat 10:00 AMField 2
7Yellow Bat Society5
10Fully Loaded4
Sat 10:00 AMField 3
2Bat Attitudes6
15Mercy Rule0
Sat 10:00 AMField 4
Quarterfinals
1Backyard Bombers5
9Basement Legends2
Sat 11:30 AMField 1
5Scuff City3
13Dad Bod Dynasty4
Sat 11:30 AMField 2
6Pitch Please2
3The Whiffleblowers6
Sat 11:30 AMField 3
7Yellow Bat Society4
2Bat Attitudes7
Sat 11:30 AMField 4
Semifinals
1Backyard Bombers7
13Dad Bod Dynasty3
Sat 1:30 PMField 1
3The Whiffleblowers4
2Bat Attitudes5
Sat 1:30 PMField 2
Champion
2Bat Attitudes
Round of 16
1Backyard Bombers8
16The Ringers1
Sat 9:00 AMField 1
8Caught Looking3
9Basement Legends4
Sat 9:00 AMField 2
5Scuff City6
12Rip City2
Sat 9:00 AMField 3
4Plastic Fantastic4
13Dad Bod Dynasty5
Sat 9:00 AMField 4
6Pitch Please7
11The Big Sticks5
Sat 10:00 AMField 1
3The Whiffleblowers9
14Off the Garage3
Sat 10:00 AMField 2
7Yellow Bat Society5
10Fully Loaded4
Sat 10:00 AMField 3
2Bat Attitudes6
15Mercy Rule0
Sat 10:00 AMField 4
Quarterfinals
1Backyard Bombers5
9Basement Legends2
Sat 11:30 AMField 1
5Scuff City3
13Dad Bod Dynasty4
Sat 11:30 AMField 2
6Pitch Please2
3The Whiffleblowers6
Sat 11:30 AMField 3
7Yellow Bat Society4
2Bat Attitudes7
Sat 11:30 AMField 4
Semifinals
1Backyard Bombers7
13Dad Bod Dynasty3
Sat 1:30 PMField 1
3The Whiffleblowers4
2Bat Attitudes5
Sat 1:30 PMField 2
Champion
2Bat Attitudes

schedulePlacement="between" for tournament day: one game live, one upcoming, final TBD

Semifinals
1Backyard Bombers4
LiveField 1
13Dad Bod Dynasty2
3The Whiffleblowers
Sat 1:30 PMField 2
2Bat Attitudes
Final
TBD
Sat 3:30 PMField 1
TBD
Semifinals
1Backyard Bombers4
LiveField 1
13Dad Bod Dynasty2
3The Whiffleblowers
Sat 1:30 PMField 2
2Bat Attitudes
Final
TBD
Sat 3:30 PMField 1
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

  1. Fri 5:00 PM

    Setup & Check-in

    Fields marked, tents up, kiosk mode active for walk-up registrations

  2. Sat 8:00 AM

    Youth Division Pool Play

    Grades K-3, 4-6, 7-8 across 12 fields, 305 kids on 23 school teams

  3. Sat 1:00 PM

    Youth Bracket Rounds

    Pool winners advance to single-elimination, umpires dispatched via Schotty

  4. Sat 5:00 PM

    Youth Championships

    Three division titles decided on Field 1, 2, 3

  5. Sat 7:00 PM

    Adult Division Check-in

    105 adult teams confirmed, waivers verified, rosters locked

  6. Sun 8:30 AM

    Adult Pool Play

    16 pools across 12 fields, round-robin into bracket seeding

  7. Sun 12:30 PM

    Adult Bracket Play

    Single-elimination rounds, live scoring on every field

  8. Sun 5:00 PM

    Championship Sunday

    Final games on Field 1, Hall of Champions updated in real time

Tap a team to pick: 0/7 games picked
Quarterfinals
1Backyard Bombers
8Caught Looking
4Plastic Fantastic
5Scuff City
3The Whiffleblowers
6Pitch Please
2Bat Attitudes
7Yellow Bat Society
Semifinals
TBD
TBD
TBD
TBD
Final
TBD
TBD
Quarterfinals
1Backyard Bombers
8Caught Looking
4Plastic Fantastic
5Scuff City
3The Whiffleblowers
6Pitch Please
2Bat Attitudes
7Yellow Bat Society
Semifinals
TBD
TBD
TBD
TBD
Final
TBD
TBD

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.

Related writing

Tags

Repository activity

TypeScript 2918 commits Updated 1mo ago

More projects

Vote+1