diff options
| author | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
| commit | 50a0f0240d7fef133eb5acc1bea2b1168b08e9db (patch) | |
| tree | 307e09e505580415a58d64b5fc3580e9235869f1 /components/home | |
| parent | Update README.md (#104) (diff) | |
| download | moopa-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.js | 125 | ||||
| -rw-r--r-- | components/home/schedule.js | 4 |
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 ); |