import { useCallback, useState } from 'react' import { useParams, Link } from 'react-router-dom' import { Loading, ErrorState } from '../../../components/PageState.jsx' import { useAsync } from '../../../lib/useAsync.js' import { dateTime, ago } from '../../../lib/format.js' import { api } from '../../../api/client.js' import { useAuth } from '../../../contexts/AuthContext.jsx' const ACTION_TABS = [ { key: 'warn', label: 'Warnings' }, { key: 'mute', label: 'Mutes' }, { key: 'kick', label: 'Kicks' }, { key: 'ban', label: 'Bans' }, ] function fmtDuration(seconds) { if (!seconds) return null if (seconds % 86400 === 0) return `${seconds / 86400}d` if (seconds % 3600 === 0) return `${seconds / 3600}h` if (seconds % 60 === 0) return `${seconds / 60}m` return `${seconds}s` } export default function ModerationUser() { const { discordId } = useParams() const { user } = useAuth() const isAdmin = user?.role === 'admin' const [tab, setTab] = useState('warn') const [tick, setTick] = useState(0) const reload = useCallback(() => setTick((t) => t + 1), []) const { loading, error, data } = useAsync( () => Promise.all([ api.admin.modUser(discordId), api.admin.modUserActions(discordId, { limit: 200 }), api.admin.modUserNotes(discordId), ]), [discordId, tick], ) if (loading) return if (error) return const [summary, actions, notes] = data const counts = summary.counts || {} const tabActions = actions.filter((a) => a.action_type === tab) return (
← Back to moderation {/* Header */}
{summary.tag || '(unknown user)'} {summary.linked_account && ( site account: {summary.linked_account.username} )}
{discordId}
{ACTION_TABS.map((t) => ( ))}
{/* Tabs */}
{ACTION_TABS.map((t) => ( setTab(t.key)}> {t.label} ({counts[t.key] || 0}) ))} setTab('notes')}> Notes ({summary.notes_count || 0})
{tab === 'notes' ? ( ) : ( )}
) } function Count({ label, value }) { return (
{value}
{label}
) } function TabButton({ active, onClick, children }) { return ( ) } function ActionTable({ rows, showDuration }) { return (
{showDuration && } {rows.length === 0 && ( )} {rows.map((a) => ( {showDuration && } ))}
Reason ActorDurationWhen
Nothing here.
{a.reason || '—'} {a.is_automated ? ( Automated ) : ( {a.staff_tag || a.staff_user_id} )} {fmtDuration(a.duration_seconds) || '—'}{dateTime(a.created_at)}
) } function NotesTab({ discordId, notes, isAdmin, onAdded }) { const [body, setBody] = useState('') const [visibility, setVisibility] = useState('staff_only') const [busy, setBusy] = useState(false) const [err, setErr] = useState('') async function add() { if (!body.trim()) return setBusy(true) setErr('') try { await api.admin.addModNote(discordId, { body: body.trim(), visibility }) setBody('') setVisibility('staff_only') onAdded() } catch (e) { setErr(e.message || 'Could not save the note.') } finally { setBusy(false) } } return (
{err &&

{err}

}