From 9a5754fdba9d778f820fe89b44d1e21ca9f0bb4d Mon Sep 17 00:00:00 2001 From: Factiven Date: Tue, 16 May 2023 22:40:02 +0700 Subject: 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 --- assets/Karla-MediumItalic.ttf | Bin 0 -> 42252 bytes assets/Outfit-Regular.ttf | Bin 0 -> 47848 bytes components/hero/content.js | 182 ++++++++++++++++++++++++++++++++++++++---- components/hero/genres.js | 69 ++++++++++++++++ components/searchBar.js | 144 +++++++++++++++++++++++++++++++++ components/videoPlayer.js | 2 - lib/useAnilist.js | 33 ++------ package-lock.json | 175 +++++++++++++++++++++++++++++++++++++++- package.json | 3 +- pages/anime/[...id].js | 129 ++++++++++++++++++++++-------- pages/api/og.jsx | 103 ++++++++++++++++++++++++ pages/index.js | 60 ++++++++++++-- pages/search/[param].js | 44 ++++++---- 13 files changed, 841 insertions(+), 103 deletions(-) create mode 100644 assets/Karla-MediumItalic.ttf create mode 100644 assets/Outfit-Regular.ttf create mode 100644 components/hero/genres.js create mode 100644 components/searchBar.js create mode 100644 pages/api/og.jsx diff --git a/assets/Karla-MediumItalic.ttf b/assets/Karla-MediumItalic.ttf new file mode 100644 index 0000000..608d425 Binary files /dev/null and b/assets/Karla-MediumItalic.ttf differ diff --git a/assets/Outfit-Regular.ttf b/assets/Outfit-Regular.ttf new file mode 100644 index 0000000..3fe424f Binary files /dev/null and b/assets/Outfit-Regular.ttf differ diff --git a/components/hero/content.js b/components/hero/content.js index 7e2d9ab..24ee942 100644 --- a/components/hero/content.js +++ b/components/hero/content.js @@ -1,9 +1,54 @@ import Link from "next/link"; -import React, { useState } from "react"; +import React, { useState, useRef, useEffect } from "react"; import Image from "next/image"; -import { MdChevronLeft, MdChevronRight } from "react-icons/md"; +import { MdChevronRight } from "react-icons/md"; +import { + ChevronRightIcon, + ArrowRightCircleIcon, +} from "@heroicons/react/24/outline"; + +import { ChevronLeftIcon } from "@heroicons/react/20/solid"; +import { ExclamationCircleIcon } from "@heroicons/react/24/solid"; + +export default function Content({ ids, section, data, og }) { + const [startX, setStartX] = useState(null); + const [scrollLefts, setScrollLefts] = useState(null); + const containerRef = useRef(null); + + const [isDragging, setIsDragging] = useState(false); + const [clicked, setClicked] = useState(false); + + useEffect(() => { + const click = localStorage.getItem("clicked"); + if (click) { + setClicked(JSON.parse(click)); + } + }, []); + + const handleMouseDown = (e) => { + setIsDragging(true); + setStartX(e.pageX - containerRef.current.offsetLeft); + setScrollLefts(containerRef.current.scrollLeft); + }; + + const handleMouseUp = () => { + setIsDragging(false); + }; + + const handleMouseMove = (e) => { + if (!isDragging) return; + e.preventDefault(); + const x = e.pageX - containerRef.current.offsetLeft; + const walk = (x - startX) * 3; + containerRef.current.scrollLeft = scrollLeft - walk; + }; + + const handleClick = (e) => { + if (isDragging) { + e.preventDefault(); + } + }; -export default function Content({ ids, section, data }) { const [scrollLeft, setScrollLeft] = useState(false); const [scrollRight, setScrollRight] = useState(true); @@ -24,27 +69,59 @@ export default function Content({ ids, section, data }) { setScrollRight(scrollRight); }; - // console.log({ left: scrollLeft, right: scrollRight }); + function handleAlert(e) { + if (localStorage.getItem("clicked")) { + const existingDataString = localStorage.getItem("clicked"); + const existingData = JSON.parse(existingDataString); + + existingData[e] = true; + + const updatedDataString = JSON.stringify(existingData); + + localStorage.setItem("clicked", updatedDataString); + } else { + const newData = { + [e]: true, + }; + + const newDataString = JSON.stringify(newData); + + localStorage.setItem("clicked", newDataString); + } + } const array = data; let filteredData = array?.filter((item) => item !== null); + const slicedData = + filteredData?.length > 15 ? filteredData?.slice(0, 15) : filteredData; + return (
-

{section}

+
+

{section}

+ +
- + > + +
- {filteredData?.map((anime) => { + {slicedData?.map((anime) => { + const progress = og?.find((i) => i.mediaId === anime.id); return (
+ {ids === "onGoing" && ( +
+
+

+ {anime.title.romaji || anime.title.english} +

+ {checkProgress(progress) && + !clicked?.hasOwnProperty(anime.id) && ( + + )} + {checkProgress(progress) && ( +
handleAlert(anime.id)} + className="group-hover:visible invisible absolute top-0 bg-black bg-opacity-20 w-full h-full z-20 text-center" + > +

+ {checkProgress(progress)} +

+
+ )} +
+

Episode {anime.nextAiringEpisode.episode} in

+

+ {convertSecondsToTime( + anime?.nextAiringEpisode?.timeUntilAiring + )} +

+
+
+
+ )}
); })} + {filteredData.length >= 10 && section !== "Recommendations" && ( +
+
+

+ More on {section} +

+ +
+
+ )}
@@ -90,3 +208,37 @@ export default function Content({ ids, section, data }) {
); } + +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 `; + time += `${hours}h`; + } else { + time += `${hours}h `; + time += `${minutes}m`; + } + + return time.trim(); +} + +function checkProgress(entry) { + const { progress, media } = entry; + const { episodes, nextAiringEpisode } = media; + + if (nextAiringEpisode !== null) { + const { episode } = nextAiringEpisode; + + if (episode - progress > 1) { + const missedEpisodes = episode - progress - 1; + return `${missedEpisodes} episode${missedEpisodes > 1 ? "s" : ""} behind`; + } + } + + return; +} diff --git a/components/hero/genres.js b/components/hero/genres.js new file mode 100644 index 0000000..1c8a475 --- /dev/null +++ b/components/hero/genres.js @@ -0,0 +1,69 @@ +import Image from "next/image"; +import { ChevronRightIcon } from "@heroicons/react/24/outline"; +import Link from "next/link"; + +const g = [ + { + name: "Action", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx20958-HuFJyr54Mmir.jpg", + }, + { + name: "Comedy", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx21202-TfzXuWQf2oLQ.png", + }, + { + name: "Horror", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx127230-FlochcFsyoF4.png", + }, + { + name: "Romance", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx124080-h8EPH92nyRfS.jpg", + }, + { + name: "Music", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx130003-5Y8rYzg982sq.png", + }, + { + name: "Sports", + img: "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx20464-eW7ZDBOcn74a.png", + }, +]; + +export default function Genres() { + return ( +
+
+

Top Genres

+ +
+
+
+
+
+ {g.map((a, index) => ( + +
+

+ {a.name} +

+
+ genres images + + ))} +
+
+
+
+
+ ); +} diff --git a/components/searchBar.js b/components/searchBar.js new file mode 100644 index 0000000..35e9b45 --- /dev/null +++ b/components/searchBar.js @@ -0,0 +1,144 @@ +import { useState, useEffect, useRef } from "react"; +import { motion as m, AnimatePresence } from "framer-motion"; +import { MagnifyingGlassIcon } from "@heroicons/react/24/outline"; +import { useAniList } from "../lib/useAnilist"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; + +const SearchBar = () => { + const [isOpen, setIsOpen] = useState(false); + const searchBoxRef = useRef(null); + + const router = useRouter(); + + const { aniAdvanceSearch } = useAniList(); + const [data, setData] = useState(null); + const [query, setQuery] = useState(""); + + useEffect(() => { + if (isOpen) { + searchBoxRef.current.querySelector("input").focus(); + } + const handleKeyDown = (e) => { + if (e.ctrlKey && e.code === "Space") { + setIsOpen((prev) => !prev); + setData(null); + setQuery(""); + } + }; + + document.addEventListener("keydown", handleKeyDown); + + const handleClick = (e) => { + if (searchBoxRef.current && !searchBoxRef.current.contains(e.target)) { + setIsOpen(false); + } + }; + document.addEventListener("click", handleClick); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + document.removeEventListener("click", handleClick); + }; + }, [isOpen]); + + async function search() { + const data = await aniAdvanceSearch({ + search: query, + type: "ANIME", + perPage: 10, + }); + setData(data); + } + + useEffect(() => { + if (query) { + search(); + } + }, [query]); + + function handleSubmit(e) { + e.preventDefault(); + if (data?.media.length) { + router.push(`/anime/${data?.media[0].id}`); + } + } + + return ( + + {isOpen && ( + +
+
+ setQuery(e.target.value)} + /> +
+
+ {data?.media.map((i) => ( + + search results +
+
+

{i.title.userPreferred}

+
+ {i.status + ?.toLowerCase() + .replace(/^\w/, (c) => c.toUpperCase())}{" "} + {i.status && i.season && <>·}{" "} + {i.season + ?.toLowerCase() + .replace(/^\w/, (c) => c.toUpperCase())}{" "} + {(i.status || i.season) && i.episodes && <>·}{" "} + {i.episodes || 0} Episodes +
+
+
+

+ {i.type + ?.toLowerCase() + .replace(/^\w/, (c) => c.toUpperCase())} +

+
+
+ + ))} +
+ {query && ( + + )} +
+
+ )} +
+ ); +}; + +export default SearchBar; diff --git a/components/videoPlayer.js b/components/videoPlayer.js index c441acc..8594645 100644 --- a/components/videoPlayer.js +++ b/components/videoPlayer.js @@ -16,7 +16,6 @@ export default function VideoPlayer({ }) { const [url, setUrl] = useState(); const [source, setSource] = useState([]); - const [loading, setLoading] = useState(true); const { markProgress } = useAniList(session); useEffect(() => { @@ -52,7 +51,6 @@ export default function VideoPlayer({ setUrl(defUrl); setSource(source); - setLoading(false); } catch (error) { console.error(error); } diff --git a/lib/useAnilist.js b/lib/useAnilist.js index 6a0a986..fc25902 100644 --- a/lib/useAnilist.js +++ b/lib/useAnilist.js @@ -20,6 +20,10 @@ export function useAniList(session, stats) { score media { id + nextAiringEpisode { + timeUntilAiring + episode + } title { english romaji @@ -104,34 +108,7 @@ export function useAniList(session, stats) { "Content-Type": "application/json", }, body: JSON.stringify({ - query: ` - query ($username: String, $status: MediaListStatus) { - MediaListCollection(userName: $username, type: ANIME, status: $status) { - lists { - status - name - entries { - id - mediaId - status - progress - score - media { - id - title { - english - romaji - } - episodes - coverImage { - large - } - } - } - } - } - } - `, + query: queryMedia, variables: { username: username, status: statuss?.stats, diff --git a/package-lock.json b/package-lock.json index 5ecc78b..bdb2ab2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,17 @@ { "name": "moopa", - "version": "3.5.0", + "version": "3.5.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "moopa", - "version": "3.5.0", + "version": "3.5.7", "dependencies": { "@apollo/client": "^3.7.3", "@heroicons/react": "^2.0.17", "@next/font": "13.0.7", + "@vercel/og": "^0.5.4", "artplayer": "latest", "axios": "^1.2.2", "closest-match": "^1.3.3", @@ -3259,6 +3260,14 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/@resvg/resvg-wasm": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@resvg/resvg-wasm/-/resvg-wasm-2.4.1.tgz", + "integrity": "sha512-yi6R0HyHtsoWTRA06Col4WoDs7SvlXU3DLMNP2bdAgs7HK18dTEVl1weXgxRzi8gwLteGUbIg29zulxIB3GSdg==", + "engines": { + "node": ">= 10" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3339,6 +3348,21 @@ "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", "dev": true }, + "node_modules/@shuding/opentype.js": { + "version": "1.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@shuding/opentype.js/-/opentype.js-1.4.0-beta.0.tgz", + "integrity": "sha512-3NgmNyH3l/Hv6EvsWJbsvpcpUba6R8IREQ83nH83cyakCw7uM1arZKNfHwv1Wz6jgqrF/j4x5ELvR6PnK9nTcA==", + "dependencies": { + "fflate": "^0.7.3", + "string.prototype.codepointat": "^0.2.1" + }, + "bin": { + "ot": "bin/ot" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3547,6 +3571,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vercel/og": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@vercel/og/-/og-0.5.4.tgz", + "integrity": "sha512-o4Zjgw66HPtfGFYG4CFcKfnCcK6qCY+3FC6g22Z1SdAN5jN8XjbHCh3hRVHH5IGBQp8mxH941EcH53KimAm+wg==", + "dependencies": { + "@resvg/resvg-wasm": "2.4.1", + "satori": "0.7.2", + "yoga-wasm-web": "0.3.3" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz", @@ -4289,6 +4326,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001481", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001481.tgz", @@ -4496,6 +4541,34 @@ "node": ">=8" } }, + "node_modules/css-background-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/css-background-parser/-/css-background-parser-0.1.0.tgz", + "integrity": "sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==" + }, + "node_modules/css-box-shadow": { + "version": "1.0.0-3", + "resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz", + "integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg==" + }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5381,6 +5454,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5866,6 +5944,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hex-rgb": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-4.3.0.tgz", + "integrity": "sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/hey-listen": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz", @@ -6551,6 +6640,23 @@ "node": ">=10" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7231,6 +7337,11 @@ "node": ">=6" } }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7243,6 +7354,15 @@ "node": ">=6" } }, + "node_modules/parse-css-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/parse-css-color/-/parse-css-color-0.2.1.tgz", + "integrity": "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg==", + "dependencies": { + "color-name": "^1.1.4", + "hex-rgb": "^4.1.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7496,8 +7616,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/preact": { "version": "10.13.2", @@ -7944,6 +8063,30 @@ "node": ">=6" } }, + "node_modules/satori": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/satori/-/satori-0.7.2.tgz", + "integrity": "sha512-Eltg0/i3OEbBLaveCnYi2j+p0J9Bb5rmDMXddq4Zy0/NYHbpTkPIlPYGgb+DKamhmvXhiGvWGiFdqHVjJCaCpA==", + "dependencies": { + "@shuding/opentype.js": "1.4.0-beta.0", + "css-background-parser": "^0.1.0", + "css-box-shadow": "1.0.0-3", + "css-to-react-native": "^3.0.0", + "emoji-regex": "^10.2.1", + "linebreak": "^1.1.0", + "parse-css-color": "^0.2.1", + "postcss-value-parser": "^4.2.0", + "yoga-wasm-web": "^0.3.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/satori/node_modules/emoji-regex": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz", + "integrity": "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==" + }, "node_modules/scheduler": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", @@ -8131,6 +8274,11 @@ "node": ">= 0.4" } }, + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", + "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" + }, "node_modules/string.prototype.matchall": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz", @@ -8582,6 +8730,11 @@ "node": ">=0.8" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -8780,6 +8933,15 @@ "node": ">=4" } }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, "node_modules/unique-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", @@ -9364,6 +9526,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==" + }, "node_modules/zen-observable": { "version": "0.8.15", "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", diff --git a/package.json b/package.json index 4c718b2..1106b7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moopa", - "version": "3.5.5", + "version": "3.5.7", "private": true, "scripts": { "dev": "next dev", @@ -13,6 +13,7 @@ "@apollo/client": "^3.7.3", "@heroicons/react": "^2.0.17", "@next/font": "13.0.7", + "@vercel/og": "^0.5.4", "artplayer": "latest", "axios": "^1.2.2", "closest-match": "^1.3.3", 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..."} + + + + handleClose()}>
@@ -743,13 +762,15 @@ export default function Info() { // Something went wrong, can't retrieve any episodes :/ //

-

{epiStatus} while retrieving data

+ {/*

{epiStatus} while retrieving data

*/}
-                            {error}
+                            
+                              Something went wrong while retrieving data :/
+                            
                           
)} @@ -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( + ( +
+
+ + moopa + +

+ {title} +

+
+
+ ), + { + 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() {
moopa @@ -78,7 +80,7 @@ export function Navigasi() { )}
-
+
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 ( <> @@ -361,6 +378,7 @@ export default function Home({ detail, populars, sessions }) {
+ {/* PC / TABLET */}
@@ -402,9 +420,14 @@ export default function Home({ detail, populars, sessions }) {
+ {/* {!sessions && ( +

+ {greeting}! +

+ )} */} {sessions && ( -
-
+
+
{greeting},

{sessions?.user.name}

)} -
+
+ {sessions && onGoing && ( + + + + )} + {sessions && list && ( )} + + + +
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() {
-
+

TITLE @@ -299,6 +302,7 @@ export default function Card() {

+
{isVisible && ( @@ -309,19 +313,24 @@ export default function Card() { exit={{ opacity: 0, y: -10 }} className="xl:pb-16" > -
+

GENRE