import { fetchLibraries, getBookCoverUrl, BookDto, apiFetch, ReadingStatus } from "../../../lib/api"; import { BookPreview } from "../../components/BookPreview"; import { ConvertButton } from "../../components/ConvertButton"; import { MarkBookReadButton } from "../../components/MarkBookReadButton"; import nextDynamic from "next/dynamic"; import { SafeHtml } from "../../components/SafeHtml"; import { getServerTranslations } from "../../../lib/i18n/server"; import Image from "next/image"; import Link from "next/link"; const EditBookForm = nextDynamic( () => import("../../components/EditBookForm").then(m => m.EditBookForm) ); import { notFound } from "next/navigation"; export const dynamic = "force-dynamic"; const readingStatusClassNames: Record = { unread: "bg-muted/60 text-muted-foreground border border-border", reading: "bg-amber-500/15 text-amber-600 dark:text-amber-400 border border-amber-500/30", read: "bg-green-500/15 text-green-600 dark:text-green-400 border border-green-500/30", }; async function fetchBook(bookId: string): Promise { try { return await apiFetch(`/books/${bookId}`); } catch { return null; } } export default async function BookDetailPage({ params }: { params: Promise<{ id: string }>; }) { const { id } = await params; const [book, libraries] = await Promise.all([ fetchBook(id), fetchLibraries().catch(() => [] as { id: string; name: string }[]) ]); if (!book) { notFound(); } const { t, locale } = await getServerTranslations(); const library = libraries.find(l => l.id === book.library_id); const formatBadge = (book.format ?? book.kind).toUpperCase(); const formatColor = formatBadge === "CBZ" ? "bg-success/10 text-success border-success/30" : formatBadge === "CBR" ? "bg-warning/10 text-warning border-warning/30" : formatBadge === "PDF" ? "bg-destructive/10 text-destructive border-destructive/30" : "bg-muted/50 text-muted-foreground border-border"; const statusLabel = t(`status.${book.reading_status}` as "status.unread" | "status.reading" | "status.read"); const statusClassName = readingStatusClassNames[book.reading_status]; return (
{/* Breadcrumb */}
{t("bookDetail.libraries")} / {library && ( <> {library.name} / )} {book.series && ( <> {book.series} / )} {book.title}
{/* Hero */}
{/* Cover */}
{t("bookDetail.coverOf",
{/* Info */}

{book.title}

{book.author && (

{book.author}

)}
{/* Series + Volume link */} {book.series && (
{book.series} {book.volume != null && ( Vol. {book.volume} )}
)} {/* Reading status + actions */}
{statusLabel} {book.reading_status === "reading" && book.reading_current_page != null && ` ยท p. ${book.reading_current_page}`} {book.reading_last_read_at && ( {new Date(book.reading_last_read_at).toLocaleDateString(locale)} )} {book.file_format === "cbr" && }
{/* Metadata pills */}
{formatBadge} {book.page_count && ( {book.page_count} {t("dashboard.pages").toLowerCase()} )} {book.language && ( {book.language.toUpperCase()} )} {book.isbn && ( ISBN {book.isbn} )} {book.publish_date && ( {book.publish_date} )}
{/* Description */} {book.summary && ( )}
{/* Technical info (collapsible) */}
{t("bookDetail.technicalInfo")}
{book.file_path && (
{t("bookDetail.file")} {book.file_path}
)} {book.file_format && (
{t("bookDetail.fileFormat")} {book.file_format.toUpperCase()}
)} {book.file_parse_status && (
{t("bookDetail.parsing")} {book.file_parse_status}
)}
Book ID {book.id}
Library ID {book.library_id}
{book.updated_at && (
{t("bookDetail.updatedAt")} {new Date(book.updated_at).toLocaleString(locale)}
)}
{/* Book Preview */} {book.page_count && book.page_count > 0 && ( )}
); }