import Link from "next/link" import { ArrowRight, Clock3, Flame, MessageSquare, Shuffle, Trophy } from "lucide-react" import { formatViews } from "@/lib/utils" import { HomeHotCarousel, type HotCarouselItem } from "@/components/home-hot-carousel" import { HomeRecommendationBoards } from "@/components/home-recommendation-boards" import { readerApiFetch } from "@/lib/server-api" export const dynamic = "force-dynamic" type HomeNovel = { id: string slug: string title: string authorName: string coverColor: string | null coverUrl: string | null rating: number views: number totalChapters: number status: string description: string bookmarkCount: number seriesId: string | null updatedAt: string | null } type EditorRecommendedItem = { novel: { id: string slug: string title: string authorName: string coverUrl: string | null rating: number } editorName: string recommendCount: number } type RecommendedByCountItem = { novel: { id: string slug: string title: string authorName: string coverUrl: string | null rating: number } recommendCount: number } type RankingEntry = { id: string seriesId: string | null novel: HomeNovel aggregatedViews: number } type RecentCommentItem = { id: string content: string createdAt: string | null user: { name: string | null } novel: { slug: string title: string } } type LatestChapterInfo = { chapterNumber: number | null chapterTitle: string | null chapterCreatedAt: string | null } type HomeApiResponse = { hotSlides: HotCarouselItem[] randomNovels: HomeNovel[] recommendedByCountItems: RecommendedByCountItem[] editorRecommendedItems: EditorRecommendedItem[] weeklyRanking: RankingEntry[] monthlyRanking: RankingEntry[] allTimeRanking: RankingEntry[] latestNovels: Array recentComments: RecentCommentItem[] } function formatRelativeTime(value: string | Date | null | undefined): string { if (!value) return "Vừa cập nhật" const parsed = value instanceof Date ? value : new Date(value) if (Number.isNaN(parsed.getTime())) return "Vừa cập nhật" const now = Date.now() const ts = parsed.getTime() const diff = Math.max(0, now - ts) const minute = 60 * 1000 const hour = 60 * minute const day = 24 * hour if (diff < minute) return "Vừa xong" if (diff < hour) return `${Math.floor(diff / minute)} phút trước` if (diff < day) return `${Math.floor(diff / hour)} giờ trước` if (diff < day * 30) return `${Math.floor(diff / day)} ngày trước` return parsed.toLocaleDateString("vi-VN") } function compactLine(text: string, max = 140): string { const normalized = text.replace(/\s+/g, " ").trim() if (normalized.length <= max) return normalized return `${normalized.slice(0, max).trim()}...` } function RankingBoard({ title, entries, emptyText, }: { title: string entries: RankingEntry[] emptyText: string }) { return (

{title}

{entries.length > 0 ? entries.map((entry, index) => ( {index + 1} {entry.novel.title}

{entry.novel.title}

{formatViews(entry.aggregatedViews)} lượt đọc

)) : (

{emptyText}

)}
) } export default async function HomePage() { let hotSlides: HotCarouselItem[] = [] let randomNovels: HomeNovel[] = [] let recommendedByCountItems: RecommendedByCountItem[] = [] let editorRecommendedItems: EditorRecommendedItem[] = [] let latestNovels: Array = [] let recentComments: RecentCommentItem[] = [] let weeklyRanking: RankingEntry[] = [] let monthlyRanking: RankingEntry[] = [] let allTimeRanking: RankingEntry[] = [] const latestChapterMap = new Map() try { const homeData = await readerApiFetch("/api/home") hotSlides = homeData.hotSlides randomNovels = homeData.randomNovels recommendedByCountItems = homeData.recommendedByCountItems editorRecommendedItems = homeData.editorRecommendedItems latestNovels = homeData.latestNovels recentComments = homeData.recentComments weeklyRanking = homeData.weeklyRanking monthlyRanking = homeData.monthlyRanking allTimeRanking = homeData.allTimeRanking for (const novel of latestNovels) { latestChapterMap.set(novel.id, { chapterNumber: novel.latestChapter?.number ?? null, chapterTitle: novel.latestChapter?.title ?? null, chapterCreatedAt: novel.latestChapter?.createdAt ?? null, }) } } catch (error) { console.error("Failed to fetch data for homepage during build/runtime", error) } return (

Truyện hot hôm nay

Mỗi lần trượt hiển thị 1 truyện, dữ liệu lấy từ log đọc theo tuần và tháng.

Xem tất cả
{hotSlides.length > 0 ? ( ) : (

Chưa có dữ liệu hot để hiển thị.

)}

Truyện ngẫu nhiên

Luôn cố gắng lấp đầy đủ 2 hàng
{randomNovels.length > 0 ? randomNovels.map((novel) => (
{novel.title}

{novel.title}

{novel.authorName}

{formatViews(novel.views)} lượt đọc

)) : (

Không có truyện để hiển thị.

)}

Bảng xếp hạng độ hot

So sánh độ nóng theo tuần, tháng và toàn thời gian.

Truyện mới cập nhật

Xem tất cả
{latestNovels.length > 0 ? latestNovels.map((novel) => { const chapter = latestChapterMap.get(novel.id) const chapterLabel = chapter?.chapterNumber ? `Chương ${chapter.chapterNumber}` : "Chưa có chương" const chapterTitle = chapter?.chapterTitle ? compactLine(chapter.chapterTitle, 100) : "Đang cập nhật nội dung chương" const updatedTime = formatRelativeTime(chapter?.chapterCreatedAt || novel.updatedAt) return ( {novel.title}

{novel.title}

{novel.authorName}

{chapterLabel}

{chapterTitle}

{updatedTime}
) }) : (

Chưa có truyện mới cập nhật.

)}
) }