diff options
| author | Factiven <[email protected]> | 2023-04-11 23:23:29 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-04-11 23:23:29 +0700 |
| commit | 1fcdd9f7d859b925bf92265f441655d5522e351c (patch) | |
| tree | 86391522f6fcc70d105f7e796a9f91d132ee4a29 /pages/anime | |
| parent | Initial commit (diff) | |
| download | moopa-1fcdd9f7d859b925bf92265f441655d5522e351c.tar.xz moopa-1fcdd9f7d859b925bf92265f441655d5522e351c.zip | |
initial commit
Diffstat (limited to 'pages/anime')
| -rw-r--r-- | pages/anime/[...id].js | 623 | ||||
| -rw-r--r-- | pages/anime/watch/[...info].js | 323 |
2 files changed, 946 insertions, 0 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js new file mode 100644 index 0000000..d42a394 --- /dev/null +++ b/pages/anime/[...id].js @@ -0,0 +1,623 @@ +import React, { useEffect, useState } from "react"; +import { AnimatePresence, motion as m } from "framer-motion"; +import { META } from "@consumet/extensions"; + +import Link from "next/link"; +import Layout from "../../components/layout"; +import Head from "next/head"; + +import { closestMatch } from "closest-match"; +import Content from "../../components/hero/content"; +import Image from "next/image"; + +export default function Himitsu({ + info, + slicedDesc, + color, + episodeList, + episode1, + judul, + subIndo, + epIndo, +}) { + const [isLoading, setIsloading] = useState(false); + const [showText, setShowtext] = useState(false); + const [title, setTitle] = useState(info.title.english || info.title.romaji); + const [load, setLoad] = useState(true); + const [Lang, setLang] = useState(true); + const [showAll, setShowAll] = useState(false); + + const [lastPlayed, setLastPlayed] = useState(null); + const episode = episodeList; + const epi1 = episode1; + + const maxItems = 3; + + function handleEnLang() { + setLang(true); + } + + function handleIdLang() { + setLang(false); + } + + // const { ref } = useParallax({ speed: 10 }); + + useEffect(() => { + const playedStr = JSON.parse(localStorage.getItem("lastPlayed")); + setLastPlayed( + playedStr?.filter((item) => item.title === info.title.romaji)[0]?.data + ); + function getBrightness(color) { + const rgb = color.match(/\d+/g); + return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; + } + + // set the text color based on the background color + function setTextColor(element) { + const backgroundColor = getComputedStyle(element).backgroundColor; + const brightness = getBrightness(backgroundColor); + if (brightness < 128) { + element.style.color = "#fff"; // white + } else { + element.style.color = "#000"; // black + } + } + + const elements = document.querySelectorAll(".dynamic-text"); + elements.forEach((element) => { + setTextColor(element); + }); + }, [color]); + + const handleStore = (props) => { + let existingData = JSON.parse(localStorage.getItem("recentWatch")); + if (!Array.isArray(existingData)) { + existingData = []; + } + const index = existingData.findIndex((item) => item.title === props.title); + if (index !== -1) { + existingData.splice(index, 1); + } + const updatedData = [props, ...existingData]; + localStorage.setItem("recentWatch", JSON.stringify(updatedData)); + }; + + if (!info) { + return; + } + + let episodeIndo = null; + if (epIndo < 17) { + episodeIndo = episode.slice(0, epIndo); + } else { + episodeIndo = episode; + } + + // console.log({ NEXT: subIndo }); + + // console.log(episodeIndo); + + // console.log(lastPlayed); + + function handleLoad() { + setLoad(false); + } + return ( + <> + <Head> + <title>{info.title?.english || info.title.romaji}</title> + <meta name="detail" content="Detail about the Anime" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" href="/c.svg" /> + </Head> + + <Layout navTop="text-white bg-[#121212] md:pt-0 md:px-0 bg-slate bg-opacity-40"> + <div className="text static flex w-screen flex-col justify-center pt-nav pb-10"> + <div className="pointer-events-none absolute top-0 left-0"> + <div className="brightness-90 bg-gradient-to-t from-[#121212] to-transparent"> + <img + // ref={ref} + src={info.cover || info.image} + className="h-[300px] w-screen object-cover brightness-75 mix-blend-darken" + /> + <div className="z-10 h-full drop-shadow-2xl bg-[#121212]" /> + </div> + </div> + {isLoading ? ( + <p>Loading cuy sabar...</p> + ) : info ? ( + <div className="flex flex-col items-center gap-10"> + <div className="flex w-screen flex-col gap-10 md:w-[70%]"> + <div className="z-40 flex flex-col gap-10 px-5 pt-[8rem] md:flex-row lg:mt-[5rem] lg:px-0"> + <div className="flex gap-10 md:h-[250px] md:w-52"> + <div className="flex h-[200px] w-52 bg-[#dadada50] md:h-[250px] md:w-full"> + {info.image && ( + <> + <div + style={{ + backgroundImage: `url(${info.image})`, + height: "100%", + width: "100%", + backgroundSize: "cover", + backgroundPosition: "center", + }} + // src={info.image} + className="h-[200px] w-[200px] md:h-[250px] bg-white shadow-md" + /> + </> + )} + </div> + + {/* MOBILE */} + <div className="flex w-full flex-col gap-5 lg:hidden "> + <h1 className="shrink-0 text-2xl font-semibold"> + {judul} + </h1> + <div className="flex w-[90%] flex-col gap-1"> + <div className="flex gap-2"> + <h1>Rate:</h1> + <p className="font-bold">{info.rating}%</p> + </div> + + <div className="flex w-[200px] gap-2"> + <h1>Format:</h1> + <p>{info.type}</p> + </div> + + <div className="flex gap-2"> + <h1>Status:</h1> + <p>{info.status}</p> + </div> + </div> + <div className="flex"> + {epi1 && epi1[0] ? ( + <Link + href={`/anime/watch/${epi1[0].id}/${info.id}`} + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + > + <h1 className="flex cursor-pointer items-center gap-2 rounded-[20px] bg-[#ff9537] px-4 py-2 font-bold text-[#ffffff]"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="13" + height="12" + fill="none" + viewBox="0 0 250 289" + > + <path + fill="#fff" + d="M249.734 144.5l-249 143.761V.741l249 143.759z" + ></path> + </svg>{" "} + WATCH + </h1> + </Link> + ) : ( + <h1 className="pointer-events-none flex items-center gap-2 rounded-[20px] bg-[#ff94378f] px-4 py-2 font-bold text-[#ffffffa5]"> + <svg + xmlns="http://www.w3.org/2000/svg" + width="13" + height="12" + className="fill-[#ffffff8d]" + viewBox="0 0 250 289" + > + <path d="M249.734 144.5l-249 143.761V.741l249 143.759z"></path> + </svg>{" "} + WATCH + </h1> + )} + </div> + </div> + </div> + + {/* PC */} + <div className="w-full flex-col gap-5 md:flex"> + <div className="hidden flex-col gap-5 lg:flex"> + <h1 className="text-4xl font-bold"> + {info.title?.english || + info.title.romaji || + info.title.native} + </h1> + <div className="flex gap-6"> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {episode && episode.length} Episodes + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.releaseDate} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.rating}% + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.type} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + {info.status} + </div> + <div + className={`dynamic-text rounded-md px-2 font-karla font-bold`} + style={color} + > + Sub | {subIndo === null ? "EN" : "EN/ID"} + </div> + </div> + </div> + <div + className={`hidden h-[140px] transition-all duration-300 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`} + > + <p + dangerouslySetInnerHTML={{ __html: info.description }} + className="mr-5" + /> + </div> + <div className="lg:hidden"> + <div + dangerouslySetInnerHTML={{ + __html: showText ? info.description : slicedDesc, + }} + ></div> + <button + onClick={() => setShowtext(!showText)} + className="font-rama font-bold text-white" + > + {showText ? " Show Less" : " Show More"} + </button> + </div> + </div> + </div> + + <div className=""> + <div className="flex gap-5 items-center"> + <div className="p-3 lg:p-0 text-3xl font-bold"> + Relations + </div> + {info.relations.length > maxItems && ( + <div + className="cursor-pointer" + onClick={() => setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} + </div> + )} + </div> + <div + className={`w-screen lg:w-full grid lg:grid-cols-3 justify-items-center gap-7 lg:pt-7 px-3 lg:px-4 pt-10 rounded-xl`} + > + {info.relations && + info.relations + .slice(0, showAll ? info.relations.length : maxItems) + .map((relation, index) => { + return ( + <Link + key={relation.id} + href={ + relation.type === "TV" || + relation.type === "OVA" || + relation.type === "MOVIE" || + relation.type === "SPECIAL" || + relation.type === "ONA" + ? `/anime/${relation.id}` + : `/manga/detail/id?aniId=${ + relation.id + }&aniTitle=${encodeURIComponent( + info.title?.english || + info.title.romaji || + info.title.native + )}` + } + className={`hover:scale-[1.02] scale-100 transition-transform duration-200 ease-out w-full ${ + relation.type === "MUSIC" + ? "pointer-events-none" + : "" + }`} + > + <div + key={relation.id} + initial={{ opacity: 0, y: 50 }} + animate={{ opacity: 1, y: 0 }} + exit={{ + opacity: 0, + y: -50, + transition: { duration: 0.5 }, + }} + transition={{ + duration: 0.8, + delay: index * 0.1, + }} + className="w-full shrink h-[126px] bg-secondary flex rounded-md" + > + <div className="min-w-[20%] bg-image rounded-l-md shrink-0"> + <img + src={relation.image} + alt={relation.id} + className="object-cover h-full w-full shrink-0 rounded-l-md" + /> + </div> + <div className="min-w-[80%] h-full grid px-3 items-center"> + <div className="text-action font-outfit font-bold"> + {relation.relationType} + </div> + <div className="font-outfit font-thin italic line-clamp-2"> + {relation.title.romaji} + </div> + <div className={``}>{relation.type}</div> + </div> + </div> + </Link> + ); + })} + </div> + </div> + + <div className="z-20 flex flex-col gap-10 p-3 lg:p-0"> + <div className="flex items-center gap-10"> + <h1 className="text-3xl font-bold">Episodes</h1> + <div className="flex items-center rounded-md"> + <button + onClick={handleEnLang} + className={ + Lang + ? `w-16 p-2 rounded-l-md bg-[#212121]` + : `w-16 p-2 rounded-l-md bg-[#171717] text-[#404040]` + } + > + EN + </button> + <div className="w-[1px] bg-white h-4" /> + <button + onClick={handleIdLang} + className={ + subIndo === null + ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040] pointer-events-none` + : Lang + ? `w-16 p-2 rounded-r-md bg-[#171717] text-[#404040]` + : `w-16 p-2 rounded-r-md bg-[#212121]` + } + > + ID + </button> + </div> + </div> + <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> + {episode && Lang ? ( + episode.map((episode, index) => { + const item = lastPlayed?.find( + (item) => item.id === episode.id + ); + // console.log(item); + return ( + <div key={index} className="flex flex-col gap-3"> + <Link + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + href={`/anime/watch/${episode.id}/${info.id}/${ + item ? `${item.time}` : "" + }`} + className={`text-start text-xl ${ + item ? "text-[#414141]" : "text-white" + }`} + > + <p>Episode {episode.number}</p> + {episode.title && ( + <p + className={`text-[14px] ${ + item ? "text-[#414141]" : "text-[#b1b1b1]" + } italic`} + > + "{episode.title}" + </p> + )} + </Link> + <div className="h-[1px] bg-white" /> + </div> + ); + }) + ) : subIndo === null ? ( + <p>No Episodes Available</p> + ) : ( + <> + <div className="flex h-[640px] flex-col gap-5 overflow-y-hidden scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full hover:overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> + {episodeIndo.map((episode, index) => { + return ( + <div key={index} className="flex flex-col gap-3"> + <Link + onClick={() => + handleStore({ + title: + info.title?.english || + info.title.romaji || + info.title.native, + description: info.description, + image: info.image, + id: info.id, + }) + } + href={`/anime/watch?title=${encodeURIComponent( + info.title?.romaji || info.title?.english + )}&id=${subIndo}&idInt=${info.id}&epi=${ + episode.number + }&epiTitle=${encodeURIComponent( + episode.title + )}&te=${epIndo}&sub=id`} + className="text-start text-xl" + > + <p>Episode {episode.number}</p> + <p className="text-[14px] text-[#b1b1b1] italic"> + "{episode.title}" (Sub Indonesia) + </p> + </Link> + <div className="h-[1px] bg-white" /> + </div> + ); + })} + </div> + </> + )} + </div> + </div> + </div> + <div className="w-screen md:w-[80%]"> + <Content + ids="recommendAnime" + section="Recommendations" + data={info.recommendations} + /> + </div> + </div> + ) : ( + <div className="flex h-screen flex-col items-center justify-center gap-10 pb-52 "> + <h1 className="scale-150 font-roboto text-6xl text-red-400"> + 404 + </h1> + <p className="text-4xl font-semibold">{`> Woops.. I think we don't have that Anime :(`}</p> + <Link className="pt-10 text-2xl" href="/search"> + Return to search + </Link> + </div> + )} + </div> + </Layout> + </> + ); +} + +export const getServerSideProps = async (context) => { + context.res.setHeader( + "Cache-Control", + "public, s-maxage=10, stale-while-revalidate=59" + ); + const { id } = context.query; + if (!id) { + return { + notFound: true, + }; + } + + const provider = new META.Anilist(); + + const [info, episodes] = await Promise.all([ + fetch(`https://api.moopa.my.id/meta/anilist/info/${id[0]}`).then((res) => + res.json() + ), + provider.fetchEpisodesListById(id[0]), + ]); + + let episodeList = episodes; + if (episodes.length === 0) { + const res = await fetch( + `https://api.moopa.my.id/anime/gogoanime/${ + info.title.romaji || info.title.english + }` + ); + const data = await res.json(); + const match = closestMatch( + info.title.romaji, + data.results.map((item) => item.title) + ); + const anime = data.results.filter((item) => item.title === match); + if (anime.length !== 0) { + const infos = await fetch( + `https://api.moopa.my.id/anime/gogoanime/info/${anime[0].id}` + ).then((res) => res.json()); + episodeList = infos.episodes; + } + } + + const ress = await fetch( + `https://ani-api-eight.vercel.app/kuramanime/search?query=${ + info.title.romaji || info.title?.english + }` + ); + + const yes = await ress.json(); + + // Clannad Fixer + function convertToClannad(text) { + const regex = /(?<!\w)CLANNAD(?!\w)/g; + return text.replace(regex, "Clannad"); + } + + const fixedTitle = convertToClannad(info.title.romaji); + + let epis = null; + let slug = null; + + if (!yes.error) { + // let anime = yes.list.filter((item) => item.title.includes(fixedTitle)); + let list = yes.list.map((item) => item.title); + const match = closestMatch(fixedTitle, list); + + const anime = yes.list.filter((item) => item.title === match); + + slug = anime[0]?.slug; + const inf = await fetch( + `https://ani-api-eight.vercel.app/kuramanime/anime/${slug}` + ); + + const dataInf = await inf.json(); + epis = dataInf.episode; + } + + const desc = info.description.slice(0, 150) + "..."; + const color = { backgroundColor: `${info.color}` }; + const epi1 = episodes.filter((epi) => epi.number === 1); + const title = info.title?.userPreferred || "No Title"; + + const MAX = 20; + + const oriJ = info.title?.english || info.title.romaji || info.title.native; + const judul = oriJ.length > MAX ? `${oriJ.substring(0, MAX)}...` : oriJ; + + return { + props: { + info: { + ...info, + title: { + ...info.title, + userPreferred: title, + }, + }, + slicedDesc: desc, + color, + episodeList, + episode1: epi1, + judul, + subIndo: slug, + epIndo: epis, + }, + }; +}; diff --git a/pages/anime/watch/[...info].js b/pages/anime/watch/[...info].js new file mode 100644 index 0000000..73e04f5 --- /dev/null +++ b/pages/anime/watch/[...info].js @@ -0,0 +1,323 @@ +import Layout from "../../../components/layout"; +// import { data } from "../../../lib/testData"; +// import { aniData } from "../../../lib/infoData"; +import Image from "next/image"; +import VideoPlayer from "../../../components/videoPlayer"; +import Link from "next/link"; +import { closestMatch } from "closest-match"; +import Head from "next/head"; +import { useEffect, useState } from "react"; +import Modal from "../../../components/modal"; + +export default function Info({ info }) { + const title = info.aniData.title.romaji || info.aniData.title.english; + const data = info.aniData; + const fallback = info.epiFallback; + + const [open, setOpen] = useState(false); + + const playingTitle = data.episodes + .filter((item) => item.id == info.id) + .map((item) => item.title); + + return ( + <> + <Head> + <title> + {fallback ? data.title.romaji || data.title.english : playingTitle} + </title> + </Head> + <Modal open={open} onClose={() => setOpen(false)}> + <div className="bg-[#202020] rounded-lg w-[268px] text-center"> + <div className="p-5 grid gap-2 justify-center place-items-center"> + <h1 className="text-md font-extrabold font-karla"> + Save this Anime to Your List + </h1> + <h1 className="text-sm font-karla font-extralight w-[205px]"> + Are you sure you want to save this anime to your list? + </h1> + <div className="flex gap-12 items-center pt-3 justify-between"> + <button className="p-2 font-karla font-extrabold text-sm bg-[#93FF3E] w-[84px] rounded-[10px] text-black shadow"> + YES + </button> + <button + onClick={() => setOpen(false)} + className="p-2 font-karla font-extrabold text-sm bg-white w-[84px] rounded-[10px] text-black shadow-lg" + > + NO + </button> + </div> + </div> + </div> + </Modal> + <div className="min-h-screen flex flex-col lg:gap-0 gap-5 lg:flex-row lg:py-10 lg:px-10 justify-start w-screen"> + <div className="w-screen lg:w-[67%]"> + <div className="h-auto aspect-video z-20"> + <VideoPlayer + key={info.id} + data={info.epiData} + seek={info.seek} + titles={title} + id={info.id} + /> + </div> + <div> + <div className=""> + {data.episodes.length > 0 ? ( + data.episodes + .filter((items) => items.id == info.id) + .map((item) => ( + <div key={item.id} className="p-3 grid gap-2"> + <div className="text-xl font-outfit font-semibold line-clamp-2"> + <Link + href={`/anime/${data.id}`} + className="inline hover:underline" + > + {item.title} + </Link> + </div> + <h4 className="text-sm font-karla font-light"> + Episode {item.number} + </h4> + </div> + )) + ) : ( + <> + {fallback + .filter((item) => item.id == info.id) + .map((item) => ( + <div key={item.id} className="p-3 grid gap-2"> + <div className="text-xl font-outfit font-semibold line-clamp-2"> + <Link + href={`/anime/${data.id}`} + className="inline hover:underline" + > + {title} + </Link> + </div> + <h4 className="text-sm font-karla font-light"> + Episode {item.number} + </h4> + </div> + ))} + </> + )} + </div> + <div className="h-[1px] bg-[#3b3b3b]" /> + <div> + <div className="px-4 pt-7 pb-4 h-full flex"> + <div className="aspect-[9/13] h-[240px]"> + <Image + src={data.image} + alt="Anime Cover" + width={1000} + height={1000} + className="object-cover aspect-[9/13] h-[240px] rounded-md" + /> + </div> + <div className="grid w-full px-5 gap-3 h-[240px]"> + <div className="grid grid-cols-2 gap-1 items-center"> + <h2 className="text-sm font-light font-roboto text-[#878787]"> + Studios + </h2> + <div className="row-start-2">{data.studios}</div> + <div className="grid col-start-2 place-content-end relative"> + <div className="" onClick={() => setOpen(true)}> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="none" + viewBox="0 0 24 24" + strokeWidth={1.5} + stroke="currentColor" + className="w-8 h-8 hover:fill-white hover:cursor-pointer" + > + <path + strokeLinecap="round" + strokeLinejoin="round" + d="M17.593 3.322c1.1.128 1.907 1.077 1.907 2.185V21L12 17.25 4.5 21V5.507c0-1.108.806-2.057 1.907-2.185a48.507 48.507 0 0111.186 0z" + /> + </svg> + {/* <span className=" transition-all duration-300 absolute -top-12 bg-[#2c2c2c] font-karla p-2 rounded-l-lg rounded-tr-lg right-3 select-none"> + Save to My List + </span> */} + </div> + </div> + </div> + <div className="grid gap-1 items-center"> + <h2 className="text-sm font-light font-roboto text-[#878787]"> + Status + </h2> + <div>{data.status}</div> + </div> + <div className="grid gap-1 items-center overflow-y-hidden"> + <h2 className="text-sm font-light font-roboto text-[#878787]"> + Titles + </h2> + <div className="grid grid-flow-dense grid-cols-2 gap-2 h-full w-full"> + <div className="line-clamp-3"> + {data.title.romaji || ""} + </div> + <div className="line-clamp-3"> + {data.title.english || ""} + </div> + <div className="line-clamp-3"> + {data.title.native || ""} + </div> + </div> + </div> + </div> + </div> + <div className="flex flex-wrap gap-3 px-4 pt-3 "> + {data.genres.map((item, index) => ( + <div + key={index} + className="border border-action text-gray-100 py-1 px-2 rounded-md font-karla text-sm" + > + {item} + </div> + ))} + </div> + <div className={`bg-[#2a2a2a] rounded-md mt-3 mx-3`}> + <p + dangerouslySetInnerHTML={{ __html: data.description }} + className={`p-5 text-sm font-light font-roboto text-[#e4e4e4] `} + /> + </div> + </div> + </div> + </div> + <div className="flex flex-col w-screen lg:w-[33%] "> + <h1 className="text-xl font-karla pl-4 pb-5 font-semibold"> + Episodes + </h1> + <div className="grid gap-5 lg:px-5 px-2 py-2 scrollbar-thin scrollbar-thumb-[#313131] scrollbar-thumb-rounded-full"> + {data.episodes.length > 0 + ? data.episodes.map((item) => { + return ( + <Link + href={`/anime/watch/${item.id}/${data.id}`} + key={item.id} + className={`bg-secondary flex w-full h-[110px] rounded-lg scale-100 transition-all duration-300 ease-out ${ + item.id == info.id + ? "pointer-events-none ring-1 ring-action" + : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white" + }`} + > + <div className="w-[40%] h-full relative shrink-0"> + <Image + src={item.image} + alt="image" + height={1000} + width={1000} + className={`object-cover rounded-lg h-[110px] shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)] ${ + item.id == info.id + ? "brightness-[30%]" + : "brightness-75" + }`} + /> + <span className="absolute bottom-2 left-2 font-karla font-light text-sm"> + Episode {item.number} + </span> + {item.id == info.id && ( + <div className="absolute top-11 left-[105px] scale-[1.5]"> + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 20" + fill="currentColor" + className="w-5 h-5" + > + <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z" /> + </svg> + </div> + )} + </div> + <div + className={`w-[70%] h-full select-none p-4 flex flex-col gap-2 ${ + item.id == info.id ? "text-[#7a7a7a]" : "" + }`} + > + <h1 className="font-karla font-bold italic line-clamp-1"> + {item.title} + </h1> + <p className="line-clamp-2 text-xs italic font-outfit font-extralight"> + {item.description} + </p> + </div> + </Link> + ); + }) + : fallback.map((item) => { + return ( + <Link + href={`/anime/watch/${item.id}/${data.id}`} + key={item.id} + className={`bg-secondary flex-center w-full h-[50px] rounded-lg scale-100 transition-all duration-300 ease-out ${ + item.id == info.id + ? "pointer-events-none ring-1 ring-action text-[#5d5d5d]" + : "cursor-pointer hover:scale-[1.02] ring-0 hover:ring-1 hover:shadow-lg ring-white" + }`} + > + Episode {item.number} + </Link> + ); + })} + </div> + </div> + </div> + </> + ); +} + +export async function getServerSideProps(context) { + const { info } = context.query; + if (!info) { + return { + notFound: true, + }; + } + + const id = info[0]; + const aniId = info[1]; + const seek = info[2] || 0; + let epiFallback = null; + + const res = await fetch(`https://api.moopa.my.id/meta/anilist/watch/${id}`); + const epiData = await res.json(); + + const res2 = await fetch( + `https://api.moopa.my.id/meta/anilist/info/${aniId}` + ); + const aniData = await res2.json(); + + if (aniData.episodes.length === 0) { + const res = await fetch( + `https://api.moopa.my.id/anime/gogoanime/${ + aniData.title.romaji || aniData.title.english + }` + ); + const data = await res.json(); + const match = closestMatch( + aniData.title.romaji, + data.results.map((item) => item.title) + ); + const anime = data.results.filter((item) => item.title === match); + if (anime.length !== 0) { + const infos = await fetch( + `https://api.moopa.my.id/anime/gogoanime/info/${anime[0].id}` + ).then((res) => res.json()); + epiFallback = infos.episodes; + } + } + + return { + props: { + info: { + id, + seek, + epiData, + aniData, + epiFallback, + }, + }, + }; +} |