import { ArrowDown, ArrowUp } from "lucide-react"; import { Fragment, useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; import { formatRelative } from "../../components/TaskDetailFields"; import { Avatar, AvatarFallback } from "../../components/ui/avatar"; import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "../../components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table"; import { authClient, useSession } from "../../lib/auth-client"; import { BanUserDialog } from "./BanUserDialog"; import { DeleteUserDialog } from "./DeleteUserDialog"; import { SessionsPanel } from "./SessionsPanel"; import { SetRoleDialog } from "./SetRoleDialog"; import { type DialogKind, type User } from "./types"; import { RoleBadge, StatusBadge } from "./UserBadges"; import { SkeletonRows, UserRowActions } from "./UserTableRows"; const PAGE_SIZE_OPTIONS = [10, 20, 50, 100] as const; type RoleFilter = "all" | "admin" | "user"; type StatusFilter = "all" | "active" | "banned"; type SortDirection = "asc" | "desc"; export function AdminUsersPage() { const { data: session } = useSession(); const currentUserId = (session?.user as any)?.id; const [users, setUsers] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(0); const [search, setSearch] = useState(""); const [searchInput, setSearchInput] = useState(""); const [pageSize, setPageSize] = useState<(typeof PAGE_SIZE_OPTIONS)[number]>(20); const [roleFilter, setRoleFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); const [sortDirection, setSortDirection] = useState("desc"); const [loading, setLoading] = useState(true); const [activeDialog, setActiveDialog] = useState<{ kind: DialogKind; user: User } | null>(null); const [expandedSessions, setExpandedSessions] = useState(null); const searchRef = useRef | null>(null); const fetchUsers = useCallback(async () => { setLoading(true); const query: Record = { limit: pageSize, offset: page * pageSize, sortBy: "createdAt", sortDirection, }; if (search) { query.searchField = "email"; query.searchValue = search; } if (roleFilter !== "all") { query.filterField = "role"; query.filterValue = roleFilter; query.filterOperator = "eq"; } else if (statusFilter !== "all") { query.filterField = "banned"; query.filterValue = statusFilter === "banned"; query.filterOperator = "eq"; } const { data, error } = await (authClient.admin as any).listUsers({ query }); if (error || !data) { toast.error("Failed to load users"); setLoading(false); return; } setUsers(data.users ?? []); setTotal(data.total ?? 0); setLoading(false); }, [page, pageSize, roleFilter, search, sortDirection, statusFilter]); useEffect(() => { fetchUsers(); }, [fetchUsers]); function handleSearchChange(value: string) { setSearchInput(value); if (searchRef.current) clearTimeout(searchRef.current); searchRef.current = setTimeout(() => { setPage(0); setSearch(value.trim()); }, 300); } function applyRoleFilter(value: RoleFilter | null) { if (!value) return; setPage(0); setRoleFilter(value); if (value !== "all") setStatusFilter("all"); } function applyStatusFilter(value: StatusFilter | null) { if (!value) return; setPage(0); setStatusFilter(value); if (value !== "all") setRoleFilter("all"); } function toggleCreatedSort() { setPage(0); setSortDirection((direction) => (direction === "desc" ? "asc" : "desc")); } function applyPageSize(value: string | null) { if (!value) return; setPage(0); setPageSize(Number(value) as (typeof PAGE_SIZE_OPTIONS)[number]); } async function handleUnban(user: User) { const { error } = await (authClient.admin as any).unbanUser({ userId: user.id }); if (error) { toast.error("Failed to unban user"); } else { toast.success("User unbanned"); fetchUsers(); } } function handleAction(kind: DialogKind, user: User) { if (kind === "ban" && user.banned) { handleUnban(user); return; } setActiveDialog({ kind, user }); } function onDialogSuccess() { const messages: Record = { role: "Role updated", ban: "User banned", delete: "User deleted", }; if (activeDialog) toast.success(messages[activeDialog.kind]); fetchUsers(); } function showSessionToast(type: "success" | "error", message: string) { if (type === "success") toast.success(message); else toast.error(message); } const offset = page * pageSize; const totalPages = Math.ceil(total / pageSize); return (

Users

{total} total
handleSearchChange(e.target.value)} className="max-w-xs" />
User Role Status {loading ? ( ) : users.length === 0 ? ( No users found ) : ( users.map((user) => (
{(user.name || user.email)[0].toUpperCase()}

{user.name || "—"}

{user.email}

{formatRelative(user.createdAt)} setExpandedSessions((prev) => (prev === uid ? null : uid))} />
{expandedSessions === user.id && (

Sessions

)}
)) )}
{total > 0 && (
Showing {offset + 1}–{Math.min(offset + pageSize, total)} of {total}
)} {activeDialog?.kind === "role" && ( setActiveDialog(null)} onSuccess={onDialogSuccess} /> )} {activeDialog?.kind === "ban" && ( setActiveDialog(null)} onSuccess={onDialogSuccess} /> )} {activeDialog?.kind === "delete" && ( setActiveDialog(null)} onSuccess={onDialogSuccess} /> )}
); }