Files
SkinbaseNova/resources/js/Pages/Admin/HomepageAnnouncements/Index.jsx

110 lines
6.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react'
import { Head, Link, router, usePage } from '@inertiajs/react'
import AdminLayout from '../../../Layouts/AdminLayout'
import HomepageAnnouncement from '../../../components/homepage/HomepageAnnouncement'
function formatDateRange(startsAt, endsAt) {
const formatter = new Intl.DateTimeFormat('en-GB', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })
const start = startsAt ? formatter.format(new Date(startsAt)) : 'Now'
const end = endsAt ? formatter.format(new Date(endsAt)) : 'Open ended'
return `${start}${end}`
}
function StatusBadge({ status, active }) {
const tone = status === 'published'
? 'border-emerald-300/20 bg-emerald-300/10 text-emerald-100'
: status === 'archived'
? 'border-amber-300/20 bg-amber-300/10 text-amber-100'
: 'border-slate-300/15 bg-slate-300/10 text-slate-200'
return (
<span className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] ${tone}`}>
<span className={`h-2 w-2 rounded-full ${active ? 'bg-emerald-300' : 'bg-slate-500'}`} />
{status}
</span>
)
}
export default function HomepageAnnouncementsIndex({ announcements, createUrl }) {
const { props } = usePage()
const flash = props.flash ?? {}
return (
<AdminLayout title="Homepage Announcements" subtitle="Schedule launch cards, homepage notices, and editorial announcements below the featured artwork hero.">
<Head title="Admin · Homepage Announcements" />
{flash.success ? <div className="mb-6 rounded-2xl border border-emerald-300/20 bg-emerald-300/10 px-4 py-3 text-sm text-emerald-100">{flash.success}</div> : null}
{flash.error ? <div className="mb-6 rounded-2xl border border-rose-300/20 bg-rose-300/10 px-4 py-3 text-sm text-rose-100">{flash.error}</div> : null}
<div className="mb-6 flex items-center justify-between gap-4">
<div className="max-w-2xl text-sm leading-6 text-slate-400">
Only the highest-priority published announcement that is active and inside its visibility window appears on the homepage.
</div>
<Link href={createUrl} className="rounded-full border border-sky-300/20 bg-sky-300/12 px-5 py-3 text-sm font-semibold text-sky-100 transition hover:bg-sky-300/18">
Create announcement
</Link>
</div>
<div className="space-y-6">
{(announcements?.data || []).length === 0 ? (
<div className="rounded-[28px] border border-white/10 bg-white/[0.03] px-6 py-10 text-center text-slate-400">
No homepage announcements exist yet.
</div>
) : (announcements.data.map((announcement) => (
<article key={announcement.id} className="overflow-hidden rounded-[30px] border border-white/10 bg-white/[0.03]">
<div className="grid gap-6 border-b border-white/8 px-6 py-6 lg:grid-cols-[minmax(0,1.2fr)_auto] lg:items-start">
<div>
<div className="flex flex-wrap items-center gap-3">
<StatusBadge status={announcement.status} active={announcement.is_active} />
<span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70">{announcement.type}</span>
<span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70">Priority {announcement.priority}</span>
<span className="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.18em] text-white/70">Dismiss v{announcement.dismiss_version}</span>
</div>
<h2 className="mt-4 text-2xl font-semibold tracking-[-0.04em] text-white">{announcement.title}</h2>
<p className="mt-2 text-sm text-slate-400">{formatDateRange(announcement.starts_at, announcement.ends_at)}</p>
<p className="mt-1 text-xs uppercase tracking-[0.18em] text-slate-500">{announcement.placement.replaceAll('_', ' ')}</p>
</div>
<div className="flex flex-wrap gap-3 lg:justify-end">
<Link href={announcement.edit_url} className="rounded-full border border-white/10 bg-white/[0.04] px-4 py-2 text-sm font-semibold text-white transition hover:bg-white/[0.08]">Edit</Link>
<button
type="button"
onClick={() => {
if (!window.confirm('Delete this homepage announcement?')) return
router.delete(announcement.destroy_url, { preserveScroll: true })
}}
className="rounded-full border border-rose-300/20 bg-rose-300/10 px-4 py-2 text-sm font-semibold text-rose-100 transition hover:bg-rose-300/16"
>
Delete
</button>
</div>
</div>
<div className="bg-black/10 py-2">
<HomepageAnnouncement announcement={announcement.preview} mode="preview" />
</div>
</article>
))) }
</div>
{announcements?.last_page > 1 ? (
<div className="mt-8 flex items-center justify-between gap-4">
<p className="text-xs text-slate-500">Showing {announcements.from}{announcements.to} of {announcements.total} announcements</p>
<div className="flex gap-2">
{announcements.links.map((link, index) => (
link.url ? (
<button
key={`${link.label}-${index}`}
type="button"
onClick={() => router.get(link.url, {}, { preserveScroll: true })}
className={`rounded-lg px-3 py-1.5 text-xs transition ${link.active ? 'bg-sky-300/15 text-sky-100' : 'bg-white/[0.04] text-slate-400 hover:bg-white/[0.08] hover:text-white'}`}
dangerouslySetInnerHTML={{ __html: link.label }}
/>
) : <span key={`${link.label}-${index}`} className="rounded-lg px-3 py-1.5 text-xs text-slate-600" dangerouslySetInnerHTML={{ __html: link.label }} />
))}
</div>
</div>
) : null}
</AdminLayout>
)
}