diff options
| author | Factiven <[email protected]> | 2023-09-13 00:45:53 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-09-13 00:45:53 +0700 |
| commit | 7327a69b55a20b99b14ee0803d6cf5f8b88c45ef (patch) | |
| tree | cbcca777593a8cc4b0282e7d85a6fc51ba517e25 /pages/id | |
| parent | Update issue templates (diff) | |
| download | moopa-7327a69b55a20b99b14ee0803d6cf5f8b88c45ef.tar.xz moopa-7327a69b55a20b99b14ee0803d6cf5f8b88c45ef.zip | |
Update v4 - Merge pre-push to main (#71)
* Create build-test.yml
* initial v4 commit
* update: github workflow
* update: push on branch
* Update .github/ISSUE_TEMPLATE/bug_report.md
* configuring next.config.js file
Diffstat (limited to 'pages/id')
| -rw-r--r-- | pages/id/about.js | 57 | ||||
| -rw-r--r-- | pages/id/anime/[...id].js | 846 | ||||
| -rw-r--r-- | pages/id/anime/watch/[...info].js | 485 | ||||
| -rw-r--r-- | pages/id/contact.js | 19 | ||||
| -rw-r--r-- | pages/id/dmca.js | 109 | ||||
| -rw-r--r-- | pages/id/index.js | 650 | ||||
| -rw-r--r-- | pages/id/profile/[user].js | 423 | ||||
| -rw-r--r-- | pages/id/search/[param].js | 491 |
8 files changed, 31 insertions, 3049 deletions
diff --git a/pages/id/about.js b/pages/id/about.js deleted file mode 100644 index 9bd32ed..0000000 --- a/pages/id/about.js +++ /dev/null @@ -1,57 +0,0 @@ -import Head from "next/head"; -import Layout from "../../components/layout"; -import { motion } from "framer-motion"; -import Link from "next/link"; - -export default function About() { - return ( - <> - <Head> - <title>Moopa - About</title> - <meta name="about" content="About this web" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" href="/c.svg" /> - </Head> - <Layout> - <motion.div - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - exit={{ opacity: 0 }} - className="flex flex-col justify-center items-center min-h-screen md:py-0 py-16" - > - <div className="max-w-screen-lg w-full px-4 py-10"> - <h1 className="text-4xl font-bold mb-6">About Us</h1> - <p className="text-lg mb-8"> - Moopa is a platform where you can watch and stream anime or read - manga for free, without any ads or VPNs. Our mission is to provide - a convenient and enjoyable experience for anime and manga - enthusiasts all around the world. - </p> - <p className="text-lg mb-8"> - At our site, you will find a vast collection of anime and manga - titles from different genres, including action, adventure, comedy, - romance, and more. We take pride in our fast and reliable servers, - which ensure smooth streaming and reading for all our users. - </p> - <p className="text-lg mb-8"> - We believe that anime and manga have the power to inspire and - entertain people of all ages and backgrounds. Our service is - designed to make it easy for fans to access the content they love, - whether they are casual viewers or die-hard fans. - </p> - <p className="text-lg mb-8"> - Thank you for choosing our website as your go-to platform for - anime and manga. We hope you enjoy your stay here, and feel free - to contact us if you have any feedback or suggestions. - </p> - <Link href="/en/contact"> - <div className="bg-[#ffffff] text-black font-medium py-3 px-6 rounded-lg hover:bg-action transition duration-300 ease-in-out"> - Contact Us - </div> - </Link> - </div> - </motion.div> - </Layout> - </> - ); -} diff --git a/pages/id/anime/[...id].js b/pages/id/anime/[...id].js deleted file mode 100644 index e5a26f8..0000000 --- a/pages/id/anime/[...id].js +++ /dev/null @@ -1,846 +0,0 @@ -import Skeleton from "react-loading-skeleton"; - -import { - ChevronDownIcon, - ClockIcon, - HeartIcon, -} from "@heroicons/react/20/solid"; -import { - TvIcon, - ArrowTrendingUpIcon, - RectangleStackIcon, -} from "@heroicons/react/24/outline"; - -import Head from "next/head"; -import Image from "next/image"; -import { useRouter } from "next/router"; -import { useEffect, useRef, useState } from "react"; -import Layout from "../../../components/layout"; -import Link from "next/link"; -import Content from "../../../components/home/content"; -import Modal from "../../../components/modal"; - -import { signIn, useSession } from "next-auth/react"; -import AniList from "../../../components/media/aniList"; -import ListEditor from "../../../components/listEditor"; - -import { GET_MEDIA_USER } from "../../../queries"; -import { GET_MEDIA_INFO } from "../../../queries"; -import { closestMatch } from "closest-match"; - -// import { aniInfo } from "../../components/devComp/data"; -// console.log(GET_MEDIA_USER); - -export default function Info({ info, color, api }) { - // Episodes dropdown - const [firstEpisodeIndex, setFirstEpisodeIndex] = useState(0); - const [lastEpisodeIndex, setLastEpisodeIndex] = useState(); - const [selectedRange, setSelectedRange] = useState("All"); - function onEpisodeIndexChange(e) { - if (e.target.value === "All") { - setFirstEpisodeIndex(0); - setLastEpisodeIndex(); - setSelectedRange("All"); - return; - } - setFirstEpisodeIndex(e.target.value.split("-")[0] - 1); - setLastEpisodeIndex(e.target.value.split("-")[1]); - setSelectedRange(e.target.value); - } - - const { data: session } = useSession(); - const [episode, setEpisode] = useState(null); - const [loading, setLoading] = useState(false); - const [progress, setProgress] = useState(0); - const [statuses, setStatuses] = useState(null); - const [domainUrl, setDomainUrl] = useState(""); - const [showAll, setShowAll] = useState(false); - const [visible, setVisible] = useState(false); - const [open, setOpen] = useState(false); - const [time, setTime] = useState(0); - const { id } = useRouter().query; - - const [fetchFailed, setFetchFailed] = useState(false); - const failedAttempts = useRef(0); - - const [artStorage, setArtStorage] = useState(null); - - const rec = info?.recommendations?.nodes?.map( - (data) => data.mediaRecommendation - ); - - const [log, setLog] = useState(); - - //for episodes dropdown - useEffect(() => { - setFirstEpisodeIndex(0); - setLastEpisodeIndex(); - setSelectedRange("All"); - }, [info]); - - useEffect(() => { - handleClose(); - async function fetchData() { - setLoading(true); - if (id) { - const { protocol, host } = window.location; - const url = `${protocol}//${host}`; - - setDomainUrl(url); - - setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); - - setEpisode(null); - setProgress(0); - setStatuses(null); - - try { - const res1 = await Promise.race([ - fetch( - `https://ani-indo.vercel.app/get/search?q=${encodeURIComponent( - info.title.romaji - )}` - ), - new Promise((_, reject) => - setTimeout(() => reject(new Error("timeout")), 10000) - ), - ]); - - const data1 = await res1.json(); - if (data1.data.length === 0) { - let text = info.title.romaji; - let words = text.split(" "); - let firstTwoWords = words.slice(0, 2).join(" "); - - setLog(firstTwoWords); - const anotherRes = await Promise.race([ - fetch( - `https://ani-indo.vercel.app/get/search?q=${firstTwoWords}` - ), - new Promise((_, reject) => - setTimeout(() => reject(new Error("timeout")), 10000) - ), - ]); - const fallbackData = await anotherRes.json(); - - const title = fallbackData.data.map((i) => i.title); - const match = closestMatch(info.title.romaji, title); - if (match) { - const getAnime = fallbackData.data.find((i) => i.title === match); - const res2 = await fetch( - `https://ani-indo.vercel.app/get/info/${getAnime.animeId}` - ); - const data2 = await res2.json(); - if (data2.status === "success") { - setEpisode(data2.data[0].episode); - } - // setLog(data2); - } else { - setLoading(false); - } - } - if (data1.status === "success") { - const title = data1.data.map((i) => i.title); - const match = closestMatch(info.title.romaji, title); - if (match) { - const getAnime = data1.data.find((i) => i.title === match); - const res2 = await fetch( - `https://ani-indo.vercel.app/get/info/${getAnime.animeId}` - ); - const data2 = await res2.json(); - if (data2.status === "success") { - setEpisode(data2.data[0].episode); - } - // setLog(data2); - } else { - setLoading(false); - } - // setLog(match); - } - // setLog(data1); - - if (session?.user?.name) { - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: GET_MEDIA_USER, - variables: { - username: session?.user?.name, - }, - }), - }); - - const responseData = await response.json(); - - const prog = responseData?.data?.MediaListCollection; - - if (prog && prog.lists.length > 0) { - const gut = prog.lists - .flatMap((item) => item.entries) - .find((item) => item.mediaId === parseInt(id[0])); - - if (gut) { - setProgress(gut.progress); - const statusMapping = { - CURRENT: { name: "Watching", value: "CURRENT" }, - PLANNING: { name: "Plan to watch", value: "PLANNING" }, - COMPLETED: { name: "Completed", value: "COMPLETED" }, - DROPPED: { name: "Dropped", value: "DROPPED" }, - PAUSED: { name: "Paused", value: "PAUSED" }, - REPEATING: { name: "Rewatching", value: "REPEATING" }, - }; - setStatuses(statusMapping[gut.status]); - } - } - setLoading(false); - } - - if (info.nextAiringEpisode) { - setTime( - convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring) - ); - } - } catch (error) { - if (error.message === "timeout") { - const currentAttempts = - parseInt(localStorage.getItem("failedAttempts") || "0", 10) + 1; - localStorage.setItem("failedAttempts", currentAttempts.toString()); - - if (currentAttempts < 3) { - window.location.reload(); - } else { - localStorage.removeItem("failedAttempts"); - setFetchFailed(true); - } - } else { - console.error(error); - } - } - } - setLoading(false); - } - fetchData(); - }, [id, info, session?.user?.name]); - - function handleOpen() { - setOpen(true); - document.body.style.overflow = "hidden"; - } - - function handleClose() { - setOpen(false); - document.body.style.overflow = "auto"; - } - - return ( - <> - <Head> - <title> - {info - ? info?.title?.romaji || info?.title?.english - : "Retrieving Data..."} - </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.extraLarge}`} - /> - </Head> - <Modal open={open} onClose={() => handleClose()}> - <div> - {!session && ( - <div className="flex-center flex-col gap-5 px-10 py-5 bg-secondary rounded-md"> - <h1 className="text-md font-extrabold font-karla"> - Edit your list - </h1> - <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} - prg={progress} - max={info?.episodes} - image={info} - /> - )} - </div> - </Modal> - <Layout navTop="text-white bg-primary lg:pt-0 lg:px-0 bg-slate bg-opacity-40 z-50"> - <div className="w-screen min-h-screen relative flex flex-col items-center bg-primary gap-5"> - <div className="bg-image w-screen"> - <div className="bg-gradient-to-t from-primary from-10% to-transparent absolute h-[300px] w-screen z-10 inset-0" /> - {info ? ( - <Image - src={ - info?.bannerImage || - info?.coverImage?.extraLarge || - info?.coverImage.large - } - priority={true} - alt="banner anime" - height={1000} - width={1000} - className="object-cover bg-image w-screen absolute top-0 left-0 h-[300px] brightness-[70%] z-0" - /> - ) : ( - <div className="bg-image w-screen absolute top-0 left-0 h-[300px]" /> - )} - </div> - <div className="lg:w-[90%] xl:w-[75%] lg:pt-[10rem] z-30 flex flex-col gap-5"> - {/* Mobile */} - - <div className="lg:hidden pt-5 w-screen px-5 flex flex-col"> - <div className="h-[250px] flex flex-col gap-1 justify-center"> - <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]"> - {info?.title?.romaji || info?.title?.english} - </h1> - <p - className="line-clamp-2 text-sm font-light antialiased w-[56%]" - dangerouslySetInnerHTML={{ __html: info?.description }} - /> - <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]"> - {info?.genres - ?.slice( - 0, - info?.genres?.length > 3 ? info?.genres?.length : 3 - ) - .map((item, index) => ( - <span - key={index} - className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full" - > - <span className="">{item}</span> - </span> - ))} - </div> - {info && ( - <div className="flex items-center gap-5 pt-3 text-center"> - <div className="flex items-center gap-2 text-center"> - <button - type="button" - className="bg-action px-10 rounded-sm font-karla font-bold" - onClick={() => handleOpen()} - > - {!loading - ? statuses - ? statuses.name - : "Add to List" - : "Loading..."} - </button> - <div className="h-6 w-6"> - <HeartIcon /> - </div> - </div> - </div> - )} - </div> - <div className="bg-secondary rounded-sm xs:h-[30px]"> - <div className="grid grid-cols-3 place-content-center xxs:flex items-center justify-center h-full xxs:gap-10 p-2 text-sm"> - {info && info.status !== "NOT_YET_RELEASED" ? ( - <> - <div className="flex-center flex-col xxs:flex-row gap-2"> - <TvIcon className="w-5 h-5 text-action" /> - <h4 className="font-karla">{info?.type}</h4> - </div> - <div className="flex-center flex-col xxs:flex-row gap-2"> - <ArrowTrendingUpIcon className="w-5 h-5 text-action" /> - <h4>{info?.averageScore}%</h4> - </div> - <div className="flex-center flex-col xxs:flex-row gap-2"> - <RectangleStackIcon className="w-5 h-5 text-action" /> - {info?.episodes ? ( - <h1>{info?.episodes} Episodes</h1> - ) : ( - <h1>TBA</h1> - )} - </div> - </> - ) : ( - <div>{info && "Not Yet Released"}</div> - )} - </div> - </div> - </div> - - {/* PC */} - <div className="hidden lg:flex gap-8 w-full flex-nowrap"> - <div className="shrink-0 lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] relative"> - {info ? ( - <> - <div className="bg-image lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] bg-opacity-30 absolute backdrop-blur-lg z-10 -top-7" /> - <Image - src={info.coverImage.extraLarge || info.coverImage.large} - priority={true} - alt="poster anime" - height={700} - width={700} - className="object-cover lg:h-[250px] lg:w-[180px] w-[115px] h-[164px] z-20 absolute rounded-md -top-7" - /> - <button - type="button" - className="bg-action flex-center z-20 h-[20px] w-[180px] absolute bottom-0 rounded-sm font-karla font-bold" - onClick={() => handleOpen()} - > - {!loading - ? statuses - ? statuses.name - : "Add to List" - : "Loading..."} - </button> - </> - ) : ( - <Skeleton className="h-[250px] w-[180px]" /> - )} - </div> - - {/* PC */} - <div className="hidden lg:flex w-full flex-col gap-5 h-[250px]"> - <div className="flex flex-col gap-2"> - <h1 className=" font-inter font-bold text-[36px] text-white line-clamp-1"> - {info ? ( - info?.title?.romaji || info?.title?.english - ) : ( - <Skeleton width={450} /> - )} - </h1> - {info ? ( - <div className="flex gap-6"> - {info?.episodes && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.episodes} Episodes - </div> - )} - {info?.startDate?.year && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.startDate?.year} - </div> - )} - {info?.averageScore && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.averageScore}% - </div> - )} - {info?.type && ( - <div - className={`dynamic-text rounded-md px-2 font-karla font-bold`} - style={color} - > - {info?.type} - </div> - )} - {info?.status && ( - <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 | EN - </div> - </div> - ) : ( - <Skeleton width={240} height={32} /> - )} - </div> - {info ? ( - <p - dangerouslySetInnerHTML={{ __html: info?.description }} - className="overflow-y-scroll scrollbar-thin pr-2 scrollbar-thumb-secondary scrollbar-thumb-rounded-lg h-[140px]" - /> - ) : ( - <Skeleton className="h-[130px]" /> - )} - </div> - </div> - - <div> - <div className="flex gap-5 items-center"> - {info?.relations?.edges?.length > 0 && ( - <div className="p-3 lg:p-0 text-[20px] lg:text-2xl font-bold font-karla"> - Relations - </div> - )} - {info?.relations?.edges?.length > 3 && ( - <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 lg:pb-5 px-3 lg:px-4 pt-4 rounded-xl`} - > - {info?.relations?.edges ? ( - info?.relations?.edges - .slice(0, showAll ? info?.relations?.edges.length : 3) - .map((r, index) => { - const rel = r.node; - return ( - <Link - key={rel.id} - href={ - rel.type === "ANIME" || - rel.type === "OVA" || - rel.type === "MOVIE" || - rel.type === "SPECIAL" || - rel.type === "ONA" - ? `/id/anime/${rel.id}` - : `/manga/detail/id?aniId=${ - rel.id - }&aniTitle=${encodeURIComponent( - info?.title?.english || - info?.title.romaji || - info?.title.native - )}` - } - className={`hover:scale-[1.02] hover:shadow-lg lg:px-0 px-4 scale-100 transition-transform duration-200 ease-out w-full ${ - rel.type === "MUSIC" ? "pointer-events-none" : "" - }`} - > - <div - key={rel.id} - className="w-full shrink h-[126px] bg-secondary flex rounded-md" - > - <div className="w-[90px] bg-image rounded-l-md shrink-0"> - <Image - src={ - rel.coverImage.extraLarge || - rel.coverImage.large - } - alt={rel.id} - height={500} - width={500} - className="object-cover h-full w-full shrink-0 rounded-l-md" - /> - </div> - <div className="h-full grid px-3 items-center"> - <div className="text-action font-outfit font-bold"> - {r.relationType} - </div> - <div className="font-outfit font-thin line-clamp-2"> - {rel.title.userPreferred || rel.title.romaji} - </div> - <div className={``}>{rel.type}</div> - </div> - </div> - </Link> - ); - }) - ) : ( - <> - {[1, 2, 3].map((item) => ( - <div key={item} className="w-full hidden lg:block"> - <Skeleton className="h-[126px]" /> - </div> - ))} - <div className="w-full lg:hidden"> - <Skeleton className="h-[126px]" /> - </div> - </> - )} - </div> - </div> - <div className="flex flex-col gap-5 lg:gap-10 p-3 lg:p-0"> - <div className="flex lg:flex-row flex-col gap-5 lg:gap-0 justify-between "> - <div className="flex justify-between"> - <div className="flex items-center lg:gap-10 sm:gap-7 gap-3"> - {info && ( - <h1 className="text-[20px] lg:text-2xl font-bold font-karla"> - Episodes - </h1> - )} - {info?.nextAiringEpisode && ( - <div className="flex items-center gap-2"> - <div className="flex items-center gap-4 text-[10px] xxs:text-sm lg:text-base"> - <h1>Next :</h1> - <div className="px-4 rounded-sm font-karla font-bold bg-white text-black"> - {time} - </div> - </div> - <div className="h-6 w-6"> - <ClockIcon /> - </div> - </div> - )} - </div> - {episode?.length > 50 && ( - <div - className="lg:hidden bg-secondary p-1 rounded-md cursor-pointer" - onClick={() => setVisible(!visible)} - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" - /> - </svg> - </div> - )} - </div> - {episode?.length > 50 && ( - <div - className={`flex lg:flex items-center gap-0 lg:gap-5 justify-between ${ - visible ? "" : "hidden" - }`} - > - <div className="flex items-end gap-3"> - {episode?.length > 50 && ( - <div className="relative flex gap-2 items-center"> - <p className="hidden md:block">Episodes</p> - <select - onChange={onEpisodeIndexChange} - value={selectedRange} - className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus:ring-1 focus:ring-action scrollbar-thin scrollbar-thumb-secondary scrollbar-thumb-rounded-lg" - > - <option value="All">All</option> - {[...Array(Math.ceil(episode?.length / 50))].map( - (_, index) => { - const start = index * 50 + 1; - const end = Math.min( - start + 50 - 1, - episode?.length - ); - const optionLabel = `${start} to ${end}`; - if (episode[0]?.number !== 1) { - var valueLabel = `${ - episode.length - end + 1 - }-${episode.length - start + 1}`; - } else { - var valueLabel = `${start}-${end}`; - } - return ( - <option key={valueLabel} value={valueLabel}> - {optionLabel} - </option> - ); - } - )} - </select> - <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - )} - </div> - </div> - )} - </div> - {!loading ? ( - Array.isArray(episode) ? ( - episode && ( - <div className="scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] h-[640px]"> - {episode?.length !== 0 && episode ? ( - <div - className={`flex flex-col gap-5 pb-5 pt-2 lg:pt-0`} - > - {episode - .slice(firstEpisodeIndex, lastEpisodeIndex) - .map((epi, index) => { - return ( - <div - key={index} - className="flex flex-col gap-3 px-2" - > - <Link - href={`/id/anime/watch/${info.id}/${epi.episodeId}`} - className={`text-start text-sm lg:text-lg ${ - progress && index <= progress - 1 - ? "text-[#5f5f5f]" - : "text-white" - }`} - > - <p>{epi.epsTitle}</p> - </Link> - {index !== episode?.length - 1 && ( - <span className="h-[1px] bg-white" /> - )} - </div> - ); - })} - </div> - ) : ( - <p>No Episodes Available</p> - )} - </div> - ) - ) : ( - <div className="flex flex-col"> - <pre - className={`rounded-md overflow-hidden ${getLanguageClassName( - "bash" - )}`} - > - <code> - {episode?.message || "Anime tidak tersedia :/"} - </code> - </pre> - </div> - ) - ) : ( - <div className="flex justify-center"> - <div className="lds-ellipsis"> - <div></div> - <div></div> - <div></div> - <div></div> - </div> - </div> - )} - </div> - </div> - {info && rec?.length !== 0 && ( - <div className="w-screen lg:w-[90%] xl:w-[85%]"> - <Content - ids="recommendAnime" - section="Recommendations" - data={rec} - /> - </div> - )} - </div> - </Layout> - </> - ); -} - -export async function getServerSideProps(context) { - const { id } = context.query; - const API_URI = process.env.API_URI; - - const res = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: GET_MEDIA_INFO, - variables: { - id: id?.[0], - }, - }), - }); - - const json = await res.json(); - const data = json?.data?.Media; - - if (!data) { - return { - notFound: true, - }; - } - - const textColor = setTxtColor(data?.coverImage?.color); - - const color = { - backgroundColor: `${data?.coverImage?.color || "#ffff"}`, - color: textColor, - }; - - return { - props: { - info: data, - color: color, - api: API_URI, - }, - }; -} - -function convertSecondsToTime(sec) { - let days = Math.floor(sec / (3600 * 24)); - let hours = Math.floor((sec % (3600 * 24)) / 3600); - let minutes = Math.floor((sec % 3600) / 60); - - let time = ""; - - if (days > 0) { - time += `${days}d `; - } - - if (hours > 0) { - time += `${hours}h `; - } - - if (minutes > 0) { - time += `${minutes}m `; - } - - return time.trim(); -} - -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"; -} - -const getLanguageClassName = (language) => { - switch (language) { - case "javascript": - return "language-javascript"; - case "html": - return "language-html"; - case "bash": - return "language-bash"; - // add more languages here as needed - default: - return ""; - } -}; diff --git a/pages/id/anime/watch/[...info].js b/pages/id/anime/watch/[...info].js deleted file mode 100644 index 06269ab..0000000 --- a/pages/id/anime/watch/[...info].js +++ /dev/null @@ -1,485 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; -import Head from "next/head"; -import { useEffect, useState } from "react"; -import dynamic from "next/dynamic"; - -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../../../api/auth/[...nextauth]"; - -import Skeleton from "react-loading-skeleton"; - -import { Navigasi } from "../.."; -import { ChevronDownIcon, ForwardIcon } from "@heroicons/react/24/solid"; -import { useRouter } from "next/router"; - -import { GET_MEDIA_USER } from "../../../../queries"; - -import dotenv from "dotenv"; - -import VideoPlayer from "../../../../components/id-components/player/VideoPlayerId"; - -export default function Info({ sessions, id, aniId, provider, api, proxy }) { - const [epiData, setEpiData] = useState(null); - const [data, setAniData] = useState(null); - const [episode, setEpisode] = useState(null); - const [skip, setSkip] = useState({ op: null, ed: null }); - const [statusWatch, setStatusWatch] = useState("CURRENT"); - const [playingEpisode, setPlayingEpisode] = useState(null); - const [loading, setLoading] = useState(false); - const [playingTitle, setPlayingTitle] = useState(null); - const [poster, setPoster] = useState(null); - const [progress, setProgress] = useState(0); - const [currentNumber, setCurrentNumber] = useState(null); - - const [episodes, setEpisodes] = useState([]); - const [artStorage, setArtStorage] = useState(null); - - const router = useRouter(); - - useEffect(() => { - const defaultState = { - epiData: null, - skip: { op: null, ed: null }, - statusWatch: "CURRENT", - playingEpisode: null, - loading: false, - }; - - // Reset all state variables to their default values - Object.keys(defaultState).forEach((key) => { - const value = defaultState[key]; - if (Array.isArray(value)) { - value.length - ? eval( - `set${ - key.charAt(0).toUpperCase() + key.slice(1) - }(${JSON.stringify(value)})` - ) - : eval(`set${key.charAt(0).toUpperCase() + key.slice(1)}([])`); - } else { - eval( - `set${key.charAt(0).toUpperCase() + key.slice(1)}(${JSON.stringify( - value - )})` - ); - } - }); - - const fetchData = async () => { - let currentNumber = null; - try { - const res = await fetch( - `https://ani-indo.vercel.app/get/watch/${aniId}` - ); - const epiData = await res.json(); - currentNumber = epiData.episodeActive; - setCurrentNumber(currentNumber); - setEpisode(epiData.data); - setEpiData(epiData.episodeUrl); - } catch (error) { - setTimeout(() => { - window.location.reload(); - }, 3000); - } - - let aniData = null; - setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); - - const res2 = await fetch(`${api}/meta/anilist/info/${id}`); - aniData = await res2.json(); - setEpisodes(aniData.episodes?.reverse()); - setAniData(aniData); - - let playingEpisode = aniData.episodes - .filter((item) => item.number == currentNumber) - .map((item) => item.number); - - setPlayingEpisode(playingEpisode); - - const playing = aniData.episodes.filter((item) => item.id == id); - - setPoster(playing); - - const title = aniData.episodes - .filter((item) => item.id == id) - .find((item) => item.title !== null); - setPlayingTitle( - title?.title || aniData.title?.romaji || aniData.title?.english - ); - - const res4 = await fetch( - `https://api.aniskip.com/v2/skip-times/${aniData.malId}/${parseInt( - playingEpisode - )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` - ); - const skip = await res4.json(); - - const op = skip.results?.find((item) => item.skipType === "op") || null; - const ed = skip.results?.find((item) => item.skipType === "ed") || null; - - setSkip({ op, ed }); - - if (sessions) { - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: GET_MEDIA_USER, - variables: { - username: sessions?.user.name, - }, - }), - }); - - const dat = await response.json(); - - const prog = dat.data.MediaListCollection; - - const gat = prog?.lists.map((item) => item.entries); - const git = gat?.map((item) => - item?.find((item) => item.media.id === parseInt(aniId)) - ); - const gut = git?.find((item) => item?.media.id === parseInt(aniId)); - - if (gut) { - setProgress(gut.progress); - } - - if (gut?.status === "COMPLETED") { - setStatusWatch("REPEATING"); - } else if ( - gut?.status === "REPEATING" && - gut?.media?.episodes === parseInt(playingEpisode) - ) { - setStatusWatch("COMPLETED"); - } else if (gut?.status === "REPEATING") { - setStatusWatch("REPEATING"); - } else if (gut?.media?.episodes === parseInt(playingEpisode)) { - setStatusWatch("COMPLETED"); - } else if ( - gut?.media?.episodes !== null && - aniData.totalEpisodes === parseInt(playingEpisode) - ) { - setStatusWatch("COMPLETED"); - setLoading(true); - } - } - setLoading(true); - }; - fetchData(); - }, [id, aniId, provider, sessions]); - - useEffect(() => { - const mediaSession = navigator.mediaSession; - if (!mediaSession) return; - - const artwork = - poster && poster.length > 0 - ? [{ src: poster[0].image, sizes: "512x512", type: "image/jpeg" }] - : undefined; - - mediaSession.metadata = new MediaMetadata({ - title: playingTitle, - artist: `Moopa ${ - playingTitle === data?.title?.romaji - ? "- Episode " + playingEpisode - : `- ${data?.title?.romaji || data?.title?.english}` - }`, - artwork, - }); - }, [poster, playingTitle, playingEpisode, data]); - - return ( - <> - <Head> - <title>{playingTitle || "Loading..."}</title> - </Head> - - <div className="bg-primary"> - <Navigasi /> - <div className="min-h-screen mt-3 md:mt-0 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%]"> - {loading ? ( - Array.isArray(epiData) ? ( - <div className="aspect-video z-20 bg-black"> - <VideoPlayer - key={id} - data={epiData} - id={aniId} - progress={parseInt(playingEpisode)} - session={sessions} - aniId={parseInt(data?.id)} - stats={statusWatch} - op={skip.op} - ed={skip.ed} - title={playingTitle} - poster={poster[0]?.image} - proxy={proxy} - /> - </div> - ) : ( - <div className="aspect-video bg-black flex-center select-none"> - <p className="lg:p-0 p-5 text-center"> - Whoops! Something went wrong. Please reload the page or try - other sources. {`:(`} - </p> - </div> - ) - ) : ( - <div className="aspect-video bg-black" /> - )} - <div> - {data && data?.episodes.length > 0 ? ( - data.episodes - .filter((items) => items.number == currentNumber) - .map((item, index) => ( - <div className="flex justify-between" key={item.id}> - <div className="p-3 grid gap-2 w-[60%]"> - <div className="text-xl font-outfit font-semibold line-clamp-1"> - <Link - href={`/id/anime/${data.id}`} - className="inline hover:underline" - > - {item.title || - data.title.romaji || - data.title.english} - </Link> - </div> - <h4 className="text-sm font-karla font-light"> - Episode {item.number} - </h4> - </div> - <div className="w-[50%] flex gap-4 items-center justify-end px-4"> - <div className="relative"> - <select - className="flex items-center gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer" - value={item.number} - onChange={(e) => { - const selectedEpisode = data.episodes.find( - (episode) => - episode.number === parseInt(e.target.value) - ); - router.push( - `/id/anime/watch/${selectedEpisode.id}/${data.id}` - ); - }} - > - {data.episodes.map((episode) => ( - <option - key={episode.number} - value={episode.number} - > - Episode {episode.number} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - <button - className={`${ - item.number === data.episodes.length - ? "pointer-events-none" - : "" - } relative group`} - onClick={() => { - const currentEpisodeIndex = data.episodes.findIndex( - (episode) => episode.number === item.number - ); - if ( - currentEpisodeIndex !== -1 && - currentEpisodeIndex < data.episodes.length - 1 - ) { - const nextEpisode = - data.episodes[currentEpisodeIndex + 1]; - router.push( - `/id/anime/watch/${nextEpisode.id}/${data.id}` - ); - } - }} - > - <span className="absolute z-[9999] -left-11 -top-14 p-2 shadow-xl rounded-md transform transition-all whitespace-nowrap bg-secondary lg:group-hover:block group-hover:opacity-1 hidden font-karla font-bold"> - Next Episode - </span> - <ForwardIcon className="w-6 h-6" /> - </button> - </div> - </div> - )) - ) : ( - <div className="p-3 grid gap-2"> - <div className="text-xl font-outfit font-semibold line-clamp-2"> - <div className="inline hover:underline"> - <Skeleton width={240} /> - </div> - </div> - <h4 className="text-sm font-karla font-light"> - <Skeleton width={75} /> - </h4> - </div> - )} - <div className="h-[1px] bg-[#3b3b3b]" /> - - <div className="px-4 pt-7 pb-4 h-full flex"> - <div className="aspect-[9/13] h-[240px]"> - {data ? ( - <Image - src={data.image} - alt="Anime Cover" - width={1000} - height={1000} - priority - className="object-cover aspect-[9/13] h-[240px] rounded-md" - /> - ) : ( - <Skeleton height={240} /> - )} - </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 ? data.studios : <Skeleton width={80} />} - </div> - <div className="hidden xxs:grid col-start-2 place-content-end relative"> - <div> - <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> - </div> - </div> - </div> - <div className="grid gap-1 items-center"> - <h2 className="text-sm font-light font-roboto text-[#878787]"> - Status - </h2> - <div>{data ? data.status : <Skeleton width={75} />}</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"> - {data ? ( - <> - <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> - </> - ) : ( - <Skeleton width={200} height={50} /> - )} - </div> - </div> - </div> - </div> - <div className="flex flex-wrap gap-3 px-4 pt-3"> - {data && - 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-secondary rounded-md mt-3 mx-3`}> - {data && ( - <p - dangerouslySetInnerHTML={{ __html: data.description }} - className={`p-5 text-sm font-light font-roboto text-[#e4e4e4] `} - /> - )} - </div> - </div> - </div> - <div className="flex flex-col w-screen lg:w-[35%] "> - <h1 className="text-xl font-karla pl-4 pb-5 font-semibold"> - Up Next - </h1> - <div className="flex flex-col gap-5 lg:pl-5 px-2 py-2 scrollbar-thin scrollbar-thumb-[#313131] scrollbar-thumb-rounded-full"> - {data && data?.episodes.length > 0 ? ( - episode.map((item, index) => { - return ( - <Link - href={`/id/anime/watch/${data.id}/${item.episodeId}`} - key={item.id} - className={`bg-secondary flex-center w-full h-[50px] rounded-lg scale-100 transition-all duration-300 ease-out ${ - index === currentNumber - 1 - ? "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 {index + 1} - </Link> - ); - }) - ) : ( - <> - {[1].map((item) => ( - <Skeleton - key={item} - className="bg-secondary flex w-full h-[110px] rounded-lg scale-100 transition-all duration-300 ease-out" - /> - ))} - </> - )} - </div> - </div> - </div> - </div> - </> - ); -} - -export async function getServerSideProps(context) { - dotenv.config(); - - const API_URI = process.env.API_URI; - - const session = await getServerSession(context.req, context.res, authOptions); - - const proxy = process.env.PROXY_URI; - - const { info } = context.query; - if (!info) { - return { - notFound: true, - }; - } - - const id = info[0]; - const aniId = [info[1], info[2], info[3]]; - - return { - props: { - sessions: session, - id, - aniId: aniId.join("/"), - proxy, - api: API_URI, - }, - }; -} diff --git a/pages/id/contact.js b/pages/id/contact.js deleted file mode 100644 index 400a9e8..0000000 --- a/pages/id/contact.js +++ /dev/null @@ -1,19 +0,0 @@ -import Layout from "../../components/layout"; - -const Contact = () => { - return ( - <Layout className=""> - <div className=" flex h-screen w-screen flex-col items-center justify-center font-karla font-bold"> - <h1>Contact Us</h1> - <p>If you have any questions or comments, please email us at:</p> - <p> - <a href="mailto:[email protected]?subject=[Moopa]%20-%20Your%20Subject"> - </a> - </p> - </div> - </Layout> - ); -}; - -export default Contact; diff --git a/pages/id/dmca.js b/pages/id/dmca.js deleted file mode 100644 index 8dad7d7..0000000 --- a/pages/id/dmca.js +++ /dev/null @@ -1,109 +0,0 @@ -import Head from "next/head"; -import Layout from "../../components/layout"; - -export default function DMCA() { - return ( - <> - <Head> - <title>Moopa - DMCA</title> - <meta name="DMCA" content="DMCA" /> - <meta property="og:title" content="DMCA" /> - <meta - property="og:description" - content="Moopa.live is committed to respecting the intellectual - property rights of others and complying with the Digital - Millennium Copyright Act (DMCA)." - /> - <meta - property="og:image" - content="https://cdn.discordapp.com/attachments/1068758633464201268/1081591948705546330/logo.png" - /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" href="/c.svg" /> - </Head> - <Layout> - <div className="min-h-screen z-20 flex w-screen justify-center items-center"> - <div className="w-[75%] text-2xl gap-7 flex flex-col my-[10rem]"> - <div className="flex"> - <h1 className="text-4xl font-bold font-karla rounded-md bg-[#212121] p-3"> - DMCA - Disclaimer - </h1> - </div> - <div className="flex flex-col gap-10"> - <div className="flex flex-col gap-3 text-[#cdcdcd]"> - <p> - Moopa.live is committed to respecting the intellectual - property rights of others and complying with the Digital - Millennium Copyright Act (DMCA). We take copyright - infringement seriously and will respond to notices of alleged - copyright infringement that comply with the DMCA and any other - applicable laws. - </p> - <p> - If you believe that any content on our website is infringing - upon your copyrights, please send us an email. Please allow up - to 2-5 business days for a response. Please note that emailing - your complaint to other parties such as our Internet Service - Provider, Hosting Provider, and other third parties will not - expedite your request and may result in a delayed response due - to the complaint not being filed properly. - </p> - </div> - <p className="text-white"> - In order for us to process your complaint, please provide the - following information: - </p> - <div className="text-xl ml-5 text-[#cdcdcd]"> - <ul className="flex flex-col gap-1"> - <li> - · Your name, address, and telephone number. We reserve the - right to verify this information. - </li> - <li> - · Identification of the copyrighted work claimed to have - been infringed. - </li> - <li> - · The exact and complete URL link where the infringing - material is located. - </li> - <li> - · The exact and complete URL link where the infringing - material is located. - </li> - <li> - · The exact and complete URL link where the infringing - material is located. - </li> - <li>· Please write to us in English or Indonesian.</li> - </ul> - </div> - <p className="text-[#cdcdcd]"> - Please note that anonymous or incomplete messages will not be - dealt with. Thank you for your understanding. - </p> - <h1 className="text-white font-karla">DISCLAIMER:</h1> - <p className="text-[#cdcdcd]"> - None of the files listed on Moopa.live are hosted on our - servers. All links point to content hosted on third-party - websites. Moopa.live does not accept responsibility for content - hosted on third-party websites and has no involvement in the - downloading/uploading of movies. We only post links that are - available on the internet. If you believe that any content on - our website infringes upon your intellectual property rights and - you hold the copyright for that content, please report it to{" "} - <a - href="mailto:[email protected]?subject=[Moopa]%20-%20Your%20Subject" - className="font-semibold" - > - </a>{" "} - and the content will be immediately removed. - </p> - </div> - </div> - </div> - </Layout> - </> - ); -} diff --git a/pages/id/index.js b/pages/id/index.js index 1d42ce3..661bc05 100644 --- a/pages/id/index.js +++ b/pages/id/index.js @@ -1,633 +1,45 @@ -import { aniListData } from "../../lib/anilist/AniList"; -import React, { useState, useEffect } from "react"; import Head from "next/head"; +import React from "react"; +import Navbar from "../../components/navbar"; +import Image from "next/image"; import Link from "next/link"; import Footer from "../../components/footer"; -import Image from "next/image"; -import Content from "../../components/home/content"; -import { useRouter } from "next/router"; - -import { motion } from "framer-motion"; - -import { useSession, signIn, signOut } from "next-auth/react"; -import { useAniList } from "../../lib/anilist/useAnilist"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../api/auth/[...nextauth]"; -import SearchBar from "../../components/searchBar"; -import Genres from "../../components/home/genres"; -import { ToastContainer, toast, cssTransition } from "react-toastify"; - -export function Navigasi() { - const { data: sessions, status } = useSession(); - const [year, setYear] = useState(new Date().getFullYear()); - const [season, setSeason] = useState(getCurrentSeason()); - - const router = useRouter(); - - const handleFormSubmission = (inputValue) => { - router.push(`/id/search/${encodeURIComponent(inputValue)}`); - }; - - const handleKeyDown = async (event) => { - if (event.key === "Enter") { - event.preventDefault(); - const inputValue = event.target.value; - handleFormSubmission(inputValue); - } - }; - return ( - <> - {/* NAVBAR PC */} - <div className="flex items-center justify-center"> - <div className="flex w-full items-center justify-between px-5 lg:mx-[94px]"> - <div className="flex items-center lg:gap-16 lg:pt-7"> - <Link - href="/id/" - className=" font-outfit lg:text-[40px] text-[30px] font-bold text-[#FF7F57]" - > - moopa - </Link> - <ul className="hidden items-center gap-10 pt-2 font-outfit text-[14px] lg:flex"> - <li> - <Link - href={`/id/search/anime?season=${season}&seasonYear=${year}`} - > - This Season - </Link> - </li> - <li> - <Link href="/id/search/manga">Manga</Link> - </li> - <li> - <Link href="/id/search/anime">Anime</Link> - </li> - - {status === "loading" ? ( - <li>Loading...</li> - ) : ( - <> - {!sessions && ( - <li> - <button - onClick={() => signIn("AniListProvider")} - className="ring-1 ring-action font-karla font-bold px-2 py-1 rounded-md" - > - Sign in - </button> - </li> - )} - {sessions && ( - <li className="text-center"> - <Link href={`/id/profile/${sessions?.user.name}`}> - My List - </Link> - </li> - )} - </> - )} - </ul> - </div> - <div className="relative flex lg:scale-75 scale-[65%] items-center mb-7 lg:mb-0"> - <div className="search-box "> - <input - className="search-text" - type="text" - placeholder="Search Anime" - onKeyDown={handleKeyDown} - /> - <div className="search-btn"> - <i className="fas fa-search"></i> - </div> - </div> - </div> - </div> - </div> - </> - ); -} - -export default function Home({ detail, populars, sessions }) { - const { media: current } = useAniList(sessions, { stats: "CURRENT" }); - const { media: plan } = useAniList(sessions, { stats: "PLANNING" }); - - const [isVisible, setIsVisible] = useState(false); - const [list, setList] = useState(null); - const [planned, setPlanned] = useState(null); - const [greeting, setGreeting] = useState(""); - const [onGoing, setOnGoing] = useState(null); - - const [prog, setProg] = useState(null); - - const popular = populars?.data; - const data = detail.data[0]; - - const handleShowClick = () => { - setIsVisible(true); - }; - - const handleHideClick = () => { - setIsVisible(false); - }; - - useEffect(() => { - const time = new Date().getHours(); - let greeting = ""; - - if (time >= 5 && time < 12) { - greeting = "Good morning"; - } else if (time >= 12 && time < 18) { - greeting = "Good afternoon"; - } else if (time >= 18 && time < 22) { - greeting = "Good evening"; - } else if (time >= 22 || time < 5) { - greeting = "Good night"; - } - - setGreeting(greeting); - - async function userData() { - if (!sessions) return; - const getMedia = - current.filter((item) => item.status === "CURRENT")[0] || null; - const list = getMedia?.entries - .map(({ media }) => media) - .filter((media) => media); - - const prog = getMedia?.entries.filter( - (item) => item.media.nextAiringEpisode !== null - ); - - setProg(prog); - - const planned = plan?.[0]?.entries - .map(({ media }) => media) - .filter((media) => media); - - const onGoing = list?.filter((item) => item.nextAiringEpisode !== null); - setOnGoing(onGoing); - - if (list) { - setList(list.reverse()); - } - if (planned) { - setPlanned(planned.reverse()); - } - } - userData(); - }, [sessions, current, plan]); - - const blurSlide = cssTransition({ - enter: "slide-in-blurred-right", - exit: "slide-out-blurred-right", - }); - - useEffect(() => { - function Toast() { - toast.warn( - "This site is still in development, some features may not work properly.", - { - position: "bottom-right", - autoClose: false, - hideProgressBar: true, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - theme: "dark", - transition: blurSlide, - } - ); - } - Toast(); - }, []); - - // console.log(log); +export default function Home() { return ( <> <Head> - <title>Moopa</title> - <meta charSet="UTF-8"></meta> - <meta name="twitter:card" content="summary_large_image" /> - <meta - name="twitter:title" - content="Moopa - Free Anime and Manga Streaming" - /> - <meta - name="twitter:description" - content="Discover your new favorite anime or manga title! Moopa offers a vast library of high-quality content, accessible on multiple devices and without any interruptions. Start using Moopa today!" - /> - <meta - name="twitter:image" - content="https://cdn.discordapp.com/attachments/1084446049986420786/1093300833422168094/image.png" - /> - <link rel="icon" href="/c.svg" /> + <title>Under Construction</title> + <meta name="about" content="About this web" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <link rel="icon" href="/svg/c.svg" /> </Head> - - <ToastContainer pauseOnFocusLoss={false} style={{ width: "420px" }} /> - - {/* NAVBAR */} - <div className="z-50"> - {!isVisible && ( - <button - onClick={handleShowClick} - className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden" - id="bars" - > - <svg - xmlns="http://www.w3.org/2000/svg" - className="h-[42px] w-[61.5px] text-[#8BA0B2] fill-orange-500" - viewBox="0 0 20 20" - fill="currentColor" - > - <path - fillRule="evenodd" - d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" - clipRule="evenodd" - /> - </svg> - </button> - )} - </div> - - {/* Mobile Menu */} - <div className={`transition-all duration-150 subpixel-antialiased z-50`}> - {isVisible && sessions && ( - <Link - href={`/profile/${sessions?.user.name}`} - className="fixed lg:hidden bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#17171f]" - > - <img - src={sessions?.user.image.large} - alt="user avatar" - className="object-cover w-[60px] h-[60px] rounded-full" - /> - </Link> - )} - {isVisible && ( - <div className="fixed bottom-[30px] right-[20px] z-50 flex h-[51px] w-[300px] items-center justify-center gap-8 rounded-[8px] text-[11px] bg-[#17171f] shadow-lg lg:hidden"> - <div className="grid grid-cols-4 place-items-center gap-6"> - <button className="group flex flex-col items-center"> - <Link href="/id/" className=""> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" - /> - </svg> - </Link> - <Link - href="/id/" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - home - </Link> - </button> - <button className="group flex flex-col items-center"> - <Link href="/id/about"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" - /> - </svg> - </Link> - <Link - href="/id/about" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - about - </Link> - </button> - <button className="group flex gap-[1.5px] flex-col items-center "> - <div> - <Link href="/id/search/anime"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" - /> - </svg> - </Link> - </div> - <Link - href="/id/search/anime" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - search - </Link> - </button> - {sessions ? ( - <button - onClick={() => signOut("AniListProvider")} - className="group flex gap-[1.5px] flex-col items-center " - > - <div> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 96 960 960" - className="group-hover:fill-action w-6 h-6 fill-txt" - > - <path d="M186.666 936q-27 0-46.833-19.833T120 869.334V282.666q0-27 19.833-46.833T186.666 216H474v66.666H186.666v586.668H474V936H186.666zm470.668-176.667l-47-48 102-102H370v-66.666h341.001l-102-102 46.999-48 184 184-182.666 182.666z"></path> - </svg> - </div> - <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"> - logout - </h1> - </button> - ) : ( - <button - onClick={() => signIn("AniListProvider")} - className="group flex gap-[1.5px] flex-col items-center " - > - <div> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 96 960 960" - className="group-hover:fill-action w-6 h-6 fill-txt mr-2" - > - <path d="M486 936v-66.666h287.334V282.666H486V216h287.334q27 0 46.833 19.833T840 282.666v586.668q0 27-19.833 46.833T773.334 936H486zm-78.666-176.667l-47-48 102-102H120v-66.666h341l-102-102 47-48 184 184-182.666 182.666z"></path> - </svg> - </div> - <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"> - login - </h1> - </button> - )} - </div> - <button onClick={handleHideClick}> - <svg - width="20" - height="21" - className="fill-orange-500" - viewBox="0 0 20 21" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <rect - x="2.44043" - y="0.941467" - width="23.5842" - height="3.45134" - rx="1.72567" - transform="rotate(45 2.44043 0.941467)" - /> - <rect - x="19.1172" - y="3.38196" - width="23.5842" - height="3.45134" - rx="1.72567" - transform="rotate(135 19.1172 3.38196)" - /> - </svg> - </button> - </div> - )} - </div> - - <div className="h-auto w-screen bg-[#141519] text-[#dbdcdd] "> - <Navigasi /> - <SearchBar /> - {/* PC / TABLET */} - <div className=" hidden justify-center lg:flex my-16"> - <div className="relative grid grid-rows-2 items-center lg:flex lg:h-[467px] lg:w-[80%] lg:justify-between"> - <div className="row-start-2 flex h-full flex-col gap-7 lg:w-[55%] lg:justify-center"> - <h1 className="w-[85%] font-outfit font-extrabold lg:text-[34px] line-clamp-2"> - {data.title.english || data.title.romaji || data.title.native} - </h1> - <p - className="font-roboto font-light lg:text-[18px] line-clamp-5" - dangerouslySetInnerHTML={{ __html: data?.description }} - /> - - <div className="lg:pt-5"> - <Link - href={`/id/anime/${data.id}`} - legacyBehavior - className="flex" - > - <a className="rounded-sm p-3 text-md font-karla font-light ring-1 ring-[#FF7F57]"> - START WATCHING - </a> - </Link> - </div> - </div> - <div className="z-10 row-start-1 flex justify-center "> - <div className="relative lg:h-[467px] lg:w-[322px] lg:scale-100"> - <div className="absolute bg-gradient-to-t from-[#141519] to-transparent lg:h-[467px] lg:w-[322px]" /> - - <Image - draggable={false} - src={data.coverImage?.extraLarge || data.image} - alt={`alt for ${data.title.english || data.title.romaji}`} - width={460} - height={662} - priority - className="rounded-tl-xl rounded-tr-xl object-cover bg-blend-overlay lg:h-[467px] lg:w-[322px]" - /> - </div> - </div> - </div> - </div> - {/* {!sessions && ( - <h1 className="font-bold font-karla mx-5 text-[32px] mt-2 lg:mx-24 xl:mx-36"> - {greeting}! + <main className="flex flex-col h-screen"> + <Navbar className="bg-[#0c0d10] z-50" /> + {/* Create an under construction page with tailwind css */} + <div className="h-full w-screen flex-center flex-grow flex-col"> + <Image + width={500} + height={500} + src="/work-on-progress.gif" + alt="work-on-progress" + className="w-[26vw] md:w-[15vw]" + /> + <h1 className="text-2xl sm:text-4xl xl:text-6x font-bold my-4"> + 🚧 We are still working on it 🚧 </h1> - )} */} - {sessions && ( - <div className="flex items-center justify-center lg:bg-none mt-4 lg:mt-0 w-screen"> - <div className="lg:w-[85%] w-screen px-5 lg:px-0 lg:text-4xl flex items-center gap-3 text-2xl font-bold font-karla"> - {greeting},<h1 className="lg:hidden">{sessions?.user.name}</h1> - <button - onClick={() => signOut()} - className="hidden text-center relative lg:flex justify-center group" - > - {sessions?.user.name} - <span className="absolute text-sm z-50 w-20 text-center bottom-11 text-white shadow-lg opacity-0 bg-secondary p-1 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all"> - Sign Out - </span> - </button> + <p className="text-base sm:text-lg xl:text-x text-gray-300 mb-6 text-center"> + "Please be patient, as we're still working on this page and it will + be available soon." + </p> + <Link href={`/en/`}> + <div className="bg-action xl:text-xl text-white font-bold py-2 px-4 rounded hover:bg-[#fb6f44]"> + Go back home </div> - </div> - )} - - <div className="lg:mt-16 mt-5 flex flex-col items-center"> - <motion.div - className="w-screen flex-none lg:w-[87%]" - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - transition={{ duration: 0.5, staggerChildren: 0.2 }} // Add staggerChildren prop - > - {sessions && onGoing?.length > 0 && ( - <motion.div // Add motion.div to each child component - key="onGoing" - initial={{ y: 20, opacity: 0 }} - whileInView={{ y: 0, opacity: 1 }} - transition={{ duration: 0.5 }} - viewport={{ once: true }} - > - <Content - ids="onGoing" - section="On-Going Anime" - data={onGoing} - og={prog} - /> - </motion.div> - )} - - {sessions && list?.length > 0 && ( - <motion.div // Add motion.div to each child component - key="listAnime" - initial={{ y: 20, opacity: 0 }} - whileInView={{ y: 0, opacity: 1 }} - transition={{ duration: 0.5 }} - viewport={{ once: true }} - > - <Content - ids="listAnime" - section="Your Watch List" - data={list} - /> - </motion.div> - )} - - {/* SECTION 2 */} - {sessions && planned?.length > 0 && ( - <motion.div // Add motion.div to each child component - key="plannedAnime" - initial={{ y: 20, opacity: 0 }} - whileInView={{ y: 0, opacity: 1 }} - transition={{ duration: 0.5 }} - viewport={{ once: true }} - > - <Content - ids="plannedAnime" - section="Your Plan" - data={planned} - /> - </motion.div> - )} - - {/* SECTION 3 */} - {detail && ( - <motion.div // Add motion.div to each child component - key="trendingAnime" - initial={{ y: 20, opacity: 0 }} - transition={{ duration: 0.5 }} - whileInView={{ y: 0, opacity: 1 }} - viewport={{ once: true }} - > - <Content - ids="trendingAnime" - section="Trending Now" - data={detail.data} - /> - </motion.div> - )} - - {/* SECTION 4 */} - {popular && ( - <motion.div // Add motion.div to each child component - key="popularAnime" - initial={{ y: 20, opacity: 0 }} - whileInView={{ y: 0, opacity: 1 }} - transition={{ duration: 0.5 }} - viewport={{ once: true }} - > - <Content - ids="popularAnime" - section="Popular Anime" - data={popular} - /> - </motion.div> - )} - - <motion.div // Add motion.div to each child component - key="Genres" - initial={{ y: 20, opacity: 0 }} - whileInView={{ y: 0, opacity: 1 }} - transition={{ duration: 0.5 }} - viewport={{ once: true }} - > - <Genres /> - </motion.div> - </motion.div> + </Link> </div> - </div> - <Footer /> + <Footer /> + </main> </> ); } - -export async function getServerSideProps(context) { - const session = await getServerSession(context.req, context.res, authOptions); - - const trendingDetail = await aniListData({ - sort: "TRENDING_DESC", - page: 1, - }); - const popularDetail = await aniListData({ - sort: "POPULARITY_DESC", - page: 1, - }); - const genreDetail = await aniListData({ sort: "TYPE", page: 1 }); - - return { - props: { - genre: genreDetail.props, - detail: trendingDetail.props, - populars: popularDetail.props, - sessions: session, - }, - }; -} - -function getCurrentSeason() { - const now = new Date(); - const month = now.getMonth() + 1; // getMonth() returns 0-based index - - switch (month) { - case 12: - case 1: - case 2: - return "WINTER"; - case 3: - case 4: - case 5: - return "SPRING"; - case 6: - case 7: - case 8: - return "SUMMER"; - case 9: - case 10: - case 11: - return "FALL"; - default: - return "UNKNOWN SEASON"; - } -} diff --git a/pages/id/profile/[user].js b/pages/id/profile/[user].js deleted file mode 100644 index 6bc804e..0000000 --- a/pages/id/profile/[user].js +++ /dev/null @@ -1,423 +0,0 @@ -import { getServerSession } from "next-auth"; -import { authOptions } from "../../api/auth/[...nextauth]"; -import Navbar from "../../../components/navbar"; -import Image from "next/image"; -import Link from "next/link"; -import Head from "next/head"; -import { useState } from "react"; - -export default function MyList({ media, sessions, user, time }) { - const [listFilter, setListFilter] = useState("all"); - const [visible, setVisible] = useState(false); - - const filterMedia = (status) => { - if (status === "all") { - return media; - } - return media.filter((m) => m.name === status); - }; - return ( - <> - <Head> - <title>My Lists</title> - </Head> - <Navbar /> - <div className="w-screen lg:flex justify-between lg:px-10 xl:px-32 py-5 relative"> - <div className="lg:w-[30%] h-full mt-12 lg:mr-10 grid gap-5 mx-3 lg:mx-0 antialiased"> - <div className="flex items-center gap-5"> - <Image - src={user.avatar.large} - alt="user avatar" - width={1000} - height={1000} - className="object-cover h-28 w-28 rounded-lg" - /> - {user.bannerImage ? ( - <Image - src={user.bannerImage} - alt="image" - width={1000} - height={1000} - priority - className="absolute w-screen h-[240px] object-cover -top-[7.75rem] left-0 -z-50 brightness-[65%]" - /> - ) : ( - <div className="absolute w-screen h-[240px] object-cover -top-[7.75rem] left-0 -z-50 brightness-[65%] bg-image" /> - )} - <h1 className="font-karla font-bold text-2xl pt-7">{user.name}</h1> - </div> - <div className="flex items-center justify-between"> - <div className="flex gap-2 text-sm font-karla"> - Created At : - <UnixTimeConverter unixTime={user.createdAt} /> - </div> - {sessions && user.name === sessions?.user.name ? ( - <Link - href={"https://anilist.co/settings/"} - className="flex items-center gap-2 p-1 px-2 ring-[1px] antialiased ring-txt rounded-lg text-xs font-karla hover:bg-txt hover:shadow-lg group" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-4 h-4 group-hover:stroke-black" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M9.53 16.122a3 3 0 00-5.78 1.128 2.25 2.25 0 01-2.4 2.245 4.5 4.5 0 008.4-2.245c0-.399-.078-.78-.22-1.128zm0 0a15.998 15.998 0 003.388-1.62m-5.043-.025a15.994 15.994 0 011.622-3.395m3.42 3.42a15.995 15.995 0 004.764-4.648l3.876-5.814a1.151 1.151 0 00-1.597-1.597L14.146 6.32a15.996 15.996 0 00-4.649 4.763m3.42 3.42a6.776 6.776 0 00-3.42-3.42" - /> - </svg> - <span className="group-hover:text-black">Edit Profile</span> - </Link> - ) : null} - </div> - <div className="bg-secondary lg:min-h-[160px] text-xs rounded-md p-4 font-karla"> - <div> - {user.about ? ( - <div dangerouslySetInnerHTML={{ __html: user.about }} /> - ) : ( - "No description created." - )} - </div> - </div> - - <div className="bg-secondary font-karla rounded-md h-20 p-1 grid grid-cols-3 place-items-center text-center text-txt"> - <div> - <h1 className="text-action font-bold"> - {user.statistics.anime.episodesWatched} - </h1> - <h2 className="text-sm">Total Episodes</h2> - </div> - <div> - <h1 className="text-action font-bold"> - {user.statistics.anime.count} - </h1> - <h2 className="text-sm">Total Anime</h2> - </div> - {time?.days ? ( - <div> - <h1 className="text-action font-bold">{time.days}</h1> - <h2 className="text-sm">Days Watched</h2> - </div> - ) : ( - <div> - <h1 className="text-action font-bold">{time.hours}</h1> - <h2 className="text-sm">hours</h2> - </div> - )} - </div> - {media.length !== 0 && ( - <div className="font-karla grid gap-4"> - <div className="flex md:justify-normal justify-between items-center"> - <div className="flex items-center gap-3"> - <h1>Lists Filter</h1> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-[20px] h-[20px]" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" - /> - </svg> - </div> - <div - className="md:hidden bg-secondary p-1 rounded-md cursor-pointer" - onClick={() => setVisible(!visible)} - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M6.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM12.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0zM18.75 12a.75.75 0 11-1.5 0 .75.75 0 011.5 0z" - /> - </svg> - </div> - </div> - <ul - className={`group md:grid gap-1 text-sm ${ - visible ? "" : "hidden" - }`} - > - <li - onClick={() => setListFilter("all")} - className={`p-2 cursor-pointer hover:text-action ${ - listFilter === "all" && "bg-secondary text-action" - }`} - > - <h1 className={`cursor-pointer hover:text-action`}> - Show All - </h1> - </li> - {media.map((item) => ( - <li - key={item.name} - onClick={() => setListFilter(item.name)} - className={`cursor-pointer hover:text-action flex gap-2 p-2 duration-200 ${ - item.name === listFilter && "bg-secondary text-action" - }`} - > - <h1 className="">{item.name}</h1> - <div className="text-gray-400 opacity-0 invisible duration-200 transition-all group-hover:visible group-hover:opacity-100"> - ({item.entries.length}) - </div> - </li> - ))} - </ul> - </div> - )} - </div> - - <div className="lg:w-[75%] grid gap-10 my-12 lg:pt-16"> - {media.length !== 0 ? ( - filterMedia(listFilter).map((item, index) => { - return ( - <div key={index} className="flex flex-col gap-5 mx-3"> - <h1 className="font-karla font-bold text-xl">{item.name}</h1> - <table className="bg-secondary rounded-lg"> - <thead> - <tr> - <th className="font-bold text-xs py-3 text-start pl-10 lg:w-[75%] w-[65%]"> - Title - </th> - <th className="font-bold text-xs py-3">Score</th> - <th className="font-bold text-xs py-3">Progress</th> - </tr> - </thead> - <tbody className=""> - {item.entries.map((item) => { - return ( - <tr - key={item.mediaId} - className="hover:bg-orange-400 duration-150 ease-in-out group relative" - > - <td className="font-medium py-2 pl-2 rounded-l-lg"> - <div className="flex items-center gap-2"> - {item.media.status === "RELEASING" ? ( - <span className="dot group-hover:invisible bg-green-500 shrink-0" /> - ) : item.media.status === "NOT_YET_RELEASED" ? ( - <span className="dot group-hover:invisible bg-red-500 shrink-0" /> - ) : ( - <span className="dot group-hover:invisible shrink-0" /> - )} - <Image - src={item.media.coverImage.large} - alt="Cover Image" - width={500} - height={500} - className="object-cover rounded-md w-10 h-10 shrink-0" - /> - <div className="absolute -top-10 -left-40 invisible lg:group-hover:visible"> - <Image - src={item.media.coverImage.large} - alt={item.media.id} - width={1000} - height={1000} - className="object-cover h-[186px] w-[140px] shrink-0 rounded-md" - /> - </div> - <Link - href={`/en/anime/${item.media.id}`} - className="font-semibold font-karla pl-2 text-sm line-clamp-1" - title={item.media.title.romaji} - > - {item.media.title.romaji} - </Link> - </div> - </td> - <td className="text-center text-xs text-txt"> - {item.score === 0 ? null : item.score} - </td> - <td className="text-center text-xs text-txt rounded-r-lg"> - {item.progress === item.media.episodes - ? item.progress - : item.media.episodes === null - ? item.progress - : `${item.progress}/${item.media.episodes}`} - </td> - </tr> - ); - })} - </tbody> - </table> - </div> - ); - }) - ) : ( - <div className="w-screen lg:w-full flex-center flex-col gap-5"> - {user.name === sessions?.user.name ? ( - <p className="text-center font-karla font-bold lg:text-lg"> - Oops!<br></br> Looks like you haven't watch anything yet. - </p> - ) : ( - <p className="text-center font-karla font-bold lg:text-lg"> - Oops!<br></br> It looks like this user haven't watch anything - yet. - </p> - )} - <Link - href="/en/search/anime" - className="flex gap-2 text-sm ring-1 ring-action p-2 rounded-lg font-karla" - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-5 h-5" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" - /> - </svg> - <span>Start Watching</span> - </Link> - </div> - )} - </div> - </div> - </> - ); -} - -export async function getServerSideProps(context) { - const session = await getServerSession(context.req, context.res, authOptions); - const query = context.query; - - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: ` - query ($username: String, $status: MediaListStatus) { - MediaListCollection(userName: $username, type: ANIME, status: $status, sort: SCORE_DESC) { - user { - id - name - about (asHtml: true) - createdAt - avatar { - large - } - statistics { - anime { - count - episodesWatched - meanScore - minutesWatched - } - } - bannerImage - mediaListOptions { - animeList { - sectionOrder - } - } - } - lists { - status - name - entries { - id - mediaId - status - progress - score - media { - id - status - title { - english - romaji - } - episodes - coverImage { - large - } - } - } - } - } - } - `, - variables: { - username: query.user, - }, - }), - }); - - const data = await response.json(); - - const get = data.data.MediaListCollection; - const sectionOrder = get?.user.mediaListOptions.animeList.sectionOrder; - - if (!sectionOrder) { - return { - notFound: true, - }; - } - - const prog = get.lists; - - function getIndex(status) { - const index = sectionOrder.indexOf(status); - return index === -1 ? sectionOrder.length : index; - } - - prog.sort((a, b) => getIndex(a.name) - getIndex(b.name)); - - const user = get.user; - - const time = convertMinutesToDays(user.statistics.anime.minutesWatched); - - return { - props: { - media: prog, - sessions: session, - user: user, - time: time, - }, - }; -} - -function UnixTimeConverter({ unixTime }) { - const date = new Date(unixTime * 1000); // multiply by 1000 to convert to milliseconds - const formattedDate = date.toISOString().slice(0, 10); // format date to YYYY-MM-DD - - return <p>{formattedDate}</p>; -} - -function convertMinutesToDays(minutes) { - const hours = minutes / 60; - const days = hours / 24; - - if (days >= 1) { - return days % 1 === 0 - ? { days: `${parseInt(days)}` } - : { days: `${days.toFixed(1)}` }; - } else { - return hours % 1 === 0 - ? { hours: `${parseInt(hours)}` } - : { hours: `${hours.toFixed(1)}` }; - } -} diff --git a/pages/id/search/[param].js b/pages/id/search/[param].js deleted file mode 100644 index 43f419c..0000000 --- a/pages/id/search/[param].js +++ /dev/null @@ -1,491 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { AnimatePresence, motion as m } from "framer-motion"; -import Skeleton from "react-loading-skeleton"; -import { useRouter } from "next/router"; -import Link from "next/link"; -import Navbar from "../../../components/navbar"; -import Head from "next/head"; -import Footer from "../../../components/footer"; - -import Image from "next/image"; -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { aniAdvanceSearch } from "../../../lib/anilist/aniAdvanceSearch"; - -const genre = [ - "Action", - "Adventure", - "Comedy", - "Drama", - "Ecchi", - "Fantasy", - "Horror", - "Mahou Shoujo", - "Mecha", - "Music", - "Mystery", - "Psychological", - "Romance", - "Sci-Fi", - "Slice of Life", - "Sports", - "Supernatural", - "Thriller", -]; - -const types = ["ANIME", "MANGA"]; - -const sorts = [ - { name: "Title", value: "TITLE_ROMAJI" }, - { name: "Popularity", value: "POPULARITY_DESC" }, - { name: "Trending", value: "TRENDING_DESC" }, - { name: "Favourites", value: "FAVOURITES_DESC" }, - { name: "Average Score", value: "SCORE_DESC" }, - { name: "Date Added", value: "ID_DESC" }, - { name: "Release Date", value: "START_DATE_DESC" }, -]; - -export default function Card() { - const router = useRouter(); - - const [data, setData] = useState(); - const [loading, setLoading] = useState(true); - - let hasil = null; - let tipe = "ANIME"; - let s = undefined; - let y = NaN; - let gr = undefined; - - const query = router.query; - gr = query.genres; - - if (query.param !== "anime" && query.param !== "manga") { - hasil = query.param; - } else if (query.param === "anime") { - hasil = null; - tipe = "ANIME"; - if ( - query.season !== "WINTER" && - query.season !== "SPRING" && - query.season !== "SUMMER" && - query.season !== "FALL" - ) { - s = undefined; - y = NaN; - } else { - s = query.season; - y = parseInt(query.seasonYear); - } - } else if (query.param === "manga") { - hasil = null; - tipe = "MANGA"; - if ( - query.season !== "WINTER" && - query.season !== "SPRING" && - query.season !== "SUMMER" && - query.season !== "FALL" - ) { - s = undefined; - y = NaN; - } else { - s = query.season; - y = parseInt(query.seasonYear); - } - } - - // console.log(tags); - - const [search, setQuery] = useState(hasil); - const [type, setSelectedType] = useState(tipe); - // const [genres, setSelectedGenre] = useState(); - const [sort, setSelectedSort] = useState(); - - const [isVisible, setIsVisible] = useState(false); - - const inputRef = useRef(null); - - const [page, setPage] = useState(1); - const [nextPage, setNextPage] = useState(true); - - async function advance() { - setLoading(true); - const data = await aniAdvanceSearch({ - search: search, - type: type, - genres: gr, - page: page, - sort: sort, - season: s, - seasonYear: y, - }); - if (data.media.length === 0) { - setNextPage(false); - } else if (data !== null && page > 1) { - setData((prevData) => { - return [...(prevData ?? []), ...data.media]; - }); - setNextPage(data.pageInfo.hasNextPage); - } else { - setData(data.media); - } - setNextPage(data.pageInfo.hasNextPage); - setLoading(false); - } - - useEffect(() => { - setData(null); - setPage(1); - setNextPage(true); - advance(); - }, [search, type, sort, s, y, gr]); - - useEffect(() => { - advance(); - }, [page]); - - useEffect(() => { - function handleScroll() { - if (page > 10 || !nextPage) { - window.removeEventListener("scroll", handleScroll); - return; - } - - if ( - window.innerHeight + window.pageYOffset >= - document.body.offsetHeight - 3 - ) { - setPage((prevPage) => prevPage + 1); - } - } - - window.addEventListener("scroll", handleScroll); - - return () => window.removeEventListener("scroll", handleScroll); - }, [page, nextPage]); - - const handleKeyDown = async (event) => { - if (event.key === "Enter") { - event.preventDefault(); - const inputValue = event.target.value; - if (inputValue === "") { - setQuery(null); - } else { - setQuery(inputValue); - } - } - }; - - function trash() { - setQuery(null); - inputRef.current.value = ""; - // setSelectedGenre(null); - setSelectedSort(["POPULARITY_DESC"]); - router.push(`/search/${tipe.toLocaleLowerCase()}`); - } - - function handleVisible() { - setIsVisible(!isVisible); - } - - function handleTipe(e) { - setSelectedType(e.target.value); - router.push(`/search/${e.target.value.toLowerCase()}`); - } - - // ); - - return ( - <> - <Head> - <title>Moopa - search</title> - <link rel="icon" href="/c.svg" /> - </Head> - <div className="bg-primary"> - <Navbar /> - <div className="min-h-screen mt-10 mb-14 text-white items-center gap-5 xl:gap-0 flex flex-col"> - <div className="w-screen px-10 xl:w-[80%] xl:h-[10rem] flex text-center xl:items-end xl:pb-10 justify-center lg:gap-7 xl:gap-10 gap-3 font-karla font-light"> - <div className="text-start"> - <h1 className="font-bold xl:pb-5 pb-3 hidden lg:block text-md pl-1 font-outfit"> - TITLE - </h1> - <input - className="xl:w-[297px] md:w-[297px] lg:w-[230px] xl:h-[46px] h-[35px] xxs:w-[230px] xs:w-[280px] bg-secondary rounded-[10px] font-karla font-light text-[#ffffff89] text-center" - placeholder="search here..." - type="text" - onKeyDown={handleKeyDown} - ref={inputRef} - /> - </div> - - {/* TYPE */} - <div className="hidden lg:block text-start"> - <h1 className="font-bold xl:pb-5 pb-3 text-md pl-1 font-outfit"> - TYPE - </h1> - <div className="relative"> - <select - className="xl:w-[297px] xl:h-[46px] lg:h-[35px] lg:w-[230px] bg-secondary rounded-[10px] justify-between flex items-center text-center appearance-none" - value={type} - onChange={(e) => handleTipe(e)} - > - {types.map((option) => ( - <option key={option} value={option}> - {option} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - {/* SORT */} - <div className="hidden lg:block text-start"> - <h1 className="font-bold xl:pb-5 lg:pb-3 text-md pl-1 font-outfit"> - SORT - </h1> - <div className="relative"> - <select - className="xl:w-[297px] xl:h-[46px] lg:h-[35px] lg:w-[230px] bg-secondary rounded-[10px] flex items-center text-center appearance-none" - onChange={(e) => { - setSelectedSort(e.target.value); - setData(null); - }} - > - <option value={["POPULARITY_DESC"]}>Sort By</option> - {sorts.map((sort) => ( - <option key={sort.value} value={sort.value}> - {sort.name} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - {/* OPTIONS */} - <div className="flex lg:gap-7 text-center gap-3 items-end"> - <div - className="xl:w-[73px] w-[50px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] justify-center flex items-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 group" - onClick={handleVisible} - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" - /> - </svg> - </div> - - {/* TRASH ICON */} - <div - className="xl:w-[73px] w-[50px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] justify-center flex items-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 group" - onClick={trash} - > - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" - /> - </svg> - </div> - </div> - </div> - - <div className="w-screen xl:w-[64%] flex xl:justify-end xl:pl-0"> - <AnimatePresence> - {isVisible && ( - <m.div - key="imagine" - initial={{ opacity: 0, y: -10 }} - animate={{ opacity: 1, y: 0 }} - exit={{ opacity: 0, y: -10 }} - className="xl:pb-16" - > - <div className="text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col pb-5 lg:pb-0 "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - GENRE - </h1> - <div className="relative"> - <select - className="w-[195px] xl:w-[297px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - onChange={(e) => { - // setSelectedGenre( - // e.target.value === "undefined" - // ? undefined - // : e.target.value - // ); - router.push( - `/search/${tipe.toLocaleLowerCase()}/?genres=${ - e.target.value - }` - ); - }} - > - <option value="undefined">Select a Genre</option> - {genre.map((option) => { - return ( - <option key={option} value={option}> - {option} - </option> - ); - })} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - <div className="xl:hidden text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col pb-5 "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - TYPE - </h1> - <div className="relative"> - <select - className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - value={type} - onChange={(e) => setSelectedType(e.target.value)} - > - {types.map((option) => ( - <option key={option} value={option}> - {option} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - <div className="xl:hidden text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - SORT - </h1> - <div className="relative"> - <select - className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - onChange={(e) => { - setSelectedSort(e.target.value); - }} - > - <option value={["POPULARITY_DESC"]}>Sort By</option> - {sorts.map((sort) => ( - <option key={sort.value} value={sort.value}> - {sort.name} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - </m.div> - )} - </AnimatePresence> - </div> - {gr && ( - <div className="lg:w-[70%] px-5 lg:px-4 w-screen lg:mb-6"> - <h1 className="font-bold text-[25px] font-karla"> - Looking for : {gr} - </h1> - </div> - )} - <div className="flex flex-col gap-14 items-center"> - <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> - )} - {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} - > - <Link - href={ - anime.format === "MANGA" || anime.format === "NOVEL" - ? `/manga/detail/id?aniId=${anime.id}&aniTitle=${anime.title.userPreferred}` - : `/en/anime/${anime.id}` - } - className="" - > - <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} - /> - </Link> - <Link href={`/en/anime/${anime.id}`}> - <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>} ·{" "} - {anime.status || <p>-</p>} ·{" "} - {anime.episodes || 0} Episodes - </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 }} - > - <Skeleton className="h-[192px] w-[135px] xl:h-[265px] xl:w-[185px]" /> - <Skeleton width={110} height={30} /> - </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> - </div> - </div> - <Footer /> - </div> - </> - ); -} |