"use client"; import { useEffect, useRef, useState } from "react"; import Link from "next/link"; import { ArrowRight, ArrowLeft, ChevronRight } from "lucide-react"; import { api } from "@/lib/api"; import { formatDate } from "@/lib/utils"; export interface BlogPost { id: string; slug: string; title: string; excerpt?: string; author?: string; authorPubkey?: string; publishedAt?: string; createdAt?: string; categories?: { category: { id: string; name: string; slug: string } }[]; featured?: boolean; } export interface BlogCategory { id: string; name: string; slug: string; } interface BlogIndexProps { initialPosts: BlogPost[]; initialTotal: number; categories: BlogCategory[]; limit: number; } function PostCardSkeleton() { return (
); } export function BlogIndex({ initialPosts, initialTotal, categories, limit, }: BlogIndexProps) { const [posts, setPosts] = useState(initialPosts); const [total, setTotal] = useState(initialTotal); const [activeCategory, setActiveCategory] = useState("all"); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const isFirst = useRef(true); useEffect(() => { // The initial ("all", page 1) data is already rendered from the server. if (isFirst.current) { isFirst.current = false; return; } let cancelled = false; setLoading(true); setError(null); api .getPosts({ category: activeCategory === "all" ? undefined : activeCategory, page, limit, }) .then(({ posts: data, total: t }) => { if (cancelled) return; setPosts(data); setTotal(t); }) .catch((err) => { if (!cancelled) setError(err.message); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [activeCategory, page, limit]); const totalPages = Math.ceil(total / limit); const featured = posts.find((p) => p.featured); const regularPosts = featured ? posts.filter((p) => p.id !== featured.id) : posts; return ( <>
{categories.map((cat) => ( ))}
{error && (
Failed to load posts: {error}
)} {loading ? (
{Array.from({ length: 6 }).map((_, i) => ( ))}
) : posts.length === 0 ? (

No posts yet

Check back soon for curated Bitcoin content.

) : ( <> {featured && page === 1 && (
Featured

{featured.title}

{featured.excerpt && (

{featured.excerpt}

)}
{featured.author && {featured.author}} {featured.publishedAt && ( {formatDate(featured.publishedAt)} )}
)}
{regularPosts.map((post) => ( {post.categories && post.categories.length > 0 && (
{post.categories.map((pc) => ( {pc.category.name} ))}
)}

{post.title}

{post.excerpt && (

{post.excerpt}

)}
{post.author && {post.author}} {post.author && (post.publishedAt || post.createdAt) && ยท} {(post.publishedAt || post.createdAt) && ( {formatDate(post.publishedAt || post.createdAt!)} )}
Read
))}
{totalPages > 1 && (
Page {page} of {totalPages}
)} )}
); }