Fix profile 404/500 and user deletion 403
This commit is contained in:
parent
f51a8dd99c
commit
e0b16e5b29
6 changed files with 224 additions and 45 deletions
|
|
@ -366,8 +366,19 @@ func (h *CoreHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
|
|||
// @Router /api/v1/users/{id} [delete]
|
||||
func (h *CoreHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
tenantID, ok := ctx.Value(middleware.ContextTenantID).(string)
|
||||
if !ok || tenantID == "" {
|
||||
tenantID, _ := ctx.Value(middleware.ContextTenantID).(string)
|
||||
|
||||
// Check for admin role to bypass tenant check
|
||||
userRoles := middleware.ExtractRoles(ctx.Value(middleware.ContextRoles))
|
||||
isAdmin := false
|
||||
for _, role := range userRoles {
|
||||
if role == "ADMIN" || role == "SUPERADMIN" || role == "admin" || role == "superadmin" {
|
||||
isAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAdmin && tenantID == "" {
|
||||
http.Error(w, "Tenant ID not found in context", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
|
@ -380,7 +391,18 @@ func (h *CoreHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := h.deleteUserUC.Execute(ctx, id, tenantID); err != nil {
|
||||
// If admin, we pass empty tenantID to signal bypass to the usecase (or specific admin logic)
|
||||
// But wait, UpdateUserUseCase treats empty tenantID as bypass. Let's see DeleteUserUseCase.
|
||||
// We need to match that logic.
|
||||
targetTenantID := tenantID
|
||||
if isAdmin {
|
||||
targetTenantID = "" // Signal bypass
|
||||
}
|
||||
|
||||
log.Printf("[DeleteUser] UserID: %s, RequestID: %s, IsAdmin: %v, TenantID: %s, TargetTenantID: %s", r.PathValue("id"), middleware.GetRequestID(r.Context()), isAdmin, tenantID, targetTenantID)
|
||||
|
||||
if err := h.deleteUserUC.Execute(ctx, id, targetTenantID); err != nil {
|
||||
log.Printf("[DeleteUser] Error: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -750,8 +772,10 @@ func (h *CoreHandlers) UpdateMyProfile(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// userID (string) passed directly as first arg
|
||||
log.Printf("[UpdateMyProfile] UserID: %s, TenantID: %s", userID, tenantID)
|
||||
resp, err := h.updateUserUC.Execute(ctx, userID, tenantID, req)
|
||||
if err != nil {
|
||||
log.Printf("[UpdateMyProfile] Error: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -835,13 +859,16 @@ func (h *CoreHandlers) Me(w http.ResponseWriter, r *http.Request) {
|
|||
userID = strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
log.Printf("[Me Handler] Processing request for UserID: %s", userID)
|
||||
|
||||
user, err := h.adminService.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
log.Printf("ERROR [Me Handler] GetUser failed for userID %s: %v", userID, err)
|
||||
log.Printf("[Me Handler] GetUser failed for userID %s: %v", userID, err)
|
||||
// Check for specific error types if possible, or just return 500
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("SUCCESS [Me Handler] User retrieved: %s", user.Email)
|
||||
log.Printf("[Me Handler] User retrieved: %s", user.Email)
|
||||
|
||||
company, _ := h.adminService.GetCompanyByUserID(ctx, userID)
|
||||
if company != nil {
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@ func (uc *DeleteUserUseCase) Execute(ctx context.Context, userID, tenantID strin
|
|||
return err
|
||||
}
|
||||
|
||||
if user.TenantID != tenantID {
|
||||
// 2. Check Permission (Tenant Check)
|
||||
// If tenantID is empty, it means SuperAdmin or Admin (handler logic), so we skip check.
|
||||
if tenantID != "" && user.TenantID != tenantID {
|
||||
return errors.New("user not found in this tenant")
|
||||
}
|
||||
|
||||
// 2. Delete
|
||||
// 3. Delete
|
||||
return uc.userRepo.Delete(ctx, userID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ func NewRouter() http.Handler {
|
|||
|
||||
// Public /api/v1/users/me (Authenticated)
|
||||
mux.Handle("GET /api/v1/users/me", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.Me)))
|
||||
mux.Handle("PATCH /api/v1/users/me/profile", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.UpdateMyProfile)))
|
||||
|
||||
// /api/v1/admin/companies -> Handled by coreHandlers.ListCompanies (Smart Branching)
|
||||
// Needs to be wired with Optional Auth to support both Public and Admin.
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
} from "@/components/ui/dialog"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Plus, Search, Loader2, RefreshCw, Building2, CheckCircle, XCircle, Eye, Trash2, Pencil } from "lucide-react"
|
||||
import { Switch } from "@/components/ui/switch"
|
||||
import { adminCompaniesApi, type AdminCompany } from "@/lib/api"
|
||||
import { getCurrentUser, isAdminUser } from "@/lib/auth"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -82,6 +83,8 @@ export default function AdminCompaniesPage() {
|
|||
document: "",
|
||||
address: "",
|
||||
description: "",
|
||||
active: false,
|
||||
verified: false,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -184,6 +187,8 @@ export default function AdminCompaniesPage() {
|
|||
document: company.document || "",
|
||||
address: company.address || "",
|
||||
description: company.description || "",
|
||||
active: company.active || false,
|
||||
verified: company.verified || false,
|
||||
})
|
||||
setIsEditDialogOpen(true)
|
||||
setIsViewDialogOpen(false)
|
||||
|
|
@ -193,7 +198,26 @@ export default function AdminCompaniesPage() {
|
|||
if (!selectedCompany) return
|
||||
try {
|
||||
setUpdating(true)
|
||||
await adminCompaniesApi.update(selectedCompany.id, editFormData)
|
||||
|
||||
// Check if status changed
|
||||
if (editFormData.active !== selectedCompany.active || editFormData.verified !== selectedCompany.verified) {
|
||||
await adminCompaniesApi.updateStatus(selectedCompany.id, {
|
||||
active: editFormData.active,
|
||||
verified: editFormData.verified
|
||||
})
|
||||
}
|
||||
|
||||
await adminCompaniesApi.update(selectedCompany.id, {
|
||||
name: editFormData.name,
|
||||
slug: editFormData.slug,
|
||||
email: editFormData.email,
|
||||
phone: editFormData.phone,
|
||||
website: editFormData.website,
|
||||
document: editFormData.document,
|
||||
address: editFormData.address,
|
||||
description: editFormData.description,
|
||||
})
|
||||
|
||||
toast.success("Company updated successfully")
|
||||
setIsEditDialogOpen(false)
|
||||
loadCompanies()
|
||||
|
|
@ -434,7 +458,7 @@ export default function AdminCompaniesPage() {
|
|||
</Card>
|
||||
{/* View Company Modal */}
|
||||
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5" />
|
||||
|
|
@ -561,6 +585,24 @@ export default function AdminCompaniesPage() {
|
|||
<DialogDescription>Update company information</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="flex items-center gap-4 border p-4 rounded-md">
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={editFormData.active}
|
||||
onCheckedChange={(checked) => setEditFormData({ ...editFormData, active: checked })}
|
||||
id="edit-active"
|
||||
/>
|
||||
<Label htmlFor="edit-active">Active</Label>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={editFormData.verified}
|
||||
onCheckedChange={(checked) => setEditFormData({ ...editFormData, verified: checked })}
|
||||
id="edit-verified"
|
||||
/>
|
||||
<Label htmlFor="edit-verified">Verified</Label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-name">Company name</Label>
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -28,8 +28,10 @@ export default function ProfilePage() {
|
|||
}, [])
|
||||
|
||||
const loadProfile = async () => {
|
||||
console.log("[PROFILE_FLOW] Loading profile...")
|
||||
try {
|
||||
const userData = await authApi.getCurrentUser()
|
||||
console.log("[PROFILE_FLOW] Profile loaded:", userData)
|
||||
setUser(userData)
|
||||
setFormData({
|
||||
fullName: userData.fullName || "",
|
||||
|
|
@ -38,6 +40,7 @@ export default function ProfilePage() {
|
|||
bio: userData.bio || ""
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[PROFILE_FLOW] Error loading profile:", error)
|
||||
toast.error("Failed to load profile")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
|
@ -51,14 +54,17 @@ export default function ProfilePage() {
|
|||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setSaving(true)
|
||||
console.log("[PROFILE_FLOW] Updating profile:", formData)
|
||||
try {
|
||||
await profileApi.update({
|
||||
name: formData.fullName,
|
||||
phone: formData.phone,
|
||||
bio: formData.bio
|
||||
})
|
||||
console.log("[PROFILE_FLOW] Profile updated successfully")
|
||||
toast.success("Profile updated")
|
||||
} catch (error) {
|
||||
console.error("[PROFILE_FLOW] Error updating profile:", error)
|
||||
toast.error("Failed to update profile")
|
||||
} finally {
|
||||
setSaving(false)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from "@/components/ui/dialog"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Plus, Search, Trash2, Loader2, RefreshCw, Pencil } from "lucide-react"
|
||||
import { Plus, Search, Trash2, Loader2, RefreshCw, Pencil, Eye } from "lucide-react"
|
||||
import { usersApi, adminCompaniesApi, type ApiUser, type AdminCompany } from "@/lib/api"
|
||||
import { getCurrentUser, isAdminUser } from "@/lib/auth"
|
||||
import { toast } from "sonner"
|
||||
|
|
@ -36,13 +36,23 @@ export default function AdminUsersPage() {
|
|||
const [searchTerm, setSearchTerm] = useState("")
|
||||
const [page, setPage] = useState(1)
|
||||
const [totalUsers, setTotalUsers] = useState(0)
|
||||
|
||||
// Dialog States
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
|
||||
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
|
||||
|
||||
// Action States
|
||||
const [creating, setCreating] = useState(false)
|
||||
const [updating, setUpdating] = useState(false)
|
||||
const [deleting, setDeleting] = useState(false)
|
||||
const [viewing, setViewing] = useState(false)
|
||||
|
||||
const [selectedUser, setSelectedUser] = useState<ApiUser | null>(null)
|
||||
const [companies, setCompanies] = useState<AdminCompany[]>([])
|
||||
const [currentUser, setCurrentUser] = useState<ApiUser | null>(null)
|
||||
|
||||
// Form Data
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
email: "",
|
||||
|
|
@ -64,7 +74,7 @@ export default function AdminUsersPage() {
|
|||
router.push("/dashboard")
|
||||
return
|
||||
}
|
||||
setCurrentUser(user as ApiUser) // Casting safe here due to check
|
||||
setCurrentUser(user as ApiUser)
|
||||
loadUsers()
|
||||
|
||||
if (user?.role === 'superadmin') {
|
||||
|
|
@ -85,14 +95,16 @@ export default function AdminUsersPage() {
|
|||
const totalPages = Math.max(1, Math.ceil(totalUsers / limit))
|
||||
|
||||
const loadUsers = async (targetPage = page) => {
|
||||
console.log(`[USER_FLOW] Loading users page: ${targetPage}`)
|
||||
try {
|
||||
setLoading(true)
|
||||
const data = await usersApi.list({ page: targetPage, limit })
|
||||
console.log("[USER_FLOW] Users loaded:", data)
|
||||
setUsers(data?.data || [])
|
||||
setTotalUsers(data?.pagination?.total || 0)
|
||||
setPage(data?.pagination?.page || targetPage)
|
||||
} catch (error) {
|
||||
console.error("Error loading users:", error)
|
||||
console.error("[USER_FLOW] Error loading users:", error)
|
||||
toast.error("Failed to load users")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
|
@ -100,11 +112,12 @@ export default function AdminUsersPage() {
|
|||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
console.log("[USER_FLOW] Creating user with data:", formData)
|
||||
try {
|
||||
setCreating(true)
|
||||
const payload = {
|
||||
...formData,
|
||||
roles: [formData.role], // Backend expects array
|
||||
roles: [formData.role],
|
||||
}
|
||||
await usersApi.create(payload)
|
||||
toast.success("User created successfully!")
|
||||
|
|
@ -113,14 +126,15 @@ export default function AdminUsersPage() {
|
|||
setPage(1)
|
||||
loadUsers(1)
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error)
|
||||
console.error("[USER_FLOW] Error creating user:", error)
|
||||
toast.error("Failed to create user")
|
||||
} finally {
|
||||
setCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (user: ApiUser) => {
|
||||
const handleView = (user: ApiUser) => {
|
||||
console.log("[USER_FLOW] Viewing user:", user)
|
||||
setSelectedUser(user)
|
||||
setEditFormData({
|
||||
name: user.name,
|
||||
|
|
@ -128,54 +142,78 @@ export default function AdminUsersPage() {
|
|||
role: user.role,
|
||||
status: user.status || "active",
|
||||
})
|
||||
setViewing(true)
|
||||
setIsEditDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleEdit = (user: ApiUser) => {
|
||||
console.log("[USER_FLOW] Editing user:", user)
|
||||
setSelectedUser(user)
|
||||
setEditFormData({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
status: user.status || "active",
|
||||
})
|
||||
setViewing(false)
|
||||
setIsEditDialogOpen(true)
|
||||
}
|
||||
|
||||
const handleUpdate = async () => {
|
||||
if (!selectedUser) return
|
||||
console.log("[USER_FLOW] Updating user:", selectedUser.id, "with data:", editFormData)
|
||||
try {
|
||||
setUpdating(true)
|
||||
const payload = {
|
||||
...editFormData,
|
||||
roles: [editFormData.role], // Backend expects array of roles
|
||||
roles: [editFormData.role],
|
||||
}
|
||||
await usersApi.update(selectedUser.id, payload)
|
||||
toast.success("User updated successfully!")
|
||||
setIsEditDialogOpen(false)
|
||||
loadUsers() // Refresh list
|
||||
loadUsers()
|
||||
} catch (error) {
|
||||
console.error("Error updating user:", error)
|
||||
console.error("[USER_FLOW] Error updating user:", error)
|
||||
toast.error("Failed to update user")
|
||||
} finally {
|
||||
setUpdating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (id: string) => {
|
||||
// Optimistic UI update or wait? User asked for no full reload.
|
||||
// We can remove it from state immediately.
|
||||
// We should use a proper dialog but standard confirm is quick.
|
||||
if (!confirm("Are you sure you want to delete this user?")) return
|
||||
const handleDeleteClick = (user: ApiUser) => {
|
||||
console.log("[USER_FLOW] Delete click for user:", user)
|
||||
setSelectedUser(user)
|
||||
setIsDeleteDialogOpen(true)
|
||||
}
|
||||
|
||||
// Optimistic update
|
||||
const originalUsers = [...users]
|
||||
setUsers(users.filter(u => u.id !== id))
|
||||
const confirmDelete = async () => {
|
||||
if (!selectedUser) return
|
||||
console.log("[USER_FLOW] Confirming delete for user:", selectedUser.id)
|
||||
|
||||
try {
|
||||
await usersApi.delete(id)
|
||||
setDeleting(true)
|
||||
await usersApi.delete(selectedUser.id)
|
||||
toast.success("User deleted!")
|
||||
// If we are on a page > 1 and it becomes empty, we might need to fetch prev page
|
||||
|
||||
// UI Update logic
|
||||
if (users.length === 1 && page > 1) {
|
||||
setPage(page - 1)
|
||||
loadUsers(page - 1)
|
||||
} else {
|
||||
// Background revalidate to ensure count is correct
|
||||
loadUsers(page)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting user:", error)
|
||||
toast.error("Failed to delete user")
|
||||
setUsers(originalUsers) // Revert on failure
|
||||
setIsDeleteDialogOpen(false)
|
||||
setSelectedUser(null)
|
||||
} catch (error: any) {
|
||||
console.error("[USER_FLOW] Error deleting user:", error)
|
||||
// Error handling matching user request to see logs
|
||||
if (error.message && error.message.includes("403")) {
|
||||
toast.error("You don't have permission to delete this user (403)")
|
||||
} else {
|
||||
toast.error("Failed to delete user")
|
||||
}
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,11 +350,13 @@ export default function AdminUsersPage() {
|
|||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{/* Edit / View Dialog */}
|
||||
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogContent className="max-w-md max-h-[85vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Edit User</DialogTitle>
|
||||
<DialogDescription>Update user details</DialogDescription>
|
||||
<DialogTitle>{viewing ? "User Details" : "Edit User"}</DialogTitle>
|
||||
<DialogDescription>{viewing ? "View user information" : "Update user details"}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid gap-2">
|
||||
|
|
@ -326,6 +366,8 @@ export default function AdminUsersPage() {
|
|||
value={editFormData.name}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, name: e.target.value })}
|
||||
placeholder="Full name"
|
||||
readOnly={viewing}
|
||||
disabled={viewing}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
|
|
@ -336,11 +378,17 @@ export default function AdminUsersPage() {
|
|||
value={editFormData.email}
|
||||
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
|
||||
placeholder="email@example.com"
|
||||
readOnly={viewing}
|
||||
disabled={viewing}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-role">Role</Label>
|
||||
<Select value={editFormData.role} onValueChange={(v) => setEditFormData({ ...editFormData, role: v })}>
|
||||
<Select
|
||||
value={editFormData.role}
|
||||
onValueChange={(v) => setEditFormData({ ...editFormData, role: v })}
|
||||
disabled={viewing}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
|
@ -354,7 +402,11 @@ export default function AdminUsersPage() {
|
|||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-status">Status</Label>
|
||||
<Select value={editFormData.status} onValueChange={(v) => setEditFormData({ ...editFormData, status: v })}>
|
||||
<Select
|
||||
value={editFormData.status}
|
||||
onValueChange={(v) => setEditFormData({ ...editFormData, status: v })}
|
||||
disabled={viewing}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
|
|
@ -366,10 +418,44 @@ export default function AdminUsersPage() {
|
|||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleUpdate} disabled={updating}>
|
||||
{updating && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
Save Changes
|
||||
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>
|
||||
{viewing ? "Close" : "Cancel"}
|
||||
</Button>
|
||||
{!viewing && (
|
||||
<Button onClick={handleUpdate} disabled={updating}>
|
||||
{updating && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
Save Changes
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete User</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this user? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
{selectedUser && (
|
||||
<div className="text-sm border p-4 rounded-md bg-muted/50">
|
||||
<p><strong>Name:</strong> {selectedUser.name}</p>
|
||||
<p><strong>Email:</strong> {selectedUser.email}</p>
|
||||
<p><strong>ID:</strong> {selectedUser.id}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsDeleteDialogOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={confirmDelete} disabled={deleting}>
|
||||
{deleting && <Loader2 className="h-4 w-4 mr-2 animate-spin" />}
|
||||
Delete User
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
|
@ -458,8 +544,15 @@ export default function AdminUsersPage() {
|
|||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>{getRoleBadge(user.role)}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={user.status === "active" ? "default" : "secondary"}>
|
||||
{user.status === "active" ? "Active" : user.status}
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={
|
||||
user.status === "active"
|
||||
? "border-transparent bg-green-500/15 text-green-700 hover:bg-green-500/25 dark:text-green-400"
|
||||
: "border-transparent bg-red-500/15 text-red-700 hover:bg-red-500/25 dark:text-red-400"
|
||||
}
|
||||
>
|
||||
{user.status ? user.status.toUpperCase() : "UNKNOWN"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
|
|
@ -467,6 +560,14 @@ export default function AdminUsersPage() {
|
|||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleView(user)}
|
||||
title="View Details"
|
||||
>
|
||||
<Eye className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
|
|
@ -478,7 +579,7 @@ export default function AdminUsersPage() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => handleDelete(user.id)}
|
||||
onClick={() => handleDeleteClick(user)}
|
||||
disabled={user.role === "superadmin"}
|
||||
>
|
||||
<Trash2 className="h-4 w-4 text-destructive" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue