aboutsummaryrefslogtreecommitdiff
path: root/components/home
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-12-24 13:03:54 +0700
committerFactiven <[email protected]>2023-12-24 13:03:54 +0700
commit50a0f0240d7fef133eb5acc1bea2b1168b08e9db (patch)
tree307e09e505580415a58d64b5fc3580e9235869f1 /components/home
parentUpdate README.md (#104) (diff)
downloadmoopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.tar.xz
moopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.zip
migrate to typescript
Diffstat (limited to 'components/home')
-rw-r--r--components/home/content.tsx (renamed from components/home/content.js)200
-rw-r--r--components/home/recommendation.js125
-rw-r--r--components/home/schedule.js4
3 files changed, 247 insertions, 82 deletions
diff --git a/components/home/content.js b/components/home/content.tsx
index d2498f6..b193381 100644
--- a/components/home/content.js
+++ b/components/home/content.tsx
@@ -15,6 +15,63 @@ import HistoryOptions from "./content/historyOptions";
import { toast } from "sonner";
import { truncateImgUrl } from "@/utils/imageUtils";
+type ContentProps = {
+ ids: string;
+ section: string;
+ data?: any;
+ userData?: UserDataTypes[];
+ og?: any;
+ userName?: string;
+ setRemoved?: any;
+ type?: string;
+};
+
+type UserDataTypes = {
+ id: string;
+ aniId?: string;
+ title?: string;
+ aniTitle?: string;
+ image?: string;
+ episode?: number;
+ timeWatched?: number;
+ duration?: number;
+ provider?: string;
+ nextId?: string;
+ nextNumber?: number;
+ dub?: boolean;
+ createdDate: string;
+ userProfileId: string;
+ watchId: string;
+};
+
+interface SlicedDataTypes {
+ id: string | number;
+ slug?: string;
+ nextAiringEpisode?: any;
+ currentEpisode?: number;
+ idMal: number;
+ status: string;
+ title: Title;
+ bannerImage: string;
+ coverImage: CoverImage | string;
+ image?: string;
+ episodeNumber?: number;
+ description: string;
+}
+
+interface Title {
+ romaji: string;
+ english: string;
+ native: string;
+}
+
+interface CoverImage {
+ extraLarge: string;
+ large: string;
+ medium: string;
+ color?: string;
+}
+
export default function Content({
ids,
section,
@@ -24,12 +81,12 @@ export default function Content({
userName,
setRemoved,
type = "anime",
-}) {
- const router = useRouter();
-
- const ref = useRef();
+}: ContentProps) {
+ const ref = useRef<HTMLElement>(null!);
const { events } = useDraggable(ref);
+ const router = useRouter();
+
const [clicked, setClicked] = useState(false);
useEffect(() => {
@@ -45,19 +102,27 @@ export default function Content({
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");
+ if (ref.current) {
+ ref.current.classList.add("scroll-smooth");
+ var slider = document.getElementById(ids);
+ if (slider?.scrollLeft) {
+ 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");
+ if (ref.current) {
+ ref.current.classList.add("scroll-smooth");
+ var slider = document.getElementById(ids);
+ if (slider?.scrollLeft) {
+ slider.scrollLeft = slider.scrollLeft + 500;
+ }
+ ref.current.classList.remove("scroll-smooth");
+ }
};
- const handleScroll = (e) => {
+ const handleScroll = (e: any) => {
const scrollLeft = e.target.scrollLeft > 31;
const scrollRight =
e.target.scrollLeft < e.target.scrollWidth - e.target.clientWidth;
@@ -65,10 +130,12 @@ export default function Content({
setScrollRight(scrollRight);
};
- function handleAlert(e) {
+ function handleAlert(e: string) {
if (localStorage.getItem("clicked")) {
const existingDataString = localStorage.getItem("clicked");
- const existingData = JSON.parse(existingDataString);
+ const existingData = existingDataString
+ ? JSON.parse(existingDataString)
+ : {};
existingData[e] = true;
@@ -87,8 +154,8 @@ export default function Content({
}
const array = data;
- let filteredData = array?.filter((item) => item !== null);
- const slicedData =
+ let filteredData = array?.filter((item: any) => item !== null);
+ const slicedData: SlicedDataTypes[] =
filteredData?.length > 15 ? filteredData?.slice(0, 15) : filteredData;
const goToPage = () => {
@@ -112,7 +179,7 @@ export default function Content({
}
};
- const removeItem = async (id, aniId) => {
+ const removeItem = async (id: string, aniId: string) => {
if (userName) {
// remove from database
const res = await fetch(`/api/user/update/episode`, {
@@ -131,7 +198,7 @@ export default function Content({
if (id) {
// remove from local storage
const artplayerSettings =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ JSON.parse(localStorage.getItem("artplayer_settings") || "{}") || {};
if (artplayerSettings[id]) {
delete artplayerSettings[id];
localStorage.setItem(
@@ -142,9 +209,9 @@ export default function Content({
}
if (aniId) {
const currentData =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ JSON.parse(localStorage.getItem("artplayer_settings") || "{}") || {};
- const updatedData = {};
+ const updatedData: { [key: string]: any } = {};
for (const key in currentData) {
const item = currentData[key];
@@ -166,7 +233,7 @@ export default function Content({
if (id) {
// remove from local storage
const artplayerSettings =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ JSON.parse(localStorage.getItem("artplayer_settings") || "{}") || {};
if (artplayerSettings[id]) {
delete artplayerSettings[id];
localStorage.setItem(
@@ -178,10 +245,10 @@ export default function Content({
}
if (aniId) {
const currentData =
- JSON.parse(localStorage.getItem("artplayer_settings")) || {};
+ JSON.parse(localStorage.getItem("artplayer_settings") || "{}") || {};
// Create a new object to store the updated data
- const updatedData = {};
+ const updatedData: { [key: string]: any } = {};
// Iterate through the current data and copy items with different aniId to the updated object
for (const key in currentData) {
@@ -223,11 +290,22 @@ export default function Content({
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}
{...events}
- ref={ref}
+ ref={ref as React.RefObject<HTMLDivElement>}
>
{ids !== "recentlyWatched"
? slicedData?.map((anime) => {
- const progress = og?.find((i) => i.mediaId === anime.id);
+ const progress = og?.find((i: any) => i.mediaId === anime.id);
+
+ let image;
+ if (typeof anime.coverImage === "string") {
+ image = truncateImgUrl(anime.coverImage);
+ } else if (anime.coverImage) {
+ image = anime.coverImage.extraLarge || anime.coverImage.large;
+ }
+
+ if (!image && anime.image) {
+ image = anime.image;
+ }
return (
<div
@@ -238,6 +316,14 @@ export default function Content({
href={
ids === "listManga"
? `/en/manga/${anime.id}`
+ : ids === "recentAdded"
+ ? anime?.slug
+ ? `/en/anime/watch/${
+ anime.id
+ }/gogoanime?id=${encodeURIComponent(
+ anime?.slug
+ )}&num=${anime.currentEpisode}`
+ : `/en/${type}/${anime.id}`
: `/en/${type}/${anime.id}`
}
className="hover:scale-105 hover:shadow-lg duration-300 ease-out group relative"
@@ -255,7 +341,7 @@ export default function Content({
)}
{checkProgress(progress) && (
<div
- onClick={() => handleAlert(anime.id)}
+ onClick={() => handleAlert(String(anime.id))}
className="group-hover:visible invisible absolute top-0 bg-black bg-opacity-20 w-full h-full z-20 text-center"
>
<h1 className="text-[12px] lg:text-sm pt-28 lg:pt-44 font-bold opacity-100">
@@ -282,31 +368,20 @@ export default function Content({
{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 ||
- truncateImgUrl(anime?.coverImage) ||
- "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"
- />
+ {image && (
+ <Image
+ draggable={false}
+ src={image}
+ alt={
+ anime.title.romaji ||
+ anime.title.english ||
+ "coverImage"
+ }
+ width={500}
+ height={300}
+ className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90"
+ />
+ )}
</div>
{ids === "recentAdded" && (
<Fragment>
@@ -356,7 +431,7 @@ export default function Content({
.map((i) => {
const time = i.timeWatched;
const duration = i.duration;
- let prog = (time / duration) * 100;
+ let prog = time && duration ? (time / duration) * 100 : 0;
if (prog > 90) prog = 100;
return (
@@ -378,9 +453,11 @@ export default function Content({
router.push(
`/en/anime/watch/${i.aniId}/${
i.provider
- }?id=${encodeURIComponent(i?.nextId)}&num=${
- i?.nextNumber
- }${i?.dub ? `&dub=${i?.dub}` : ""}`
+ }?id=${encodeURIComponent(
+ i?.nextId || ""
+ )}&num=${i?.nextNumber}${
+ i?.dub ? `&dub=${i?.dub}` : ""
+ }`
);
}}
>
@@ -404,11 +481,11 @@ export default function Content({
<PlayIcon className="w-5 h-5 shrink-0" />
<h1
className="font-semibold font-karla line-clamp-1"
- title={i?.title || i.anititle}
+ title={i?.title || i?.aniTitle}
>
{i?.title === i.aniTitle
? `Episode ${i.episode}`
- : i?.title || i.anititle}
+ : i?.title || i?.aniTitle}
</h1>
</div>
<span
@@ -456,7 +533,8 @@ export default function Content({
</div>
);
})}
- {userData?.filter((i) => i.aniId !== null)?.length >= 10 &&
+ {userData &&
+ userData?.filter((i) => i.aniId !== null)?.length >= 10 &&
section !== "Recommendations" && (
<div
key={section}
@@ -498,7 +576,7 @@ export default function Content({
);
}
-function convertSecondsToTime(sec) {
+function convertSecondsToTime(sec: number) {
let days = Math.floor(sec / (3600 * 24));
let hours = Math.floor((sec % (3600 * 24)) / 3600);
let minutes = Math.floor((sec % 3600) / 60);
@@ -516,7 +594,7 @@ function convertSecondsToTime(sec) {
return time.trim();
}
-function checkProgress(entry) {
+function checkProgress(entry: { progress: any; media: any }) {
const { progress, media } = entry;
const { episodes, nextAiringEpisode } = media;
diff --git a/components/home/recommendation.js b/components/home/recommendation.js
index 842932c..b643456 100644
--- a/components/home/recommendation.js
+++ b/components/home/recommendation.js
@@ -1,13 +1,22 @@
import Image from "next/image";
// import data from "../../assets/dummyData.json";
-import { BookOpenIcon, PlayIcon } from "@heroicons/react/24/solid";
+import {
+ BookOpenIcon as BookOpenSolid,
+ PlayIcon,
+} from "@heroicons/react/24/solid";
import { useDraggable } from "react-use-draggable-scroll";
import { useRef } from "react";
import Link from "next/link";
+import {
+ BookOpenIcon as BookOpenOutline,
+ PlayCircleIcon,
+} from "@heroicons/react/24/outline";
export default function UserRecommendation({ data }) {
- const ref = useRef(null);
- const { events } = useDraggable(ref);
+ const mobileRef = useRef(null);
+ const desktopRef = useRef(null);
+ const { events: mobileEvent } = useDraggable(mobileRef);
+ const { events: desktopEvent } = useDraggable(desktopRef);
const uniqueRecommendationIds = new Set();
@@ -25,10 +34,13 @@ export default function UserRecommendation({ data }) {
});
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 lg:bg-tersier relative rounded overflow-hidden">
+ <div className="hidden lg: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">
+ <h2
+ className="font-inter font-bold text-3xl text-white line-clamp-2"
+ title={data[0].title.userPreferred}
+ >
{data[0].title.userPreferred}
</h2>
<p
@@ -37,53 +49,128 @@ export default function UserRecommendation({ data }) {
}}
className="font-roboto font-light line-clamp-3 lg:line-clamp-3"
/>
- <button
- type="button"
+ <Link
+ href={`/en/${data[0].type.toLowerCase()}/${data[0].id}`}
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" />
+ <BookOpenSolid className="w-5 h-5 text-white" />
)}
{data[0].type === "ANIME" ? "Watch" : "Read"} Now
- </button>
+ </Link>
</div>
<div
id="recommendation-list"
className="flex gap-5 overflow-x-scroll scrollbar-none px-5 py-7 lg:py-10"
- ref={ref}
- {...events}
+ ref={desktopRef}
+ {...desktopEvent}
>
{filteredData.slice(0, 9).map((i) => (
<Link
- key={i.id}
+ key={`desktop-${i.id}`}
href={`/en/${i.type.toLowerCase()}/${i.id}`}
- className="relative snap-start shrink-0 group hover:bg-white/20 p-1 rounded"
+ className="relative flex-center snap-start shrink-0 group rounded"
>
+ <span className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded absolute bg-gradient-to-b from-black/50 from-5% to-30% to-transparent z-40" />
+ <span className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded absolute group-hover:bg-gradient-to-t from-black/90 to-transparent z-40 opacity-0 group-hover:opacity-100 transition-all duration-200 ease" />
+ <span
+ title={i.title.userPreferred}
+ className="absolute bottom-5 text-center line-clamp-2 font-karla font-semibold opacity-0 group-hover:opacity-100 w-[70%] z-50 transition-all duration-200 ease"
+ >
+ {i.title.userPreferred}
+ </span>
+ <div className="absolute top-0 right-0 z-40 font-karla font-bold">
+ {i.type === "ANIME" ? (
+ <span className="flex items-center px-2 py-1 gap-1 text-sm text-white">
+ <PlayCircleIcon className="w-5 h-5" />
+ </span>
+ ) : (
+ <span className="flex items-center px-2 py-1 gap-1 text-sm text-white">
+ <BookOpenOutline className="w-5 h-5" />
+ </span>
+ )}
+ </div>
<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"
+ className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] brightness-[90%] 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">
+ {/* <span className="absolute rounded pointer-events-none w-[240px] h-[50%] transition-all duration-150 ease-in transform 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>
+ </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" />
+ <div className="flex lg:hidden">
+ <div
+ id="recommendation-list"
+ className="flex gap-5 overflow-x-scroll scrollbar-none px-5 py-5 lg:py-10"
+ ref={mobileRef}
+ {...mobileEvent}
+ >
+ {filteredData.slice(0, 9).map((i) => (
+ <div key={`mobile-${i.id}`} className="flex flex-col gap-2">
+ <Link
+ title={i.title.userPreferred}
+ href={`/en/${i.type.toLowerCase()}/${i.id}`}
+ className="relative flex-center snap-start shrink-0 group rounded scale-100 hover:scale-105 duration-300 ease-out"
+ >
+ <span className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded absolute bg-gradient-to-b from-black/50 from-5% to-30% to-transparent z-40" />
+ <div className="absolute top-0 right-0 z-40 font-karla font-bold">
+ {i.type === "ANIME" ? (
+ <span className="flex items-center px-2 py-1 gap-1 text-sm text-white">
+ <PlayCircleIcon className="w-5 h-5" />
+ </span>
+ ) : (
+ <span className="flex items-center px-2 py-1 gap-1 text-sm text-white">
+ <BookOpenOutline className="w-5 h-5" />
+ </span>
+ )}
+ </div>
+ <Image
+ src={i.coverImage.extraLarge}
+ alt={i.title.userPreferred}
+ width={190}
+ height={256}
+ className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] shrink-0 brightness-[90%] rounded-md object-cover overflow-hidden transition-all duration-150 ease-in-out"
+ />
+ </Link>
+ <Link
+ href={
+ i.type === "MANGA"
+ ? `/en/manga/${i.id}`
+ : `/en/${i.type.toLowerCase()}/${i.id}`
+ }
+ className="w-[135px] lg:w-[185px] line-clamp-2"
+ title={i.title.romaji}
+ >
+ <h1 className="font-karla font-semibold xl:text-base text-[15px]">
+ {i.status === "RELEASING" ? (
+ <span className="dots bg-green-500" />
+ ) : i.status === "NOT_YET_RELEASED" ? (
+ <span className="dots bg-red-500" />
+ ) : null}
+ {i.title.userPreferred}
+ </h1>
+ </Link>
+ </div>
+ ))}
+ </div>
+ </div>
+ <div className="hidden lg:block 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"
+ className="hidden lg:block 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 bb35d08..19260c2 100644
--- a/components/home/schedule.js
+++ b/components/home/schedule.js
@@ -4,7 +4,7 @@ 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";
+import { useCountdown } from "../../lib/hooks/useCountdownSeconds";
export default function Schedule({ data, scheduleData, anime, update }) {
let now = new Date();
@@ -13,7 +13,7 @@ export default function Schedule({ data, scheduleData, anime, update }) {
"Schedule";
currentDay = currentDay.replace("Schedule", "");
- const [day, hours, minutes, seconds] = useCountdown(
+ const { day, hours, minutes, seconds } = useCountdown(
anime[0]?.airingSchedule.nodes[0]?.airingAt * 1000 || Date.now(),
update
);