diff options
| author | Factiven <[email protected]> | 2023-05-16 22:40:02 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-05-16 22:40:02 +0700 |
| commit | 9a5754fdba9d778f820fe89b44d1e21ca9f0bb4d (patch) | |
| tree | 8bd574163e760216bc91f7b3c164232b6982efe8 /pages | |
| parent | Update v3.5.6 (diff) | |
| download | moopa-9a5754fdba9d778f820fe89b44d1e21ca9f0bb4d.tar.xz moopa-9a5754fdba9d778f820fe89b44d1e21ca9f0bb4d.zip | |
Update v3.5.7 (#12)
* Merge request (#11)
* Update v3.5.5
> Now Skip button will hide if player is not in focused state.
> Added some options to player.
> Manga images should be displayed now.
* Update videoPlayer.js
* Revamp hero section #1
* UI Improvement
> Updating main page
> Updated Genres selection using params method
> Added search bar v1.0 on main page ( [ctrl + space] to access search bar )
* update meta
* Update [...id].js
* Update [...id].js
> Back to ssr I guess
* update episode selector
* Update [...info].js
* Update UI
> Added On-Going section for AniList user
* Update content.js
* added dynamic og
* Update og.jsx
* Update og
* Update og.jsx
* update og and id fallback
> Added fallback for anime info if it's not found
* Update v3.5.7
> Added On-Going section for AniList user
> Added Genre section
> Added dynamic Open Graph when sharing anime
> Added Episode Selector above information
Diffstat (limited to 'pages')
| -rw-r--r-- | pages/anime/[...id].js | 129 | ||||
| -rw-r--r-- | pages/api/og.jsx | 103 | ||||
| -rw-r--r-- | pages/index.js | 60 | ||||
| -rw-r--r-- | pages/search/[param].js | 44 |
4 files changed, 283 insertions, 53 deletions
diff --git a/pages/anime/[...id].js b/pages/anime/[...id].js index ae6ac34..dc385f9 100644 --- a/pages/anime/[...id].js +++ b/pages/anime/[...id].js @@ -140,16 +140,18 @@ const infoQuery = `query ($id: Int) { } }`; -export default function Info() { - const { data: session, status } = useSession(); +export default function Info({ info, color }) { + const { data: session } = useSession(); const [data, setData] = useState(null); - const [info, setInfo] = useState(null); + // const [infos, setInfo] = useState(null); const [episode, setEpisode] = useState(null); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [statuses, setStatuses] = useState(null); const [stall, setStall] = useState(false); - const [color, setColor] = useState(null); + const [domainUrl, setDomainUrl] = useState(""); + + // console.log(info); const [showAll, setShowAll] = useState(false); const [open, setOpen] = useState(false); @@ -164,13 +166,15 @@ export default function Info() { (data) => data.mediaRecommendation ); - // const [log, setLog] = useState(null); - // console.log(rec); - useEffect(() => { + const { protocol, host } = window.location; + const url = `${protocol}//${host}`; + + setDomainUrl(url); + const defaultState = { data: null, - info: null, + // info: null, episode: null, loading: true, statuses: null, @@ -204,23 +208,23 @@ export default function Info() { if (id) { setLoading(false); try { - const [res, info] = await Promise.all([ + const [res] = await Promise.all([ fetch(`https://api.moopa.my.id/meta/anilist/info/${id?.[0]}`), - fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: infoQuery, - variables: { - id: id?.[0], - }, - }), - }), + // fetch("https://graphql.anilist.co/", { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ + // query: infoQuery, + // variables: { + // id: id?.[0], + // }, + // }), + // }), ]); const data = await res.json(); - const infos = await info.json(); + // const infos = await info.json(); if (res.status === 500) { setEpisode(null); @@ -229,10 +233,10 @@ export default function Info() { } else if (res.status === 404) { window.location.href("/404"); } - setInfo(infos.data.Media); + // setInfo(infos.data.Media); // setLog(data); - const textColor = setTxtColor(infos.data.Media.coverImage?.color); + // const textColor = setTxtColor(infos.data.Media.coverImage?.color); if (!data || data?.episodes?.length === 0) { const res = await fetch( @@ -246,19 +250,19 @@ export default function Info() { } else { setEpisode(datas.episodes); } - setColor({ - backgroundColor: `${data?.color || "#ffff"}`, - color: textColor, - }); + // setColor({ + // backgroundColor: `${data?.color || "#ffff"}`, + // color: textColor, + // }); setStall(true); } else { setEpisode(data.episodes); } - setColor({ - backgroundColor: `${data?.color || "#ffff"}`, - color: textColor, - }); + // setColor({ + // backgroundColor: `${data?.color || "#ffff"}`, + // color: textColor, + // }); if (session?.user?.name) { const response = await fetch("https://graphql.anilist.co/", { @@ -339,6 +343,21 @@ export default function 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> @@ -743,13 +762,15 @@ export default function Info() { // Something went wrong, can't retrieve any episodes :/ // </p> <div className="flex flex-col"> - <h1>{epiStatus} while retrieving data</h1> + {/* <h1>{epiStatus} while retrieving data</h1> */} <pre className={`rounded-md ${getLanguageClassName( "bash" )}`} > - <code>{error}</code> + <code> + Something went wrong while retrieving data :/ + </code> </pre> </div> )} @@ -785,6 +806,46 @@ export default function Info() { ); } +export async function getServerSideProps(context) { + const { id } = context.query; + + const res = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: infoQuery, + 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, + }, + }; +} + function convertSecondsToTime(sec) { let days = Math.floor(sec / (3600 * 24)); let hours = Math.floor((sec % (3600 * 24)) / 3600); diff --git a/pages/api/og.jsx b/pages/api/og.jsx new file mode 100644 index 0000000..b1cf238 --- /dev/null +++ b/pages/api/og.jsx @@ -0,0 +1,103 @@ +import { ImageResponse } from "@vercel/og"; + +export const config = { + runtime: "edge", +}; + +const karla = fetch( + new URL("../../assets/Karla-MediumItalic.ttf", import.meta.url) +).then((res) => res.arrayBuffer()); +const outfit = fetch( + new URL("../../assets/Outfit-Regular.ttf", import.meta.url) +).then((res) => res.arrayBuffer()); + +export default async function handler(request) { + const Karla = await karla; + const Outfit = await outfit; + + const { searchParams } = request.nextUrl; + const hasTitle = searchParams.has("title"); + const title = hasTitle + ? searchParams.get("title").length > 64 + ? searchParams.get("title").slice(0, 64) + "..." + : searchParams.get("title") + : "Watch Now"; + const image = searchParams.get("image"); + if (!title) { + return new ImageResponse(<>Visit with "?username=vercel"</>, { + width: 1200, + height: 630, + }); + } + + return new ImageResponse( + ( + <div + style={{ + display: "flex", + fontSize: 60, + color: "black", + background: "#f6f6f6", + width: "100%", + height: "100%", + backgroundImage: `url(${image})`, + }} + className="relative w-[1900px] h-[400px] text-[10px]" + > + <div + className="w-[1900px] h-[400px]" + style={{ + display: "flex", + width: "100%", + paddingLeft: 100, + alignItems: "center", + color: "white", + position: "relative", + backgroundImage: `linear-gradient(to right, rgba(0,0,0,0.93), rgba(0,0,0,0.8) , rgba(0,0,0,0.2))`, + filter: "brightness(20%)", + }} + > + <span + style={{ + display: "flex", + position: "absolute", + top: 10, + left: 25, + fontSize: "40", + color: "#FF7F57", + fontFamily: "Outfit", + filter: "brightness(100%)", + }} + > + moopa + </span> + <h1 + style={{ + width: "70%", + fontSize: "70px", + filter: "brightness(100%)", + }} + > + {title} + </h1> + </div> + </div> + ), + { + width: 1900, + height: 400, + fonts: [ + { + name: "Karla", + data: Karla, + style: "normal", + }, + { + name: "Outfit", + data: Outfit, + style: "normal", + }, + ], + } + ); +} diff --git a/pages/index.js b/pages/index.js index 88687d7..abe5df3 100644 --- a/pages/index.js +++ b/pages/index.js @@ -13,6 +13,8 @@ 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]"; +import SearchBar from "../components/searchBar"; +import Genres from "../components/hero/genres"; export function Navigasi() { const { data: sessions, status } = useSession(); @@ -38,7 +40,7 @@ export function Navigasi() { <div className="flex items-center lg:gap-16 lg:pt-7"> <Link href="/" - className=" font-outfit text-[40px] font-bold text-[#FF7F57]" + className=" font-outfit lg:text-[40px] text-[30px] font-bold text-[#FF7F57]" > moopa </Link> @@ -78,7 +80,7 @@ export function Navigasi() { )} </ul> </div> - <div className="relative flex scale-75 items-center mb-7 lg:mb-0"> + <div className="relative flex lg:scale-75 scale-[65%] items-center mb-7 lg:mb-0"> <div className="search-box "> <input className="search-text" @@ -105,6 +107,10 @@ export default function Home({ detail, populars, sessions }) { 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]; @@ -140,10 +146,19 @@ export default function Home({ detail, populars, sessions }) { .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()); } @@ -154,6 +169,8 @@ export default function Home({ detail, populars, sessions }) { userData(); }, [sessions, current, plan]); + // console.log(log); + return ( <> <Head> @@ -361,6 +378,7 @@ export default function Home({ detail, populars, sessions }) { <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"> @@ -402,9 +420,14 @@ export default function Home({ detail, populars, sessions }) { </div> </div> </div> + {/* {!sessions && ( + <h1 className="font-bold font-karla mx-5 text-[32px] mt-2 lg:mx-24 xl:mx-36"> + {greeting}! + </h1> + )} */} {sessions && ( - <div className="flex items-center mx-3 lg:mx-0 mt-10 lg:mt-0"> - <div className="lg:text-4xl lg:mx-32 flex items-center gap-3 text-2xl font-bold font-karla"> + <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()} @@ -419,13 +442,30 @@ export default function Home({ detail, populars, sessions }) { </div> )} - <div className="lg:mt-16 mt-12 flex flex-col items-center"> + <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 && ( + <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 && ( <motion.div // Add motion.div to each child component key="listAnime" @@ -492,6 +532,16 @@ export default function Home({ detail, populars, sessions }) { /> </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> </div> </div> diff --git a/pages/search/[param].js b/pages/search/[param].js index 9fc2b17..96d6671 100644 --- a/pages/search/[param].js +++ b/pages/search/[param].js @@ -56,8 +56,11 @@ export default function Card() { 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") { @@ -96,9 +99,8 @@ export default function Card() { const [search, setQuery] = useState(hasil); const [type, setSelectedType] = useState(tipe); - const [genres, setSelectedGenre] = useState(); + // const [genres, setSelectedGenre] = useState(); const [sort, setSelectedSort] = useState(); - // console.log(data); const [isVisible, setIsVisible] = useState(false); @@ -112,7 +114,7 @@ export default function Card() { const data = await aniAdvanceSearch({ search: search, type: type, - genres: genres, + genres: gr, page: page, sort: sort, season: s, @@ -137,7 +139,7 @@ export default function Card() { setPage(1); setNextPage(true); advance(); - }, [search, type, genres, sort, s, y]); + }, [search, type, sort, s, y, gr]); useEffect(() => { advance(); @@ -178,8 +180,9 @@ export default function Card() { function trash() { setQuery(null); inputRef.current.value = ""; - setSelectedGenre(null); + // setSelectedGenre(null); setSelectedSort(["POPULARITY_DESC"]); + router.push(`/search/${tipe.toLocaleLowerCase()}`); } function handleVisible() { @@ -202,7 +205,7 @@ export default function Card() { <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-16 justify-center lg:gap-7 xl:gap-10 gap-3 font-karla font-light"> + <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 @@ -299,6 +302,7 @@ export default function Card() { </div> </div> </div> + <div className="w-screen xl:w-[64%] flex xl:justify-end xl:pl-0"> <AnimatePresence> {isVisible && ( @@ -309,19 +313,24 @@ export default function Card() { 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 "> + <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> <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" - onChange={(e) => - setSelectedGenre( - e.target.value === "undefined" - ? undefined - : e.target.value - ) - } + 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) => { @@ -372,6 +381,13 @@ export default function Card() { )} </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 |