Editing a user role in Settings → Users does not persist #710
Replies: 4 comments
-
|
Thanks @bdunncompany — confirmed, and the analysis is exactly right. The root cause class is the silent field-drop: Couple of small preferences on scope:
Open as a draft and I'll review. |
Beta Was this translation helpful? Give feedback.
-
|
Correction to my comment above — the change-detection guard I suggested was wrong. const currentRoleId = roles.find(r => r.name === selectedUser.role)?.id;
if (values.roleId && values.roleId !== currentRoleId) {
// POST /users/:id/role
}Also a small correction to the body shape I quoted: the edit flow sends Everything else stands: the PATCH silently strips |
Beta Was this translation helpful? Give feedback.
-
|
Draft PR up at #713. All four refinements folded in:
apps/api: 14/14 tests pass. apps/web: 2/2 tests pass. tsc clean on both. |
Beta Was this translation helpful? Give feedback.
-
|
Shipped — landed on Root cause was the silent field-drop: Thanks @bdunncompany for the precise root-cause. Closing as resolved — reopen if role changes still don't stick. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Reproduction
Result: dialog closes (no error), row in the table still shows X. The join-table row is unchanged.
Root cause
The Edit dialog (
apps/web/src/components/settings/UsersPage.tsx:197-218,handleEditSubmit) submits:The API validator (
apps/api/src/routes/users.ts:73-76,updateUserSchema) is:It is not
.strict(), soroleIdis silently dropped. The handler (apps/api/src/routes/users.ts:895-991) writes onlyname/statusto theuserstable; the role join tables are never touched. Response is 200, andfetchUsers()reloads showing the unchanged role.The dedicated endpoint that writes role (
apps/api/src/routes/users.ts:1042-1114,POST /users/:id/role, writes topartnerUsers.roleId/organizationUsers.roleId) is not invoked by the Edit flow.The
namefield also goes along in the PATCH body, which is why the!data.name && !data.status400 guard does not fire — the request returns 200 with name-as-no-op and the role silently unchanged.Proposed fix
Make
handleEditSubmitPOST the role separately whenvalues.roleIdis set, in addition to (or instead of) the name PATCH.Optional hardening (separate concern): make
updateUserSchema.strict()so silently-dropped fields surface as 400s in future.Test: Vitest component test that mocks the Edit flow, picks a different role, and asserts
fetchWithAuthwas called withPOST /users/:id/roleand the newroleId.Happy to open the PR if this looks like the right shape.
Beta Was this translation helpful? Give feedback.
All reactions