diff options
| author | real-zephex <[email protected]> | 2024-06-07 09:55:23 +0530 |
|---|---|---|
| committer | real-zephex <[email protected]> | 2024-06-07 09:55:23 +0530 |
| commit | bdd48555bf59552864d5a59a3ee43291e4136b47 (patch) | |
| tree | dc3ab66ac60fe715b79c17843f9e87646aaae93a /src/app/manga | |
| parent | Delete src/app/globals.module.css (diff) | |
| download | dramalama-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.jsx | 50 | ||||
| -rw-r--r-- | src/app/manga/components/chapterPages.jsx | 35 | ||||
| -rw-r--r-- | src/app/manga/components/descriptionTabs.jsx | 159 | ||||
| -rw-r--r-- | src/app/manga/components/inputContainer.jsx | 116 | ||||
| -rw-r--r-- | src/app/manga/components/requests.js | 28 | ||||
| -rw-r--r-- | src/app/manga/page.jsx | 21 |
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; |