import React from 'react'
import { Head, usePage } from '@inertiajs/react'
import Checkbox from '../../components/ui/Checkbox'
import DateTimePicker from '../../components/ui/DateTimePicker'
import NovaSelect from '../../components/ui/NovaSelect'
const FILTER_OPTIONS = [
{ value: 'all', label: 'All rows' },
{ value: 'winner', label: 'Winner' },
{ value: 'active', label: 'Active' },
{ value: 'eligible', label: 'Eligible' },
{ value: 'attention', label: 'Needs attention' },
{ value: 'ineligible', label: 'Ineligible' },
{ value: 'expired', label: 'Expired' },
{ value: 'inactive', label: 'Inactive' },
]
const SORT_OPTIONS = [
{ value: 'priority', label: 'Priority' },
{ value: 'featured_at', label: 'Featured Since' },
{ value: 'expires_at', label: 'Expires' },
{ value: 'score_30d', label: 'Medal Score (30d)' },
]
const PRIORITY_PRESETS = [60, 100, 180, 260, 340]
const PAGE_SIZE = 24
function cn(...values) {
return values.filter(Boolean).join(' ')
}
function getCsrfToken() {
if (typeof document === 'undefined') return ''
return document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
}
async function requestJson(url, { method = 'POST', body } = {}) {
const response = await fetch(url, {
method,
credentials: 'same-origin',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-CSRF-TOKEN': getCsrfToken(),
'X-Requested-With': 'XMLHttpRequest',
},
body: body ? JSON.stringify(body) : undefined,
})
const payload = await response.json().catch(() => ({}))
if (!response.ok) {
throw new Error(payload?.message || payload?.errors?.artwork_id?.[0] || payload?.errors?.is_active?.[0] || payload?.errors?.force_hero?.[0] || 'Request failed.')
}
return payload
}
function isoToLocalInput(value) {
if (!value) return ''
const date = new Date(value)
if (Number.isNaN(date.getTime())) return ''
const local = new Date(date.getTime() - (date.getTimezoneOffset() * 60000))
return local.toISOString().slice(0, 16)
}
function localInputToIso(value) {
if (!value) return null
const date = new Date(value)
if (Number.isNaN(date.getTime())) return null
return date.toISOString()
}
function formatDateTime(value) {
if (!value) return '—'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return '—'
return new Intl.DateTimeFormat('en', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(date)
}
function formatShortDate(value) {
if (!value) return 'No expiry'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return 'No expiry'
return new Intl.DateTimeFormat('en', {
month: 'short',
day: 'numeric',
}).format(date)
}
function formatRelativeExpiry(value) {
if (!value) return 'No expiry'
const date = new Date(value)
if (Number.isNaN(date.getTime())) return 'No expiry'
const now = new Date()
const diffInDays = Math.ceil((date.getTime() - now.getTime()) / 86400000)
if (diffInDays < 0) return `${Math.abs(diffInDays)}d overdue`
if (diffInDays === 0) return 'Due today'
if (diffInDays === 1) return 'Due tomorrow'
return `${diffInDays}d left`
}
function formatOwner(entry) {
if (!entry?.artwork?.owner) return 'Unknown artist'
if (entry.artwork.owner.type === 'group') return `${entry.artwork.owner.display_name || 'Unknown'} · Group publisher`
return `${entry.artwork.owner.display_name || 'Unknown'} · @${entry.artwork.owner.username || ''}`
}
function compareWinnerOrder(left, right) {
if (Boolean(left?.is_force_hero) !== Boolean(right?.is_force_hero)) {
return Boolean(right?.is_force_hero) - Boolean(left?.is_force_hero)
}
const priorityDiff = Number(right?.priority || 0) - Number(left?.priority || 0)
if (priorityDiff !== 0) return priorityDiff
const scoreDiff = Number(right?.medals?.score_30d || 0) - Number(left?.medals?.score_30d || 0)
if (scoreDiff !== 0) return scoreDiff
const featuredDiff = (new Date(right?.featured_at || 0).getTime() || 0) - (new Date(left?.featured_at || 0).getTime() || 0)
if (featuredDiff !== 0) return featuredDiff
const publishedDiff = (new Date(right?.artwork?.published_at || 0).getTime() || 0) - (new Date(left?.artwork?.published_at || 0).getTime() || 0)
if (publishedDiff !== 0) return publishedDiff
return Number(right?.id || 0) - Number(left?.id || 0)
}
function compareEntries(left, right, sortKey, direction) {
const dir = direction === 'asc' ? 1 : -1
const value = (entry) => {
switch (sortKey) {
case 'featured_at':
return new Date(entry.featured_at || 0).getTime() || 0
case 'expires_at':
return new Date(entry.expires_at || 0).getTime() || 0
case 'score_30d':
return Number(entry.medals?.score_30d || 0)
default:
return Number(entry.priority || 0)
}
}
const leftValue = value(left)
const rightValue = value(right)
if (leftValue !== rightValue) {
return (leftValue > rightValue ? 1 : -1) * dir
}
return compareWinnerOrder(left, right)
}
function buildEntrySummary(entry) {
if (entry?.is_force_hero) {
return 'Force Hero is enabled, so this row overrides the normal homepage winner order until editors switch it off.'
}
if (entry?.is_winner && entry?.winner_reason) {
return entry.winner_reason
}
if (!entry?.is_active) {
return 'Inactive rows stay visible for planning and editing, but they cannot win the homepage slot.'
}
if (entry?.is_expired) {
return 'This featured slot has expired and is excluded from the live homepage rotation.'
}
if (!entry?.eligibility?.is_eligible) {
const reasons = Array.isArray(entry?.eligibility?.reasons) && entry.eligibility.reasons.length > 0
? entry.eligibility.reasons.join(', ')
: 'Eligibility checks failed.'
return `Currently blocked from winning: ${reasons}`
}
return 'Eligible and in the active selection pool. Priority leads first, then medal score, featured time, and publish date.'
}
function Badge({ label, tone = 'slate' }) {
const toneClasses = {
slate: 'border-white/10 bg-white/[0.07] text-slate-100',
sky: 'border-sky-300/20 bg-sky-400/15 text-sky-100',
emerald: 'border-emerald-300/20 bg-emerald-400/15 text-emerald-100',
amber: 'border-amber-300/20 bg-amber-400/15 text-amber-100',
rose: 'border-rose-300/20 bg-rose-400/15 text-rose-100',
}
return (
{label}
)
}
function Field({ label, help, children }) {
return (
{label}
{children}
{help ? {help} : null}
)
}
function NoticeBanner({ notice }) {
if (!notice?.message) return null
const toneClasses = {
success: 'border-emerald-300/20 bg-emerald-400/10 text-emerald-50',
error: 'border-rose-300/20 bg-rose-400/10 text-rose-50',
info: 'border-sky-300/20 bg-sky-400/10 text-sky-50',
}
return (
{notice.message}
)
}
function StatCard({ label, value, detail, tone = 'sky' }) {
const toneClasses = {
sky: 'border-sky-300/15 bg-sky-400/10 text-sky-100',
amber: 'border-amber-300/15 bg-amber-400/10 text-amber-100',
emerald: 'border-emerald-300/15 bg-emerald-400/10 text-emerald-100',
rose: 'border-rose-300/15 bg-rose-400/10 text-rose-100',
}
return (
{label}
{value}
{detail ?
{detail}
: null}
)
}
function MetricTile({ label, value, hint }) {
return (
{label}
{value}
{hint ?
{hint}
: null}
)
}
function DockButton({ label, detail, tone = 'sky', onClick }) {
const toneClasses = {
sky: 'border-sky-300/25 bg-sky-400/12 text-sky-50',
amber: 'border-amber-300/25 bg-amber-400/12 text-amber-50',
slate: 'border-white/10 bg-white/[0.05] text-slate-100',
}
return (
{label}
{detail}
)
}
function FilterChip({ label, value, active, onClick, count }) {
return (
onClick(value)}
className={cn(
'inline-flex items-center gap-2 rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.18em] transition',
active
? 'border-sky-300/40 bg-sky-400/15 text-sky-50 shadow-[0_10px_30px_rgba(56,189,248,0.16)]'
: 'border-white/10 bg-white/[0.03] text-slate-300 hover:border-white/20 hover:bg-white/[0.06]'
)}
>
{label}
{count}
)
}
function emptyForm() {
return {
artwork_id: '',
priority: 100,
featured_at: isoToLocalInput(new Date().toISOString()),
expires_at: '',
is_active: true,
}
}
function mapEntryToCandidate(entry) {
if (!entry) return null
return {
...entry.artwork,
medals: entry.medals,
eligibility: entry.eligibility,
existing_feature_count: entry.duplicate_count,
already_featured: entry.duplicate_count > 0,
}
}
export default function FeaturedArtworksAdmin() {
const { props } = usePage()
const composerRef = React.useRef(null)
const rosterRef = React.useRef(null)
const loadMoreRef = React.useRef(null)
const endpoints = props.endpoints || {}
const capabilities = props.capabilities || {}
const seo = props.seo || {}
const [entries, setEntries] = React.useState(Array.isArray(props.entries) ? props.entries : [])
const [winner, setWinner] = React.useState(props.winner || null)
const [stats, setStats] = React.useState(props.stats || {})
const [notice, setNotice] = React.useState(null)
const [busy, setBusy] = React.useState('')
const [filter, setFilter] = React.useState('all')
const [sortKey, setSortKey] = React.useState('priority')
const [sortDirection, setSortDirection] = React.useState('desc')
const [listQuery, setListQuery] = React.useState('')
const deferredListQuery = React.useDeferredValue(listQuery)
const [searchQuery, setSearchQuery] = React.useState('')
const [searchResults, setSearchResults] = React.useState([])
const [selectedArtwork, setSelectedArtwork] = React.useState(null)
const [editingId, setEditingId] = React.useState(null)
const [form, setForm] = React.useState(emptyForm())
const [visibleCount, setVisibleCount] = React.useState(PAGE_SIZE)
React.useEffect(() => {
setEntries(Array.isArray(props.entries) ? props.entries : [])
setWinner(props.winner || null)
setStats(props.stats || {})
}, [props.entries, props.stats, props.winner])
React.useEffect(() => {
setVisibleCount(PAGE_SIZE)
}, [deferredListQuery, filter, sortDirection, sortKey])
function scrollToSection(ref) {
ref.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
function scrollToTop() {
if (typeof window !== 'undefined') {
window.scrollTo({ top: 0, behavior: 'smooth' })
}
}
function syncPayload(payload) {
setEntries(Array.isArray(payload.entries) ? payload.entries : [])
setWinner(payload.winner || null)
setStats(payload.stats || {})
if (payload.message) {
setNotice({ tone: 'success', message: payload.message })
}
}
function resetEditor() {
setEditingId(null)
setSelectedArtwork(null)
setSearchResults([])
setSearchQuery('')
setForm(emptyForm())
}
async function handleArtworkSearch(event) {
event.preventDefault()
if (!searchQuery.trim()) {
setSearchResults([])
return
}
setBusy('search')
setNotice(null)
try {
const url = `${endpoints.search}?q=${encodeURIComponent(searchQuery.trim())}`
const payload = await requestJson(url, { method: 'GET' })
const results = Array.isArray(payload.results) ? payload.results : []
setSearchResults(results)
if (results.length === 0) {
setNotice({ tone: 'info', message: 'No artworks matched that search.' })
}
} catch (error) {
setNotice({ tone: 'error', message: error.message || 'Artwork search failed.' })
} finally {
setBusy('')
}
}
function chooseArtwork(artwork) {
setSelectedArtwork(artwork)
setForm((current) => ({
...current,
artwork_id: artwork.id,
}))
}
function editEntry(entry) {
setEditingId(entry.id)
setSelectedArtwork(mapEntryToCandidate(entry))
setSearchResults([])
setSearchQuery('')
setForm({
artwork_id: entry.artwork_id,
priority: entry.priority,
featured_at: isoToLocalInput(entry.featured_at),
expires_at: isoToLocalInput(entry.expires_at),
is_active: Boolean(entry.is_active),
})
scrollToSection(composerRef)
}
async function handleSubmit(event) {
event.preventDefault()
if (!editingId && !form.artwork_id) {
setNotice({ tone: 'error', message: 'Select an artwork first.' })
return
}
setBusy('submit')
setNotice(null)
try {
const payload = await requestJson(
editingId
? endpoints.updatePattern.replace('__FEATURE__', String(editingId))
: endpoints.store,
{
method: editingId ? 'PATCH' : 'POST',
body: {
artwork_id: Number(form.artwork_id),
priority: Number(form.priority || 0),
featured_at: localInputToIso(form.featured_at),
expires_at: localInputToIso(form.expires_at),
is_active: Boolean(form.is_active),
},
},
)
syncPayload(payload)
resetEditor()
} catch (error) {
setNotice({ tone: 'error', message: error.message || 'Failed to save this featured entry.' })
} finally {
setBusy('')
}
}
async function handleToggle(entry) {
setBusy(`toggle-${entry.id}`)
setNotice(null)
try {
const payload = await requestJson(endpoints.togglePattern.replace('__FEATURE__', String(entry.id)), {
method: 'PATCH',
})
syncPayload(payload)
} catch (error) {
setNotice({ tone: 'error', message: error.message || 'Failed to change active state.' })
} finally {
setBusy('')
}
}
async function handleDelete(entry) {
if (typeof window !== 'undefined' && !window.confirm(`Delete featured entry #${entry.id}?`)) {
return
}
setBusy(`delete-${entry.id}`)
setNotice(null)
try {
const payload = await requestJson(endpoints.destroyPattern.replace('__FEATURE__', String(entry.id)), {
method: 'DELETE',
})
syncPayload(payload)
if (editingId === entry.id) {
resetEditor()
}
} catch (error) {
setNotice({ tone: 'error', message: error.message || 'Failed to delete this featured entry.' })
} finally {
setBusy('')
}
}
async function handleForceHero(entry) {
setBusy(`force-${entry.id}`)
setNotice(null)
try {
const payload = await requestJson(endpoints.forceHeroPattern.replace('__FEATURE__', String(entry.id)), {
method: 'PATCH',
})
syncPayload(payload)
} catch (error) {
setNotice({ tone: 'error', message: error.message || 'Failed to change force hero state.' })
} finally {
setBusy('')
}
}
const duplicateSelection = !editingId && selectedArtwork?.already_featured
const suggestedPriority = winner ? Number(winner.priority || 0) + 20 : 120
const filterCounts = React.useMemo(() => ({
all: entries.length,
winner: entries.filter((entry) => Boolean(entry.is_winner)).length,
active: entries.filter((entry) => Boolean(entry.is_active)).length,
eligible: entries.filter((entry) => Boolean(entry.eligibility?.is_eligible)).length,
attention: entries.filter((entry) => Boolean(entry.is_active) && (Boolean(entry.is_expired) || !entry.eligibility?.is_eligible)).length,
ineligible: entries.filter((entry) => !entry.eligibility?.is_eligible).length,
expired: entries.filter((entry) => Boolean(entry.is_expired)).length,
inactive: entries.filter((entry) => !entry.is_active).length,
}), [entries])
const activeForceHeroEntry = React.useMemo(() => entries.find((entry) => Boolean(entry.is_force_hero)) || null, [entries])
const soonExpiringEntries = React.useMemo(() => {
const now = Date.now()
const cutoff = now + (7 * 86400000)
return entries
.filter((entry) => {
if (!entry.expires_at || entry.is_expired) return false
const expiresAt = new Date(entry.expires_at).getTime()
return !Number.isNaN(expiresAt) && expiresAt >= now && expiresAt <= cutoff
})
.sort((left, right) => (new Date(left.expires_at).getTime() || 0) - (new Date(right.expires_at).getTime() || 0))
}, [entries])
const naturalFallback = React.useMemo(() => {
return [...entries]
.filter((entry) => Boolean(entry.is_active) && !Boolean(entry.is_expired) && Boolean(entry.eligibility?.is_eligible) && !Boolean(entry.is_winner))
.sort(compareWinnerOrder)[0] || null
}, [entries])
const attentionEntries = React.useMemo(() => {
return [...entries]
.filter((entry) => Boolean(entry.is_active) && (Boolean(entry.is_expired) || !entry.eligibility?.is_eligible))
.sort(compareWinnerOrder)
.slice(0, 3)
}, [entries])
const selectedArtworkSignals = React.useMemo(() => {
if (!selectedArtwork) return []
const signals = []
const eligibilityReasons = Array.isArray(selectedArtwork.eligibility?.reasons) ? selectedArtwork.eligibility.reasons : []
signals.push({
label: selectedArtwork.eligibility?.is_eligible ? 'Ready for rotation now' : 'Needs editorial attention',
tone: selectedArtwork.eligibility?.is_eligible ? 'emerald' : 'rose',
detail: selectedArtwork.eligibility?.is_eligible
? 'Current eligibility checks pass.'
: eligibilityReasons.join(', ') || 'Eligibility checks are failing.',
})
if (winner) {
const priorityGap = Number(form.priority || 0) - Number(winner.priority || 0)
signals.push({
label: priorityGap >= 0 ? 'Priority matches or exceeds current winner' : 'Lower priority than current winner',
tone: priorityGap >= 0 ? 'sky' : 'amber',
detail: `Current winner priority: ${winner.priority}. Proposed priority: ${Number(form.priority || 0)}.`,
})
}
if (selectedArtwork.already_featured) {
signals.push({
label: 'Existing featured row found',
tone: 'amber',
detail: `This artwork already appears in ${selectedArtwork.existing_feature_count || 1} featured row${selectedArtwork.existing_feature_count === 1 ? '' : 's'}.`,
})
}
return signals
}, [form.priority, selectedArtwork, winner])
const filteredEntries = React.useMemo(() => {
const query = deferredListQuery.trim().toLowerCase()
return [...entries]
.filter((entry) => {
if (filter === 'active') return Boolean(entry.is_active)
if (filter === 'inactive') return !entry.is_active
if (filter === 'expired') return Boolean(entry.is_expired)
if (filter === 'winner') return Boolean(entry.is_winner)
if (filter === 'eligible') return Boolean(entry.eligibility?.is_eligible)
if (filter === 'ineligible') return !entry.eligibility?.is_eligible
if (filter === 'attention') return Boolean(entry.is_active) && (Boolean(entry.is_expired) || !entry.eligibility?.is_eligible)
return true
})
.filter((entry) => {
if (!query) return true
const haystack = [
entry.artwork?.title,
entry.artwork?.owner?.display_name,
entry.artwork?.owner?.username,
entry.artwork?.id,
entry.id,
].join(' ').toLowerCase()
return haystack.includes(query)
})
.sort((left, right) => compareEntries(left, right, sortKey, sortDirection))
}, [deferredListQuery, entries, filter, sortDirection, sortKey])
const visibleEntries = React.useMemo(() => filteredEntries.slice(0, visibleCount), [filteredEntries, visibleCount])
const hasMoreEntries = visibleEntries.length < filteredEntries.length
React.useEffect(() => {
if (!hasMoreEntries || typeof IntersectionObserver === 'undefined' || !loadMoreRef.current) {
return undefined
}
const observer = new IntersectionObserver((observerEntries) => {
if (observerEntries.some((observerEntry) => observerEntry.isIntersecting)) {
setVisibleCount((current) => Math.min(current + PAGE_SIZE, filteredEntries.length))
}
}, { rootMargin: '320px 0px' })
observer.observe(loadMoreRef.current)
return () => observer.disconnect()
}, [filteredEntries.length, hasMoreEntries, visibleCount])
function loadMoreEntries() {
setVisibleCount((current) => Math.min(current + PAGE_SIZE, filteredEntries.length))
}
return (
<>
{seo.title || 'Featured Artworks'}
{seo.description ? : null}
{seo.robots ? : null}
{seo.canonical ? : null}
Featured Artworks
Curate the homepage with live winner context, not guesswork.
This workspace now behaves like an editorial command center: current winner visibility, natural fallback awareness, faster filters, and a cleaner composer for new or existing featured rows.
scrollToSection(composerRef)} className="rounded-full bg-white px-5 py-3 text-sm font-semibold text-slate-950 transition hover:bg-slate-100">
Add or edit featured row
{
React.startTransition(() => setFilter('attention'))
scrollToSection(rosterRef)
}}
className="rounded-full border border-white/10 px-5 py-3 text-sm font-semibold text-slate-100 transition hover:border-white/20 hover:bg-white/[0.06]"
>
Review attention items
{winner?.artwork?.canonical_url ? (
Open current winner
) : null}
0 ? 'rose' : 'sky'} />
Current Homepage Hero
{winner ? winner.artwork?.title : 'No eligible featured artwork'}
{winner?.selection_reason || 'There is no active, non-expired, eligible featured artwork right now.'}
{winner ? : }
{winner?.is_force_hero ? : }
{winner?.is_force_hero ? (
Force Hero is active. This row bypasses the normal ranking until editors disable the override from the roster below.
) : null}
Editorial Radar
Know what happens next before you touch a row.
Natural fallback
{naturalFallback?.artwork?.title || 'No fallback candidate'}
{naturalFallback ? `${formatOwner(naturalFallback)} · Priority ${naturalFallback.priority} · Medal ${naturalFallback.medals?.score_30d || 0}` : 'All remaining rows are either inactive, expired, or ineligible.'}
Needs attention
{attentionEntries.length === 0 ? (
No active rows are currently blocked by expiry or eligibility rules.
) : (
{attentionEntries.map((entry) => (
{
React.startTransition(() => {
setFilter('attention')
setListQuery(String(entry.artwork?.id || entry.artwork_id || entry.id))
})
scrollToSection(rosterRef)
}}
className="block w-full rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-left transition hover:border-white/20 hover:bg-white/[0.05]"
>
{entry.artwork?.title || `Featured row #${entry.id}`}
{buildEntrySummary(entry)}
))}
)}
Next expiry checkpoint
{soonExpiringEntries[0]?.artwork?.title || 'Nothing expiring soon'}
{soonExpiringEntries[0] ? `${formatRelativeExpiry(soonExpiringEntries[0].expires_at)} · ${formatDateTime(soonExpiringEntries[0].expires_at)}` : 'No featured rows expire in the next 7 days.'}
Featured Pool
Every featured row, with enough context to act quickly.
Showing {visibleEntries.length} of {filteredEntries.length} matching rows. {entries.length} rows in the full pool.
setListQuery(event.target.value)}
placeholder="Filter by title, artist, row ID, or artwork ID"
className="rounded-2xl border border-white/10 bg-[#08111d] px-4 py-3 text-sm text-white outline-none transition focus:border-sky-300/40"
/>
setSortKey(value)} searchable={false} options={SORT_OPTIONS} />
setSortDirection((current) => current === 'desc' ? 'asc' : 'desc')} className="rounded-2xl border border-white/10 px-4 py-3 text-sm font-semibold text-slate-100 transition hover:border-white/20 hover:bg-white/5">
{sortDirection === 'desc' ? 'Desc' : 'Asc'}
{FILTER_OPTIONS.map((option) => (
React.startTransition(() => setFilter(nextValue))}
count={filterCounts[option.value] ?? 0}
/>
))}
{filteredEntries.length === 0 ? (
No featured entries match the current search and filter combination.
) : visibleEntries.map((entry) => (
{entry.artwork?.title || 'Missing artwork'}
Artwork #{entry.artwork?.id || entry.artwork_id}
Row #{entry.id}
{formatOwner(entry)}
{buildEntrySummary(entry)}
{(entry.status_badges || []).map((badge, index) => (
))}
Visibility: {entry.artwork?.visibility || '—'}
•
Published: {entry.artwork?.published_at ? 'Yes' : 'No'}
Open artwork
editEntry(entry)} className="rounded-full border border-white/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-100 transition hover:border-white/20 hover:bg-white/5">
Edit row
{capabilities.forceHeroEnabled ? (
handleForceHero(entry)} disabled={busy === `force-${entry.id}`} className={cn('rounded-full border px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] transition disabled:cursor-not-allowed disabled:opacity-60', entry.is_force_hero ? 'border-amber-300/25 text-amber-100 hover:border-amber-300/40 hover:bg-amber-400/10' : 'border-amber-300/15 text-amber-50 hover:border-amber-300/30 hover:bg-amber-400/5')}>
{busy === `force-${entry.id}` ? 'Saving…' : entry.is_force_hero ? 'Disable Force Hero' : 'Force Hero'}
) : null}
handleToggle(entry)} disabled={busy === `toggle-${entry.id}`} className="rounded-full border border-sky-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-sky-100 transition hover:border-sky-300/40 hover:bg-sky-400/10 disabled:cursor-not-allowed disabled:opacity-60">
{busy === `toggle-${entry.id}` ? 'Saving…' : entry.is_active ? 'Deactivate' : 'Activate'}
handleDelete(entry)} disabled={busy === `delete-${entry.id}`} className="rounded-full border border-rose-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-rose-100 transition hover:border-rose-300/40 hover:bg-rose-400/10 disabled:cursor-not-allowed disabled:opacity-60">
{busy === `delete-${entry.id}` ? 'Deleting…' : 'Delete'}
))}
{hasMoreEntries ? (
Loading more rows as you reach the bottom.
Load 24 more
) : filteredEntries.length > PAGE_SIZE ? (
All matching rows loaded
) : null}
Editorial Composer
{editingId ? `Edit featured row #${editingId}` : 'Build a new featured row'}
Search, inspect readiness, choose timing, and ship the change without leaving the page context.
{editingId ? (
Cancel edit
) : null}
{!editingId ? (
) : null}
{selectedArtwork ? (
Selected Artwork
{selectedArtwork.title}
#{selectedArtwork.id} · {selectedArtwork.owner?.display_name || 'Unknown'} · Medal Score (30d): {selectedArtwork.medals?.score_30d || 0}
{(selectedArtwork.eligibility?.is_eligible ? [{ label: 'Currently eligible', tone: 'emerald' }] : [{ label: 'Currently ineligible', tone: 'rose' }]).concat(
(selectedArtwork.eligibility?.reasons || []).map((reason) => ({
label: reason,
tone: reason === 'Missing preview' ? 'rose' : 'slate',
}))
).map((badge) => (
))}
{selectedArtwork.canonical_url ? (
Open artwork
) : null}
{selectedArtwork.already_featured ? (
{
React.startTransition(() => {
setFilter('all')
setListQuery(String(selectedArtwork?.id || ''))
})
scrollToSection(rosterRef)
}}
className="rounded-full border border-amber-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-amber-50 transition hover:border-amber-300/40 hover:bg-amber-400/10"
>
Find existing row
) : null}
{selectedArtworkSignals.length > 0 ? (
{selectedArtworkSignals.map((signal) => (
))}
) : null}
) : null}
{duplicateSelection ? (
This artwork already has a featured entry. Edit the existing row instead of creating a duplicate.
{
React.startTransition(() => {
setFilter('all')
setListQuery(String(selectedArtwork?.id || ''))
})
scrollToSection(rosterRef)
}}
className="rounded-full border border-amber-300/20 px-4 py-2 text-xs font-semibold uppercase tracking-[0.16em] text-amber-50 transition hover:border-amber-300/40 hover:bg-amber-400/10"
>
Find existing row
) : null}
{
React.startTransition(() => setFilter('attention'))
scrollToSection(rosterRef)
}}
/>
scrollToSection(composerRef)}
/>
>
)
}