diff options
| author | Factiven <[email protected]> | 2023-09-25 00:44:40 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-09-25 00:44:40 +0700 |
| commit | 1a85c2571690ba592ac5183d5eadaf9846fe532b (patch) | |
| tree | 3f3552c00cd49c0eeab5275275cf5cf5666e5027 /pages | |
| parent | Delete .github/workflows/deploy.yml (diff) | |
| download | moopa-4.1.0.tar.xz moopa-4.1.0.zip | |
Update v4.1.0 (#79)v4.1.0
* Update v4.1.0
* Update pages/_app.js
Diffstat (limited to 'pages')
29 files changed, 1292 insertions, 515 deletions
diff --git a/pages/404.js b/pages/404.js index 5b6162b..f6e609f 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,10 +1,9 @@ import Head from "next/head"; -import Footer from "../components/footer"; -import Navbar from "../components/navbar"; import Link from "next/link"; import { useEffect, useState } from "react"; import { parseCookies } from "nookies"; import Image from "next/image"; +import Footer from "@/components/shared/footer"; export default function Custom404() { const [lang, setLang] = useState("en"); @@ -31,7 +30,6 @@ export default function Custom404() { <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/svg/c.svg" /> </Head> - <Navbar className="bg-[#0c0d10]" /> <div className="min-h-screen w-screen flex flex-col items-center justify-center "> <Image width={500} diff --git a/pages/_app.js b/pages/_app.js index 5303b71..68b4acd 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -6,9 +6,13 @@ import "../styles/globals.css"; import "react-toastify/dist/ReactToastify.css"; import "react-loading-skeleton/dist/skeleton.css"; import { SkeletonTheme } from "react-loading-skeleton"; -import SearchPalette from "../components/searchPalette"; -import { SearchProvider } from "../lib/hooks/isOpenState"; +import SearchPalette from "@/components/searchPalette"; +import { SearchProvider } from "@/lib/hooks/isOpenState"; import Head from "next/head"; +import { WatchPageProvider } from "@/lib/hooks/watchPageProvider"; +import { ToastContainer, toast } from "react-toastify"; +import { useEffect } from "react"; +import { unixTimestampToRelativeTime } from "@/utils/getTimes"; export default function App({ Component, @@ -16,6 +20,42 @@ export default function App({ }) { const router = useRouter(); + useEffect(() => { + async function getBroadcast() { + try { + const res = await fetch("/api/v2/admin/broadcast", { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Broadcast-Key": "get-broadcast", + }, + }); + const data = await res.json(); + if ( + data && + data?.message !== "No broadcast" && + data?.message !== "unauthorized" + ) { + toast(data.message + unixTimestampToRelativeTime(data.startAt), { + position: "top-center", + autoClose: false, + closeOnClick: true, + draggable: true, + theme: "colored", + className: "toaster", + style: { + background: "#232329", + color: "#fff", + }, + }); + } + } catch (err) { + console.log(err); + } + } + getBroadcast(); + }, []); + return ( <> <Head> @@ -26,37 +66,41 @@ export default function App({ </Head> <SessionProvider session={session}> <SearchProvider> - <AnimatePresence mode="wait"> - <SkeletonTheme baseColor="#232329" highlightColor="#2a2a32"> - <m.div - key={`route-${router.route}`} - transition={{ duration: 0.5 }} - initial="initialState" - animate="animateState" - exit="exitState" - variants={{ - initialState: { - opacity: 0, - }, - animateState: { - opacity: 1, - }, - exitState: {}, - }} - className="z-50 w-screen" - > - <NextNProgress - color="#FF7E2C" - startPosition={0.3} - stopDelayMs={200} - height={3} - showOnShallow={true} - /> - <SearchPalette /> - <Component {...pageProps} /> - </m.div> - </SkeletonTheme> - </AnimatePresence> + <WatchPageProvider> + <AnimatePresence mode="wait"> + <SkeletonTheme baseColor="#232329" highlightColor="#2a2a32"> + <ToastContainer pauseOnFocusLoss={false} pauseOnHover={false} /> + <m.div + key={`route-${router.route}`} + transition={{ duration: 0.5 }} + initial="initialState" + animate="animateState" + exit="exitState" + variants={{ + initialState: { + opacity: 0, + }, + animateState: { + opacity: 1, + }, + exitState: {}, + }} + className="z-50 w-screen" + > + <NextNProgress + color="#FF7E2C" + startPosition={0.3} + stopDelayMs={200} + height={3} + showOnShallow={true} + /> + + <SearchPalette /> + <Component {...pageProps} /> + </m.div> + </SkeletonTheme> + </AnimatePresence> + </WatchPageProvider> </SearchProvider> </SessionProvider> </> diff --git a/pages/admin/index.js b/pages/admin/index.js new file mode 100644 index 0000000..4fdc8c2 --- /dev/null +++ b/pages/admin/index.js @@ -0,0 +1,263 @@ +import { getServerSession } from "next-auth"; +import { authOptions } from "pages/api/auth/[...nextauth]"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +// Define a function to convert the data +function convertData(episodes) { + const convertedData = episodes.map((episode) => ({ + episode: episode.episode, + title: episode?.title, + description: episode?.description || null, + img: episode?.img?.hd || episode?.img?.mobile || null, // Use hd if available, otherwise use mobile + })); + + return convertedData; +} + +export async function getServerSideProps(context) { + const sessions = await getServerSession( + context.req, + context.res, + authOptions + ); + + if (!sessions) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + + const admin = sessions?.user?.name === process.env.ADMIN_USERNAME; + const api = process.env.API_URI; + + if (!admin) { + return { + redirect: { + destination: "/", + permanent: false, + }, + }; + } + + return { + props: { + session: sessions, + api, + }, + }; +} + +export default function Admin({ api }) { + const [id, setId] = useState(); + const [resultData, setResultData] = useState(null); + + const [query, setQuery] = useState(""); + const [tmdbId, setTmdbId] = useState(); + const [hasilQuery, setHasilQuery] = useState([]); + const [season, setSeason] = useState(); + + const [override, setOverride] = useState(); + + const [loading, setLoading] = useState(false); + + const handleSearch = async () => { + try { + setLoading(true); + setResultData(null); + const res = await fetch(`${api}/meta/tmdb/${query}`); + const json = await res.json(); + const data = json.results; + setHasilQuery(data); + setLoading(false); + } catch (err) { + console.log(err); + } + }; + + const handleDetail = async () => { + try { + setLoading(true); + const res = await fetch(`${api}/meta/tmdb/info/${tmdbId}?type=TV%20Series +`); + const json = await res.json(); + const data = json.seasons; + setHasilQuery(data); + setLoading(false); + } catch (err) { + console.log(err); + } + }; + + const handleStore = async () => { + try { + setLoading(true); + if (!resultData && !id) { + console.log("No data to store"); + setLoading(false); + return; + } + const data = await fetch("/api/v2/admin/meta", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + id: id, + data: resultData, + }), + }); + if (data.status === 200) { + const json = await data.json(); + toast.success(json.message); + setLoading(false); + } + } catch (err) { + console.log(err); + } + }; + + const handleOverride = async () => { + setResultData(JSON.parse(override)); + }; + + return ( + <> + <div className="container mx-auto p-4"> + <h1 className="text-3xl font-semibold mb-4">Append Data Page</h1> + <div> + <div className="space-y-3 mb-4"> + <label>Search Anime:</label> + <input + type="text" + className="w-full px-3 py-2 border rounded-md text-black" + value={query} + onChange={(e) => setQuery(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleSearch} + > + Find Anime{" "} + {loading && <span className="animate-spin ml-2">🔄</span>} + </button> + </div> + <div className="space-y-3 mb-4"> + <label>Get Episodes:</label> + <input + type="number" + className="w-full px-3 py-2 border rounded-md text-black" + value={tmdbId} + onChange={(e) => setTmdbId(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleDetail} + > + Get Details{" "} + {loading && <span className="animate-spin ml-2">🔄</span>} + </button> + </div> + + <div className="space-y-3 mb-4"> + <label>Override Result:</label> + <textarea + rows="5" + className="w-full px-3 py-2 border rounded-md text-black" + value={override} + onChange={(e) => setOverride(e.target.value)} + /> + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleOverride} + > + Override{" "} + {loading && <span className="animate-spin ml-2">🔄</span>} + </button> + </div> + + <div className="space-y-3 mb-4"> + <label className="block text-sm font-medium text-gray-300"> + Anime ID: + </label> + <input + type="number" + className="w-full px-3 py-2 border rounded-md text-black" + value={id} + onChange={(e) => setId(e.target.value)} + /> + </div> + <div className="mb-4"> + <button + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + onClick={handleStore} + > + Store Data {season && `Season ${season}`} + </button> + </div> + + {hasilQuery?.some((i) => i?.season) && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2"> + Which season do you want to format? + </h2> + <div className="w-full flex gap-2"> + {hasilQuery?.map((season, index) => ( + <button + type="button" + className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" + key={index} + onClick={() => { + setLoading(true); + const data = hasilQuery[index].episodes; + const convertedData = convertData(data); + setSeason(index + 1); + setResultData(convertedData); + console.log(convertedData); + setLoading(false); + }} + > + <p> + {season.season}{" "} + {loading && <span className="animate-spin ml-2">🔄</span>} + </p> + </button> + ))} + </div> + </div> + )} + + {resultData && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2">Season {season}</h2> + <pre>{JSON.stringify(resultData, null, 2)}</pre> + </div> + )} + {hasilQuery && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2"> + Result Data,{" "} + {hasilQuery.length > 0 && `${hasilQuery.length} Seasons`}: + </h2> + <pre>{JSON.stringify(hasilQuery, null, 2)}</pre> + </div> + )} + </div> + <div> + {/* {resultData && ( + <div className="border rounded-md p-4 mt-4"> + <h2 className="text-lg font-semibold mb-2">Result Data:</h2> + <pre>{JSON.stringify(resultData, null, 2)}</pre> + </div> + )} */} + </div> + </div> + </> + ); +} diff --git a/pages/api/user/profile.js b/pages/api/user/profile.js index 89a23d5..5ca6b75 100644 --- a/pages/api/user/profile.js +++ b/pages/api/user/profile.js @@ -1,71 +1,66 @@ import { getServerSession } from "next-auth"; import { authOptions } from "../auth/[...nextauth]"; -import { - createUser, - deleteUser, - getUser, - updateUser, -} from "../../../prisma/user"; +import { createUser, deleteUser, getUser, updateUser } from "@/prisma/user"; export default async function handler(req, res) { - // const session = await getServerSession(req, res, authOptions); - // if (session) { - // Signed in - try { - switch (req.method) { - case "POST": { - const { name } = req.body; - const new_user = await createUser(name); - if (!new_user) { - return res.status(200).json({ message: "User is already created" }); - } else { - return res.status(201).json(new_user); - } - } - case "PUT": { - const { name, settings } = req.body; - const user = await updateUser(name, settings); - if (!user) { - return res.status(200).json({ message: "Can't update settings" }); - } else { - return res.status(200).json(user); + const session = await getServerSession(req, res, authOptions); + if (session) { + // Signed in + try { + switch (req.method) { + case "POST": { + const { name } = req.body; + const new_user = await createUser(name); + if (!new_user) { + return res.status(200).json({ message: "User is already created" }); + } else { + return res.status(201).json(new_user); + } } - } - case "GET": { - const { name } = req.query; - const user = await getUser(name); - if (!user) { - return res.status(404).json({ message: "User not found" }); - } else { - return res.status(200).json(user); + case "PUT": { + const { name, settings } = req.body; + const user = await updateUser(name, settings); + if (!user) { + return res.status(200).json({ message: "Can't update settings" }); + } else { + return res.status(200).json(user); + } } - } - case "DELETE": { - const { name } = req.body; - // return res.status(200).json({ name }); - if (session.user.name !== name) { - return res.status(401).json({ message: "Unauthorized" }); - } else { - const user = await deleteUser(name); + case "GET": { + const { name } = req.query; + const user = await getUser(name); if (!user) { return res.status(404).json({ message: "User not found" }); } else { return res.status(200).json(user); } } + case "DELETE": { + const { name } = req.body; + // return res.status(200).json({ name }); + if (session.user.name !== name) { + return res.status(401).json({ message: "Unauthorized" }); + } else { + const user = await deleteUser(name); + if (!user) { + return res.status(404).json({ message: "User not found" }); + } else { + return res.status(200).json(user); + } + } + } + default: { + return res.status(405).json({ message: "Method not allowed" }); + } } - default: { - return res.status(405).json({ message: "Method not allowed" }); - } + } catch (error) { + console.log(error); + return res.status(500).json({ message: "Internal server error" }); } - } catch (error) { - console.log(error); - return res.status(500).json({ message: "Internal server error" }); + } else { + // Not Signed in + res.status(401); } - // } else { - // // Not Signed in - // res.status(401); - // } - // res.end(); + res.end(); } diff --git a/pages/api/user/update/episode.js b/pages/api/user/update/episode.js index 3ee345d..bee98ab 100644 --- a/pages/api/user/update/episode.js +++ b/pages/api/user/update/episode.js @@ -7,7 +7,7 @@ import { deleteList, getEpisode, updateUserEpisode, -} from "../../../../prisma/user"; +} from "@/prisma/user"; export default async function handler(req, res) { const session = await getServerSession(req, res, authOptions); diff --git a/pages/api/v2/admin/broadcast/index.js b/pages/api/v2/admin/broadcast/index.js new file mode 100644 index 0000000..d3d3af0 --- /dev/null +++ b/pages/api/v2/admin/broadcast/index.js @@ -0,0 +1,40 @@ +import { rateLimitStrict, redis } from "@/lib/redis"; +// import { getServerSession } from "next-auth"; +// import { authOptions } from "pages/api/auth/[...nextauth]"; + +export default async function handler(req, res) { + // Check if the custom header "X-Your-Custom-Header" is present and has a specific value + const customHeaderValue = req.headers["x-broadcast-key"]; + + if (customHeaderValue !== "get-broadcast") { + return res.status(401).json({ message: "Unauthorized" }); + } + + try { + if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimitStrict.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + + const getId = await redis.get(`broadcast`); + if (getId) { + const broadcast = JSON.parse(getId); + return res + .status(200) + .json({ message: broadcast.message, startAt: broadcast.startAt }); + } else { + return res.status(200).json({ message: "No broadcast" }); + } + } + + return res.status(200).json({ message: "redis is not defined" }); + } catch (err) { + console.error(err); + res.status(500).json({ error: err.message }); + } +} diff --git a/pages/api/v2/admin/bug-report/index.js b/pages/api/v2/admin/bug-report/index.js new file mode 100644 index 0000000..fc5ee77 --- /dev/null +++ b/pages/api/v2/admin/bug-report/index.js @@ -0,0 +1,49 @@ +import { rateLimitStrict, redis } from "@/lib/redis"; +// import { getServerSession } from "next-auth"; +// import { authOptions } from "pages/api/auth/[...nextauth]"; + +export default async function handler(req, res) { + // const session = await getServerSession(req, res, authOptions); + // const admin = session?.user?.name === process.env.ADMIN_USERNAME; + // create random id each time the endpoint is called + const id = Math.random().toString(36).substr(2, 9); + + // if (!admin) { + // return res.status(401).json({ message: "Unauthorized" }); + // } + const { data } = req.body; + + // if method is not POST return message "Method not allowed" + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + try { + if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimitStrict.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + + const getId = await redis.get(`report:${id}`); + if (getId) { + return res + .status(200) + .json({ message: `Data already exist for id: ${id}` }); + } + await redis.set(`report:${id}`, JSON.stringify(data)); + return res + .status(200) + .json({ message: `Report has successfully sent, with Id of ${id}` }); + } + + return res.status(200).json({ message: "redis is not defined" }); + } catch (err) { + console.error(err); + res.status(500).json({ error: err.message }); + } +} diff --git a/pages/api/v2/admin/meta/index.js b/pages/api/v2/admin/meta/index.js new file mode 100644 index 0000000..5f51b7f --- /dev/null +++ b/pages/api/v2/admin/meta/index.js @@ -0,0 +1,47 @@ +import { rateLimitStrict, redis } from "@/lib/redis"; +import { getServerSession } from "next-auth"; +import { authOptions } from "pages/api/auth/[...nextauth]"; + +export default async function handler(req, res) { + const session = await getServerSession(req, res, authOptions); + const admin = session?.user?.name === process.env.ADMIN_USERNAME; + + if (!admin) { + return res.status(401).json({ message: "Unauthorized" }); + } + const { id, data } = req.body; + + // if method is not POST return message "Method not allowed" + if (req.method !== "POST") { + return res.status(405).json({ message: "Method not allowed" }); + } + + try { + if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimitStrict.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + + const getId = await redis.get(`meta:${id}`); + if (getId) { + return res + .status(200) + .json({ message: `Data already exist for id: ${id}` }); + } + await redis.set(`meta:${id}`, JSON.stringify(data)); + return res + .status(200) + .json({ message: `Data stored successfully for id: ${id}` }); + } + + return res.status(200).json({ message: "redis is not defined" }); + } catch (err) { + console.error(err); + res.status(500).json({ error: err.message }); + } +} diff --git a/pages/api/v2/episode/[id].js b/pages/api/v2/episode/[id].js index c5d98f5..ab2d321 100644 --- a/pages/api/v2/episode/[id].js +++ b/pages/api/v2/episode/[id].js @@ -1,5 +1,7 @@ import axios from "axios"; -import redis from "../../../../lib/redis"; +import { rateLimitStrict, rateLimiterRedis, redis } from "@/lib/redis"; +import appendImagesToEpisodes from "@/utils/combineImages"; +import appendMetaToEpisodes from "@/utils/appendMetaToEpisodes"; const CONSUMET_URI = process.env.API_URI; const API_KEY = process.env.API_KEY; @@ -23,15 +25,27 @@ async function fetchConsumet(id, dub) { `${CONSUMET_URI}/meta/anilist/episodes/${id}` ); - if (data?.message === "Anime not found") { + if (data?.message === "Anime not found" || data?.length < 1) { return []; } + const reformatted = data.map((item) => ({ + id: item?.id || null, + title: item?.title || null, + img: item?.image || null, + number: item?.number || null, + createdAt: item?.createdAt || null, + description: item?.description || null, + url: item?.url || null, + })); + const array = [ { map: true, providerId: "gogoanime", - episodes: isAscending(data) ? data : data.reverse(), + episodes: isAscending(reformatted) + ? reformatted + : reformatted.reverse(), }, ]; @@ -74,20 +88,60 @@ async function fetchAnify(id) { } } +async function fetchCoverImage(id) { + try { + if (!process.env.API_KEY) { + return []; + } + + const { data } = await axios.get( + `https://api.anify.tv/episode-covers/${id}?apikey=${API_KEY}` + ); + + if (!data) { + return []; + } + + return data; + } catch (error) { + console.error("Error fetching and processing data:", error.message); + return []; + } +} + export default async function handler(req, res) { - const { id, releasing = "false", dub = false } = req.query; + const { id, releasing = "false", dub = false, refresh = null } = req.query; // if releasing is true then cache for 10 minutes, if it false cache for 1 month; const cacheTime = releasing === "true" ? 60 * 10 : 60 * 60 * 24 * 30; let cached; + let meta; if (redis) { - cached = await redis.get(id); - console.log("using redis"); + try { + const ipAddress = req.socket.remoteAddress; + refresh + ? await rateLimitStrict.consume(ipAddress) + : await rateLimiterRedis.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + + if (refresh) { + await redis.del(id); + console.log("deleted cache"); + } else { + cached = await redis.get(id); + console.log("using redis"); + } + + meta = await redis.get(`meta:${id}`); } - if (cached) { + if (cached && !refresh) { if (dub) { const filtered = JSON.parse(cached).filter((item) => item.episodes.some((epi) => epi.hasDub === true) @@ -96,27 +150,46 @@ export default async function handler(req, res) { } else { return res.status(200).json(JSON.parse(cached)); } - } + } else { + const [consumet, anify, cover] = await Promise.all([ + fetchConsumet(id, dub), + fetchAnify(id), + fetchCoverImage(id), + ]); + + const hasImage = consumet.map((i) => + i.episodes.some( + (e) => e.img !== null || !e.img.includes("https://s4.anilist.co/") + ) + ); - const [consumet, anify] = await Promise.all([ - fetchConsumet(id, dub), - fetchAnify(id), - ]); + const rawData = [...consumet, ...anify]; - const data = [...consumet, ...anify]; + let data = rawData; - if (redis && cacheTime !== null) { - await redis.set(id, JSON.stringify(data), "EX", cacheTime); - } + if (meta) { + data = await appendMetaToEpisodes(rawData, JSON.parse(meta)); + } else if (cover && cover?.length > 0 && !hasImage.includes(true)) + data = await appendImagesToEpisodes(rawData, cover); - if (dub) { - const filtered = data.filter((item) => - item.episodes.some((epi) => epi.hasDub === true) - ); - return res.status(200).json(filtered); - } + if (redis && cacheTime !== null) { + await redis.set( + id, + JSON.stringify(data.filter((i) => i.episodes.length > 0)), + "EX", + cacheTime + ); + } - console.log("fresh data"); + if (dub) { + const filtered = data.filter((item) => + item.episodes.some((epi) => epi.hasDub === true) + ); + return res.status(200).json(filtered); + } - return res.status(200).json(data); + console.log("fresh data"); + + return res.status(200).json(data.filter((i) => i.episodes.length > 0)); + } } diff --git a/pages/api/v2/etc/recent/[page].js b/pages/api/v2/etc/recent/[page].js index 19495c1..6727787 100644 --- a/pages/api/v2/etc/recent/[page].js +++ b/pages/api/v2/etc/recent/[page].js @@ -1,7 +1,19 @@ +import { rateLimiterRedis, redis } from "@/lib/redis"; + const API_URL = process.env.API_URI; export default async function handler(req, res) { try { + if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimiterRedis.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + } const page = req.query.page || 1; var hasNextPage = true; diff --git a/pages/api/v2/etc/schedule/index.js b/pages/api/v2/etc/schedule/index.js index 7a13fff..9b8f43d 100644 --- a/pages/api/v2/etc/schedule/index.js +++ b/pages/api/v2/etc/schedule/index.js @@ -1,6 +1,6 @@ import axios from "axios"; import cron from "cron"; -import redis from "../../../../../lib/redis"; +import { rateLimiterRedis, redis } from "@/lib/redis"; const API_KEY = process.env.API_KEY; @@ -43,6 +43,14 @@ export default async function handler(req, res) { try { let cached; if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimiterRedis.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } cached = await redis.get("schedule"); } if (cached) { @@ -60,9 +68,9 @@ export default async function handler(req, res) { 60 * 60 * 24 * 7 ); } - res.status(200).json(data); + return res.status(200).json(data); } else { - res.status(404).json({ message: "Schedule not found" }); + return res.status(404).json({ message: "Schedule not found" }); } } } catch (error) { diff --git a/pages/api/v2/info/[id].js b/pages/api/v2/info/[id].js index 41daa6e..243756c 100644 --- a/pages/api/v2/info/[id].js +++ b/pages/api/v2/info/[id].js @@ -1,5 +1,5 @@ import axios from "axios"; -import redis from "../../../../lib/redis"; +import { rateLimiterRedis, redis } from "@/lib/redis"; const API_KEY = process.env.API_KEY; @@ -19,6 +19,14 @@ export default async function handler(req, res) { const id = req.query.id; let cached; if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimiterRedis.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } cached = await redis.get(id); } if (cached) { diff --git a/pages/api/v2/source/index.js b/pages/api/v2/source/index.js index 51ac5ec..74a63cb 100644 --- a/pages/api/v2/source/index.js +++ b/pages/api/v2/source/index.js @@ -1,3 +1,4 @@ +import { rateLimiterRedis, redis } from "@/lib/redis"; import axios from "axios"; const CONSUMET_URI = process.env.API_URI; @@ -33,6 +34,17 @@ export default async function handler(req, res) { return res.status(405).json({ message: "Method not allowed" }); } + if (redis) { + try { + const ipAddress = req.socket.remoteAddress; + await rateLimiterRedis.consume(ipAddress); + } catch (error) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + } + const { source, providerId, watchId, episode, id, sub = "sub" } = req.body; if (source === "anify") { diff --git a/pages/en/about.js b/pages/en/about.js index cfbee6b..aa0ba30 100644 --- a/pages/en/about.js +++ b/pages/en/about.js @@ -1,7 +1,8 @@ import Head from "next/head"; -import Layout from "../../components/layout"; import { motion } from "framer-motion"; import Link from "next/link"; +import { NewNavbar } from "@/components/shared/NavBar"; +import Footer from "@/components/shared/footer"; export default function About() { return ( @@ -20,46 +21,46 @@ export default function About() { <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/svg/c.svg" /> </Head> - <Layout> - <motion.div - initial={{ opacity: 0 }} - animate={{ opacity: 1 }} - exit={{ opacity: 0 }} - className="flex flex-col justify-center items-center min-h-screen md:py-0 py-16" - > - <div className="max-w-screen-lg w-full px-4 py-10"> - <h1 className="text-4xl font-bold mb-6">About Us</h1> - <p className="text-lg mb-8"> - Moopa is a platform where you can watch and stream anime or read - manga for free, without any ads or VPNs. Our mission is to provide - a convenient and enjoyable experience for anime and manga - enthusiasts all around the world. - </p> - <p className="text-lg mb-8"> - At our site, you will find a vast collection of anime and manga - titles from different genres, including action, adventure, comedy, - romance, and more. We take pride in our fast and reliable servers, - which ensure smooth streaming and reading for all our users. - </p> - <p className="text-lg mb-8"> - We believe that anime and manga have the power to inspire and - entertain people of all ages and backgrounds. Our service is - designed to make it easy for fans to access the content they love, - whether they are casual viewers or die-hard fans. - </p> - <p className="text-lg mb-8"> - Thank you for choosing our website as your go-to platform for - anime and manga. We hope you enjoy your stay here, and feel free - to contact us if you have any feedback or suggestions. - </p> - <Link href="/en/contact"> - <div className="bg-[#ffffff] text-black font-medium py-3 px-6 rounded-lg hover:bg-action transition duration-300 ease-in-out"> - Contact Us - </div> - </Link> - </div> - </motion.div> - </Layout> + <NewNavbar withNav={true} scrollP={5} shrink={true} /> + <motion.div + initial={{ opacity: 0 }} + animate={{ opacity: 1 }} + exit={{ opacity: 0 }} + className="flex flex-col justify-center items-center min-h-screen md:py-0 py-16" + > + <div className="max-w-screen-lg w-full px-4 py-10"> + <h1 className="text-4xl font-bold mb-6">About Us</h1> + <p className="text-lg mb-8"> + Moopa is a platform where you can watch and stream anime or read + manga for free, without any ads or VPNs. Our mission is to provide a + convenient and enjoyable experience for anime and manga enthusiasts + all around the world. + </p> + <p className="text-lg mb-8"> + At our site, you will find a vast collection of anime and manga + titles from different genres, including action, adventure, comedy, + romance, and more. We take pride in our fast and reliable servers, + which ensure smooth streaming and reading for all our users. + </p> + <p className="text-lg mb-8"> + We believe that anime and manga have the power to inspire and + entertain people of all ages and backgrounds. Our service is + designed to make it easy for fans to access the content they love, + whether they are casual viewers or die-hard fans. + </p> + <p className="text-lg mb-8"> + Thank you for choosing our website as your go-to platform for anime + and manga. We hope you enjoy your stay here, and feel free to + contact us if you have any feedback or suggestions. + </p> + <Link href="/en/contact"> + <div className="bg-[#ffffff] text-black font-medium py-3 px-6 rounded-lg hover:bg-action transition duration-300 ease-in-out"> + Contact Us + </div> + </Link> + </div> + </motion.div> + <Footer /> </> ); } diff --git a/pages/en/anime/[...id].js b/pages/en/anime/[...id].js index ec19f9f..193f50f 100644 --- a/pages/en/anime/[...id].js +++ b/pages/en/anime/[...id].js @@ -2,23 +2,21 @@ import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import Content from "../../../components/home/content"; -import Modal from "../../../components/modal"; +import Content from "@/components/home/content"; +import Modal from "@/components/modal"; import { signIn, useSession } from "next-auth/react"; -import AniList from "../../../components/media/aniList"; -import ListEditor from "../../../components/listEditor"; +import AniList from "@/components/media/aniList"; +import ListEditor from "@/components/listEditor"; -import { ToastContainer } from "react-toastify"; - -import DetailTop from "../../../components/anime/mobile/topSection"; -import AnimeEpisode from "../../../components/anime/episode"; -import { useAniList } from "../../../lib/anilist/useAnilist"; -import Footer from "../../../components/footer"; -import { mediaInfoQuery } from "../../../lib/graphql/query"; -import MobileNav from "../../../components/shared/MobileNav"; -import redis from "../../../lib/redis"; -import Characters from "../../../components/anime/charactersCard"; +import DetailTop from "@/components/anime/mobile/topSection"; +import AnimeEpisode from "@/components/anime/episode"; +import { useAniList } from "@/lib/anilist/useAnilist"; +import Footer from "@/components/shared/footer"; +import { mediaInfoQuery } from "@/lib/graphql/query"; +import MobileNav from "@/components/shared/MobileNav"; +import redis from "@/lib/redis"; +import Characters from "@/components/anime/charactersCard"; export default function Info({ info, color }) { const { data: session } = useSession(); @@ -117,7 +115,6 @@ export default function Info({ info, color }) { }&image=${info.bannerImage || info.coverImage.extraLarge}`} /> </Head> - <ToastContainer pauseOnHover={false} /> <Modal open={open} onClose={() => handleClose()}> <div> {!session && ( @@ -188,7 +185,7 @@ export default function Info({ info, color }) { {info?.characters?.edges && ( <div className="w-full"> - <Characters info={info?.characters?.edges}/> + <Characters info={info?.characters?.edges} /> </div> )} diff --git a/pages/en/anime/popular.js b/pages/en/anime/popular.js index 7b40a0e..4e6535b 100644 --- a/pages/en/anime/popular.js +++ b/pages/en/anime/popular.js @@ -3,11 +3,11 @@ import Image from "next/image"; import Link from "next/link"; import { Fragment, useEffect, useState } from "react"; import Skeleton from "react-loading-skeleton"; -import Footer from "../../../components/footer"; +import Footer from "@/components/shared/footer"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; import Head from "next/head"; -import MobileNav from "../../../components/shared/MobileNav"; +import MobileNav from "@/components/shared/MobileNav"; export default function PopularAnime({ sessions }) { const [data, setData] = useState(null); diff --git a/pages/en/anime/recent.js b/pages/en/anime/recent.js index 89a868a..400e926 100644 --- a/pages/en/anime/recent.js +++ b/pages/en/anime/recent.js @@ -3,11 +3,11 @@ import { Fragment, useEffect, useState } from "react"; import Link from "next/link"; import { ChevronLeftIcon } from "@heroicons/react/24/outline"; import Skeleton from "react-loading-skeleton"; -import Footer from "../../../components/footer"; +import Footer from "@/components/shared/footer"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; import Image from "next/image"; -import MobileNav from "../../../components/shared/MobileNav"; +import MobileNav from "@/components/shared/MobileNav"; export async function getServerSideProps(context) { const session = await getServerSession(context.req, context.res, authOptions); diff --git a/pages/en/anime/recently-watched.js b/pages/en/anime/recently-watched.js index 0b7a710..c723394 100644 --- a/pages/en/anime/recently-watched.js +++ b/pages/en/anime/recently-watched.js @@ -3,15 +3,15 @@ import Image from "next/image"; import Link from "next/link"; import { useEffect, useState } from "react"; import Skeleton from "react-loading-skeleton"; -import Footer from "../../../components/footer"; +import Footer from "@/components/shared/footer"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; -import { ToastContainer, toast } from "react-toastify"; +import { toast } from "react-toastify"; import { ChevronRightIcon } from "@heroicons/react/24/outline"; import { useRouter } from "next/router"; -import HistoryOptions from "../../../components/home/content/historyOptions"; +import HistoryOptions from "@/components/home/content/historyOptions"; import Head from "next/head"; -import MobileNav from "../../../components/shared/MobileNav"; +import MobileNav from "@/components/shared/MobileNav"; export default function PopularAnime({ sessions }) { const [data, setData] = useState(null); @@ -154,7 +154,6 @@ export default function PopularAnime({ sessions }) { <title>Moopa - Recently Watched Episodes</title> </Head> <MobileNav sessions={sessions} /> - <ToastContainer pauseOnHover={false} /> <div className="flex flex-col gap-2 items-center min-h-screen w-screen px-2 relative pb-10"> <div className="z-50 bg-primary pt-5 pb-3 shadow-md shadow-primary w-full fixed left-0 px-3"> <Link href="/en" className="flex gap-2 items-center font-karla"> @@ -171,8 +170,6 @@ export default function PopularAnime({ sessions }) { let prog = (time / duration) * 100; if (prog > 90) prog = 100; - console.log({ i }); - return ( <div key={i.watchId} @@ -233,7 +230,7 @@ export default function PopularAnime({ sessions }) { width={200} height={200} alt="Episode Thumbnail" - className="w-fit group-hover:scale-[1.02] duration-300 ease-out z-10" + className="w-full object-cover group-hover:scale-[1.02] duration-300 ease-out z-10" /> )} </Link> diff --git a/pages/en/anime/trending.js b/pages/en/anime/trending.js index 18eadf9..aae468a 100644 --- a/pages/en/anime/trending.js +++ b/pages/en/anime/trending.js @@ -3,11 +3,11 @@ import Image from "next/image"; import Link from "next/link"; import { Fragment, useEffect, useState } from "react"; import Skeleton from "react-loading-skeleton"; -import Footer from "../../../components/footer"; +import Footer from "@/components/shared/footer"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; import Head from "next/head"; -import MobileNav from "../../../components/shared/MobileNav"; +import MobileNav from "@/components/shared/MobileNav"; export default function TrendingAnime({ sessions }) { const [data, setData] = useState(null); diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js index aa0b672..f5b4fce 100644 --- a/pages/en/anime/watch/[...info].js +++ b/pages/en/anime/watch/[...info].js @@ -1,38 +1,156 @@ +import React, { useEffect, useRef, useState } from "react"; +import PlayerComponent from "@/components/watch/player/playerComponent"; +import { FlagIcon, ShareIcon } from "@heroicons/react/24/solid"; +import Details from "@/components/watch/primary/details"; +import EpisodeLists from "@/components/watch/secondary/episodeLists"; +import { getServerSession } from "next-auth"; +import { useWatchProvider } from "@/lib/hooks/watchPageProvider"; +import { authOptions } from "../../../api/auth/[...nextauth]"; +import { createList, createUser, getEpisode } from "@/prisma/user"; +import Link from "next/link"; +import MobileNav from "@/components/shared/MobileNav"; +import { NewNavbar } from "@/components/shared/NavBar"; +import Modal from "@/components/modal"; +import AniList from "@/components/media/aniList"; +import { signIn } from "next-auth/react"; +import BugReportForm from "@/components/shared/bugReport"; +import Skeleton from "react-loading-skeleton"; import Head from "next/head"; -import { useEffect, useState } from "react"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../../../api/auth/[...nextauth]"; +export async function getServerSideProps(context) { + let userData = null; + const session = await getServerSession(context.req, context.res, authOptions); + const accessToken = session?.user?.token || null; + + const query = context?.query; + if (!query) { + return { + notFound: true, + }; + } -import Navigasi from "../../../../components/home/staticNav"; -import PrimarySide from "../../../../components/anime/watch/primarySide"; -import SecondarySide from "../../../../components/anime/watch/secondarySide"; -import { createList, createUser, getEpisode } from "../../../../prisma/user"; + const proxy = process.env.PROXY_URI; + const disqus = process.env.DISQUS_SHORTNAME; -export default function Info({ - sessions, + const [aniId, provider] = query?.info; + const watchId = query?.id; + const epiNumber = query?.num; + const dub = query?.dub; + + const ress = await fetch(`https://graphql.anilist.co`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + body: JSON.stringify({ + query: `query ($id: Int) { + Media (id: $id) { + mediaListEntry { + progress + status + customLists + repeat + } + id + idMal + title { + romaji + english + native + } + status + genres + episodes + studios { + edges { + node { + id + name + } + } + } + bannerImage + description + coverImage { + extraLarge + color + } + synonyms + + } + } + `, + variables: { + id: aniId, + }, + }), + }); + const data = await ress.json(); + + try { + if (session) { + await createUser(session.user.name); + await createList(session.user.name, watchId); + const data = await getEpisode(session.user.name, watchId); + userData = JSON.parse( + JSON.stringify(data, (key, value) => { + if (key === "createdDate") { + return String(value); + } + return value; + }) + ); + } + } catch (error) { + console.error(error); + // Handle the error here + } + return { + props: { + sessions: session, + provider: provider || null, + watchId: watchId || null, + epiNumber: epiNumber || null, + dub: dub || null, + userData: userData?.[0] || null, + info: data.data.Media || null, + proxy, + disqus, + }, + }; +} + +export default function Watch({ + info, watchId, - provider, - epiNumber, + disqus, + proxy, dub, - info, userData, - proxy, - disqus, + sessions, + provider, + epiNumber, }) { - const [currentEpisode, setCurrentEpisode] = useState(null); - const [loading, setLoading] = useState(false); - const [artStorage, setArtStorage] = useState(null); + + const [episodeNavigation, setEpisodeNavigation] = useState(null); const [episodesList, setepisodesList] = useState(); - const [mapProviders, setMapProviders] = useState(null); + const [mapEpisode, setMapEpisode] = useState(null); + + const [episodeSource, setEpisodeSource] = useState(null); + + const [open, setOpen] = useState(false); + const [isOpen, setIsOpen] = useState(false); const [onList, setOnList] = useState(false); - const [origin, setOrigin] = useState(null); + + const { theaterMode, setPlayerState, setAutoPlay, setMarked } = + useWatchProvider(); + + const playerRef = useRef(null); useEffect(() => { - setLoading(true); - setOrigin(window.location.origin); async function getInfo() { if (info.mediaListEntry) { setOnList(true); @@ -56,7 +174,7 @@ export default function Info({ }); } - setMapProviders(getMap?.episodes); + setMapEpisode(getMap?.episodes); } if (episodes) { @@ -79,207 +197,319 @@ export default function Info({ const previousEpisode = episodeList?.find( (i) => i.number === parseInt(epiNumber) - 1 ); - setCurrentEpisode({ + setEpisodeNavigation({ prev: previousEpisode, playing: { id: currentEpisode.id, title: playingData?.title, description: playingData?.description, - image: playingData?.image, + img: playingData?.img || playingData?.image, number: currentEpisode.number, }, next: nextEpisode, }); - } else { - setLoading(false); } } setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); // setEpiData(episodes); - setLoading(false); } getInfo(); return () => { - setCurrentEpisode(null); + setEpisodeNavigation(null); }; }, [sessions?.user?.name, epiNumber, dub]); + useEffect(() => { + async function fetchData() { + if (info) { + const autoplay = + localStorage.getItem("autoplay_video") === "true" ? true : false; + setAutoPlay(autoplay); + + const anify = await fetch("/api/v2/source", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + source: + provider === "gogoanime" && !watchId.startsWith("/") + ? "consumet" + : "anify", + providerId: provider, + watchId: watchId, + episode: epiNumber, + id: info.id, + sub: dub ? "dub" : "sub", + }), + }).then((res) => res.json()); + + const skip = await fetch( + `https://api.aniskip.com/v2/skip-times/${info.idMal}/${parseInt( + epiNumber + )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` + ).then((res) => { + if (!res.ok) { + switch (res.status) { + case 404: { + return null; + } + } + } + return res.json(); + }); + + const op = + skip?.results?.find((item) => item.skipType === "op") || null; + const ed = + skip?.results?.find((item) => item.skipType === "ed") || null; + + const episode = { + epiData: anify, + skip: { + op, + ed, + }, + }; + + setEpisodeSource(episode); + } + } + + fetchData(); + return () => { + setEpisodeSource(); + setPlayerState({ + currentTime: 0, + isPlaying: false, + }); + setMarked(0); + }; + }, [provider, watchId, info?.id]); + + const handleShareClick = async () => { + try { + if (navigator.share) { + await navigator.share({ + title: `Watch Now - ${info?.title?.english || info.title.romaji}`, + // text: `Watch [${info?.title?.romaji}] and more on Moopa. Join us for endless anime entertainment"`, + url: window.location.href, + }); + } else { + // Web Share API is not supported, provide a fallback or show a message + alert("Web Share API is not supported in this browser."); + } + } catch (error) { + console.error("Error sharing:", error); + } + }; + + function handleOpen() { + setOpen(true); + document.body.style.overflow = "hidden"; + } + + function handleClose() { + setOpen(false); + document.body.style.overflow = "auto"; + } + return ( <> <Head> - <title>{info?.title?.romaji || "Retrieving data..."}</title> + <title> + {episodeNavigation?.playing?.title || + `${info?.title?.romaji} - Episode ${epiNumber}`} + </title> + {/* Write the best SEO for this watch page with data of anime title from info.title.romaji, episode title from episodeNavigation?.playing?.title, description from episodeNavigation?.playing?.description, episode number from epiNumber */} + <meta name="twitter:card" content="summary_large_image" /> + {/* Write the best SEO for this homepage */} <meta - name="title" - data-title-romaji={info?.title?.romaji} - data-title-english={info?.title?.english} - data-title-native={info?.title?.native} + name="description" + content={episodeNavigation?.playing?.description || info?.description} /> <meta - name="description" - content={currentEpisode?.playing?.description || info?.description} + name="keywords" + content="anime, anime streaming, anime streaming website, anime streaming free, anime streaming website free, anime streaming website free english subbed, anime streaming website free english dubbed, anime streaming website free english subbed and dubbed, anime streaming webs + ite free english subbed and dubbed download, anime streaming website free english subbed and dubbed" /> + <meta name="robots" content="index, follow" /> + + <meta property="og:type" content="website" /> + <meta property="og:url" content="https://moopa.live/" /> + <meta + property="og:title" + content={`Watch - ${ + episodeNavigation?.playing?.title || info?.title?.english + }`} + /> + <meta + property="og:description" + content="Discover your new favorite anime or manga title! Moopa offers a vast library of high-quality content, accessible on multiple devices and without any interruptions. Start using Moopa today!" + /> + <meta property="og:image" content="/preview.png" /> + <meta property="og:site_name" content="Moopa" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" - content={`Episode ${epiNumber} - ${ - info.title.romaji || info.title.english + content={`Watch - ${ + episodeNavigation?.playing?.title || info?.title?.english }`} /> <meta name="twitter:description" - content={`${ - currentEpisode?.playing?.description?.slice(0, 180) || - info?.description?.slice(0, 180) - }...`} - /> - <meta - name="twitter:image" - content={`${origin}/api/og?title=${ - info.title.romaji || info.title.english - }&image=${info.bannerImage || info.coverImage.extraLarge}`} + content={episodeNavigation?.playing?.description || info?.description} /> </Head> + <Modal open={open} onClose={() => handleClose()}> + {!sessions && ( + <div className="flex-center flex-col gap-5 px-10 py-5 bg-secondary rounded-md"> + <h1 className="text-md font-extrabold font-karla"> + Edit your list + </h1> + <button + className="flex items-center bg-[#363642] rounded-md text-white p-1" + onClick={() => signIn("AniListProvider")} + > + <h1 className="px-1 font-bold font-karla">Login with AniList</h1> + <div className="scale-[60%] pb-[1px]"> + <AniList /> + </div> + </button> + </div> + )} + </Modal> + <BugReportForm isOpen={isOpen} setIsOpen={setIsOpen} /> + <main className="w-screen h-full"> + <NewNavbar + scrollP={20} + withNav={true} + shrink={true} + paddingY={`py-2 ${theaterMode ? "" : "lg:py-4"}`} + /> + <MobileNav hideProfile={true} sessions={sessions} /> + <div + className={`mx-auto pt-16 ${theaterMode ? "lg:pt-16" : "lg:pt-20"}`} + > + {theaterMode && ( + <PlayerComponent + id={"cinematic"} + session={sessions} + playerRef={playerRef} + dub={dub} + info={info} + watchId={watchId} + proxy={proxy} + track={episodeNavigation} + data={episodeSource?.epiData} + skip={episodeSource?.skip} + timeWatched={userData?.timeWatched} + provider={provider} + className="w-screen max-h-[85dvh]" + /> + )} + <div + id="default" + className={`${ + theaterMode ? "lg:max-w-[80%]" : "lg:max-w-[95%]" + } w-full flex flex-col lg:flex-row mx-auto`} + > + <div id="primary" className="w-full"> + {!theaterMode && ( + <PlayerComponent + id={"default"} + session={sessions} + playerRef={playerRef} + dub={dub} + info={info} + watchId={watchId} + proxy={proxy} + track={episodeNavigation} + data={episodeSource?.epiData} + skip={episodeSource?.skip} + timeWatched={userData?.timeWatched} + provider={provider} + /> + )} + <div + id="details" + className="flex flex-col gap-5 w-full px-3 lg:px-0" + > + <div className="flex items-end justify-between pt-3 border-b-2 border-secondary pb-2"> + <div className="w-[55%]"> + <div className="flex font-outfit font-semibold text-lg lg:text-2xl text-white line-clamp-1"> + <Link + href={`/en/anime/${info?.id}`} + className="hover:underline line-clamp-1" + > + {(episodeNavigation?.playing?.title || + info.title.romaji) ?? + "Loading..."} + </Link> + </div> + <p className="font-karla"> + {episodeNavigation?.playing?.number ? ( + `Episode ${episodeNavigation?.playing?.number}` + ) : ( + <Skeleton width={120} height={16} /> + )} + </p> + </div> + <div> + <div className="flex gap-2 text-sm"> + <button + type="button" + onClick={handleShareClick} + className="flex items-center gap-2 px-3 py-1 ring-[1px] ring-white/20 rounded overflow-hidden" + > + <ShareIcon className="w-5 h-5" /> + share + </button> + <button + type="button" + onClick={() => setIsOpen(true)} + className="flex items-center gap-2 px-3 py-1 ring-[1px] ring-white/20 rounded overflow-hidden" + > + <FlagIcon className="w-5 h-5" /> + report + </button> + </div> + </div> + {/* <div>right</div> */} + </div> - <Navigasi /> - <div className="w-screen flex justify-center my-3 lg:my-10"> - <div className="lg:w-[95%] flex flex-col lg:flex-row gap-5 lg:gap-0 justify-between"> - <PrimarySide - info={info} - navigation={currentEpisode} - episodeList={episodesList} - session={sessions} - epiNumber={epiNumber} - providerId={provider} - watchId={watchId} - onList={onList} - proxy={proxy} - disqus={disqus} - setOnList={setOnList} - setLoading={setLoading} - loading={loading} - timeWatched={userData?.timeWatched} - dub={dub} - /> - <SecondarySide - info={info} - map={mapProviders} - providerId={provider} - watchId={watchId} - episode={episodesList} - artStorage={artStorage} - dub={dub} - /> + <Details + info={info} + session={sessions} + description={info?.description} + epiNumber={epiNumber} + id={info} + onList={onList} + setOnList={setOnList} + handleOpen={() => handleOpen()} + disqus={disqus} + /> + </div> + </div> + <div + id="secondary" + className={`relative ${theaterMode ? "pt-2" : ""}`} + > + <EpisodeLists + info={info} + map={mapEpisode} + providerId={provider} + watchId={watchId} + episode={episodesList} + artStorage={artStorage} + dub={dub} + /> + </div> + </div> </div> - </div> + </main> </> ); } - -export async function getServerSideProps(context) { - const session = await getServerSession(context.req, context.res, authOptions); - const accessToken = session?.user?.token || null; - - const query = context.query; - if (!query) { - return { - notFound: true, - }; - } - - const proxy = process.env.PROXY_URI; - const disqus = process.env.DISQUS_SHORTNAME; - - const [aniId, provider] = query.info; - const watchId = query.id; - const epiNumber = query.num; - const dub = query.dub; - - let userData = null; - - const ress = await fetch(`https://graphql.anilist.co`, { - method: "POST", - headers: { - "Content-Type": "application/json", - ...(accessToken && { Authorization: `Bearer ${accessToken}` }), - }, - body: JSON.stringify({ - query: `query ($id: Int) { - Media (id: $id) { - mediaListEntry { - progress - status - customLists - repeat - } - id - idMal - title { - romaji - english - native - } - status - genres - episodes - studios { - edges { - node { - id - name - } - } - } - bannerImage - description - coverImage { - extraLarge - color - } - synonyms - - } - } - `, - variables: { - id: aniId, - }, - }), - }); - const data = await ress.json(); - - try { - if (session) { - await createUser(session.user.name); - await createList(session.user.name, watchId); - const data = await getEpisode(session.user.name, watchId); - userData = JSON.parse( - JSON.stringify(data, (key, value) => { - if (key === "createdDate") { - return String(value); - } - return value; - }) - ); - } - } catch (error) { - console.error(error); - // Handle the error here - } - - return { - props: { - sessions: session, - aniId: aniId || null, - provider: provider || null, - watchId: watchId || null, - epiNumber: epiNumber || null, - dub: dub || null, - userData: userData?.[0] || null, - info: data.data.Media || null, - proxy, - disqus, - }, - }; -} diff --git a/pages/en/contact.js b/pages/en/contact.js index 400a9e8..385bdb1 100644 --- a/pages/en/contact.js +++ b/pages/en/contact.js @@ -1,8 +1,10 @@ -import Layout from "../../components/layout"; +import { NewNavbar } from "@/components/shared/NavBar"; +import Footer from "@/components/shared/footer"; const Contact = () => { return ( - <Layout className=""> + <> + <NewNavbar withNav={true} scrollP={5} shrink={true} /> <div className=" flex h-screen w-screen flex-col items-center justify-center font-karla font-bold"> <h1>Contact Us</h1> <p>If you have any questions or comments, please email us at:</p> @@ -12,7 +14,8 @@ const Contact = () => { </a> </p> </div> - </Layout> + <Footer /> + </> ); }; diff --git a/pages/en/dmca.js b/pages/en/dmca.js index d6d7ccf..e559829 100644 --- a/pages/en/dmca.js +++ b/pages/en/dmca.js @@ -1,5 +1,7 @@ +import MobileNav from "@/components/shared/MobileNav"; +import { NewNavbar } from "@/components/shared/NavBar"; +import Footer from "@/components/shared/footer"; import Head from "next/head"; -import Layout from "../../components/layout"; export default function DMCA() { return ( @@ -18,11 +20,14 @@ export default function DMCA() { <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="icon" href="/svg/c.svg" /> </Head> - <Layout> + <> + <NewNavbar withNav={true} scrollP={5} shrink={true} /> + + <MobileNav hideProfile={true} /> <div className="min-h-screen z-20 flex w-screen justify-center items-center"> - <div className="w-[75%] text-2xl gap-7 flex flex-col my-[10rem]"> + <div className="px-5 lg:px-0 lg:w-[75%] text-2xl gap-7 flex flex-col my-[10rem]"> <div className="flex"> - <h1 className="text-4xl font-bold font-karla rounded-md bg-[#212121] p-3"> + <h1 className="text-4xl font-bold font-karla rounded-md bg-secondary p-3"> DMCA - Disclaimer </h1> </div> @@ -100,7 +105,8 @@ export default function DMCA() { </div> </div> </div> - </Layout> + <Footer /> + </> </> ); } diff --git a/pages/en/index.js b/pages/en/index.js index 0ef8d27..d4f5584 100644 --- a/pages/en/index.js +++ b/pages/en/index.js @@ -1,26 +1,24 @@ -import { aniListData } from "../../lib/anilist/AniList"; +import { aniListData } from "@/lib/anilist/AniList"; import { useState, useEffect, Fragment } from "react"; import Head from "next/head"; import Link from "next/link"; -import Footer from "../../components/footer"; +import Footer from "@/components/shared/footer"; import Image from "next/image"; -import Content from "../../components/home/content"; +import Content from "@/components/home/content"; import { motion } from "framer-motion"; import { signOut, useSession } from "next-auth/react"; -import Genres from "../../components/home/genres"; -import Schedule from "../../components/home/schedule"; -import getUpcomingAnime from "../../lib/anilist/getUpcomingAnime"; +import Genres from "@/components/home/genres"; +import Schedule from "@/components/home/schedule"; +import getUpcomingAnime from "@/lib/anilist/getUpcomingAnime"; -import Navigasi from "../../components/home/staticNav"; - -import { ToastContainer } from "react-toastify"; -import getMedia from "../../lib/anilist/getMedia"; +import GetMedia from "@/lib/anilist/getMedia"; // import UserRecommendation from "../../components/home/recommendation"; -import MobileNav from "../../components/shared/MobileNav"; -import { getGreetings } from "../../utils/getGreetings"; -import redis from "../../lib/redis"; +import MobileNav from "@/components/shared/MobileNav"; +import { getGreetings } from "@/utils/getGreetings"; +import { redis } from "@/lib/redis"; +import { NewNavbar } from "@/components/shared/NavBar"; export async function getServerSideProps() { let cachedData; @@ -79,9 +77,11 @@ export async function getServerSideProps() { export default function Home({ detail, populars, upComing }) { const { data: sessions } = useSession(); - const { media: current } = getMedia(sessions, { stats: "CURRENT" }); - const { media: plan } = getMedia(sessions, { stats: "PLANNING" }); - const { media: release, recommendations } = getMedia(sessions); + const { anime: currentAnime, manga: currentManga } = GetMedia(sessions, { + stats: "CURRENT", + }); + const { anime: plan } = GetMedia(sessions, { stats: "PLANNING" }); + const { anime: release } = GetMedia(sessions); const [schedules, setSchedules] = useState(null); const [anime, setAnime] = useState([]); @@ -89,7 +89,9 @@ export default function Home({ detail, populars, upComing }) { const [recentAdded, setRecentAdded] = useState([]); async function getRecent() { - const data = await fetch(`/api/v2/etc/recent/1`).then((res) => res.json()); + const data = await fetch(`/api/v2/etc/recent/1`) + .then((res) => res.json()) + .catch((err) => console.log(err)); setRecentAdded(data?.results); } @@ -118,13 +120,17 @@ export default function Home({ detail, populars, upComing }) { useEffect(() => { const getSchedule = async () => { - const res = await fetch(`/api/v2/etc/schedule`); - const data = await res.json(); + try { + const res = await fetch(`/api/v2/etc/schedule`); + const data = await res.json(); - if (!res.ok) { - setSchedules(null); - } else { - setSchedules(data); + if (!res.ok) { + setSchedules(null); + } else { + setSchedules(data); + } + } catch (err) { + console.log(err); } }; getSchedule(); @@ -155,7 +161,8 @@ export default function Home({ detail, populars, upComing }) { getRelease(); }, [release]); - const [list, setList] = useState(null); + const [listAnime, setListAnime] = useState(null); + const [listManga, setListManga] = useState(null); const [planned, setPlanned] = useState(null); const [user, setUser] = useState(null); const [removed, setRemoved] = useState(); @@ -257,8 +264,14 @@ export default function Home({ detail, populars, upComing }) { if (!sessions?.user?.name) return; const getMedia = - current.filter((item) => item.status === "CURRENT")[0] || null; - const list = getMedia?.entries + currentAnime.find((item) => item.status === "CURRENT") || null; + const listAnime = getMedia?.entries + .map(({ media }) => media) + .filter((media) => media); + + const getManga = + currentManga?.find((item) => item.status === "CURRENT") || null; + const listManga = getManga?.entries .map(({ media }) => media) .filter((media) => media); @@ -266,15 +279,20 @@ export default function Home({ detail, populars, upComing }) { .map(({ media }) => media) .filter((media) => media); - if (list) { - setList(list); + if (listManga) { + setListManga(listManga); + } + if (listAnime) { + setListAnime(listAnime); } if (planned) { setPlanned(planned); } } userData(); - }, [sessions?.user?.name, current, plan]); + }, [sessions?.user?.name, currentAnime, plan]); + + // console.log({ recentAdded }); return ( <Fragment> @@ -321,15 +339,8 @@ export default function Home({ detail, populars, upComing }) { </Head> <MobileNav sessions={sessions} hideProfile={true} /> + <NewNavbar paddingY="pt-2 lg:pt-10" withNav={true} home={true} /> <div className="h-auto w-screen bg-[#141519] text-[#dbdcdd]"> - <Navigasi /> - <ToastContainer - pauseOnHover={false} - style={{ - width: "400px", - }} - /> - {/* 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"> @@ -359,8 +370,8 @@ export default function Home({ detail, populars, upComing }) { draggable={false} src={data.coverImage?.extraLarge || data.image} alt={`cover ${data.title.english || data.title.romaji}`} - width="0" - height="0" + width={1200} + height={1200} priority className="rounded-tl-xl rounded-tr-xl object-cover bg-blend-overlay lg:h-[467px] lg:w-[322px]" /> @@ -434,7 +445,7 @@ export default function Home({ detail, populars, upComing }) { </motion.section> )} - {sessions && list?.length > 0 && ( + {sessions && listAnime?.length > 0 && ( <motion.section // Add motion.div to each child component key="listAnime" initial={{ y: 20, opacity: 0 }} @@ -445,13 +456,31 @@ export default function Home({ detail, populars, upComing }) { <Content ids="listAnime" section="Your Watch List" - data={list} + data={listAnime} og={prog} userName={sessions?.user?.name} /> </motion.section> )} + {sessions && listManga?.length > 0 && ( + <motion.section // Add motion.div to each child component + key="listManga" + initial={{ y: 20, opacity: 0 }} + whileInView={{ y: 0, opacity: 1 }} + transition={{ duration: 0.5 }} + viewport={{ once: true }} + > + <Content + ids="listManga" + section="Your Manga List" + data={listManga} + // og={prog} + userName={sessions?.user?.name} + /> + </motion.section> + )} + {/* {recommendations.length > 0 && ( <div className="space-y-5 mb-10"> <div className="px-5"> diff --git a/pages/en/manga/[id].js b/pages/en/manga/[id].js index e928bd4..6f25532 100644 --- a/pages/en/manga/[id].js +++ b/pages/en/manga/[id].js @@ -1,14 +1,14 @@ -import ChapterSelector from "../../../components/manga/chapters"; -import HamburgerMenu from "../../../components/manga/mobile/hamburgerMenu"; -import Navbar from "../../../components/navbar"; -import TopSection from "../../../components/manga/info/topSection"; -import Footer from "../../../components/footer"; +import ChapterSelector from "@/components/manga/chapters"; +import HamburgerMenu from "@/components/manga/mobile/hamburgerMenu"; +import TopSection from "@/components/manga/info/topSection"; +import Footer from "@/components/shared/footer"; import Head from "next/head"; import { useEffect, useState } from "react"; import { setCookie } from "nookies"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; -import getAnifyInfo from "../../../lib/anify/info"; +import getAnifyInfo from "@/lib/anify/info"; +import { NewNavbar } from "@/components/shared/NavBar"; export default function Manga({ info, userManga }) { const [domainUrl, setDomainUrl] = useState(""); @@ -53,7 +53,7 @@ export default function Manga({ info, userManga }) { </Head> <div className="min-h-screen w-screen flex flex-col items-center relative"> <HamburgerMenu /> - <Navbar className="absolute top-0 w-full z-40" /> + <NewNavbar info={info} manga={true} /> <div className="flex flex-col w-screen items-center gap-5 md:gap-10 py-10 pt-nav"> <div className="flex-center w-full relative z-30"> <TopSection info={info} firstEp={firstEp} setCookie={setCookie} /> diff --git a/pages/en/manga/read/[...params].js b/pages/en/manga/read/[...params].js index b71f8a7..a7769e2 100644 --- a/pages/en/manga/read/[...params].js +++ b/pages/en/manga/read/[...params].js @@ -1,20 +1,19 @@ import { useEffect, useRef, useState } from "react"; -import { LeftBar } from "../../../../components/manga/leftBar"; +import { LeftBar } from "@/components/manga/leftBar"; import { useRouter } from "next/router"; -import RightBar from "../../../../components/manga/rightBar"; -import FirstPanel from "../../../../components/manga/panels/firstPanel"; -import SecondPanel from "../../../../components/manga/panels/secondPanel"; -import ThirdPanel from "../../../../components/manga/panels/thirdPanel"; +import RightBar from "@/components/manga/rightBar"; +import FirstPanel from "@/components/manga/panels/firstPanel"; +import SecondPanel from "@/components/manga/panels/secondPanel"; +import ThirdPanel from "@/components/manga/panels/thirdPanel"; import { getServerSession } from "next-auth"; import { authOptions } from "../../../api/auth/[...nextauth]"; -import BottomBar from "../../../../components/manga/mobile/bottomBar"; -import TopBar from "../../../../components/manga/mobile/topBar"; -import { ToastContainer } from "react-toastify"; +import BottomBar from "@/components/manga/mobile/bottomBar"; +import TopBar from "@/components/manga/mobile/topBar"; import Head from "next/head"; import nookies from "nookies"; -import ShortCutModal from "../../../../components/manga/modals/shortcutModal"; -import ChapterModal from "../../../../components/manga/modals/chapterModal"; -import getAnifyPage from "../../../../lib/anify/page"; +import ShortCutModal from "@/components/manga/modals/shortcutModal"; +import ChapterModal from "@/components/manga/modals/chapterModal"; +import getAnifyPage from "@/lib/anify/page"; export default function Read({ data, currentId, sessions }) { const [info, setInfo] = useState(); @@ -121,7 +120,6 @@ export default function Read({ data, currentId, sessions }) { <meta id="CoverImage" data-manga-cover={info?.coverImage} /> </Head> <div className="w-screen flex justify-evenly relative"> - <ToastContainer pauseOnFocusLoss={false} /> <ShortCutModal isOpen={isKeyOpen} setIsOpen={setIsKeyOpen} /> <ChapterModal id={info?.id} diff --git a/pages/en/profile/[user].js b/pages/en/profile/[user].js index 2961328..b931597 100644 --- a/pages/en/profile/[user].js +++ b/pages/en/profile/[user].js @@ -1,12 +1,12 @@ 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 { useEffect, useState } from "react"; -import { getUser } from "../../../prisma/user"; -import { ToastContainer, toast } from "react-toastify"; +import { getUser } from "@/prisma/user"; +import { toast } from "react-toastify"; +import { NewNavbar } from "@/components/shared/NavBar"; export default function MyList({ media, sessions, user, time, userSettings }) { const [listFilter, setListFilter] = useState("all"); @@ -57,8 +57,7 @@ export default function MyList({ media, sessions, user, time, userSettings }) { <Head> <title>My Lists</title> </Head> - <Navbar /> - <ToastContainer pauseOnHover={false} /> + <NewNavbar /> <div className="w-screen lg:flex justify-between lg:px-10 xl:px-32 py-5 relative"> <div className="lg:w-[30%] h-full mt-12 lg:mr-10 grid gap-5 mx-3 lg:mx-0 antialiased"> diff --git a/pages/en/schedule/index.js b/pages/en/schedule/index.js index ddb0a49..f1e6730 100644 --- a/pages/en/schedule/index.js +++ b/pages/en/schedule/index.js @@ -1,24 +1,24 @@ import Image from "next/image"; import { useEffect, useRef, useState } from "react"; -import { NewNavbar } from "../../../components/anime/mobile/topSection"; import Link from "next/link"; import { CalendarIcon } from "@heroicons/react/24/solid"; import { ClockIcon } from "@heroicons/react/24/outline"; -import Loading from "../../../components/shared/loading"; -import { timeStamptoAMPM, timeStamptoHour } from "../../../utils/getTimes"; +import Loading from "@/components/shared/loading"; +import { timeStamptoAMPM, timeStamptoHour } from "@/utils/getTimes"; import { filterFormattedSchedule, filterScheduleByDay, sortScheduleByDay, transformSchedule, -} from "../../../utils/schedulesUtils"; +} from "@/utils/schedulesUtils"; -import { scheduleQuery } from "../../../lib/graphql/query"; -import MobileNav from "../../../components/shared/MobileNav"; +import { scheduleQuery } from "@/lib/graphql/query"; +import MobileNav from "@/components/shared/MobileNav"; import { useSession } from "next-auth/react"; -import redis from "../../../lib/redis"; +import { redis } from "@/lib/redis"; import Head from "next/head"; +import { NewNavbar } from "@/components/shared/NavBar"; const day = [ "Sunday", @@ -64,9 +64,6 @@ export async function getServerSideProps() { if (cachedData) { const scheduleByDay = JSON.parse(cachedData); - // const today = now.getDay(); - // const todaySchedule = day[today]; - return { props: { schedule: scheduleByDay, @@ -81,21 +78,6 @@ export async function getServerSideProps() { const weekStart = yesterdayStart; const weekEnd = weekStart + 604800; - // const today = now.getDay(); - // const todaySchedule = day[today]; - - // const now = new Date(); - // const currentDayOfWeek = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday - - // // Calculate the number of seconds until the current Saturday at 00:00:00 - // const secondsUntilSaturday = (6 - currentDayOfWeek) * 24 * 60 * 60; - - // // Calculate weekStart as the current time minus secondsUntilSaturday - // const weekStart = Math.floor(now.getTime() / 1000) - secondsUntilSaturday; - - // // Calculate weekEnd as one week from weekStart - // const weekEnd = weekStart + 604800; // One week in seconds - let page = 1; const airingSchedules = []; @@ -283,8 +265,8 @@ export default function Schedule({ schedule }) { /> </Head> <MobileNav sessions={session} hideProfile={true} /> + <NewNavbar scrollP={10} toTop={true} /> <div className="w-screen"> - <NewNavbar scrollP={10} session={session} toTop={true} /> <span className="absolute z-20 top-0 left-0 w-screen h-[190px] lg:h-[250px] bg-secondary overflow-hidden"> <div className="absolute top-40 lg:top-36 w-full h-full bg-primary rounded-t-3xl xl:rounded-t-[50px]" /> </span> @@ -376,13 +358,13 @@ export default function Schedule({ schedule }) { key={m.id} // id={`same_${m.id}`} href={`/en/${m.type.toLowerCase()}/${m.id}`} - className={`flex bg-secondary rounded group cursor-pointer ml-4 z-50`} + className={`flex bg-secondary rounded group cursor-pointer overflow-hidden ml-4 z-50`} > <Image src={m.coverImage.extraLarge} alt="image" - width="0" - height="0" + width={300} + height={300} className="w-[50px] h-[65px] object-cover shrink-0" /> <div className="flex flex-col justify-center font-karla p-2"> @@ -469,31 +451,12 @@ export default function Schedule({ schedule }) { <span className="tooltip">Airing Now</span> </span> </p> - {/* <span - className={`${ - s.id === nextAiringAnime - ? "bg-orange-700 text-sm px-3 py-1 rounded-full font-bold text-white" - : "" - } mx-auto`} - > - Airing Next - </span> */} - {/* </p> */} - {/* {s.media?.bannerImage && ( - <Image - src={s.media?.bannerImage} - alt="banner" - width="0" - height="0" - className="absolute pointer-events-none top-0 opacity-0 group-hover:opacity-10 transition-all duration-500 ease-linear -z-10 left-0 rounded-l w-full h-[250px] object-cover" - /> - )} */} <Image src={m.coverImage.extraLarge} alt="image" - width="0" - height="0" - className="w-[50px] h-[65px] object-cover shrink-0" + width={200} + height={200} + className="w-[50px] h-[65px] object-cover shrink-0 rounded-l" /> <div className="flex flex-col justify-center font-karla p-2"> <h1 className="font-semibold line-clamp-1 text-sm group-hover:text-action transition-all duration-200 ease-out"> diff --git a/pages/en/search/[...param].js b/pages/en/search/[...param].js index 2ec7681..603cd17 100644 --- a/pages/en/search/[...param].js +++ b/pages/en/search/[...param].js @@ -3,14 +3,13 @@ import { AnimatePresence, motion as m } from "framer-motion"; import Skeleton from "react-loading-skeleton"; import { useRouter } from "next/router"; import Link from "next/link"; -import Navbar from "../../../components/navbar"; import Head from "next/head"; -import Footer from "../../../components/footer"; +import Footer from "@/components/shared/footer"; import Image from "next/image"; -import { aniAdvanceSearch } from "../../../lib/anilist/aniAdvanceSearch"; -import MultiSelector from "../../../components/search/dropdown/multiSelector"; -import SingleSelector from "../../../components/search/dropdown/singleSelector"; +import { aniAdvanceSearch } from "@/lib/anilist/aniAdvanceSearch"; +import MultiSelector from "@/components/search/dropdown/multiSelector"; +import SingleSelector from "@/components/search/dropdown/singleSelector"; import { animeFormatOptions, formatOptions, @@ -20,12 +19,12 @@ import { seasonOptions, tagsOption, yearOptions, -} from "../../../components/search/selection"; -import InputSelect from "../../../components/search/dropdown/inputSelect"; +} from "@/components/search/selection"; +import InputSelect from "@/components/search/dropdown/inputSelect"; import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/20/solid"; -import useDebounce from "../../../lib/hooks/useDebounce"; -// import { NewNavbar } from "../../../components/anime/mobile/topSection"; -// import { useSession } from "next-auth/react"; +import useDebounce from "@/lib/hooks/useDebounce"; +import { NewNavbar } from "@/components/shared/NavBar"; +import MobileNav from "@/components/shared/MobileNav"; export async function getServerSideProps(context) { const { param } = context.query; @@ -211,9 +210,15 @@ export default function Card({ <meta name="description" content="Search your favourites Anime/Manga" /> <link rel="icon" href="/svg/c.svg" /> </Head> - <Navbar /> - {/* <NewNavbar session={session} /> */} - <main className="w-screen min-h-screen z-40"> + + <NewNavbar + scrollP={10} + withNav={true} + shrink={true} + paddingY="py-1 lg:py-3" + /> + <MobileNav hideProfile={true} /> + <main className="w-screen min-h-screen z-40 py-14 lg:py-24"> <div className="max-w-screen-xl flex flex-col gap-3 mx-auto"> <div className="w-full flex justify-between items-end gap-2 my-3 lg:gap-10 px-5 xl:px-0 relative"> <div className="hidden lg:flex items-end w-full gap-5 z-50"> diff --git a/pages/id/index.js b/pages/id/index.js index 661bc05..b8898e5 100644 --- a/pages/id/index.js +++ b/pages/id/index.js @@ -1,9 +1,9 @@ import Head from "next/head"; import React from "react"; -import Navbar from "../../components/navbar"; import Image from "next/image"; import Link from "next/link"; -import Footer from "../../components/footer"; +import Footer from "@/components/shared/footer"; +import { NewNavbar } from "@/components/shared/NavBar"; export default function Home() { return ( @@ -15,7 +15,7 @@ export default function Home() { <link rel="icon" href="/svg/c.svg" /> </Head> <main className="flex flex-col h-screen"> - <Navbar className="bg-[#0c0d10] z-50" /> + <NewNavbar /> {/* Create an under construction page with tailwind css */} <div className="h-full w-screen flex-center flex-grow flex-col"> <Image @@ -26,7 +26,7 @@ export default function Home() { className="w-[26vw] md:w-[15vw]" /> <h1 className="text-2xl sm:text-4xl xl:text-6x font-bold my-4"> - 🚧 We are still working on it 🚧 + 🚧 Work still on progress 🚧 </h1> <p className="text-base sm:text-lg xl:text-x text-gray-300 mb-6 text-center"> "Please be patient, as we're still working on this page and it will |