diff options
Diffstat (limited to 'pages')
| -rw-r--r-- | pages/about.js | 3 | ||||
| -rw-r--r-- | pages/anime/[...id].js | 166 | ||||
| -rw-r--r-- | pages/anime/watch/[...info].js | 76 | ||||
| -rw-r--r-- | pages/api/get-media.js | 78 | ||||
| -rw-r--r-- | pages/api/get-user.js | 4 | ||||
| -rw-r--r-- | pages/api/getUser.js | 20 | ||||
| -rw-r--r-- | pages/index.js | 371 | ||||
| -rw-r--r-- | pages/profile/[user].js | 366 | ||||
| -rw-r--r-- | pages/search.js | 110 | ||||
| -rw-r--r-- | pages/test.js | 236 | ||||
| -rw-r--r-- | pages/testing.js | 76 |
11 files changed, 903 insertions, 603 deletions
diff --git a/pages/about.js b/pages/about.js index 24f04f3..7255401 100644 --- a/pages/about.js +++ b/pages/about.js @@ -1,10 +1,7 @@ import Head from "next/head"; import Layout from "../components/layout"; -import UnderConstruction from "../components/underConst"; export default function About() { - const clientId = process.env.ANILIST_CLIENT_ID; - return ( <> <Head> diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js index 81e5706..01ed629 100644 --- a/pages/anime/[...id].js +++ b/pages/anime/[...id].js @@ -1,3 +1,5 @@ +const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; + import React, { useEffect, useState } from "react"; import { META } from "@consumet/extensions"; @@ -8,28 +10,26 @@ import Head from "next/head"; import { closestMatch } from "closest-match"; import Content from "../../components/hero/content"; -import { useSession } from "next-auth/react"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "../api/auth/[...nextauth]"; export default function Himitsu({ info, - slicedDesc, color, episodeList, episode1, - judul, subIndo, epIndo, + sessions, + progress, + status, + lastPlayed, }) { - const [isLoading, setIsloading] = useState(false); const [showText, setShowtext] = useState(false); const [load, setLoad] = useState(true); const [Lang, setLang] = useState(true); const [showAll, setShowAll] = useState(false); - const { data: session } = useSession(); - - const [lastPlayed, setLastPlayed] = useState(null); - const [user, setUser] = useState(null); const episode = episodeList; const epi1 = episode1; @@ -44,21 +44,6 @@ export default function Himitsu({ } useEffect(() => { - async function userData() { - setLoad(false); - if (!session) return; - setLoad(true); - const res = await fetch(`/api/get-user?userName=${session?.user.name}`); - const data = await res.json(); - setLastPlayed( - data?.recentWatch.filter( - (item) => item.title.romaji === info.title.romaji - )[0]?.episode - ); - setUser(data); - setLoad(false); - } - function getBrightness(color) { const rgb = color.match(/\d+/g); return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; @@ -80,12 +65,8 @@ export default function Himitsu({ setTextColor(element); }); - userData(); - }, [color, session]); - - if (!info) { - return; - } + setLoad(false); + }, [color, sessions, info.id]); let episodeIndo = null; if (epIndo < 17) { @@ -95,11 +76,11 @@ export default function Himitsu({ } async function handleUpdate(data) { - if (!session) return; + if (!sessions) return; const res = await fetch("/api/update-user", { method: "POST", body: JSON.stringify({ - name: session?.user.name, + name: sessions?.user.name, newData: { recentWatch: data, }, @@ -111,6 +92,8 @@ export default function Himitsu({ console.log(res.status); } + // console.log(lastPlayed); + return ( <> <Head> @@ -127,12 +110,10 @@ export default function Himitsu({ <img // ref={ref} src={info.cover || info.image} - className="md:h-[300px] h-[420px] w-screen object-cover brightness-50" + className="md:h-[300px] h-[420px] w-screen object-cover brightness-[60%]" /> </div> - {isLoading ? ( - <p>Loading cuy sabar...</p> - ) : info ? ( + {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"> @@ -141,6 +122,7 @@ export default function Himitsu({ {info.image && ( <> <div + key={info.id} style={{ backgroundImage: `url(${info.image})`, height: "100%", @@ -157,8 +139,8 @@ export default function Himitsu({ {/* MOBILE */} <div className="flex w-full flex-col gap-5 lg:hidden "> - <h1 className="shrink-0 text-2xl font-semibold"> - {judul} + <h1 className="shrink-0 text-2xl font-semibold line-clamp-2"> + {info.title.romaji || info.title.english} </h1> <div className="flex w-[90%] flex-col gap-1"> <div className="flex gap-2"> @@ -278,7 +260,7 @@ export default function Himitsu({ </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`} + className={`hidden h-[140px] transition-all duration-300 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-md overflow-y-scroll hover:scrollbar-thumb-[#2e2f37] lg:block`} > <p dangerouslySetInnerHTML={{ __html: info.description }} @@ -286,11 +268,12 @@ export default function Himitsu({ /> </div> <div className="lg:hidden"> - <div + <p + className={`${showText ? "" : "line-clamp-3"}`} dangerouslySetInnerHTML={{ - __html: showText ? info.description : slicedDesc, + __html: info.description, }} - ></div> + /> <button onClick={() => setShowtext(!showText)} className="font-rama font-bold text-white" @@ -385,15 +368,15 @@ export default function Himitsu({ </div> <div className="z-20 flex flex-col gap-10 p-3 lg:p-0"> - <div className="flex items-center gap-10"> + <div className="flex items-center md:gap-10 gap-7"> <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]` + ? `w-16 p-2 rounded-l-md bg-secondary text-action shadow-action` + : `w-16 p-2 rounded-l-md bg-[#17171b] text-[#404040]` } > EN @@ -412,8 +395,18 @@ export default function Himitsu({ ID </button> </div> + {status && ( + <> + <div className="font-karla relative group flex justify-center"> + {status} + <span className="absolute bottom-8 shadow-lg invisible group-hover:visible transition-all opacity-0 group-hover:opacity-100 font-karla font-light bg-secondary p-1 px-2 rounded-lg"> + status + </span> + </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]"> + <div className="flex h-[640px] flex-col gap-5 scrollbar-thin scrollbar-thumb-[#1b1c21] scrollbar-thumb-rounded-full overflow-y-scroll hover:scrollbar-thumb-[#2e2f37]"> {load ? ( <p>Loading...</p> ) : episode && Lang ? ( @@ -421,9 +414,8 @@ export default function Himitsu({ const item = lastPlayed?.find( (item) => item.id === episode.id ); - console.log(item); return ( - <div key={index} className="flex flex-col gap-3"> + <div key={index} className="flex flex-col gap-3 px-2"> <Link onClick={() => handleUpdate({ @@ -444,14 +436,18 @@ export default function Himitsu({ item ? `${item.time}` : "" }`} className={`text-start text-xl ${ - item ? "text-[#414141]" : "text-white" + episode.number <= progress + ? "text-[#5f5f5f]" + : "text-white" }`} > <p>Episode {episode.number}</p> {episode.title && ( <p className={`text-[14px] ${ - item ? "text-[#414141]" : "text-[#b1b1b1]" + episode.number <= progress + ? "text-[#5f5f5f]" + : "text-[#b1b1b1]" } italic`} > "{episode.title}" @@ -531,11 +527,13 @@ export default function Himitsu({ ); } -export const getServerSideProps = async (context) => { +export async function getServerSideProps(context) { context.res.setHeader( "Cache-Control", "public, s-maxage=10, stale-while-revalidate=59" ); + const session = await getServerSession(context.req, context.res, authOptions); + const { id } = context.query; if (!id) { return { @@ -552,6 +550,12 @@ export const getServerSideProps = async (context) => { provider.fetchEpisodesListById(id[0]), ]); + if (!info) { + return { + notFound: true, + }; + } + let episodeList = episodes; if (episodes.length === 0) { const res = await fetch( @@ -608,16 +612,58 @@ export const getServerSideProps = async (context) => { epis = dataInf.episode; } - const desc = info.description.slice(0, 150) + "..."; + let progress = null; + let status = null; + let lastPlayed = null; + + if (session) { + const res = await fetch(`${baseUrl}/api/get-media`, { + method: "POST", + body: JSON.stringify({ + username: session?.user.name, + }), + headers: { + "Content-Type": "application/json", + }, + }); + + const resp = await fetch( + `${baseUrl}/api/get-user?userName=${session?.user.name}` + ); + const data = await resp.json(); + + lastPlayed = data?.recentWatch.filter( + (item) => item.title.romaji === info.title.romaji + )[0]?.episode; + + const prog = await res.json(); + + const gat = prog.lists.map((item) => item.entries); + const git = gat.map((item) => + item.find((item) => item.media.id === parseInt(info.id)) + ); + const gut = git?.find((item) => item?.media.id === parseInt(info.id)); + + if (gut) { + progress = gut?.progress; + if (gut.status === "CURRENT") { + status = "Watching"; + } else if (gut.status === "PLANNING") { + status = "Planned to watch"; + } else if (gut.status === "COMPLETED") { + status = "Completed"; + } else if (gut.status === "DROPPED") { + status = "Dropped"; + } else if (gut.status === "PAUSED") { + status = "Paused"; + } + } + } + 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: { @@ -627,13 +673,15 @@ export const getServerSideProps = async (context) => { userPreferred: title, }, }, - slicedDesc: desc, color, episodeList, episode1: epi1, - judul, subIndo: slug, epIndo: epis, + sessions: session, + progress: progress || null, + status: status, + lastPlayed: lastPlayed || null, }, }; -}; +} diff --git a/pages/anime/watch/[...info].js b/pages/anime/watch/[...info].js index 552102c..756e0c1 100644 --- a/pages/anime/watch/[...info].js +++ b/pages/anime/watch/[...info].js @@ -1,3 +1,5 @@ +const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; + import Layout from "../../../components/layout"; // import { data } from "../../../lib/testData"; // import { aniData } from "../../../lib/infoData"; @@ -11,26 +13,30 @@ import Modal from "../../../components/modal"; import { useNotification } from "../../../lib/useNotify"; -import { useSession, signIn, signOut } from "next-auth/react"; +import { signIn, signOut } from "next-auth/react"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "../../api/auth/[...nextauth]"; + import AniList from "../../../components/media/aniList"; -import { AnimatePresence, motion as m } from "framer-motion"; -import Navbar from "../../../components/navbar"; import { Navigasi } from "../.."; -export default function Info({ info }) { - const { data: session, status } = useSession(); +export default function Info({ info, sessions, statusWatch }) { const title = info.aniData.title.romaji || info.aniData.title.english; const data = info.aniData; const fallback = info.epiFallback; const { Notification: NotificationComponent, show } = useNotification(); - // console.log(session); - - const playingEpisode = data.episodes + let playingEpisode = data.episodes .filter((item) => item.id == info.id) .map((item) => item.number); + if (playingEpisode == 0) { + playingEpisode = fallback + .filter((item) => item.id == info.id) + .map((item) => item.number); + } + const [open, setOpen] = useState(false); const [aniStatus, setAniStatus] = useState(""); const [aniProgress, setAniProgress] = useState(parseInt(playingEpisode)); @@ -56,12 +62,6 @@ export default function Info({ info }) { .filter((item) => item.id == info.id) .map((item) => item.title); - if (status === "loading") { - return <p>Loading...</p>; - } - - console.log(); - return ( <> <Head> @@ -78,7 +78,7 @@ export default function Info({ info }) { <h1 className="text-md font-extrabold font-karla"> Save this Anime to Your List </h1> - {!session && ( + {!sessions && ( <button className="flex items-center bg-[#3a3a3a] mt-4 rounded-md text-white p-1" onClick={() => signIn("AniListProvider")} @@ -91,7 +91,7 @@ export default function Info({ info }) { </div> </button> )} - {session && ( + {sessions && ( <> <form onSubmit={handleSubmit} @@ -165,8 +165,9 @@ export default function Info({ info }) { titles={title} id={info.id} progress={parseInt(playingEpisode)} - session={session} + session={sessions} aniId={parseInt(data.id)} + stats={statusWatch} /> </div> <div> @@ -378,6 +379,8 @@ export default function Info({ info }) { } export async function getServerSideProps(context) { + const session = await getServerSession(context.req, context.res, authOptions); + const { info } = context.query; if (!info) { return { @@ -418,6 +421,43 @@ export async function getServerSideProps(context) { } } + const playingEpisode = aniData.episodes + .filter((item) => item.id == id) + .map((item) => item.number); + + const resp = await fetch(`${baseUrl}/api/get-media`, { + method: "POST", + body: JSON.stringify({ + username: session?.user.name, + }), + headers: { + "Content-Type": "application/json", + }, + }); + + const prog = await resp.json(); + + 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)); + + let statusWatch = "CURRENT"; + + if (gut?.status === "COMPLETED") { + statusWatch = "REPEATING"; + } else if ( + gut?.status === "REPEATING" && + gut?.media?.episodes === parseInt(playingEpisode) + ) { + statusWatch = "COMPLETED"; + } else if (gut?.status === "REPEATING") { + statusWatch = "REPEATING"; + } else if (aniData.totalEpisodes === parseInt(playingEpisode)) { + statusWatch = "COMPLETED"; + } + return { props: { info: { @@ -427,6 +467,8 @@ export async function getServerSideProps(context) { aniData, epiFallback, }, + sessions: session, + statusWatch: statusWatch, }, }; } diff --git a/pages/api/get-media.js b/pages/api/get-media.js new file mode 100644 index 0000000..7c45d03 --- /dev/null +++ b/pages/api/get-media.js @@ -0,0 +1,78 @@ +// pages/api/anime-media-list.js + +export default async function handler(req, res) { + const { username, status } = req.body; + + try { + 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, + status, + }, + }), + }); + + const data = await response.json(); + res.status(200).json(data.data.MediaListCollection); + } catch (error) { + console.error(error); + res.status(500).json({ message: "Internal server error" }); + } +} diff --git a/pages/api/get-user.js b/pages/api/get-user.js index 7df10a6..36bc974 100644 --- a/pages/api/get-user.js +++ b/pages/api/get-user.js @@ -7,7 +7,9 @@ export async function getUser(userName) { const collection = db.collection("users"); const user = await collection.findOne({ name: userName }); - user._id = String(user._id); + if (user && user._id) { + user._id = String(user._id); + } return user; } diff --git a/pages/api/getUser.js b/pages/api/getUser.js deleted file mode 100644 index 7df10a6..0000000 --- a/pages/api/getUser.js +++ /dev/null @@ -1,20 +0,0 @@ -import clientPromise from "../../lib/mongodb"; - -export async function getUser(userName) { - const client = await clientPromise; - const db = client.db("authbase"); - - const collection = db.collection("users"); - const user = await collection.findOne({ name: userName }); - - user._id = String(user._id); - - return user; -} - -export default async function handler(req, res) { - const { userName } = req.query; - const user = await getUser(userName); - - res.status(200).json(user); -} diff --git a/pages/index.js b/pages/index.js index a9aca07..f0b6ce4 100644 --- a/pages/index.js +++ b/pages/index.js @@ -10,11 +10,13 @@ import { useRouter } from "next/router"; import { motion } from "framer-motion"; -import { useSession, signIn } from "next-auth/react"; +import { useSession, signIn, signOut } from "next-auth/react"; import { useAniList } from "../lib/useAnilist"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "./api/auth/[...nextauth]"; export function Navigasi() { - const { data: session, status } = useSession(); + const { data: sessions, status } = useSession(); const router = useRouter(); @@ -54,7 +56,7 @@ export function Navigasi() { <li>Loading...</li> ) : ( <> - {!session && ( + {!sessions && ( <li> <button onClick={() => signIn("AniListProvider")} @@ -64,10 +66,12 @@ export function Navigasi() { </button> </li> )} - {session && ( + {sessions && ( <li className="text-center"> - {/* <div className="p-2"><img src={session?.user.image.large} alt="imagine" /></div> */} - My List + {/* <div className="p-2"><img src={sessions?.user.image.large} alt="imagine" /></div> */} + <Link href={`/profile/${sessions?.user.name}`}> + My List + </Link> </li> )} </> @@ -93,15 +97,14 @@ export function Navigasi() { ); } -export default function Home({ detail, populars }) { - const { data: session, status } = useSession(); - const { media } = useAniList(session); +export default function Home({ detail, populars, sessions }) { + const { media } = useAniList(sessions, { stats: "CURRENT" }); const [isVisible, setIsVisible] = useState(false); - const [recently, setRecently] = useState(null); const [plan, setPlan] = useState(null); const [user, setUser] = useState(null); const [array, setArray] = useState([]); + const [fade, setFade] = useState(false); const popular = populars?.data; const data = detail.data[0]; @@ -109,42 +112,48 @@ export default function Home({ detail, populars }) { const handleShowClick = () => { setIsVisible(true); + setFade(true); }; const handleHideClick = () => { setIsVisible(false); + setFade(false); }; - // const reversed = user?.recentWatch.reverse(); - // console.log(plan); - useEffect(() => { async function userData() { - if (!session) return; - const res = await fetch(`/api/get-user?userName=${session?.user.name}`); + if (!sessions) return; + const res = await fetch(`/api/get-user?userName=${sessions?.user.name}`); const data = await res.json(); const getMedia = - media.filter((item) => item.status === "PAUSED")[0] || null; + media.filter((item) => item.status === "CURRENT")[0] || null; const plan = getMedia?.entries .map(({ media }) => media) .filter((media) => media); - setPlan(plan); - setArray(data?.recentWatch.reverse()); - setUser(data); - } - function fetchData() { - const recent = JSON.parse(localStorage.getItem("recentWatch")); - if (recent) { - setRecently(recent); + const get = media.flatMap((item) => item.entries); + const newArray = []; + + get.forEach((item) => { + if (!newArray.some((element) => element.id === item.id)) { + newArray.push(item); + } + }); + + const getlog = newArray + .map(({ media }) => media) + .filter((media) => media); + + if (plan) { + setPlan(plan.reverse()); } + + setArray(data?.recentWatch?.reverse()); + setUser(data); } userData(); - fetchData(); - }, [session]); - - // console.log(user?.recentWatch.reverse()); + }, [sessions, media]); return ( <> @@ -172,7 +181,7 @@ export default function Home({ detail, populars }) { {!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-[#101925] shadow-menu md:hidden" + 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 md:hidden" id="bars" > <svg @@ -189,162 +198,166 @@ export default function Home({ detail, populars }) { </svg> </button> )} + </div> - {/* Mobile Menu */} - <div className="md:hidden"> - {isVisible && ( - <button - type="button" - onClick={() => signIn("AniListProvider")} - className="fixed bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#101925]" - > - {!session && ( - <div> + {/* Mobile Menu */} + <div className={`transition-all duration-150 subpixel-antialiased z-50`}> + {isVisible && sessions && ( + <Link + href={`/profile/${sessions?.user.name}`} + className="fixed md: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 md:hidden"> + <div className="grid grid-cols-4 place-items-center gap-6"> + <button className="group flex flex-col items-center"> + <Link href="/" className=""> <svg xmlns="http://www.w3.org/2000/svg" - width="32" - height="26" fill="none" - viewBox="0 0 33 26" + viewBox="0 0 24 24" + strokeWidth={1.5} + stroke="currentColor" + className="w-6 h-6 group-hover:stroke-action" > <path - fill="#fff" - d="M15.167 24.638v-1.732h8.942c.209 0 .4-.087.573-.26.174-.174.26-.365.26-.573V3.28c0-.209-.086-.4-.26-.573-.173-.174-.364-.26-.573-.26h-8.942V.714h8.942c.707 0 1.311.25 1.813.753.502.502.753 1.106.753 1.813v18.792c0 .706-.25 1.31-.753 1.812a2.471 2.471 0 01-1.813.753h-8.942zm-1.532-6.536l-1.303-1.26 3.32-3.322H3.86v-1.732h11.766l-3.321-3.321 1.321-1.233 5.448 5.448-5.438 5.42z" - ></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> - </div> - )} - {session && ( - <img - src={session?.user.image.large} - alt="user avatar" - className="object-cover w-[60px] h-[60px] rounded-full" - /> - )} - </button> - )} - {isVisible && ( - <div className="fixed bottom-[25px] right-[15px] z-50 flex h-[66px] w-[255px] items-center justify-center gap-8 rounded-[10px] text-[11px] bg-[#101925] shadow-menu md:hidden"> - <div className="flex gap-7"> - <button className="group flex flex-col items-center"> - <Link href="/" className=""> + </Link> + <Link + href="/" + className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" + > + home + </Link> + </button> + <button className="group flex flex-col items-center"> + <Link href="/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="/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="/search"> <svg - width="28" - height="24" - viewBox="0 0 28 24" - className=" group-hover:fill-cyan-700 fill-white" - fill="none" 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" > - <g clipPath="url(#clip0_224_286)"> - <path d="M14.0937 -0.571411C14.0937 -0.571411 5.91783 6.54859 1.34879 10.4046C1.08049 10.6499 0.876953 11.0073 0.876953 11.4286C0.876953 12.1659 1.46774 12.7619 2.19863 12.7619H4.84199V22.0953C4.84199 22.8326 5.43278 23.4286 6.16367 23.4286H10.1287C10.8596 23.4286 11.4504 22.8313 11.4504 22.0953V16.7619H16.7371V22.0953C16.7371 22.8313 17.3279 23.4286 18.0588 23.4286H22.0238C22.7547 23.4286 23.3455 22.8326 23.3455 22.0953V12.7619H25.9888C26.7197 12.7619 27.3105 12.1659 27.3105 11.4286C27.3105 11.0073 27.107 10.6499 26.8043 10.4046C22.267 6.54859 14.0937 -0.571411 14.0937 -0.571411Z" /> - </g> - <defs> - <clipPath id="clip0_224_286"> - <rect - width="27" - height="24" - fill="white" - transform="translate(0.5)" - /> - </clipPath> - </defs> + <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> - <Link - href="/" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-cyan-700" - > - home - </Link> - </button> - <button className="group flex flex-col items-center"> - <Link href="/about"> + </div> + <Link + href="/search" + 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 - width="27" - height="25" - viewBox="0 0 27 25" - className=" group-hover:fill-cyan-700 fill-white" - fill="none" xmlns="http://www.w3.org/2000/svg" + viewBox="0 96 960 960" + className="group-hover:fill-action w-6 h-6 fill-txt" > - <g clipPath="url(#clip0_224_292)"> - <path d="M21.3402 0.5H5.65974C4.31427 0.500087 3.02394 0.996857 2.07261 1.88103C1.12127 2.7652 0.586852 3.96435 0.586914 5.21469V19.7853C0.586852 21.0356 1.12127 22.2348 2.07261 23.119C3.02394 24.0031 4.31427 24.4999 5.65974 24.5H21.3402C22.6856 24.4999 23.976 24.0031 24.9273 23.119C25.8786 22.2348 26.4131 21.0356 26.413 19.7853V5.21469C26.4131 3.96435 25.8786 2.7652 24.9273 1.88103C23.976 0.996857 22.6856 0.500087 21.3402 0.5ZM13.5 4.93182C13.8482 4.93182 14.1887 5.02779 14.4782 5.20759C14.7678 5.3874 14.9935 5.64297 15.1268 5.94197C15.2601 6.24098 15.2949 6.57 15.227 6.88742C15.159 7.20484 14.9913 7.49642 14.7451 7.72527C14.4988 7.95412 14.1851 8.10996 13.8435 8.1731C13.5019 8.23624 13.1479 8.20384 12.8261 8.07999C12.5043 7.95613 12.2293 7.7464 12.0358 7.4773C11.8424 7.2082 11.7391 6.89182 11.7391 6.56818C11.7391 6.13419 11.9246 5.71798 12.2548 5.4111C12.5851 5.10422 13.0329 4.93182 13.5 4.93182ZM15.9212 20.1364H11.2255C10.9142 20.1364 10.6156 20.0214 10.3954 19.8168C10.1753 19.6123 10.0516 19.3348 10.0516 19.0455C10.0516 18.7561 10.1753 18.4787 10.3954 18.2741C10.6156 18.0695 10.9142 17.9545 11.2255 17.9545H12.326V11.4091H11.2255C10.9142 11.4091 10.6156 11.2942 10.3954 11.0896C10.1753 10.885 10.0516 10.6075 10.0516 10.3182C10.0516 10.0289 10.1753 9.75138 10.3954 9.54679C10.6156 9.34221 10.9142 9.22727 11.2255 9.22727H14.6739V17.9545H15.9212C16.2325 17.9545 16.5311 18.0695 16.7512 18.2741C16.9714 18.4787 17.0951 18.7561 17.0951 19.0455C17.0951 19.3348 16.9714 19.6123 16.7512 19.8168C16.5311 20.0214 16.2325 20.1364 15.9212 20.1364Z" /> - </g> - <defs> - <clipPath id="clip0_224_292"> - <rect - width="27" - height="24" - fill="white" - transform="translate(0 0.5)" - /> - </clipPath> - </defs> + <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> - </Link> - <Link - href="/about" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-cyan-700" - > - about - </Link> + </div> + <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"> + logout + </h1> </button> - <button className="group flex gap-[1.5px] flex-col items-center "> + ) : ( + <button + onClick={() => signIn("AniListProvider")} + className="group flex gap-[1.5px] flex-col items-center " + > <div> - <Link href="/search"> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="currentColor" - className="group-hover:fill-cyan-700 fill-white w-6 h-6" - > - <path - fillRule="evenodd" - d="M10.5 3.75a6.75 6.75 0 100 13.5 6.75 6.75 0 000-13.5zM2.25 10.5a8.25 8.25 0 1114.59 5.28l4.69 4.69a.75.75 0 11-1.06 1.06l-4.69-4.69A8.25 8.25 0 012.25 10.5z" - clipRule="evenodd" - /> - </svg> - </Link> + <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> - <Link - href="/search" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-cyan-700" - > - search - </Link> + <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> + <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] "> @@ -389,10 +402,19 @@ export default function Home({ detail, populars }) { </div> </div> </div> - {session && ( - <div className="w-screen flex md:justify-center mx-3 md:mx-0 mt-10 md:mt-0"> - <div className="md:w-[86%] md:text-3xl text-2xl font-bold font-karla"> - {greeting}, {session?.user.name} + {sessions && ( + <div className="flex items-center mx-3 md:mx-0 mt-10 md:mt-0"> + <div className="md:text-4xl md:mx-32 flex items-center gap-3 text-2xl font-bold font-karla"> + {greeting},<h1 className="md:hidden">{sessions?.user.name}</h1> + <button + onClick={() => signOut()} + className="hidden text-center relative md: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> </div> </div> )} @@ -405,7 +427,7 @@ export default function Home({ detail, populars }) { transition={{ duration: 0.5, staggerChildren: 0.2 }} // Add staggerChildren prop > {/* SECTION 1 */} - {session && user?.recentWatch && ( + {sessions && user?.recentWatch && ( <motion.div // Add motion.div to each child component key="recentlyWatched" initial={{ y: 20, opacity: 0 }} @@ -421,7 +443,7 @@ export default function Home({ detail, populars }) { </motion.div> )} - {session && plan && ( + {sessions && plan && ( <motion.div // Add motion.div to each child component key="plannedAnime" initial={{ y: 20, opacity: 0 }} @@ -431,7 +453,7 @@ export default function Home({ detail, populars }) { > <Content ids="plannedAnime" - section="Start Watching Again" + section="Your Watch List" data={plan} /> </motion.div> @@ -478,7 +500,9 @@ export default function Home({ detail, populars }) { ); } -export async function getServerSideProps({ req, res }) { +export async function getServerSideProps(context) { + const session = await getServerSession(context.req, context.res, authOptions); + const trendingDetail = await aniListData({ sort: "TRENDING_DESC", page: 1, @@ -494,6 +518,7 @@ export async function getServerSideProps({ req, res }) { genre: genreDetail.props, detail: trendingDetail.props, populars: popularDetail.props, + sessions: session, }, }; } @@ -508,7 +533,7 @@ function getGreeting() { greeting = "Good afternoon"; } else if (time >= 18 && time < 22) { greeting = "Good evening"; - } else if (time >= 22 && time < 5) { + } else if (time >= 22 || time < 5) { greeting = "Good night"; } diff --git a/pages/profile/[user].js b/pages/profile/[user].js new file mode 100644 index 0000000..b40c41a --- /dev/null +++ b/pages/profile/[user].js @@ -0,0 +1,366 @@ +const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; + +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); + }; + // console.log(media); + return ( + <> + <Head> + <title>My Lists</title> + </Head> + <Navbar /> + <div className="w-screen lg:flex justify-between lg: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-5 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) => ( + <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, index) => ( + <tr + key={index + 1} + 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={`/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="/search" + 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 res = await fetch(`${baseUrl}/api/get-media`, { + method: "POST", + body: JSON.stringify({ + username: query.user, + }), + headers: { + "Content-Type": "application/json", + }, + }); + + const get = await res.json(); + 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/search.js b/pages/search.js index e398e9e..99b12d7 100644 --- a/pages/search.js +++ b/pages/search.js @@ -62,16 +62,11 @@ const sorts = [ export default function Card() { const router = useRouter(); - // const { genres } = router.query; - // console.log(genres); const { aniAdvanceSearch } = useAniList(); const [data, setData] = useState(); const [loading, setLoading] = useState(true); - // const [selectedGenre, setSelectedGenre] = useState(null); - // const [selectedType, setSelectedType] = useState(type[0]); - // const [selectedSort, setSelectedSort] = useState(null); const { hasil } = router.query; @@ -85,7 +80,6 @@ export default function Card() { const [isVisible, setIsVisible] = useState(false); - // const [query, setQuery] = useState(hasil || null); const inputRef = useRef(null); async function advance() { @@ -107,32 +101,15 @@ export default function Card() { advance(); }, [search, type, seasonYear, season, genres, perPage, sort]); - // useEffect(() => { - // async function fetchData() { - // setLoading(true); - // try { - // const res = await fetch( - // `https://api.moopa.my.id/meta/anilist/advanced-search?${ - // query ? `query=${query}&` : "" - // }${selectedGenre ? `genres=["${selectedGenre}"]&` : ""}${ - // selectedType ? `type=${selectedType}&` : "" - // }${selectedSort ? `sort=["${selectedSort}"]` : ""}` - // ); - // const data = await res.json(); - // setData(data); - // setLoading(false); - // } catch (e) { - // console.error(e); - // } - // } - // fetchData(); - // }, [query, selectedGenre, selectedType, selectedSort]); - const handleKeyDown = async (event) => { if (event.key === "Enter") { event.preventDefault(); const inputValue = event.target.value; - setQuery(inputValue); + if (inputValue === "") { + setQuery(null); + } else { + setQuery(inputValue); + } } }; @@ -193,7 +170,7 @@ export default function Card() { className="w-[297px] h-[46px] bg-secondary rounded-[10px] flex items-center text-center" onChange={(e) => setSelectedSort(e.target.value)} > - <option value="">Sort By</option> + <option value={["POPULARITY_DESC"]}>Sort By</option> {sorts.map((option) => ( <option key={option} value={option}> {option} @@ -277,7 +254,7 @@ export default function Card() { TYPE </h1> <select - className="w-[195px] h-[35px] bg-[#26272B] rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300" + className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300" value={type} onChange={(e) => setSelectedType(e.target.value)} > @@ -294,11 +271,11 @@ export default function Card() { SORT </h1> <select - className="w-[195px] h-[35px] bg-[#26272B] rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300" + className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300" onChange={(e) => setSelectedSort(e.target.value)} > - <option value="">Sort By</option> - {sort.map((option) => ( + <option value={["POPULARITY_DESC"]}>Sort By</option> + {sorts.map((option) => ( <option key={option} value={option}> {option} </option> @@ -317,55 +294,16 @@ export default function Card() { {loading ? ( <> <SkeletonTheme baseColor="#3B3C41" highlightColor="#4D4E52"> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> - <div - className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> - <Skeleton width={110} height={30} /> - </div> + {[1, 2, 4, 5, 6, 7, 8].map((item) => ( + <div + key={item} + className="flex flex-col w-[115px] xs:w-[140px] lg:w-[228px] gap-5" + style={{ scale: 0.98 }} + > + <Skeleton className="lg:h-[313px] xs:h-[215px] h-[175px]" /> + <Skeleton width={110} height={30} /> + </div> + ))} </SkeletonTheme> </> ) : data && data.media.length === 0 ? ( @@ -390,7 +328,6 @@ export default function Card() { className="" > <div - // className=" bg-[#3B3C41] h-[313px] hover:ring-4 ring-[#ff8a57] transition-all cursor-pointer duration-100 ease-in-out rounded-[10px]" className=" bg-[#3B3C41] lg:h-[313px] xs:h-[215px] h-[175px] hover:scale-105 scale-100 transition-all cursor-pointer duration-200 ease-out rounded-[10px]" style={{ backgroundImage: `url(${anime.coverImage.extraLarge})`, @@ -400,7 +337,12 @@ export default function Card() { /> </Link> <Link href={`/anime/${anime.id}`}> - <h1 className="font-outfit font-bold lg:text-[20px] pt-4 title-overflow"> + <h1 className="font-outfit font-bold lg:text-[20px] 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> diff --git a/pages/test.js b/pages/test.js deleted file mode 100644 index 0db3a17..0000000 --- a/pages/test.js +++ /dev/null @@ -1,236 +0,0 @@ -import { signIn, signOut, useSession } from "next-auth/react"; -import { useEffect, useState } from "react"; -import { useQuery } from "@apollo/client"; -import { GET_MEDIA } from "../queries"; -import StackPlayer from "../components/test/player"; -import Modal from "../components/modal"; - -import { AniData as data } from "../components/test/dataAni"; -import Image from "next/image"; -import { client } from "../lib/apolloClient"; -import Link from "next/link"; -import { useAniList } from "../lib/useAnilist"; - -export default function AniTest() { - const { data: session, status } = useSession(); - const { media, aniAdvanceSearch, markComplete } = useAniList(session); - const [advanceSearch, setAdvanceSearch] = useState(); - - const [search, setSearch] = useState(); - const [type, setType] = useState("ANIME"); - const [seasonYear, setSeasonYear] = useState(); - const [season, setSeason] = useState(); - const [genres, setGenres] = useState(); - const [perPage, setPerPage] = useState(25); - const [sort, setSort] = useState(["POPULARITY_DESC"]); - - // async function handleUpdateMediaEntry(entryId, status, progress, score) { - // try { - // const updatedEntry = await updateMediaEntry( - // entryId, - // status, - // progress, - // score - // ); - // console.log(updatedEntry); - // } catch (error) { - // console.error(error); - // } - // } - - // const userId = session?.user?.id; - // const MediaList = ({ userId }) => { - // const { data, loading, error } = useQuery(GET_MEDIA, { - // variables: { page: 1, userId, type: "ANIME", status: "COMPLETED" }, - // }); - - // if (loading) return <p>Loading...</p>; - // if (error) return <p>Error :(</p>; - - // const { mediaList } = data.Page; - // console.log(mediaList); - // }; - - // const [open, setOpen] = useState(false); - - async function markAsComplete(id) { - const response = await markComplete(id); - console.log(response); - } - - async function advance() { - const data = await aniAdvanceSearch( - search, - type, - seasonYear, - season, - genres, - perPage, - sort - ); - setAdvanceSearch(data); - } - - useEffect(() => { - advance(); - }, [search, type, seasonYear, season, genres, perPage, sort]); - - if (status === "loading") { - return <div>Loading...</div>; - } - - const astatus = "COMPLETED"; - - // const { data } = aniAdvanceSearch({ - // search: "naruto", - // }); - - // console.log(advanceSearch); - - return ( - // <div className="h-[720px] w-[1280px]"> - // <StackPlayer /> - // </div> - <> - {/* <button - className="bg-[#30c168] p-2 rounded-lg m-5 text-black font-semibold font-karla" - onClick={() => setOpen(true)} - > - Start Watching - </button> - <Modal open={open} onClose={() => setOpen(false)}> - <div className="bg-white rounded-md text-black"> - <div className=""> - <Image - src={data.episodes[0].image} - alt="iamge" - width={1000} - height={1000} - className="object-cover rounded-t-md w-[420px] h-[100px]" - /> - </div> - <div>Episode 6</div> - <div>test</div> - </div> - </Modal> */} - {!session && ( - <button onClick={() => signIn("AniListProvider")}> - Sign in with Anilist - </button> - )} - {session && ( - <div> - <button onClick={() => signOut()}> - Sign out ({session.user?.name}) - </button> - <img - src={session.user?.image.large} - className="w-[100px] h-[100px]" - /> - </div> - )} - {media?.length > 0 && ( - <div className="flex-center flex-col gap-5"> - {media.map((item, index) => { - return ( - <div key={index} className="flex-center flex-col gap-5"> - <h2 className="font-bold text-xl font-karla">{item.name}</h2> - <div className="grid grid-cols-4 gap-5"> - {item.entries.map((items, index) => { - return ( - <div key={index}> - <div className="bg-secondary flex h-[120px] w-[420px]"> - <div className="w-[20%] shrink-0"> - <Image - src={items.media.coverImage.large} - alt="image deez nuts" - height={1000} - width={1000} - className="object-cover h-[120px] shrink-0" - /> - </div> - <div className="p-3"> - <h1 className="text-semibold font-karla text-lg line-clamp-3"> - {items.media.title.romaji} - </h1> - <h3 className="text-sm font-karla text-light"> - Episodes {items.progress} - {items.media.episodes} - </h3> - {item.name === "Watching" && ( - <button - onClick={() => markAsComplete(items.media.id)} - > - Mark as Complete - </button> - )} - </div> - </div> - </div> - ); - })} - </div> - </div> - ); - })} - {/* <h2 className="font-bold text-xl font-karla">Paused</h2> - <div className="grid grid-cols-4 gap-5"> - {media[1].entries.map((item, index) => { - return ( - <div key={index} className=""> - <div className="bg-secondary flex h-[120px] w-[420px]"> - <div className="w-[20%] shrink-0"> - <Image - src={item.media.coverImage.large} - alt="image deez nuts" - height={1000} - width={1000} - className="object-cover h-[120px]" - /> - </div> - <div className="p-3"> - <h1 className="text-semibold font-karla text-lg line-clamp-3"> - {item.media.title.romaji} - </h1> - <h3 className="text-sm font-karla text-light"> - Episodes {item.progress} - {item.media.episodes} - </h3> - </div> - </div> - </div> - ); - })} - </div> - <h2 className="font-bold text-xl font-karla">Dropped</h2> - <div className="grid grid-cols-4 gap-5"> - {media[2].entries.map((item, index) => { - return ( - <div key={index} className=""> - <div className="bg-secondary flex h-[120px] w-[420px]"> - <div className="w-[20%] shrink-0"> - <Image - src={item.media.coverImage.large} - alt="image deez nuts" - height={1000} - width={1000} - className="object-cover h-[120px]" - /> - </div> - <div className="p-3"> - <h1 className="text-semibold font-karla text-lg line-clamp-3"> - {item.media.title.romaji} - </h1> - <h3 className="text-sm font-karla text-light"> - Episodes {item.progress} - {item.media.episodes} - </h3> - </div> - </div> - </div> - ); - })} - </div> */} - </div> - )} - <Link href="/">Home</Link> - </> - ); -} diff --git a/pages/testing.js b/pages/testing.js index 7540093..e951635 100644 --- a/pages/testing.js +++ b/pages/testing.js @@ -1,18 +1,34 @@ import { signIn, signOut, useSession } from "next-auth/react"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "./api/auth/[...nextauth]"; +const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; -export default function Testing() { +export default function Testing({ sesi, data, progress, statusWatch }) { const { data: session, status } = useSession(); + // console.log(progress); async function handleUpdate() { - const lastPlayed = { - id: "apahisya", - time: 812989929, - }; - const res = await fetch("/api/watched-episode", { + // const data = ; + const res = await fetch("/api/update-user", { method: "POST", body: JSON.stringify({ - username: session?.user.name, - id: 150672, - newData: lastPlayed, + name: session?.user.name, + newData: { + recentWatch: { + id: parseInt(9280220), + title: { + romaji: "something title here", + }, + description: + "lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quod.", + coverImage: { + extraLarge: "this should be an image url", + }, + episode: { + id: "first-id-yeah", + time: 12344, + }, + }, + }, }), headers: { "Content-Type": "application/json", @@ -23,7 +39,7 @@ export default function Testing() { console.log(res.status); } - console.log(session); + console.log(statusWatch); return ( <div> <button onClick={() => handleUpdate()}>Click for update</button> @@ -34,3 +50,43 @@ export default function Testing() { </div> ); } + +export async function getServerSideProps(context) { + const session = await getServerSession(context.req, context.res, authOptions); + + const res = await fetch(`${baseUrl}/api/get-media`, { + method: "POST", + body: JSON.stringify({ + username: session?.user.name, + }), + headers: { + "Content-Type": "application/json", + }, + }); + + const prog = await res.json(); + + const gat = prog.lists.map((item) => item.entries); + const git = gat.map((item) => item.find((item) => item.media.id === 130003)); + const gut = git.find((item) => item?.media.id === 130003); + + let progress = null; + let statusWatch = "CURRENT"; + + if (gut?.status === "COMPLETED") { + statusWatch = "REPEATING"; + } + + if (gut) { + progress = gut?.progress; + } + + return { + props: { + sesi: session, + data: gut || null, + progress: progress, + statusWatch: statusWatch, + }, + }; +} |