aboutsummaryrefslogtreecommitdiff
path: root/src/app/manga
diff options
context:
space:
mode:
authorreal-zephex <[email protected]>2024-06-07 09:55:23 +0530
committerreal-zephex <[email protected]>2024-06-07 09:55:23 +0530
commitbdd48555bf59552864d5a59a3ee43291e4136b47 (patch)
treedc3ab66ac60fe715b79c17843f9e87646aaae93a /src/app/manga
parentDelete src/app/globals.module.css (diff)
downloaddramalama-bdd48555bf59552864d5a59a3ee43291e4136b47.tar.xz
dramalama-bdd48555bf59552864d5a59a3ee43291e4136b47.zip
🚀 feat(ui): added manga with better UI
Diffstat (limited to 'src/app/manga')
-rw-r--r--src/app/manga/[id]/page.jsx50
-rw-r--r--src/app/manga/components/chapterPages.jsx35
-rw-r--r--src/app/manga/components/descriptionTabs.jsx159
-rw-r--r--src/app/manga/components/inputContainer.jsx116
-rw-r--r--src/app/manga/components/requests.js28
-rw-r--r--src/app/manga/page.jsx21
6 files changed, 409 insertions, 0 deletions
diff --git a/src/app/manga/[id]/page.jsx b/src/app/manga/[id]/page.jsx
new file mode 100644
index 0000000..d6828b2
--- /dev/null
+++ b/src/app/manga/[id]/page.jsx
@@ -0,0 +1,50 @@
+import { MangaInfoResults } from "../components/requests";
+import Image from "next/image";
+import { Chip } from "@nextui-org/react";
+
+import MangaDescriptionTabs from "../components/descriptionTabs";
+
+const MangaInfoPage = async ({ params }) => {
+ const { id } = params;
+
+ const data = await MangaInfoResults(id);
+
+ return (
+ <section>
+ <section>
+ <section className="m-auto w-full lg:w-9/12">
+ {/* header section */}
+ <div className="flex items-center p-2">
+ <Image
+ src={data.image}
+ width={170}
+ height={280}
+ className="rounded-lg"
+ alt="Manga Poster"
+ />
+ <div className="ml-2">
+ <h3 className="text-2xl">
+ {data.title.english || data.title.romaji}
+ </h3>
+ {data.genres &&
+ data.genres.map((item, index) => (
+ <Chip
+ key={index}
+ color="warning"
+ variant="faded"
+ size="sm"
+ className="mr-1"
+ >
+ {item}
+ </Chip>
+ ))}
+ </div>
+ </div>
+ <MangaDescriptionTabs data={data} />
+ </section>
+ </section>
+ </section>
+ );
+};
+
+export default MangaInfoPage;
diff --git a/src/app/manga/components/chapterPages.jsx b/src/app/manga/components/chapterPages.jsx
new file mode 100644
index 0000000..59320fd
--- /dev/null
+++ b/src/app/manga/components/chapterPages.jsx
@@ -0,0 +1,35 @@
+"use server";
+
+import { MangaPages } from "./requests";
+import Image from "next/image";
+
+const MangaChapters = async (id) => {
+ const data = await MangaPages(id);
+
+ let chapterPages = [];
+ for (let items of data.chapter.data) {
+ chapterPages.push(`${data.baseUrl}/data/${data.chapter.hash}/${items}`);
+ }
+ console.log(chapterPages);
+
+ return (
+ <div className="flex flex-col items-center">
+ {chapterPages &&
+ chapterPages.length > 0 &&
+ chapterPages.map((item, index) => (
+ <div key={index} className="mb-4">
+ <Image
+ src={`https://sup-proxy.zephex0-f6c.workers.dev/api-content?url=${item}&referer=https://mangadex.org`}
+ width={1280}
+ height={720}
+ className="h-auto w-auto"
+ alt="Manga Pages"
+ />
+ <p className="text-center">{index}</p>
+ </div>
+ ))}
+ </div>
+ );
+};
+
+export default MangaChapters;
diff --git a/src/app/manga/components/descriptionTabs.jsx b/src/app/manga/components/descriptionTabs.jsx
new file mode 100644
index 0000000..19191ab
--- /dev/null
+++ b/src/app/manga/components/descriptionTabs.jsx
@@ -0,0 +1,159 @@
+"use client";
+
+import {
+ Tabs,
+ Tab,
+ Card,
+ CardBody,
+ Divider,
+ Image,
+ Select,
+ SelectItem,
+} from "@nextui-org/react";
+import { FaRegThumbsUp, FaRegStar } from "react-icons/fa";
+import Link from "next/link";
+import { useState } from "react";
+
+import MangaChapters from "./chapterPages";
+
+const MangaDescriptionTabs = ({ data }) => {
+ const [pages, setPages] = useState(<></>);
+
+ async function get_pages(id) {
+ setPages(<p className="text-center">Loading...</p>);
+ const data = await MangaChapters(id);
+ setPages(data);
+ }
+
+ return (
+ <div className="flex w-full flex-col">
+ <Tabs aria-label="Options">
+ <Tab key="description" title="Description">
+ <Card shadow="sm">
+ <CardBody
+ dangerouslySetInnerHTML={{
+ __html: data.description,
+ }}
+ ></CardBody>
+ </Card>
+ </Tab>
+ <Tab key="details" title="Details">
+ <Card shadow="sm">
+ <CardBody>
+ <h4>
+ <strong>Status</strong>:{" "}
+ <span>{data.status || "not sure"}</span>
+ </h4>
+ <h4>
+ <strong>Type</strong>:{" "}
+ <span>{data.type || "not sure"}</span>
+ </h4>
+ <h4>
+ <strong className="text-green-400">
+ Started on
+ </strong>
+ :{" "}
+ <span>
+ {data.startDate.day}-{data.startDate.month}-
+ {data.startDate.year}
+ </span>
+ </h4>
+ <h4>
+ <strong className="text-red-400">
+ Ended on
+ </strong>
+ :{" "}
+ <span>
+ {data.endDate.day}-{data.endDate.month}-
+ {data.endDate.year}
+ </span>
+ </h4>
+ <div className="flex items-center">
+ <section className="flex items-center">
+ <FaRegThumbsUp />
+ <p className="ml-1">{data.popularity}</p>
+ </section>
+ <Divider orientation="vertical" />
+ <section className="ml-2 flex items-center">
+ <FaRegStar />
+ <p className="ml-1">
+ {Number(data.rating) / 10}
+ </p>
+ </section>
+ </div>
+ </CardBody>
+ </Card>
+ </Tab>
+ <Tab key="recommendations" title="Recommendation">
+ <Card shadow="sm">
+ <CardBody>
+ {data.recommendations &&
+ data.recommendations.length > 0 &&
+ data.recommendations.map((item, index) => (
+ <Link
+ key={index}
+ href={`/manga/${item.id}`}
+ >
+ <Card
+ isPressable
+ isHoverable
+ shadow="sm"
+ className="mb-2 flex w-full flex-row items-center"
+ >
+ <Image
+ width={120}
+ src={item.image}
+ className="p-1"
+ shadow="lg"
+ isBlurred
+ />
+ <CardBody>
+ <p className="text-xl">
+ {item.title.english ||
+ item.title.romaji}
+ </p>
+ </CardBody>
+ </Card>
+ </Link>
+ ))}
+ </CardBody>
+ </Card>
+ </Tab>
+ <Tab key="chapter" title="Chapter">
+ <Card shadow="sm" className="p-2">
+ <Select
+ className="w-full lg:max-w-md"
+ label="Select chapter"
+ >
+ {data.chapters &&
+ data.chapters.length > 0 &&
+ data.chapters.map((item, index) => {
+ if (item.pages > 0) {
+ return (
+ <SelectItem
+ key={index}
+ onClick={async () =>
+ await get_pages(item.id)
+ }
+ textValue={item.title}
+ >
+ {item.title} -{" "}
+ {item.chapterNumber}
+ </SelectItem>
+ );
+ } else {
+ return;
+ }
+ })}
+ </Select>
+ <CardBody>
+ <div className="w-full">{pages}</div>
+ </CardBody>
+ </Card>
+ </Tab>
+ </Tabs>
+ </div>
+ );
+};
+
+export default MangaDescriptionTabs;
diff --git a/src/app/manga/components/inputContainer.jsx b/src/app/manga/components/inputContainer.jsx
new file mode 100644
index 0000000..7526f9a
--- /dev/null
+++ b/src/app/manga/components/inputContainer.jsx
@@ -0,0 +1,116 @@
+"use client";
+
+import {
+ Input,
+ Progress,
+ Card,
+ CardBody,
+ Image,
+ Chip,
+} from "@nextui-org/react";
+import Link from "next/link";
+import { useState } from "react";
+
+import { SearchedMangaResults } from "./requests";
+
+const MangaSearchBox = () => {
+ const [searchedMangaTitle, setMangaSearchedTitle] = useState("");
+ const [results, setResults] = useState(
+ <div className="mt-4 w-full">
+ <p className="text-center">
+ Start typing and results will show here
+ </p>
+ </div>,
+ );
+ const [loading, setLoading] = useState(<></>);
+
+ async function GetResults() {
+ if (!searchedMangaTitle) {
+ setResults(<></>);
+ return;
+ }
+ setLoading(
+ <Progress
+ size="sm"
+ isIndeterminate
+ aria-label="Loading..."
+ className="mb-4 mt-4 w-full"
+ />,
+ );
+ const data = await SearchedMangaResults(searchedMangaTitle);
+ const format = (
+ <div className="mt-2 w-full">
+ {data && data.results.length > 0 ? (
+ data.results.map((item, index) => (
+ <Link href={`/manga/${item.id}`} key={index}>
+ <Card
+ isPressable
+ isBlurred
+ isHoverable
+ shadow="sm"
+ className="mb-2 flex w-full flex-row items-center"
+ >
+ <Image
+ src={item.image}
+ width={150}
+ isBlurred
+ shadow="sm"
+ className="p-1"
+ />
+ <CardBody>
+ <p className="text-xl">
+ {item.title.english ||
+ item.title.romaji}
+ </p>
+ <section>
+ {item.genres &&
+ item.genres.map((item, index) => (
+ <Chip
+ key={index}
+ size="sm"
+ color="warning"
+ variant="faded"
+ className="mr-1"
+ >
+ {item}
+ </Chip>
+ ))}
+ </section>
+ </CardBody>
+ </Card>
+ </Link>
+ ))
+ ) : (
+ <p className="text-center">
+ No results found for the searched title
+ </p>
+ )}
+ </div>
+ );
+ setLoading(<></>);
+ setResults(format);
+ }
+ return (
+ <main>
+ <div className="flex w-full flex-wrap">
+ <Input
+ type="text"
+ autoFocus
+ label="Manga"
+ placeholder="Enter manga/manhwa title"
+ value={searchedMangaTitle}
+ onChange={(event) => {
+ setMangaSearchedTitle(event.target.value);
+ }}
+ onKeyDown={async () => {
+ await GetResults();
+ }}
+ />
+ {loading}
+ {results}
+ </div>
+ </main>
+ );
+};
+
+export default MangaSearchBox;
diff --git a/src/app/manga/components/requests.js b/src/app/manga/components/requests.js
new file mode 100644
index 0000000..d3d9e9c
--- /dev/null
+++ b/src/app/manga/components/requests.js
@@ -0,0 +1,28 @@
+"use server";
+import {
+ manga_search_url,
+ manga_info_url,
+ manga_chapters_pages,
+} from "../../../../utils/manga_urls";
+
+export const SearchedMangaResults = async (title) => {
+ const res = await fetch(manga_search_url(title), {
+ next: { revalidate: 21600 },
+ });
+ const data = await res.json();
+ return data;
+};
+
+export const MangaInfoResults = async (id) => {
+ const res = await fetch(manga_info_url(id), {
+ next: { revalidate: 21600 },
+ });
+ const data = await res.json();
+ return data;
+};
+
+export const MangaPages = async (id) => {
+ const res = await fetch(manga_chapters_pages(id), { cache: "force-cache" });
+ const data = await res.json();
+ return data;
+};
diff --git a/src/app/manga/page.jsx b/src/app/manga/page.jsx
new file mode 100644
index 0000000..6992fa7
--- /dev/null
+++ b/src/app/manga/page.jsx
@@ -0,0 +1,21 @@
+import MangaSearchBox from "./components/inputContainer";
+
+const MangaHomePage = async () => {
+ return (
+ <main className="flex h-[90dvh] w-full flex-col items-center">
+ <div className="mt-2">
+ <p className="text-center text-xl">
+ Welcome to <br />
+ <span className="text-3xl text-sky-400">
+ Dramalama-Manga
+ </span>
+ </p>
+ </div>
+ <div className="mt-2 w-full lg:w-1/3">
+ <MangaSearchBox />
+ </div>
+ </main>
+ );
+};
+
+export default MangaHomePage;