Files
OpenFrontIO/tests
Evan bf61f5847b feat(rewards): claimable subscription rewards UI (#4566)
## Summary

Subscription perks (signup bonus + daily soft/hard currency) now land as
**unclaimed rewards** the player must explicitly claim, instead of
crediting balances directly (API side: openfrontio/infra#409). This PR
adds the client UI:

- **`RewardsPanel`** — shared Lit element listing pending rewards
(currency icon, `+amount`, server `note` with translated fallbacks for
known reasons), with per-reward **Claim** and **Claim All**. Both claim
endpoints return post-claim balances, so the wallet updates without
re-fetching `/users/@me`.
- **Account modal** — renders the panel above the subscription panel
(rewards survive subscription lapse, so it doesn't depend on an active
sub).
- **`RewardsModal`** — popup at login when rewards are pending. Reuses
`RewardsPanel`, auto-closes when the last reward is claimed. Only opens
on a clean homepage load (`pathname === "/"`, empty hash) so it never
pops over join links, `#modal=...` deep links, or the Stripe
`#purchase-completed` return — that flow reloads clean, so the signup
bonus popup appears right after purchase.
- **Schemas/API** — `RewardSchema` + `rewards[]` on `/users/@me`
(optional for older API versions), `claimReward` / `claimAllRewards` in
`Api.ts`. Reward `id`/`amount` stay opaque bigint strings; a 404 on
single claim means "already claimed elsewhere" (double-click / second
device) and re-syncs instead of erroring.

## Screenshots

Login popup (also embedded in the account modal):

- Panel: currency icon + amount + note per row, Claim / Claim All
buttons
- Amounts formatted via `BigInt` (can exceed `Number.MAX_SAFE_INTEGER`)

## Test plan

- [x] `npm test` — 161 tests pass, including new coverage for
`RewardSchema`, `/users/@me` with/without `rewards`, and both claim
response shapes
- [x] `tsc --noEmit`, ESLint, Prettier clean
- [x] Verified in the running app (Playwright): panel renders in the
account-modal style; single claim against a stubbed endpoint credits
balances and removes the row; Claim All empties the list and auto-closes
the popup; body scroll restored

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:21:52 -07:00
..
2026-05-22 13:19:22 +01:00
2026-06-19 14:54:09 -07:00
2026-03-17 15:55:47 -07:00