"use client"; import { useMemo, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { api } from "@/lib/client"; import { categoryLabel } from "@/lib/validate"; import type { BlogPost } from "@/lib/types"; type Props = { posts: BlogPost[]; revision: string; categories: string[] }; export default function PostTable({ posts, revision, categories }: Props) { const router = useRouter(); const [query, setQuery] = useState(""); const [category, setCategory] = useState(""); const [error, setError] = useState(null); const [busy, setBusy] = useState(null); const visible = useMemo(() => { const q = query.trim().toLowerCase(); return posts.filter( (p) => (!category || p.category === category) && (!q || [p.title, p.slug, p.description, ...p.tags].some((s) => s.toLowerCase().includes(q))) ); }, [posts, query, category]); async function remove(post: BlogPost) { const typed = window.prompt(`Delete "${post.title}"?\n\nType the slug (${post.slug}) to confirm.`); if (typed === null) return; if (typed.trim() !== post.slug) { setError("Slug didn't match; nothing was deleted."); return; } const withImages = window.confirm( `Also delete the image folder public/img/blog/${post.slug}/?\n\nOK = delete images too, Cancel = keep them.` ); setBusy(post.slug); setError(null); try { const params = new URLSearchParams({ revision, ...(withImages ? { images: "1" } : {}) }); await api(`/api/posts/${post.slug}?${params}`, { method: "DELETE" }); router.refresh(); } catch (err) { setError((err as Error).message); } finally { setBusy(null); } } return ( <>

Posts

setQuery(e.target.value)} /> + New post
{error &&
{error}
} {visible.map((post) => ( ))} {visible.length === 0 && ( )}
title date category tags images
{post.title}
/blog/{post.slug}
{post.date} {post.category} {post.tags.map((t) => ( {t} ))} {post.images?.length ?? 0} Edit
{posts.length ? "No posts match." : "No posts yet."}

{visible.length} of {posts.length} posts

); }