"use client"; import Image from "next/image"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Search, ShieldPlus, UserMinus, UserPlus, UserX } from "lucide-react"; import { NoPermissionState } from "@/components/auth/no-permission-state"; import { useSuperAdminSession } from "@/components/auth/session-context"; import { PageHeader } from "@/components/dashboard/page-header"; import { PaginationControls } from "@/components/dashboard/pagination-controls"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Drawer } from "@/components/ui/drawer"; import { EmptyState } from "@/components/ui/empty-state"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Textarea } from "@/components/ui/textarea"; import { useToast } from "@/components/ui/toast"; import { createAdminUser, deleteAdminUser, deletePlatformAdmin, getAdminUserById, getProfileOverviewForSuperAdmin, searchAdminUsers, searchPlatformAdmins, setAdminUserRole, updateAdminUser, updatePlatformAdmin, } from "@/lib/api/admin-users"; import { getItems, getPagination } from "@/lib/api/core"; import { updateSuperAdminUserStatus } from "@/lib/api/superadmin"; import { formatDateTime } from "@/lib/format"; import { resolveMediaUrl } from "@/lib/media-url"; import { SUPERADMIN_PERMISSIONS, hasPermission } from "@/lib/permissions"; import { cn } from "@/lib/utils"; import type { AdminUpdatePayload, AdminCreatePayload, ApiRole, ApiUser, PaginatedResponse, ProfileOverviewResponse, } from "@/types/api"; type ScopeFilter = "all-users" | "admins"; const emptyCreateState: AdminCreatePayload = { name: "", username: "", email: "", password: "", confirmPassword: "", }; function buildEditPayload(user: ApiUser): Partial { return { name: user.name ?? "", username: user.username ?? "", email: user.email ?? "", role: user.role ?? "user", stageName: user.stageName ?? "", bio: user.bio ?? "", location: user.location ?? "", isPrivate: user.isPrivate ?? false, isVerified: user.isVerified ?? false, }; } function normalizeComparableString(value: string | null | undefined) { return (value ?? "").trim(); } function buildUserUpdatePayload( selectedUser: ApiUser, editPayload: Partial, ): AdminUpdatePayload { const payload: AdminUpdatePayload = {}; const nextName = normalizeComparableString( typeof editPayload.name === "string" ? editPayload.name : selectedUser.name, ); const nextUsername = normalizeComparableString( typeof editPayload.username === "string" ? editPayload.username : selectedUser.username, ); const nextEmail = normalizeComparableString( typeof editPayload.email === "string" ? editPayload.email : selectedUser.email, ); const currentName = normalizeComparableString(selectedUser.name); const currentUsername = normalizeComparableString(selectedUser.username); const currentEmail = normalizeComparableString(selectedUser.email); if (nextName !== currentName) { if (!nextName) { throw new Error("Name cannot be empty."); } payload.name = nextName; } if (nextUsername !== currentUsername) { if (!nextUsername) { throw new Error("Username cannot be empty."); } payload.username = nextUsername; } if (nextEmail !== currentEmail) { if (!nextEmail) { throw new Error("Email cannot be empty."); } payload.email = nextEmail; } const nextStageName = normalizeComparableString( typeof editPayload.stageName === "string" ? editPayload.stageName : selectedUser.stageName, ); const currentStageName = normalizeComparableString(selectedUser.stageName); if (nextStageName !== currentStageName) { payload.stageName = nextStageName; } const nextBio = normalizeComparableString( typeof editPayload.bio === "string" ? editPayload.bio : selectedUser.bio, ); const currentBio = normalizeComparableString(selectedUser.bio); if (nextBio !== currentBio) { payload.bio = nextBio; } const nextLocation = normalizeComparableString( typeof editPayload.location === "string" ? editPayload.location : selectedUser.location, ); const currentLocation = normalizeComparableString(selectedUser.location); if (nextLocation !== currentLocation) { payload.location = nextLocation; } const nextIsPrivate = Boolean(editPayload.isPrivate ?? selectedUser.isPrivate ?? false); const currentIsPrivate = Boolean(selectedUser.isPrivate ?? false); if (nextIsPrivate !== currentIsPrivate) { payload.isPrivate = nextIsPrivate; } const nextIsVerified = Boolean(editPayload.isVerified ?? selectedUser.isVerified ?? false); const currentIsVerified = Boolean(selectedUser.isVerified ?? false); if (nextIsVerified !== currentIsVerified) { payload.isVerified = nextIsVerified; } return payload; } function FieldRow({ label, value, valueDir, valueClassName, }: { label: string; value: string | number | boolean | null | undefined; valueDir?: "auto" | "rtl" | "ltr"; valueClassName?: string; }) { const normalized = value === null || value === undefined || value === "" ? "-" : typeof value === "boolean" ? value ? "نعم" : "لا" : String(value); const isTechnicalValue = typeof normalized === "string" && (normalized.includes("@") || normalized.includes("_") || normalized.includes("-") || normalized.includes(".")); const resolvedDir = valueDir ?? (isTechnicalValue ? "ltr" : "auto"); return (
{label}
{normalized}
); } export default function UsersPage() { const { permissions } = useSuperAdminSession(); const [scope, setScope] = useState("all-users"); const [search, setSearch] = useState(""); const [verifiedFilter, setVerifiedFilter] = useState("all"); const [page, setPage] = useState(1); const [response, setResponse] = useState | null>(null); const [loading, setLoading] = useState(true); const [selectedUserId, setSelectedUserId] = useState(null); const [selectedUser, setSelectedUser] = useState(null); const [avatarLoadFailed, setAvatarLoadFailed] = useState(false); const [overview, setOverview] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [editPayload, setEditPayload] = useState>({}); const [createOpen, setCreateOpen] = useState(false); const [createPayload, setCreatePayload] = useState(emptyCreateState); const { toast } = useToast(); const filtersRef = useRef({ search, verifiedFilter }); const canReadUsers = hasPermission(permissions, SUPERADMIN_PERMISSIONS.USERS_READ); const canManageUsers = hasPermission(permissions, SUPERADMIN_PERMISSIONS.USERS_MANAGE); filtersRef.current = { search, verifiedFilter }; useEffect(() => { setAvatarLoadFailed(false); }, [selectedUser?.avatar]); const loadUsers = useCallback(async () => { if (!canReadUsers) { setLoading(false); return; } setLoading(true); try { const { search: currentSearch, verifiedFilter: currentVerifiedFilter } = filtersRef.current; const isVerified = currentVerifiedFilter === "all" ? undefined : currentVerifiedFilter === "verified" ? true : false; const nextResponse = scope === "admins" ? await searchPlatformAdmins({ page, limit: 12, q: currentSearch || undefined, isVerified, sortBy: "createdAt", sortOrder: "desc", }) : await searchAdminUsers({ page, limit: 12, q: currentSearch || undefined, isVerified, sortBy: "createdAt", sortOrder: "desc", }); setResponse(nextResponse); } catch (error) { const message = String(error); toast({ title: "تعذر تحميل المستخدمين", description: message, variant: "danger" }); } finally { setLoading(false); } }, [canReadUsers, page, scope, toast]); useEffect(() => { void loadUsers(); }, [loadUsers]); useEffect(() => { if (!selectedUserId || !canReadUsers) return; let active = true; const loadDetails = async () => { setDetailLoading(true); try { const [user, userOverview] = await Promise.all([ getAdminUserById(selectedUserId), getProfileOverviewForSuperAdmin(selectedUserId), ]); if (!active) return; setSelectedUser(user); setOverview(userOverview); setEditPayload(buildEditPayload(user)); } catch (error) { if (!active) return; toast({ title: "تعذر تحميل التفاصيل", description: String(error), variant: "danger" }); } finally { if (active) setDetailLoading(false); } }; void loadDetails(); return () => { active = false; }; }, [canReadUsers, selectedUserId, toast]); const users = getItems(response); const stats = useMemo(() => { const disabled = users.filter((item) => item.isDisabled).length; const admins = users.filter((item) => item.role === "admin").length; return { total: response?.pagination?.total ?? users.length, disabled, admins }; }, [response, users]); const syncUpdatedUser = (userId: string, patch: Partial) => { setResponse((prev) => { if (!prev) return prev; const source = getItems(prev); const updatedItems = source.map((item) => (item._id === userId ? { ...item, ...patch } : item)); return { ...prev, items: updatedItems, data: updatedItems, }; }); setSelectedUser((prev) => (prev && prev._id === userId ? { ...prev, ...patch } : prev)); }; const handleDisable = async (userId: string) => { const reason = typeof window === "undefined" ? "Disabled by SuperAdmin dashboard" : window.prompt("Reason for disabling this user", "Disabled by SuperAdmin dashboard"); if (reason === null) { return; } try { const updated = await updateSuperAdminUserStatus( userId, true, reason.trim() || "Disabled by SuperAdmin dashboard", ); syncUpdatedUser(userId, { isDisabled: true, disabledReason: updated.disabledReason }); toast({ title: "تم التعطيل", description: userId, variant: "warning" }); } catch (error) { toast({ title: "فشل التعطيل", description: String(error), variant: "danger" }); } }; const handleEnable = async (userId: string) => { try { const updated = await updateSuperAdminUserStatus(userId, false); syncUpdatedUser(userId, { isDisabled: false, disabledReason: updated.disabledReason ?? "" }); toast({ title: "تم التفعيل", description: userId, variant: "success" }); } catch (error) { toast({ title: "فشل التفعيل", description: String(error), variant: "danger" }); } }; const handleDelete = async (userId: string) => { try { if (scope === "admins") { await deletePlatformAdmin(userId); } else { await deleteAdminUser(userId); } if (selectedUserId === userId) { setSelectedUserId(null); setSelectedUser(null); setOverview(null); } await loadUsers(); toast({ title: "تم الحذف", description: userId, variant: "warning" }); } catch (error) { toast({ title: "فشل الحذف", description: String(error), variant: "danger" }); } }; const handleSave = async () => { if (!selectedUserId || !selectedUser || !canManageUsers) return; try { const updater = scope === "admins" ? updatePlatformAdmin : updateAdminUser; const updatePayload = buildUserUpdatePayload(selectedUser, editPayload); const currentRole = selectedUser.role ?? "user"; const nextRole = (editPayload.role as ApiRole | undefined) ?? currentRole; const roleChanged = nextRole !== currentRole; const hasProfileChanges = Object.keys(updatePayload).length > 0; if (!hasProfileChanges && !roleChanged) { toast({ title: "No changes to save", description: "Update one or more fields before saving.", variant: "default", }); return; } if (hasProfileChanges) { await updater(selectedUserId, updatePayload); } if (roleChanged) { await setAdminUserRole(selectedUserId, nextRole); } const [freshUser, freshOverview] = await Promise.all([ getAdminUserById(selectedUserId), getProfileOverviewForSuperAdmin(selectedUserId), ]); syncUpdatedUser(selectedUserId, freshUser); setSelectedUser(freshUser); setOverview(freshOverview); setEditPayload(buildEditPayload(freshUser)); await loadUsers(); toast({ title: "تم حفظ التعديلات", description: "تم تحديث بيانات المستخدم.", variant: "success", }); } catch (error) { toast({ title: "فشل التحديث", description: String(error), variant: "danger" }); } }; const handleCreate = async () => { if (!canManageUsers) return; try { await createAdminUser(createPayload); setCreateOpen(false); setCreatePayload(emptyCreateState); setScope("admins"); setPage(1); await loadUsers(); toast({ title: "تم إنشاء الأدمن", description: createPayload.email, variant: "success" }); } catch (error) { toast({ title: "فشل إنشاء الأدمن", description: String(error), variant: "danger" }); } }; if (!canReadUsers) { return (
); } return (
إنشاء حساب إداري هذا الطلب مرتبط بمسار SuperAdmin المباشر لإنشاء Admin جديد.
setCreatePayload((prev) => ({ ...prev, name: event.target.value })) } /> setCreatePayload((prev) => ({ ...prev, username: event.target.value })) } /> setCreatePayload((prev) => ({ ...prev, email: event.target.value })) } /> setCreatePayload((prev) => ({ ...prev, password: event.target.value, confirmPassword: event.target.value, })) } />
) : null} />
إجمالي النتائج {stats.total} أدمنز في الصفحة {stats.admins} حسابات معطلة في الصفحة {stats.disabled}
الفلاتر
setSearch(event.target.value)} placeholder="ابحث بالاسم أو البريد أو اليوزر" className="pr-9" />
النتائج {!users.length && !loading ? ( ) : ( الاسم البريد الدور موثق الحالة إجراءات {users.map((user) => ( setSelectedUserId(user._id)} > {user.name ?? user.username ?? "-"} {user.email} {user.role ?? "user"} {user.isVerified ? "موثق" : "غير موثق"} {user.isDisabled ? "معطل" : "نشط"} {user.isDisabled ? ( ) : ( )} ))}
)}
{ setSelectedUserId(null); setSelectedUser(null); setOverview(null); setEditPayload({}); }} title="User details" description="Review the account profile overview, safety state, and editable admin fields." side="right" widthClassName="w-full sm:w-[92vw] sm:max-w-2xl" > {detailLoading ? (
Loading details...
) : selectedUser ? (
{selectedUser.avatar && !avatarLoadFailed ? ( {selectedUser.name setAvatarLoadFailed(true)} className="h-full w-full object-cover" /> ) : ( {(selectedUser.name ?? selectedUser.username ?? "U").charAt(0).toUpperCase()} )}
{selectedUser.name ?? selectedUser.username ?? "-"}
{selectedUser.email}
{selectedUser.isVerified ? "Verified" : "Unverified"} {selectedUser.isDisabled ? "Disabled" : "Active"}
Quick edit
setEditPayload((prev) => ({ ...prev, name: event.target.value })) } /> setEditPayload((prev) => ({ ...prev, username: event.target.value })) } />
setEditPayload((prev) => ({ ...prev, email: event.target.value })) } />
setEditPayload((prev) => ({ ...prev, stageName: event.target.value })) } /> setEditPayload((prev) => ({ ...prev, location: event.target.value })) } />