A full-stack fixture management system for Eton College Field Game — covering match scheduling, live results, league standings, tournament brackets, and live referee scoring across web and iOS.
Live at field-game.co.uk
- Fixtures — Browse upcoming matches with filtering by house, school team, umpire, and date range
- Results — Paginated, filterable match results
- League Standings — Live league table with W/D/L, points, and goal difference
- Tournament Brackets — Interactive knockout and round-robin tournament views
- Field Game Laws — Searchable rules reference
- Weather — Forecast display for match days (via Open-Meteo at Eton College coordinates)
- Calendar Export — Download fixtures as
.icsor text - Offline Support — LocalStorage caching with stale-while-revalidate strategy
- "My House" Personalisation — Save your house and school team for quick filtering
- Dark Mode — Full light/dark theme support
- Score Entry — House captains submit match scores; admins can manage all matches
- Fixture Admin — Edit/delete fixtures with override tracking and audit logs
- User Management — Role-based access (admin, captain, viewer)
- Housemaster Admin — Map housemaster initials to formal house names
- Native SwiftUI — All web features in a native iOS experience
- Referee Module — Full live match scoring with epoch-based timer, event sourcing, golden point, substitution tracking, and crash recovery
- Offline Caching — Core Data persistence with network fallback
- Haptics & Audio — Whistle sounds and haptic feedback during refereeing
- Email Import — Gmail monitor fetches fixture emails, parses Word document attachments, and imports matches automatically
- Garmin Watch API — 3-character match codes for Garmin Connect IQ integration
| Layer | Technology |
|---|---|
| Web Frontend | Next.js 15, React 19, TypeScript, Tailwind CSS |
| Database | Supabase (PostgreSQL) with Row Level Security |
| Auth | Supabase Auth with role-based access |
| iOS App | SwiftUI, Combine, MVVM, Swift Package Manager |
| iOS Backend | Supabase Swift SDK |
| Email Parser | Python 3, Gmail API, python-docx |
| Hosting | Vercel (web), Xcode Cloud (iOS) |
| CI/CD | GitHub Actions (lint, test, build) |
field-game-fixtures/
├── src/ # Next.js web app
│ ├── app/ # Pages (fixtures, results, standings, tournaments, admin)
│ │ ├── page.tsx # Main fixtures page
│ │ ├── results/ # Match results
│ │ ├── standings/ # League table
│ │ ├── tournament/ # Tournament brackets
│ │ ├── laws/ # Field Game laws
│ │ ├── admin/ # Admin pages (fixtures, users, housemasters)
│ │ ├── resultsentry/ # Captain score entry
│ │ └── api/ # API routes (Garmin watch, score submission)
│ ├── components/ # React components (MatchCard, FilterBar, etc.)
│ ├── lib/ # Shared utilities
│ │ ├── houses.ts # House name ↔ initials mapping
│ │ ├── filters.ts # Match filtering logic
│ │ ├── competitions.ts # Competition types and colours
│ │ ├── cache.ts # LocalStorage caching with TTL
│ │ └── supabase/ # Supabase client configuration
│ └── types/ # TypeScript type definitions
├── ios-app/ # Native iOS app (SwiftUI)
│ └── FieldGameFixtures/
│ ├── App/ # Entry point and tab navigation
│ ├── Models/ # Data models
│ ├── ViewModels/ # MVVM view models
│ │ └── Referee/ # Live match state machine
│ ├── Views/ # SwiftUI views
│ │ ├── Fixtures/ # Match browsing
│ │ ├── Tournaments/ # Bracket views
│ │ ├── Referee/ # Live scoring UI
│ │ └── Laws/ # Rules reference
│ ├── Services/ # Supabase, caching, crash recovery
│ └── Utilities/ # House mapper, extensions
├── parser/
│ └── parse_fixtures.py # Word document → structured match data
├── scripts/
│ └── gmail_monitor.py # Email monitoring and import
├── supabase/
│ └── migrations/ # 22 SQL migration files
├── docs/ # Documentation and specs
│ ├── DATABASE_SCHEMA.md
│ ├── CODEBASE_DOCUMENTATION.md
│ └── referee/ # Referee module build plan
└── public/
└── images/houses/ # House colour images
- Node.js 20+
- Python 3.9+ (for email import)
- A Supabase project
Run the migrations in order in the Supabase SQL Editor:
# Or use the Supabase CLI
supabase db pushCreate .env.local:
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
npm install
npm run devVisit localhost:3000.
pip install -r requirements.txt
# First-time Gmail authorisation
cd scripts
python gmail_monitor.py --testThis opens a browser for OAuth consent, then checks for fixture emails without writing to the database.
Automate with cron:
# Check every 15 minutes
*/15 * * * * cd /path/to/field-game-fixtures && python scripts/gmail_monitor.py >> logs/gmail.log 2>&1Open ios-app/FieldGameFixtures.xcodeproj in Xcode. The Supabase Swift SDK is fetched via Swift Package Manager. Configure Supabase.xcconfig with your project URL and anon key.
The system uses PostgreSQL via Supabase with these core tables:
| Table | Purpose |
|---|---|
houses |
House definitions (formal names as primary key) |
housemasters |
Maps housemaster initials → formal house names |
matches |
All fixtures and results (uses initials for team IDs) |
user_profiles |
Users with roles (admin/captain/viewer) |
score_audit_log |
Audit trail for score entries |
tournament_definitions |
Tournament bracket structures (JSONB) |
tournament_results |
Tournament match outcomes |
Key views: upcoming_matches, recent_results, league_standings
See docs/DATABASE_SCHEMA.md for full schema documentation.
Houses are referenced in two formats — a critical detail for anyone working on the codebase:
| Format | Example | Used In |
|---|---|---|
| Formal name | "Angelo's", "Baldwin's Bec" | houses table, UI display |
| Housemaster initials | "JDM", "SPH" | matches table team IDs |
All 25 houses have a mapping defined in src/lib/houses.ts. When filtering matches, queries must search for both formats. See CLAUDE.md for the complete mapping and filtering patterns.
npm run test # Run tests
npm run test:ci # Run with coverage
npm run lint # ESLintTests cover house mapping, competition colours, date utilities, and tournament logic.
- Web: Deploy to Vercel with
vercel deployor connect the GitHub repo - iOS: Xcode Cloud is configured for automated builds
- CI: GitHub Actions runs lint, test, and build on every push
- Never commit
credentials.json,token.pickle, or.env.localto git - The
.gitignoreexcludes these automatically - Admin pages use Supabase Auth with role-based RLS policies
- Score submission API uses match codes for the Garmin watch integration
Private project for Eton College Field Game.