[www] add displayname change

This commit is contained in:
Devin Zuczek
2026-10-13 16:07:57 -04:00
parent 4f36146912
commit ee725056d2
+78
View File
@@ -506,6 +506,24 @@ async function changeUsername(username: string): Promise<SelfAccount> {
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<unknown> =>
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: () => <UsernameForm account={account} onChange={onChange} />,
},
{
id: 'displayname',
label: 'Display name',
render: () => <DisplayNameForm account={account} onChange={onChange} />,
},
{
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 (
<section className="card">
<h2>Display name</h2>
<p className="muted">
The name other players see in the game. Changing it doesn&apos;t change what you sign in
with.
</p>
<form
onSubmit={(e) => {
e.preventDefault()
void run(async () => {
const saved = displayName.trim()
await saveDisplayName(saved)
onChange({ ...account, displayName: saved })
setDisplayName(saved)
return 'Display name saved.'
})
}}
>
<label>
Display name
<input
type="text"
value={displayName}
autoComplete="nickname"
maxLength={15}
onChange={(e) => setDisplayName(e.target.value)}
required
/>
<span className="hint">
Up to 15 characters: letters, numbers, spaces and .,&apos;!?_&amp;()#@:+-
</span>
</label>
{error && <p className="error">{error}</p>}
{done && <p className="ok">{done}</p>}
<button type="submit" disabled={pending || unchanged}>
{pending ? 'Saving…' : 'Save display name'}
</button>
</form>
</section>
)
}
function EmailForm({
account,
onChange,