Add roles and users admin UI with role management API

Backend:
- GET /v1/users (list company users)
- GET/POST/PATCH/DELETE /v1/roles (role CRUD with permissions)
- GET/POST/DELETE /v1/users/:userId/roles (role assignment)
- GET /v1/me/permissions (current user's effective permissions)

Frontend:
- Roles list page with kebab menu (edit permissions, delete custom)
- Role detail page with grouped permission checkboxes and inheritance note
- New role page with auto-generated slug
- Users list page showing assigned roles per user
- Manage Roles dialog for adding/removing roles per user
- Sidebar: Admin section with Users, Roles, Help links
This commit is contained in:
Ryan Moon
2026-03-28 17:16:53 -05:00
parent 4a1fc608f0
commit 58bf54a251
12 changed files with 1085 additions and 1 deletions

View File

@@ -0,0 +1,56 @@
import { queryOptions } from '@tanstack/react-query'
import { api } from '@/lib/api-client'
import type { Permission, Role } from '@/types/rbac'
export const rbacKeys = {
permissions: ['permissions'] as const,
roles: ['roles'] as const,
role: (id: string) => ['roles', id] as const,
userRoles: (userId: string) => ['users', userId, 'roles'] as const,
myPermissions: ['me', 'permissions'] as const,
}
export function permissionListOptions() {
return queryOptions({
queryKey: rbacKeys.permissions,
queryFn: () => api.get<{ data: Permission[] }>('/v1/permissions'),
})
}
export function roleListOptions() {
return queryOptions({
queryKey: rbacKeys.roles,
queryFn: () => api.get<{ data: Role[] }>('/v1/roles'),
})
}
export function roleDetailOptions(id: string) {
return queryOptions({
queryKey: rbacKeys.role(id),
queryFn: () => api.get<Role & { permissions: string[] }>(`/v1/roles/${id}`),
})
}
export function myPermissionsOptions() {
return queryOptions({
queryKey: rbacKeys.myPermissions,
queryFn: () => api.get<{ permissions: string[]; roles: { id: string; name: string; slug: string }[] }>('/v1/me/permissions'),
})
}
export const rbacMutations = {
createRole: (data: Record<string, unknown>) =>
api.post<Role>('/v1/roles', data),
updateRole: (id: string, data: Record<string, unknown>) =>
api.patch<Role>(`/v1/roles/${id}`, data),
deleteRole: (id: string) =>
api.del<Role>(`/v1/roles/${id}`),
assignRole: (userId: string, roleId: string) =>
api.post(`/v1/users/${userId}/roles`, { roleId }),
removeRole: (userId: string, roleId: string) =>
api.del(`/v1/users/${userId}/roles/${roleId}`),
}