Compare commits

...

2 Commits

11 changed files with 109 additions and 90 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -12,34 +12,28 @@ export default async function ChapterPage(props: { params: paramsType}) {
const book = await fetchBookById(bookId); const book = await fetchBookById(bookId);
const chapters :Chapter[] = book.chapters; const chapters :Chapter[] = book.chapters;
const sorted_chapters:Chapter[] = chapters.sort((a, b) => a.Chapter_Number - b.Chapter_Number); const sorted_chapters:Chapter[] = chapters.sort((a, b) => a.number - b.number);
const current_chapter = sorted_chapters.find((chapter) => chapter.documentId === chapterId) || undefined; const current_chapter = sorted_chapters.find((chapter) => chapter.documentId === chapterId) || undefined;
const next_chapter = current_chapter ? sorted_chapters.find((chapter) => chapter.Chapter_Number === current_chapter.Chapter_Number + 1)?.documentId || "" : ""; const next_chapter = current_chapter ? sorted_chapters.find((chapter) => chapter.number === current_chapter.number + 1)?.documentId || "" : "";
const prev_chapter = current_chapter ? sorted_chapters.find((chapter) => chapter.Chapter_Number === current_chapter.Chapter_Number - 1)?.documentId || "" : ""; const prev_chapter = current_chapter ? sorted_chapters.find((chapter) => chapter.number === current_chapter.number - 1)?.documentId || "" : "";
// Fetch chapter data // Fetch chapter data
if (current_chapter === undefined) { if (current_chapter === undefined) {
return ( return (
<div className="relative bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen"> <div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg mt-4">
<div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg"> <div dangerouslySetInnerHTML={{ __html: '<center><h1> Chapter not found !</h1></center>' }}></div>
<div dangerouslySetInnerHTML={{ __html: '<center><h1> Chapter not found !</h1></center>' }}></div> <NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
</div>
{/* Client component for navigation */}
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
</div>
</div>
) )
} }
return ( return (
<div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg"> <div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg mt-4">
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} /> <NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
<div className="pt-4" dangerouslySetInnerHTML={{ __html: current_chapter.content }}></div>
<div className="pt-4" dangerouslySetInnerHTML={{ __html: current_chapter.Content }}></div>
{/* Client component for navigation */} <NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter}/>
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter}/>
</div> </div>
); );

View File

@ -1,7 +1,8 @@
import { fetchBookChapterLinks } from "@/lib/api"; import { fetchBookById, fetchBookChapterLinks } from "@/lib/api";
import { Book } from "@/lib/types"; import { Book } from "@/lib/types";
import { formatDateToMonthDayYear } from "@/lib/utils"; import { formatDateToMonthDayYear } from "@/lib/utils";
import ChapterDropdown from "@/components/ChapterDropdown"; import ChapterDropdown from "@/components/ChapterDropdown";
import Image from "next/image";
export type paramsType = Promise<{ bookId: string}>; export type paramsType = Promise<{ bookId: string}>;
@ -11,7 +12,7 @@ export default async function BookPage(props: { params: paramsType }) {
let book: Book; let book: Book;
try { try {
book = await fetchBookChapterLinks(bookId); book = await fetchBookById(bookId);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return ( return (
@ -21,31 +22,35 @@ export default async function BookPage(props: { params: paramsType }) {
); );
} }
const { Name, Author, Description, chapters } = book; const { title, author, description, chapters, cover } = book;
const cover_media = cover?.at(0);
const recentChapters = chapters.length > 6 ? chapters.slice(chapters.length - 6, chapters.length) : chapters; const recentChapters = chapters.length > 6 ? chapters.slice(chapters.length - 6, chapters.length) : chapters;
return ( return (
<div className="max-w-6xl mx-auto py-10 px-4"> <div className="max-w-6xl mx-auto py-10 px-4">
<div className="flex items-center justify-between mb-4 pt-4"> <div className="flex flex-col items-center justify-center">
{/* Book Title */} <img src={`${process.env.NEXT_PUBLIC_API_URL}${cover_media?.url}`}
<h1 className="text-5xl font-bold">{Name}</h1> alt={cover_media?.alternativeText || `Cover of ${book.title}`}
className="rounded-lg object-cover w-64 h-96"
{/* Patreon Button */} />
<a <div className="flex items-center justify-between mb-4 pt-4">
href="https://www.patreon.com/c/nulltranslationgroup/membership?view_as=patron" // Replace with your Patreon URL <h1 className="text-5xl font-bold">{title}</h1>
target="_blank" <a
rel="noopener noreferrer" href="https://www.patreon.com/c/nulltranslationgroup/membership?view_as=patron" // Replace with your Patreon URL
className="ml-4 bg-yellow-500 text-white font-semibold py-2 px-4 rounded hover:bg-yellow-600 transition duration-200" target="_blank"
> rel="noopener noreferrer"
Join Our Patreon for Unreleased Chapters className="ml-4 bg-yellow-500 text-white font-semibold py-2 px-4 rounded hover:bg-yellow-600 transition duration-200"
</a> >
Join Our Patreon for Unreleased Chapters
</a>
</div>
</div> </div>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-4"> <p className="text-lg text-gray-600 dark:text-gray-400 mb-4">
<strong>Author:</strong> {Author} <strong>Author:</strong> {author}
<br></br> <br></br>
<strong>Translator:</strong> Null Translation Group <strong>Translator:</strong> Null Translation Group
</p> </p>
<p className="mb-6">{Description}</p> <p className="mb-6">{description}</p>
<h2 className="text-3xl font-semibold mb-4">Recent Chapters</h2> <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"> <ul className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
@ -56,10 +61,10 @@ export default async function BookPage(props: { params: paramsType }) {
className="block bg-white dark:bg-gray-800 rounded-lg shadow p-4 hover:shadow-lg transition duration-200" 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"> <h3 className="text-xl font-medium">
Chapter {chapter.Chapter_Number}: {chapter.Name} Chapter {chapter.number}: {chapter.title}
</h3> </h3>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2"> <p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.ReleaseDate))} <strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_date))}
</p> </p>
</a> </a>
</li> </li>
@ -76,10 +81,10 @@ export default async function BookPage(props: { params: paramsType }) {
className="block bg-white dark:bg-gray-800 rounded-lg shadow p-4 hover:shadow-lg transition duration-200" 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"> <h3 className="text-xl font-medium">
Chapter {chapter.Chapter_Number}: {chapter.Name} Chapter {chapter.number}: {chapter.title}
</h3> </h3>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2"> <p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.ReleaseDate))} <strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_date))}
</p> </p>
</a> </a>
</li> </li>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -53,7 +53,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<body className="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen"> <body className="bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
<Navbar /> <Navbar />
<main className="relative">{children}</main> <main className="relative">{children}</main>
<div className="absolute bottom-4 right-4"> <div className="fixed bottom-4 right-4">
<NightModeToggle isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} /> <NightModeToggle isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />
</div> </div>
</body> </body>

View File

@ -1,6 +1,6 @@
import { Book } from "@/lib/types"; import { Book } from "@/lib/types";
import { fetchBooks } from "@/lib/api"; import { fetchBooks } from "@/lib/api";
import Image from "next/image";
export default async function HomePage() { export default async function HomePage() {
let books: Book[] = []; let books: Book[] = [];
@ -17,7 +17,6 @@ export default async function HomePage() {
return ( return (
<div className="mx-auto p-6 bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen"> <div className="mx-auto p-6 bg-gray-100 dark:bg-gray-900 text-gray-900 dark:text-gray-100 min-h-screen">
{/* Patreon Advertisement */}
<div className="hidden md:block bg-yellow-500 text-black py-2 px-4 rounded-lg hover:bg-yellow-600 transition duration-200 mb-6"> <div className="hidden md:block bg-yellow-500 text-black py-2 px-4 rounded-lg hover:bg-yellow-600 transition duration-200 mb-6">
<a <a
href="https://patreon.com/NullTranslationGroup" href="https://patreon.com/NullTranslationGroup"
@ -29,28 +28,29 @@ export default async function HomePage() {
</a> </a>
</div> </div>
{/* Books Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 px-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 px-6">
{books.map((book: Book) => ( {books.map((book: Book) => {
const cover = book.cover?.at(0);
return (
<div <div
key={book.id} key={book.id}
className="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition flex flex-col" className="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition flex flex-col"
> >
{book.Cover?.url && ( {book.cover?.at(0)?.url && (
<div className="relative w-full aspect-w-3 aspect-h-4 mb-4"> <div className="relative w-full aspect-w-3 aspect-h-4 mb-4">
<img <img
src={`${process.env.NEXT_PUBLIC_API_URL}${book.Cover.url}`} src={`${process.env.NEXT_PUBLIC_API_URL}${cover?.url}`}
alt={book.Cover.alternativeText || `Cover of ${book.Name}`} alt={cover?.alternativeText || `Cover of ${book.title}`}
className="rounded-lg object-cover" className="rounded-lg object-cover w-full h-full"
/> />
</div> </div>
)} )}
<h2 className="text-2xl font-semibold mb-2">{book.Name}</h2> <h2 className="text-2xl font-semibold mb-2">{book.title}</h2>
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
<strong>Author:</strong> {book.Author} <strong>Author:</strong> {book.author}
</p> </p>
<p className="text-sm mt-2 line-clamp-3">{book.Description}</p> <p className="text-sm mt-2 line-clamp-3">{book.description}</p>
<a <a
className="mt-4 inline-block bg-blue-500 hover:bg-blue-600 text-white text-sm font-semibold px-4 py-2 rounded text-center" className="mt-4 inline-block bg-blue-500 hover:bg-blue-600 text-white text-sm font-semibold px-4 py-2 rounded text-center"
@ -59,7 +59,7 @@ export default async function HomePage() {
Read Book Read Book
</a> </a>
</div> </div>
))} )})}
</div> </div>
</div> </div>
); );

View File

@ -4,7 +4,7 @@ export default function ChapterDropdown({
chapters, chapters,
bookId, bookId,
}: { }: {
chapters: { id: number; documentId: string; Chapter_Number: number; Name: string }[]; chapters: { id: number; documentId: string; number: number; title: string }[];
bookId: string; bookId: string;
}) { }) {
@ -24,7 +24,7 @@ export default function ChapterDropdown({
</option> </option>
{chapters.map((chapter) => ( {chapters.map((chapter) => (
<option key={chapter.id} value={chapter.documentId}> <option key={chapter.id} value={chapter.documentId}>
Chapter {chapter.Chapter_Number}: {chapter.Name} Chapter {chapter.number}: {chapter.title}
</option> </option>
))} ))}
</select> </select>

View File

@ -2,6 +2,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { Ad } from "@/lib/types";
export default function Navbar() { export default function Navbar() {
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
@ -11,6 +12,8 @@ export default function Navbar() {
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
<Image <Image
src="/logo.png" // Replace with your logo path src="/logo.png" // Replace with your logo path
width={32}
height={32}
alt="Logo" alt="Logo"
className="h-8 w-8" className="h-8 w-8"
/> />

View File

@ -2,7 +2,7 @@
import React from "react"; import React from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Ad } from "@/lib/types";
interface NavigationButtonsProps { interface NavigationButtonsProps {
bookId: string; bookId: string;
@ -13,10 +13,10 @@ interface NavigationButtonsProps {
const NavigationButtons: React.FC<NavigationButtonsProps> = ({ bookId, documentId, prevChapter, nextChapter }) => { const NavigationButtons: React.FC<NavigationButtonsProps> = ({ bookId, documentId, prevChapter, nextChapter }) => {
const router = useRouter(); const router = useRouter();
console.log(documentId)
const navigateToChapter = (destinationId: string) => { const navigateToChapter = (destinationId: string) => {
router.push(`/books/${bookId}/chapters/${destinationId}`); router.push(`/books/${bookId}/chapters/${destinationId}`);
}; };
Ad.patreon
const navigateToAllChapters = () => { const navigateToAllChapters = () => {
router.push(`/books/${bookId}`); router.push(`/books/${bookId}`);
@ -24,14 +24,14 @@ const NavigationButtons: React.FC<NavigationButtonsProps> = ({ bookId, documentI
return ( return (
<div className="mt-8 flex justify-between"> <div className="mt-2 flex justify-between">
<button <button
className={` className={`
bg-teal-500 text-white py-2 px-4 rounded bg-teal-500 text-white py-2 px-4 rounded
hover:bg-teal-600 hover:bg-teal-600
disabled:bg-gray-400 disabled:cursor-not-allowed disabled:bg-gray-400 disabled:cursor-not-allowed
`} `}
onClick={() => prevChapter === ""? navigateToAllChapters() : navigateToChapter(prevChapter)} onClick={() => prevChapter === "" ? navigateToAllChapters() : navigateToChapter(prevChapter)}
disabled={prevChapter === ""} disabled={prevChapter === ""}
> >
Prev Chapter Prev Chapter
@ -42,19 +42,32 @@ const NavigationButtons: React.FC<NavigationButtonsProps> = ({ bookId, documentI
> >
All Chapters All Chapters
</button> </button>
<button {nextChapter !== "" ? (
className={` <button
bg-green-500 text-white py-2 px-4 rounded className={`
hover:bg-green-600 bg-green-500 text-white py-2 px-4 rounded
disabled:bg-gray-400 disabled:cursor-not-allowed hover:bg-green-600
`} disabled:bg-gray-400 disabled:cursor-not-allowed
onClick={() => `}
nextChapter === "" ? navigateToAllChapters() : navigateToChapter(nextChapter) onClick={() => navigateToChapter(nextChapter)}
} >
disabled={nextChapter === ""} Next Chapter
> </button>
Next Chapter ) : (
</button> <a
href={Ad.patreon}
target="_blank"
rel="noopener noreferrer"
className={`
bg-yellow-500 text-white py-2 px-4 rounded
hover:bg-yellow-600
transition duration-200
`}
>
Unreleased Chapters
</a>
)
}
</div> </div>
); );
}; };

View File

@ -50,8 +50,8 @@ export async function fetchBooks(): Promise<Book[]> {
} }
export async function fetchBookChapterLinks(bookId: string): Promise<Book> { export async function fetchBookChapterLinks(bookId: string): Promise<Book> {
const currentDate = new Date().toISOString().split("T")[0]; const currentDateTime = new Date().toISOString();
const data = await fetchFromAPI<{ data: Book }>(`/api/books/${bookId}?populate[chapters][filters][ReleaseDate][$lte]=${currentDate}`); const data = await fetchFromAPI<{ data: Book }>(`/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${currentDateTime}`);
return data.data return data.data
} }
@ -60,9 +60,9 @@ export async function fetchBookChapterLinks(bookId: string): Promise<Book> {
* Filters chapters by release date to include only valid ones. * Filters chapters by release date to include only valid ones.
*/ */
export async function fetchBookById(bookId: string): Promise<Book> { export async function fetchBookById(bookId: string): Promise<Book> {
const currentDate = new Date().toISOString().split("T")[0]; const currentDateTime = new Date().toISOString();
const data = await fetchFromAPI<{ data: Book }>( const data = await fetchFromAPI<{ data: Book }>(
`/api/books/${bookId}?populate[chapters][filters][ReleaseDate][$lte]=${currentDate}` `/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${currentDateTime}&populate=cover`
); );
return data.data; return data.data;
} }

View File

@ -2,10 +2,11 @@
export interface Chapter { export interface Chapter {
id: number; id: number;
documentId: string; documentId: string;
Name: string; number: number;
Chapter_Number: number; title: string;
ReleaseDate: string; editor?: Editor;
Content: string; release_date: string;
content: string;
book?: Book; book?: Book;
} }
@ -13,13 +14,14 @@ export interface Editor {
id: number; id: number;
name: string; name: string;
email: string; email: string;
books: Book[]; discord: string;
chapters: Chapter[];
} }
export interface Glossary { export interface Glossary {
id: number; id: number;
name: string; chinese_english: JSON;
entries: string[]; english_english: JSON;
} }
export interface Media { export interface Media {
@ -50,16 +52,18 @@ export interface Media {
export interface Book { export interface Book {
id: number; id: number;
documentId: string; documentId: string;
Name: string; title: string;
ReleaseDate: string; raw_title: string;
author: string;
raw_author: string;
cover: Media[] | null;
description: string;
release_date: string;
chapters: Chapter[]; chapters: Chapter[];
Cover: Media | null;
Author: string;
Complete: boolean;
editors: Editor[];
RawName: string;
RawAuthor: string;
glossary: Glossary; glossary: Glossary;
Description: string;
} }
export const Ad = {
patreon: "https://patreon.com/nulltranslationgroup",
}