:3
This commit is contained in:
@@ -67,7 +67,7 @@ export function AdminCenter() {
|
||||
try {
|
||||
const res = await api.adminReportList();
|
||||
setOpenReportCount(
|
||||
res.reports.filter((r) => r.status === "open" || r.status === "reviewing").length,
|
||||
res.reports.filter((r) => (r.status === "open" || r.status === "reviewing") && r.reporter_shadowbanned !== 1).length,
|
||||
);
|
||||
} catch {
|
||||
// Transient failure: keep last count.
|
||||
|
||||
@@ -126,15 +126,6 @@ function ReportDialog({ open, onClose, conversationId, messageIndex, content, pr
|
||||
<Button variant="secondary" onClick={onClose}>{t("common.close")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<p className="flex items-center gap-2 rounded-2xl bg-emerald-600/10 px-4 py-3 text-sm text-emerald-700 dark:text-emerald-400">
|
||||
<Check size={16} /> {t("report.sent")}
|
||||
</p>
|
||||
<div className="flex justify-end">
|
||||
<Button variant="secondary" onClick={onClose}>{t("common.close")}</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={submit} className="space-y-4">
|
||||
<Field label={t("report.reason")}>
|
||||
|
||||
@@ -136,8 +136,10 @@ function ReportDetail({ report, onChanged }: { report: Report; onChanged: () =>
|
||||
const [note, setNote] = useState(report.admin_note);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [confirmBan, setConfirmBan] = useState<boolean | null>(null);
|
||||
const { t } = useT();
|
||||
const closed = report.status === "resolved" || report.status === "dismissed";
|
||||
const isBanned = report.reporter_shadowbanned === 1;
|
||||
|
||||
useEffect(() => {
|
||||
setNote(report.admin_note);
|
||||
@@ -156,8 +158,29 @@ function ReportDetail({ report, onChanged }: { report: Report; onChanged: () =>
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleBan(shadowbanned: boolean) {
|
||||
setBusy(true);
|
||||
setError("");
|
||||
try {
|
||||
await api.adminShadowban(report.reporter_id, shadowbanned);
|
||||
onChanged();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t("reports.updateFailed"));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2 space-y-3 rounded-3xl border border-stone-200/70 bg-stone-50 p-4 dark:border-zinc-800 dark:bg-zinc-900/60">
|
||||
{report.prompt && (
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium uppercase tracking-wider opacity-50">{t("reports.prompt")}</p>
|
||||
<div className="max-h-40 overflow-y-auto whitespace-pre-wrap rounded-2xl border border-stone-200/70 bg-white px-3.5 py-2.5 text-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
{report.prompt}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<p className="mb-1 text-xs font-medium uppercase tracking-wider opacity-50">{t("reports.response")}</p>
|
||||
<div className="max-h-56 overflow-y-auto whitespace-pre-wrap rounded-2xl border border-stone-200/70 bg-white px-3.5 py-2.5 text-sm dark:border-zinc-800 dark:bg-zinc-900">
|
||||
@@ -176,6 +199,26 @@ function ReportDetail({ report, onChanged }: { report: Report; onChanged: () =>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button size="sm" variant="secondary" disabled={busy} onClick={() => setConfirmBan(!isBanned)}>
|
||||
{isBanned ? <Eye size={14} /> : <EyeOff size={14} />}
|
||||
{isBanned ? t("reports.unshadowban") : t("reports.shadowban")}
|
||||
</Button>
|
||||
{isBanned && <span className="text-xs opacity-60">{t("reports.unreliable")}</span>}
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
open={confirmBan !== null}
|
||||
onClose={() => setConfirmBan(null)}
|
||||
onConfirm={() => {
|
||||
const v = confirmBan;
|
||||
setConfirmBan(null);
|
||||
if (v !== null) void toggleBan(v);
|
||||
}}
|
||||
title={t(confirmBan === false ? "reports.unshadowbanTitle" : "reports.shadowbanTitle")}
|
||||
message={t(confirmBan === false ? "reports.unshadowbanMsg" : "reports.shadowbanMsg", { user: report.reporter_name })}
|
||||
confirmLabel={t(confirmBan === false ? "reports.unshadowban" : "reports.shadowban")}
|
||||
/>
|
||||
|
||||
{!closed ? (
|
||||
<div className="flex flex-wrap items-end gap-2 rounded-2xl border border-stone-200 bg-white p-3 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<div className="min-w-48 flex-1">
|
||||
|
||||
@@ -467,6 +467,15 @@ const en = {
|
||||
"reports.btnDismiss": "Dismiss",
|
||||
"reports.updateFailed": "Update failed",
|
||||
"reports.closedNote": "Report {status}.",
|
||||
"report.alreadyReported": "This response was already reported.",
|
||||
"reports.prompt": "User prompt",
|
||||
"reports.unreliable": "Unreliable reporter",
|
||||
"reports.shadowban": "Shadow-ban reporter",
|
||||
"reports.unshadowban": "Lift shadow-ban",
|
||||
"reports.shadowbanTitle": "Shadow-ban this reporter?",
|
||||
"reports.shadowbanMsg": "@{user}'s reports will still be accepted but marked unreliable and hidden from the badge.",
|
||||
"reports.unshadowbanTitle": "Lift the shadow-ban?",
|
||||
"reports.unshadowbanMsg": "@{user}'s reports will count toward the badge again.",
|
||||
|
||||
"review.title": "Access request",
|
||||
"review.loadingTicket": "Loading ticket…",
|
||||
@@ -961,6 +970,15 @@ const es: Dict = {
|
||||
"reports.btnDismiss": "Descartar",
|
||||
"reports.updateFailed": "No se pudo actualizar",
|
||||
"reports.closedNote": "Denuncia {status}.",
|
||||
"report.alreadyReported": "Esta respuesta ya fue denunciada.",
|
||||
"reports.prompt": "Pregunta del usuario",
|
||||
"reports.unreliable": "Denunciante no fiable",
|
||||
"reports.shadowban": "Silenciar denunciante",
|
||||
"reports.unshadowban": "Quitar silencio",
|
||||
"reports.shadowbanTitle": "¿Silenciar a este denunciante?",
|
||||
"reports.shadowbanMsg": "Las denuncias de @{user} se seguirán aceptando, pero se marcarán como no fiables y no contarán en el contador.",
|
||||
"reports.unshadowbanTitle": "¿Quitar el silencio?",
|
||||
"reports.unshadowbanMsg": "Las denuncias de @{user} volverán a contar.",
|
||||
|
||||
"review.title": "Solicitud de acceso",
|
||||
"review.loadingTicket": "Cargando ticket…",
|
||||
@@ -1452,6 +1470,15 @@ const fr: Dict = {
|
||||
"reports.btnDismiss": "Rejeter",
|
||||
"reports.updateFailed": "Mise à jour impossible",
|
||||
"reports.closedNote": "Signalement {status}.",
|
||||
"report.alreadyReported": "Cette réponse a déjà été signalée.",
|
||||
"reports.prompt": "Demande de l'utilisateur",
|
||||
"reports.unreliable": "Signaleur non fiable",
|
||||
"reports.shadowban": "Shadow-ban le signaleur",
|
||||
"reports.unshadowban": "Lever le shadow-ban",
|
||||
"reports.shadowbanTitle": "Shadow-ban ce signaleur ?",
|
||||
"reports.shadowbanMsg": "Les signalements de @{user} seront toujours acceptés mais marqués non fiables et exclus du compteur.",
|
||||
"reports.unshadowbanTitle": "Lever le shadow-ban ?",
|
||||
"reports.unshadowbanMsg": "Les signalements de @{user} compteront à nouveau.",
|
||||
|
||||
"review.title": "Demande d'accès",
|
||||
"review.loadingTicket": "Chargement du ticket…",
|
||||
@@ -1931,6 +1958,15 @@ const ru: Dict = {
|
||||
"reports.btnDismiss": "Отклонить",
|
||||
"reports.updateFailed": "Не обновилось",
|
||||
"reports.closedNote": "Жалоба {status}.",
|
||||
"report.alreadyReported": "На этот ответ уже пожаловались.",
|
||||
"reports.prompt": "Запрос пользователя",
|
||||
"reports.unreliable": "Ненадёжный автор",
|
||||
"reports.shadowban": "Теневой бан",
|
||||
"reports.unshadowban": "Снять теневой бан",
|
||||
"reports.shadowbanTitle": "Теневой бан для этого автора?",
|
||||
"reports.shadowbanMsg": "Жалобы @{user} будут приниматься, но помечаться как ненадёжные и не учитываться в счётчике.",
|
||||
"reports.unshadowbanTitle": "Снять теневой бан?",
|
||||
"reports.unshadowbanMsg": "Жалобы @{user} снова будут учитываться.",
|
||||
|
||||
"review.title": "Заявка на доступ",
|
||||
"review.loadingTicket": "Загружаем тикет…",
|
||||
@@ -2415,6 +2451,15 @@ const it: Dict = {
|
||||
"reports.btnDismiss": "Respingi",
|
||||
"reports.updateFailed": "Aggiornamento fallito",
|
||||
"reports.closedNote": "Segnalazione {status}.",
|
||||
"report.alreadyReported": "Questa risposta è già stata segnalata.",
|
||||
"reports.prompt": "Richiesta dell'utente",
|
||||
"reports.unreliable": "Autore inaffidabile",
|
||||
"reports.shadowban": "Silenzia autore",
|
||||
"reports.unshadowban": "Togli silenzio",
|
||||
"reports.shadowbanTitle": "Silenziare questo autore?",
|
||||
"reports.shadowbanMsg": "Le segnalazioni di @{user} saranno comunque accettate ma marcate come inaffidabili ed escluse dal conteggio.",
|
||||
"reports.unshadowbanTitle": "Togliere il silenzio?",
|
||||
"reports.unshadowbanMsg": "Le segnalazioni di @{user} conteranno di nuovo.",
|
||||
|
||||
"review.title": "Richiesta di accesso",
|
||||
"review.loadingTicket": "Carico ticket…",
|
||||
@@ -2890,6 +2935,15 @@ const pt: Dict = {
|
||||
"reports.btnDismiss": "Rejeitar",
|
||||
"reports.updateFailed": "Falha ao atualizar",
|
||||
"reports.closedNote": "Denúncia {status}.",
|
||||
"report.alreadyReported": "Esta resposta já foi denunciada.",
|
||||
"reports.prompt": "Pergunta do utilizador",
|
||||
"reports.unreliable": "Autor pouco fiável",
|
||||
"reports.shadowban": "Silenciar autor",
|
||||
"reports.unshadowban": "Tirar silêncio",
|
||||
"reports.shadowbanTitle": "Silenciar este autor?",
|
||||
"reports.shadowbanMsg": "As denúncias de @{user} continuarão a ser aceites, mas marcadas como pouco fiáveis e excluídas da contagem.",
|
||||
"reports.unshadowbanTitle": "Tirar o silêncio?",
|
||||
"reports.unshadowbanMsg": "As denúncias de @{user} voltarão a contar.",
|
||||
|
||||
"review.title": "Pedido de acesso",
|
||||
"review.loadingTicket": "A carregar ticket…",
|
||||
|
||||
Reference in New Issue
Block a user