aboutsummaryrefslogtreecommitdiff
path: root/pages/en
diff options
context:
space:
mode:
Diffstat (limited to 'pages/en')
-rw-r--r--pages/en/anime/[...id].js11
-rw-r--r--pages/en/anime/recently-watched.js7
-rw-r--r--pages/en/anime/watch/[...info].js23
-rw-r--r--pages/en/index.js38
-rw-r--r--pages/en/manga/[...id].js425
-rw-r--r--pages/en/manga/[id].js146
-rw-r--r--pages/en/manga/read/[...params].js202
-rw-r--r--pages/en/profile/[user].js2
-rw-r--r--pages/en/search/[...param].js305
9 files changed, 859 insertions, 300 deletions
diff --git a/pages/en/anime/[...id].js b/pages/en/anime/[...id].js
index 910bbc6..e2c0039 100644
--- a/pages/en/anime/[...id].js
+++ b/pages/en/anime/[...id].js
@@ -72,6 +72,8 @@ export default function Info({ info, color }) {
}
}
fetchData();
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, info, session?.user?.name]);
function handleOpen() {
@@ -143,7 +145,7 @@ export default function Info({ info, color }) {
stats={statuses?.value}
prg={progress}
max={info?.episodes}
- image={info}
+ info={info}
close={handleClose}
/>
)}
@@ -208,7 +210,12 @@ export default function Info({ info, color }) {
export async function getServerSideProps(ctx) {
const { id } = ctx.query;
- const API_URI = process.env.API_URI;
+
+ let API_URI;
+ API_URI = process.env.API_URI;
+ if (API_URI.endsWith("/")) {
+ API_URI = API_URI.slice(0, -1);
+ }
let cache;
diff --git a/pages/en/anime/recently-watched.js b/pages/en/anime/recently-watched.js
index c723394..6abf09d 100644
--- a/pages/en/anime/recently-watched.js
+++ b/pages/en/anime/recently-watched.js
@@ -6,12 +6,12 @@ import Skeleton from "react-loading-skeleton";
import Footer from "@/components/shared/footer";
import { getServerSession } from "next-auth";
import { authOptions } from "../../api/auth/[...nextauth]";
-import { toast } from "react-toastify";
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";
import HistoryOptions from "@/components/home/content/historyOptions";
import Head from "next/head";
import MobileNav from "@/components/shared/MobileNav";
+import { toast } from "sonner";
export default function PopularAnime({ sessions }) {
const [data, setData] = useState(null);
@@ -105,11 +105,6 @@ export default function PopularAnime({ sessions }) {
if (data?.message === "Episode deleted") {
toast.success("Episode removed from history", {
position: "bottom-right",
- autoClose: 5000,
- hideProgressBar: false,
- closeOnClick: true,
- draggable: true,
- theme: "dark",
});
}
} else {
diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js
index f918f86..a838b7f 100644
--- a/pages/en/anime/watch/[...info].js
+++ b/pages/en/anime/watch/[...info].js
@@ -29,8 +29,12 @@ export async function getServerSideProps(context) {
};
}
- const proxy = process.env.PROXY_URI;
- const disqus = process.env.DISQUS_SHORTNAME || null;
+ let proxy;
+ proxy = process.env.PROXY_URI;
+ if (proxy.endsWith("/")) {
+ proxy = proxy.slice(0, -1);
+ }
+ const disqus = process.env.DISQUS_SHORTNAME;
const [aniId, provider] = query?.info;
const watchId = query?.id;
@@ -114,7 +118,7 @@ export async function getServerSideProps(context) {
epiNumber: epiNumber || null,
dub: dub || null,
userData: userData?.[0] || null,
- info: data.data.Media || null,
+ info: data?.data?.Media || null,
proxy,
disqus,
},
@@ -179,9 +183,10 @@ export default function Watch({
if (episodes) {
const getProvider = episodes?.find((i) => i.providerId === provider);
- const episodeList = dub
- ? getProvider?.episodes?.filter((x) => x.hasDub === true)
- : getProvider?.episodes.slice(0, getMap?.episodes.length);
+ const episodeList = getProvider?.episodes.slice(
+ 0,
+ getMap?.episodes.length
+ );
const playingData = getMap?.episodes.find(
(i) => i.number === Number(epiNumber)
);
@@ -219,6 +224,7 @@ export default function Watch({
return () => {
setEpisodeNavigation(null);
};
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessions?.user?.name, epiNumber, dub]);
useEffect(() => {
@@ -287,6 +293,8 @@ export default function Watch({
});
setMarked(0);
};
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [provider, watchId, info?.id]);
useEffect(() => {
@@ -524,7 +532,7 @@ export default function Watch({
</div>
<div
id="secondary"
- className={`relative ${theaterMode ? "pt-2" : ""}`}
+ className={`relative ${theaterMode ? "pt-5" : "pt-4 lg:pt-0"}`}
>
<EpisodeLists
info={info}
@@ -534,6 +542,7 @@ export default function Watch({
watchId={watchId}
episode={episodesList}
artStorage={artStorage}
+ track={episodeNavigation}
dub={dub}
/>
</div>
diff --git a/pages/en/index.js b/pages/en/index.js
index 9be3c2c..29b0778 100644
--- a/pages/en/index.js
+++ b/pages/en/index.js
@@ -118,23 +118,23 @@ export default function Home({ detail, populars, upComing }) {
}
}, [upComing]);
- useEffect(() => {
- const getSchedule = async () => {
- try {
- const res = await fetch(`/api/v2/etc/schedule`);
- const data = await res.json();
-
- if (!res.ok) {
- setSchedules(null);
- } else {
- setSchedules(data);
- }
- } catch (err) {
- console.log(err);
- }
- };
- getSchedule();
- }, []);
+ // useEffect(() => {
+ // const getSchedule = async () => {
+ // try {
+ // const res = await fetch(`/api/v2/etc/schedule`);
+ // const data = await res.json();
+
+ // if (!res.ok) {
+ // setSchedules(null);
+ // } else {
+ // setSchedules(data);
+ // }
+ // } catch (err) {
+ // console.log(err);
+ // }
+ // };
+ // getSchedule();
+ // }, []);
const [releaseData, setReleaseData] = useState([]);
@@ -290,6 +290,8 @@ export default function Home({ detail, populars, upComing }) {
}
}
userData();
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessions?.user?.name, currentAnime, plan]);
// console.log({ recentAdded });
@@ -402,7 +404,7 @@ export default function Home({ detail, populars, upComing }) {
</div>
)}
- <div className="lg:mt-16 mt-5 flex flex-col gap-5 items-center">
+ <div className="lg:mt-16 mt-5 flex flex-col items-center">
<motion.div
className="w-screen flex-none lg:w-[95%] xl:w-[87%]"
initial={{ opacity: 0 }}
diff --git a/pages/en/manga/[...id].js b/pages/en/manga/[...id].js
new file mode 100644
index 0000000..106bce2
--- /dev/null
+++ b/pages/en/manga/[...id].js
@@ -0,0 +1,425 @@
+import ChapterSelector from "@/components/manga/chapters";
+import Footer from "@/components/shared/footer";
+import Head from "next/head";
+import { useEffect, useState } from "react";
+import { getServerSession } from "next-auth";
+import { authOptions } from "../../api/auth/[...nextauth]";
+import { mediaInfoQuery } from "@/lib/graphql/query";
+import Modal from "@/components/modal";
+import { signIn, useSession } from "next-auth/react";
+import AniList from "@/components/media/aniList";
+import ListEditor from "@/components/listEditor";
+import MobileNav from "@/components/shared/MobileNav";
+import Image from "next/image";
+import DetailTop from "@/components/anime/mobile/topSection";
+import Characters from "@/components/anime/charactersCard";
+import Content from "@/components/home/content";
+import { toast } from "sonner";
+import axios from "axios";
+import getAnifyInfo from "@/lib/anify/info";
+import { redis } from "@/lib/redis";
+import getMangaId from "@/lib/anify/getMangaId";
+
+export default function Manga({ info, anifyData, color, chapterNotFound }) {
+ const [domainUrl, setDomainUrl] = useState("");
+ const { data: session } = useSession();
+
+ const [loading, setLoading] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const [statuses, setStatuses] = useState(null);
+ const [watch, setWatch] = useState();
+
+ const [chapter, setChapter] = useState(null);
+
+ const [open, setOpen] = useState(false);
+
+ const rec = info?.recommendations?.nodes?.map(
+ (data) => data.mediaRecommendation
+ );
+
+ useEffect(() => {
+ setDomainUrl(window.location.origin);
+ }, []);
+
+ useEffect(() => {
+ if (chapterNotFound) {
+ toast.error("Chapter not found");
+ const cleanUrl = window.location.origin + window.location.pathname;
+ window.history.replaceState(null, null, cleanUrl);
+ }
+ }, [chapterNotFound]);
+
+ useEffect(() => {
+ async function fetchData() {
+ try {
+ setLoading(true);
+
+ const { data } = await axios.get(`/api/v2/info?id=${anifyData.id}`);
+
+ if (!data.chapters) {
+ setLoading(false);
+ return;
+ }
+
+ setChapter(data);
+ setLoading(false);
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ fetchData();
+
+ return () => {
+ setChapter(null);
+ };
+ }, [info?.id]);
+
+ function handleOpen() {
+ setOpen(true);
+ document.body.style.overflow = "hidden";
+ }
+
+ function handleClose() {
+ setOpen(false);
+ document.body.style.overflow = "auto";
+ }
+
+ return (
+ <>
+ <Head>
+ <title>
+ {info
+ ? `Manga - ${
+ info.title.romaji || info.title.english || info.title.native
+ }`
+ : "Getting Info..."}
+ </title>
+ <meta name="twitter:card" content="summary_large_image" />
+ <meta
+ name="twitter:title"
+ content={`Moopa - ${info.title.romaji || info.title.english}`}
+ />
+ <meta
+ name="twitter:description"
+ content={`${info.description?.slice(0, 180)}...`}
+ />
+ <meta
+ name="twitter:image"
+ content={`${domainUrl}/api/og?title=${
+ info.title.romaji || info.title.english
+ }&image=${info.bannerImage || info.coverImage}`}
+ />
+ <meta
+ name="title"
+ data-title-romaji={info?.title?.romaji}
+ data-title-english={info?.title?.english}
+ data-title-native={info?.title?.native}
+ />
+ </Head>
+ <Modal open={open} onClose={() => handleClose()}>
+ <div>
+ {!session && (
+ <div className="flex-center flex-col gap-5 px-10 py-5 bg-secondary rounded-md">
+ <div className="text-md font-extrabold font-karla">
+ Edit your list
+ </div>
+ <button
+ className="flex items-center bg-[#363642] rounded-md text-white p-1"
+ onClick={() => signIn("AniListProvider")}
+ >
+ <h1 className="px-1 font-bold font-karla">
+ Login with AniList
+ </h1>
+ <div className="scale-[60%] pb-[1px]">
+ <AniList />
+ </div>
+ </button>
+ </div>
+ )}
+ {session && info && (
+ <ListEditor
+ animeId={info?.id}
+ session={session}
+ stats={statuses?.value}
+ prg={progress}
+ max={info?.episodes}
+ info={info}
+ close={handleClose}
+ />
+ )}
+ </div>
+ </Modal>
+ <MobileNav sessions={session} hideProfile={true} />
+ <main className="w-screen min-h-screen overflow-hidden relative flex flex-col items-center gap-5">
+ {/* <div className="absolute bg-gradient-to-t from-primary from-85% to-100% to-transparent w-screen h-full z-10" /> */}
+ <div className="w-screen absolute">
+ <div className="bg-gradient-to-t from-primary from-10% to-transparent absolute h-[280px] w-screen z-10 inset-0" />
+ {info?.bannerImage && (
+ <Image
+ src={info?.bannerImage}
+ alt="banner anime"
+ height={1000}
+ width={1000}
+ blurDataURL={info?.bannerImage}
+ className="object-cover bg-image blur-[2px] w-screen absolute top-0 left-0 h-[250px] brightness-[55%] z-0"
+ />
+ )}
+ </div>
+ <div className="w-full lg:max-w-screen-lg xl:max-w-screen-2xl z-30 flex flex-col gap-5 pb-10">
+ <DetailTop
+ info={info}
+ session={session}
+ handleOpen={handleOpen}
+ loading={loading}
+ statuses={statuses}
+ watchUrl={watch}
+ progress={progress}
+ color={color}
+ />
+
+ {!loading ? (
+ chapter?.chapters?.length > 0 ? (
+ <ChapterSelector
+ chaptersData={chapter.chapters}
+ mangaId={chapter.id}
+ data={info}
+ setWatch={setWatch}
+ />
+ ) : (
+ <div className="h-[20vh] lg:w-full flex-center flex-col gap-5">
+ <p className="text-center font-karla font-bold lg:text-lg">
+ Oops!<br></br> It looks like this manga is not available.
+ </p>
+ </div>
+ )
+ ) : (
+ <div className="flex justify-center">
+ <div className="lds-ellipsis">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+ </div>
+ )}
+
+ {info?.characters?.edges?.length > 0 && (
+ <div className="w-full">
+ <Characters info={info?.characters?.edges} />
+ </div>
+ )}
+
+ {info && rec && rec?.length !== 0 && (
+ <div className="w-full">
+ <Content
+ ids="recommendAnime"
+ section="Recommendations"
+ type="manga"
+ data={rec}
+ />
+ </div>
+ )}
+ </div>
+ </main>
+ <Footer />
+ </>
+ );
+}
+
+export async function getServerSideProps(context) {
+ const session = await getServerSession(context.req, context.res, authOptions);
+ const accessToken = session?.user?.token || null;
+
+ const { chapter } = context.query;
+ const [id1, id2] = context.query.id;
+
+ let cached;
+ let aniId, mangadexId;
+ let info, data, color, chapterNotFound;
+
+ if (String(id1).length > 6) {
+ aniId = id2;
+ mangadexId = id1;
+ } else {
+ aniId = id1;
+ mangadexId = id2;
+ }
+
+ if (chapter) {
+ // create random id string
+ chapterNotFound = Math.random().toString(36).substring(7);
+ }
+
+ if (aniId === "na" && mangadexId) {
+ const datas = await getAnifyInfo(mangadexId);
+
+ aniId =
+ datas.mappings.filter((i) => i.providerId === "anilist")[0]?.id || null;
+
+ if (!aniId) {
+ info = datas;
+ data = datas;
+ color = {
+ backgroundColor: `${"#ffff"}`,
+ color: "#000",
+ };
+ // return {
+ // redirect: {
+ // destination: "/404",
+ // permanent: false,
+ // },
+ // };
+ }
+ } else if (aniId && !mangadexId) {
+ // console.log({ aniId });
+ const response = await fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ },
+ body: JSON.stringify({
+ query: `query ($id: Int, $type: MediaType) {
+ Media (id: $id, type: $type) {
+ id
+ title {
+ romaji
+ english
+ native
+ }
+ }
+ }`,
+ variables: {
+ id: parseInt(aniId),
+ type: "MANGA",
+ },
+ }),
+ });
+ const aniListData = await response.json();
+ const info = aniListData?.data?.Media;
+
+ const mangaId = await getMangaId(
+ info?.title?.romaji,
+ info?.title?.english,
+ info?.title?.native
+ );
+ mangadexId = mangaId?.id;
+
+ if (!mangadexId) {
+ return {
+ redirect: {
+ destination: "/404",
+ permanent: false,
+ },
+ };
+ }
+
+ return {
+ redirect: {
+ destination: `/en/manga/${aniId}/${mangadexId}${
+ chapter ? "?chapter=404" : ""
+ }`,
+ permanent: true,
+ },
+ };
+ } else if (!aniId && mangadexId) {
+ const data = await getAnifyInfo(mangadexId);
+
+ aniId =
+ data.mappings.filter((i) => i.providerId === "anilist")[0]?.id || null;
+
+ if (!aniId) {
+ info = data;
+ // return {
+ // redirect: {
+ // destination: "/404",
+ // permanent: false,
+ // },
+ // };
+ }
+
+ return {
+ redirect: {
+ destination: `/en/manga/${aniId ? aniId : "na"}${`/${mangadexId}`}${
+ chapter ? "?chapter=404" : ""
+ }`,
+ permanent: true,
+ },
+ };
+ } else {
+ const getCached = await redis.get(`mangaPage:${mangadexId}`);
+
+ if (getCached) {
+ cached = JSON.parse(getCached);
+ }
+
+ // let chapters;
+
+ if (cached) {
+ data = cached.data;
+ info = cached.info;
+ color = cached.color;
+ } else {
+ data = await getAnifyInfo(mangadexId);
+
+ const aniListId =
+ data.mappings.filter((i) => i.providerId === "anilist")[0]?.id || null;
+
+ const response = await fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ },
+ body: JSON.stringify({
+ query: mediaInfoQuery,
+ variables: {
+ id: parseInt(aniListId),
+ type: "MANGA",
+ },
+ }),
+ });
+ const aniListData = await response.json();
+ if (aniListData?.data?.Media) info = aniListData?.data?.Media;
+
+ const textColor = setTxtColor(info?.color);
+
+ color = {
+ backgroundColor: `${info?.color || "#ffff"}`,
+ color: textColor,
+ };
+
+ await redis.set(
+ `mangaPage:${mangadexId}`,
+ JSON.stringify({ data, info, color }),
+ "ex",
+ 60 * 60 * 24
+ );
+ }
+ }
+
+ return {
+ props: {
+ info: info || null,
+ anifyData: data || null,
+ chapterNotFound: chapterNotFound || null,
+ color: color || null,
+ },
+ };
+}
+
+function getBrightness(hexColor) {
+ if (!hexColor) {
+ return 200;
+ }
+ const rgb = hexColor
+ .match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i)
+ .slice(1)
+ .map((x) => parseInt(x, 16));
+ return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000;
+}
+
+function setTxtColor(hexColor) {
+ const brightness = getBrightness(hexColor);
+ return brightness < 150 ? "#fff" : "#000";
+}
diff --git a/pages/en/manga/[id].js b/pages/en/manga/[id].js
deleted file mode 100644
index 6f25532..0000000
--- a/pages/en/manga/[id].js
+++ /dev/null
@@ -1,146 +0,0 @@
-import ChapterSelector from "@/components/manga/chapters";
-import HamburgerMenu from "@/components/manga/mobile/hamburgerMenu";
-import TopSection from "@/components/manga/info/topSection";
-import Footer from "@/components/shared/footer";
-import Head from "next/head";
-import { useEffect, useState } from "react";
-import { setCookie } from "nookies";
-import { getServerSession } from "next-auth";
-import { authOptions } from "../../api/auth/[...nextauth]";
-import getAnifyInfo from "@/lib/anify/info";
-import { NewNavbar } from "@/components/shared/NavBar";
-
-export default function Manga({ info, userManga }) {
- const [domainUrl, setDomainUrl] = useState("");
- const [firstEp, setFirstEp] = useState();
- const chaptersData = info.chapters.data;
-
- useEffect(() => {
- setDomainUrl(window.location.origin);
- }, []);
-
- return (
- <>
- <Head>
- <title>
- {info
- ? `Manga - ${
- info.title.romaji || info.title.english || info.title.native
- }`
- : "Getting Info..."}
- </title>
- <meta name="twitter:card" content="summary_large_image" />
- <meta
- name="twitter:title"
- content={`Moopa - ${info.title.romaji || info.title.english}`}
- />
- <meta
- name="twitter:description"
- content={`${info.description?.slice(0, 180)}...`}
- />
- <meta
- name="twitter:image"
- content={`${domainUrl}/api/og?title=${
- info.title.romaji || info.title.english
- }&image=${info.bannerImage || info.coverImage}`}
- />
- <meta
- name="title"
- data-title-romaji={info?.title?.romaji}
- data-title-english={info?.title?.english}
- data-title-native={info?.title?.native}
- />
- </Head>
- <div className="min-h-screen w-screen flex flex-col items-center relative">
- <HamburgerMenu />
- <NewNavbar info={info} manga={true} />
- <div className="flex flex-col w-screen items-center gap-5 md:gap-10 py-10 pt-nav">
- <div className="flex-center w-full relative z-30">
- <TopSection info={info} firstEp={firstEp} setCookie={setCookie} />
- <>
- <div className="absolute hidden md:block z-20 bottom-0 h-1/2 w-full bg-secondary" />
- <div className="absolute hidden md:block z-20 top-0 h-1/2 w-full bg-transparent" />
- </>
- </div>
- <div className="w-[90%] xl:w-[70%] min-h-[35vh] z-40">
- {chaptersData.length > 0 ? (
- <ChapterSelector
- chaptersData={chaptersData}
- data={info}
- setFirstEp={setFirstEp}
- setCookie={setCookie}
- userManga={userManga}
- />
- ) : (
- <p>No Chapter Available :(</p>
- )}
- </div>
- </div>
- <Footer />
- </div>
- </>
- );
-}
-
-export async function getServerSideProps(context) {
- const session = await getServerSession(context.req, context.res, authOptions);
- const accessToken = session?.user?.token || null;
-
- const { id } = context.query;
- const key = process.env.API_KEY;
- const data = await getAnifyInfo(id, key);
-
- let userManga = null;
-
- if (session) {
- const response = await fetch("https://graphql.anilist.co/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
- },
- body: JSON.stringify({
- query: `
- query ($id: Int) {
- Media (id: $id) {
- mediaListEntry {
- status
- progress
- progressVolumes
- status
- }
- id
- idMal
- title {
- romaji
- english
- native
- }
- }
- }
- `,
- variables: {
- id: parseInt(id),
- },
- }),
- });
- const data = await response.json();
- const user = data?.data?.Media?.mediaListEntry;
- if (user) {
- userManga = user;
- }
- }
-
- if (!data?.chapters) {
- return {
- notFound: true,
- };
- }
-
- return {
- props: {
- info: data,
- userManga,
- },
- };
-}
diff --git a/pages/en/manga/read/[...params].js b/pages/en/manga/read/[...params].js
index a7769e2..1076601 100644
--- a/pages/en/manga/read/[...params].js
+++ b/pages/en/manga/read/[...params].js
@@ -10,13 +10,27 @@ import { authOptions } from "../../../api/auth/[...nextauth]";
import BottomBar from "@/components/manga/mobile/bottomBar";
import TopBar from "@/components/manga/mobile/topBar";
import Head from "next/head";
-import nookies from "nookies";
import ShortCutModal from "@/components/manga/modals/shortcutModal";
import ChapterModal from "@/components/manga/modals/chapterModal";
-import getAnifyPage from "@/lib/anify/page";
+// import getConsumetPages from "@/lib/consumet/manga/getPage";
+import { mediaInfoQuery } from "@/lib/graphql/query";
+// import { redis } from "@/lib/redis";
+// import getConsumetChapters from "@/lib/consumet/manga/getChapters";
+import { toast } from "sonner";
+import axios from "axios";
+import { redis } from "@/lib/redis";
+import getAnifyInfo from "@/lib/anify/info";
-export default function Read({ data, currentId, sessions }) {
- const [info, setInfo] = useState();
+export default function Read({
+ data,
+ info,
+ chaptersData,
+ currentId,
+ sessions,
+ provider,
+ mangaDexId,
+ number,
+}) {
const [chapter, setChapter] = useState([]);
const [layout, setLayout] = useState(1);
@@ -30,8 +44,8 @@ export default function Read({ data, currentId, sessions }) {
const [paddingX, setPaddingX] = useState(208);
const [scaleImg, setScaleImg] = useState(1);
- const [nextChapterId, setNextChapterId] = useState(null);
- const [prevChapterId, setPrevChapterId] = useState(null);
+ const [nextChapter, setNextChapter] = useState(null);
+ const [prevChapter, setPrevChapter] = useState(null);
const [currentChapter, setCurrentChapter] = useState(null);
const [currentPage, setCurrentPage] = useState(0);
@@ -40,17 +54,22 @@ export default function Read({ data, currentId, sessions }) {
const router = useRouter();
+ // console.log({ info });
+
useEffect(() => {
- hasRun.current = false;
- }, [currentId]);
+ toast.message("This page is still under development", {
+ description: "If you found any bugs, please report it to us!",
+ position: "top-center",
+ duration: 10000,
+ });
+ }, []);
useEffect(() => {
- const get = JSON.parse(localStorage.getItem("manga"));
- const chapters = get.manga;
+ hasRun.current = false;
+ const chapters = chaptersData.find((x) => x.providerId === provider);
const currentChapter = chapters.chapters?.find((x) => x.id === currentId);
setCurrentChapter(currentChapter);
- setInfo(get.data);
setChapter(chapters);
if (Array.isArray(chapters?.chapters)) {
@@ -60,25 +79,36 @@ export default function Read({ data, currentId, sessions }) {
if (currentIndex !== -1) {
const nextChapter = chapters.chapters[currentIndex - 1];
const prevChapter = chapters.chapters[currentIndex + 1];
- setNextChapterId(nextChapter ? nextChapter.id : null);
- setPrevChapterId(prevChapter ? prevChapter.id : null);
+ setNextChapter(nextChapter ? nextChapter : null);
+ setPrevChapter(prevChapter ? prevChapter : null);
}
}
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentId]);
useEffect(() => {
const handleKeyDown = (event) => {
- if (event.key === "ArrowRight" && event.ctrlKey && nextChapterId) {
+ event.preventDefault();
+ if (event.key === "ArrowRight" && event.ctrlKey && nextChapter?.id) {
router.push(
- `/en/manga/read/${chapter.providerId}?id=${
- info.id
- }&chapterId=${encodeURIComponent(nextChapterId)}`
+ `/en/manga/read/${
+ chapter.providerId
+ }?id=${mangaDexId}&chapterId=${encodeURIComponent(nextChapter?.id)}${
+ info?.id?.length > 6 ? "" : `&anilist=${info?.id}`
+ }&num=${nextChapter?.number}`
);
- } else if (event.key === "ArrowLeft" && event.ctrlKey && prevChapterId) {
+ } else if (
+ event.key === "ArrowLeft" &&
+ event.ctrlKey &&
+ prevChapter?.id
+ ) {
router.push(
- `/en/manga/read/${chapter.providerId}?id=${
- info.id
- }&chapterId=${encodeURIComponent(prevChapterId)}`
+ `/en/manga/read/${
+ chapter.providerId
+ }?id=${mangaDexId}&chapterId=${encodeURIComponent(prevChapter?.id)}${
+ info?.id?.length > 6 ? "" : `&anilist=${info?.id}`
+ }&num=${prevChapter?.number}`
);
}
if (event.code === "Slash" && event.ctrlKey) {
@@ -99,7 +129,9 @@ export default function Read({ data, currentId, sessions }) {
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
- }, [nextChapterId, prevChapterId, visible, isKeyOpen, paddingX]);
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [nextChapter?.id, prevChapter?.id, visible, isKeyOpen, paddingX]);
return (
<>
@@ -134,13 +166,15 @@ export default function Read({ data, currentId, sessions }) {
<TopBar info={info} />
<BottomBar
id={info?.id}
- prevChapter={prevChapterId}
- nextChapter={nextChapterId}
+ prevChapter={prevChapter}
+ nextChapter={nextChapter}
currentPage={currentPage}
chapter={chapter}
- page={data}
+ data={data}
setSeekPage={setSeekPage}
setIsOpen={setIsChapOpen}
+ number={number}
+ mangadexId={mangaDexId}
/>
</>
)}
@@ -149,13 +183,17 @@ export default function Read({ data, currentId, sessions }) {
data={chapter}
page={data}
info={info}
+ number={number}
+ mediaId={mangaDexId}
currentId={currentId}
setSeekPage={setSeekPage}
+ providerId={provider}
/>
)}
{layout === 1 && (
<FirstPanel
aniId={info?.id}
+ providerId={provider}
data={data}
hasRun={hasRun}
currentId={currentId}
@@ -164,19 +202,22 @@ export default function Read({ data, currentId, sessions }) {
visible={visible}
setVisible={setVisible}
chapter={chapter}
- nextChapter={nextChapterId}
- prevChapter={prevChapterId}
+ nextChapter={nextChapter}
+ prevChapter={prevChapter}
paddingX={paddingX}
session={sessions}
mobileVisible={mobileVisible}
setMobileVisible={setMobileVisible}
setCurrentPage={setCurrentPage}
+ mangadexId={mangaDexId}
+ number={number}
/>
)}
{layout === 2 && (
<SecondPanel
aniId={info?.id}
data={data}
+ chapterData={chapter}
hasRun={hasRun}
currentChapter={currentChapter}
currentId={currentId}
@@ -185,12 +226,14 @@ export default function Read({ data, currentId, sessions }) {
visible={visible}
setVisible={setVisible}
session={sessions}
+ providerId={provider}
/>
)}
{layout === 3 && (
<ThirdPanel
aniId={info?.id}
data={data}
+ chapterData={chapter}
hasRun={hasRun}
currentId={currentId}
currentChapter={currentChapter}
@@ -202,6 +245,7 @@ export default function Read({ data, currentId, sessions }) {
scaleImg={scaleImg}
setMobileVisible={setMobileVisible}
mobileVisible={mobileVisible}
+ providerId={provider}
/>
)}
{visible && (
@@ -224,42 +268,130 @@ export default function Read({ data, currentId, sessions }) {
)}
</div>
</>
+ // <p></p>
);
}
-export async function getServerSideProps(context) {
- const cookies = nookies.get(context);
+async function fetchAnifyPages(id, number, provider, readId, key) {
+ try {
+ let cached;
+ cached = await redis.get(`pages:${readId}`);
+
+ if (cached) {
+ return JSON.parse(cached);
+ }
+
+ const url = `https://api.anify.tv/pages?id=${id}&chapterNumber=${number}&providerId=${provider}&readId=${encodeURIComponent(
+ readId
+ )}`;
+
+ const { data } = await axios.get(url);
+
+ if (!data) {
+ return null;
+ }
+
+ await redis.set(
+ `pages:${readId}`,
+ JSON.stringify(data),
+ "EX",
+ 60 * 60 * 24 * 7
+ );
+
+ return data;
+ } catch (error) {
+ return { error: "Error fetching data" };
+ }
+}
+
+export async function getServerSideProps(context) {
const key = process.env.API_KEY;
const query = context.query;
const providerId = query.params[0];
const chapterId = query.chapterId;
const mediaId = query.id;
+ const number = query.num;
+ const anilistId = query.anilist;
+
+ const session = await getServerSession(context.req, context.res, authOptions);
+ const accessToken = session?.user?.token || null;
+
+ // const data = await getConsumetPages(mediaId, providerId, chapterId, key);
+ // const chapters = await getConsumetChapters(mediaId, redis);
+
+ const dataManga = await fetchAnifyPages(
+ mediaId,
+ number,
+ providerId,
+ chapterId,
+ mediaId,
+ key
+ );
+
+ let info;
- if (!cookies.manga || cookies.manga !== mediaId) {
+ if (anilistId) {
+ const response = await fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ },
+ body: JSON.stringify({
+ query: mediaInfoQuery,
+ variables: {
+ id: parseInt(anilistId),
+ type: "MANGA",
+ },
+ }),
+ });
+ const json = await response.json();
+ info = json?.data?.Media;
+ } else {
+ const datas = await getAnifyInfo(mediaId);
+ if (datas) {
+ info = datas;
+ }
+ }
+
+ const chapters = await (
+ await fetch("https://api.anify.tv/chapters/" + mediaId + "?apikey=" + key)
+ ).json();
+
+ if ((dataManga && dataManga?.error) || dataManga?.length === 0) {
return {
redirect: {
- destination: `/en/manga/${mediaId}`,
+ destination: `/en/manga/${anilistId}?chapter=404`,
},
};
}
- const session = await getServerSession(context.req, context.res, authOptions);
-
- const data = await getAnifyPage(mediaId, providerId, chapterId, key);
+ /*
+ const { data } = await axios.get(
+ `https://beta.moopa.live/api/v2/info/${romaji}${
+ english ? `/${english}` : ""
+ }${native ? `/${native}` : ""}?id=${anilistId}`
+ );
if (data.error) {
return {
notFound: true,
};
}
+ */
return {
props: {
- data: data,
+ data: dataManga,
+ mangaDexId: mediaId,
+ info: info,
+ number: number,
+ chaptersData: chapters,
currentId: chapterId,
sessions: session,
+ provider: providerId,
},
};
}
diff --git a/pages/en/profile/[user].js b/pages/en/profile/[user].js
index b931597..7ef5de3 100644
--- a/pages/en/profile/[user].js
+++ b/pages/en/profile/[user].js
@@ -5,8 +5,8 @@ import Link from "next/link";
import Head from "next/head";
import { useEffect, useState } from "react";
import { getUser } from "@/prisma/user";
-import { toast } from "react-toastify";
import { NewNavbar } from "@/components/shared/NavBar";
+import { toast } from "sonner";
export default function MyList({ media, sessions, user, time, userSettings }) {
const [listFilter, setListFilter] = useState("all");
diff --git a/pages/en/search/[...param].js b/pages/en/search/[...param].js
index 603cd17..2cb609f 100644
--- a/pages/en/search/[...param].js
+++ b/pages/en/search/[...param].js
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
-import { AnimatePresence, motion as m } from "framer-motion";
+import { motion as m } from "framer-motion";
import Skeleton from "react-loading-skeleton";
import { useRouter } from "next/router";
import Link from "next/link";
@@ -25,6 +25,8 @@ import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/20/solid";
import useDebounce from "@/lib/hooks/useDebounce";
import { NewNavbar } from "@/components/shared/NavBar";
import MobileNav from "@/components/shared/MobileNav";
+import SearchByImage from "@/components/search/searchByImage";
+import { PlayIcon } from "@heroicons/react/24/outline";
export async function getServerSideProps(context) {
const { param } = context.query;
@@ -91,9 +93,10 @@ export default function Card({
}) {
const inputRef = useRef(null);
const router = useRouter();
- // const { data: session } = useSession();
const [data, setData] = useState();
+ const [imageSearch, setImageSearch] = useState();
+
const [loading, setLoading] = useState(true);
const [search, setQuery] = useState(query);
@@ -125,16 +128,18 @@ export default function Card({
});
if (data?.media?.length === 0) {
setNextPage(false);
+ setLoading(false);
} else if (data !== null && page > 1) {
setData((prevData) => {
return [...(prevData ?? []), ...data?.media];
});
setNextPage(data?.pageInfo.hasNextPage);
+ setLoading(false);
} else {
setData(data?.media);
+ setNextPage(data?.pageInfo.hasNextPage);
+ setLoading(false);
}
- setNextPage(data?.pageInfo.hasNextPage);
- setLoading(false);
}
useEffect(() => {
@@ -142,6 +147,7 @@ export default function Card({
setPage(1);
setNextPage(true);
advance();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, [
debounceSearch,
type?.value,
@@ -153,11 +159,17 @@ export default function Card({
]);
useEffect(() => {
+ if (imageSearch) return;
advance();
- }, [page]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [page, imageSearch]);
useEffect(() => {
function handleScroll() {
+ if (imageSearch) {
+ window.removeEventListener("scroll", handleScroll);
+ return;
+ }
if (page > 10 || !nextPage) {
window.removeEventListener("scroll", handleScroll);
return;
@@ -174,7 +186,7 @@ export default function Card({
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
- }, [page, nextPage]);
+ }, [page, nextPage, imageSearch]);
const handleKeyDown = async (event) => {
if (event.key === "Enter") {
@@ -189,6 +201,7 @@ export default function Card({
};
function trash() {
+ setImageSearch();
setQuery();
setGenre();
setFormat();
@@ -202,6 +215,18 @@ export default function Card({
setIsVisible(!isVisible);
}
+ const handleVideoHover = (hovered, id) => {
+ const updatedImageSearch = imageSearch?.map((item) => {
+ if (item.filename === id) {
+ return { ...item, hovered };
+ }
+ return item;
+ });
+ setImageSearch(updatedImageSearch);
+ };
+
+ // console.log({ loading, data });
+
return (
<>
<Head>
@@ -290,6 +315,7 @@ export default function Card({
>
<Cog6ToothIcon className="w-5 h-5" />
</div>
+ <SearchByImage setMedia={setData} setData={setImageSearch} />
<div
className="py-2 px-2 bg-secondary rounded flex justify-center items-center cursor-pointer hover:bg-opacity-75 transition-all duration-100 group"
onClick={trash}
@@ -343,91 +369,200 @@ export default function Card({
)}
{/* <div> */}
<div className="flex flex-col gap-14 items-center z-30">
- <AnimatePresence>
- <div
- key="card-keys"
- className="grid pt-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6 justify-items-center grid-cols-2 xxs:grid-cols-3 w-screen px-2 xl:w-auto xl:gap-10 gap-2 xl:gap-y-24 gap-y-12 overflow-hidden"
- >
- {loading
- ? ""
- : !data?.length && (
- <div className="w-screen text-[#ff7f57] xl:col-start-3 col-start-2 items-center flex justify-center text-center font-bold font-karla xl:text-2xl">
- Oops!<br></br> Nothing's Found...
+ <div
+ key="card-keys"
+ className={`${
+ imageSearch ? "hidden" : ""
+ } grid pt-3 px-5 xl:px-0 xxs:grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6 justify-items-center grid-cols-2 w-screen xl:w-auto xl:gap-7 gap-5 gap-y-10`}
+ >
+ {loading
+ ? ""
+ : !data && (
+ <div className="w-full text-[#ff7f57] col-span-6 items-center flex justify-center text-center font-bold font-karla xl:text-2xl">
+ Oops!<br></br> Nothing's Found...
+ </div>
+ )}
+
+ {data &&
+ data?.length > 0 &&
+ !imageSearch &&
+ data?.map((anime, index) => {
+ const anilistId = anime?.mappings?.find(
+ (x) => x.providerId === "anilist"
+ )?.id;
+ return (
+ <m.div
+ initial={{ scale: 0.98 }}
+ animate={{ scale: 1, transition: { duration: 0.35 } }}
+ className="w-full"
+ key={index}
+ >
+ <Link
+ href={
+ anime.format === "MANGA" || anime.format === "NOVEL"
+ ? `/en/manga/${
+ anilistId ? anilistId : ""
+ }${`/${anime.id}`}`
+ : `/en/anime/${anime.id}`
+ }
+ title={anime.title.userPreferred}
+ className="block relative overflow-hidden bg-secondary hover:scale-[1.03] scale-100 transition-all cursor-pointer duration-200 ease-out rounded"
+ style={{
+ paddingTop: "145%", // 2:3 aspect ratio (3/2 * 100%)
+ }}
+ >
+ <Image
+ className="object-cover"
+ src={anime.coverImage.extraLarge}
+ alt={anime.title.userPreferred}
+ sizes="(min-width: 808px) 50vw, 100vw"
+ quality={100}
+ fill
+ />
+ </Link>
+ <Link
+ href={
+ anime.format === "MANGA" || anime.format === "NOVEL"
+ ? `/en/manga/${
+ anilistId ? anilistId : ""
+ }${`/${anime.id}`}`
+ : `/en/anime/${anime.id}`
+ }
+ title={anime.title.userPreferred}
+ >
+ <h1 className="font-outfit font-bold xl:text-base text-[15px] pt-4 line-clamp-2">
+ {anime.status === "RELEASING" ? (
+ <span className="dots bg-green-500" />
+ ) : anime.status === "NOT_YET_RELEASED" ? (
+ <span className="dots bg-red-500" />
+ ) : null}
+ {anime.title.userPreferred}
+ </h1>
+ </Link>
+ <h2 className="font-outfit xl:text-[15px] text-[11px] font-light pt-2 text-[#8B8B8B]">
+ {anime.format || <p>-</p>} &#183;{" "}
+ {anime.status || <p>-</p>} &#183;{" "}
+ {anime.episodes
+ ? `${anime.episodes || "N/A"} Episodes`
+ : `${anime.chapters || "N/A"} Chapters`}
+ </h2>
+ </m.div>
+ );
+ })}
+
+ {loading && (
+ <>
+ {[1, 2, 4, 5, 6, 7, 8].map((item) => (
+ <div className="w-full" key={item}>
+ <div className="w-full">
+ <Skeleton
+ className="w-full rounded"
+ style={{
+ paddingTop: "140%", // 2:3 aspect ratio (3/2 * 100%)
+ width: "(min-width: 808px) 50vw, 100vw",
+ lineHeight: 1,
+ }}
+ />
</div>
- )}
- {data &&
- data?.map((anime, index) => {
- return (
- <m.div
- initial={{ scale: 0.9 }}
- animate={{ scale: 1, transition: { duration: 0.35 } }}
- className="w-[146px] xxs:w-[115px] xs:w-[135px] xl:w-[185px]"
- key={index}
+ <div>
+ <h1 className="font-outfit w-[320px] font-bold xl:text-base text-[15px] pt-4 line-clamp-2">
+ <Skeleton width={120} height={26} />
+ </h1>
+ </div>
+ </div>
+ ))}
+ </>
+ )}
+ </div>
+
+ {imageSearch && (
+ <div className="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 gap-3 md:gap-7 px-5 lg:px-0">
+ {imageSearch.map((a, index) => {
+ return (
+ <m.div
+ key={index}
+ initial={{ scale: 0.9 }}
+ animate={{ scale: 1, transition: { duration: 0.35 } }}
+ className="flex flex-col gap-2 shrink-0 cursor-pointer relative group/item"
+ >
+ <Link
+ className="relative aspect-video rounded-md overflow-hidden group"
+ href={`/en/anime/${a.anilist.id}`}
+ onMouseEnter={() => {
+ handleVideoHover(true, a.filename);
+ }}
+ onMouseLeave={() => handleVideoHover(false, a.filename)}
>
- <Link
- href={
- anime.format === "MANGA" || anime.format === "NOVEL"
- ? `/en/manga/${anime.id}`
- : `/en/anime/${anime.id}`
- }
- title={anime.title.userPreferred}
- >
+ <div className="w-full h-full bg-gradient-to-t from-black/70 from-20% to-transparent group-hover:to-black/40 transition-all duration-300 ease-out absolute z-30" />
+ <div className="absolute bottom-3 left-0 mx-2 text-white flex gap-2 items-center w-[80%] z-30">
+ <PlayIcon className="w-5 h-5 shrink-0" />
+ <h1
+ className="font-semibold font-karla line-clamp-1"
+ title={a?.anilist.title.romaji}
+ >
+ {`Episode ${a.episode}`}
+ </h1>
+ </div>
+
+ {a?.image && (
<Image
- className="object-cover bg-[#3B3C41] w-[146px] h-[208px] xxs:w-[115px] xxs:h-[163px] xs:w-[135px] xs:h-[192px] xl:w-[185px] xl:h-[265px] hover:scale-105 scale-100 transition-all cursor-pointer duration-200 ease-out rounded-[10px]"
- src={anime.coverImage.extraLarge}
- alt={anime.title.userPreferred}
- width={500}
- height={500}
+ src={a?.image}
+ width={200}
+ height={200}
+ alt="Episode Thumbnail"
+ className={`w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10 ${
+ !a.hovered ? "visible" : "hidden"
+ }`}
/>
- </Link>
- <Link
- href={`/en/anime/${anime.id}`}
- title={anime.title.userPreferred}
- >
- <h1 className="font-outfit font-bold xl:text-base text-[15px] pt-4 line-clamp-2">
- {anime.status === "RELEASING" ? (
- <span className="dots bg-green-500" />
- ) : anime.status === "NOT_YET_RELEASED" ? (
- <span className="dots bg-red-500" />
- ) : null}
- {anime.title.userPreferred}
- </h1>
- </Link>
- <h2 className="font-outfit xl:text-[15px] text-[11px] font-light pt-2 text-[#8B8B8B]">
- {anime.format || <p>-</p>} &#183;{" "}
- {anime.status || <p>-</p>} &#183;{" "}
- {anime.episodes
- ? `${anime.episodes || "N/A"} Episodes`
- : `${anime.chapters || "N/A"} Chapters`}
- </h2>
- </m.div>
- );
- })}
-
- {loading && (
- <>
- {[1, 2, 4, 5, 6, 7, 8].map((item) => (
- <div
- key={item}
- className="flex flex-col w-[135px] xl:w-[185px] gap-5"
- style={{ scale: 0.98 }}
+ )}
+ {a?.video && (
+ <video
+ src={a.video}
+ className={`w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10 ${
+ a.hovered ? "visible" : "hidden"
+ }`}
+ autoPlay
+ muted
+ loop
+ playsInline
+ />
+ )}
+ </Link>
+
+ <Link
+ className="flex flex-col font-karla w-full"
+ href={`/en/anime/${a.anilist.id}`}
>
- <Skeleton className="h-[192px] w-[135px] xl:h-[265px] xl:w-[185px]" />
- <Skeleton width={110} height={30} />
- </div>
- ))}
- </>
- )}
+ {/* <h1 className="font-semibold">{a.title}</h1> */}
+ <p className="flex items-center gap-1 text-sm text-gray-400 w-[320px]">
+ <span
+ className="text-white max-w-[120px] md:max-w-[200px] lg:max-w-[220px]"
+ style={{
+ display: "inline-block",
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ }}
+ title={a?.anilist.title.romaji}
+ >
+ {a?.anilist.title.romaji}
+ </span>{" "}
+ | Episode {a.episode}
+ </p>
+ </Link>
+ </m.div>
+ );
+ })}
</div>
- {!loading && page > 10 && nextPage && (
- <button
- onClick={() => setPage((p) => p + 1)}
- className="bg-secondary xl:w-[30%] w-[80%] h-10 rounded-md"
- >
- Load More
- </button>
- )}
- </AnimatePresence>
+ )}
+ {!loading && page > 10 && nextPage && (
+ <button
+ onClick={() => setPage((p) => p + 1)}
+ className="bg-secondary xl:w-[30%] w-[80%] h-10 rounded-md"
+ >
+ Load More
+ </button>
+ )}
</div>
{/* </div> */}
</div>