import { Link } from 'react-router-dom'; import { Flag, EyeOff, BadgeCheck, Undo2, Loader2, ExternalLink, CheckCircle2, ShieldQuestion, } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { useAuthor } from '@/hooks/useAuthor'; import { useRecentReports, reportTargetMeta, type RecentReport } from '@/hooks/useRecentReports'; import { useAdminHide, useAdminVerify, useAdminClear } from '@/hooks/useAdminHide'; import { useAdminModeration } from '@/hooks/useAdminModeration'; import { useToast } from '@/hooks/useToast'; import { useTranslation } from '@/contexts/language'; import { genUserName } from '@/lib/genUserName'; /** "3 h ago" — uses the shared `routeUi.age*` keys so it localizes. */ function ago(unixSeconds: number, t: (key: string, params?: Record) => string): string { const mins = Math.round((Date.now() / 1000 - unixSeconds) / 60); if (mins < 1) return t('routeUi.ageJustNow'); if (mins < 60) return t('routeUi.ageMinAgo', { count: mins }); const hours = Math.round(mins / 60); if (hours < 24) return t('routeUi.ageHourAgo', { count: hours }); return t('routeUi.ageDayAgo', { count: Math.round(hours / 24) }); } function Reporter({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const name = author.data?.metadata?.name ?? author.data?.metadata?.display_name ?? genUserName(pubkey); return {name}; } function ReportRow({ report }: { report: RecentReport }) { const { t } = useTranslation(); const { hideContent, isPending: hiding } = useAdminHide(); const { confirmContent, isPending: confirming } = useAdminVerify(); const { clearVerdict, isPending: clearing } = useAdminClear(); const moderation = useAdminModeration(); const { toast } = useToast(); const { target } = report; const meta = target ? reportTargetMeta(target.kind) : null; const coord = target?.coord; const hidden = !!target && moderation.has({ coord, id: target.id }); const verified = !!target && moderation.isVerified({ coord, id: target.id }); const busy = hiding || confirming || clearing; const act = async (fn: () => Promise, title: string) => { try { await fn(); toast({ title }); } catch (e) { toast({ title: t('toastsUi.couldNotComplete'), description: e instanceof Error ? e.message : t('toastsUi.tryAgainLower'), variant: 'destructive', }); } }; return (
{report.reason} {hidden && ( {t('toastsUi.hiddenToast')} )} {verified && ( {t('toastsUi.confirmed')} )} {t('miscUi.reportedBy')} · {ago(report.createdAt, t)}
{/* What was reported */}
{target ? ( <>

{target.title || t('legacyMiscUi.reportsUntitled')} {meta?.label && ( {meta.label} )}

{report.content && (

{report.content}

)} ) : (

{t('legacyMiscUi.reportsResolveFailed')} {report.content && “{report.content}”}

)}
{target && !hidden && !verified && ( )} {target && !hidden && !verified && ( )} {target && (hidden || verified) && ( )} {target && meta?.path && target.identifier && ( )}
); } /** * The report inbox tab. Independent of the heuristic queue: anything anyone * reported shows up here, resolvable or not. */ export function ReportsInbox() { const { t } = useTranslation(); const { data: reports, isLoading } = useRecentReports(200); if (isLoading) { return (
); } if (!reports || reports.length === 0) { return (

{t('legacyMiscUi.reportsNoReports')}

); } return (
{reports.map((r) => ( ))}
); }