Files
OpenFrontIO/tests
9c14ab04e0 feat(client): account-level custom usernames (base.suffix + premium bare names) (#4644)
## Summary

Client integration for account-level usernames (backend:
openfrontio/infra#434). Players get one canonical account name stored as
a base plus a server-assigned 4-digit suffix (`bob.4821`); subscribers
display the bare base (`bob`) with an exclusive case-insensitive claim
on it.

- **Schemas** (`src/core/ApiSchemas.ts`): new `player` username fields
on `GET /users/@me`, `UsernameStatusSchema`,
`PutUsernameResponseSchema`, and an `isTemporaryUsername()` helper. The
display name is rendered exactly as the server resolves it — the client
never assembles `base.suffix`. Discriminators stay strings (leading
zeros).
- **API** (`src/client/Api.ts`): `updateUsername()` for `PUT
/users/@me/username`, returning a discriminated result covering every
documented failure: `invalid` (400), `profane` (400 +
`USERNAME_PROFANE`), `taken` (both 409 bodies), `cooldown` (429 +
`Retry-After`), `failed`.
- **UI** (`src/client/components/UsernamePanel.ts`, in the Account tab):
set/change form prefilled with the base, live 3–20 / `[a-zA-Z0-9_-]`
validation (`validateAccountUsername`), form locked with the date while
the 30-day cooldown runs, grace-period warning for lapsed claim holders,
and a free-rename notice after a server-side `TEMPORARY####` rename. The
confirm dialog composes warnings by state: 30-day lock always,
case-only-change notice, and abandon-reservation warning for `claimed`
players. Suffixes are never mentioned in user-facing copy — subscribers
should feel like they own the bare name outright.
- **Load prompt** (`src/client/Main.ts`): on a clean homepage load, a
subscriber renamed to `TEMPORARY####` is prompted to pick a new name
(free); takes priority over the rewards popup (the account modal shows
rewards anyway).

## Deploy notes

**No deploy-order constraint.** All new `/users/@me` fields are optional
in the schema: against the current API the response still parses and the
panel simply doesn't render (`usernameStatus === undefined`). The client
can ship before or after infra#434.

## Test plan

- New schema tests (old-API absence, leading-zero discriminators,
unknown statuses, PUT payload) and validation tests (dots, spaces,
unicode, trim) — full suite passes (2,031 + 171).
- Drove the real app headless with synthetic `/users/@me` payloads:
verified all panel states (unclaimed, never-set, claimed+grace,
TEMPORARY, cooldown-locked, old API hidden), the confirm-dialog
warnings, and the inline error path on a failed PUT.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:05:31 -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