aboutsummaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-09-13 00:45:53 +0700
committerGitHub <[email protected]>2023-09-13 00:45:53 +0700
commit7327a69b55a20b99b14ee0803d6cf5f8b88c45ef (patch)
treecbcca777593a8cc4b0282e7d85a6fc51ba517e25 /components
parentUpdate issue templates (diff)
downloadmoopa-7327a69b55a20b99b14ee0803d6cf5f8b88c45ef.tar.xz
moopa-7327a69b55a20b99b14ee0803d6cf5f8b88c45ef.zip
Update v4 - Merge pre-push to main (#71)
* Create build-test.yml * initial v4 commit * update: github workflow * update: push on branch * Update .github/ISSUE_TEMPLATE/bug_report.md * configuring next.config.js file
Diffstat (limited to 'components')
-rw-r--r--components/anime/changeView.js30
-rw-r--r--components/anime/episode.js185
-rw-r--r--components/anime/infoDetails.js6
-rw-r--r--components/anime/mobile/reused/description.js44
-rw-r--r--components/anime/mobile/reused/infoChip.js43
-rw-r--r--components/anime/mobile/topSection.js504
-rw-r--r--components/anime/viewMode/listMode.js58
-rw-r--r--components/anime/viewMode/thumbnailDetail.js25
-rw-r--r--components/anime/viewMode/thumbnailOnly.js30
-rw-r--r--components/anime/watch/primarySide.js45
-rw-r--r--components/anime/watch/secondarySide.js19
-rw-r--r--components/footer.js246
-rw-r--r--components/home/content.js254
-rw-r--r--components/home/content/historyOptions.js56
-rw-r--r--components/home/genres.js4
-rw-r--r--components/home/recommendation.js91
-rw-r--r--components/home/schedule.js28
-rw-r--r--components/home/staticNav.js160
-rw-r--r--components/id/player/Artplayer.js (renamed from components/id-components/player/Artplayer.js)0
-rw-r--r--components/id/player/VideoPlayerId.js (renamed from components/id-components/player/VideoPlayerId.js)0
-rw-r--r--components/navbar.js189
-rw-r--r--components/search/dropdown/inputSelect.js111
-rw-r--r--components/search/dropdown/multiSelector.js168
-rw-r--r--components/search/dropdown/singleSelector.js98
-rw-r--r--components/search/selection.js415
-rw-r--r--components/searchBar.js155
-rw-r--r--components/searchPalette.js265
-rw-r--r--components/shared/MobileNav.js170
-rw-r--r--components/shared/hamburgerMenu.js (renamed from components/home/mobileNav.js)86
-rw-r--r--components/shared/loading.js20
-rw-r--r--components/videoPlayer.js27
31 files changed, 2661 insertions, 871 deletions
diff --git a/components/anime/changeView.js b/components/anime/changeView.js
index cab9054..75ebdff 100644
--- a/components/anime/changeView.js
+++ b/components/anime/changeView.js
@@ -1,14 +1,14 @@
-import { useEffect, useState } from "react";
-
-export default function ChangeView({ view, setView, episode }) {
- // const [view, setView] = useState(1);
- // const episode = null;
+export default function ChangeView({ view, setView, episode, map }) {
return (
<div className="flex gap-3 rounded-sm items-center p-2">
<div
className={
episode?.length > 0
- ? episode?.some((item) => item?.title === null)
+ ? map?.every(
+ (item) =>
+ item?.image?.includes("https://s4.anilist.co/") ||
+ item.title === null
+ ) || !map
? "pointer-events-none"
: "cursor-pointer"
: "pointer-events-none"
@@ -30,7 +30,11 @@ export default function ChangeView({ view, setView, episode }) {
height="20"
className={`${
episode?.length > 0
- ? episode?.some((item) => item?.title === null)
+ ? map?.every(
+ (item) =>
+ item?.image?.includes("https://s4.anilist.co/") ||
+ item.title === null
+ ) || !map
? "fill-[#1c1c22]"
: view === 1
? "fill-action"
@@ -44,7 +48,11 @@ export default function ChangeView({ view, setView, episode }) {
<div
className={
episode?.length > 0
- ? episode?.some((item) => item?.title === null)
+ ? map?.every(
+ (item) =>
+ item?.image?.includes("https://s4.anilist.co/") ||
+ item.title === null
+ ) || !map
? "pointer-events-none"
: "cursor-pointer"
: "pointer-events-none"
@@ -61,7 +69,11 @@ export default function ChangeView({ view, setView, episode }) {
fill="none"
className={`${
episode?.length > 0
- ? episode?.some((item) => item?.title === null)
+ ? map?.every(
+ (item) =>
+ item?.image?.includes("https://s4.anilist.co/") ||
+ item.title === null
+ ) || !map
? "fill-[#1c1c22]"
: view === 2
? "fill-action"
diff --git a/components/anime/episode.js b/components/anime/episode.js
index 5d3451b..b2f4bd7 100644
--- a/components/anime/episode.js
+++ b/components/anime/episode.js
@@ -1,13 +1,18 @@
import { useEffect, useState, Fragment } from "react";
-import { ChevronDownIcon, ClockIcon } from "@heroicons/react/20/solid";
-import { convertSecondsToTime } from "../../utils/getTimes";
+import { ChevronDownIcon } from "@heroicons/react/20/solid";
import ChangeView from "./changeView";
import ThumbnailOnly from "./viewMode/thumbnailOnly";
import ThumbnailDetail from "./viewMode/thumbnailDetail";
import ListMode from "./viewMode/listMode";
-import axios from "axios";
+import { convertSecondsToTime } from "../../utils/getTimes";
-export default function AnimeEpisode({ info, progress }) {
+export default function AnimeEpisode({
+ info,
+ session,
+ progress,
+ setProgress,
+ setWatch,
+}) {
const [providerId, setProviderId] = useState(); // default provider
const [currentPage, setCurrentPage] = useState(1); // for pagination
const [visible, setVisible] = useState(false); // for mobile view
@@ -19,42 +24,60 @@ export default function AnimeEpisode({ info, progress }) {
const [isDub, setIsDub] = useState(false);
const [providers, setProviders] = useState(null);
+ const [mapProviders, setMapProviders] = useState(null);
useEffect(() => {
setLoading(true);
- setProviders(null);
const fetchData = async () => {
- try {
- const { data: firstResponse } = await axios.get(
- `/api/consumet/episode/${info.id}${isDub === true ? "?dub=true" : ""}`
- );
- if (firstResponse.data.length > 0) {
- const defaultProvider = firstResponse.data?.find(
- (x) => x.providerId === "gogoanime"
- );
- setProviderId(
- defaultProvider?.providerId || firstResponse.data[0].providerId
- ); // set to first provider id
- }
+ const response = await fetch(
+ `/api/v2/episode/${info.id}?releasing=${
+ info.status === "RELEASING" ? "true" : "false"
+ }${isDub ? "&dub=true" : ""}`
+ ).then((res) => res.json());
+ const getMap = response.find((i) => i?.map === true);
+ let allProvider = response;
- setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings")));
- setProviders(firstResponse.data);
- setLoading(false);
- } catch (error) {
- setLoading(false);
- setProviders([]);
+ if (getMap) {
+ allProvider = response.filter((i) => {
+ if (i?.providerId === "gogoanime" && i?.map !== true) {
+ return null;
+ }
+ return i;
+ });
+ setMapProviders(getMap?.episodes);
}
+
+ if (allProvider.length > 0) {
+ const defaultProvider = allProvider.find(
+ (x) => x.providerId === "gogoanime" || x.providerId === "9anime"
+ );
+ setProviderId(defaultProvider?.providerId || allProvider[0].providerId); // set to first provider id
+ }
+
+ setView(Number(localStorage.getItem("view")) || 3);
+ setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings")));
+ setProviders(allProvider);
+ setLoading(false);
};
fetchData();
+
+ return () => {
+ setProviders(null);
+ setMapProviders(null);
+ };
}, [info.id, isDub]);
const episodes =
- providers?.find((provider) => provider.providerId === providerId)
- ?.episodes || [];
+ providers
+ ?.find((provider) => provider.providerId === providerId)
+ ?.episodes?.slice(0, mapProviders?.length) || [];
const lastEpisodeIndex = currentPage * itemsPerPage;
const firstEpisodeIndex = lastEpisodeIndex - itemsPerPage;
- const currentEpisodes = episodes.slice(firstEpisodeIndex, lastEpisodeIndex);
+ let currentEpisodes = episodes.slice(firstEpisodeIndex, lastEpisodeIndex);
+ if (isDub) {
+ currentEpisodes = currentEpisodes.filter((i) => i.hasDub === true);
+ }
const totalPages = Math.ceil(episodes.length / itemsPerPage);
const handleChange = (event) => {
@@ -66,36 +89,90 @@ export default function AnimeEpisode({ info, progress }) {
};
useEffect(() => {
- if (episodes?.some((item) => item?.title === null)) {
+ if (
+ !mapProviders ||
+ mapProviders?.every(
+ (item) =>
+ item?.image?.includes("https://s4.anilist.co/") ||
+ item?.image === null
+ )
+ ) {
setView(3);
}
}, [providerId, episodes]);
+ useEffect(() => {
+ if (episodes) {
+ const getEpi = info?.nextAiringEpisode
+ ? episodes.find((i) => i.number === progress + 1)
+ : episodes[0];
+ if (getEpi) {
+ const watchUrl = `/en/anime/watch/${
+ info.id
+ }/${providerId}?id=${encodeURIComponent(getEpi.id)}&num=${
+ getEpi.number
+ }${isDub ? `&dub=${isDub}` : ""}`;
+ setWatch(watchUrl);
+ } else {
+ setWatch(null);
+ }
+ }
+ }, [episodes]);
+
+ useEffect(() => {
+ if (artStorage) {
+ // console.log({ artStorage });
+ const currentData =
+ JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+
+ // Create a new object to store the updated data
+ const updatedData = {};
+
+ // Iterate through the current data and copy items with different aniId to the updated object
+ for (const key in currentData) {
+ const item = currentData[key];
+ if (Number(item.aniId) === info.id && item.provider === providerId) {
+ updatedData[key] = item;
+ }
+ }
+
+ if (!session?.user?.name) {
+ setProgress(
+ Object.keys(updatedData).length > 0
+ ? Math.max(
+ ...Object.keys(updatedData).map(
+ (key) => updatedData[key].episode
+ )
+ )
+ : 0
+ );
+ } else {
+ return;
+ }
+ }
+ }, [providerId, artStorage, info.id, session?.user?.name]);
+
return (
<>
- <div className="flex flex-col gap-5 px-3">
+ <div className="flex flex-col gap-5 px-3">
<div className="flex lg:flex-row flex-col gap-5 lg:gap-0 justify-between ">
<div className="flex justify-between">
- <div className="flex items-center lg:gap-10 sm:gap-7 gap-3">
+ <div className="flex items-center md:gap-5">
{info && (
<h1 className="text-[20px] lg:text-2xl font-bold font-karla">
Episodes
</h1>
)}
- {info?.nextAiringEpisode && (
- <div className="flex items-center gap-2">
- <div className="flex items-center gap-4 text-[10px] xxs:text-sm lg:text-base">
- <h1>Next :</h1>
- <div className="px-4 rounded-sm font-karla font-bold bg-white text-black">
- {convertSecondsToTime(
- info.nextAiringEpisode.timeUntilAiring
- )}
- </div>
- </div>
- <div className="h-6 w-6">
- <ClockIcon />
- </div>
- </div>
+ {info.nextAiringEpisode?.timeUntilAiring && (
+ <p className="hidden md:block bg-gray-100 text-gray-900 rounded-md px-2 font-karla font-medium">
+ Ep {info.nextAiringEpisode.episode}{" "}
+ <span className="animate-pulse">{">>"}</span>{" "}
+ <span className="font-bold">
+ {convertSecondsToTime(
+ info.nextAiringEpisode.timeUntilAiring
+ )}{" "}
+ </span>
+ </p>
)}
</div>
@@ -165,9 +242,6 @@ export default function AnimeEpisode({ info, progress }) {
</option>
))}
</select>
- {/* <span className="absolute invisible opacity-0 group-hover:opacity-100 group-hover:scale-100 scale-0 group-hover:-translate-y-7 translate-y-0 group-hover:visible rounded-sm shadow top-0 w-32 bg-secondary text-center transition-all transform duration-200 ease-out">
- Select Providers
- </span> */}
<ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" />
</div>
@@ -197,6 +271,7 @@ export default function AnimeEpisode({ info, progress }) {
view={view}
setView={setView}
episode={currentEpisodes}
+ map={mapProviders}
/>
</div>
</div>
@@ -204,15 +279,21 @@ export default function AnimeEpisode({ info, progress }) {
{/* Episodes */}
{!loading ? (
<div
- className={
+ className={`${
view === 1
? "grid md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-5 lg:gap-8 place-items-center"
- : `flex flex-col gap-3`
- }
+ : view === 2
+ ? "flex flex-col gap-3"
+ : `flex flex-col odd:bg-secondary even:bg-primary`
+ } py-2`}
>
{Array.isArray(providers) ? (
providers.length > 0 ? (
currentEpisodes.map((episode, index) => {
+ const mapData = mapProviders?.find(
+ (i) => i.number === episode.number
+ );
+
return (
<Fragment key={index}>
{view === 1 && (
@@ -220,17 +301,20 @@ export default function AnimeEpisode({ info, progress }) {
key={index}
index={index}
info={info}
+ image={mapData?.image}
providerId={providerId}
episode={episode}
artStorage={artStorage}
progress={progress}
dub={isDub}
- // image={thumbnail}
/>
)}
{view === 2 && (
<ThumbnailDetail
key={index}
+ image={mapData?.image}
+ title={mapData?.title}
+ description={mapData?.description}
index={index}
epi={episode}
provider={providerId}
@@ -245,7 +329,6 @@ export default function AnimeEpisode({ info, progress }) {
key={index}
info={info}
episode={episode}
- index={index}
artStorage={artStorage}
providerId={providerId}
progress={progress}
diff --git a/components/anime/infoDetails.js b/components/anime/infoDetails.js
index 814e49b..8200bfa 100644
--- a/components/anime/infoDetails.js
+++ b/components/anime/infoDetails.js
@@ -165,9 +165,7 @@ export default function DesktopDetails({
>
<div className="w-[90px] bg-image rounded-l-md shrink-0">
<Image
- src={
- rel.coverImage.extraLarge || rel.coverImage.large
- }
+ src={rel.coverImage.extraLarge}
alt={rel.id}
height={500}
width={500}
@@ -179,7 +177,7 @@ export default function DesktopDetails({
{r.relationType}
</div>
<div className="font-outfit font-thin line-clamp-2">
- {rel.title.userPreferred || rel.title.romaji}
+ {rel.title.userPreferred}
</div>
<div className={``}>{rel.type}</div>
</div>
diff --git a/components/anime/mobile/reused/description.js b/components/anime/mobile/reused/description.js
new file mode 100644
index 0000000..99973d3
--- /dev/null
+++ b/components/anime/mobile/reused/description.js
@@ -0,0 +1,44 @@
+export default function Description({
+ info,
+ readMore,
+ setReadMore,
+ className,
+}) {
+ return (
+ <div className={`${className} relative md:py-2 z-40`}>
+ <div
+ className={`${
+ info?.description?.replace(/<[^>]*>/g, "").length > 240
+ ? ""
+ : "pointer-events-none"
+ } ${
+ readMore ? "hidden" : ""
+ } absolute z-30 flex items-end justify-center top-0 w-full h-full transition-all duration-200 ease-linear md:opacity-0 md:hover:opacity-100 bg-gradient-to-b from-transparent to-primary to-95%`}
+ >
+ <button
+ type="button"
+ disabled={readMore}
+ onClick={() => setReadMore(!readMore)}
+ className="text-center font-bold text-gray-200 py-1 w-full"
+ >
+ Read {readMore ? "Less" : "More"}
+ </button>
+ </div>
+ <p
+ className={`${
+ readMore
+ ? "text-start md:h-[90px] md:overflow-y-scroll md:scrollbar-thin md:scrollbar-thumb-secondary md:scrollbar-thumb-rounded"
+ : "md:line-clamp-2 line-clamp-3 md:text-start text-center"
+ } text-sm md:text-base font-light antialiased font-karla leading-6`}
+ style={{
+ scrollbarGutter: "stable",
+ }}
+ dangerouslySetInnerHTML={{
+ __html: readMore
+ ? info?.description
+ : info?.description?.replace(/<[^>]*>/g, ""),
+ }}
+ />
+ </div>
+ );
+}
diff --git a/components/anime/mobile/reused/infoChip.js b/components/anime/mobile/reused/infoChip.js
new file mode 100644
index 0000000..41def85
--- /dev/null
+++ b/components/anime/mobile/reused/infoChip.js
@@ -0,0 +1,43 @@
+import React from "react";
+import { getFormat } from "../../../../utils/getFormat";
+
+export default function InfoChip({ info, color, className }) {
+ return (
+ <div
+ className={`flex-wrap w-full justify-start md:pt-1 gap-4 ${className}`}
+ >
+ {info?.episodes && (
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info?.episodes} Episodes
+ </div>
+ )}
+ {info?.averageScore && (
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info?.averageScore}%
+ </div>
+ )}
+ {info?.format && (
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {getFormat(info?.format)}
+ </div>
+ )}
+ {info?.status && (
+ <div
+ className={`dynamic-text rounded-md px-2 font-karla font-bold`}
+ style={color}
+ >
+ {info?.status}
+ </div>
+ )}
+ </div>
+ );
+}
diff --git a/components/anime/mobile/topSection.js b/components/anime/mobile/topSection.js
index e9c9c7d..25d387f 100644
--- a/components/anime/mobile/topSection.js
+++ b/components/anime/mobile/topSection.js
@@ -1,81 +1,459 @@
-import { HeartIcon } from "@heroicons/react/20/solid";
+import {
+ ArrowUpCircleIcon,
+ MagnifyingGlassIcon,
+} from "@heroicons/react/24/solid";
import {
- TvIcon,
- ArrowTrendingUpIcon,
- RectangleStackIcon,
-} from "@heroicons/react/24/outline";
+ ArrowLeftIcon,
+ PlayIcon,
+ PlusIcon,
+ ShareIcon,
+ UserIcon,
+} from "@heroicons/react/24/solid";
+import Image from "next/image";
+import { useRouter } from "next/router";
+import { useSearch } from "../../../lib/hooks/isOpenState";
+import { useEffect, useState } from "react";
+import { convertSecondsToTime } from "../../../utils/getTimes";
+import Link from "next/link";
+import { signIn } from "next-auth/react";
+import InfoChip from "./reused/infoChip";
+import Description from "./reused/description";
+
+const getScrollPosition = (el = window) => ({
+ x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
+ y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop,
+});
+
+export function NewNavbar({ info, session, scrollP = 200, toTop = false }) {
+ const router = useRouter();
+ const [scrollPosition, setScrollPosition] = useState();
+ const { isOpen, setIsOpen } = useSearch();
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setScrollPosition(getScrollPosition());
+ };
-export default function DetailTop({ info, statuses, handleOpen, loading }) {
+ // Add a scroll event listener when the component mounts
+ window.addEventListener("scroll", handleScroll);
+
+ // Clean up the event listener when the component unmounts
+ return () => {
+ window.removeEventListener("scroll", handleScroll);
+ };
+ }, []);
return (
- <div className="lg:hidden pt-5 w-screen px-5 flex flex-col">
- <div className="h-[250px] flex flex-col gap-1 justify-center">
- <h1 className="font-karla font-extrabold text-lg line-clamp-1 w-[70%]">
- {info?.title?.romaji || info?.title?.english}
- </h1>
- <p
- className="line-clamp-2 text-sm font-light antialiased w-[56%]"
- dangerouslySetInnerHTML={{ __html: info?.description }}
- />
- <div className="font-light flex gap-1 py-1 flex-wrap font-outfit text-[10px] text-[#ffffff] w-[70%]">
- {info?.genres
- ?.slice(0, info?.genres?.length > 3 ? info?.genres?.length : 3)
- .map((item, index) => (
- <span
- key={index}
- className="px-2 py-1 bg-secondary shadow-lg font-outfit font-light rounded-full"
+ <>
+ <nav
+ className={`fixed z-[200] top-0 py-3 px-5 w-full ${
+ scrollPosition?.y >= scrollP
+ ? "bg-tersier shadow-tersier shadow-sm"
+ : ""
+ } transition-all duration-200 ease-linear`}
+ >
+ <div className="flex items-center justify-between max-w-screen-2xl mx-auto">
+ <div className="flex w-full items-center gap-4">
+ {info ? (
+ <>
+ <button
+ type="button"
+ className="flex-center w-7 h-7 text-white"
+ onClick={() => {
+ // router.back();
+ router.push("/en");
+ }}
+ >
+ <ArrowLeftIcon className="w-full h-full" />
+ </button>
+ <span
+ className={`font-inter font-semibold w-[50%] line-clamp-1 select-none ${
+ scrollPosition?.y >= scrollP + 80
+ ? "opacity-100"
+ : "opacity-0"
+ } transition-all duration-200 ease-linear`}
+ >
+ {info.title.romaji}
+ </span>
+ </>
+ ) : (
+ // <></>
+ <Link
+ href={"/en"}
+ className="flex-center text-white font-outfit text-2xl font-semibold"
>
- <span>{item}</span>
- </span>
- ))}
- </div>
- {info && (
- <div className="flex items-center gap-5 pt-3 text-center">
- <div className="flex items-center gap-2 text-center">
+ moopa
+ </Link>
+ )}
+ </div>
+ <div className="flex items-center gap-4">
+ <button
+ type="button"
+ onClick={() => setIsOpen(true)}
+ className="flex-center w-[26px] h-[26px]"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="32"
+ height="32"
+ viewBox="0 0 24 24"
+ >
+ <path
+ fill="none"
+ stroke="currentColor"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="2"
+ d="M15 15l6 6m-11-4a7 7 0 110-14 7 7 0 010 14z"
+ ></path>
+ </svg>
+ </button>
+ {/* <div
+ className="bg-white"
+ // title={sessions ? "Go to Profile" : "Login With AniList"}
+ > */}
+ {session ? (
<button
type="button"
- className="bg-action px-10 rounded-sm font-karla font-bold"
- onClick={() => handleOpen()}
+ onClick={() => router.push(`/en/profile/${session?.user.name}`)}
+ className="w-7 h-7 relative flex flex-col items-center group"
>
- {!loading
- ? statuses
- ? statuses.name
- : "Add to List"
- : "Loading..."}
+ <Image
+ src={session?.user.image.large}
+ alt="avatar"
+ width={50}
+ height={50}
+ className="w-full h-full object-cover"
+ />
+ <div className="hidden absolute z-50 w-28 text-center -bottom-20 text-white shadow-2xl opacity-0 bg-secondary p-1 py-2 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all md:grid place-items-center gap-1">
+ <Link
+ href={`/en/profile/${session?.user.name}`}
+ className="hover:text-action"
+ >
+ Profile
+ </Link>
+ <div
+ onClick={() => signOut({ callbackUrl: "/" })}
+ className="hover:text-action"
+ >
+ Log out
+ </div>
+ </div>
</button>
- <div className="h-6 w-6">
- <HeartIcon />
- </div>
- </div>
+ ) : (
+ <button
+ type="button"
+ onClick={() => signIn("AniListProvider")}
+ title="Login With AniList"
+ className="w-7 h-7 bg-white/30 rounded-full overflow-hidden"
+ >
+ <UserIcon className="w-full h-full translate-y-2" />
+ </button>
+ )}
+ {/* </div> */}
</div>
- )}
+ </div>
+ </nav>
+ {toTop && (
+ <button
+ type="button"
+ onClick={() => {
+ window.scrollTo({
+ top: 0,
+ behavior: "smooth",
+ });
+ }}
+ className={`${
+ scrollPosition?.y >= 180
+ ? "-translate-x-6 opacity-100"
+ : "translate-x-[100%] opacity-0"
+ } transform transition-all duration-300 ease-in-out fixed bottom-24 lg:bottom-14 right-0 z-[500]`}
+ >
+ <ArrowUpCircleIcon className="w-10 h-10 text-white" />
+ </button>
+ )}
+ </>
+ );
+}
+
+export default function DetailTop({
+ info,
+ session,
+ statuses,
+ handleOpen,
+ watchUrl,
+ progress,
+ color,
+}) {
+ const router = useRouter();
+ const [readMore, setReadMore] = useState(false);
+
+ const [showAll, setShowAll] = useState(false);
+
+ useEffect(() => {
+ setReadMore(false);
+ }, [info.id]);
+
+ const handleShareClick = async () => {
+ try {
+ if (navigator.share) {
+ await navigator.share({
+ title: `Watch Now - ${info?.title?.english}`,
+ // 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);
+ }
+ };
+
+ return (
+ <div className="gap-6 w-full px-3 pt-4 md:pt-10 flex flex-col items-center justify-center">
+ <NewNavbar info={info} session={session} />
+
+ {/* MAIN */}
+ <div className="flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12">
+ <div className="shrink-0 w-[180px] h-[250px] rounded overflow-hidden">
+ <Image
+ src={info?.coverImage?.extraLarge}
+ // alt="coverImage"
+ alt="poster anime"
+ width={300}
+ height={300}
+ className="w-full h-full object-cover"
+ />
+ </div>
+ <div className="flex flex-col gap-4 items-center md:items-start justify-end w-full">
+ <div className="flex flex-col gap-1 text-center md:text-start">
+ <h3 className="font-karla text-lg capitalize leading-none">
+ {info?.season?.toLowerCase()} {info.seasonYear}
+ </h3>
+ <h1 className="font-outfit font-extrabold text-2xl md:text-4xl line-clamp-2 text-white">
+ {info?.title?.romaji || info?.title?.english}
+ </h1>
+ <h2 className="font-karla line-clamp-1 text-sm md:text-lg md:pb-2 font-light text-white/70">
+ {info.title?.english}
+ </h2>
+ <InfoChip info={info} color={color} className="hidden md:flex" />
+ {info?.description && (
+ <Description
+ info={info}
+ readMore={readMore}
+ setReadMore={setReadMore}
+ className="md:block hidden"
+ />
+ )}
+ </div>
+ </div>
</div>
- <div className="bg-secondary rounded-sm xs:h-[30px]">
- <div className="grid grid-cols-3 place-content-center xxs:flex items-center justify-center h-full xxs:gap-10 p-2 text-sm">
- {info && info.status !== "NOT_YET_RELEASED" ? (
- <>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <TvIcon className="w-5 h-5 text-action" />
- <h4 className="font-karla">{info?.type}</h4>
- </div>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <ArrowTrendingUpIcon className="w-5 h-5 text-action" />
- <h4>{info?.averageScore ? `${info?.averageScore}%` : "N/A"}</h4>
- </div>
- <div className="flex-center flex-col xxs:flex-row gap-2">
- <RectangleStackIcon className="w-5 h-5 text-action" />
- {info?.episodes ? (
- <h1>{info?.episodes} Episodes</h1>
- ) : (
- <h1>N/A</h1>
- )}
- </div>
- </>
+
+ <div className="hidden md:flex gap-5 items-center justify-start w-full">
+ <button
+ type="button"
+ onClick={() => router.push(watchUrl)}
+ className={`${
+ !watchUrl ? "opacity-30 pointer-events-none" : ""
+ } w-[180px] flex-center text-lg font-karla font-semibold gap-1 border-black border-opacity-10 text-black rounded-full py-1 px-4 bg-white hover:opacity-80`}
+ >
+ <PlayIcon className="w-5 h-5" />
+ {progress > 0 ? (
+ statuses?.value === "COMPLETED" ? (
+ "Rewatch"
+ ) : !watchUrl && info?.nextAiringEpisode ? (
+ <span>
+ {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "}
+ </span>
+ ) : (
+ "Continue"
+ )
) : (
- <div>{info && "Not Yet Released"}</div>
+ "Watch Now"
)}
+ </button>
+ <div className="flex gap-2">
+ <button
+ type="button"
+ className="flex-center group relative w-10 h-10 bg-secondary rounded-full"
+ onClick={() => handleOpen()}
+ >
+ <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out">
+ Add to List
+ </span>
+ <PlusIcon className="w-5 h-5" />
+ </button>
+ <button
+ type="button"
+ className="flex-center group relative w-10 h-10 bg-secondary rounded-full"
+ onClick={handleShareClick}
+ >
+ <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out">
+ Share Anime
+ </span>
+ <ShareIcon className="w-5 h-5" />
+ </button>
+ <a
+ target="_blank"
+ rel="noopener noreferrer"
+ href={`https://anilist.co/anime/${info.id}`}
+ className="flex-center group relative w-10 h-10 bg-secondary rounded-full"
+ >
+ <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out">
+ See on AniList
+ </span>
+ <Image
+ src="/svg/anilist-icon.svg"
+ alt="anilist_icon"
+ width={20}
+ height={20}
+ />
+ </a>
</div>
</div>
+
+ <div className="md:hidden flex gap-2 items-center justify-center w-[90%]">
+ <button
+ type="button"
+ className="flex-center group relative w-10 h-10 bg-secondary rounded-full"
+ onClick={() => handleOpen()}
+ >
+ <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out">
+ Add to List
+ </span>
+ <PlusIcon className="w-5 h-5" />
+ </button>
+ <button
+ // href={watchUrl || ""}
+ type="button"
+ // disabled={!watchUrl || info?.nextAiringEpisode}
+ onClick={() => router.push(watchUrl)}
+ className={`${
+ !watchUrl ? "opacity-30 pointer-events-none" : ""
+ } flex items-center text-lg font-karla font-semibold gap-1 border-black border-opacity-10 text-black rounded-full py-2 px-4 bg-white`}
+ >
+ <PlayIcon className="w-5 h-5" />
+ {progress > 0 ? (
+ statuses?.value === "COMPLETED" ? (
+ "Rewatch"
+ ) : !watchUrl && info?.nextAiringEpisode ? (
+ <span>
+ {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "}
+ </span>
+ ) : (
+ "Continue"
+ )
+ ) : (
+ "Watch Now"
+ )}
+ </button>
+ <button
+ type="button"
+ className="flex-center group relative w-10 h-10 bg-secondary rounded-full"
+ onClick={handleShareClick}
+ >
+ <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out">
+ Share Anime
+ </span>
+ <ShareIcon className="w-5 h-5" />
+ </button>
+ </div>
+
+ {info.nextAiringEpisode?.timeUntilAiring && (
+ <p className="md:hidden">
+ Episode {info.nextAiringEpisode.episode} in{" "}
+ <span className="font-bold">
+ {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "}
+ </span>
+ </p>
+ )}
+
+ {info?.description && (
+ <Description
+ info={info}
+ readMore={readMore}
+ setReadMore={setReadMore}
+ className="md:hidden"
+ />
+ )}
+
+ <InfoChip
+ info={info}
+ color={color}
+ className={`${readMore ? "flex" : "hidden"} md:hidden`}
+ />
+
+ {info?.relations?.edges?.length > 0 && (
+ <div className="w-screen md:w-full">
+ <div className="flex justify-between items-center p-3 md:p-0">
+ {info?.relations?.edges?.length > 0 && (
+ <div className="text-[20px] md:text-2xl font-bold font-karla">
+ Relations
+ </div>
+ )}
+ {info?.relations?.edges?.length > 3 && (
+ <div
+ className="cursor-pointer font-karla"
+ onClick={() => setShowAll(!showAll)}
+ >
+ {showAll ? "show less" : "show more"}
+ </div>
+ )}
+ </div>
+ <div
+ className={` md:w-full flex gap-5 overflow-x-scroll snap-x scroll-px-5 scrollbar-none md:grid md:grid-cols-3 justify-items-center md:pt-7 md:pb-5 px-3 md:px-4 pt-4 rounded-xl`}
+ >
+ {info?.relations?.edges
+ .slice(0, showAll ? info?.relations?.edges.length : 3)
+ .map((r, index) => {
+ const rel = r.node;
+ return (
+ <Link
+ key={rel.id}
+ href={
+ rel.type === "ANIME" ||
+ rel.type === "OVA" ||
+ rel.type === "MOVIE" ||
+ rel.type === "SPECIAL" ||
+ rel.type === "ONA"
+ ? `/en/anime/${rel.id}`
+ : `/en/manga/${rel.id}`
+ }
+ className={`md:hover:scale-[1.02] snap-start hover:shadow-lg scale-100 transition-transform duration-200 ease-out w-full ${
+ rel.type === "MUSIC" ? "pointer-events-none" : ""
+ }`}
+ >
+ <div
+ key={rel.id}
+ className="w-[400px] md:w-full h-[126px] bg-secondary flex rounded-md"
+ >
+ <div className="w-[90px] bg-image rounded-l-md shrink-0">
+ <Image
+ src={rel.coverImage.extraLarge}
+ alt={rel.id}
+ height={500}
+ width={500}
+ className="object-cover h-full w-full shrink-0 rounded-l-md"
+ />
+ </div>
+ <div className="h-full grid px-3 items-center">
+ <div className="text-action font-outfit font-bold capitalize">
+ {r.relationType.replace(/_/g, " ")}
+ </div>
+ <div className="font-outfit line-clamp-2">
+ {rel.title.userPreferred}
+ </div>
+ <div className="font-thin">{rel.format}</div>
+ </div>
+ </div>
+ </Link>
+ );
+ })}
+ </div>
+ </div>
+ )}
</div>
);
}
diff --git a/components/anime/viewMode/listMode.js b/components/anime/viewMode/listMode.js
index f3bcf05..5beded1 100644
--- a/components/anime/viewMode/listMode.js
+++ b/components/anime/viewMode/listMode.js
@@ -3,7 +3,6 @@ import Link from "next/link";
export default function ListMode({
info,
episode,
- index,
artStorage,
providerId,
progress,
@@ -15,39 +14,32 @@ export default function ListMode({
if (prog > 90) prog = 100;
return (
- <div key={episode.number} className="flex flex-col gap-3 px-2">
- <Link
- href={`/en/anime/watch/${info.id}/${providerId}?id=${encodeURIComponent(
- episode.id
- )}&num=${episode.number}${dub ? `&dub=${dub}` : ""}`}
- className={`text-start text-sm lg:text-lg ${
- progress
- ? progress && episode.number <= progress
+ <Link
+ key={episode.number}
+ href={`/en/anime/watch/${info.id}/${providerId}?id=${encodeURIComponent(
+ episode.id
+ )}&num=${episode.number}${dub ? `&dub=${dub}` : ""}`}
+ className={`flex gap-3 py-4 hover:bg-secondary/10 odd:bg-secondary/30 even:bg-primary`}
+ >
+ <div className="flex w-full">
+ <span className="shrink-0 px-4 text-center text-white/50">
+ {episode.number}
+ </span>
+ <p
+ className={`w-full line-clamp-1 ${
+ progress
+ ? progress && episode.number <= progress
+ ? "text-[#5f5f5f]"
+ : "text-white"
+ : prog === 100
? "text-[#5f5f5f]"
: "text-white"
- : prog === 100
- ? "text-[#5f5f5f]"
- : "text-white"
- }`}
- >
- <p>Episode {episode.number}</p>
- {episode.title && (
- <p
- className={`text-xs lg:text-sm ${
- progress
- ? progress && episode.number <= progress
- ? "text-[#5f5f5f]"
- : "text-[#b1b1b1]"
- : prog === 100
- ? "text-[#5f5f5f]"
- : "text-[#b1b1b1]"
- } italic`}
- >
- "{episode.title}"
- </p>
- )}
- </Link>
- {index !== episode?.length - 1 && <span className="h-[1px] bg-white" />}
- </div>
+ }`}
+ >
+ {episode?.title || `Episode ${episode.number}`}
+ </p>
+ <p className="capitalize text-sm text-white/50 px-4">{providerId}</p>
+ </div>
+ </Link>
);
}
diff --git a/components/anime/viewMode/thumbnailDetail.js b/components/anime/viewMode/thumbnailDetail.js
index 6efeb77..296e0d2 100644
--- a/components/anime/viewMode/thumbnailDetail.js
+++ b/components/anime/viewMode/thumbnailDetail.js
@@ -5,6 +5,9 @@ export default function ThumbnailDetail({
index,
epi,
info,
+ image,
+ title,
+ description,
provider,
artStorage,
progress,
@@ -25,13 +28,15 @@ export default function ThumbnailDetail({
>
<div className="w-[43%] lg:w-[30%] relative shrink-0 z-40 rounded-lg overflow-hidden shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)]">
<div className="relative">
- <Image
- src={epi?.image}
- alt="Anime Cover"
- width={1000}
- height={1000}
- className="object-cover z-30 rounded-lg h-[110px] lg:h-[160px] brightness-[65%]"
- />
+ {image && (
+ <Image
+ src={image || ""}
+ alt="Anime Cover"
+ width={1000}
+ height={1000}
+ className="object-cover z-30 rounded-lg h-[110px] lg:h-[160px] brightness-[65%]"
+ />
+ )}
<span
className={`absolute bottom-0 left-0 h-[2px] bg-red-700`}
style={{
@@ -63,11 +68,11 @@ export default function ThumbnailDetail({
className={`w-[70%] h-full select-none p-4 flex flex-col justify-center gap-3`}
>
<h1 className="font-karla font-bold text-base lg:text-lg xl:text-xl italic line-clamp-1">
- {epi?.title}
+ {title}
</h1>
- {epi?.description && (
+ {description && (
<p className="line-clamp-2 text-xs lg:text-md xl:text-lg italic font-outfit font-extralight">
- {epi?.description}
+ {description}
</p>
)}
</div>
diff --git a/components/anime/viewMode/thumbnailOnly.js b/components/anime/viewMode/thumbnailOnly.js
index 99f02bd..69cd8c3 100644
--- a/components/anime/viewMode/thumbnailOnly.js
+++ b/components/anime/viewMode/thumbnailOnly.js
@@ -3,6 +3,7 @@ import Link from "next/link";
export default function ThumbnailOnly({
info,
+ image,
providerId,
episode,
artStorage,
@@ -35,25 +36,16 @@ export default function ThumbnailOnly({
: "0%",
}}
/>
- <div className="absolute inset-0 bg-black z-30 opacity-20" />
- <Image
- // src={
- // providerId === "animepahe"
- // ? `https://img.moopa.live/image-proxy?url=${encodeURIComponent(
- // episode.img
- // )}&headers=${encodeURIComponent(
- // JSON.stringify({ Referer: "https://animepahe.com/" })
- // )}`
- // : thumbnail?.img.includes("null")
- // ? info.coverImage.large
- // : thumbnail?.img || info.coverImage.large
- // }
- src={episode?.image}
- alt="epi image"
- width={500}
- height={500}
- className="object-cover w-full h-[150px] sm:h-[100px] z-20"
- />
+ {/* <div className="absolute inset-0 bg-black z-30 opacity-20" /> */}
+ {image && (
+ <Image
+ src={image || ""}
+ alt="epi image"
+ width={500}
+ height={500}
+ className="object-cover w-full h-[150px] sm:h-[100px] z-20 brightness-75"
+ />
+ )}
</Link>
);
}
diff --git a/components/anime/watch/primarySide.js b/components/anime/watch/primarySide.js
index b032fd6..a3d9f4f 100644
--- a/components/anime/watch/primarySide.js
+++ b/components/anime/watch/primarySide.js
@@ -9,18 +9,14 @@ import Link from "next/link";
import Skeleton from "react-loading-skeleton";
import Modal from "../../modal";
import AniList from "../../media/aniList";
-import axios from "axios";
export default function PrimarySide({
info,
session,
epiNumber,
- setLoading,
navigation,
- loading,
providerId,
watchId,
- status,
onList,
proxy,
disqus,
@@ -33,15 +29,31 @@ export default function PrimarySide({
const [open, setOpen] = useState(false);
const [skip, setSkip] = useState();
+ const [loading, setLoading] = useState(true);
+
const router = useRouter();
useEffect(() => {
setLoading(true);
async function fetchData() {
if (info) {
- const { data } = await axios.get(
- `/api/consumet/source/${providerId}/${watchId}`
- );
+ const anify = await fetch("/api/v2/source", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ source:
+ providerId === "gogoanime" && !watchId.startsWith("/")
+ ? "consumet"
+ : "anify",
+ providerId: providerId,
+ 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(
@@ -65,10 +77,9 @@ export default function PrimarySide({
setSkip({ op, ed });
- setEpisodeData(data);
+ setEpisodeData(anify);
setLoading(false);
}
- // setMal(malId);
}
fetchData();
@@ -134,7 +145,7 @@ export default function PrimarySide({
<div className="w-full h-full">
<div key={watchId} className="w-full aspect-video bg-black">
{!loading ? (
- episodeData && (
+ navigation && episodeData?.sources?.length !== 0 ? (
<VideoPlayer
session={session}
info={info}
@@ -142,7 +153,6 @@ export default function PrimarySide({
provider={providerId}
id={watchId}
progress={epiNumber}
- stats={status}
skip={skip}
proxy={proxy}
aniId={info.id}
@@ -151,9 +161,20 @@ export default function PrimarySide({
timeWatched={timeWatched}
dub={dub}
/>
+ ) : (
+ <p className="h-full flex-center">
+ Video is not available, please try other providers
+ </p>
)
) : (
- <div className="aspect-video bg-black" />
+ <div className="flex-center aspect-video bg-black">
+ <div className="lds-ellipsis">
+ <div></div>
+ <div></div>
+ <div></div>
+ <div></div>
+ </div>
+ </div>
)}
</div>
<div className="flex flex-col divide-y divide-white/20">
diff --git a/components/anime/watch/secondarySide.js b/components/anime/watch/secondarySide.js
index 5d9b8f9..c9ef684 100644
--- a/components/anime/watch/secondarySide.js
+++ b/components/anime/watch/secondarySide.js
@@ -4,24 +4,27 @@ import Link from "next/link";
export default function SecondarySide({
info,
+ map,
providerId,
watchId,
episode,
- progress,
artStorage,
dub,
}) {
+ const progress = info.mediaListEntry?.progress;
return (
<div className="lg:w-[35%] shrink-0 w-screen">
<h1 className="text-xl font-karla pl-4 pb-5 font-semibold">Up Next</h1>
<div className="flex flex-col gap-5 lg:pl-5 py-2 scrollbar-thin px-2 scrollbar-thumb-[#313131] scrollbar-thumb-rounded-full">
{episode && episode.length > 0 ? (
- episode.some((item) => item.title && item.description) > 0 ? (
+ map?.some((item) => item.title && item.description) > 0 ? (
episode.map((item) => {
const time = artStorage?.[item.id]?.timeWatched;
const duration = artStorage?.[item.id]?.duration;
let prog = (time / duration) * 100;
if (prog > 90) prog = 100;
+
+ const mapData = map?.find((i) => i.number === item.number);
return (
<Link
href={`/en/anime/watch/${
@@ -38,8 +41,9 @@ export default function SecondarySide({
>
<div className="w-[43%] lg:w-[40%] h-[110px] relative rounded-lg z-40 shrink-0 overflow-hidden shadow-[4px_0px_5px_0px_rgba(0,0,0,0.3)]">
<div className="relative">
+ {/* {mapData?.image && ( */}
<Image
- src={item.image}
+ src={mapData?.image || info?.coverImage?.extraLarge}
alt="Anime Cover"
width={1000}
height={1000}
@@ -49,6 +53,7 @@ export default function SecondarySide({
: "brightness-75"
}`}
/>
+ {/* )} */}
<span
className={`absolute bottom-0 left-0 h-[2px] bg-red-700`}
style={{
@@ -61,7 +66,7 @@ export default function SecondarySide({
}}
/>
<span className="absolute bottom-2 left-2 font-karla font-bold text-sm">
- Episode {item.number}
+ Episode {item?.number}
</span>
{item.id == watchId && (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 scale-[1.5]">
@@ -78,15 +83,15 @@ export default function SecondarySide({
</div>
</div>
<div
- className={`w-[70%] h-full select-none p-4 flex flex-col gap-2 ${
+ className={`w-full h-full overflow-x-hidden select-none p-4 flex flex-col gap-2 ${
item.id == watchId ? "text-[#7a7a7a]" : ""
}`}
>
<h1 className="font-karla font-bold italic line-clamp-1">
- {item.title}
+ {mapData?.title}
</h1>
<p className="line-clamp-2 text-xs italic font-outfit font-extralight">
- {item?.description}
+ {mapData?.description}
</p>
</div>
</Link>
diff --git a/components/footer.js b/components/footer.js
index d658172..ca5a21f 100644
--- a/components/footer.js
+++ b/components/footer.js
@@ -1,13 +1,11 @@
import Link from "next/link";
-import { signIn, useSession } from "next-auth/react";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { parseCookies, setCookie } from "nookies";
function Footer() {
- const { data: session, status } = useSession();
- const [year, setYear] = useState(new Date().getFullYear());
- const [season, setSeason] = useState(getCurrentSeason());
+ const [year] = useState(new Date().getFullYear());
+ const [season] = useState(getCurrentSeason());
const [lang, setLang] = useState("en");
const [checked, setChecked] = useState(false);
@@ -41,118 +39,160 @@ function Footer() {
});
router.push("/en");
} else {
- console.log("switching to id");
- setCookie(null, "lang", "id", {
- maxAge: 365 * 24 * 60 * 60,
- path: "/",
- });
router.push("/id");
}
}
return (
- <section className="text-[#dbdcdd] z-40 bg-[#0c0d10] lg:flex lg:h-[12rem] w-full lg:items-center lg:justify-between">
- <div className="mx-auto flex w-[80%] lg:w-[95%] xl:w-[80%] flex-col space-y-10 pb-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0">
- <div className="flex items-center gap-24">
- <div className="lg:flex grid items-center lg:gap-10 gap-3">
- {/* <h1 className="font-outfit text-[2.56rem]">moopa</h1> */}
- <h1 className="font-outfit text-[40px]">moopa</h1>
- <div className="flex flex-col gap-5">
- <div className="flex flex-col gap-1">
- <p className="flex items-center gap-1 font-karla lg:text-[0.81rem] text-[0.7rem] text-[#CCCCCC]">
- &copy; {new Date().getFullYear()} moopa.live | Website Made by
- Factiven
- </p>
- <p className="font-karla lg:text-[0.8rem] text-[0.65rem] text-[#9c9c9c] lg:w-[520px] italic">
- This site does not store any files on our server, we only
- linked to the media which is hosted on 3rd party services.
- </p>
- </div>
-
- <label className="flex items-center relative w-max cursor-pointer select-none text-txt">
- <span className="text-base text-[#cccccc] font-inter font-semibold mr-3">
- Lang
- </span>
- <input
- type="checkbox"
- checked={checked}
- onChange={() => switchLang()}
- className="appearance-none transition-colors cursor-pointer w-14 h-5 rounded-full focus:outline-none focus:ring-offset-2 focus:ring-offset-black focus:ring-action bg-secondary"
- />
- <span className="absolute font-medium text-xs uppercase right-2 text-action">
- {" "}
- EN{" "}
- </span>
- <span className="absolute font-medium text-xs uppercase right-[2.1rem] text-action">
- {" "}
- ID{" "}
- </span>
- <span className="w-6 h-6 right-[2.1rem] absolute rounded-full transform transition-transform bg-gray-200" />
- </label>
- </div>
+ <footer className="flex-col w-full">
+ <div className="text-[#dbdcdd] z-40 bg-[#0c0d10] lg:flex lg:h-[12rem] w-full lg:items-center lg:justify-between">
+ <div className="mx-auto flex w-[85%] lg:w-[95%] xl:w-[80%] flex-col space-y-10 py-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0">
+ <div className="flex flex-col gap-2">
+ {/* <div className="flex items-center gap-2"> */}
+ {/* <Image
+ src="/svg/c.svg"
+ alt="Website Logo"
+ width={100}
+ height={100}
+ className="w-10 h-10"
+ /> */}
+ <p className="font-outfit text-4xl">moopa</p>
+ <p className="font-karla lg:text-[0.8rem] text-[0.65rem] text-[#9c9c9c] lg:w-[520px] italic">
+ This site does not store any files on our server, we only linked
+ to the media which is hosted on 3rd party services.
+ </p>
+ {/* </div> */}
</div>
- {/* <div className="lg:hidden lg:block">
- <Image
- src="https://i1210.photobucket.com/albums/cc417/kusanagiblog/NarutoVSSasuke.gif"
- alt="gambar"
- title="request nya rapip yulistian"
- width={210}
- height={85}
- />
- </div> */}
- </div>
- <div className="flex flex-col gap-10 lg:flex-row lg:items-end lg:gap-[9.06rem] text-[#a7a7a7] text-sm lg:text-end">
- <div className="flex flex-col gap-10 font-karla font-bold lg:flex-row lg:gap-[5.94rem]">
- <ul className="flex flex-col gap-y-[0.7rem] ">
- <li className="cursor-pointer hover:text-action">
- <Link
- href={`/${lang}/search/anime?season=${season}&seasonYear=${year}`}
- >
- This Season
- </Link>
- </li>
- <li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/search/anime`}>Popular Anime</Link>
- </li>
- <li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/search/manga`}>Popular Manga</Link>
- </li>
- {status === "loading" ? (
- <p>Loading...</p>
- ) : session ? (
+ <div className="flex flex-col gap-10 lg:flex-row lg:items-end lg:gap-[9.06rem] text-[#a7a7a7] text-sm lg:text-end">
+ <div className="flex flex-col gap-10 font-karla font-bold lg:flex-row lg:gap-[5.94rem]">
+ <ul className="flex flex-col gap-y-[0.7rem] ">
<li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/profile/${session?.user?.name}`}>
- My List
+ <Link
+ href={`/${lang}/search/anime?season=${season}&year=${year}`}
+ >
+ This Season
</Link>
</li>
- ) : (
- <li className="hover:text-action">
- <button onClick={() => signIn("AniListProvider")}>
- Login
- </button>
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`/${lang}/search/anime`}>Popular Anime</Link>
+ </li>
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`/${lang}/search/manga`}>Popular Manga</Link>
+ </li>
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`https://ko-fi.com/factiven`}>Donate</Link>
+ </li>
+ </ul>
+ <ul className="flex flex-col gap-y-[0.7rem]">
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`/${lang}/search/anime?format=MOVIE`}>
+ Movies
+ </Link>
</li>
- )}
- </ul>
- <ul className="flex flex-col gap-y-[0.7rem]">
- <li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/search/anime`}>Movies</Link>
- </li>
- <li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/search/anime`}>TV Shows</Link>
- </li>
- <li className="cursor-pointer hover:text-action">
- <Link href={`/${lang}/dmca`}>DMCA</Link>
- </li>
- <li className="cursor-pointer hover:text-action">
- <Link href="https://github.com/DevanAbinaya/Ani-Moopa">
- Github
- </Link>
- </li>
- </ul>
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`/${lang}/search/anime?format=TV`}>TV Shows</Link>
+ </li>
+ <li className="cursor-pointer hover:text-action">
+ <Link href={`/${lang}/dmca`}>DMCA</Link>
+ </li>
+ <li className="cursor-pointer hover:text-action">
+ <Link href="https://github.com/DevanAbinaya/Ani-Moopa">
+ Github
+ </Link>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="bg-tersier border-t border-white/5">
+ <div className="mx-auto flex w-[90%] lg:w-[95%] xl:w-[80%] flex-col pb-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0">
+ <p className="flex items-center gap-1 font-karla lg:text-[0.81rem] text-[0.7rem] text-[#CCCCCC] py-3">
+ &copy; {new Date().getFullYear()} moopa.live | Website Made by{" "}
+ <span className="text-white font-bold">Factiven</span>
+ </p>
+ <div className="flex items-center gap-5">
+ {/* Github Icon */}
+ <Link
+ href="https://github.com/Ani-Moopa/Moopa"
+ className="w-5 h-5 hover:opacity-75"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="#fff"
+ viewBox="0 0 20 20"
+ >
+ <g>
+ <g
+ fill="none"
+ fillRule="evenodd"
+ stroke="none"
+ strokeWidth="1"
+ >
+ <g fill="#fff" transform="translate(-140 -7559)">
+ <g transform="translate(56 160)">
+ <path d="M94 7399c5.523 0 10 4.59 10 10.253 0 4.529-2.862 8.371-6.833 9.728-.507.101-.687-.219-.687-.492 0-.338.012-1.442.012-2.814 0-.956-.32-1.58-.679-1.898 2.227-.254 4.567-1.121 4.567-5.059 0-1.12-.388-2.034-1.03-2.752.104-.259.447-1.302-.098-2.714 0 0-.838-.275-2.747 1.051a9.396 9.396 0 00-2.505-.345 9.375 9.375 0 00-2.503.345c-1.911-1.326-2.751-1.051-2.751-1.051-.543 1.412-.2 2.455-.097 2.714-.639.718-1.03 1.632-1.03 2.752 0 3.928 2.335 4.808 4.556 5.067-.286.256-.545.708-.635 1.371-.57.262-2.018.715-2.91-.852 0 0-.529-.985-1.533-1.057 0 0-.975-.013-.068.623 0 0 .655.315 1.11 1.5 0 0 .587 1.83 3.369 1.21.005.857.014 1.665.014 1.909 0 .271-.184.588-.683.493-3.974-1.355-6.839-5.199-6.839-9.729 0-5.663 4.478-10.253 10-10.253"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </svg>
+ </Link>
+ {/* Discord Icon */}
+ <Link
+ href="https://discord.gg/v5fjSdKwr2"
+ className="w-6 h-6 hover:opacity-75"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ preserveAspectRatio="xMidYMid"
+ viewBox="0 -28.5 256 256"
+ >
+ <path
+ fill="#fff"
+ d="M216.856 16.597A208.502 208.502 0 00164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 00-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0079.735 175.3a136.413 136.413 0 01-21.846-10.632 108.636 108.636 0 005.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 005.355 4.237 136.07 136.07 0 01-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36zM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18z"
+ ></path>
+ </svg>
+ </Link>
+
+ {/* Kofi */}
+ <Link
+ href="https://ko-fi.com/factiven"
+ className="w-6 h-6 hover:opacity-75"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ fill="#fff"
+ viewBox="0 0 24 24"
+ >
+ <path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"></path>
+ </svg>
+ </Link>
+
+ <label
+ className="flex items-center relative w-max cursor-pointer select-none text-txt"
+ title="Switch to ID"
+ >
+ <input
+ type="checkbox"
+ checked={checked}
+ onChange={() => switchLang()}
+ className="appearance-none transition-colors cursor-pointer w-14 h-5 rounded-full focus:outline-none focus:ring-offset-2 focus:ring-offset-black focus:ring-action bg-secondary"
+ />
+ <span className="absolute font-medium text-xs uppercase right-2 text-action">
+ {" "}
+ EN{" "}
+ </span>
+ <span className="absolute font-medium text-xs uppercase right-[2.1rem] text-action">
+ {" "}
+ ID{" "}
+ </span>
+ <span className="w-6 h-6 right-[2.1rem] absolute rounded-full transform transition-transform bg-gray-200" />
+ </label>
</div>
</div>
</div>
- </section>
+ </footer>
);
}
diff --git a/components/home/content.js b/components/home/content.js
index 70f0e3f..e18e5d8 100644
--- a/components/home/content.js
+++ b/components/home/content.js
@@ -1,5 +1,6 @@
import Link from "next/link";
-import React, { useState, useRef, useEffect } from "react";
+import React, { useState, useRef, useEffect, Fragment } from "react";
+import { useDraggable } from "react-use-draggable-scroll";
import Image from "next/image";
import { MdChevronRight } from "react-icons/md";
import {
@@ -14,6 +15,7 @@ import { ChevronLeftIcon } from "@heroicons/react/20/solid";
import { ExclamationCircleIcon, PlayIcon } from "@heroicons/react/24/solid";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
+import HistoryOptions from "./content/historyOptions";
export default function Content({
ids,
@@ -26,11 +28,10 @@ export default function Content({
}) {
const router = useRouter();
- const [startX, setStartX] = useState(null);
- const containerRef = useRef(null);
+ const ref = useRef();
+ const { events } = useDraggable(ref);
const [cookie, setCookie] = useState(null);
- const [isDragging, setIsDragging] = useState(false);
const [clicked, setClicked] = useState(false);
const [lang, setLang] = useState("en");
@@ -55,39 +56,20 @@ export default function Content({
}
}, []);
- const handleMouseDown = (e) => {
- setIsDragging(true);
- setStartX(e.pageX - containerRef.current.offsetLeft);
- };
-
- 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();
- }
- };
-
const [scrollLeft, setScrollLeft] = useState(false);
const [scrollRight, setScrollRight] = useState(true);
const slideLeft = () => {
+ ref.current.classList.add("scroll-smooth");
var slider = document.getElementById(ids);
slider.scrollLeft = slider.scrollLeft - 500;
+ ref.current.classList.remove("scroll-smooth");
};
const slideRight = () => {
+ ref.current.classList.add("scroll-smooth");
var slider = document.getElementById(ids);
slider.scrollLeft = slider.scrollLeft + 500;
+ ref.current.classList.remove("scroll-smooth");
};
const handleScroll = (e) => {
@@ -128,6 +110,9 @@ export default function Content({
if (section === "Recently Watched") {
router.push(`/${lang}/anime/recently-watched`);
}
+ if (section === "New Episodes") {
+ router.push(`/${lang}/anime/recent`);
+ }
if (section === "Trending Now") {
router.push(`/${lang}/anime/trending`);
}
@@ -142,7 +127,7 @@ export default function Content({
}
};
- const removeItem = async (id) => {
+ const removeItem = async (id, aniId) => {
if (userName) {
// remove from database
const res = await fetch(`/api/user/update/episode`, {
@@ -152,24 +137,42 @@ export default function Content({
},
body: JSON.stringify({
name: userName,
- id: id,
+ id,
+ aniId,
}),
});
const data = await res.json();
- // remove from local storage
- const artplayerSettings =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
- if (artplayerSettings[id]) {
- delete artplayerSettings[id];
- localStorage.setItem(
- "artplayer_settings",
- JSON.stringify(artplayerSettings)
- );
+ if (id) {
+ // remove from local storage
+ const artplayerSettings =
+ JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ if (artplayerSettings[id]) {
+ delete artplayerSettings[id];
+ localStorage.setItem(
+ "artplayer_settings",
+ JSON.stringify(artplayerSettings)
+ );
+ }
+ }
+ if (aniId) {
+ const currentData =
+ JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+
+ const updatedData = {};
+
+ for (const key in currentData) {
+ const item = currentData[key];
+ if (item.aniId !== aniId) {
+ updatedData[key] = item;
+ }
+ }
+
+ localStorage.setItem("artplayer_settings", JSON.stringify(updatedData));
}
// update client
- setRemoved(id);
+ setRemoved(id || aniId);
if (data?.message === "Episode deleted") {
toast.success("Episode removed from history", {
@@ -182,17 +185,38 @@ export default function Content({
});
}
} else {
- const artplayerSettings =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
- if (artplayerSettings[id]) {
- delete artplayerSettings[id];
- localStorage.setItem(
- "artplayer_settings",
- JSON.stringify(artplayerSettings)
- );
+ if (id) {
+ // remove from local storage
+ const artplayerSettings =
+ JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ if (artplayerSettings[id]) {
+ delete artplayerSettings[id];
+ localStorage.setItem(
+ "artplayer_settings",
+ JSON.stringify(artplayerSettings)
+ );
+ }
+ setRemoved(id);
+ }
+ if (aniId) {
+ const currentData =
+ JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+
+ // Create a new object to store the updated data
+ const updatedData = {};
+
+ // Iterate through the current data and copy items with different aniId to the updated object
+ for (const key in currentData) {
+ const item = currentData[key];
+ if (item.aniId !== aniId) {
+ updatedData[key] = item;
+ }
+ }
+
+ // Update localStorage with the filtered data
+ localStorage.setItem("artplayer_settings", JSON.stringify(updatedData));
+ setRemoved(aniId);
}
-
- setRemoved(id);
}
};
@@ -218,13 +242,10 @@ export default function Content({
</div>
<div
id={ids}
- className="scroll flex h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide lg:gap-8 gap-4 lg:p-10 py-8 px-5 z-30 scroll-smooth"
+ className="flex h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide lg:gap-8 gap-4 lg:p-10 py-8 px-5 z-30"
onScroll={handleScroll}
- onMouseDown={handleMouseDown}
- onMouseUp={handleMouseUp}
- onMouseMove={handleMouseMove}
- onClick={handleClick}
- ref={containerRef}
+ {...events}
+ ref={ref}
>
{ids !== "recentlyWatched"
? slicedData?.map((anime) => {
@@ -241,14 +262,14 @@ export default function Content({
title={anime.title.romaji}
>
{ids === "onGoing" && (
- <div className="h-[190px] lg:h-[265px] w-[135px] lg:w-[185px] bg-gradient-to-b from-transparent to-black absolute z-40 rounded-md whitespace-normal font-karla group">
+ <div className="h-[190px] lg:h-[265px] w-[135px] lg:w-[185px] bg-gradient-to-b from-transparent to-black/90 absolute z-40 rounded-md whitespace-normal font-karla group">
<div className="flex flex-col items-center h-full justify-end text-center pb-5">
<h1 className="line-clamp-1 w-[70%] text-[10px]">
{anime.title.romaji || anime.title.english}
</h1>
{checkProgress(progress) &&
!clicked?.hasOwnProperty(anime.id) && (
- <ExclamationCircleIcon className="w-7 h-7 absolute z-40 -top-3 -right-3" />
+ <ExclamationCircleIcon className="w-7 h-7 absolute z-40 text-white -top-3 -right-3" />
)}
{checkProgress(progress) && (
<div
@@ -275,30 +296,52 @@ export default function Content({
</div>
</div>
)}
- <Image
- draggable={false}
- src={
- anime.image ||
- anime.coverImage?.extraLarge ||
- anime.coverImage?.large ||
- "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png"
- }
- alt={
- anime.title.romaji ||
- anime.title.english ||
- "coverImage"
- }
- width={500}
- height={300}
- placeholder="blur"
- blurDataURL={
- anime.image ||
- anime.coverImage?.extraLarge ||
- anime.coverImage?.large ||
- "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png"
- }
- className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90"
- />
+ <div className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded-md z-30">
+ {ids === "recentAdded" && (
+ <div className="absolute bg-gradient-to-b from-black/30 to-transparent from-5% to-30% top-0 z-30 w-full h-full rounded" />
+ )}
+ <Image
+ draggable={false}
+ src={
+ anime.image ||
+ anime.coverImage?.extraLarge ||
+ anime.coverImage?.large ||
+ "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png"
+ }
+ alt={
+ anime.title.romaji ||
+ anime.title.english ||
+ "coverImage"
+ }
+ width={500}
+ height={300}
+ placeholder="blur"
+ blurDataURL={
+ anime.image ||
+ anime.coverImage?.extraLarge ||
+ anime.coverImage?.large ||
+ "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png"
+ }
+ className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90"
+ />
+ </div>
+ {ids === "recentAdded" && (
+ <Fragment>
+ <Image
+ src="/svg/episode-badge.svg"
+ alt="episode-bade"
+ width={200}
+ height={100}
+ className="w-24 lg:w-32 absolute top-1 -right-[12px] lg:-right-[17px] z-40"
+ />
+ <p className="absolute z-40 text-center w-[86px] lg:w-[110px] top-1 -right-2 lg:top-[5.5px] lg:-right-2 font-karla text-sm lg:text-base">
+ Episode{" "}
+ <span className="text-white">
+ {anime?.episodeNumber}
+ </span>
+ </p>
+ </Fragment>
+ )}
</Link>
{ids !== "onGoing" && (
<Link
@@ -307,7 +350,8 @@ export default function Content({
title={anime.title.romaji}
>
<h1 className="font-karla font-semibold xl:text-base text-[15px]">
- {anime.status === "RELEASING" ? (
+ {anime.status === "RELEASING" ||
+ ids === "recentAdded" ? (
<span className="dots bg-green-500" />
) : anime.status === "NOT_YET_RELEASED" ? (
<span className="dots bg-red-500" />
@@ -333,22 +377,50 @@ export default function Content({
key={i.watchId}
className="flex flex-col gap-2 shrink-0 cursor-pointer relative group/item"
>
- <div className="absolute z-40 top-1 right-1 group-hover/item:visible invisible hover:text-action">
- <div
- className="flex flex-col items-center group/delete"
+ <div className="absolute flex flex-col gap-1 z-40 top-1 right-1 transition-all duration-200 ease-out opacity-0 group-hover/item:opacity-100 scale-90 group-hover/item:scale-100 group-hover/item:visible invisible ">
+ {/* <button
+ type="button"
+ className="flex flex-col items-center group/delete relative"
onClick={() => removeItem(i.watchId)}
>
- <XMarkIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full" />
+ <XMarkIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out" />
<span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/delete:visible group-hover/delete:scale-100 group-hover/delete:translate-x-0 group-hover/delete:opacity-100 opacity-0 translate-x-10 scale-50 invisible">
Remove from history
</span>
- </div>
+ </button> */}
+ <HistoryOptions
+ remove={removeItem}
+ watchId={i.watchId}
+ aniId={i.aniId}
+ />
+ {i?.nextId && (
+ <button
+ type="button"
+ className="flex flex-col items-center group/next relative"
+ onClick={() => {
+ router.push(
+ `/en/anime/watch/${i.aniId}/${
+ i.provider
+ }?id=${encodeURIComponent(i?.nextId)}&num=${
+ i?.nextNumber
+ }${i?.dub ? `&dub=${i?.dub}` : ""}`
+ );
+ }}
+ >
+ <ChevronRightIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out" />
+ <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/next:visible group-hover/next:scale-100 group-hover/next:translate-x-0 group-hover/next:opacity-100 opacity-0 translate-x-10 scale-50 invisible">
+ Play Next Episode
+ </span>
+ </button>
+ )}
</div>
<Link
className="relative w-[320px] aspect-video rounded-md overflow-hidden group"
href={`/en/anime/watch/${i.aniId}/${
i.provider
- }?id=${encodeURIComponent(i.watchId)}&num=${i.episode}`}
+ }?id=${encodeURIComponent(i.watchId)}&num=${i.episode}${
+ i?.dub ? `&dub=${i?.dub}` : ""
+ }`}
>
<div className="w-full h-full bg-gradient-to-t from-black/70 from-20% to-transparent group-hover:to-black/40 transition-all duration-300 ease-out absolute z-30" />
<div className="absolute bottom-3 left-0 mx-2 text-white flex gap-2 items-center w-[80%] z-30">
@@ -372,8 +444,8 @@ export default function Content({
{i?.image && (
<Image
src={i?.image}
- width={200}
- height={200}
+ width="0"
+ height="0"
alt="Episode Thumbnail"
className="w-fit group-hover:scale-[1.02] duration-300 ease-out z-10"
/>
@@ -411,7 +483,7 @@ export default function Content({
section !== "Recommendations" && (
<div
key={section}
- className="flex cursor-pointer"
+ className="flex flex-col cursor-pointer"
onClick={goToPage}
>
<div className="w-[320px] aspect-video overflow-hidden object-cover rounded-md border-secondary border-2 flex flex-col gap-2 items-center text-center justify-center text-[#6a6a6a] hover:text-[#9f9f9f] hover:border-[#757575] transition-colors duration-200">
diff --git a/components/home/content/historyOptions.js b/components/home/content/historyOptions.js
new file mode 100644
index 0000000..1b9c5ed
--- /dev/null
+++ b/components/home/content/historyOptions.js
@@ -0,0 +1,56 @@
+import { Menu, Transition } from "@headlessui/react";
+import { XMarkIcon } from "@heroicons/react/24/outline";
+import React, { Fragment } from "react";
+
+export default function HistoryOptions({ remove, watchId, aniId }) {
+ return (
+ <Menu as="div" className="relative inline-block text-left">
+ <div>
+ <Menu.Button className="group/delete w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out">
+ <XMarkIcon />
+ <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/delete:visible group-hover/delete:scale-100 group-hover/delete:translate-x-0 group-hover/delete:opacity-100 opacity-0 translate-x-10 scale-50 invisible">
+ Remove from history
+ </span>
+ </Menu.Button>
+ </div>
+ <Transition
+ as={Fragment}
+ enter="transition ease-out duration-100"
+ enterFrom="transform opacity-0 scale-95"
+ enterTo="transform opacity-100 scale-100"
+ leave="transition ease-in duration-75"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95"
+ >
+ <Menu.Items className="absolute z-50 right-0 mt-1 w-56 origin-top-right rounded-md bg-secondary shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
+ <div className="px-1 py-1 ">
+ <Menu.Item>
+ {({ active }) => (
+ <button
+ className={`${
+ active ? "bg-white/10 text-white" : "text-gray-100"
+ } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
+ onClick={() => remove(null, aniId)}
+ >
+ Delete All Episodes
+ </button>
+ )}
+ </Menu.Item>
+ <Menu.Item>
+ {({ active }) => (
+ <button
+ className={`${
+ active ? "bg-white/10 text-white" : "text-gray-100"
+ } group flex w-full items-center rounded-md px-2 py-2 text-sm`}
+ onClick={() => remove(watchId, null)}
+ >
+ Delete Just This Episode
+ </button>
+ )}
+ </Menu.Item>
+ </div>
+ </Menu.Items>
+ </Transition>
+ </Menu>
+ );
+}
diff --git a/components/home/genres.js b/components/home/genres.js
index 3eefecd..f054fc9 100644
--- a/components/home/genres.js
+++ b/components/home/genres.js
@@ -55,7 +55,7 @@ export default function Genres() {
<ChevronRightIcon className="w-5 h-5" />
</div>
<div className="flex xl:justify-center items-center relative">
- <div className="bg-gradient-to-r from-primary to-transparent z-40 absolute w-7 h-[200px] left-0" />
+ <div className="bg-gradient-to-r from-primary to-transparent z-40 absolute w-7 h-full left-0" />
<div className="flex lg:gap-8 gap-3 lg:p-10 py-8 px-5 z-30 overflow-y-hidden overflow-x-scroll snap-x snap-proximity scrollbar-none relative">
<div className="flex lg:gap-10 gap-4">
{g.map((a, index) => (
@@ -80,7 +80,7 @@ export default function Genres() {
))}
</div>
</div>
- <div className="bg-gradient-to-l from-primary to-transparent z-40 absolute w-7 h-[200px] lg:h-[300px] right-0" />
+ <div className="bg-gradient-to-l from-primary to-transparent z-40 absolute w-7 h-full right-0" />
</div>
</div>
);
diff --git a/components/home/recommendation.js b/components/home/recommendation.js
new file mode 100644
index 0000000..842932c
--- /dev/null
+++ b/components/home/recommendation.js
@@ -0,0 +1,91 @@
+import Image from "next/image";
+// import data from "../../assets/dummyData.json";
+import { BookOpenIcon, PlayIcon } from "@heroicons/react/24/solid";
+import { useDraggable } from "react-use-draggable-scroll";
+import { useRef } from "react";
+import Link from "next/link";
+
+export default function UserRecommendation({ data }) {
+ const ref = useRef(null);
+ const { events } = useDraggable(ref);
+
+ const uniqueRecommendationIds = new Set();
+
+ // Filter out duplicates from the recommendations array
+ const filteredData = data.filter((recommendation) => {
+ // Check if the ID is already in the set
+ if (uniqueRecommendationIds.has(recommendation.id)) {
+ // If it's a duplicate, return false to exclude it from the filtered array
+ return false;
+ }
+
+ // If it's not a duplicate, add the ID to the set and return true
+ uniqueRecommendationIds.add(recommendation.id);
+ return true;
+ });
+
+ return (
+ <div className="flex flex-col bg-tersier relative rounded overflow-hidden">
+ <div className="flex lg:gap-5 z-50">
+ <div className="flex flex-col items-start justify-center gap-3 lg:gap-7 lg:w-[50%] pl-5 lg:px-10">
+ <h2 className="font-bold text-3xl text-white">
+ {data[0].title.userPreferred}
+ </h2>
+ <p
+ dangerouslySetInnerHTML={{
+ __html: data[0].description?.replace(/<[^>]*>/g, ""),
+ }}
+ className="font-roboto font-light line-clamp-3 lg:line-clamp-3"
+ />
+ <button
+ type="button"
+ className="border border-white/70 py-1 px-2 lg:py-2 lg:px-4 rounded-full flex items-center gap-2 text-white font-bold"
+ >
+ {data[0].type === "ANIME" ? (
+ <PlayIcon className="w-5 h-5 text-white" />
+ ) : (
+ <BookOpenIcon className="w-5 h-5 text-white" />
+ )}
+ {data[0].type === "ANIME" ? "Watch" : "Read"} Now
+ </button>
+ </div>
+ <div
+ id="recommendation-list"
+ className="flex gap-5 overflow-x-scroll scrollbar-none px-5 py-7 lg:py-10"
+ ref={ref}
+ {...events}
+ >
+ {filteredData.slice(0, 9).map((i) => (
+ <Link
+ key={i.id}
+ href={`/en/${i.type.toLowerCase()}/${i.id}`}
+ className="relative snap-start shrink-0 group hover:bg-white/20 p-1 rounded"
+ >
+ <Image
+ src={i.coverImage.extraLarge}
+ alt={i.title.userPreferred}
+ width={190}
+ height={256}
+ className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded-md object-cover overflow-hidden transition-all duration-150 ease-in-out"
+ />
+ <span className="absolute rounded pointer-events-none w-[240px] h-[50%] transition-all duration-150 ease-in transform translate-x-[75%] group-hover:translate-x-[80%] top-0 left-0 bg-secondary opacity-0 group-hover:opacity-100 flex flex-col z-50">
+ <div className="">{i.title.userPreferred}</div>
+ <div>a</div>
+ </span>
+ </Link>
+ ))}
+ </div>
+ </div>
+ <div className="absolute top-0 left-0 z-40 bg-gradient-to-r from-transparent from-30% to-80% to-tersier w-[80%] lg:w-[60%] h-full" />
+ {data[0]?.bannerImage && (
+ <Image
+ src={data[0]?.bannerImage}
+ alt={data[0].title.userPreferred}
+ width={500}
+ height={500}
+ className="absolute top-0 left-0 z-30 w-[60%] h-full object-cover opacity-30"
+ />
+ )}
+ </div>
+ );
+}
diff --git a/components/home/schedule.js b/components/home/schedule.js
index 4043c5e..a9846a7 100644
--- a/components/home/schedule.js
+++ b/components/home/schedule.js
@@ -1,17 +1,23 @@
import Image from "next/image";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useState } from "react";
import { convertUnixToTime } from "../../utils/getTimes";
import { PlayIcon } from "@heroicons/react/20/solid";
import { BackwardIcon, ForwardIcon } from "@heroicons/react/24/solid";
import Link from "next/link";
+import { useCountdown } from "../../utils/useCountdownSeconds";
-export default function Schedule({ data, scheduleData, time }) {
+export default function Schedule({ data, scheduleData, anime, update }) {
let now = new Date();
let currentDay =
now.toLocaleString("default", { weekday: "long" }).toLowerCase() +
"Schedule";
currentDay = currentDay.replace("Schedule", "");
+ const [day, hours, minutes, seconds] = useCountdown(
+ anime[0]?.airingSchedule.nodes[0]?.airingAt * 1000 || Date.now(),
+ update
+ );
+
const [currentPage, setCurrentPage] = useState(0);
const [days, setDay] = useState();
@@ -37,8 +43,6 @@ export default function Schedule({ data, scheduleData, time }) {
setCurrentPage(todayIndex >= 0 ? todayIndex : 0);
}, [currentDay, days]);
- // console.log({ scheduleData });
-
return (
<div className="flex flex-col gap-5 px-4 lg:px-0">
<h1 className="font-bold font-karla text-[20px] lg:px-5">
@@ -46,7 +50,7 @@ export default function Schedule({ data, scheduleData, time }) {
</h1>
<div className="rounded mb-5 shadow-md shadow-black">
<div className="overflow-hidden w-full h-[96px] lg:h-[10rem] rounded relative">
- <div className="absolute flex flex-col justify-center pl-5 lg:pl-16 rounded z-20 bg-gradient-to-r from-30% from-[#0c0c0c] to-transparent w-full h-full">
+ <div className="absolute flex flex-col justify-center pl-5 lg:pl-16 rounded z-20 bg-gradient-to-r from-30% from-tersier to-transparent w-full h-full">
<h1 className="text-xs lg:text-lg">Coming Up Next!</h1>
<div className="w-1/2 lg:w-2/5 hidden lg:block font-medium font-karla leading-[2.9rem] text-white line-clamp-1">
<Link
@@ -62,15 +66,15 @@ export default function Schedule({ data, scheduleData, time }) {
</div>
{data.bannerImage ? (
<Image
- src={data.bannerImage || data.coverImage.large}
+ src={data.bannerImage || data.coverImage.extraLarge}
width={500}
height={500}
alt="banner next anime"
- className="absolute z-10 top-0 right-0 w-3/4 h-full object-cover brightness-[30%]"
+ className="absolute z-10 top-0 right-0 w-3/4 h-full object-cover opacity-30"
/>
) : (
<Image
- src={data.coverImage.large}
+ src={data.coverImage.extraLarge}
width={500}
height={500}
sizes="100vw"
@@ -87,22 +91,22 @@ export default function Schedule({ data, scheduleData, time }) {
<div className="flex items-center gap-2 md:gap-5 font-bold font-karla text-sm md:text-xl">
{/* Countdown Timer */}
<div className="flex flex-col items-center">
- <span className="text-action/80">{time.days}</span>
+ <span className="text-action/80">{day}</span>
<span className="text-sm lg:text-base font-medium">Days</span>
</div>
<span></span>
<div className="flex flex-col items-center">
- <span className="text-action/80">{time.hours}</span>
+ <span className="text-action/80">{hours}</span>
<span className="text-sm lg:text-base font-medium">Hours</span>
</div>
<span></span>
<div className="flex flex-col items-center">
- <span className="text-action/80">{time.minutes}</span>
+ <span className="text-action/80">{minutes}</span>
<span className="text-sm lg:text-base font-medium">Mins</span>
</div>
<span></span>
<div className="flex flex-col items-center">
- <span className="text-action/80">{time.seconds}</span>
+ <span className="text-action/80">{seconds}</span>
<span className="text-sm lg:text-base font-medium">Secs</span>
</div>
</div>
diff --git a/components/home/staticNav.js b/components/home/staticNav.js
index 93f7b26..b22a9e3 100644
--- a/components/home/staticNav.js
+++ b/components/home/staticNav.js
@@ -1,51 +1,27 @@
-import { signIn, useSession } from "next-auth/react";
-import { useRouter } from "next/router";
-import { useEffect, useState } from "react";
+import { signIn, signOut, useSession } from "next-auth/react";
import { getCurrentSeason } from "../../utils/getTimes";
import Link from "next/link";
-import { parseCookies } from "nookies";
+// import { } from "@heroicons/react/24/solid";
+import { useSearch } from "../../lib/hooks/isOpenState";
+import Image from "next/image";
+import { UserIcon } from "@heroicons/react/20/solid";
+import { useRouter } from "next/router";
export default function Navigasi() {
const { data: sessions, status } = useSession();
- const [year, setYear] = useState(new Date().getFullYear());
- const [season, setSeason] = useState(getCurrentSeason());
-
- const [lang, setLang] = useState("en");
- const [cookie, setCookies] = useState(null);
+ const year = new Date().getFullYear();
+ const season = getCurrentSeason();
const router = useRouter();
- useEffect(() => {
- let lang = null;
- if (!cookie) {
- const cookie = parseCookies();
- lang = cookie.lang || null;
- setCookies(cookie);
- }
- if (lang === "en" || lang === null) {
- setLang("en");
- } else if (lang === "id") {
- setLang("id");
- }
- }, []);
+ const { setIsOpen } = useSearch();
- const handleFormSubmission = (inputValue) => {
- router.push(`/${lang}/search/${encodeURIComponent(inputValue)}`);
- };
-
- const handleKeyDown = async (event) => {
- if (event.key === "Enter") {
- event.preventDefault();
- const inputValue = event.target.value;
- handleFormSubmission(inputValue);
- }
- };
return (
<>
{/* NAVBAR PC */}
<div className="flex items-center justify-center">
- <div className="flex w-full items-center justify-between px-5 lg:mx-[94px]">
- <div className="flex items-center lg:gap-16 lg:pt-7">
+ <div className="flex w-full items-center justify-between px-5 lg:mx-[94px] lg:pt-7">
+ <div className="flex items-center lg:gap-16">
<Link
href="/en/"
className=" font-outfit lg:text-[40px] text-[30px] font-bold text-[#FF7F57]"
@@ -55,16 +31,35 @@ export default function Navigasi() {
<ul className="hidden items-center gap-10 pt-2 font-outfit text-[14px] lg:flex">
<li>
<Link
- href={`/en/search/anime?season=${season}&seasonYear=${year}`}
+ href={`/en/search/anime?season=${season}&year=${year}`}
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
>
This Season
</Link>
</li>
<li>
- <Link href="/en/search/manga">Manga</Link>
+ <Link
+ href="/en/search/manga"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Manga
+ </Link>
</li>
<li>
- <Link href="/en/search/anime">Anime</Link>
+ <Link
+ href="/en/search/anime"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Anime
+ </Link>
+ </li>
+ <li>
+ <Link
+ href="/en/schedule"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
+ Schedule
+ </Link>
</li>
{status === "loading" ? (
@@ -75,15 +70,19 @@ export default function Navigasi() {
<li>
<button
onClick={() => signIn("AniListProvider")}
- className="ring-1 ring-action font-karla font-bold px-2 py-1 rounded-md"
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ // className="px-2 py-1 ring-1 ring-action font-bold font-karla rounded-md"
>
- Sign in
+ Sign In
</button>
</li>
)}
{sessions && (
<li className="text-center">
- <Link href={`/en/profile/${sessions?.user.name}`}>
+ <Link
+ href={`/en/profile/${sessions?.user.name}`}
+ className="hover:text-action/80 transition-all duration-150 ease-linear"
+ >
My List
</Link>
</li>
@@ -92,18 +91,73 @@ export default function Navigasi() {
)}
</ul>
</div>
- <div className="relative flex lg:scale-75 scale-[65%] items-center mb-7 lg:mb-1">
- <div className="search-box ">
- <input
- className="search-text"
- type="text"
- placeholder="Search Anime"
- onKeyDown={handleKeyDown}
- />
- <div className="search-btn">
- <i className="fas fa-search"></i>
- </div>
- </div>
+ <div className="flex items-center gap-4">
+ <button
+ type="button"
+ onClick={() => setIsOpen(true)}
+ className="flex-center w-[26px] h-[26px]"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ width="32"
+ height="32"
+ viewBox="0 0 24 24"
+ >
+ <path
+ fill="none"
+ stroke="currentColor"
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth="2"
+ d="M15 15l6 6m-11-4a7 7 0 110-14 7 7 0 010 14z"
+ ></path>
+ </svg>
+ </button>
+ {/* <div
+ className="bg-white"
+ // title={sessions ? "Go to Profile" : "Login With AniList"}
+ > */}
+ {sessions ? (
+ <button
+ type="button"
+ onClick={() =>
+ router.push(`/en/profile/${sessions?.user.name}`)
+ }
+ className="w-7 h-7 relative flex flex-col items-center group"
+ >
+ <Image
+ src={sessions?.user.image.large}
+ alt="avatar"
+ width={50}
+ height={50}
+ className="w-full h-full object-cover"
+ />
+ <div className="hidden absolute z-50 w-28 text-center -bottom-20 text-white shadow-2xl opacity-0 bg-secondary p-1 py-2 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all md:grid place-items-center gap-1">
+ <Link
+ href={`/en/profile/${sessions?.user.name}`}
+ className="hover:text-action"
+ >
+ Profile
+ </Link>
+ <div
+ onClick={() => signOut({ callbackUrl: "/" })}
+ className="hover:text-action cursor-pointer"
+ >
+ Log out
+ </div>
+ </div>
+ </button>
+ ) : (
+ <button
+ type="button"
+ onClick={() => signIn("AniListProvider")}
+ title="Login With AniList"
+ className="w-7 h-7 bg-white/30 rounded-full overflow-hidden"
+ >
+ <UserIcon className="w-full h-full translate-y-2 text-white/50" />
+ </button>
+ )}
+ {/* </div> */}
</div>
</div>
</div>
diff --git a/components/id-components/player/Artplayer.js b/components/id/player/Artplayer.js
index e209433..e209433 100644
--- a/components/id-components/player/Artplayer.js
+++ b/components/id/player/Artplayer.js
diff --git a/components/id-components/player/VideoPlayerId.js b/components/id/player/VideoPlayerId.js
index 1168313..1168313 100644
--- a/components/id-components/player/VideoPlayerId.js
+++ b/components/id/player/VideoPlayerId.js
diff --git a/components/navbar.js b/components/navbar.js
index e148b09..7edd6c1 100644
--- a/components/navbar.js
+++ b/components/navbar.js
@@ -3,6 +3,7 @@ import Link from "next/link";
import { useSession, signIn, signOut } from "next-auth/react";
import Image from "next/image";
import { parseCookies } from "nookies";
+import MobileNav from "./shared/MobileNav";
function Navbar(props) {
const { data: session, status } = useSession();
@@ -45,193 +46,7 @@ function Navbar(props) {
<Link href={`/${lang}/`}>moopa</Link>
</div>
- {/* Mobile Hamburger */}
- {!isVisible && (
- <button
- onClick={handleShowClick}
- className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden"
- id="bars"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- className="h-[42px] w-[61.5px] text-[#8BA0B2] fill-orange-500"
- viewBox="0 0 20 20"
- fill="currentColor"
- >
- <path
- fillRule="evenodd"
- d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
- clipRule="evenodd"
- />
- </svg>
- </button>
- )}
-
- {/* Mobile Menu */}
- <div
- className={`transition-all duration-150 ${
- fade ? "opacity-100" : "opacity-0"
- } z-50`}
- >
- {isVisible && session && (
- <Link
- href={`/${lang}/profile/${session?.user?.name}`}
- className="fixed lg:hidden bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#17171f]"
- >
- <Image
- src={session?.user.image.large}
- alt="user avatar"
- height={500}
- width={500}
- className="object-cover w-[60px] h-[60px] rounded-full"
- />
- </Link>
- )}
- {isVisible && (
- <div className="fixed bottom-[30px] right-[20px] z-50 flex h-[51px] w-[300px] items-center justify-center gap-8 rounded-[8px] text-[11px] bg-[#17171f] shadow-lg lg:hidden">
- <div className="grid grid-cols-4 place-items-center gap-6">
- <button className="group flex flex-col items-center">
- <Link href={`/${lang}/`} className="">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="w-6 h-6 group-hover:stroke-action"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25"
- />
- </svg>
- </Link>
- <Link
- href={`/${lang}/`}
- className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
- >
- home
- </Link>
- </button>
- <button className="group flex flex-col items-center">
- <Link href={`/${lang}/about`}>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="w-6 h-6 group-hover:stroke-action"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"
- />
- </svg>
- </Link>
- <Link
- href={`/${lang}/about`}
- className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
- >
- about
- </Link>
- </button>
- <button className="group flex gap-[1.5px] flex-col items-center ">
- <div>
- <Link href={`/${lang}/search/anime`}>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- strokeWidth={1.5}
- stroke="currentColor"
- className="w-6 h-6 group-hover:stroke-action"
- >
- <path
- strokeLinecap="round"
- strokeLinejoin="round"
- d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
- />
- </svg>
- </Link>
- </div>
- <Link
- href={`/${lang}/search/anime`}
- className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
- >
- search
- </Link>
- </button>
- {session ? (
- <button
- onClick={() => signOut("AniListProvider")}
- className="group flex gap-[1.5px] flex-col items-center "
- >
- <div>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 96 960 960"
- className="group-hover:fill-action w-6 h-6 fill-txt"
- >
- <path d="M186.666 936q-27 0-46.833-19.833T120 869.334V282.666q0-27 19.833-46.833T186.666 216H474v66.666H186.666v586.668H474V936H186.666zm470.668-176.667l-47-48 102-102H370v-66.666h341.001l-102-102 46.999-48 184 184-182.666 182.666z"></path>
- </svg>
- </div>
- <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action">
- logout
- </h1>
- </button>
- ) : (
- <button
- onClick={() => signIn("AniListProvider")}
- className="group flex gap-[1.5px] flex-col items-center "
- >
- <div>
- <svg
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 96 960 960"
- className="group-hover:fill-action w-6 h-6 fill-txt mr-2"
- >
- <path d="M486 936v-66.666h287.334V282.666H486V216h287.334q27 0 46.833 19.833T840 282.666v586.668q0 27-19.833 46.833T773.334 936H486zm-78.666-176.667l-47-48 102-102H120v-66.666h341l-102-102 47-48 184 184-182.666 182.666z"></path>
- </svg>
- </div>
- <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action">
- login
- </h1>
- </button>
- )}
- </div>
- <button onClick={handleHideClick}>
- <svg
- width="20"
- height="21"
- className="fill-orange-500"
- viewBox="0 0 20 21"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <rect
- x="2.44043"
- y="0.941467"
- width="23.5842"
- height="3.45134"
- rx="1.72567"
- transform="rotate(45 2.44043 0.941467)"
- />
- <rect
- x="19.1172"
- y="3.38196"
- width="23.5842"
- height="3.45134"
- rx="1.72567"
- transform="rotate(135 19.1172 3.38196)"
- />
- </svg>
- </button>
- </div>
- )}
- </div>
+ <MobileNav sessions={session} />
<nav className="left-0 top-[-100%] hidden w-auto items-center gap-10 px-5 lg:flex">
<ul className="hidden gap-10 font-roboto text-md lg:flex items-center relative">
diff --git a/components/search/dropdown/inputSelect.js b/components/search/dropdown/inputSelect.js
new file mode 100644
index 0000000..d36ee6e
--- /dev/null
+++ b/components/search/dropdown/inputSelect.js
@@ -0,0 +1,111 @@
+import { Fragment } from "react";
+import { Combobox, Transition } from "@headlessui/react";
+import {
+ CheckIcon,
+ ChevronDownIcon,
+ MagnifyingGlassIcon,
+} from "@heroicons/react/20/solid";
+import React from "react";
+import { useRouter } from "next/router";
+
+export default function InputSelect({
+ data,
+ label,
+ keyDown,
+ selected,
+ setSelected,
+ query,
+ setQuery,
+ inputRef,
+}) {
+ const router = useRouter();
+
+ function handleChange(event) {
+ setSelected(event);
+ router.push(`/en/search/${event.value.toLowerCase()}`);
+ }
+
+ return (
+ <Combobox value={selected} onChange={(e) => handleChange(e)}>
+ <div className="relative mt-1 z-[55] w-full">
+ <div className="flex items-center gap-2 mb-2 relative">
+ <span className="font-bold text-lg">{label}</span>
+ <Combobox.Button className="py-[2px] bg-secondary/70 rounded text-sm font-karla flex items-center px-2">
+ {selected.name}
+ <ChevronDownIcon
+ className="h-5 w-5 text-gray-400"
+ aria-hidden="true"
+ />
+ </Combobox.Button>
+ </div>
+ <div className="relative w-full cursor-default overflow-hidden rounded-lg bg-secondary text-left shadow-md focus:outline-none sm:text-sm">
+ <input
+ type="text"
+ value={query || ""}
+ className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 bg-secondary text-gray-300 focus:ring-0 outline-none"
+ onKeyDown={keyDown}
+ onChange={(e) => setQuery(e.target.value)}
+ ref={inputRef}
+ />
+ <div className="absolute inset-y-0 right-0 flex items-center pr-2">
+ <MagnifyingGlassIcon className="h-5 w-5 text-gray-400" />
+ </div>
+ </div>
+ <Transition
+ as={Fragment}
+ enter="transition ease-out duration-200"
+ enterFrom="transform opacity-0 scale-95 translate-y-5"
+ enterTo="transform opacity-100 scale-100"
+ leave="transition ease-in duration-75"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95 translate-y-5"
+ afterLeave={() => setQuery("")}
+ >
+ <Combobox.Options
+ className="absolute z-[55] mt-1 max-h-60 w-full rounded-md bg-secondary py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
+ style={{ scrollbarGutter: "stable" }}
+ >
+ {data.length === 0 && query !== "" ? (
+ <div className="relative cursor-default select-none py-2 px-4 text-gray-300">
+ Nothing found.
+ </div>
+ ) : (
+ data.map((item) => (
+ <Combobox.Option
+ key={item.value}
+ className={({ active }) =>
+ `relative cursor-pointer select-none py-2 px-2 mx-2 rounded-md ${
+ active ? "bg-white/5 text-white" : "text-gray-300"
+ }`
+ }
+ value={item}
+ >
+ {({ selected, active }) => (
+ <React.Fragment>
+ <span
+ className={`block truncate ${
+ selected ? "font-medium text-white" : "font-normal"
+ }`}
+ >
+ {item.name}
+ </span>
+ {selected ? (
+ <span
+ className={`absolute inset-y-0 right-0 flex items-center pl-3 pr-1 ${
+ active ? "text-white" : "text-action"
+ }`}
+ >
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
+ </span>
+ ) : null}
+ </React.Fragment>
+ )}
+ </Combobox.Option>
+ ))
+ )}
+ </Combobox.Options>
+ </Transition>
+ </div>
+ </Combobox>
+ );
+}
diff --git a/components/search/dropdown/multiSelector.js b/components/search/dropdown/multiSelector.js
new file mode 100644
index 0000000..8eea547
--- /dev/null
+++ b/components/search/dropdown/multiSelector.js
@@ -0,0 +1,168 @@
+import { Fragment, useState } from "react";
+import { Combobox, Transition } from "@headlessui/react";
+import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid";
+import React from "react";
+
+export default function MultiSelector({
+ data,
+ other,
+ label,
+ selected,
+ setSelected,
+ inputRef,
+}) {
+ // const [selected, setSelected] = useState();
+ const [query, setQuery] = useState("");
+
+ const filteredMain =
+ query === ""
+ ? data
+ : data.filter((item) =>
+ item.name
+ .toLowerCase()
+ .replace(/\s+/g, "")
+ .includes(query.toLowerCase().replace(/\s+/g, ""))
+ );
+
+ const filteredOther =
+ query === ""
+ ? other
+ : other.filter((item) =>
+ item.name
+ .toLowerCase()
+ .replace(/\s+/g, "")
+ .includes(query.toLowerCase().replace(/\s+/g, ""))
+ );
+
+ return (
+ <Combobox value={selected} onChange={setSelected} multiple>
+ <div className="relative mt-1 min-w-full lg:min-w-[160px] w-full">
+ <div className="font-bold text-lg mb-2">{label}</div>
+ <div className="relative w-full cursor-default overflow-hidden rounded-lg bg-secondary text-left shadow-md focus:outline-none sm:text-sm">
+ <Combobox.Input
+ className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 bg-secondary text-gray-300 focus:ring-0 outline-none"
+ displayValue={(item) => item?.map((item) => item?.name).join(", ")}
+ placeholder="Any"
+ onChange={(event) => setQuery(event.target.value)}
+ />
+ <Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
+ <ChevronDownIcon
+ className="h-5 w-5 text-gray-400"
+ aria-hidden="true"
+ />
+ </Combobox.Button>
+ </div>
+ <Transition
+ as={Fragment}
+ enter="transition ease-out duration-200"
+ enterFrom="transform opacity-0 scale-95 translate-y-5"
+ enterTo="transform opacity-100 scale-100"
+ leave="transition ease-in duration-75"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95 translate-y-5"
+ afterLeave={() => setQuery("")}
+ >
+ <Combobox.Options
+ className="absolute z-50 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-white/10 scrollbar-thumb-rounded-lg mt-1 max-h-60 w-full overflow-auto rounded-md bg-secondary py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
+ // style={{ scrollbarGutter: "stable" }}
+ >
+ {filteredOther.length === 0 &&
+ filteredMain.length === 0 &&
+ query !== "" ? (
+ <div className="relative cursor-default select-none py-2 px-4 text-gray-300">
+ Nothing found.
+ </div>
+ ) : (
+ <div className="space-y-1">
+ <span className="px-3 font-karla font-bold text-sm text-gray-200">
+ GENRES
+ </span>
+ <div>
+ {filteredMain.map((item) => (
+ <Combobox.Option
+ key={item.value}
+ className={({ active }) =>
+ `relative cursor-pointer select-none py-2 px-2 ml-2 mr-1 rounded-md ${
+ active ? "bg-white/5 text-action" : "text-gray-300"
+ }`
+ }
+ value={item}
+ >
+ {({ selected, active }) => (
+ <React.Fragment>
+ <span
+ className={`block truncate ${
+ selected
+ ? "font-medium text-white"
+ : "font-normal"
+ }`}
+ >
+ {item.name}
+ </span>
+ {selected ? (
+ <span
+ className={`absolute inset-y-0 right-0 flex items-center pl-3 pr-1 ${
+ active ? "text-white" : "text-action"
+ }`}
+ >
+ <CheckIcon
+ className="h-5 w-5"
+ aria-hidden="true"
+ />
+ </span>
+ ) : null}
+ </React.Fragment>
+ )}
+ </Combobox.Option>
+ ))}
+ </div>
+ <span className="px-3 font-karla font-bold text-sm text-gray-200">
+ TAGS
+ </span>
+ <div>
+ {filteredOther.map((item) => (
+ <Combobox.Option
+ key={item.value}
+ className={({ active }) =>
+ `relative cursor-pointer select-none py-2 px-2 ml-2 mr-1 rounded-md ${
+ active ? "bg-white/5 text-white" : "text-gray-300"
+ }`
+ }
+ value={item}
+ >
+ {({ selected, active }) => (
+ <React.Fragment>
+ <span
+ className={`block truncate ${
+ selected
+ ? "font-medium text-white"
+ : "font-normal"
+ }`}
+ >
+ {item.name}
+ </span>
+ {selected ? (
+ <span
+ className={`absolute inset-y-0 right-0 flex items-center pl-3 pr-1 ${
+ active ? "text-white" : "text-action"
+ }`}
+ >
+ <CheckIcon
+ className="h-5 w-5"
+ aria-hidden="true"
+ />
+ </span>
+ ) : null}
+ </React.Fragment>
+ )}
+ </Combobox.Option>
+ ))}
+ </div>
+ </div>
+ )}
+ </Combobox.Options>
+ </Transition>
+ </div>
+ </Combobox>
+ );
+}
diff --git a/components/search/dropdown/singleSelector.js b/components/search/dropdown/singleSelector.js
new file mode 100644
index 0000000..ec8afe0
--- /dev/null
+++ b/components/search/dropdown/singleSelector.js
@@ -0,0 +1,98 @@
+import { Fragment, useState } from "react";
+import { Combobox, Listbox, Transition } from "@headlessui/react";
+import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid";
+import React from "react";
+
+export default function SingleSelector({ data, label, selected, setSelected }) {
+ // const [selected, setSelected] = useState();
+ const [query, setQuery] = useState("");
+
+ const filteredData =
+ query === ""
+ ? data
+ : data.filter((item) =>
+ item.name
+ .toLowerCase()
+ .replace(/\s+/g, "")
+ .includes(query.toLowerCase().replace(/\s+/g, ""))
+ );
+
+ return (
+ <Listbox value={selected} onChange={setSelected}>
+ <div className="relative mt-1 min-w-full lg:min-w-[160px] w-full">
+ <div className="font-bold text-lg mb-2">{label}</div>
+ <div className="relative w-full cursor-default overflow-hidden rounded-lg bg-secondary text-left shadow-md focus:outline-none sm:text-sm">
+ {/* <Combobox.Input
+ className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 bg-secondary text-gray-300 focus:ring-0 outline-none"
+ displayValue={(item) => item.name}
+ placeholder="Any"
+ onChange={(event) => setQuery(event.target.value)}
+ /> */}
+ <Listbox.Button className="w-full border-none py-2 text-start pl-3 text-sm leading-5 bg-secondary text-gray-400">
+ <span>{selected?.name || "Any"}</span>
+ <div className="absolute inset-y-0 right-0 flex items-center pr-2">
+ <ChevronDownIcon
+ className="h-5 w-5 text-gray-400"
+ aria-hidden="true"
+ />
+ </div>
+ </Listbox.Button>
+ </div>
+ <Transition
+ as={Fragment}
+ enter="transition ease-out duration-200"
+ enterFrom="transform opacity-0 scale-95 translate-y-5"
+ enterTo="transform opacity-100 scale-100"
+ leave="transition ease-in duration-75"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95 translate-y-5"
+ afterLeave={() => setQuery("")}
+ >
+ <Listbox.Options
+ className="absolute z-50 scrollbar-thin scrollbar-thumb-white/10 scrollbar-thumb-rounded-lg mt-1 max-h-80 w-full overflow-auto rounded-md bg-secondary py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
+ style={{ scrollbarGutter: "stable" }}
+ >
+ {filteredData.length === 0 && query !== "" ? (
+ <div className="relative cursor-default select-none py-2 px-4 text-gray-300">
+ Nothing found.
+ </div>
+ ) : (
+ filteredData.map((item) => (
+ <Listbox.Option
+ key={item.value}
+ className={({ active }) =>
+ `relative cursor-pointer select-none py-2 px-2 ml-2 mr-1 rounded-md ${
+ active ? "bg-white/5 text-action" : "text-gray-300"
+ }`
+ }
+ value={item}
+ >
+ {({ selected, active }) => (
+ <React.Fragment>
+ <span
+ className={`block truncate ${
+ selected ? "font-medium text-white" : "font-normal"
+ }`}
+ >
+ {item.name}
+ </span>
+ {selected ? (
+ <span
+ className={`absolute inset-y-0 right-0 flex items-center pl-3 pr-1 ${
+ active ? "text-white" : "text-action"
+ }`}
+ >
+ <CheckIcon className="h-5 w-5" aria-hidden="true" />
+ </span>
+ ) : null}
+ </React.Fragment>
+ )}
+ </Listbox.Option>
+ ))
+ )}
+ </Listbox.Options>
+ </Transition>
+ </div>
+ </Listbox>
+ );
+}
diff --git a/components/search/selection.js b/components/search/selection.js
new file mode 100644
index 0000000..767361d
--- /dev/null
+++ b/components/search/selection.js
@@ -0,0 +1,415 @@
+export const mediaType = [
+ { name: "Anime", value: "ANIME" },
+ { name: "Manga", value: "MANGA" },
+];
+export const genreOptions = [
+ {
+ name: "Action",
+ value: "Action",
+ type: "genres",
+ },
+ {
+ name: "Adventure",
+ value: "Adventure",
+ type: "genres",
+ },
+ {
+ name: "Comedy",
+ value: "Comedy",
+ type: "genres",
+ },
+ {
+ name: "Drama",
+ value: "Drama",
+ type: "genres",
+ },
+ {
+ name: "Ecchi",
+ value: "Ecchi",
+ type: "genres",
+ },
+ {
+ name: "Fantasy",
+ value: "Fantasy",
+ type: "genres",
+ },
+ {
+ name: "Horror",
+ value: "Horror",
+ type: "genres",
+ },
+ {
+ name: "Mahou Shoujo",
+ value: "Mahou Shoujo",
+ type: "genres",
+ },
+ {
+ name: "Mecha",
+ value: "Mecha",
+ type: "genres",
+ },
+ {
+ name: "Music",
+ value: "Music",
+ type: "genres",
+ },
+ {
+ name: "Mystery",
+ value: "Mystery",
+ type: "genres",
+ },
+ {
+ name: "Psychological",
+ value: "Psychological",
+ type: "genres",
+ },
+ {
+ name: "Romance",
+ value: "Romance",
+ type: "genres",
+ },
+ {
+ name: "Sci-Fi",
+ value: "Sci-Fi",
+ type: "genres",
+ },
+ {
+ name: "Slice of Life",
+ value: "Slice of Life",
+ type: "genres",
+ },
+ {
+ name: "Sports",
+ value: "Sports",
+ type: "genres",
+ },
+ {
+ name: "Supernatural",
+ value: "Supernatural",
+ type: "genres",
+ },
+ {
+ name: "Thriller",
+ value: "Thriller",
+ type: "genres",
+ },
+];
+export const tagsOption = [
+ { name: "4-koma", value: "4-koma", type: "tags" },
+ { name: "Achronological Order", value: "Achronological Order", type: "tags" },
+ { name: "Afterlife", value: "Afterlife", type: "tags" },
+ { name: "Age Gap", value: "Age Gap", type: "tags" },
+ { name: "Airsoft", value: "Airsoft", type: "tags" },
+ { name: "Aliens", value: "Aliens", type: "tags" },
+ { name: "Alternate Universe", value: "Alternate Universe", type: "tags" },
+ { name: "American Football", value: "American Football", type: "tags" },
+ { name: "Amnesia", value: "Amnesia", type: "tags" },
+ { name: "Anti-Hero", value: "Anti-Hero", type: "tags" },
+ { name: "Archery", value: "Archery", type: "tags" },
+ { name: "Assassins", value: "Assassins", type: "tags" },
+ { name: "Athletics", value: "Athletics", type: "tags" },
+ { name: "Augmented Reality", value: "Augmented Reality", type: "tags" },
+ { name: "Aviation", value: "Aviation", type: "tags" },
+ { name: "Badminton", value: "Badminton", type: "tags" },
+ { name: "Band", value: "Band", type: "tags" },
+ { name: "Bar", value: "Bar", type: "tags" },
+ { name: "Baseball", value: "Baseball", type: "tags" },
+ { name: "Basketball", value: "Basketball", type: "tags" },
+ { name: "Battle Royale", value: "Battle Royale", type: "tags" },
+ { name: "Biographical", value: "Biographical", type: "tags" },
+ { name: "Bisexual", value: "Bisexual", type: "tags" },
+ { name: "Body Swapping", value: "Body Swapping", type: "tags" },
+ { name: "Boxing", value: "Boxing", type: "tags" },
+ { name: "Bullying", value: "Bullying", type: "tags" },
+ { name: "Calligraphy", value: "Calligraphy", type: "tags" },
+ { name: "Card Battle", value: "Card Battle", type: "tags" },
+ { name: "Cars", value: "Cars", type: "tags" },
+ { name: "CGI", value: "CGI", type: "tags" },
+ { name: "Chibi", value: "Chibi", type: "tags" },
+ { name: "Chuunibyou", value: "Chuunibyou", type: "tags" },
+ { name: "Classic Literature", value: "Classic Literature", type: "tags" },
+ { name: "College", value: "College", type: "tags" },
+ { name: "Coming of Age", value: "Coming of Age", type: "tags" },
+ { name: "Cosplay", value: "Cosplay", type: "tags" },
+ { name: "Crossdressing", value: "Crossdressing", type: "tags" },
+ { name: "Crossover", value: "Crossover", type: "tags" },
+ { name: "Cultivation", value: "Cultivation", type: "tags" },
+ {
+ name: "Cute Girls Doing Cute Things",
+ value: "Cute Girls Doing Cute Things",
+ type: "tags",
+ },
+ { name: "Cyberpunk", value: "Cyberpunk", type: "tags" },
+ { name: "Cycling", value: "Cycling", type: "tags" },
+ { name: "Dancing", value: "Dancing", type: "tags" },
+ { name: "Delinquents", value: "Delinquents", type: "tags" },
+ { name: "Demons", value: "Demons", type: "tags" },
+ { name: "Development", value: "Development", type: "tags" },
+ { name: "Dragons", value: "Dragons", type: "tags" },
+ { name: "Drawing", value: "Drawing", type: "tags" },
+ { name: "Dystopian", value: "Dystopian", type: "tags" },
+ { name: "Economics", value: "Economics", type: "tags" },
+ { name: "Educational", value: "Educational", type: "tags" },
+ { name: "Ensemble Cast", value: "Ensemble Cast", type: "tags" },
+ { name: "Environmental", value: "Environmental", type: "tags" },
+ { name: "Episodic", value: "Episodic", type: "tags" },
+ { name: "Espionage", value: "Espionage", type: "tags" },
+ { name: "Fairy Tale", value: "Fairy Tale", type: "tags" },
+ { name: "Family Life", value: "Family Life", type: "tags" },
+ { name: "Fashion", value: "Fashion", type: "tags" },
+ { name: "Female Protagonist", value: "Female Protagonist", type: "tags" },
+ { name: "Fishing", value: "Fishing", type: "tags" },
+ { name: "Fitness", value: "Fitness", type: "tags" },
+ { name: "Flash", value: "Flash", type: "tags" },
+ { name: "Food", value: "Food", type: "tags" },
+ { name: "Football", value: "Football", type: "tags" },
+ { name: "Foreign", value: "Foreign", type: "tags" },
+ { name: "Fugitive", value: "Fugitive", type: "tags" },
+ { name: "Full CGI", value: "Full CGI", type: "tags" },
+ { name: "Full Colour", value: "Full Colour", type: "tags" },
+ { name: "Gambling", value: "Gambling", type: "tags" },
+ { name: "Gangs", value: "Gangs", type: "tags" },
+ { name: "Gender Bending", value: "Gender Bending", type: "tags" },
+ { name: "Gender Neutral", value: "Gender Neutral", type: "tags" },
+ { name: "Ghost", value: "Ghost", type: "tags" },
+ { name: "Gods", value: "Gods", type: "tags" },
+ { name: "Gore", value: "Gore", type: "tags" },
+ { name: "Guns", value: "Guns", type: "tags" },
+ { name: "Gyaru", value: "Gyaru", type: "tags" },
+ { name: "Harem", value: "Harem", type: "tags" },
+ { name: "Henshin", value: "Henshin", type: "tags" },
+ { name: "Hikikomori", value: "Hikikomori", type: "tags" },
+ { name: "Historical", value: "Historical", type: "tags" },
+ { name: "Ice Skating", value: "Ice Skating", type: "tags" },
+ { name: "Idol", value: "Idol", type: "tags" },
+ { name: "Isekai", value: "Isekai", type: "tags" },
+ { name: "Iyashikei", value: "Iyashikei", type: "tags" },
+ { name: "Josei", value: "Josei", type: "tags" },
+ { name: "Kaiju", value: "Kaiju", type: "tags" },
+ { name: "Karuta", value: "Karuta", type: "tags" },
+ { name: "Kemonomimi", value: "Kemonomimi", type: "tags" },
+ { name: "Kids", value: "Kids", type: "tags" },
+ { name: "Love Triangle", value: "Love Triangle", type: "tags" },
+ { name: "Mafia", value: "Mafia", type: "tags" },
+ { name: "Magic", value: "Magic", type: "tags" },
+ { name: "Mahjong", value: "Mahjong", type: "tags" },
+ { name: "Maids", value: "Maids", type: "tags" },
+ { name: "Male Protagonist", value: "Male Protagonist", type: "tags" },
+ { name: "Martial Arts", value: "Martial Arts", type: "tags" },
+ { name: "Memory Manipulation", value: "Memory Manipulation", type: "tags" },
+ { name: "Meta", value: "Meta", type: "tags" },
+ { name: "Military", value: "Military", type: "tags" },
+ { name: "Monster Girl", value: "Monster Girl", type: "tags" },
+ { name: "Mopeds", value: "Mopeds", type: "tags" },
+ { name: "Motorcycles", value: "Motorcycles", type: "tags" },
+ { name: "Musical", value: "Musical", type: "tags" },
+ { name: "Mythology", value: "Mythology", type: "tags" },
+ { name: "Nekomimi", value: "Nekomimi", type: "tags" },
+ { name: "Ninja", value: "Ninja", type: "tags" },
+ { name: "No Dialogue", value: "No Dialogue", type: "tags" },
+ { name: "Noir", value: "Noir", type: "tags" },
+ { name: "Nudity", value: "Nudity", type: "tags" },
+ { name: "Otaku Culture", value: "Otaku Culture", type: "tags" },
+ { name: "Outdoor", value: "Outdoor", type: "tags" },
+ { name: "Parody", value: "Parody", type: "tags" },
+ { name: "Philosophy", value: "Philosophy", type: "tags" },
+ { name: "Photography", value: "Photography", type: "tags" },
+ { name: "Pirates", value: "Pirates", type: "tags" },
+ { name: "Poker", value: "Poker", type: "tags" },
+ { name: "Police", value: "Police", type: "tags" },
+ { name: "Politics", value: "Politics", type: "tags" },
+ { name: "Post-Apocalyptic", value: "Post-Apocalyptic", type: "tags" },
+ { name: "Primarily Adult Cast", value: "Primarily Adult Cast", type: "tags" },
+ {
+ name: "Primarily Female Cast",
+ value: "Primarily Female Cast",
+ type: "tags",
+ },
+ { name: "Primarily Male Cast", value: "Primarily Male Cast", type: "tags" },
+ { name: "Puppetry", value: "Puppetry", type: "tags" },
+ { name: "Real Robot", value: "Real Robot", type: "tags" },
+ { name: "Rehabilitation", value: "Rehabilitation", type: "tags" },
+ { name: "Reincarnation", value: "Reincarnation", type: "tags" },
+ { name: "Revenge", value: "Revenge", type: "tags" },
+ { name: "Reverse Harem", value: "Reverse Harem", type: "tags" },
+ { name: "Robots", value: "Robots", type: "tags" },
+ { name: "Rugby", value: "Rugby", type: "tags" },
+ { name: "Rural", value: "Rural", type: "tags" },
+ { name: "Samurai", value: "Samurai", type: "tags" },
+ { name: "Satire", value: "Satire", type: "tags" },
+ { name: "School", value: "School", type: "tags" },
+ { name: "School Club", value: "School Club", type: "tags" },
+ { name: "Seinen", value: "Seinen", type: "tags" },
+ { name: "Ships", value: "Ships", type: "tags" },
+ { name: "Shogi", value: "Shogi", type: "tags" },
+ { name: "Shoujo", value: "Shoujo", type: "tags" },
+ { name: "Shoujo Ai", value: "Shoujo Ai", type: "tags" },
+ { name: "Shounen", value: "Shounen", type: "tags" },
+ { name: "Shounen Ai", value: "Shounen Ai", type: "tags" },
+ { name: "Slapstick", value: "Slapstick", type: "tags" },
+ { name: "Slavery", value: "Slavery", type: "tags" },
+ { name: "Space", value: "Space", type: "tags" },
+ { name: "Space Opera", value: "Space Opera", type: "tags" },
+ { name: "Steampunk", value: "Steampunk", type: "tags" },
+ { name: "Stop Motion", value: "Stop Motion", type: "tags" },
+ { name: "Super Power", value: "Super Power", type: "tags" },
+ { name: "Super Robot", value: "Super Robot", type: "tags" },
+ { name: "Superhero", value: "Superhero", type: "tags" },
+ { name: "Surreal Comedy", value: "Surreal Comedy", type: "tags" },
+ { name: "Survival", value: "Survival", type: "tags" },
+ { name: "Swimming", value: "Swimming", type: "tags" },
+ { name: "Swordplay", value: "Swordplay", type: "tags" },
+ { name: "Table Tennis", value: "Table Tennis", type: "tags" },
+ { name: "Tanks", value: "Tanks", type: "tags" },
+ { name: "Teacher", value: "Teacher", type: "tags" },
+ { name: "Tennis", value: "Tennis", type: "tags" },
+ { name: "Terrorism", value: "Terrorism", type: "tags" },
+ { name: "Time Manipulation", value: "Time Manipulation", type: "tags" },
+ { name: "Time Skip", value: "Time Skip", type: "tags" },
+ { name: "Tragedy", value: "Tragedy", type: "tags" },
+ { name: "Trains", value: "Trains", type: "tags" },
+ { name: "Triads", value: "Triads", type: "tags" },
+ { name: "Tsundere", value: "Tsundere", type: "tags" },
+ { name: "Urban Fantasy", value: "Urban Fantasy", type: "tags" },
+ { name: "Vampire", value: "Vampire", type: "tags" },
+ { name: "Video Games", value: "Video Games", type: "tags" },
+ { name: "Virtual World", value: "Virtual World", type: "tags" },
+ { name: "Volleyball", value: "Volleyball", type: "tags" },
+ { name: "War", value: "War", type: "tags" },
+ { name: "Witch", value: "Witch", type: "tags" },
+ { name: "Work", value: "Work", type: "tags" },
+ { name: "Wrestling", value: "Wrestling", type: "tags" },
+ { name: "Writing", value: "Writing", type: "tags" },
+ { name: "Wuxia", value: "Wuxia", type: "tags" },
+ { name: "Yakuza", value: "Yakuza", type: "tags" },
+ { name: "Yandere", value: "Yandere", type: "tags" },
+ { name: "Youkai", value: "Youkai", type: "tags" },
+ { name: "Zombie", value: "Zombie", type: "tags" },
+];
+export const formatOptions = [
+ { name: "TV", value: "TV" },
+ { name: "TV Short", value: "TV_SHORT" },
+ { name: "Movie", value: "MOVIE" },
+ { name: "Special", value: "SPECIAL" },
+ { name: "OVA", value: "OVA" },
+ { name: "ONA", value: "ONA" },
+ { name: "Music", value: "MUSIC" },
+ { name: "Manga", value: "MANGA" },
+ { name: "Novel", value: "NOVEL" },
+ { name: "One Shot", value: "ONE_SHOT" },
+];
+export const animeFormatOptions = [
+ { name: "TV", value: "TV" },
+ { name: "TV Short", value: "TV_SHORT" },
+ { name: "Movie", value: "MOVIE" },
+ { name: "Special", value: "SPECIAL" },
+ { name: "OVA", value: "OVA" },
+ { name: "ONA", value: "ONA" },
+];
+export const mangaFormatOptions = [
+ { name: "Manga", value: "MANGA" },
+ { name: "Novel", value: "NOVEL" },
+ { name: "One Shot", value: "ONE_SHOT" },
+];
+export const sortOptions = [
+ { name: "Date Added", value: "ID_DESC" },
+ { name: "Title", value: "TITLE_ROMAJI" },
+ { name: "Release Date", value: "START_DATE_DESC" },
+ { name: "Average Score", value: "SCORE_DESC" },
+ { name: "Popularity", value: "POPULARITY_DESC" },
+ { name: "Trending", value: ["TRENDING_DESC", "POPULARITY_DESC"] },
+ { name: "Favorites", value: "FAVOURITES_DESC" },
+];
+export const yearOptions = [
+ { name: "1940", value: "1940" },
+ { name: "1941", value: "1941" },
+ { name: "1942", value: "1942" },
+ { name: "1943", value: "1943" },
+ { name: "1944", value: "1944" },
+ { name: "1945", value: "1945" },
+ { name: "1946", value: "1946" },
+ { name: "1947", value: "1947" },
+ { name: "1948", value: "1948" },
+ { name: "1949", value: "1949" },
+ { name: "1950", value: "1950" },
+ { name: "1951", value: "1951" },
+ { name: "1952", value: "1952" },
+ { name: "1953", value: "1953" },
+ { name: "1954", value: "1954" },
+ { name: "1955", value: "1955" },
+ { name: "1956", value: "1956" },
+ { name: "1957", value: "1957" },
+ { name: "1958", value: "1958" },
+ { name: "1959", value: "1959" },
+ { name: "1960", value: "1960" },
+ { name: "1961", value: "1961" },
+ { name: "1962", value: "1962" },
+ { name: "1963", value: "1963" },
+ { name: "1964", value: "1964" },
+ { name: "1965", value: "1965" },
+ { name: "1966", value: "1966" },
+ { name: "1967", value: "1967" },
+ { name: "1968", value: "1968" },
+ { name: "1969", value: "1969" },
+ { name: "1970", value: "1970" },
+ { name: "1971", value: "1971" },
+ { name: "1972", value: "1972" },
+ { name: "1973", value: "1973" },
+ { name: "1974", value: "1974" },
+ { name: "1975", value: "1975" },
+ { name: "1976", value: "1976" },
+ { name: "1977", value: "1977" },
+ { name: "1978", value: "1978" },
+ { name: "1979", value: "1979" },
+ { name: "1980", value: "1980" },
+ { name: "1981", value: "1981" },
+ { name: "1982", value: "1982" },
+ { name: "1983", value: "1983" },
+ { name: "1984", value: "1984" },
+ { name: "1985", value: "1985" },
+ { name: "1986", value: "1986" },
+ { name: "1987", value: "1987" },
+ { name: "1988", value: "1988" },
+ { name: "1989", value: "1989" },
+ { name: "1990", value: "1990" },
+ { name: "1991", value: "1991" },
+ { name: "1992", value: "1992" },
+ { name: "1993", value: "1993" },
+ { name: "1994", value: "1994" },
+ { name: "1995", value: "1995" },
+ { name: "1996", value: "1996" },
+ { name: "1997", value: "1997" },
+ { name: "1998", value: "1998" },
+ { name: "1999", value: "1999" },
+ { name: "2000", value: "2000" },
+ { name: "2001", value: "2001" },
+ { name: "2002", value: "2002" },
+ { name: "2003", value: "2003" },
+ { name: "2004", value: "2004" },
+ { name: "2005", value: "2005" },
+ { name: "2006", value: "2006" },
+ { name: "2007", value: "2007" },
+ { name: "2008", value: "2008" },
+ { name: "2009", value: "2009" },
+ { name: "2010", value: "2010" },
+ { name: "2011", value: "2011" },
+ { name: "2012", value: "2012" },
+ { name: "2013", value: "2013" },
+ { name: "2014", value: "2014" },
+ { name: "2015", value: "2015" },
+ { name: "2016", value: "2016" },
+ { name: "2017", value: "2017" },
+ { name: "2018", value: "2018" },
+ { name: "2019", value: "2019" },
+ { name: "2020", value: "2020" },
+ { name: "2021", value: "2021" },
+ { name: "2022", value: "2022" },
+ { name: "2023", value: "2023" },
+ { name: "2024", value: "2024" },
+];
+export const seasonOptions = [
+ { name: "Winter", value: "WINTER" },
+ { name: "Spring", value: "SPRING" },
+ { name: "Summer", value: "SUMMER" },
+ { name: "Fall", value: "FALL" },
+];
diff --git a/components/searchBar.js b/components/searchBar.js
deleted file mode 100644
index 20d2d7c..0000000
--- a/components/searchBar.js
+++ /dev/null
@@ -1,155 +0,0 @@
-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/anilist/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("");
-
- const [lang, setLang] = useState("en");
-
- 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]);
-
- useEffect(() => {
- const lang = localStorage.getItem("lang") || "id";
- if (lang === "en" || lang === null) {
- setLang("en");
- } else if (lang === "id") {
- setLang("id");
- }
- }, []);
-
- function handleSubmit(e) {
- e.preventDefault();
- if (data?.media.length) {
- router.push(`${lang}/anime/${data?.media[0].id}`);
- }
- }
-
- return (
- <AnimatePresence>
- {isOpen && (
- <m.div
- initial={{ opacity: 0, y: -100 }}
- animate={{ opacity: 1, y: 0 }}
- exit={{ opacity: 0, y: -100 }}
- className="fixed top-0 w-screen flex justify-center z-50"
- >
- <div
- ref={searchBoxRef}
- className={` bg-[#1c1c1fef] text-white p-4 ${
- isOpen ? "flex" : "hidden"
- } flex-col w-[80%] backdrop-blur-sm rounded-b-lg`}
- >
- <form onSubmit={handleSubmit}>
- <input
- type="text"
- className="w-full rounded-lg px-4 py-2 mb-2 bg-[#474747]"
- placeholder="Search..."
- onChange={(e) => setQuery(e.target.value)}
- />
- </form>
- <div className="flex flex-col gap-2 p-2 font-karla">
- {data?.media.map((i) => (
- <Link
- key={i.id}
- href={i.type === "ANIME" ? `${lang}/anime/${i.id}` : `/`}
- className="flex hover:bg-[#3e3e3e] rounded-md"
- >
- <Image
- src={i.coverImage.extraLarge}
- alt="search results"
- width={500}
- height={500}
- className="object-cover w-14 h-14 rounded-md"
- />
- <div className="flex items-center justify-between w-full px-5">
- <div>
- <h1>{i.title.userPreferred}</h1>
- <h5 className="text-sm font-light text-[#878787] flex gap-2">
- {i.status
- ?.toLowerCase()
- .replace(/^\w/, (c) => c.toUpperCase())}{" "}
- {i.status && i.season && <>&#183;</>}{" "}
- {i.season
- ?.toLowerCase()
- .replace(/^\w/, (c) => c.toUpperCase())}{" "}
- {(i.status || i.season) && i.episodes && <>&#183;</>}{" "}
- {i.episodes || 0} Episodes
- </h5>
- </div>
- <div className="text-sm text-[#b5b5b5] ">
- <h1>
- {i.type
- ?.toLowerCase()
- .replace(/^\w/, (c) => c.toUpperCase())}
- </h1>
- </div>
- </div>
- </Link>
- ))}
- </div>
- {query && (
- <button className="flex items-center gap-2 justify-center">
- <MagnifyingGlassIcon className="h-5 w-5" />
- <Link href={`${lang}/search/${query}`}>More Results...</Link>
- </button>
- )}
- </div>
- </m.div>
- )}
- </AnimatePresence>
- );
-};
-
-export default SearchBar;
diff --git a/components/searchPalette.js b/components/searchPalette.js
new file mode 100644
index 0000000..07c8f89
--- /dev/null
+++ b/components/searchPalette.js
@@ -0,0 +1,265 @@
+import { Fragment, useEffect, useState } from "react";
+import { Combobox, Dialog, Menu, Transition } from "@headlessui/react";
+import useDebounce from "../lib/hooks/useDebounce";
+import Image from "next/image";
+import { useRouter } from "next/router";
+import { useSearch } from "../lib/hooks/isOpenState";
+import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
+import { BookOpenIcon, PlayIcon } from "@heroicons/react/20/solid";
+import { useAniList } from "../lib/anilist/useAnilist";
+import { getFormat } from "../utils/getFormat";
+
+export default function SearchPalette() {
+ const { isOpen, setIsOpen } = useSearch();
+ const { quickSearch } = useAniList();
+
+ const [query, setQuery] = useState("");
+ const [data, setData] = useState(null);
+ const debounceSearch = useDebounce(query, 500);
+ const [loading, setLoading] = useState(false);
+ const [type, setType] = useState("ANIME");
+
+ const [nextPage, setNextPage] = useState(false);
+
+ const router = useRouter();
+
+ function closeModal() {
+ setIsOpen(false);
+ }
+
+ function handleChange(event) {
+ router.push(`/en/${type.toLowerCase()}/${event}`);
+ }
+
+ async function advance() {
+ setLoading(true);
+ const res = await quickSearch({
+ search: debounceSearch,
+ type,
+ });
+ setData(res?.data?.Page?.results);
+ setNextPage(res?.data?.Page?.pageInfo?.hasNextPage);
+ setLoading(false);
+ }
+
+ useEffect(() => {
+ advance();
+ }, [debounceSearch, type]);
+
+ useEffect(() => {
+ const handleKeyDown = (e) => {
+ if (e.code === "KeyS" && e.ctrlKey) {
+ // do your stuff
+ e.preventDefault();
+ setIsOpen((prev) => !prev);
+ setData(null);
+ setQuery("");
+ }
+ };
+
+ window.addEventListener("keydown", handleKeyDown);
+
+ return () => {
+ window.removeEventListener("keydown", handleKeyDown);
+ };
+ }, []);
+
+ return (
+ <Transition appear show={isOpen} as={Fragment}>
+ <Dialog as="div" className="relative z-[6969]" onClose={closeModal}>
+ <Transition.Child
+ as={Fragment}
+ enter="ease-out duration-300"
+ enterFrom="opacity-0"
+ enterTo="opacity-100"
+ leave="ease-in duration-200"
+ leaveFrom="opacity-100"
+ leaveTo="opacity-0"
+ >
+ <div className="fixed inset-0 bg-black/90" />
+ </Transition.Child>
+
+ <div className="fixed inset-0 overflow-y-auto">
+ <div className="flex min-h-full items-center justify-center p-4 text-center">
+ <Transition.Child
+ as={Fragment}
+ enter="ease-out duration-200"
+ enterFrom="opacity-0 scale-95"
+ enterTo="opacity-100 scale-100"
+ leave="ease-in duration-100"
+ leaveFrom="opacity-100 scale-100"
+ leaveTo="opacity-0 scale-95"
+ >
+ <Dialog.Panel className="w-full max-w-2xl max-h-[68dvh] transform text-left transition-all">
+ <Combobox
+ as="div"
+ className="max-w-2xl mx-auto rounded-lg shadow-2xl relative flex flex-col"
+ onChange={(e) => {
+ handleChange(e);
+ setData(null);
+ setIsOpen(false);
+ setQuery("");
+ }}
+ >
+ <div className="flex justify-between py-1 font-karla">
+ <div className="flex items-center px-2 gap-2">
+ <p>For quick access :</p>
+ <div className="bg-secondary text-white text-xs font-bold px-2 py-1 rounded-md">
+ <span>CTRL</span>
+ </div>
+ <span>+</span>
+ <div className="bg-secondary text-white text-xs font-bold px-2 py-1 rounded-md">
+ <span>S</span>
+ </div>
+ </div>
+ <div>
+ <Menu
+ as="div"
+ className="relative inline-block text-left"
+ >
+ <div>
+ <Menu.Button className="capitalize bg-secondary inline-flex w-full justify-center rounded-md px-3 py-2 text-sm font-medium text-white hover:bg-opacity-80 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75">
+ {type.toLowerCase()}
+ <ChevronDownIcon
+ className="ml-2 -mr-1 h-5 w-5 text-violet-200 hover:text-violet-100"
+ // aria-hidden="true"
+ />
+ </Menu.Button>
+ </div>
+ <Transition
+ as={Fragment}
+ enter="transition ease-out duration-100"
+ enterFrom="transform opacity-0 scale-95"
+ enterTo="transform opacity-100 scale-100"
+ leave="transition ease-in duration-75"
+ leaveFrom="transform opacity-100 scale-100"
+ leaveTo="transform opacity-0 scale-95"
+ >
+ <Menu.Items className="absolute right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-primary shadow ring-1 ring-black ring-opacity-5 focus:outline-none">
+ <div className="px-1 py-1">
+ <Menu.Item>
+ {({ active }) => (
+ <button
+ onClick={() => setType("ANIME")}
+ className={`${
+ active
+ ? "bg-secondary text-white"
+ : "text-white"
+ } group flex w-full items-center gap-3 rounded-md px-2 py-2 text-sm`}
+ >
+ <PlayIcon className="w-6 h-6" />
+ <span>Anime</span>
+ </button>
+ )}
+ </Menu.Item>
+ <Menu.Item>
+ {({ active }) => (
+ <button
+ onClick={() => setType("MANGA")}
+ className={`${
+ active
+ ? "bg-secondary text-white"
+ : "text-white"
+ } group flex w-full items-center gap-3 rounded-md px-2 py-2 text-sm`}
+ >
+ <BookOpenIcon className="w-6 h-6" />
+ <span>Manga</span>
+ </button>
+ )}
+ </Menu.Item>
+ </div>
+ </Menu.Items>
+ </Transition>
+ </Menu>
+ </div>
+ </div>
+ <div className="flex items-center text-base font-medium rounded bg-secondary">
+ <Combobox.Input
+ className="p-5 text-white w-full bg-transparent border-0 outline-none"
+ placeholder="Search something..."
+ onChange={(event) => setQuery(event.target.value)}
+ />
+ </div>
+ <Combobox.Options
+ static
+ className="bg-secondary rounded mt-2 max-h-[50dvh] overflow-y-auto flex flex-col scrollbar-thin scrollbar-thumb-primary scrollbar-thumb-rounded"
+ >
+ {!loading ? (
+ <Fragment>
+ {data?.length > 0
+ ? data?.map((i) => (
+ <Combobox.Option
+ key={i.id}
+ value={i.id}
+ className={({ active }) =>
+ `flex items-center gap-3 p-5 ${
+ active ? "bg-primary/40 cursor-pointer" : ""
+ }`
+ }
+ >
+ <div className="shrink-0">
+ <Image
+ src={i.coverImage.medium}
+ alt="coverImage"
+ width={100}
+ height={100}
+ className="w-16 h-16 object-cover rounded"
+ />
+ </div>
+ <div className="flex flex-col w-full h-full">
+ <h3 className="font-karla font-semibold">
+ {i.title.userPreferred}
+ </h3>
+ <p className="text-sm text-white/50">
+ {i.startDate.year} {getFormat(i.format)}
+ </p>
+ </div>
+ </Combobox.Option>
+ ))
+ : !loading &&
+ debounceSearch !== "" && (
+ <p className="flex-center font-karla gap-3 p-5">
+ No results found.
+ </p>
+ )}
+ {nextPage && (
+ <button
+ type="button"
+ onClick={() => {
+ router.push(
+ `/en/search/${type.toLowerCase()}/${
+ query !== "" ? `?search=${query}` : ""
+ }`
+ );
+ setIsOpen(false);
+ setQuery("");
+ }}
+ className="flex-center font-karla gap-2 py-4 hover:bg-primary/30 cursor-pointer"
+ >
+ <span>View More</span>
+ <ChevronRightIcon className="w-4 h-4" />
+ </button>
+ )}
+ </Fragment>
+ ) : (
+ <div className="flex-center gap-3 p-5">
+ <div className="flex justify-center">
+ <div className="lds-ellipsis">
+ <span></span>
+ <span></span>
+ <span></span>
+ <span></span>
+ </div>
+ </div>
+ </div>
+ )}
+ </Combobox.Options>
+ </Combobox>
+ </Dialog.Panel>
+ </Transition.Child>
+ </div>
+ </div>
+ </Dialog>
+ </Transition>
+ );
+}
diff --git a/components/shared/MobileNav.js b/components/shared/MobileNav.js
new file mode 100644
index 0000000..6dd1e64
--- /dev/null
+++ b/components/shared/MobileNav.js
@@ -0,0 +1,170 @@
+import { MagnifyingGlassIcon } from "@heroicons/react/20/solid";
+import { CalendarIcon, ClockIcon, HomeIcon } from "@heroicons/react/24/outline";
+import { signIn, signOut } from "next-auth/react";
+import Image from "next/image";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { useState } from "react";
+
+export default function MobileNav({ sessions, hideProfile = false }) {
+ const [isVisible, setIsVisible] = useState(false);
+
+ const handleShowClick = () => {
+ setIsVisible(true);
+ };
+
+ const handleHideClick = () => {
+ setIsVisible(false);
+ };
+ return (
+ <>
+ {/* NAVBAR */}
+ <div className="z-[1000]">
+ {!isVisible && (
+ <button
+ onClick={handleShowClick}
+ className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden"
+ id="bars"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className="h-[42px] w-[61.5px] text-white/60 fill-orange-500"
+ viewBox="0 0 20 20"
+ fill="currentColor"
+ >
+ <path
+ fillRule="evenodd"
+ d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
+ clipRule="evenodd"
+ />
+ </svg>
+ </button>
+ )}
+ </div>
+
+ {/* Mobile Menu */}
+ <div
+ className={`transition-all duration-150 subpixel-antialiased z-[500]`}
+ >
+ {isVisible && sessions && !hideProfile && (
+ <Link
+ href={`/en/profile/${sessions?.user.name}`}
+ className="fixed lg:hidden bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#17171f]"
+ >
+ <Image
+ src={sessions?.user.image.large}
+ alt="user avatar"
+ width={60}
+ height={60}
+ className="object-cover w-[60px] h-[60px] rounded-full"
+ />
+ </Link>
+ )}
+ {isVisible && (
+ <div className="fixed bottom-[30px] right-[20px] z-[500] flex h-[51px] px-5 items-center justify-center gap-8 rounded-[8px] text-[11px] bg-[#17171f] shadow-lg lg:hidden">
+ <div className="flex items-center gap-5">
+ <button className="group flex flex-col items-center">
+ <Link href="/en/">
+ <HomeIcon className="w-6 h-6 group-hover:text-action" />
+ </Link>
+ <Link
+ href="/en/"
+ className="font-karla font-bold text-white/60 group-hover:text-action"
+ >
+ home
+ </Link>
+ </button>
+ <button className="group flex flex-col items-center gap-[1px]">
+ <Link href="/en/schedule">
+ <CalendarIcon className="w-6 h-6 group-hover:text-action" />
+ </Link>
+ <Link
+ href="/en/schedule"
+ className="font-karla font-bold text-white/60 group-hover:text-action"
+ >
+ schedule
+ </Link>
+ </button>
+ <button className="group flex gap-[1px] flex-col items-center">
+ <Link href="/en/search/anime">
+ <MagnifyingGlassIcon className="w-6 h-6 group-hover:text-action" />
+ </Link>
+
+ <Link
+ href="/en/search/anime"
+ className="font-karla font-bold text-white/60 group-hover:text-action"
+ >
+ search
+ </Link>
+ </button>
+ {sessions ? (
+ <button
+ onClick={() => signOut("AniListProvider")}
+ className="group flex gap-[1.5px] flex-col items-center "
+ >
+ <div>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 96 960 960"
+ className="group-hover:fill-action w-6 h-6 fill-txt"
+ >
+ <path d="M186.666 936q-27 0-46.833-19.833T120 869.334V282.666q0-27 19.833-46.833T186.666 216H474v66.666H186.666v586.668H474V936H186.666zm470.668-176.667l-47-48 102-102H370v-66.666h341.001l-102-102 46.999-48 184 184-182.666 182.666z"></path>
+ </svg>
+ </div>
+ <h1 className="font-karla font-bold text-white/60 group-hover:text-action">
+ logout
+ </h1>
+ </button>
+ ) : (
+ <button
+ onClick={() => signIn("AniListProvider")}
+ className="group flex gap-[1.5px] flex-col items-center "
+ >
+ <div>
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 96 960 960"
+ className="group-hover:fill-action w-6 h-6 fill-txt mr-2"
+ >
+ <path d="M486 936v-66.666h287.334V282.666H486V216h287.334q27 0 46.833 19.833T840 282.666v586.668q0 27-19.833 46.833T773.334 936H486zm-78.666-176.667l-47-48 102-102H120v-66.666h341l-102-102 47-48 184 184-182.666 182.666z"></path>
+ </svg>
+ </div>
+ <h1 className="font-karla font-bold text-white/60 group-hover:text-action">
+ login
+ </h1>
+ </button>
+ )}
+ </div>
+ <button onClick={handleHideClick}>
+ <svg
+ width="20"
+ height="21"
+ className="fill-orange-500"
+ viewBox="0 0 20 21"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <rect
+ x="2.44043"
+ y="0.941467"
+ width="23.5842"
+ height="3.45134"
+ rx="1.72567"
+ transform="rotate(45 2.44043 0.941467)"
+ />
+ <rect
+ x="19.1172"
+ y="3.38196"
+ width="23.5842"
+ height="3.45134"
+ rx="1.72567"
+ transform="rotate(135 19.1172 3.38196)"
+ />
+ </svg>
+ </button>
+ </div>
+ )}
+ </div>
+ </>
+ );
+}
diff --git a/components/home/mobileNav.js b/components/shared/hamburgerMenu.js
index 52c9d52..7e4bdf1 100644
--- a/components/home/mobileNav.js
+++ b/components/shared/hamburgerMenu.js
@@ -1,62 +1,52 @@
-import { signIn, signOut } from "next-auth/react";
+import { signIn, signOut, useSession } from "next-auth/react";
+import Image from "next/image";
import Link from "next/link";
-import { useState } from "react";
+import React, { useState } from "react";
-export default function MobileNav({ sessions }) {
+export default function HamburgerMenu() {
+ const { data: session } = useSession();
const [isVisible, setIsVisible] = useState(false);
+ const [fade, setFade] = useState(false);
const handleShowClick = () => {
setIsVisible(true);
+ setFade(true);
};
const handleHideClick = () => {
setIsVisible(false);
+ setFade(false);
};
- return (
- <>
- {/* NAVBAR */}
- <div className="z-50">
- {!isVisible && (
- <button
- onClick={handleShowClick}
- className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden"
- id="bars"
- >
- <svg
- xmlns="http://www.w3.org/2000/svg"
- className="h-[42px] w-[61.5px] text-[#8BA0B2] fill-orange-500"
- viewBox="0 0 20 20"
- fill="currentColor"
- >
- <path
- fillRule="evenodd"
- d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
- clipRule="evenodd"
- />
- </svg>
- </button>
- )}
- </div>
- {/* Mobile Menu */}
- <div className={`transition-all duration-150 subpixel-antialiased z-50`}>
- {isVisible && sessions && (
- <Link
- href={`/en/profile/${sessions?.user.name}`}
- className="fixed lg:hidden bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#17171f]"
+ return (
+ <React.Fragment>
+ {/* Mobile Hamburger */}
+ {!isVisible && (
+ <button
+ onClick={handleShowClick}
+ className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden"
+ id="bars"
+ >
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className="h-[42px] w-[61.5px] text-[#8BA0B2] fill-orange-500"
+ viewBox="0 0 20 20"
+ fill="currentColor"
>
- <img
- src={sessions?.user.image.large}
- alt="user avatar"
- className="object-cover w-[60px] h-[60px] rounded-full"
+ <path
+ fillRule="evenodd"
+ d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
+ clipRule="evenodd"
/>
- </Link>
- )}
+ </svg>
+ </button>
+ )}
+ <div className={`z-50`}>
{isVisible && (
<div className="fixed bottom-[30px] right-[20px] z-50 flex h-[51px] w-[300px] items-center justify-center gap-8 rounded-[8px] text-[11px] bg-[#17171f] shadow-lg lg:hidden">
<div className="grid grid-cols-4 place-items-center gap-6">
<button className="group flex flex-col items-center">
- <Link href="/en/">
+ <Link href={`/en/`} className="">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -73,14 +63,14 @@ export default function MobileNav({ sessions }) {
</svg>
</Link>
<Link
- href="/en/"
+ href={`/en/`}
className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
>
home
</Link>
</button>
<button className="group flex flex-col items-center">
- <Link href="/en/about">
+ <Link href={`/en/about`}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -97,7 +87,7 @@ export default function MobileNav({ sessions }) {
</svg>
</Link>
<Link
- href="/en/about"
+ href={`/en/about`}
className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
>
about
@@ -105,7 +95,7 @@ export default function MobileNav({ sessions }) {
</button>
<button className="group flex gap-[1.5px] flex-col items-center ">
<div>
- <Link href="/en/search/anime">
+ <Link href={`/en/search/anime`}>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
@@ -123,13 +113,13 @@ export default function MobileNav({ sessions }) {
</Link>
</div>
<Link
- href="/en/search/anime"
+ href={`/en/search/anime`}
className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"
>
search
</Link>
</button>
- {sessions ? (
+ {session ? (
<button
onClick={() => signOut("AniListProvider")}
className="group flex gap-[1.5px] flex-col items-center "
@@ -197,6 +187,6 @@ export default function MobileNav({ sessions }) {
</div>
)}
</div>
- </>
+ </React.Fragment>
);
}
diff --git a/components/shared/loading.js b/components/shared/loading.js
new file mode 100644
index 0000000..4620645
--- /dev/null
+++ b/components/shared/loading.js
@@ -0,0 +1,20 @@
+import Image from "next/image";
+
+export default function Loading() {
+ return (
+ <>
+ <div className="flex flex-col gap-5 items-center justify-center w-full z-[800]">
+ {/* <Image
+ src="/wait-animation.gif"
+ width="0"
+ height="0"
+ className="w-[30%] h-[30%]"
+ /> */}
+ <div className="flex flex-col items-center font-karla gap-2">
+ <p>Please Wait...</p>
+ <div className="loader"></div>
+ </div>
+ </div>
+ </>
+ );
+}
diff --git a/components/videoPlayer.js b/components/videoPlayer.js
index dcde703..a961c1b 100644
--- a/components/videoPlayer.js
+++ b/components/videoPlayer.js
@@ -26,7 +26,6 @@ export default function VideoPlayer({
progress,
session,
aniId,
- stats,
skip,
title,
poster,
@@ -86,12 +85,10 @@ export default function VideoPlayer({
return {
...(isDefault && { default: true }),
html: items.quality === "default" ? "adaptive" : items.quality,
- url:
- provider === "gogoanime"
- ? `https://cors.moopa.workers.dev/?url=${encodeURIComponent(
- items.url
- )}${referer ? `&referer=${encodeURIComponent(referer)}` : ""}`
- : `${proxy}${items.url}`,
+ // url: `https://cors.moopa.live/${items.url}`,
+ url: `${proxy}?url=${encodeURIComponent(items.url)}${
+ referer ? `&referer=${encodeURIComponent(referer)}` : ""
+ }`,
};
});
@@ -136,7 +133,7 @@ export default function VideoPlayer({
option={{
url: `${url}`,
title: `${title}`,
- autoplay: false,
+ autoplay: true,
screenshot: true,
moreVideoAttr: {
crossOrigin: "anonymous",
@@ -225,16 +222,19 @@ export default function VideoPlayer({
name: session?.user?.name,
id: String(aniId),
watchId: id,
- title: track?.playing?.title || aniTitle,
+ title: track.playing?.title || aniTitle,
aniTitle: aniTitle,
- image: track?.playing?.image || info?.coverImage?.extraLarge,
+ image: track.playing?.image || info?.coverImage?.extraLarge,
number: Number(progress),
duration: art.duration,
timeWatched: art.currentTime,
provider: provider,
+ nextId: track.next?.id,
+ nextNumber: Number(track.next?.number),
+ dub: dub ? true : false,
}),
});
- // console.log("updating db");
+ // console.log("updating db", { track });
}, 5000);
art.on("video:pause", () => {
@@ -263,6 +263,9 @@ export default function VideoPlayer({
duration: art.duration,
timeWatched: art.currentTime,
provider: provider,
+ nextId: track?.next?.id,
+ nextNumber: track?.next?.number,
+ dub: dub ? true : false,
createdAt: new Date().toISOString(),
});
}, 5000);
@@ -297,7 +300,7 @@ export default function VideoPlayer({
// use >= instead of >
if (marked < 1) {
marked = 1;
- markProgress(aniId, progress, stats);
+ markProgress(aniId, progress);
}
}
});