"use client" import { useEffect, useMemo, useState } from "react" import Image from "next/image" import { authClient } from "@/lib/auth-client" import { Loader2, SearchIcon, BanIcon, UserCheckIcon, UsersIcon } from "lucide-react" type Role = "user" | "contributor" | "admin" interface AdminUser { id: string name: string email: string emailVerified: boolean image: string | null createdAt: Date | string role: Role banned: boolean } const roles: Role[] = ["user", "contributor", "admin"] function formatDate(value: Date | string | null | undefined) { if (!value) return "—" const d = typeof value === "string" ? new Date(value) : value return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) } function getInitial(name: string) { return name?.charAt(0)?.toUpperCase() || "?" } export function UsersClient() { const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [search, setSearch] = useState("") const [actionLoading, setActionLoading] = useState>({}) useEffect(() => { const fetchUsers = async () => { setLoading(true) const res = await authClient.admin.listUsers({ query: { limit: 100 } }) if (res.data?.users) { setUsers(res.data.users as AdminUser[]) } setLoading(false) } fetchUsers() }, []) const filteredUsers = useMemo(() => { const term = search.trim().toLowerCase() if (!term) return users return users.filter( (u) => u.name?.toLowerCase().includes(term) || u.email?.toLowerCase().includes(term) ) }, [users, search]) const handleRoleChange = async (userId: string, newRole: Role) => { setActionLoading((prev) => ({ ...prev, [userId]: true })) try { await authClient.admin.setRole({ userId, role: newRole as "user" | "admin" }) setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, role: newRole } : u)) ) } finally { setActionLoading((prev) => ({ ...prev, [userId]: false })) } } const handleBan = async (userId: string) => { setActionLoading((prev) => ({ ...prev, [userId]: true })) try { await authClient.admin.banUser({ userId }) setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, banned: true } : u)) ) } finally { setActionLoading((prev) => ({ ...prev, [userId]: false })) } } const handleUnban = async (userId: string) => { setActionLoading((prev) => ({ ...prev, [userId]: true })) try { await authClient.admin.unbanUser({ userId }) setUsers((prev) => prev.map((u) => (u.id === userId ? { ...u, banned: false } : u)) ) } finally { setActionLoading((prev) => ({ ...prev, [userId]: false })) } } return (
{/* Header with count */}

Users

({filteredUsers.length})
{/* Search */}
setSearch(e.target.value)} placeholder="Search by name or email..." className="w-full pl-9 pr-4 py-2.5 rounded-lg bg-text/5 border border-border text-sm text-text placeholder:text-text/40 focus:outline-none focus:border-primary/60 transition-colors" />
{/* Users List */} {loading ? (
) : filteredUsers.length === 0 ? (

No users found

) : (
{filteredUsers.map((user) => (
{/* Avatar */} {user.image ? ( ) : (
{getInitial(user.name)}
)} {/* User Info */}

{user.name || "Unnamed"}

{user.banned && ( Banned )}

{user.email}

Joined {formatDate(user.createdAt)}

{/* Role Selector */} {/* Action Button */} {actionLoading[user.id] ? ( ) : user.banned ? ( ) : ( )}
))}
)}
) }