feat: allow editing user avatars (#26652)

Adds an avatar URL field to the admin **Edit user** page, available only
for users whose login type is `password` or `none`.

For identity-provider login types (`github`, `oidc`) the avatar is
synced from the IdP on every login, so the field is hidden and the API
ignores any submitted avatar to avoid confusing overwrites.

The field reuses the same emoji picker + URL input (`IconField`) already
used for template, group, and organization icons.

A follow-up PR will add the same control to the self-service Account
settings page.

<details>
<summary>Implementation plan & decisions</summary>

**Goal:** Let an admin set/clear a user's avatar from the Edit user
page, gated to `password`/`none` login types.

**Backend**
- Add `avatar_url` to `codersdk.UpdateUserProfileRequest`.
- `putUserProfile` applies the submitted avatar only for
`password`/`none`; otherwise it preserves the existing (IdP-synced)
value.
- Regenerated TS types and API docs via `make gen`.

**Frontend**
- `EditUserForm` renders an `IconField` ("Avatar URL") when the login
type allows it.
- `EditUserPage` passes the avatar value and a `canEditAvatar` flag.
- `AccountPage` round-trips `avatar_url` so the shared request type
doesn't wipe avatars on the self-service path.

**Gating** is enforced in both the UI (field hidden) and the backend
(submitted value ignored for IdP login types).

**Tests/stories:** backend `TestUpdateUserProfile` covers apply
(password) and ignore (SSO); `EditUserForm` stories cover the
shown/hidden states with interaction tests.

</details>

---
> Generated by Coder Agents on behalf of @aslilac.
This commit is contained in:
McKayla はな
2026-06-26 16:35:03 -06:00
committed by GitHub
parent 7481e1a5a0
commit efd93027ce
17 changed files with 185 additions and 9 deletions
+9 -1
View File
@@ -917,11 +917,19 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
return
}
// Avatars for password and none login types are managed manually. For
// other login types the avatar is synced from the identity provider on
// login, so we preserve the existing value and ignore any submitted one.
avatarURL := user.AvatarURL
if user.LoginType == database.LoginTypePassword || user.LoginType == database.LoginTypeNone {
avatarURL = params.AvatarURL
}
updatedUserProfile, err := api.Database.UpdateUserProfile(ctx, database.UpdateUserProfileParams{
ID: user.ID,
Email: user.Email,
Name: params.Name,
AvatarURL: user.AvatarURL,
AvatarURL: avatarURL,
Username: params.Username,
UpdatedAt: dbtime.Now(),
})