added Markdown support for rich text for all type. Markdown support for books description page too to add Translator Notes. Added a Rendering Pipeline for specialized Glossary Popup to work. Added more info to book, from back and front end. Getting ready for ratings views and reader list.
114 lines
5.5 KiB
TypeScript
114 lines
5.5 KiB
TypeScript
import { fetchBookById } from "@/lib/api";
|
|
import { Book, Chapter } from "@/lib/types";
|
|
import { formatDateToMonthDayYear, markdownToHtml } from "@/lib/utils";
|
|
import ChapterDropdown from "@/components/ChapterDropdown";
|
|
import { Ad } from "@/lib/types";
|
|
|
|
export type paramsType = Promise<{ bookId: string}>;
|
|
|
|
export const metadata = {
|
|
title: 'Null Translation Group',
|
|
description: 'Null Translation Group book description page',
|
|
};
|
|
|
|
export default async function BookPage(props: { params: paramsType }) {
|
|
const { bookId } = await props.params;
|
|
let book: Book;
|
|
try {
|
|
book = await fetchBookById(bookId);
|
|
} catch (error) {
|
|
console.error(error);
|
|
return (
|
|
<div className="text-center mt-10 text-red-500">
|
|
Book does not exists, are you lost?
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const {title, author, description, chapters, cover, translator_note, glossary} = book;
|
|
const english_glossary = glossary?.english_english;
|
|
const translator_note_html = await markdownToHtml(translator_note);
|
|
const sorted_chapters:Chapter[] = chapters.sort((a, b) => a.number - b.number);
|
|
|
|
const cover_media = cover?.at(0);
|
|
const recentChapters = sorted_chapters.length > 6 ? sorted_chapters.slice(sorted_chapters.length - 6, sorted_chapters.length) : sorted_chapters;
|
|
|
|
return (
|
|
<div className="max-w-6xl mx-auto py-10 px-4">
|
|
<div className="flex flex-col items-center justify-center">
|
|
<img src={`${process.env.NEXT_PUBLIC_API_URL}${cover_media?.url}`}
|
|
alt={cover_media?.alternativeText || `Cover of ${book.title}`}
|
|
className="rounded-lg object-cover w-64 h-96"
|
|
/>
|
|
<h1 className="text-5xl pb-2 font-bold">{title}</h1>
|
|
<a
|
|
href={Ad.patreon}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="ml-4 bg-yellow-500 text-white font-semibold py-2 px-4 rounded hover:bg-yellow-600 transition duration-200"
|
|
>
|
|
Join Our Patreon to read ahead!
|
|
</a>
|
|
</div>
|
|
|
|
<p className="text-lg text-gray-600 dark:text-gray-400 mb-4">
|
|
<strong>Author:</strong> {author}
|
|
<br></br>
|
|
<strong>Translator:</strong> Null Translation Group
|
|
</p>
|
|
<p className="mb-6">Description: {description}</p>
|
|
<div className="mb-6" dangerouslySetInnerHTML={{__html: translator_note_html}} />
|
|
<h2 className="text-3xl font-semibold mb-4">Recent Chapters</h2>
|
|
<ul className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
|
{recentChapters.map((chapter) => (
|
|
<li key={chapter.id}>
|
|
<a
|
|
href={`/books/${bookId}/chapters/${chapter.documentId}`}
|
|
className="block bg-white dark:bg-gray-800 rounded-lg shadow p-4 hover:shadow-lg transition duration-200"
|
|
>
|
|
<h3 className="text-xl font-medium">
|
|
Chapter {chapter.number}: {chapter.title}
|
|
</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
|
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_datetime))}
|
|
</p>
|
|
</a>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<div className="flex items-center justify-between mb-4 pt-4">
|
|
<h2 className="text-3xl font-semibold">All Chapters</h2>
|
|
<ChapterDropdown chapters={sorted_chapters} bookId={bookId} />
|
|
</div>
|
|
{sorted_chapters.map((chapter) => (
|
|
<li key={chapter.id} className="mb-2 list-none">
|
|
<a
|
|
href={`/books/${bookId}/chapters/${chapter.documentId}`}
|
|
className="block bg-white dark:bg-gray-800 rounded-lg shadow p-4 hover:shadow-lg transition duration-200"
|
|
>
|
|
<h3 className="text-xl font-medium">
|
|
Chapter {chapter.number}: {chapter.title}
|
|
</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
|
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_datetime))}
|
|
</p>
|
|
</a>
|
|
</li>
|
|
))}
|
|
{glossary && (
|
|
<div className="mt-8">
|
|
<h2 className="text-3xl font-semibold">Glossary</h2>
|
|
<ul className="list-disc list-inside mt-4">
|
|
{Object.entries(english_glossary).map(([term, definition]) => (
|
|
<li key={term} className="mb-2">
|
|
<strong>{term}:</strong> {definition}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
}
|