import React, { useEffect, useState } from 'react'
function tagList(item) {
return Array.isArray(item?.tags) ? item.tags.filter(Boolean) : []
}
function categoryList(item) {
return Array.isArray(item?.categories) ? item.categories.filter(Boolean) : []
}
function creatorName(item) {
return item?.user?.username || item?.user?.name || 'Unknown creator'
}
function isMature(item) {
return Boolean(item?.nsfw || item?.is_mature)
}
function formatDate(value) {
if (!value) {
return null
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) {
return null
}
return date.toLocaleString()
}
function PreviewImage({ src, alt, className }) {
const [failed, setFailed] = useState(false)
useEffect(() => {
setFailed(false)
}, [src])
if (!src || failed) {
return (
No preview
)
}
return (
setFailed(true)}
/>
)
}
function Badge({ children, tone = 'slate' }) {
const tones = {
slate: 'border-white/10 bg-white/5 text-white/70',
amber: 'border-amber-300/20 bg-amber-400/10 text-amber-100',
rose: 'border-rose-300/20 bg-rose-400/10 text-rose-100',
emerald: 'border-emerald-300/20 bg-emerald-400/10 text-emerald-100',
}
return (
{children}
)
}
export default function AdminUploadQueue() {
const [items, setItems] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [notes, setNotes] = useState({})
const [selected, setSelected] = useState(null)
const [busyId, setBusyId] = useState(null)
const loadPending = async () => {
setLoading(true)
setError('')
try {
const [legacyResponse, artworkResponse] = await Promise.all([
window.axios.get('/api/admin/uploads/pending'),
window.axios.get('/api/admin/artwork-review/pending'),
])
const legacyItems = Array.isArray(legacyResponse?.data?.data)
? legacyResponse.data.data.map((item) => ({ ...item, queue: 'upload' }))
: []
const artworkItems = Array.isArray(artworkResponse?.data?.data)
? artworkResponse.data.data.map((item) => ({ ...item, queue: 'artwork' }))
: []
setItems([...artworkItems, ...legacyItems])
} catch (loadError) {
setError(loadError?.response?.data?.message || 'Failed to load moderation queue.')
} finally {
setLoading(false)
}
}
useEffect(() => {
loadPending()
}, [])
useEffect(() => {
if (!selected) {
return undefined
}
const onKeyDown = (event) => {
if (event.key === 'Escape') {
setSelected(null)
}
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [selected])
const moderate = async (item, action) => {
const id = item?.id
if (!id || busyId) {
return
}
setBusyId(id)
try {
const payload = { note: String(notes[id] || '') }
const base = item.queue === 'artwork' ? '/api/admin/artwork-review' : '/api/admin/uploads'
await window.axios.post(`${base}/${id}/${action}`, payload)
setItems((prev) => prev.filter((candidate) => !(candidate.id === id && candidate.queue === item.queue)))
setSelected((current) => (current?.id === id && current?.queue === item.queue ? null : current))
} catch (moderateError) {
setError(moderateError?.response?.data?.message || `Failed to ${action} upload.`)
} finally {
setBusyId(null)
}
}
const renderMeta = (item, { compact = false } = {}) => {
const tags = tagList(item)
const categories = categoryList(item)
const createdAt = formatDate(item.created_at)
return (
{item.title || '(untitled upload)'}
{item.queue === 'artwork' ? 'Artwork' : 'Draft upload'}
{isMature(item) ?
NSFW : null}
{creatorName(item)} · {item.type} · {item.id}
{createdAt ?
{createdAt}
: null}
{item.description ? (
{item.description}
) : (
No description
)}
{categories.length > 0 ? (
{categories.join(' · ')}
) : null}
{tags.length > 0 ? (
{(compact ? tags.slice(0, 10) : tags).map((tag) => (
{tag}
))}
) : (
No tags
)}
{item.moderation_note ? (
Review reasons: {item.moderation_note}
) : null}
)
}
const renderActions = (item) => (
)
return (
Pending Upload Moderation
{loading ? Loading…
: null}
{error ? {error}
: null}
{!loading && items.length === 0 ? No pending uploads.
: null}
{items.map((item) => {
const previewSrc = item.preview_url || item.preview_lg_url || null
return (
-
{renderActions(item)}
)
})}
{selected ? (
setSelected(null)}
>
event.stopPropagation()}
>
{selected.title || '(untitled upload)'}
{creatorName(selected)} · {selected.type} · {selected.id}
Description
{selected.description || 'No description'}
Categories
{categoryList(selected).join(', ') || 'None'}
Tags
{tagList(selected).length > 0 ? tagList(selected).map((tag) => (
{tag}
)) : None}
Visibility
{selected.visibility || 'n/a'}
Maturity
{isMature(selected) ? 'NSFW / mature' : (selected.maturity_status || 'safe')}
License
{selected.license || 'n/a'}
Submitted
{formatDate(selected.created_at) || 'n/a'}
{selected.moderation_note ? (
Review reasons
{selected.moderation_note}
) : null}
{renderActions(selected)}
) : null}
)
}