Moderation Queue

Reported content with its report reasons and a decision. Sensitive items are blurred until revealed, because a moderator should choose when to look at the worst thing in the queue.

src/components/ui/moderation-queue.tsx

About

From the component's own source, where it was written.

A review queue for reported content.

Reported content is blurred until revealed. Moderators do not consent to seeing every image that gets flagged, and a queue that renders graphic material on load is a duty-of-care failure — the reveal is per item and deliberate.

Decisions are reported, never applied locally. Two moderators working the same queue must not both act on one item because each interface hid it optimistically; the server decides who won.

The report count and reason distribution are shown together — ten reports for spam is a different problem from ten reports across five unrelated reasons, which usually means brigading.

Install

npx astralyx-ui add moderation-queue

Usage

import { ModerationQueue } from '@/components/ui/moderation-queue'

<ModerationQueue items={items} onDecide={decide} onBulkDecide={decideMany} />

Composer

Change the props and the source below follows.

3 pending
  • user_88213

    user_8821314

    reported

    Check out this totally legitimate investment opportunity, DM me for the link
    spam · 11scam · 3
  • grimwald

    grimwald1

    reported

    Honestly the update broke everything and support has been useless for a week.
    harassment · 1
  • anon_4412

    anon_44126

    reported

    [graphic image description withheld]
    graphic · 5self-harm · 1
  • kestrel

    kestrel2Approved

    reported

    Reposting because the first one got buried — free giveaway, 100 winners!
    spam · 2
Props
tsx
<ModerationQueue items={items} onDecide={decide} onBulkDecide={decideMany} />

Examples

A queue with bulk actions

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

ModerationQueue

Extends Omit<ComponentProps<'div'>, 'onSelect'>.

PropTypeDefaultDescription
itemsModerationItem[]required{ id, author, content, sensitive?, reportedAt, reportCount?, reasons?, status? }.
onDecide(id: string, decision: 'approve' | 'remove') => void—'approve' | 'remove' for one item.
onBulkDecide(ids: string[], decision: 'approve' | 'remove') => void—Enables selection. Omit it and each item is decided on its own, which is right for a small, high-stakes queue.
nowDate——
localestring'en-GB'—
emptyLabelReactNode'Queue is clear'—
selectAllLabelstring'Select all pending'—
clearSelectionLabelstring'Clear selection'—
selectItemLabel(id: string) => stringDEFAULT_SELECT_ITEM_LABEL—
selectedLabel(count: number) => ReactNodeDEFAULT_SELECTED_LABEL—
pendingLabel(count: number) => ReactNodeDEFAULT_PENDING_LABEL—
approveLabelReactNode'Approve'—
removeLabelReactNode'Remove'—
approvedLabelReactNode'Approved'—
removedLabelReactNode'Removed'—
reportedLabelReactNode'reported'Precedes the report time.
revealLabelReactNode'Reveal reported content'Button that unblurs a sensitive item.

Types

ModerationItem

A review queue for reported content.

FieldTypeDescription
idstring—
authorReactNode—
authorAvatar?ReactNode—
contentReactNode—
sensitive?booleanBlurs the content behind a reveal. The reason and report count stay visible, which is often enough to decide without looking.
reportedAtDate—
reportCount?number—
reasons?Record<string, number>Reason to count. Fourteen spam reports and one harassment report are a different case from the reverse.
status?'pending' | 'approved' | 'removed'—