Integrate role deletion response shape
Ensures the role delete API returns the standardized `{ message, success, status_code }` success payload.
Made-with: Cursor
This commit is contained in:
parent
23d100cdde
commit
28af7994f8
|
|
@ -5,6 +5,7 @@ import type {
|
||||||
GetRolesParams,
|
GetRolesParams,
|
||||||
CreateRoleRequest,
|
CreateRoleRequest,
|
||||||
CreateRoleResponse,
|
CreateRoleResponse,
|
||||||
|
DeleteRoleResponse,
|
||||||
SetRolePermissionsRequest,
|
SetRolePermissionsRequest,
|
||||||
GetPermissionsResponse,
|
GetPermissionsResponse,
|
||||||
} from "../types/rbac.types"
|
} from "../types/rbac.types"
|
||||||
|
|
@ -26,3 +27,6 @@ export const setRolePermissions = (roleId: number, data: SetRolePermissionsReque
|
||||||
|
|
||||||
export const getAllPermissions = () =>
|
export const getAllPermissions = () =>
|
||||||
http.get<GetPermissionsResponse>("/rbac/permissions")
|
http.get<GetPermissionsResponse>("/rbac/permissions")
|
||||||
|
|
||||||
|
export const deleteRole = (roleId: number) =>
|
||||||
|
http.delete<DeleteRoleResponse>(`/rbac/roles/${roleId}`)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import {
|
import {
|
||||||
Plus, Search, Shield, ShieldCheck, ChevronLeft, ChevronRight,
|
Plus,
|
||||||
AlertCircle, Eye, X, Pencil, Check,
|
Search,
|
||||||
|
Shield,
|
||||||
|
ShieldCheck,
|
||||||
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
|
AlertCircle,
|
||||||
|
Eye,
|
||||||
|
X,
|
||||||
|
Pencil,
|
||||||
|
Check,
|
||||||
|
Trash2,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { Button } from "../../components/ui/button"
|
import { Button } from "../../components/ui/button"
|
||||||
import { Card, CardContent } from "../../components/ui/card"
|
import { Card, CardContent } from "../../components/ui/card"
|
||||||
|
|
@ -12,7 +22,14 @@ import { Textarea } from "../../components/ui/textarea"
|
||||||
import {
|
import {
|
||||||
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription,
|
Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription,
|
||||||
} from "../../components/ui/dialog"
|
} from "../../components/ui/dialog"
|
||||||
import { getRoles, getRoleDetail, getAllPermissions, setRolePermissions, updateRole } from "../../api/rbac.api"
|
import {
|
||||||
|
getRoles,
|
||||||
|
getRoleDetail,
|
||||||
|
getAllPermissions,
|
||||||
|
setRolePermissions,
|
||||||
|
updateRole,
|
||||||
|
deleteRole,
|
||||||
|
} from "../../api/rbac.api"
|
||||||
import type { Role, RoleDetail, RolePermission } from "../../types/rbac.types"
|
import type { Role, RoleDetail, RolePermission } from "../../types/rbac.types"
|
||||||
import { cn } from "../../lib/utils"
|
import { cn } from "../../lib/utils"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
@ -36,6 +53,11 @@ export function RolesListPage() {
|
||||||
const [detailOpen, setDetailOpen] = useState(false)
|
const [detailOpen, setDetailOpen] = useState(false)
|
||||||
const [detailLoading, setDetailLoading] = useState(false)
|
const [detailLoading, setDetailLoading] = useState(false)
|
||||||
|
|
||||||
|
// Delete modal state
|
||||||
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
|
const [roleToDelete, setRoleToDelete] = useState<Role | null>(null)
|
||||||
|
const [deleteLoading, setDeleteLoading] = useState(false)
|
||||||
|
|
||||||
// Role info editing state
|
// Role info editing state
|
||||||
const [editingRole, setEditingRole] = useState(false)
|
const [editingRole, setEditingRole] = useState(false)
|
||||||
const [editName, setEditName] = useState("")
|
const [editName, setEditName] = useState("")
|
||||||
|
|
@ -97,6 +119,45 @@ export function RolesListPage() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDeleteRoleClick = (role: Role) => {
|
||||||
|
setRoleToDelete(role)
|
||||||
|
setDeleteDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancelDeleteRole = () => {
|
||||||
|
setDeleteDialogOpen(false)
|
||||||
|
setRoleToDelete(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleConfirmDeleteRole = async () => {
|
||||||
|
if (!roleToDelete) return
|
||||||
|
setDeleteLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await deleteRole(roleToDelete.id)
|
||||||
|
toast.success(res.data.message ?? "Role deleted successfully")
|
||||||
|
|
||||||
|
// Close dialogs if the deleted role is currently opened.
|
||||||
|
if (selectedRole?.id === roleToDelete.id) {
|
||||||
|
setDetailOpen(false)
|
||||||
|
setSelectedRole(null)
|
||||||
|
setEditingPermissions(false)
|
||||||
|
setEditingRole(false)
|
||||||
|
setPermSearch("")
|
||||||
|
}
|
||||||
|
|
||||||
|
setRoleToDelete(null)
|
||||||
|
setDeleteDialogOpen(false)
|
||||||
|
setPage(1) // trigger list refresh via the existing effect
|
||||||
|
} catch (err: unknown) {
|
||||||
|
const message =
|
||||||
|
(err as { response?: { data?: { message?: string } } })?.response?.data?.message ??
|
||||||
|
"Failed to delete role."
|
||||||
|
toast.error(message)
|
||||||
|
} finally {
|
||||||
|
setDeleteLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enter role info edit mode
|
// Enter role info edit mode
|
||||||
const handleEditRole = () => {
|
const handleEditRole = () => {
|
||||||
if (!selectedRole) return
|
if (!selectedRole) return
|
||||||
|
|
@ -360,16 +421,33 @@ export function RolesListPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="text-[11px] text-grayScale-400">Open details to view permissions</span>
|
<span className="text-[11px] text-grayScale-400">
|
||||||
<Button
|
Open details to view permissions
|
||||||
variant="outline"
|
</span>
|
||||||
size="sm"
|
<div className="flex items-center gap-2">
|
||||||
className="h-8 gap-1.5 text-xs"
|
{!role.is_system && (
|
||||||
onClick={() => handleViewRole(role.id)}
|
<Button
|
||||||
>
|
type="button"
|
||||||
<Eye className="h-3.5 w-3.5" />
|
variant="destructive"
|
||||||
View
|
size="icon"
|
||||||
</Button>
|
className="h-8 w-8"
|
||||||
|
onClick={() => handleDeleteRoleClick(role)}
|
||||||
|
disabled={deleteLoading}
|
||||||
|
aria-label={`Delete role ${role.name}`}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-8 gap-1.5 text-xs"
|
||||||
|
onClick={() => handleViewRole(role.id)}
|
||||||
|
>
|
||||||
|
<Eye className="h-3.5 w-3.5" />
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -703,6 +781,55 @@ export function RolesListPage() {
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Delete role dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={deleteDialogOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setDeleteDialogOpen(open)
|
||||||
|
if (!open) handleCancelDeleteRole()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-sm">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2 text-red-600">
|
||||||
|
<Trash2 className="h-5 w-5" />
|
||||||
|
Delete Role
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Are you sure you want to delete this role? This action cannot be undone.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{roleToDelete && (
|
||||||
|
<div className="rounded-lg bg-red-50 border border-red-100 p-3">
|
||||||
|
<p className="text-sm font-medium text-red-700">{roleToDelete.name}</p>
|
||||||
|
<p className="text-xs text-red-500 mt-0.5">Role #{roleToDelete.id}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleCancelDeleteRole}
|
||||||
|
disabled={deleteLoading}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
className="gap-1.5"
|
||||||
|
disabled={deleteLoading || !roleToDelete}
|
||||||
|
onClick={handleConfirmDeleteRole}
|
||||||
|
>
|
||||||
|
{deleteLoading ? <SpinnerIcon className="h-3.5 w-3.5" /> : <Trash2 className="h-3.5 w-3.5" />}
|
||||||
|
{deleteLoading ? "Deleting..." : "Delete"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,14 @@ export interface CreateRoleResponse {
|
||||||
metadata: unknown
|
metadata: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeleteRoleResponse {
|
||||||
|
message: string
|
||||||
|
success: boolean
|
||||||
|
status_code: number
|
||||||
|
// Some backends may include extra fields; keep it optional for compatibility.
|
||||||
|
metadata?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
export interface SetRolePermissionsRequest {
|
export interface SetRolePermissionsRequest {
|
||||||
permission_ids: number[]
|
permission_ids: number[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user