Files
ToBePlayed-Frontend/README.md
T

187 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, its trend of the last 90
days ("this month: +N added, -N off the pile") 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.
- **Your stats** (`/stats`): totals and statuses; the pile in hours, in weeks at your pace of the last 90 days and in
years of full-time work, and its oldest unplayed game; most played and most loyal games; top genres and moods;
trips to the moon, the longest gaming day, games started but under one hour; finished games per year.
- **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. Games already in ToBePlayed come first.
- **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.
- **Confetti**: opening a finished game plays a small confetti burst, a finished game with every achievement a big
one; setting the status to finished plays it at once. Logo and accent colors, a canvas of our own (no library),
nothing with "reduce motion".
- **Finished?**: Core marks a game as finished by itself when the ending achievement is unlocked and the status was
never set by hand. Otherwise the game page asks: "You unlocked the ending, finished?" or "You played longer than
most players need, finished?", with "Mark as finished" and "Not yet".
- **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.
The user picks in the settings which stat groups of "Your stats" it shows (none by default), and designs its
look: accent, corners, banner and background from own game art ("Profile art" on a game's page), and which
sections it shows in which order. Visitors see the owner's look on that page only; light or dark mode stays theirs.
- **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.
- **Admin** (PLATFORM_ADMIN only, `/admin/users`): search the users, block, unblock and delete them (the email is
typed to confirm a delete). "View as" in the menu switches the own session to User or Auditor and back; a badge
shows the lower role. Auth checks every right on the server.
- **Dashboard hints**: news from the admin at the bottom of the dashboard, one at a time. A "Dismiss" hint closes
after 30 s or with X and comes back in the next browser session unless "Do not show again" is ticked; an
"Accept" hint stays until "OK". Admins write, preview and end hints on `/admin/hints` (Auth stores them).
- **Correct game data** (AUDITOR or higher): "Correct data" on the game detail page changes name, header image,
speedrun link, moods, endless flag and genres for every user; each field goes back to the source on its own.
"Merge duplicate" joins two entries of the same game (e.g. the Steam game and the same game added by hand) for
every user.
- **Delete my account** in the settings: a dialog asks for the own email and the password. Auth blocks the
account at once and removes it after 30 days; the page ends the session and goes to the landing page.
- **Build version** in the settings: commit and build time of Frontend, Auth and Core, each hash links to its
commit. CI passes them as Docker build args; a local build shows `dev`.
- **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<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`. `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) and the public `GET /api/version` in the settings 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
### Local development without keys (profile `local`)
The whole stack runs on your machine with no API keys and no calls to Steam, Twitch/IGDB or speedrun.com. Needs
Java 25, Node 24 and Docker. Clone `ToBePlayed-Auth`, `ToBePlayed-Core` and `ToBePlayed-Frontend` next to each
other, then start them in this order (Core also starts without Auth and finds it later):
1. **Auth**: in `ToBePlayed-Auth`, `./mvnw spring-boot:run -Dspring-boot.run.profiles=local`
(IDE: active profile `local`). Port 8080.
2. **Core**: in `ToBePlayed-Core`, `./mvnw spring-boot:run -Dspring-boot.run.profiles=local`. Port 8090.
3. **Frontend**: in `ToBePlayed-Frontend`, `npm install`, then `npm run dev`, and open `http://localhost:5173`
(`localhost`, not `127.0.0.1`, so the browser sends the login cookies to every local port). The development
values must be `VITE_API_URL=http://localhost:8080` and `VITE_CORE_API_URL=http://localhost:8090`.
4. **Log in** with `dev@localhost` / `password123`. On the dashboard, start a Steam sync: the library fills with 8
games, achievements, time to beat, moods, a wishlist and one speedrun game from recorded answers.
The dev user is `PLATFORM_ADMIN`, so user, auditor and admin pages can all be tried ("View as" in the menu
switches down). What the profile does in Auth and Core: their readmes, "Run locally".
### Against other services
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.