Files
website/client/src/routes/admin/views/PostsAdmin.jsx
2026-06-26 21:51:27 -05:00

109 lines
3.8 KiB
JavaScript

import { useCallback, useState } from 'react'
import { Loading, ErrorState } from '../../../components/PageState.jsx'
import { useAsync } from '../../../lib/useAsync.js'
import { shortDate, categoryLabel } from '../../../lib/format.js'
import { api } from '../../../api/client.js'
import PostEditor from './PostEditor.jsx'
const FILTERS = [
{ key: 'all', label: 'All' },
{ key: 'news', label: 'News', db: 'news' },
{ key: 'five_on_friday', label: 'Five on Friday', db: 'five_on_friday' },
{ key: 'newsletter', label: 'Newsletter', db: 'newsletter' },
{ key: 'screenshot', label: 'Screenshots', db: 'screenshot' },
]
export default function PostsAdmin() {
const [tick, setTick] = useState(0)
const reload = useCallback(() => setTick((t) => t + 1), [])
const { loading, error, data } = useAsync(() => api.admin.listPosts(), [tick])
const [filter, setFilter] = useState('all')
const [editing, setEditing] = useState(null) // null | 'new' | post object
const posts = (data || []).filter((p) => filter === 'all' || p.category === filter)
return (
<section>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 14, marginBottom: 18, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
{FILTERS.map((f) => (
<button
key={f.key}
onClick={() => setFilter(f.key)}
className="pill"
style={
filter === f.key
? { borderColor: 'var(--accent)', background: 'var(--blue)', color: 'var(--ink)' }
: undefined
}
>
{f.label}
</button>
))}
</div>
<button onClick={() => setEditing('new')} className="btn btn-primary btn-sq">
+ New post
</button>
</div>
{loading && <Loading />}
{error && <ErrorState message="Could not load posts." />}
{!loading && !error && (
<div className="panel-flat">
<table className="adm-table">
<thead>
<tr>
<th className="adm-th">Title</th>
<th className="adm-th">Category</th>
<th className="adm-th">Status</th>
<th className="adm-th">Date</th>
<th className="adm-th" />
</tr>
</thead>
<tbody>
{posts.length === 0 && (
<tr>
<td className="adm-td" colSpan={5} style={{ color: 'var(--muted)' }}>
No posts in this category yet.
</td>
</tr>
)}
{posts.map((p) => (
<tr key={p.id}>
<td className="adm-td" style={{ color: 'var(--head)' }}>
{p.title}
</td>
<td className="adm-td">{categoryLabel(p.category)}</td>
<td className="adm-td">
<span className={`badge ${p.published ? 'badge-pub' : 'badge-draft'}`}>
{p.published ? 'Published' : 'Draft'}
</span>
</td>
<td className="adm-td dim">{shortDate(p.published_at || p.created_at)}</td>
<td className="adm-td" style={{ textAlign: 'right' }}>
<span className="link-accent" onClick={() => setEditing(p)}>
Edit
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{editing && (
<PostEditor
post={editing === 'new' ? null : editing}
onClose={() => setEditing(null)}
onSaved={() => {
setEditing(null)
reload()
}}
/>
)}
</section>
)
}