aboutsummaryrefslogtreecommitdiff
path: root/components/anime
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/anime
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/anime')
-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
11 files changed, 781 insertions, 208 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>