Public Access
6.7 KiB
6.7 KiB
To Be Played – Frontend
Web app of To Be Played, a Steam backlog tracker: import your Steam library, decide what to play, and track what you finished. The UI is in German and English, with light and dark mode.
| Game detail | Public profile | Mobile |
|---|---|---|
![]() |
![]() |
![]() |
To Be Played is split into three repositories:
| Repository | Role |
|---|---|
| ToBePlayed-Auth | Users, login, settings, signs JWTs |
| ToBePlayed-Core | Steam library, backlog, wishlist, stats; verifies JWTs |
| ToBePlayed-Frontend (this one) | React single-page app |
Features
- Dashboard: most played games, games in progress, "Pile of Shame" with backlog hours and a random suggestion, Steam avatar and a banner from the most played game of the last two weeks, and an achievement card: unlocked total, perfect games, average completion and the 5 games closest to 100 %.
- What to play next (
/play-next): three backlog picks for a time budget (up to 5 / 15 / 40 h or any) and a mood (story, co-op, chill, action, scary from IGDB; speedrun from speedrun.com), high priority first, reroll without repeats, and "Start playing" in one click. - Library: status filters, "short games" filter (up to 10 h), search, sorting by playtime, recently seen, newest in library, rating or priority, paging. Each row shows the unlocked achievements.
- Add a game by name for games outside Steam (PlayStation, Switch, Xbox, physical): search IGDB, pick a hit, a platform and optional hours. Works without a Steam ID.
- Game detail: status, priority, 1–5 star rating, notes, achievements, time to beat (story / extras / 100 %), a link to the game's speedrun.com page when it has verified runs, a hint when Steam shows recent playtime for a game still in the backlog, and one to set the status to 100 % when every achievement is unlocked.
- Hide games: a Steam game (e.g. a playtest or a tool) can be hidden on its detail page. It leaves the lists, the dashboard, the stats and the public profile; the library's "Hidden" filter shows it again for unhiding.
- Wishlist with store price and discount.
- Steam account linked in the settings with "Sign in through Steam": the user signs in on Steam and Auth gets the proved Steam ID, never a typed-in one. A Steam ID typed in before shows as "not verified" with a button to verify it; unlinking is possible.
- Public profile at
/u/<display name>, opt-in in the settings, with perfect games and average completion. - Appearance: accent color, corner style, wide layout, page background and banner from your own game art. Saved in Auth, so it follows you to every device.
- Broken pictures: a Steam cover or achievement icon that does not load shows a placeholder and is reported
to Core once per page load (
POST /api/library/broken-images, only with a session). Core repairs it at night; a repaired cover comes back asimageUrl. - German and English, dark mode, mobile layout, session-expired message instead of a silent logout.
- Installable as an app on phone and desktop (web app manifest, no service worker, so no offline mode).
- Session: the tokens are HttpOnly cookies that no script can read.
apiFetchrenews an expired access token once and sends the request again; parallel requests and other tabs share one refresh.
Tech stack
- React 19, React Router 7, Vite 8
- Tailwind CSS 4, Radix UI primitives (shadcn/ui style components), Lucide icons
- react-hook-form + zod for forms, i18next for translations (
public/i18n) - Vitest + React Testing Library + jsdom
- Production image: nginx (Alpine), SPA fallback, long-term caching for hashed assets, Content Security Policy and security headers
- CI: Forgejo Actions (
npm ci,npm test,npm run build, Docker image)
Architecture
flowchart LR
Browser["Browser"] --> Proxy["Traefik (TLS)"]
Proxy --> Frontend["ToBePlayed-Frontend<br/>nginx, static files"]
Proxy --> Auth["ToBePlayed-Auth"]
Proxy --> Core["ToBePlayed-Core"]
Core -->|"JWKS"| Auth
Auth --> Db[("PostgreSQL")]
Core --> Db
Core --> Steam["Steam Web API"]
Core --> Igdb["IGDB API"]
Core -->|"OAuth token"| Twitch["Twitch"]
Core --> Speedrun["speedrun.com API"]
Browser -.->|"game art"| Images["Steam and IGDB image hosts"]
Browser -.->|"Sign in through Steam"| SteamLogin["steamcommunity.com (OpenID)"]
Auth -->|"check_authentication"| SteamLogin
- nginx serves the built app. It has no backend of its own: the browser calls Auth and Core directly
(
VITE_API_URL,VITE_CORE_API_URL). The Content Security Policy allows only these two hosts and the image hosts. - Auth and Core each have their own database. Core verifies Auth's tokens with Auth's public keys (JWKS).
- Calls for the logged-in user go through
src/utils/api.js.clientFetchsends the HttpOnly cookies and theX-TBP-Clientheader.apiFetchanswers a401with onePOST /api/auth/refreshand sends the request again. Parallel requests and other tabs share this refresh (navigator.locks). When the second answer is still a401, the page callsuseSessionExpired, which opens the login page. Public pages (landing page, public profile) use plainfetch. src/utils/brokenImages.jsreports pictures that fail to load (GameCover, achievement icons on the game detail page) to Core.useLibrarySyncpolls Core's/api/library/sync-statuswhile a Steam sync runs, then loads the data again.
Project structure
src/
├── pages/ # One component per route (Dashboard, GameLibrary, GameDetail, ...)
├── components/ # Shared components; ui/ holds the base components
├── hooks/ # Data hooks (library sync, Steam profile, appearance, session expiry)
├── context/ # PreferencesContext: theme, language, appearance
├── utils/ # Validation schemas and helpers
└── test/ # Test setup (i18n mock)
Tests sit next to the code they test (Component.test.jsx).
Run locally
Needs Node 24 and running Auth and Core services.
npm install
npm run dev
| Variable | Purpose |
|---|---|
VITE_API_URL |
Base URL of the Auth service |
VITE_CORE_API_URL |
Base URL of the Core service |
Values for development and production live in .env.development and .env.production. They end up in the
browser bundle, so they must never hold a secret.
Tests and build
npm test
npm run build
Roadmap
See the roadmap in the ToBePlayed-Core readme.
License
All rights reserved. The source is public for reading only.



