import { Suspense } from "react"; import Link from "next/link"; import Image from "next/image"; import { apiFetch } from "@/lib/api"; import { BlogPost, PaginatedResult, TagCloudItem } from "@/lib/types"; import { PostCard } from "@/components/post-card"; import { Pagination } from "@/components/pagination"; import { PostFilterForm } from "@/components/post-filter-form"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Eye, Star, TrendingUp } from "lucide-react"; interface HomeSearchParams { q?: string; tags?: string; category?: string; sort?: string; page?: string; pageSize?: string; } async function getFeatured(): Promise { try { return await apiFetch("/blog-posts/public/featured"); } catch { return []; } } async function getPosts( params: HomeSearchParams ): Promise> { const qs = new URLSearchParams(); if (params.q) qs.set("q", params.q); if (params.tags) qs.set("tags", params.tags); if (params.category) qs.set("category", params.category); if (params.sort) qs.set("sort", params.sort); qs.set("page", params.page || "1"); qs.set("pageSize", params.pageSize || "9"); try { return await apiFetch>( `/blog-posts/public?${qs.toString()}` ); } catch { return { items: [], total: 0, page: 1, pageSize: 9, totalPages: 0 }; } } async function getAllPostsForCloud(): Promise { try { const result = await apiFetch>( "/blog-posts/public?pageSize=100" ); return result.items; } catch { return []; } } function buildTagCloud(posts: BlogPost[]): TagCloudItem[] { const counts: Record = {}; for (const post of posts) { for (const tag of post.tags || []) { const t = tag.trim(); if (t) counts[t] = (counts[t] || 0) + 1; } } return Object.entries(counts) .map(([name, count]) => ({ name, count })) .sort((a, b) => b.count - a.count) .slice(0, 20); } function buildCategoryCloud(posts: BlogPost[]): TagCloudItem[] { const counts: Record = {}; for (const post of posts) { for (const cat of post.categories || []) { const c = cat.trim(); if (c) counts[c] = (counts[c] || 0) + 1; } } return Object.entries(counts) .map(([name, count]) => ({ name, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); } export default async function HomePage({ searchParams, }: { searchParams: Promise; }) { const params = await searchParams; const [featured, postsResult, allPosts, popularResult] = await Promise.all([ getFeatured(), getPosts(params), getAllPostsForCloud(), apiFetch>( "/blog-posts/public?sort=most_viewed&pageSize=5" ).catch(() => ({ items: [] as BlogPost[] })), ]); const topTags = buildTagCloud(allPosts); const topCategories = buildCategoryCloud(allPosts); const popularPosts = (popularResult as PaginatedResult).items || []; const { items: posts, page, totalPages, total } = postsResult; return (
{/* ── Hero ──────────────────────────────────────────────────────────── */}

The blog is all about web dev tech

Fast backend patterns, frontend craft, and product lessons from shipping real systems.

{topCategories.slice(0, 6).map((c) => ( {c.name} ({c.count}) ))}
{/* ── Featured posts ─────────────────────────────────────────────────── */} {featured.length > 0 && (

Featured

{/* Primary featured */}
Featured pick {featured[0].featuredImageUrl && (
{featured[0].featuredImageAlt
)}

{featured[0].title}

{featured[0].excerpt}

{featured[0].views.toLocaleString()} views

{/* Secondary featured */}
{featured.slice(1).map((post) => (
{post.featuredImageUrl && (
{post.featuredImageAlt
)}

{post.title}

{post.excerpt}

))}
)} {/* ── Main content + sidebar ─────────────────────────────────────────── */}
{/* Filter form */} {/* Post grid */} {posts.length === 0 ? (

No posts found for this filter.

) : (
{posts.map((post) => ( ))}
)} {/* Pagination */}
{/* Sidebar */}
); }