A web app for tracking board game results among friends and family. Players form groups, log games, and results only become "official" once every registered participant confirms the ranking. Built with a Catan-themed UI, complete with ambient music and a hex-tile background.
Tech stack: React (Vite) · Django + DRF · JWT auth · PostgreSQL (or SQLite for local dev)
- Project Structure
- Prerequisites
- Backend Setup
- Frontend Setup
- Running Locally
- Using the App
- Deploying to the Cloud (Free)
- Adding a New Game
catania/
├── backend/ # Django project
│ ├── config/ # Settings, URLs, WSGI
│ ├── core/ # Users, Groups, Memberships, Invites
│ ├── games/ # GameType, GameSession, Participants
│ ├── manage.py
│ ├── seed.py # Seeds the Catan game type
│ └── requirements.txt
├── frontend/ # React + Vite SPA
│ ├── src/
│ │ ├── api/ # REST client wrappers
│ │ ├── auth/ # Login, Register, AuthContext
│ │ ├── groups/ # Groups list + detail pages
│ │ ├── games/ # Session submit/edit/confirm pages
│ │ ├── theming/ # Catan theme + music player
│ │ └── layout/ # Navbar + page shell
│ └── vite.config.js
└── README.md
| Tool | Version | Notes |
|---|---|---|
| Python | 3.10+ | 3.12 tested |
| pip | latest | comes with Python |
| Node.js | 18–21 | v21 tested (Node 22+ requires Vite 6+) |
| npm | 8+ | comes with Node |
| PostgreSQL | 14+ | Optional — SQLite used automatically if no DATABASE_URL is set |
cd catania/backend# Windows (PowerShell)
python -m venv venv
.\venv\Scripts\Activate.ps1
# macOS / Linux
python -m venv venv
source venv/bin/activatepip install -r requirements.txtCopy the example file and edit it:
copy .env.example .env # Windows
# cp .env.example .env # Mac/LinuxOpen .env and set:
SECRET_KEY=some-long-random-string-here
DEBUG=True
# Leave DATABASE_URL blank to use SQLite (easiest for local dev):
# DATABASE_URL=
# For PostgreSQL (Supabase, Neon, or local):
# DATABASE_URL=postgresql://user:password@host:5432/dbname
ALLOWED_HOSTS=localhost,127.0.0.1
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173Quick start with SQLite: Just leave
DATABASE_URLcommented out. Django automatically createsdb.sqlite3.
python manage.py migratepython manage.py seed # OR:
python seed.pypython manage.py createsuperuserThen visit http://localhost:8000/admin/ to manage data directly.
cd catania/frontendnpm installThe frontend is pre-configured to call http://localhost:8000/api via the .env file. If your backend runs on a different port, edit frontend/.env:
VITE_API_URL=http://localhost:8000/apiFrom the project root, start both servers with one command:
.\start.ps1Or double-click start.bat. That launches the Django API and the Vite frontend together. Press Ctrl+C to stop both.
Then open http://localhost:5173. The API is at http://localhost:8000/api/.
To start them separately instead:
.\start-backend.ps1
.\start-frontend.ps1Go to http://localhost:5173/register and create your account. Repeat this in different browser profiles (or incognito windows) for each player you want to test with.
After logging in, click + New Group. Give your group a name (e.g. "Friday Night Catan"). You are automatically the admin of this group.
Go to your group → Members tab → click Generate Invite Link. Share the link with your friends. They must have an account and click the link to join.
Alternatively, search for a player by username and add them directly.
Inside a group, click + Log Game:
- Choose Catan as the game type
- Set the date the game was played
- Add each player, assign ranks (1 = winner) and optionally exact scores
- Add guests by name for players without accounts
- Click Submit Game
Each registered participant (except you, the submitter) receives a pending confirmation. They see a notification badge on the Inbox tab in the navbar. They can:
- ✅ Confirm — they agree with the ranking
- ❌ Reject — they dispute it (flips session to "Disputed")
A session becomes Confirmed once all registered players confirm. Only confirmed sessions count toward the leaderboard.
If someone rejects a result, the session turns Disputed. The original submitter can click ✏️ Edit on the session to correct the rankings and resubmit. All other participants are prompted to confirm again.
Inside a group, click the Leaderboard tab to see wins, losses, and win percentages for all members (computed only from confirmed games).
If a guest player later creates an account and joins the group, go to the Members tab → Claim Guest section. Enter the guest name that was used in previous sessions. Their historical results will be linked to the real account.
Click the 🔇 button in the top-right navbar to toggle the ambient music. A volume slider appears while music is playing. Music only starts on user interaction (respects browser autoplay rules).
- Push your
backend/folder to a GitHub repo. - Create a new Web Service on Render.
- Set Build Command:
pip install -r requirements.txt && python manage.py migrate && python seed.py - Set Start Command:
gunicorn config.wsgi:application - Add environment variables in the Render dashboard (copy from
.env.example, setDEBUG=False). - Add a PostgreSQL add-on (or connect a free Neon / Supabase DB via
DATABASE_URL).
Install gunicorn first:
pip install gunicorn
pip freeze > requirements.txt- Push
frontend/to GitHub. - Import the repo in Vercel or Netlify.
- Set the build command to
npm run buildand output directory todist. - Add environment variable:
VITE_API_URL=https://your-backend.onrender.com/api
Both offer always-on free PostgreSQL:
- Neon: Copy the connection string → paste as
DATABASE_URLin backend env vars. - Supabase: Project Settings → Database → Connection String (URI mode).
The backend is fully game-agnostic. To add, say, Ticket to Ride:
-
Insert a GameType row (one-time, via Django admin or a seed script):
GameType.objects.create( slug='ticket-to-ride', display_name='Ticket to Ride', min_players=2, max_players=5, rank_direction='higher_is_better', )
-
Add a frontend theme under
frontend/src/theming/ticket-to-ride/theme.jswith new color tokens. -
That's it — no migrations, no new tables, no backend logic changes.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register/ |
Register new user |
| POST | /api/auth/login/ |
Login → returns JWT tokens |
| POST | /api/auth/refresh/ |
Refresh access token |
| GET | /api/groups/ |
List my groups |
| POST | /api/groups/ |
Create group |
| GET | /api/groups/{id}/ |
Group detail + members |
| POST | /api/groups/{id}/invites/ |
Generate invite link (admin) |
| POST | /api/groups/join/{code}/ |
Join via invite link |
| GET | /api/groups/{id}/stats/ |
Leaderboard stats |
| GET | /api/game-types/ |
List supported games |
| GET | /api/groups/{id}/sessions/ |
List sessions in group |
| POST | /api/groups/{id}/sessions/ |
Submit new game |
| GET | /api/sessions/{id}/ |
Session detail |
| POST | /api/sessions/{id}/confirm/ |
Confirm result |
| POST | /api/sessions/{id}/reject/ |
Reject result |
| PATCH | /api/sessions/{id}/ |
Edit disputed session |
| GET | /api/confirmations/ |
My pending confirmations |
| Problem | Fix |
|---|---|
CORS errors in browser |
Make sure CORS_ALLOWED_ORIGINS in .env includes the frontend URL |
401 Unauthorized on all requests |
Check that access_token is in localStorage; try logging out and back in |
| Frontend shows blank page | Check the browser console; ensure VITE_API_URL points to the running backend |
No module named 'psycopg2' |
You set DATABASE_URL but don't have Postgres running locally — either remove it to use SQLite, or install PostgreSQL |
| Music doesn't play | Click anywhere on the page first (browser autoplay policy requires a user gesture) |