Fix profile 404/500 and user deletion 403

This commit is contained in:
Tiago Yamamoto 2025-12-26 09:55:19 -03:00
parent f51a8dd99c
commit e0b16e5b29
6 changed files with 224 additions and 45 deletions

View file

@ -366,8 +366,19 @@ func (h *CoreHandlers) ListUsers(w http.ResponseWriter, r *http.Request) {
// @Router /api/v1/users/{id} [delete] // @Router /api/v1/users/{id} [delete]
func (h *CoreHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) { func (h *CoreHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
tenantID, ok := ctx.Value(middleware.ContextTenantID).(string) tenantID, _ := ctx.Value(middleware.ContextTenantID).(string)
if !ok || tenantID == "" {
// 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) http.Error(w, "Tenant ID not found in context", http.StatusForbidden)
return return
} }
@ -380,7 +391,18 @@ func (h *CoreHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
return 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) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -750,8 +772,10 @@ func (h *CoreHandlers) UpdateMyProfile(w http.ResponseWriter, r *http.Request) {
} }
// userID (string) passed directly as first arg // 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) resp, err := h.updateUserUC.Execute(ctx, userID, tenantID, req)
if err != nil { if err != nil {
log.Printf("[UpdateMyProfile] Error: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
@ -835,13 +859,16 @@ func (h *CoreHandlers) Me(w http.ResponseWriter, r *http.Request) {
userID = strconv.Itoa(int(v)) userID = strconv.Itoa(int(v))
} }
log.Printf("[Me Handler] Processing request for UserID: %s", userID)
user, err := h.adminService.GetUser(ctx, userID) user, err := h.adminService.GetUser(ctx, userID)
if err != nil { 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) http.Error(w, err.Error(), http.StatusInternalServerError)
return 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) company, _ := h.adminService.GetCompanyByUserID(ctx, userID)
if company != nil { if company != nil {

View file

@ -24,10 +24,12 @@ func (uc *DeleteUserUseCase) Execute(ctx context.Context, userID, tenantID strin
return err 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") return errors.New("user not found in this tenant")
} }
// 2. Delete // 3. Delete
return uc.userRepo.Delete(ctx, userID) return uc.userRepo.Delete(ctx, userID)
} }

View file

@ -157,6 +157,7 @@ func NewRouter() http.Handler {
// Public /api/v1/users/me (Authenticated) // Public /api/v1/users/me (Authenticated)
mux.Handle("GET /api/v1/users/me", authMiddleware.HeaderAuthGuard(http.HandlerFunc(coreHandlers.Me))) 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) // /api/v1/admin/companies -> Handled by coreHandlers.ListCompanies (Smart Branching)
// Needs to be wired with Optional Auth to support both Public and Admin. // Needs to be wired with Optional Auth to support both Public and Admin.

View file

@ -18,6 +18,7 @@ import {
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Plus, Search, Loader2, RefreshCw, Building2, CheckCircle, XCircle, Eye, Trash2, Pencil } from "lucide-react" 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 { adminCompaniesApi, type AdminCompany } from "@/lib/api"
import { getCurrentUser, isAdminUser } from "@/lib/auth" import { getCurrentUser, isAdminUser } from "@/lib/auth"
import { toast } from "sonner" import { toast } from "sonner"
@ -82,6 +83,8 @@ export default function AdminCompaniesPage() {
document: "", document: "",
address: "", address: "",
description: "", description: "",
active: false,
verified: false,
}) })
useEffect(() => { useEffect(() => {
@ -184,6 +187,8 @@ export default function AdminCompaniesPage() {
document: company.document || "", document: company.document || "",
address: company.address || "", address: company.address || "",
description: company.description || "", description: company.description || "",
active: company.active || false,
verified: company.verified || false,
}) })
setIsEditDialogOpen(true) setIsEditDialogOpen(true)
setIsViewDialogOpen(false) setIsViewDialogOpen(false)
@ -193,7 +198,26 @@ export default function AdminCompaniesPage() {
if (!selectedCompany) return if (!selectedCompany) return
try { try {
setUpdating(true) 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") toast.success("Company updated successfully")
setIsEditDialogOpen(false) setIsEditDialogOpen(false)
loadCompanies() loadCompanies()
@ -434,7 +458,7 @@ export default function AdminCompaniesPage() {
</Card> </Card>
{/* View Company Modal */} {/* View Company Modal */}
<Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}> <Dialog open={isViewDialogOpen} onOpenChange={setIsViewDialogOpen}>
<DialogContent className="max-w-2xl"> <DialogContent className="max-w-2xl max-h-[85vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center gap-2"> <DialogTitle className="flex items-center gap-2">
<Building2 className="h-5 w-5" /> <Building2 className="h-5 w-5" />
@ -561,6 +585,24 @@ export default function AdminCompaniesPage() {
<DialogDescription>Update company information</DialogDescription> <DialogDescription>Update company information</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <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"> <div className="grid gap-2">
<Label htmlFor="edit-name">Company name</Label> <Label htmlFor="edit-name">Company name</Label>
<Input <Input

View file

@ -28,8 +28,10 @@ export default function ProfilePage() {
}, []) }, [])
const loadProfile = async () => { const loadProfile = async () => {
console.log("[PROFILE_FLOW] Loading profile...")
try { try {
const userData = await authApi.getCurrentUser() const userData = await authApi.getCurrentUser()
console.log("[PROFILE_FLOW] Profile loaded:", userData)
setUser(userData) setUser(userData)
setFormData({ setFormData({
fullName: userData.fullName || "", fullName: userData.fullName || "",
@ -38,6 +40,7 @@ export default function ProfilePage() {
bio: userData.bio || "" bio: userData.bio || ""
}) })
} catch (error) { } catch (error) {
console.error("[PROFILE_FLOW] Error loading profile:", error)
toast.error("Failed to load profile") toast.error("Failed to load profile")
} finally { } finally {
setLoading(false) setLoading(false)
@ -51,14 +54,17 @@ export default function ProfilePage() {
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
setSaving(true) setSaving(true)
console.log("[PROFILE_FLOW] Updating profile:", formData)
try { try {
await profileApi.update({ await profileApi.update({
name: formData.fullName, name: formData.fullName,
phone: formData.phone, phone: formData.phone,
bio: formData.bio bio: formData.bio
}) })
console.log("[PROFILE_FLOW] Profile updated successfully")
toast.success("Profile updated") toast.success("Profile updated")
} catch (error) { } catch (error) {
console.error("[PROFILE_FLOW] Error updating profile:", error)
toast.error("Failed to update profile") toast.error("Failed to update profile")
} finally { } finally {
setSaving(false) setSaving(false)

View file

@ -18,7 +18,7 @@ import {
} from "@/components/ui/dialog" } from "@/components/ui/dialog"
import { Label } from "@/components/ui/label" import { Label } from "@/components/ui/label"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" 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 { usersApi, adminCompaniesApi, type ApiUser, type AdminCompany } from "@/lib/api"
import { getCurrentUser, isAdminUser } from "@/lib/auth" import { getCurrentUser, isAdminUser } from "@/lib/auth"
import { toast } from "sonner" import { toast } from "sonner"
@ -36,13 +36,23 @@ export default function AdminUsersPage() {
const [searchTerm, setSearchTerm] = useState("") const [searchTerm, setSearchTerm] = useState("")
const [page, setPage] = useState(1) const [page, setPage] = useState(1)
const [totalUsers, setTotalUsers] = useState(0) const [totalUsers, setTotalUsers] = useState(0)
// Dialog States
const [isDialogOpen, setIsDialogOpen] = useState(false) const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false) const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
// Action States
const [creating, setCreating] = useState(false) const [creating, setCreating] = useState(false)
const [updating, setUpdating] = 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 [selectedUser, setSelectedUser] = useState<ApiUser | null>(null)
const [companies, setCompanies] = useState<AdminCompany[]>([]) const [companies, setCompanies] = useState<AdminCompany[]>([])
const [currentUser, setCurrentUser] = useState<ApiUser | null>(null) const [currentUser, setCurrentUser] = useState<ApiUser | null>(null)
// Form Data
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: "", name: "",
email: "", email: "",
@ -64,7 +74,7 @@ export default function AdminUsersPage() {
router.push("/dashboard") router.push("/dashboard")
return return
} }
setCurrentUser(user as ApiUser) // Casting safe here due to check setCurrentUser(user as ApiUser)
loadUsers() loadUsers()
if (user?.role === 'superadmin') { if (user?.role === 'superadmin') {
@ -85,14 +95,16 @@ export default function AdminUsersPage() {
const totalPages = Math.max(1, Math.ceil(totalUsers / limit)) const totalPages = Math.max(1, Math.ceil(totalUsers / limit))
const loadUsers = async (targetPage = page) => { const loadUsers = async (targetPage = page) => {
console.log(`[USER_FLOW] Loading users page: ${targetPage}`)
try { try {
setLoading(true) setLoading(true)
const data = await usersApi.list({ page: targetPage, limit }) const data = await usersApi.list({ page: targetPage, limit })
console.log("[USER_FLOW] Users loaded:", data)
setUsers(data?.data || []) setUsers(data?.data || [])
setTotalUsers(data?.pagination?.total || 0) setTotalUsers(data?.pagination?.total || 0)
setPage(data?.pagination?.page || targetPage) setPage(data?.pagination?.page || targetPage)
} catch (error) { } catch (error) {
console.error("Error loading users:", error) console.error("[USER_FLOW] Error loading users:", error)
toast.error("Failed to load users") toast.error("Failed to load users")
} finally { } finally {
setLoading(false) setLoading(false)
@ -100,11 +112,12 @@ export default function AdminUsersPage() {
} }
const handleCreate = async () => { const handleCreate = async () => {
console.log("[USER_FLOW] Creating user with data:", formData)
try { try {
setCreating(true) setCreating(true)
const payload = { const payload = {
...formData, ...formData,
roles: [formData.role], // Backend expects array roles: [formData.role],
} }
await usersApi.create(payload) await usersApi.create(payload)
toast.success("User created successfully!") toast.success("User created successfully!")
@ -113,14 +126,15 @@ export default function AdminUsersPage() {
setPage(1) setPage(1)
loadUsers(1) loadUsers(1)
} catch (error) { } catch (error) {
console.error("Error creating user:", error) console.error("[USER_FLOW] Error creating user:", error)
toast.error("Failed to create user") toast.error("Failed to create user")
} finally { } finally {
setCreating(false) setCreating(false)
} }
} }
const handleEdit = (user: ApiUser) => { const handleView = (user: ApiUser) => {
console.log("[USER_FLOW] Viewing user:", user)
setSelectedUser(user) setSelectedUser(user)
setEditFormData({ setEditFormData({
name: user.name, name: user.name,
@ -128,54 +142,78 @@ export default function AdminUsersPage() {
role: user.role, role: user.role,
status: user.status || "active", 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) setIsEditDialogOpen(true)
} }
const handleUpdate = async () => { const handleUpdate = async () => {
if (!selectedUser) return if (!selectedUser) return
console.log("[USER_FLOW] Updating user:", selectedUser.id, "with data:", editFormData)
try { try {
setUpdating(true) setUpdating(true)
const payload = { const payload = {
...editFormData, ...editFormData,
roles: [editFormData.role], // Backend expects array of roles roles: [editFormData.role],
} }
await usersApi.update(selectedUser.id, payload) await usersApi.update(selectedUser.id, payload)
toast.success("User updated successfully!") toast.success("User updated successfully!")
setIsEditDialogOpen(false) setIsEditDialogOpen(false)
loadUsers() // Refresh list loadUsers()
} catch (error) { } catch (error) {
console.error("Error updating user:", error) console.error("[USER_FLOW] Error updating user:", error)
toast.error("Failed to update user") toast.error("Failed to update user")
} finally { } finally {
setUpdating(false) setUpdating(false)
} }
} }
const handleDelete = async (id: string) => { const handleDeleteClick = (user: ApiUser) => {
// Optimistic UI update or wait? User asked for no full reload. console.log("[USER_FLOW] Delete click for user:", user)
// We can remove it from state immediately. setSelectedUser(user)
// We should use a proper dialog but standard confirm is quick. setIsDeleteDialogOpen(true)
if (!confirm("Are you sure you want to delete this user?")) return }
// Optimistic update const confirmDelete = async () => {
const originalUsers = [...users] if (!selectedUser) return
setUsers(users.filter(u => u.id !== id)) console.log("[USER_FLOW] Confirming delete for user:", selectedUser.id)
try { try {
await usersApi.delete(id) setDeleting(true)
await usersApi.delete(selectedUser.id)
toast.success("User deleted!") 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) { if (users.length === 1 && page > 1) {
setPage(page - 1) setPage(page - 1)
loadUsers(page - 1) loadUsers(page - 1)
} else { } else {
// Background revalidate to ensure count is correct
loadUsers(page) loadUsers(page)
} }
} catch (error) { setIsDeleteDialogOpen(false)
console.error("Error deleting user:", error) setSelectedUser(null)
toast.error("Failed to delete user") } catch (error: any) {
setUsers(originalUsers) // Revert on failure 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> </DialogContent>
</Dialog> </Dialog>
</div> </div>
{/* Edit / View Dialog */}
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}> <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DialogContent> <DialogContent className="max-w-md max-h-[85vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle>Edit User</DialogTitle> <DialogTitle>{viewing ? "User Details" : "Edit User"}</DialogTitle>
<DialogDescription>Update user details</DialogDescription> <DialogDescription>{viewing ? "View user information" : "Update user details"}</DialogDescription>
</DialogHeader> </DialogHeader>
<div className="grid gap-4 py-4"> <div className="grid gap-4 py-4">
<div className="grid gap-2"> <div className="grid gap-2">
@ -326,6 +366,8 @@ export default function AdminUsersPage() {
value={editFormData.name} value={editFormData.name}
onChange={(e) => setEditFormData({ ...editFormData, name: e.target.value })} onChange={(e) => setEditFormData({ ...editFormData, name: e.target.value })}
placeholder="Full name" placeholder="Full name"
readOnly={viewing}
disabled={viewing}
/> />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
@ -336,11 +378,17 @@ export default function AdminUsersPage() {
value={editFormData.email} value={editFormData.email}
onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })} onChange={(e) => setEditFormData({ ...editFormData, email: e.target.value })}
placeholder="email@example.com" placeholder="email@example.com"
readOnly={viewing}
disabled={viewing}
/> />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="edit-role">Role</Label> <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> <SelectTrigger>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@ -354,7 +402,11 @@ export default function AdminUsersPage() {
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="edit-status">Status</Label> <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> <SelectTrigger>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
@ -366,10 +418,44 @@ export default function AdminUsersPage() {
</div> </div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>Cancel</Button> <Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>
<Button onClick={handleUpdate} disabled={updating}> {viewing ? "Close" : "Cancel"}
{updating && <Loader2 className="h-4 w-4 mr-2 animate-spin" />} </Button>
Save Changes {!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> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
@ -458,8 +544,15 @@ export default function AdminUsersPage() {
<TableCell>{user.email}</TableCell> <TableCell>{user.email}</TableCell>
<TableCell>{getRoleBadge(user.role)}</TableCell> <TableCell>{getRoleBadge(user.role)}</TableCell>
<TableCell> <TableCell>
<Badge variant={user.status === "active" ? "default" : "secondary"}> <Badge
{user.status === "active" ? "Active" : user.status} 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> </Badge>
</TableCell> </TableCell>
<TableCell> <TableCell>
@ -467,6 +560,14 @@ export default function AdminUsersPage() {
</TableCell> </TableCell>
<TableCell className="text-right"> <TableCell className="text-right">
<div className="flex justify-end gap-2"> <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 <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
@ -478,7 +579,7 @@ export default function AdminUsersPage() {
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
onClick={() => handleDelete(user.id)} onClick={() => handleDeleteClick(user)}
disabled={user.role === "superadmin"} disabled={user.role === "superadmin"}
> >
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />