Logo swap, layour changes so the night mode button stays on top. Change src images to Image from next otherwise lint will complain. Api changes to accomendate the new Strapi. Same for type changes to fit into the new Strapi.

This commit is contained in:
Hieuhuy Pham 2025-01-12 04:12:17 -05:00
parent 3fed14c353
commit 7a0ec01a39
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 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 next_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.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.number === current_chapter.number - 1)?.documentId || "" : "";
// Fetch chapter data
if (current_chapter === undefined) {
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">
<div dangerouslySetInnerHTML={{ __html: '<center><h1> Chapter not found !</h1></center>' }}></div>
{/* Client component for navigation */}
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
</div>
</div>
<div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg mt-4">
<div dangerouslySetInnerHTML={{ __html: '<center><h1> Chapter not found !</h1></center>' }}></div>
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
</div>
)
}
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} />
<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>
);

View File

@ -1,7 +1,8 @@
import { fetchBookChapterLinks } from "@/lib/api";
import { fetchBookById, fetchBookChapterLinks } from "@/lib/api";
import { Book } from "@/lib/types";
import { formatDateToMonthDayYear } from "@/lib/utils";
import ChapterDropdown from "@/components/ChapterDropdown";
import Image from "next/image";
export type paramsType = Promise<{ bookId: string}>;
@ -11,7 +12,7 @@ export default async function BookPage(props: { params: paramsType }) {
let book: Book;
try {
book = await fetchBookChapterLinks(bookId);
book = await fetchBookById(bookId);
} catch (error) {
console.error(error);
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;
return (
<div className="max-w-6xl mx-auto py-10 px-4">
<div className="flex items-center justify-between mb-4 pt-4">
{/* Book Title */}
<h1 className="text-5xl font-bold">{Name}</h1>
{/* Patreon Button */}
<a
href="https://www.patreon.com/c/nulltranslationgroup/membership?view_as=patron" // Replace with your Patreon URL
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 for Unreleased Chapters
</a>
<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"
/>
<div className="flex items-center justify-between mb-4 pt-4">
<h1 className="text-5xl font-bold">{title}</h1>
<a
href="https://www.patreon.com/c/nulltranslationgroup/membership?view_as=patron" // Replace with your Patreon URL
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 for Unreleased Chapters
</a>
</div>
</div>
<p className="text-lg text-gray-600 dark:text-gray-400 mb-4">
<strong>Author:</strong> {Author}
<strong>Author:</strong> {author}
<br></br>
<strong>Translator:</strong> Null Translation Group
</p>
<p className="mb-6">{Description}</p>
<p className="mb-6">{description}</p>
<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">
@ -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"
>
<h3 className="text-xl font-medium">
Chapter {chapter.Chapter_Number}: {chapter.Name}
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.ReleaseDate))}
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_date))}
</p>
</a>
</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"
>
<h3 className="text-xl font-medium">
Chapter {chapter.Chapter_Number}: {chapter.Name}
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.ReleaseDate))}
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.release_date))}
</p>
</a>
</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">
<Navbar />
<main className="relative">{children}</main>
<div className="absolute bottom-4 right-4">
<div className="fixed bottom-4 right-4">
<NightModeToggle isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />
</div>
</body>

View File

@ -1,6 +1,6 @@
import { Book } from "@/lib/types";
import { fetchBooks } from "@/lib/api";
import Image from "next/image";
export default async function HomePage() {
let books: Book[] = [];
@ -17,7 +17,6 @@ export default async function HomePage() {
return (
<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">
<a
href="https://patreon.com/NullTranslationGroup"
@ -29,28 +28,29 @@ export default async function HomePage() {
</a>
</div>
{/* Books Grid */}
<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
key={book.id}
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">
<img
src={`${process.env.NEXT_PUBLIC_API_URL}${book.Cover.url}`}
alt={book.Cover.alternativeText || `Cover of ${book.Name}`}
className="rounded-lg object-cover"
src={`${process.env.NEXT_PUBLIC_API_URL}${cover?.url}`}
alt={cover?.alternativeText || `Cover of ${book.title}`}
className="rounded-lg object-cover w-full h-full"
/>
</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">
<strong>Author:</strong> {book.Author}
<strong>Author:</strong> {book.author}
</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
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
</a>
</div>
))}
)})}
</div>
</div>
);

View File

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

View File

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

View File

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

View File

@ -50,8 +50,8 @@ export async function fetchBooks(): Promise<Book[]> {
}
export async function fetchBookChapterLinks(bookId: string): Promise<Book> {
const currentDate = new Date().toISOString().split("T")[0];
const data = await fetchFromAPI<{ data: Book }>(`/api/books/${bookId}?populate[chapters][filters][ReleaseDate][$lte]=${currentDate}`);
const currentDateTime = new Date().toISOString();
const data = await fetchFromAPI<{ data: Book }>(`/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${currentDateTime}`);
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.
*/
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 }>(
`/api/books/${bookId}?populate[chapters][filters][ReleaseDate][$lte]=${currentDate}`
`/api/books/${bookId}?populate[chapters][filters][release_datetime][$lte]=${currentDateTime}&populate=cover`
);
return data.data;
}

View File

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