## Summary
- Tier upgrade/downgrade in the Store. The Subscriptions tab now shows
all tiers including the user's current one. Other tiers swap "Subscribe"
→ "Switch" when the user already has a sub, and clicking them calls the
new `POST /subscriptions/@me/change-tier` endpoint with a
direction-aware confirm (upgrade charges prorated diff now, downgrade
gives account credit).
- Owned-tier card renders a **Current Plan** badge in place of the
purchase button. Resolution logic in `resolveCosmetics` now reads
`userMeResponse.player.subscription.tier` (with flare fallback) and
marks that tier as `owned`.
- AccountModal's `<subscription-panel>` reworked into a proper
two-column layout:
- **Left**: tier name, `$X.XX/mo` price, description, daily Pu/Caps
amounts.
- **Right**: status badge (Active / Renews date / Cancels date),
`[Manage] [Change Tier]` button row, `[Cancel]` centered underneath.
When `cancelAtPeriodEnd === true`, the row collapses to a single
`[Reactivate]` button (opens the Stripe portal).
- New `<o-button size="xs">` variant (`py-2 px-3 text-xs`) for the
compact panel buttons.
- Store dollar-purchase price label now supports an optional suffix
(`/mo` for subs only) via a `priceSuffix` prop plumbed through
`CosmeticContainer` → `PurchaseButton`.
- `Api.ts` gains `changeSubscriptionTier(tierName)` with the same
401-handling pattern as the existing subscription helpers.
<img width="1114" height="728" alt="Screenshot 2026-05-14 at 7 09 20 PM"
src="https://github.com/user-attachments/assets/688f83d5-4010-4580-9214-6885af8ec98e"
/>
<img width="1038" height="276" alt="Screenshot 2026-05-14 at 7 09 33 PM"
src="https://github.com/user-attachments/assets/458197f5-a0d4-4c32-bc55-31e5679629b5"
/>
<img width="887" height="286" alt="Screenshot 2026-05-14 at 7 09 55 PM"
src="https://github.com/user-attachments/assets/8149ed82-89cc-4bbe-83de-3614f886b331"
/>
## Discord
evan
## Summary
- Add a **Subscriptions** tab to the Store. Each tier renders as a
`<cosmetic-button>` with description, daily Pu/Caps amounts, and a
Stripe checkout button driven by the existing `createCheckoutSession`
flow.
- Show the player's active subscription in the **Account modal** via a
new `<subscription-panel>` Lit component (status badge, period-end /
cancel-at-period-end, daily currency breakdown).
- **Manage** button opens the Stripe billing portal in a new tab (`POST
/subscriptions/@me/portal`).
- **Cancel** button (hidden once `cancelAtPeriodEnd === true`) calls
`POST /subscriptions/@me/cancel` after a `confirm()` prompt, then
invalidates the userMe cache and refetches.
- Block re-purchase: clicking Subscribe when the user already has a
`subscription:*` flare alerts "Already subscribed" before opening
checkout (upgrade/downgrade flows are out of scope for now).
- Schema additions:
- `CosmeticsSchema.subscriptions: Record<string, SubscriptionSchema>`
(optional) in `src/core/CosmeticSchemas.ts`.
- `UserMeResponse.player.subscription: { tier, status, currentPeriodEnd,
cancelAtPeriodEnd } | null` in `src/core/ApiSchemas.ts`.
- Translations: new `store.*` and `account_modal.sub_*` keys in
`resources/lang/en.json` (English only — Crowdin handles the rest).
-
<img width="942" height="313" alt="Screenshot 2026-05-14 at 1 13 05 PM"
src="https://github.com/user-attachments/assets/3d28df13-9e03-49f0-bee8-a25f9ad0c420"
/>
<img width="545" height="439" alt="Screenshot 2026-05-14 at 1 13 32 PM"
src="https://github.com/user-attachments/assets/b413b275-d6f2-40dc-9230-d68cd11fb07a"
/>
## Discord
evanpelle
## Description:
Continuation from #3276
Adds the complete client-side clan UI as a Lit web component
(`<clan-modal>`), a typed API client with Zod-validated responses,
shared response schemas, and a reusable `<confirm-dialog>` component.
### New: `ClanModal.ts`
| View | What it does |
|------|-------------|
| **My Clans** | Lists joined clans + pending join requests (built from
`/users/@me`, no extra fetches) |
| **Browse** | Search by tag (min 3 chars), paginated results,
configurable per-page (10/25/50) |
| **Clan Detail** | Stats, paginated + searchable member list, role
badges, join/leave/request actions |
| **Manage** | Edit name (max 35 chars) + description, toggle
open/invite-only, disband |
| **Transfer** | Leadership transfer with member selector + confirmation
|
| **Requests** | Approve/deny join requests (leader/officer) |
| **Bans** | View and unban (leader/officer) |
| **My Requests** | View and withdraw outgoing requests |
### New: `ConfirmDialog.ts`
Reusable `<confirm-dialog>` Lit component — replaces native
`confirm()`/`prompt()` which are blocked or broken on mobile and
CrazyGames iframes. Supports danger/warning variants and an optional
textarea (used for ban reasons). Fires `confirm`/`cancel` events.
### New: `ClanApi.ts`
Typed API client covering all clan endpoints. Every response is
Zod-validated. Auth header is always last in the spread (can't be
overridden by callers). Unknown server error messages always fall back
to a generic client-side string — never displayed verbatim.
### New: `ClanApiSchemas.ts` (in `src/core/`)
Shared Zod schemas for clan API responses with max-length constraints on
`name` (35) and `description` (200). Lives in `core/` so it can be
consumed by both client code and the leaderboard table.
### Modified: `ApiSchemas.ts`
- Added `clans` and `clanRequests` arrays to `UserMeResponseSchema`
- Moved clan leaderboard schemas out to `ClanApiSchemas.ts`
- Renamed `LeaderboardClanTagSchema` → `RequiredClanTagSchema`
### Modified: `Api.ts`
- Added `invalidateUserMe()` to bust the cached `/users/me` response
after mutations
- Removed `fetchClanLeaderboard` (moved to `ClanApi.ts`)
### Tests
- `ClanModal.test.ts` — rendering, view navigation, user actions
- `ClanApiQueries.test.ts` — fetch functions, error handling, pagination
- `ClanApiMutations.test.ts` — join, leave, kick, ban, promote,
transfer, etc.
- `ClanApiBans.test.ts` — ban/unban calls and error paths
- `ClanApiSchemas.test.ts` — Zod schema validation edge cases
- `LeaderboardModal.test.ts` — updated imports
## Notable design decisions
- **Not-logged-in state** — shows "Sign in to join clans" instead of
false "no clans" empty state
- **Rate limit feedback** — reads `Retry-After` header and surfaces wait
time to the user
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
w.o.n
---------
Co-authored-by: evanpelle <evanpelle@gmail.com>
## Description:
Caps & Plutonium can be used to purchase different cosmetics.
* The cosmetic button can display pluto/caps/dollars
* Create a "purchaseCosmetic" helper function that handles purchase
logic
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
Resolves#2998
## Description:
Adds a news box to the lobby homepage that advertises upcoming clan
tournaments, weekly tournaments, and new player tutorials. The component
sits above the username input and cycles through items automatically.
<img width="1138" height="591" alt="screenshot-2026-03-31_00-48-33"
src="https://github.com/user-attachments/assets/4b79287d-6aca-4c81-9bfe-36aad043f381"
/>
<img width="1107" height="595" alt="screenshot-2026-03-31_00-48-24"
src="https://github.com/user-attachments/assets/598e6b8b-e0f2-4864-a5fb-a00c0cc98f37"
/>
<img width="1367" height="599" alt="screenshot-2026-03-31_00-48-04"
src="https://github.com/user-attachments/assets/14f74e70-9dc0-4d67-af6e-c4708e539490"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
deathllotus
---------
Co-authored-by: Evan <evanpelle@gmail.com>
## Description:
Add support for purchasable/gated flags.
* Create a new "Store" modal that renders both skins & flags
* move all store related logic out of TerritoryPatternsModal
* use nation:code for existing nation flags & flag:key for gated flags
* check if user has the appropriate flags before purchasing
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
Skin trials has been a failure, very low fill rate and cause a major
drop in sales.
reverts
https://github.com/openfrontio/OpenFrontIO/commit/97d0a05d58e926e3de4ba46d8dd14a04d60d6698
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
Added rewarded video ads for skin trials via Playwire's
manuallyCreateRewardUi API. Users can now click "Try me" to watch a
video ad and receive a temporary skin trial. Upon completion a temporary
flare is granted to the player so they have ~5 minutes to use the skin.
added getPlayerCosmeticsRefs and getPlayerCosmetics to Cosmetics.ts to
centralize cosmetic retrieval & validation.
<img width="801" height="534" alt="Screenshot 2026-02-10 at 7 58 14 PM"
src="https://github.com/user-attachments/assets/51cc378c-2feb-4692-8cf2-20ee54cea3b8"
/>
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves#3126
## Description:
update & move reached_limit check
## Please complete the following:
- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
wraith4081
---------
Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)
@wraith4081 's pr
updates the stats modal to show both 1v1 and clan stats
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
regression is found:
w.o.n
---------
Co-authored-by: Wraith <54374743+wraith4081@users.noreply.github.com>
Co-authored-by: iamlewis <lewismmmm@gmail.com>
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #(issue number)
## Description:
@wraith4081 's pr
updates the stats modal to show both 1v1 and clan stats
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
w.o.n
---------
Co-authored-by: Wraith <54374743+wraith4081@users.noreply.github.com>
Co-authored-by: iamlewis <lewismmmm@gmail.com>
## Description:
There were several issues with the matchmaking modal:
1. It was defined twice (once before login, and once after login), so
players would sometimes join the matchmaking queue twice.
2. When clicking away from the modal (not clicking the back button), the
"onClose" callback was not triggered. So if a person closed & reopened
the modal, they would join twice'
3. Cache the userMe response so it can be called multiple times
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan
## Description:
The previous login system used long lived jwts which could be stolen by
XSS. The current system uses long lived refresh tokens that are stored
as http-only cookies. Then the client calls /refresh to get a short
lived jwt using the refresh token. The jwt is stored in memory only so
it's discarded on page close. This way a XSS can only steal the
short-lived jwt.
It also updates how accounts work: players get an account automatically
when they join the webpage. They can see their stats even if not logged
in. If a player wants to keep their account, they can tie it to their
Discord or email, allowing them to log in if cookies are lost.
## Please complete the following:
- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced
## Please put your Discord username so you can be contacted if a bug or
regression is found:
evan