diff options
| author | Factiven <[email protected]> | 2023-09-13 00:45:53 +0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-09-13 00:45:53 +0700 |
| commit | 7327a69b55a20b99b14ee0803d6cf5f8b88c45ef (patch) | |
| tree | cbcca777593a8cc4b0282e7d85a6fc51ba517e25 /components/home | |
| parent | Update issue templates (diff) | |
| download | moopa-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/home')
| -rw-r--r-- | components/home/content.js | 254 | ||||
| -rw-r--r-- | components/home/content/historyOptions.js | 56 | ||||
| -rw-r--r-- | components/home/genres.js | 4 | ||||
| -rw-r--r-- | components/home/mobileNav.js | 202 | ||||
| -rw-r--r-- | components/home/recommendation.js | 91 | ||||
| -rw-r--r-- | components/home/schedule.js | 28 | ||||
| -rw-r--r-- | components/home/staticNav.js | 160 |
7 files changed, 435 insertions, 360 deletions
diff --git a/components/home/content.js b/components/home/content.js index 70f0e3f..e18e5d8 100644 --- a/components/home/content.js +++ b/components/home/content.js @@ -1,5 +1,6 @@ import Link from "next/link"; -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useRef, useEffect, Fragment } from "react"; +import { useDraggable } from "react-use-draggable-scroll"; import Image from "next/image"; import { MdChevronRight } from "react-icons/md"; import { @@ -14,6 +15,7 @@ import { ChevronLeftIcon } from "@heroicons/react/20/solid"; import { ExclamationCircleIcon, PlayIcon } from "@heroicons/react/24/solid"; import { useRouter } from "next/router"; import { toast } from "react-toastify"; +import HistoryOptions from "./content/historyOptions"; export default function Content({ ids, @@ -26,11 +28,10 @@ export default function Content({ }) { const router = useRouter(); - const [startX, setStartX] = useState(null); - const containerRef = useRef(null); + const ref = useRef(); + const { events } = useDraggable(ref); const [cookie, setCookie] = useState(null); - const [isDragging, setIsDragging] = useState(false); const [clicked, setClicked] = useState(false); const [lang, setLang] = useState("en"); @@ -55,39 +56,20 @@ export default function Content({ } }, []); - const handleMouseDown = (e) => { - setIsDragging(true); - setStartX(e.pageX - containerRef.current.offsetLeft); - }; - - const handleMouseUp = () => { - setIsDragging(false); - }; - - const handleMouseMove = (e) => { - if (!isDragging) return; - e.preventDefault(); - const x = e.pageX - containerRef.current.offsetLeft; - const walk = (x - startX) * 3; - containerRef.current.scrollLeft = scrollLeft - walk; - }; - - const handleClick = (e) => { - if (isDragging) { - e.preventDefault(); - } - }; - const [scrollLeft, setScrollLeft] = useState(false); const [scrollRight, setScrollRight] = useState(true); const slideLeft = () => { + ref.current.classList.add("scroll-smooth"); var slider = document.getElementById(ids); slider.scrollLeft = slider.scrollLeft - 500; + ref.current.classList.remove("scroll-smooth"); }; const slideRight = () => { + ref.current.classList.add("scroll-smooth"); var slider = document.getElementById(ids); slider.scrollLeft = slider.scrollLeft + 500; + ref.current.classList.remove("scroll-smooth"); }; const handleScroll = (e) => { @@ -128,6 +110,9 @@ export default function Content({ if (section === "Recently Watched") { router.push(`/${lang}/anime/recently-watched`); } + if (section === "New Episodes") { + router.push(`/${lang}/anime/recent`); + } if (section === "Trending Now") { router.push(`/${lang}/anime/trending`); } @@ -142,7 +127,7 @@ export default function Content({ } }; - const removeItem = async (id) => { + const removeItem = async (id, aniId) => { if (userName) { // remove from database const res = await fetch(`/api/user/update/episode`, { @@ -152,24 +137,42 @@ export default function Content({ }, body: JSON.stringify({ name: userName, - id: id, + id, + aniId, }), }); const data = await res.json(); - // remove from local storage - const artplayerSettings = - JSON.parse(localStorage.getItem("artplayer_settings")) || {}; - if (artplayerSettings[id]) { - delete artplayerSettings[id]; - localStorage.setItem( - "artplayer_settings", - JSON.stringify(artplayerSettings) - ); + if (id) { + // remove from local storage + const artplayerSettings = + JSON.parse(localStorage.getItem("artplayer_settings")) || {}; + if (artplayerSettings[id]) { + delete artplayerSettings[id]; + localStorage.setItem( + "artplayer_settings", + JSON.stringify(artplayerSettings) + ); + } + } + if (aniId) { + const currentData = + JSON.parse(localStorage.getItem("artplayer_settings")) || {}; + + const updatedData = {}; + + for (const key in currentData) { + const item = currentData[key]; + if (item.aniId !== aniId) { + updatedData[key] = item; + } + } + + localStorage.setItem("artplayer_settings", JSON.stringify(updatedData)); } // update client - setRemoved(id); + setRemoved(id || aniId); if (data?.message === "Episode deleted") { toast.success("Episode removed from history", { @@ -182,17 +185,38 @@ export default function Content({ }); } } else { - const artplayerSettings = - JSON.parse(localStorage.getItem("artplayer_settings")) || {}; - if (artplayerSettings[id]) { - delete artplayerSettings[id]; - localStorage.setItem( - "artplayer_settings", - JSON.stringify(artplayerSettings) - ); + if (id) { + // remove from local storage + const artplayerSettings = + JSON.parse(localStorage.getItem("artplayer_settings")) || {}; + if (artplayerSettings[id]) { + delete artplayerSettings[id]; + localStorage.setItem( + "artplayer_settings", + JSON.stringify(artplayerSettings) + ); + } + setRemoved(id); + } + if (aniId) { + const currentData = + JSON.parse(localStorage.getItem("artplayer_settings")) || {}; + + // Create a new object to store the updated data + const updatedData = {}; + + // Iterate through the current data and copy items with different aniId to the updated object + for (const key in currentData) { + const item = currentData[key]; + if (item.aniId !== aniId) { + updatedData[key] = item; + } + } + + // Update localStorage with the filtered data + localStorage.setItem("artplayer_settings", JSON.stringify(updatedData)); + setRemoved(aniId); } - - setRemoved(id); } }; @@ -218,13 +242,10 @@ export default function Content({ </div> <div id={ids} - className="scroll flex h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide lg:gap-8 gap-4 lg:p-10 py-8 px-5 z-30 scroll-smooth" + className="flex h-full w-full select-none overflow-x-scroll overflow-y-hidden scrollbar-hide lg:gap-8 gap-4 lg:p-10 py-8 px-5 z-30" onScroll={handleScroll} - onMouseDown={handleMouseDown} - onMouseUp={handleMouseUp} - onMouseMove={handleMouseMove} - onClick={handleClick} - ref={containerRef} + {...events} + ref={ref} > {ids !== "recentlyWatched" ? slicedData?.map((anime) => { @@ -241,14 +262,14 @@ export default function Content({ title={anime.title.romaji} > {ids === "onGoing" && ( - <div className="h-[190px] lg:h-[265px] w-[135px] lg:w-[185px] bg-gradient-to-b from-transparent to-black absolute z-40 rounded-md whitespace-normal font-karla group"> + <div className="h-[190px] lg:h-[265px] w-[135px] lg:w-[185px] bg-gradient-to-b from-transparent to-black/90 absolute z-40 rounded-md whitespace-normal font-karla group"> <div className="flex flex-col items-center h-full justify-end text-center pb-5"> <h1 className="line-clamp-1 w-[70%] text-[10px]"> {anime.title.romaji || anime.title.english} </h1> {checkProgress(progress) && !clicked?.hasOwnProperty(anime.id) && ( - <ExclamationCircleIcon className="w-7 h-7 absolute z-40 -top-3 -right-3" /> + <ExclamationCircleIcon className="w-7 h-7 absolute z-40 text-white -top-3 -right-3" /> )} {checkProgress(progress) && ( <div @@ -275,30 +296,52 @@ export default function Content({ </div> </div> )} - <Image - draggable={false} - src={ - anime.image || - anime.coverImage?.extraLarge || - anime.coverImage?.large || - "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png" - } - alt={ - anime.title.romaji || - anime.title.english || - "coverImage" - } - width={500} - height={300} - placeholder="blur" - blurDataURL={ - anime.image || - anime.coverImage?.extraLarge || - anime.coverImage?.large || - "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png" - } - className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90" - /> + <div className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded-md z-30"> + {ids === "recentAdded" && ( + <div className="absolute bg-gradient-to-b from-black/30 to-transparent from-5% to-30% top-0 z-30 w-full h-full rounded" /> + )} + <Image + draggable={false} + src={ + anime.image || + anime.coverImage?.extraLarge || + anime.coverImage?.large || + "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png" + } + alt={ + anime.title.romaji || + anime.title.english || + "coverImage" + } + width={500} + height={300} + placeholder="blur" + blurDataURL={ + anime.image || + anime.coverImage?.extraLarge || + anime.coverImage?.large || + "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png" + } + className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90" + /> + </div> + {ids === "recentAdded" && ( + <Fragment> + <Image + src="/svg/episode-badge.svg" + alt="episode-bade" + width={200} + height={100} + className="w-24 lg:w-32 absolute top-1 -right-[12px] lg:-right-[17px] z-40" + /> + <p className="absolute z-40 text-center w-[86px] lg:w-[110px] top-1 -right-2 lg:top-[5.5px] lg:-right-2 font-karla text-sm lg:text-base"> + Episode{" "} + <span className="text-white"> + {anime?.episodeNumber} + </span> + </p> + </Fragment> + )} </Link> {ids !== "onGoing" && ( <Link @@ -307,7 +350,8 @@ export default function Content({ title={anime.title.romaji} > <h1 className="font-karla font-semibold xl:text-base text-[15px]"> - {anime.status === "RELEASING" ? ( + {anime.status === "RELEASING" || + ids === "recentAdded" ? ( <span className="dots bg-green-500" /> ) : anime.status === "NOT_YET_RELEASED" ? ( <span className="dots bg-red-500" /> @@ -333,22 +377,50 @@ export default function Content({ key={i.watchId} className="flex flex-col gap-2 shrink-0 cursor-pointer relative group/item" > - <div className="absolute z-40 top-1 right-1 group-hover/item:visible invisible hover:text-action"> - <div - className="flex flex-col items-center group/delete" + <div className="absolute flex flex-col gap-1 z-40 top-1 right-1 transition-all duration-200 ease-out opacity-0 group-hover/item:opacity-100 scale-90 group-hover/item:scale-100 group-hover/item:visible invisible "> + {/* <button + type="button" + className="flex flex-col items-center group/delete relative" onClick={() => removeItem(i.watchId)} > - <XMarkIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full" /> + <XMarkIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out" /> <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/delete:visible group-hover/delete:scale-100 group-hover/delete:translate-x-0 group-hover/delete:opacity-100 opacity-0 translate-x-10 scale-50 invisible"> Remove from history </span> - </div> + </button> */} + <HistoryOptions + remove={removeItem} + watchId={i.watchId} + aniId={i.aniId} + /> + {i?.nextId && ( + <button + type="button" + className="flex flex-col items-center group/next relative" + onClick={() => { + router.push( + `/en/anime/watch/${i.aniId}/${ + i.provider + }?id=${encodeURIComponent(i?.nextId)}&num=${ + i?.nextNumber + }${i?.dub ? `&dub=${i?.dub}` : ""}` + ); + }} + > + <ChevronRightIcon className="w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out" /> + <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/next:visible group-hover/next:scale-100 group-hover/next:translate-x-0 group-hover/next:opacity-100 opacity-0 translate-x-10 scale-50 invisible"> + Play Next Episode + </span> + </button> + )} </div> <Link className="relative w-[320px] aspect-video rounded-md overflow-hidden group" href={`/en/anime/watch/${i.aniId}/${ i.provider - }?id=${encodeURIComponent(i.watchId)}&num=${i.episode}`} + }?id=${encodeURIComponent(i.watchId)}&num=${i.episode}${ + i?.dub ? `&dub=${i?.dub}` : "" + }`} > <div className="w-full h-full bg-gradient-to-t from-black/70 from-20% to-transparent group-hover:to-black/40 transition-all duration-300 ease-out absolute z-30" /> <div className="absolute bottom-3 left-0 mx-2 text-white flex gap-2 items-center w-[80%] z-30"> @@ -372,8 +444,8 @@ export default function Content({ {i?.image && ( <Image src={i?.image} - width={200} - height={200} + width="0" + height="0" alt="Episode Thumbnail" className="w-fit group-hover:scale-[1.02] duration-300 ease-out z-10" /> @@ -411,7 +483,7 @@ export default function Content({ section !== "Recommendations" && ( <div key={section} - className="flex cursor-pointer" + className="flex flex-col cursor-pointer" onClick={goToPage} > <div className="w-[320px] aspect-video overflow-hidden object-cover rounded-md border-secondary border-2 flex flex-col gap-2 items-center text-center justify-center text-[#6a6a6a] hover:text-[#9f9f9f] hover:border-[#757575] transition-colors duration-200"> diff --git a/components/home/content/historyOptions.js b/components/home/content/historyOptions.js new file mode 100644 index 0000000..1b9c5ed --- /dev/null +++ b/components/home/content/historyOptions.js @@ -0,0 +1,56 @@ +import { Menu, Transition } from "@headlessui/react"; +import { XMarkIcon } from "@heroicons/react/24/outline"; +import React, { Fragment } from "react"; + +export default function HistoryOptions({ remove, watchId, aniId }) { + return ( + <Menu as="div" className="relative inline-block text-left"> + <div> + <Menu.Button className="group/delete w-6 h-6 shrink-0 bg-primary p-1 rounded-full hover:text-action scale-100 hover:scale-105 transition-all duration-200 ease-out"> + <XMarkIcon /> + <span className="absolute font-karla bg-secondary shadow-black shadow-2xl py-1 px-2 whitespace-nowrap text-white text-sm rounded-md right-7 -bottom-[2px] z-40 duration-300 transition-all ease-out group-hover/delete:visible group-hover/delete:scale-100 group-hover/delete:translate-x-0 group-hover/delete:opacity-100 opacity-0 translate-x-10 scale-50 invisible"> + Remove from history + </span> + </Menu.Button> + </div> + <Transition + as={Fragment} + enter="transition ease-out duration-100" + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leave="transition ease-in duration-75" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + > + <Menu.Items className="absolute z-50 right-0 mt-1 w-56 origin-top-right rounded-md bg-secondary shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"> + <div className="px-1 py-1 "> + <Menu.Item> + {({ active }) => ( + <button + className={`${ + active ? "bg-white/10 text-white" : "text-gray-100" + } group flex w-full items-center rounded-md px-2 py-2 text-sm`} + onClick={() => remove(null, aniId)} + > + Delete All Episodes + </button> + )} + </Menu.Item> + <Menu.Item> + {({ active }) => ( + <button + className={`${ + active ? "bg-white/10 text-white" : "text-gray-100" + } group flex w-full items-center rounded-md px-2 py-2 text-sm`} + onClick={() => remove(watchId, null)} + > + Delete Just This Episode + </button> + )} + </Menu.Item> + </div> + </Menu.Items> + </Transition> + </Menu> + ); +} diff --git a/components/home/genres.js b/components/home/genres.js index 3eefecd..f054fc9 100644 --- a/components/home/genres.js +++ b/components/home/genres.js @@ -55,7 +55,7 @@ export default function Genres() { <ChevronRightIcon className="w-5 h-5" /> </div> <div className="flex xl:justify-center items-center relative"> - <div className="bg-gradient-to-r from-primary to-transparent z-40 absolute w-7 h-[200px] left-0" /> + <div className="bg-gradient-to-r from-primary to-transparent z-40 absolute w-7 h-full left-0" /> <div className="flex lg:gap-8 gap-3 lg:p-10 py-8 px-5 z-30 overflow-y-hidden overflow-x-scroll snap-x snap-proximity scrollbar-none relative"> <div className="flex lg:gap-10 gap-4"> {g.map((a, index) => ( @@ -80,7 +80,7 @@ export default function Genres() { ))} </div> </div> - <div className="bg-gradient-to-l from-primary to-transparent z-40 absolute w-7 h-[200px] lg:h-[300px] right-0" /> + <div className="bg-gradient-to-l from-primary to-transparent z-40 absolute w-7 h-full right-0" /> </div> </div> ); diff --git a/components/home/mobileNav.js b/components/home/mobileNav.js deleted file mode 100644 index 52c9d52..0000000 --- a/components/home/mobileNav.js +++ /dev/null @@ -1,202 +0,0 @@ -import { signIn, signOut } from "next-auth/react"; -import Link from "next/link"; -import { useState } from "react"; - -export default function MobileNav({ sessions }) { - const [isVisible, setIsVisible] = useState(false); - - const handleShowClick = () => { - setIsVisible(true); - }; - - const handleHideClick = () => { - setIsVisible(false); - }; - return ( - <> - {/* NAVBAR */} - <div className="z-50"> - {!isVisible && ( - <button - onClick={handleShowClick} - className="fixed bottom-[30px] right-[20px] z-[100] flex h-[51px] w-[50px] cursor-pointer items-center justify-center rounded-[8px] bg-[#17171f] shadow-lg lg:hidden" - id="bars" - > - <svg - xmlns="http://www.w3.org/2000/svg" - className="h-[42px] w-[61.5px] text-[#8BA0B2] fill-orange-500" - viewBox="0 0 20 20" - fill="currentColor" - > - <path - fillRule="evenodd" - d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" - clipRule="evenodd" - /> - </svg> - </button> - )} - </div> - - {/* Mobile Menu */} - <div className={`transition-all duration-150 subpixel-antialiased z-50`}> - {isVisible && sessions && ( - <Link - href={`/en/profile/${sessions?.user.name}`} - className="fixed lg:hidden bottom-[100px] w-[60px] h-[60px] flex items-center justify-center right-[20px] rounded-full z-50 bg-[#17171f]" - > - <img - src={sessions?.user.image.large} - alt="user avatar" - className="object-cover w-[60px] h-[60px] rounded-full" - /> - </Link> - )} - {isVisible && ( - <div className="fixed bottom-[30px] right-[20px] z-50 flex h-[51px] w-[300px] items-center justify-center gap-8 rounded-[8px] text-[11px] bg-[#17171f] shadow-lg lg:hidden"> - <div className="grid grid-cols-4 place-items-center gap-6"> - <button className="group flex flex-col items-center"> - <Link href="/en/"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" - /> - </svg> - </Link> - <Link - href="/en/" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - home - </Link> - </button> - <button className="group flex flex-col items-center"> - <Link href="/en/about"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" - /> - </svg> - </Link> - <Link - href="/en/about" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - about - </Link> - </button> - <button className="group flex gap-[1.5px] flex-col items-center "> - <div> - <Link href="/en/search/anime"> - <svg - xmlns="http://www.w3.org/2000/svg" - fill="none" - viewBox="0 0 24 24" - strokeWidth={1.5} - stroke="currentColor" - className="w-6 h-6 group-hover:stroke-action" - > - <path - strokeLinecap="round" - strokeLinejoin="round" - d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" - /> - </svg> - </Link> - </div> - <Link - href="/en/search/anime" - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - search - </Link> - </button> - {sessions ? ( - <button - onClick={() => signOut("AniListProvider")} - className="group flex gap-[1.5px] flex-col items-center " - > - <div> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 96 960 960" - className="group-hover:fill-action w-6 h-6 fill-txt" - > - <path d="M186.666 936q-27 0-46.833-19.833T120 869.334V282.666q0-27 19.833-46.833T186.666 216H474v66.666H186.666v586.668H474V936H186.666zm470.668-176.667l-47-48 102-102H370v-66.666h341.001l-102-102 46.999-48 184 184-182.666 182.666z"></path> - </svg> - </div> - <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"> - logout - </h1> - </button> - ) : ( - <button - onClick={() => signIn("AniListProvider")} - className="group flex gap-[1.5px] flex-col items-center " - > - <div> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 96 960 960" - className="group-hover:fill-action w-6 h-6 fill-txt mr-2" - > - <path d="M486 936v-66.666h287.334V282.666H486V216h287.334q27 0 46.833 19.833T840 282.666v586.668q0 27-19.833 46.833T773.334 936H486zm-78.666-176.667l-47-48 102-102H120v-66.666h341l-102-102 47-48 184 184-182.666 182.666z"></path> - </svg> - </div> - <h1 className="font-karla font-bold text-[#8BA0B2] group-hover:text-action"> - login - </h1> - </button> - )} - </div> - <button onClick={handleHideClick}> - <svg - width="20" - height="21" - className="fill-orange-500" - viewBox="0 0 20 21" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <rect - x="2.44043" - y="0.941467" - width="23.5842" - height="3.45134" - rx="1.72567" - transform="rotate(45 2.44043 0.941467)" - /> - <rect - x="19.1172" - y="3.38196" - width="23.5842" - height="3.45134" - rx="1.72567" - transform="rotate(135 19.1172 3.38196)" - /> - </svg> - </button> - </div> - )} - </div> - </> - ); -} diff --git a/components/home/recommendation.js b/components/home/recommendation.js new file mode 100644 index 0000000..842932c --- /dev/null +++ b/components/home/recommendation.js @@ -0,0 +1,91 @@ +import Image from "next/image"; +// import data from "../../assets/dummyData.json"; +import { BookOpenIcon, PlayIcon } from "@heroicons/react/24/solid"; +import { useDraggable } from "react-use-draggable-scroll"; +import { useRef } from "react"; +import Link from "next/link"; + +export default function UserRecommendation({ data }) { + const ref = useRef(null); + const { events } = useDraggable(ref); + + const uniqueRecommendationIds = new Set(); + + // Filter out duplicates from the recommendations array + const filteredData = data.filter((recommendation) => { + // Check if the ID is already in the set + if (uniqueRecommendationIds.has(recommendation.id)) { + // If it's a duplicate, return false to exclude it from the filtered array + return false; + } + + // If it's not a duplicate, add the ID to the set and return true + uniqueRecommendationIds.add(recommendation.id); + return true; + }); + + return ( + <div className="flex flex-col bg-tersier relative rounded overflow-hidden"> + <div className="flex lg:gap-5 z-50"> + <div className="flex flex-col items-start justify-center gap-3 lg:gap-7 lg:w-[50%] pl-5 lg:px-10"> + <h2 className="font-bold text-3xl text-white"> + {data[0].title.userPreferred} + </h2> + <p + dangerouslySetInnerHTML={{ + __html: data[0].description?.replace(/<[^>]*>/g, ""), + }} + className="font-roboto font-light line-clamp-3 lg:line-clamp-3" + /> + <button + type="button" + className="border border-white/70 py-1 px-2 lg:py-2 lg:px-4 rounded-full flex items-center gap-2 text-white font-bold" + > + {data[0].type === "ANIME" ? ( + <PlayIcon className="w-5 h-5 text-white" /> + ) : ( + <BookOpenIcon className="w-5 h-5 text-white" /> + )} + {data[0].type === "ANIME" ? "Watch" : "Read"} Now + </button> + </div> + <div + id="recommendation-list" + className="flex gap-5 overflow-x-scroll scrollbar-none px-5 py-7 lg:py-10" + ref={ref} + {...events} + > + {filteredData.slice(0, 9).map((i) => ( + <Link + key={i.id} + href={`/en/${i.type.toLowerCase()}/${i.id}`} + className="relative snap-start shrink-0 group hover:bg-white/20 p-1 rounded" + > + <Image + src={i.coverImage.extraLarge} + alt={i.title.userPreferred} + width={190} + height={256} + className="h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] rounded-md object-cover overflow-hidden transition-all duration-150 ease-in-out" + /> + <span className="absolute rounded pointer-events-none w-[240px] h-[50%] transition-all duration-150 ease-in transform translate-x-[75%] group-hover:translate-x-[80%] top-0 left-0 bg-secondary opacity-0 group-hover:opacity-100 flex flex-col z-50"> + <div className="">{i.title.userPreferred}</div> + <div>a</div> + </span> + </Link> + ))} + </div> + </div> + <div className="absolute top-0 left-0 z-40 bg-gradient-to-r from-transparent from-30% to-80% to-tersier w-[80%] lg:w-[60%] h-full" /> + {data[0]?.bannerImage && ( + <Image + src={data[0]?.bannerImage} + alt={data[0].title.userPreferred} + width={500} + height={500} + className="absolute top-0 left-0 z-30 w-[60%] h-full object-cover opacity-30" + /> + )} + </div> + ); +} diff --git a/components/home/schedule.js b/components/home/schedule.js index 4043c5e..a9846a7 100644 --- a/components/home/schedule.js +++ b/components/home/schedule.js @@ -1,17 +1,23 @@ import Image from "next/image"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { convertUnixToTime } from "../../utils/getTimes"; import { PlayIcon } from "@heroicons/react/20/solid"; import { BackwardIcon, ForwardIcon } from "@heroicons/react/24/solid"; import Link from "next/link"; +import { useCountdown } from "../../utils/useCountdownSeconds"; -export default function Schedule({ data, scheduleData, time }) { +export default function Schedule({ data, scheduleData, anime, update }) { let now = new Date(); let currentDay = now.toLocaleString("default", { weekday: "long" }).toLowerCase() + "Schedule"; currentDay = currentDay.replace("Schedule", ""); + const [day, hours, minutes, seconds] = useCountdown( + anime[0]?.airingSchedule.nodes[0]?.airingAt * 1000 || Date.now(), + update + ); + const [currentPage, setCurrentPage] = useState(0); const [days, setDay] = useState(); @@ -37,8 +43,6 @@ export default function Schedule({ data, scheduleData, time }) { setCurrentPage(todayIndex >= 0 ? todayIndex : 0); }, [currentDay, days]); - // console.log({ scheduleData }); - return ( <div className="flex flex-col gap-5 px-4 lg:px-0"> <h1 className="font-bold font-karla text-[20px] lg:px-5"> @@ -46,7 +50,7 @@ export default function Schedule({ data, scheduleData, time }) { </h1> <div className="rounded mb-5 shadow-md shadow-black"> <div className="overflow-hidden w-full h-[96px] lg:h-[10rem] rounded relative"> - <div className="absolute flex flex-col justify-center pl-5 lg:pl-16 rounded z-20 bg-gradient-to-r from-30% from-[#0c0c0c] to-transparent w-full h-full"> + <div className="absolute flex flex-col justify-center pl-5 lg:pl-16 rounded z-20 bg-gradient-to-r from-30% from-tersier to-transparent w-full h-full"> <h1 className="text-xs lg:text-lg">Coming Up Next!</h1> <div className="w-1/2 lg:w-2/5 hidden lg:block font-medium font-karla leading-[2.9rem] text-white line-clamp-1"> <Link @@ -62,15 +66,15 @@ export default function Schedule({ data, scheduleData, time }) { </div> {data.bannerImage ? ( <Image - src={data.bannerImage || data.coverImage.large} + src={data.bannerImage || data.coverImage.extraLarge} width={500} height={500} alt="banner next anime" - className="absolute z-10 top-0 right-0 w-3/4 h-full object-cover brightness-[30%]" + className="absolute z-10 top-0 right-0 w-3/4 h-full object-cover opacity-30" /> ) : ( <Image - src={data.coverImage.large} + src={data.coverImage.extraLarge} width={500} height={500} sizes="100vw" @@ -87,22 +91,22 @@ export default function Schedule({ data, scheduleData, time }) { <div className="flex items-center gap-2 md:gap-5 font-bold font-karla text-sm md:text-xl"> {/* Countdown Timer */} <div className="flex flex-col items-center"> - <span className="text-action/80">{time.days}</span> + <span className="text-action/80">{day}</span> <span className="text-sm lg:text-base font-medium">Days</span> </div> <span></span> <div className="flex flex-col items-center"> - <span className="text-action/80">{time.hours}</span> + <span className="text-action/80">{hours}</span> <span className="text-sm lg:text-base font-medium">Hours</span> </div> <span></span> <div className="flex flex-col items-center"> - <span className="text-action/80">{time.minutes}</span> + <span className="text-action/80">{minutes}</span> <span className="text-sm lg:text-base font-medium">Mins</span> </div> <span></span> <div className="flex flex-col items-center"> - <span className="text-action/80">{time.seconds}</span> + <span className="text-action/80">{seconds}</span> <span className="text-sm lg:text-base font-medium">Secs</span> </div> </div> diff --git a/components/home/staticNav.js b/components/home/staticNav.js index 93f7b26..b22a9e3 100644 --- a/components/home/staticNav.js +++ b/components/home/staticNav.js @@ -1,51 +1,27 @@ -import { signIn, useSession } from "next-auth/react"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { signIn, signOut, useSession } from "next-auth/react"; import { getCurrentSeason } from "../../utils/getTimes"; import Link from "next/link"; -import { parseCookies } from "nookies"; +// import { } from "@heroicons/react/24/solid"; +import { useSearch } from "../../lib/hooks/isOpenState"; +import Image from "next/image"; +import { UserIcon } from "@heroicons/react/20/solid"; +import { useRouter } from "next/router"; export default function Navigasi() { const { data: sessions, status } = useSession(); - const [year, setYear] = useState(new Date().getFullYear()); - const [season, setSeason] = useState(getCurrentSeason()); - - const [lang, setLang] = useState("en"); - const [cookie, setCookies] = useState(null); + const year = new Date().getFullYear(); + const season = getCurrentSeason(); const router = useRouter(); - useEffect(() => { - let lang = null; - if (!cookie) { - const cookie = parseCookies(); - lang = cookie.lang || null; - setCookies(cookie); - } - if (lang === "en" || lang === null) { - setLang("en"); - } else if (lang === "id") { - setLang("id"); - } - }, []); + const { setIsOpen } = useSearch(); - const handleFormSubmission = (inputValue) => { - router.push(`/${lang}/search/${encodeURIComponent(inputValue)}`); - }; - - const handleKeyDown = async (event) => { - if (event.key === "Enter") { - event.preventDefault(); - const inputValue = event.target.value; - handleFormSubmission(inputValue); - } - }; return ( <> {/* NAVBAR PC */} <div className="flex items-center justify-center"> - <div className="flex w-full items-center justify-between px-5 lg:mx-[94px]"> - <div className="flex items-center lg:gap-16 lg:pt-7"> + <div className="flex w-full items-center justify-between px-5 lg:mx-[94px] lg:pt-7"> + <div className="flex items-center lg:gap-16"> <Link href="/en/" className=" font-outfit lg:text-[40px] text-[30px] font-bold text-[#FF7F57]" @@ -55,16 +31,35 @@ export default function Navigasi() { <ul className="hidden items-center gap-10 pt-2 font-outfit text-[14px] lg:flex"> <li> <Link - href={`/en/search/anime?season=${season}&seasonYear=${year}`} + href={`/en/search/anime?season=${season}&year=${year}`} + className="hover:text-action/80 transition-all duration-150 ease-linear" > This Season </Link> </li> <li> - <Link href="/en/search/manga">Manga</Link> + <Link + href="/en/search/manga" + className="hover:text-action/80 transition-all duration-150 ease-linear" + > + Manga + </Link> </li> <li> - <Link href="/en/search/anime">Anime</Link> + <Link + href="/en/search/anime" + className="hover:text-action/80 transition-all duration-150 ease-linear" + > + Anime + </Link> + </li> + <li> + <Link + href="/en/schedule" + className="hover:text-action/80 transition-all duration-150 ease-linear" + > + Schedule + </Link> </li> {status === "loading" ? ( @@ -75,15 +70,19 @@ export default function Navigasi() { <li> <button onClick={() => signIn("AniListProvider")} - className="ring-1 ring-action font-karla font-bold px-2 py-1 rounded-md" + className="hover:text-action/80 transition-all duration-150 ease-linear" + // className="px-2 py-1 ring-1 ring-action font-bold font-karla rounded-md" > - Sign in + Sign In </button> </li> )} {sessions && ( <li className="text-center"> - <Link href={`/en/profile/${sessions?.user.name}`}> + <Link + href={`/en/profile/${sessions?.user.name}`} + className="hover:text-action/80 transition-all duration-150 ease-linear" + > My List </Link> </li> @@ -92,18 +91,73 @@ export default function Navigasi() { )} </ul> </div> - <div className="relative flex lg:scale-75 scale-[65%] items-center mb-7 lg:mb-1"> - <div className="search-box "> - <input - className="search-text" - type="text" - placeholder="Search Anime" - onKeyDown={handleKeyDown} - /> - <div className="search-btn"> - <i className="fas fa-search"></i> - </div> - </div> + <div className="flex items-center gap-4"> + <button + type="button" + onClick={() => setIsOpen(true)} + className="flex-center w-[26px] h-[26px]" + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + viewBox="0 0 24 24" + > + <path + fill="none" + stroke="currentColor" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="2" + d="M15 15l6 6m-11-4a7 7 0 110-14 7 7 0 010 14z" + ></path> + </svg> + </button> + {/* <div + className="bg-white" + // title={sessions ? "Go to Profile" : "Login With AniList"} + > */} + {sessions ? ( + <button + type="button" + onClick={() => + router.push(`/en/profile/${sessions?.user.name}`) + } + className="w-7 h-7 relative flex flex-col items-center group" + > + <Image + src={sessions?.user.image.large} + alt="avatar" + width={50} + height={50} + className="w-full h-full object-cover" + /> + <div className="hidden absolute z-50 w-28 text-center -bottom-20 text-white shadow-2xl opacity-0 bg-secondary p-1 py-2 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all md:grid place-items-center gap-1"> + <Link + href={`/en/profile/${sessions?.user.name}`} + className="hover:text-action" + > + Profile + </Link> + <div + onClick={() => signOut({ callbackUrl: "/" })} + className="hover:text-action cursor-pointer" + > + Log out + </div> + </div> + </button> + ) : ( + <button + type="button" + onClick={() => signIn("AniListProvider")} + title="Login With AniList" + className="w-7 h-7 bg-white/30 rounded-full overflow-hidden" + > + <UserIcon className="w-full h-full translate-y-2 text-white/50" /> + </button> + )} + {/* </div> */} </div> </div> </div> |