aboutsummaryrefslogtreecommitdiff
path: root/components/anime/mobile
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/mobile
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/mobile')
-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
3 files changed, 528 insertions, 63 deletions
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>
);
}