feat: admin can change user display name

API: new updateName adminProcedure in user router
- Input: userId + name (min 1, max 200 chars)
- Argon2 not involved (name only, not password)
- Audit log: "Changed name from X to Y"

UI: "Display Name" editable section in user edit modal
- Shows current name with "Edit" link
- Click Edit: inline input with Save/Cancel + Enter/Escape
- Auto-focuses input, saves on Enter
- Invalidates user list on success

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-03-23 09:41:56 +01:00
parent ea9263de29
commit 840f355f4f
2 changed files with 99 additions and 0 deletions
+35
View File
@@ -196,6 +196,41 @@ export const userRouter = createTRPCRouter({
return updated;
}),
updateName: adminProcedure
.input(
z.object({
id: z.string(),
name: z.string().min(1, "Name is required").max(200),
}),
)
.mutation(async ({ ctx, input }) => {
const before = await ctx.db.user.findUniqueOrThrow({
where: { id: input.id },
select: { id: true, name: true, email: true },
});
const updated = await ctx.db.user.update({
where: { id: input.id },
data: { name: input.name },
select: { id: true, name: true, email: true },
});
void createAuditEntry({
db: ctx.db,
entityType: "User",
entityId: updated.id,
entityName: `${updated.name} (${updated.email})`,
action: "UPDATE",
...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
before: before as unknown as Record<string, unknown>,
after: updated as unknown as Record<string, unknown>,
source: "ui",
summary: `Changed name from "${before.name}" to "${updated.name}"`,
});
return updated;
}),
// ─── Resource Linking ──────────────────────────────────────────────────
linkResource: adminProcedure