import { useCallback, useEffect, useState } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Badge } from "@/components/ui/badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Loader2, Trash2, ExternalLink, RefreshCw } from "lucide-react"; import { aethexToast } from "@/lib/aethex-toast"; interface BlogPost { id?: string; slug: string; title: string; excerpt?: string | null; author?: string | null; date?: string | null; category?: string | null; image?: string | null; published_at?: string | null; body_html?: string | null; } export default function AdminBlogManager() { const [blogPosts, setBlogPosts] = useState([]); const [loading, setLoading] = useState(false); const [deleting, setDeleting] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [filterCategory, setFilterCategory] = useState(""); const loadBlogPosts = useCallback(async () => { setLoading(true); try { const res = await fetch("/api/blog?limit=100"); if (res.ok) { const data = await res.json(); if (Array.isArray(data)) { setBlogPosts(data); aethexToast.success({ title: "Blog posts loaded", description: `Loaded ${data.length} blog posts`, }); } } else { const errorText = await res.text(); console.error("Failed to load blog posts:", errorText); aethexToast.error({ title: "Failed to load blog posts", description: res.statusText || "Unknown error", }); } } catch (error) { console.error("Error loading blog posts:", error); aethexToast.error({ title: "Error loading blog posts", description: String(error), }); } finally { setLoading(false); } }, []); useEffect(() => { loadBlogPosts(); }, [loadBlogPosts]); const handleDeleteBlogPost = useCallback(async (slug: string) => { setDeleting(slug); try { const res = await fetch(`/api/blog/${slug}`, { method: "DELETE" }); if (res.ok) { setBlogPosts((posts) => posts.filter((p) => p.slug !== slug)); aethexToast.success({ title: "Blog post deleted", description: `Post "${slug}" has been removed`, }); } else { aethexToast.error({ title: "Failed to delete blog post", description: res.statusText || "Unknown error", }); } } catch (error) { console.error("Error deleting blog post:", error); aethexToast.error({ title: "Error deleting blog post", description: String(error), }); } finally { setDeleting(null); } }, []); const filteredPosts = blogPosts.filter((post) => { const matchesSearch = post.title.toLowerCase().includes(searchQuery.toLowerCase()) || post.slug.toLowerCase().includes(searchQuery.toLowerCase()) || (post.author && post.author.toLowerCase().includes(searchQuery.toLowerCase())); const matchesCategory = !filterCategory || post.category === filterCategory; return matchesSearch && matchesCategory; }); const categories = Array.from( new Set(blogPosts.map((p) => p.category).filter(Boolean)), ); const formatDate = (dateStr?: string | null) => { if (!dateStr) return "—"; try { return new Date(dateStr).toLocaleDateString("en-US", { year: "numeric", month: "short", day: "numeric", }); } catch { return dateStr; } }; return (
Blog Management {blogPosts.length} published{" "} {blogPosts.length === 1 ? "post" : "posts"}
setSearchQuery(e.target.value)} className="h-8" />
{filteredPosts.length === 0 ? (

{blogPosts.length === 0 ? "No blog posts found" : "No matching blog posts"}

{blogPosts.length === 0 && ( )}
) : (
Title Author Category Date Actions {filteredPosts.map((post) => (

{post.title}

{post.slug}

{post.author || "—"} {post.category ? ( {post.category} ) : ( )} {formatDate(post.published_at || post.date)}
))}
)}
{deleteConfirm && ( !open && setDeleteConfirm(null)} > Delete blog post? Are you sure you want to delete "{deleteConfirm.title}"? This action cannot be undone.
setDeleteConfirm(null)}> Cancel { handleDeleteBlogPost(deleteConfirm.slug); setDeleteConfirm(null); }} className="bg-destructive text-destructive-foreground hover:bg-destructive/90" > Delete
)}
); }