From ee725056d2cdf9a8495cc004f37786f35a20fc22 Mon Sep 17 00:00:00 2001 From: Devin Zuczek Date: Tue, 13 Oct 2026 16:07:57 -0400 Subject: [PATCH] [www] add displayname change --- apps/www/src/client/App.tsx | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/apps/www/src/client/App.tsx b/apps/www/src/client/App.tsx index 55f1ea8..146bdba 100644 --- a/apps/www/src/client/App.tsx +++ b/apps/www/src/client/App.tsx @@ -506,6 +506,24 @@ async function changeUsername(username: string): Promise { return fetchMe() } +/** + * Set the display name — the name other players read, as opposed to the username the + * account signs in with. Unrationed, unlike a username change. + * + * Not checked here first, for the reason the email below isn't: `accounts` owns the rule + * (`displayNameRejection` in `@repo/domain`, the game client's own charset), and that + * module can't come into the browser — it imports `isemail`. The route refuses with an + * empty 400, so the rule is spelled out in the refusal instead. + */ +const saveDisplayName = (displayName: string): Promise => + call(`${where().accounts}/account/me/displayname`, { + method: 'PUT', + form: { displayName }, + authed: true, + refusal: + "That display name can't be used. Use up to 15 letters, numbers, spaces and .,'!?_&()#@:+- — and no profanity.", + }) + /** * Set the account's email. * @@ -3810,6 +3828,11 @@ function Dashboard({ label: 'Username', render: () => , }, + { + id: 'displayname', + label: 'Display name', + render: () => , + }, { id: 'email', label: 'Email', @@ -4609,6 +4632,61 @@ function UsernameForm({ ) } +function DisplayNameForm({ + account, + onChange, +}: { + account: SelfAccount + onChange: (a: SelfAccount) => void +}) { + const [displayName, setDisplayName] = useState(account.displayName) + const { pending, error, done, run } = useAction() + + const unchanged = displayName.trim() === account.displayName + + return ( +
+

Display name

+

+ The name other players see in the game. Changing it doesn't change what you sign in + with. +

+
{ + e.preventDefault() + void run(async () => { + const saved = displayName.trim() + await saveDisplayName(saved) + onChange({ ...account, displayName: saved }) + setDisplayName(saved) + return 'Display name saved.' + }) + }} + > + + {error &&

{error}

} + {done &&

{done}

} + +
+
+ ) +} + function EmailForm({ account, onChange,