# 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. ![Library](docs/screenshots/library-desktop-dark.png) | Game detail | Public profile | Mobile | |---|---|---| | ![Game detail](docs/screenshots/detail-desktop-dark.png) | ![Public profile](docs/screenshots/profile-desktop-dark.png) | ![Mobile library](docs/screenshots/library-mobile-dark.png) | 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/`, 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 as `imageUrl`. - 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. `apiFetch` renews 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 ```mermaid flowchart LR Browser["Browser"] --> Proxy["Traefik (TLS)"] Proxy --> Frontend["ToBePlayed-Frontend
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`. `clientFetch` sends the HttpOnly cookies and the `X-TBP-Client` header. `apiFetch` answers a `401` with one `POST /api/auth/refresh` and sends the request again. Parallel requests and other tabs share this refresh (`navigator.locks`). When the second answer is still a `401`, the page calls `useSessionExpired`, which opens the login page. Public pages (landing page, public profile) use plain `fetch`. - `src/utils/brokenImages.js` reports pictures that fail to load (`GameCover`, achievement icons on the game detail page) to Core. - `useLibrarySync` polls Core's `/api/library/sync-status` while a Steam sync runs, then loads the data again. ## Project structure ```text 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. ```bash 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 ```bash 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.