"use client"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { api, AdminError, type AdminUser } from "@/lib/api"; import { usePagedList } from "@/lib/use-paged-list"; import { ListShell, EmptyRow } from "@/components/list-shell"; import { Table, THead, TR, TH, TD } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Modal } from "@/components/ui/modal"; import { useConfirm } from "@/components/confirm"; import { Input, Textarea } from "@/components/ui/input"; import { fmtDate } from "@/lib/utils"; export default function UsersPage() { const [q, setQ] = useState(""); const fetcher = useCallback( (limit: number, offset: number) => api.users(q, limit, offset), [q], ); const list = usePagedList(fetcher, [q]); const confirm = useConfirm(); const [notify, setNotify] = useState(null); async function toggleBan(u: AdminUser) { const banning = !u.banned_at; if ( banning && !(await confirm({ title: `Suspend @${u.handle}?`, message: "They'll be signed out and unable to log in until you unban them.", confirmLabel: "Suspend account", destructive: true, })) ) { return; } try { await (banning ? api.banUser(u.id) : api.unbanUser(u.id)); toast.success(banning ? "Account suspended." : "Account restored."); list.refresh(); } catch (err) { toast.error(err instanceof AdminError ? err.message : "Action failed."); } } return ( {list.items.length === 0 && !list.loading ? ( ) : ( list.items.map((u) => ( )) )}
User Email Role Joined Actions
{u.name} {u.banned_at ? ( Suspended ) : null}
@{u.handle}
{u.email ?? —} {u.role} {fmtDate(u.created_at)}
{u.role !== "admin" ? ( ) : null}
setNotify(null)} />
); } function NotifyModal({ user, onClose, }: { user: AdminUser | null; onClose: () => void; }) { const [title, setTitle] = useState(""); const [body, setBody] = useState(""); const [busy, setBusy] = useState(false); async function send() { if (!user || body.trim() === "") return; setBusy(true); try { await api.notifyUser(user.id, title.trim(), body.trim()); toast.success(`Notification sent to @${user.handle}`); setTitle(""); setBody(""); onClose(); } catch (err) { toast.error(err instanceof AdminError ? err.message : "Could not send."); } finally { setBusy(false); } } return (

Sends an in-app notification, plus web push and email where available.