164 lines
9.0 KiB
JavaScript
164 lines
9.0 KiB
JavaScript
import React from 'react'
|
|
import { router, usePage } from '@inertiajs/react'
|
|
import StudioLayout from '../../Layouts/StudioLayout'
|
|
|
|
function formatDate(value) {
|
|
if (!value) return 'Draft'
|
|
|
|
const date = new Date(value)
|
|
if (Number.isNaN(date.getTime())) return 'Draft'
|
|
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
})
|
|
}
|
|
|
|
function statusTone(status) {
|
|
switch (status) {
|
|
case 'published':
|
|
return 'border-emerald-300/20 bg-emerald-400/10 text-emerald-100'
|
|
case 'scheduled':
|
|
return 'border-sky-300/20 bg-sky-400/10 text-sky-100'
|
|
case 'in_review':
|
|
return 'border-amber-300/20 bg-amber-400/10 text-amber-100'
|
|
case 'archived':
|
|
return 'border-white/10 bg-white/[0.05] text-slate-300'
|
|
default:
|
|
return 'border-white/10 bg-white/[0.05] text-slate-300'
|
|
}
|
|
}
|
|
|
|
export default function StudioNewsIndex() {
|
|
const { props } = usePage()
|
|
const items = Array.isArray(props.listing?.items) ? props.listing.items : []
|
|
const filters = props.listing?.filters || {}
|
|
const meta = props.listing?.meta || {}
|
|
|
|
const updateFilter = (next) => {
|
|
router.get('/studio/news', {
|
|
...filters,
|
|
...next,
|
|
page: 1,
|
|
}, {
|
|
preserveState: true,
|
|
preserveScroll: true,
|
|
})
|
|
}
|
|
|
|
return (
|
|
<StudioLayout title={props.title} subtitle={props.description}>
|
|
<section className="rounded-[32px] border border-white/10 bg-[radial-gradient(circle_at_top_left,rgba(56,189,248,0.16),transparent_38%),linear-gradient(180deg,rgba(15,23,42,0.96),rgba(2,6,23,0.9))] p-6 shadow-[0_24px_70px_rgba(2,6,23,0.32)]">
|
|
<div className="flex flex-col gap-5 lg:flex-row lg:items-end lg:justify-between">
|
|
<div className="max-w-3xl">
|
|
<p className="text-[11px] font-semibold uppercase tracking-[0.28em] text-sky-200/75">Editorial surface</p>
|
|
<h2 className="mt-3 text-3xl font-semibold tracking-[-0.04em] text-white">Run a first-party newsroom for launches, tutorials, and community stories.</h2>
|
|
<p className="mt-3 text-sm leading-7 text-slate-300">Pinned stories drive the hero, featured pieces strengthen discovery, and related entity links keep News wired into Groups, releases, collections, and profiles.</p>
|
|
</div>
|
|
<div className="flex flex-wrap gap-3">
|
|
<a href={props.createUrl} className="inline-flex items-center gap-2 rounded-2xl border border-sky-300/20 bg-sky-400/10 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-400/15">
|
|
<i className="fa-solid fa-plus" />
|
|
New article
|
|
</a>
|
|
<a href={props.categoriesUrl} className="inline-flex items-center gap-2 rounded-2xl border border-white/10 bg-white/[0.05] px-5 py-3 text-sm font-semibold text-white transition hover:bg-white/[0.08]">
|
|
<i className="fa-solid fa-tags" />
|
|
Taxonomies
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="mt-6 rounded-[28px] border border-white/10 bg-white/[0.03] p-5">
|
|
<div className="grid gap-4 lg:grid-cols-[minmax(0,1fr)_220px_220px_220px_auto] lg:items-center">
|
|
<label className="grid gap-2 text-sm text-slate-300">
|
|
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Search</span>
|
|
<input
|
|
defaultValue={filters.q || ''}
|
|
placeholder="Search titles, excerpts, and metadata"
|
|
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
|
onKeyDown={(event) => {
|
|
if (event.key === 'Enter') {
|
|
updateFilter({
|
|
q: event.currentTarget.value || '',
|
|
status: filters.status || '',
|
|
type: filters.type || '',
|
|
category_id: filters.category_id || '',
|
|
})
|
|
}
|
|
}}
|
|
/>
|
|
</label>
|
|
<label className="grid gap-2 text-sm text-slate-300">
|
|
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Status</span>
|
|
<select
|
|
value={filters.status || ''}
|
|
onChange={(event) => updateFilter({ status: event.target.value, q: filters.q || '', type: filters.type || '', category_id: filters.category_id || '' })}
|
|
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
|
>
|
|
<option value="">All statuses</option>
|
|
{(Array.isArray(props.statusOptions) ? props.statusOptions : []).map((option) => (
|
|
<option key={option.value} value={option.value}>{option.label}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<label className="grid gap-2 text-sm text-slate-300">
|
|
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Type</span>
|
|
<select
|
|
value={filters.type || ''}
|
|
onChange={(event) => updateFilter({ type: event.target.value, q: filters.q || '', status: filters.status || '', category_id: filters.category_id || '' })}
|
|
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
|
>
|
|
<option value="">All types</option>
|
|
{(Array.isArray(props.typeOptions) ? props.typeOptions : []).map((option) => (
|
|
<option key={option.value} value={option.value}>{option.label}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<label className="grid gap-2 text-sm text-slate-300">
|
|
<span className="text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-500">Category</span>
|
|
<select
|
|
value={filters.category_id || ''}
|
|
onChange={(event) => updateFilter({ category_id: event.target.value, q: filters.q || '', status: filters.status || '', type: filters.type || '' })}
|
|
className="rounded-2xl border border-white/10 bg-black/20 px-4 py-3 text-white outline-none"
|
|
>
|
|
<option value="">All categories</option>
|
|
{(Array.isArray(props.categoryOptions) ? props.categoryOptions : []).map((option) => (
|
|
<option key={option.id} value={option.id}>{option.name}</option>
|
|
))}
|
|
</select>
|
|
</label>
|
|
<div className="text-sm text-slate-400 lg:text-right">{Number(meta.total || 0).toLocaleString()} articles</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
|
{items.length > 0 ? items.map((item) => (
|
|
<article key={item.id} className="overflow-hidden rounded-[24px] border border-white/10 bg-black/20 shadow-[0_18px_40px_rgba(2,6,23,0.18)]">
|
|
<div className="aspect-[16/9] bg-slate-950/60">
|
|
{item.cover_url ? <img src={item.cover_url} alt={item.title} className="h-full w-full object-cover" /> : <div className="flex h-full items-center justify-center text-slate-500"><i className="fa-solid fa-newspaper text-3xl" /></div>}
|
|
</div>
|
|
<div className="p-5">
|
|
<div className="flex flex-wrap items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.16em] text-slate-400">
|
|
<span className="rounded-full border border-white/10 bg-white/[0.04] px-2.5 py-1 text-white/70">{item.type_label}</span>
|
|
<span className={`rounded-full border px-2.5 py-1 ${statusTone(item.editorial_status)}`}>{item.editorial_status.replaceAll('_', ' ')}</span>
|
|
{item.is_pinned ? <span className="rounded-full border border-amber-300/20 bg-amber-400/10 px-2.5 py-1 text-amber-100">Pinned</span> : null}
|
|
{item.is_featured ? <span className="rounded-full border border-emerald-300/20 bg-emerald-400/10 px-2.5 py-1 text-emerald-100">Featured</span> : null}
|
|
</div>
|
|
<h3 className="mt-3 text-xl font-semibold text-white">{item.title}</h3>
|
|
<div className="mt-3 flex flex-wrap gap-3 text-sm text-slate-400">
|
|
{item.category_name ? <span>{item.category_name}</span> : null}
|
|
<span>{item.author_name}</span>
|
|
<span>{formatDate(item.published_at)}</span>
|
|
</div>
|
|
<div className="mt-5 flex flex-wrap gap-2">
|
|
<a href={item.edit_url} className="rounded-full border border-sky-300/20 bg-sky-400/10 px-4 py-2 text-sm font-semibold text-sky-100">Edit</a>
|
|
<a href={item.editorial_status === 'published' ? item.public_url : item.preview_url} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white">{item.editorial_status === 'published' ? 'View' : 'Preview'}</a>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
)) : <div className="rounded-[24px] border border-dashed border-white/10 bg-white/[0.02] p-6 text-sm text-slate-400">No News articles match the current filters.</div>}
|
|
</section>
|
|
</StudioLayout>
|
|
)
|
|
} |