"use client";
import { useEffect, useState, useCallback, useRef } from "react";
import { createPortal } from "react-dom";
import Link from "next/link";
import Image from "next/image";
import {
FiUsers, FiArrowLeft, FiShield, FiUser, FiLoader,
FiChevronDown, FiCalendar, FiHash, FiUserCheck, FiUserX,
} from "react-icons/fi";
import { MdAdminPanelSettings } from "react-icons/md";
import "../styles/AdminDashboard.css";
import "./AdminUsers.css";
const PAGE_LIMIT = 12;
function RoleBadge({ role }) {
if (role === "superadmin")
return Super Admin;
if (role === "admin")
return Admin;
return User;
}
function formatDate(dateStr) {
if (!dateStr) return "—";
return new Date(dateStr).toLocaleDateString("en-IN", {
day: "2-digit", month: "short", year: "numeric",
});
}
export default function AdminUsers() {
const [myRole, setMyRole] = useState(null);
const [token, setToken] = useState("");
const [myUsn, setMyUsn] = useState("");
const [isLoaded, setIsLoaded] = useState(false);
const [users, setUsers] = useState([]);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState("");
const [changingRole, setChangingRole] = useState({});
const [roleMsg, setRoleMsg] = useState({});
const [openConfirmUsn, setOpenConfirmUsn] = useState(null);
const [confirmAction, setConfirmAction] = useState(null);
const [dropdownPos, setDropdownPos] = useState({ top: 0, left: 0 });
const confirmRef = useRef(null);
const btnRefs = useRef({});
// ── bootstrap ──
useEffect(() => {
const r = localStorage.getItem("role") || "";
const t = localStorage.getItem("token") || "";
const u = localStorage.getItem("usn") || "";
setMyRole(r); setToken(t); setMyUsn(u);
setTimeout(() => setIsLoaded(true), 100);
}, []);
// ── fetch users ──
const fetchUsers = useCallback(async (pageNum, append = false) => {
if (!token) return;
append ? setLoadingMore(true) : setLoading(true);
setError("");
try {
const res = await fetch(`/api/admin/users?page=${pageNum}&limit=${PAGE_LIMIT}`, {
headers: { Authorization: `Bearer ${token}` },
});
const data = await res.json();
if (!res.ok) { setError(data.error || "Failed to fetch users"); return; }
append ? setUsers(prev => [...prev, ...data.users]) : setUsers(data.users);
setTotal(data.total);
setPage(data.page);
setTotalPages(data.totalPages);
} catch {
setError("Network error. Please try again.");
} finally {
setLoading(false); setLoadingMore(false);
}
}, [token]);
useEffect(() => {
if (token && (myRole === "admin" || myRole === "superadmin")) fetchUsers(1, false);
}, [token, myRole, fetchUsers]);
const handleViewMore = () => fetchUsers(page + 1, true);
// ── inline confirm helpers ──
const openConfirm = (user, newRole, usn) => {
const btn = btnRefs.current[usn];
if (btn) {
const rect = btn.getBoundingClientRect();
const dropH = 190;
const dropW = 230;
const spaceBelow = window.innerHeight - rect.bottom;
const top = spaceBelow > dropH ? rect.bottom + 8 : rect.top - dropH - 8;
const spaceRight = window.innerWidth - rect.left;
const left = spaceRight > dropW ? rect.left : rect.right - dropW;
setDropdownPos({ top, left });
}
setOpenConfirmUsn(user.usn);
setConfirmAction({ user, newRole });
};
const closeConfirm = () => { setOpenConfirmUsn(null); setConfirmAction(null); };
const handleConfirmed = () => {
if (!confirmAction) return;
handleRoleChange(confirmAction.user, confirmAction.newRole);
closeConfirm();
};
// close on outside click
useEffect(() => {
if (!openConfirmUsn) return;
const handler = (e) => {
if (confirmRef.current && !confirmRef.current.contains(e.target)) {
const btns = Object.values(btnRefs.current);
if (btns.some(b => b && b.contains(e.target))) return;
closeConfirm();
}
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [openConfirmUsn]);
// ── change role ──
const handleRoleChange = async (user, newRole) => {
setChangingRole(prev => ({ ...prev, [user.usn]: true }));
setRoleMsg(prev => ({ ...prev, [user.usn]: "" }));
try {
const res = await fetch("/api/admin/users/role", {
method: "PATCH",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
body: JSON.stringify({ targetUsn: user.usn, newRole }),
});
const data = await res.json();
if (!res.ok) { setRoleMsg(prev => ({ ...prev, [user.usn]: data.error || "Failed" })); return; }
setUsers(prev => prev.map(u => u.usn === user.usn ? { ...u, role: data.user.role } : u));
setRoleMsg(prev => ({
...prev,
[user.usn]: newRole === "admin" ? "Made admin ✓" : "Removed admin ✓",
}));
setTimeout(() => setRoleMsg(prev => ({ ...prev, [user.usn]: "" })), 2500);
} catch {
setRoleMsg(prev => ({ ...prev, [user.usn]: "Network error" }));
} finally {
setChangingRole(prev => ({ ...prev, [user.usn]: false }));
}
};
// ── guards ──
if (myRole === null) return null;
const isSuperAdmin = myRole === "superadmin";
return (
{/* ── Header ── */}
Admin Dashboard
User Management
All Users
Latest registered users — {total > 0 ? `${total} total` : "loading…"}
{/* ── Summary strip ── */}
{users.filter(u => (u.role || "user") === "admin").length}
Admins (loaded)
{users.filter(u => !u.role || u.role === "user").length}
Regular Users
{/* ── Error ── */}
{error &&
{error}
}
{/* ── User list ── */}
{loading ? (
) : (
<>
{users.map((user, idx) => {
const userRole = user.role || "user";
const isChanging = changingRole[user.usn];
const msg = roleMsg[user.usn];
const isOwnAccount = user.usn === myUsn;
const isOpen = openConfirmUsn === user.usn;
return (
{/* Avatar + name row */}
{/* Meta row */}
Joined {formatDate(user.createdAt)}
{/* Role action — superadmin only, not own account */}
{isSuperAdmin && !isOwnAccount && (
{userRole === "admin" ? (
) : (
)}
{msg &&
{msg}}
)}
{/* Own account label */}
{isOwnAccount &&
You
}
);
})}
{/* ── View more ── */}
{page < totalPages && (
)}
{users.length > 0 && page >= totalPages && (
All {total} users loaded
)}
{users.length === 0 && !loading && (
)}
>
)}
{/* ── Portal confirm dropdown ── renders on document.body, floats above everything ── */}
{openConfirmUsn && confirmAction && typeof document !== "undefined" &&
createPortal(
{confirmAction.newRole === "admin" ? : }
{confirmAction.newRole === "admin" ? "Make Admin?" : "Remove Admin?"}
{confirmAction.newRole === "admin"
? <>{confirmAction.user.name} will gain admin access.>
: <>{confirmAction.user.name} will lose admin access.>}
,
document.body
)
}
);
}