Full push for initial version of NullTranslationGroup website.
This commit is contained in:
46
src/app/books/[bookId]/chapters/[chapterId]/page.tsx
Normal file
46
src/app/books/[bookId]/chapters/[chapterId]/page.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import NavigationButtons from "@/components/NavigationButtons";
|
||||
import { Chapter } from "@/lib/types";
|
||||
import { fetchBookById } from "@/lib/api";
|
||||
|
||||
export type paramsType = Promise<{ bookId: string; chapterId: string }>;
|
||||
|
||||
// Dynamic page component
|
||||
export default async function ChapterPage(props: { params: paramsType}) {
|
||||
const { bookId, chapterId } = await props.params;
|
||||
|
||||
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 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 || "" : "";
|
||||
// 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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="prose dark:prose-invert mx-auto p-6 bg-white dark:bg-gray-800 shadow-md rounded-lg">
|
||||
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter} />
|
||||
|
||||
<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}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
88
src/app/books/[bookId]/page.tsx
Normal file
88
src/app/books/[bookId]/page.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { fetchBookChapterLinks } from "@/lib/api";
|
||||
import { Book } from "@/lib/types";
|
||||
import { formatDateToMonthDayYear } from "@/lib/utils";
|
||||
import ChapterDropdown from "@/components/ChapterDropdown";
|
||||
|
||||
export type paramsType = Promise<{ bookId: string}>;
|
||||
|
||||
|
||||
export default async function BookPage(props: { params: paramsType }) {
|
||||
const { bookId } = await props.params;
|
||||
|
||||
let book: Book;
|
||||
try {
|
||||
book = await fetchBookChapterLinks(bookId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return (
|
||||
<div className="text-center mt-10 text-red-500">
|
||||
Error fetching book data. Please try again later.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { Name, Author, Description, chapters } = book;
|
||||
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>
|
||||
|
||||
<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}</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">
|
||||
{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.Chapter_Number}: {chapter.Name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.ReleaseDate))}
|
||||
</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={chapters} bookId={bookId} />
|
||||
</div>
|
||||
{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.Chapter_Number}: {chapter.Name}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
<strong>Release Date:</strong> {formatDateToMonthDayYear(new Date(chapter.ReleaseDate))}
|
||||
</p>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</div>);
|
||||
}
|
||||
@@ -1,33 +1,61 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "./globals.css";
|
||||
"use client";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
import "tailwindcss/tailwind.css";
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: "--font-geist-mono",
|
||||
subsets: ["latin"],
|
||||
});
|
||||
import React, { useEffect, useState } from "react";
|
||||
import NightModeToggle from "@/components/NightModeToggle";
|
||||
import Navbar from "@/components/NavigationBar";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
};
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
|
||||
const shouldEnableDark = savedTheme === "dark" || (!savedTheme && prefersDark);
|
||||
setIsDarkMode(shouldEnableDark);
|
||||
|
||||
if (shouldEnableDark) {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
}, []);
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
const newTheme = isDarkMode ? "light" : "dark";
|
||||
setIsDarkMode(!isDarkMode);
|
||||
localStorage.setItem("theme", newTheme);
|
||||
|
||||
document.documentElement.classList.toggle("dark", !isDarkMode);
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<html lang="en" className={isDarkMode ? "dark" : ""} suppressHydrationWarning>
|
||||
<head>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
if (savedTheme === 'dark' || (!savedTheme && prefersDark)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
})();
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<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">
|
||||
<NightModeToggle isDarkMode={isDarkMode} toggleDarkMode={toggleDarkMode} />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
153
src/app/page.tsx
153
src/app/page.tsx
@@ -1,101 +1,66 @@
|
||||
import Image from "next/image";
|
||||
import { Book } from "@/lib/types";
|
||||
import { fetchBooks } from "@/lib/api";
|
||||
|
||||
export default async function HomePage() {
|
||||
let books: Book[] = [];
|
||||
|
||||
try {
|
||||
books = await fetchBooks();
|
||||
} catch (error) {
|
||||
return (
|
||||
console.log(error),
|
||||
<div className="text-center mt-10 text-red-500">
|
||||
Error fetching books. Please try again later.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
<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"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-semibold text-center block"
|
||||
>
|
||||
🌟 Join Us on Patreon for Unreleased Chapters!
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{/* Books Grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 px-6">
|
||||
{books.map((book: Book) => (
|
||||
<div
|
||||
key={book.id}
|
||||
className="p-4 bg-white dark:bg-gray-800 rounded-lg shadow-md hover:shadow-lg transition flex flex-col"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
{book.Cover?.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"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<h2 className="text-2xl font-semibold mb-2">{book.Name}</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
<strong>Author:</strong> {book.Author}
|
||||
</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"
|
||||
href={`/books/${book.documentId}`}
|
||||
>
|
||||
Read Book
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user