Role Manager
관리자용 컴포넌트관리자(MASTER)만 쓰는 화면에 쓰입니다.
관리자가 사용자에게 권한(역할)을 부여하거나 바꾸는 확인 창입니다.
관리자가 한 사용자에게 권한(역할)을 부여하거나 바꾸는 확인 창입니다. 단순한 선택 상자와 달리, 역할마다 무엇을 할 수 있는지(C·R·U·D)를 보여 주고, 한 사람에게 역할을 여러 개 줄 수 있습니다. 위험한 변경(MASTER 부여·해제)은 확인 전에 경고하고, 본인의 역할은 바꿀 수 없게 잠급니다(서버도 막습니다).
Preview
표에서 사용자 옆의 변경을 눌러 보세요. 첫 번째 사용자(MASTER)는 "현재 로그인한 관리자 본인"으로 가정했습니다 — 이 사용자의 창을 열면 역할을 바꿀 수 없다는 안내가 나옵니다.
| ID | 아이디 | 이름 | 이메일 | 역할 | 상태 | 가입일 | |
|---|---|---|---|---|---|---|---|
| 1 | master01 | 김관리 | master@example.com | MASTER | 활성 | 2026-10-01 | |
| 2 | writer01 | 이작성 | writer@example.com | VIEWERWRITER | 활성 | 2026-10-05 | |
| 3 | viewer01 | 박조회 | viewer@example.com | VIEWER | 비활성 | 2026-10-12 |
tsx
<MemberTable
members={members}
roleEditor="dialog" // 기본값
currentUserId={me.id} // 본인 행 잠금용
onRoleChange={(id, roles) => updateRoles.mutate({ id, roles })}
onRemove={(id) => removeMember.mutate(id)}
/>What admins can do
| 단계 | 관리자가 하는 일 | 화면 동작 |
|---|---|---|
| 1. 열기 | 멤버 표에서 역할 옆 [변경] 클릭 | 사용자 정보(아바타, 아이디, 이메일, 현재 역할)가 있는 확인 창이 열립니다. |
| 2. 비교 | 역할 카드들을 살펴봄 | 역할마다 설명과 C·R·U·D 칩이 보입니다. 색이 진한 글자가 허용되는 작업입니다. |
| 3. 선택 | 원하는 역할 카드를 클릭 | 선택한 카드가 강조되고, 위쪽에 "현재 역할 → 새 역할"이 나타납니다. |
| 4. 확인 | [역할 변경] 클릭 | 새 역할 id로 onSubmit이 호출되고 창이 닫힙니다. |
- 현재 역할과 같으면 [역할 변경] 버튼이 잠겨 있어 의미 없는 요청을 막습니다.
- 삭제된 사용자는 [변경] 버튼이 비활성화됩니다.
- 창을 닫았다가 다른 사용자를 열면 선택 상태가 새로 시작됩니다.
Warnings
| 상황 | 화면 |
|---|---|
| 본인(currentUserId)의 MASTER를 다른 역할로 낮춤 | 빨간 경고 "본인의 관리자 권한을 내리려 하고 있습니다"와 빨간 확인 버튼 |
| 다른 사용자에게 MASTER 부여 | "모든 사용자와 데이터를 관리할 수 있습니다" 안내 |
| 그 외 변경 | 경고 없이 변경 내용만 표시 |
이 경고는 실수를 줄이는 안내일 뿐입니다. 서버는 반드시 (1) 요청자가 MASTER인지, (2) 마지막 MASTER를 낮추는 요청이 아닌지, (3) 대상 사용자와 역할이 유효한지를 다시 검사해야 합니다.
Inline select vs dialog
같은 MemberTable이 두 방식을 모두 지원합니다. 기본은 확인 창이고, 목록에서 빠르게 바꾸고 싶다면 인라인으로 바꿀 수 있습니다.
| roleEditor | 모양 | 이럴 때 |
|---|---|---|
"dialog" | 역할 배지 + [변경] → 확인 창 | 역할 설명을 보고 신중히 바꿔야 할 때 (기본, 권장) |
"inline" | 표 안의 선택 상자 | 역할이 단순하고 빠른 변경이 중요할 때 (Role Select 참고) |
Use the dialog on its own
표 밖(사용자 상세 화면 등)에서도 단독으로 쓸 수 있습니다.
tsx
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>역할 변경</Button>
<RoleAssignDialog
user={user}
open={open}
onOpenChange={setOpen}
currentUserId={me.id}
pending={updateRoles.isPending}
onSubmit={(roles) => updateRoles.mutate({ id: user.id, roles })} // 예: ["VIEWER", "WRITER"]
/>Where the roles come from
선택지, 설명, C·R·U·D 칩은 모두 src/meerkit/roles.ts의 ROLES에서 만들어집니다. 역할을 추가하거나 설명을 바꾸면 이 창에 그대로 반영됩니다 (Add a role 가이드 참고).
Server rules
PUT /users/{id}/roles { "roles": ["VIEWER", "WRITER"] }
│
├─ 요청자가 MASTER가 아님 ─────────────► 403
├─ 대상이 없음 ────────────────────────► 404
├─ 없는 역할이 섞여 있음 ──────────────► 422
├─ 자기 자신의 역할을 바꾸려 함 ────────► 400 (관리자가 사라지는 것 방지)
└─ 통과 ──────────────────────────────► 200 + 갱신된 User- 역할 변경 기록(누가, 누구를, 언제, 무엇에서 무엇으로)을 남기는 것을 권장합니다.
- 역할이 바뀐 사용자의 기존 토큰은 새 역할이 즉시 반영되도록 갱신하거나 다음 요청에서 다시 확인하세요.
Accessibility
- Radix Dialog 기반: 포커스가 창 안에 갇히고
Esc로 닫을 수 있으며, 닫으면 포커스가 [변경] 버튼으로 돌아옵니다. - 역할 선택은 라디오 그룹이라 방향키로 이동하고, 각 카드는 라벨로 묶여 있어 어디를 눌러도 선택됩니다.
- C·R·U·D 칩은
title과aria-label로 의미(Create 등)를 전달합니다.
Props
RoleAssignDialog
| Prop | Type | 설명 |
|---|---|---|
user필수 | User | null | 역할을 바꿀 사용자. null이면 아무것도 그리지 않습니다. |
open필수 | boolean | 열림 여부. |
onOpenChange필수 | (open: boolean) => void | 열림 상태가 바뀔 때 호출합니다. |
onSubmit필수 | (roleId: number) => void | [역할 변경]을 누르면 새 역할 id와 함께 호출합니다. 호출 뒤 창은 닫힙니다. |
currentUserId | number | 지금 로그인한 관리자의 id. 본인 권한을 낮추는 변경을 경고하는 데 씁니다. |
pending | boolean | 요청 중이면 버튼을 잠그고 "변경 중…"을 보여 줍니다. |
MemberTable (추가된 props)
| Prop | Type | 설명 |
|---|---|---|
roleEditor | "dialog" | "inline" | 역할 변경 방식. 기본 dialog. |
currentUserId | number | 본인 권한 하향 경고용. |
Related API
이 컴포넌트가 호출하는 API입니다. 누르면 요청·응답 명세로 이동합니다.
