Add homepage announcement module
This commit is contained in:
110
resources/js/Pages/Admin/HomepageAnnouncements/Index.jsx
Normal file
110
resources/js/Pages/Admin/HomepageAnnouncements/Index.jsx
Normal file
@@ -0,0 +1,110 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user