"use client"; import { useState, useCallback } from "react"; import { clientFetch } from "@/lib/api"; import { toast } from "sonner"; import { BlogPost, PostStatus, UserRole } from "@/lib/types"; import { PostSheet } from "./post-sheet"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Eye, Loader2, Pencil, Trash2, PlusCircle, ExternalLink, Star, Search, } from "lucide-react"; import Link from "next/link"; interface Props { initialPosts: BlogPost[]; userRole: UserRole; } const statusMeta: Record = { [PostStatus.PUBLISHED]: { label: "Published", dot: "bg-emerald-500", row: "border-l-emerald-400", }, [PostStatus.DRAFT]: { label: "Draft", dot: "bg-zinc-400", row: "border-l-zinc-300", }, [PostStatus.ARCHIVED]: { label: "Archived", dot: "bg-amber-400", row: "border-l-amber-400", }, }; export function PostsTable({ initialPosts, userRole }: Props) { const [posts, setPosts] = useState(initialPosts); const [sheetOpen, setSheetOpen] = useState(false); const [editingPost, setEditingPost] = useState(null); const [deletingId, setDeletingId] = useState(null); const [search, setSearch] = useState(""); const refreshPosts = useCallback(async () => { try { const data = await clientFetch<{ posts: BlogPost[]; total: number }>( "/blog-posts?pageSize=50" ); setPosts(data.posts); } catch { /* ignore */ } }, []); const openCreate = () => { setEditingPost(null); setSheetOpen(true); }; const openEdit = (post: BlogPost) => { setEditingPost(post); setSheetOpen(true); }; const handleDelete = async (id: string) => { setDeletingId(id); try { await clientFetch(`/blog-posts/${id}`, { method: "DELETE" }); toast.success("Post deleted."); setPosts((prev) => prev.filter((p) => p.id !== id)); } catch (err: unknown) { toast.error(err instanceof Error ? err.message : "Delete failed"); } finally { setDeletingId(null); } }; const canEdit = userRole === UserRole.ADMIN; const canCreate = userRole === UserRole.ADMIN || userRole === UserRole.MANAGER; const filtered = search.trim() ? posts.filter( (p) => p.title.toLowerCase().includes(search.toLowerCase()) || p.slug.toLowerCase().includes(search.toLowerCase()) ) : posts; return ( <> {/* ── Toolbar ───────────────────────────────────────────────────────── */}
setSearch(e.target.value)} className="pl-8 h-8 w-56 text-sm" />
{canCreate && ( )}
{/* ── Posts list ────────────────────────────────────────────────────── */} {filtered.length === 0 ? (
{search ? "No posts match your search." : "No posts yet. Create your first post!"}
) : (
{filtered.map((post) => { const meta = statusMeta[post.status]; return (
{/* Thumbnail */} {post.featuredImageUrl ? ( // eslint-disable-next-line @next/next/no-img-element {post.featuredImageAlt ) : (
No img
)} {/* Main content */}
{meta.label} {post.isFeatured && ( )}
{post.title}
/{post.slug} {post.views.toLocaleString()} {post.author && ( {post.author.name || post.author.email} )}
{(post.categories.length > 0 || post.tags.length > 0) && (
{post.categories.map((c) => ( {c} ))} {post.tags.slice(0, 3).map((t) => ( #{t} ))} {post.tags.length > 3 && ( +{post.tags.length - 3} )}
)}
{/* Date */} {new Date(post.updatedAt).toLocaleDateString()} {/* Actions */} {canEdit && (
Delete post? "{post.title}" will be permanently deleted. This cannot be undone. Cancel handleDelete(post.id)} disabled={deletingId === post.id} > {deletingId === post.id && ( )} Delete
)}
); })}
)} {/* ── Post create/edit sheet ─────────────────────────────────────────── */} ); }