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 /pages/en/search | |
| 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 'pages/en/search')
| -rw-r--r-- | pages/en/search/[...param].js | 433 | ||||
| -rw-r--r-- | pages/en/search/[param].js | 496 |
2 files changed, 433 insertions, 496 deletions
diff --git a/pages/en/search/[...param].js b/pages/en/search/[...param].js new file mode 100644 index 0000000..2ec7681 --- /dev/null +++ b/pages/en/search/[...param].js @@ -0,0 +1,433 @@ +import { useEffect, useRef, useState } from "react"; +import { AnimatePresence, motion as m } from "framer-motion"; +import Skeleton from "react-loading-skeleton"; +import { useRouter } from "next/router"; +import Link from "next/link"; +import Navbar from "../../../components/navbar"; +import Head from "next/head"; +import Footer from "../../../components/footer"; + +import Image from "next/image"; +import { aniAdvanceSearch } from "../../../lib/anilist/aniAdvanceSearch"; +import MultiSelector from "../../../components/search/dropdown/multiSelector"; +import SingleSelector from "../../../components/search/dropdown/singleSelector"; +import { + animeFormatOptions, + formatOptions, + genreOptions, + mangaFormatOptions, + mediaType, + seasonOptions, + tagsOption, + yearOptions, +} from "../../../components/search/selection"; +import InputSelect from "../../../components/search/dropdown/inputSelect"; +import { Cog6ToothIcon, TrashIcon } from "@heroicons/react/20/solid"; +import useDebounce from "../../../lib/hooks/useDebounce"; +// import { NewNavbar } from "../../../components/anime/mobile/topSection"; +// import { useSession } from "next-auth/react"; + +export async function getServerSideProps(context) { + const { param } = context.query; + + const { search, format, genres, season, year } = context.query; + + let getFormat; + let getSeason; + let getYear; + let getGenres = []; + + if (genres) { + const gr = genreOptions.find( + (i) => i.value.toLowerCase() === genres.toLowerCase() + ); + getGenres.push(gr); + } + + if (season) { + getSeason = seasonOptions.find( + (i) => i.value.toLowerCase() === season.toLowerCase() + ); + if (!year) { + const now = new Date().getFullYear(); + getYear = yearOptions.find((i) => i.value === now.toString()); + } else { + getYear = yearOptions.find((i) => i.value === year); + } + } + + if (format) { + getFormat = formatOptions.find( + (i) => i.value.toLowerCase() === format.toLowerCase() + ); + } + + if (!param && param.length !== 1) { + return { + notFound: true, + }; + } + + const typeIndex = param[0] === "anime" ? 0 : 1; + + return { + props: { + index: typeIndex, + query: search || null, + formats: getFormat || null, + seasons: getSeason || null, + years: getYear || null, + genres: getGenres || null, + }, + }; +} + +export default function Card({ + index, + query, + genres, + formats, + seasons, + years, +}) { + const inputRef = useRef(null); + const router = useRouter(); + // const { data: session } = useSession(); + + const [data, setData] = useState(); + const [loading, setLoading] = useState(true); + + const [search, setQuery] = useState(query); + const debounceSearch = useDebounce(search, 500); + + const [type, setSelectedType] = useState(mediaType[index]); + const [year, setYear] = useState(years); + const [season, setSeason] = useState(seasons); + const [sort, setSelectedSort] = useState(); + const [genre, setGenre] = useState(genres); + const [format, setFormat] = useState(formats); + + const [isVisible, setIsVisible] = useState(false); + + const [page, setPage] = useState(1); + const [nextPage, setNextPage] = useState(true); + + async function advance() { + setLoading(true); + const data = await aniAdvanceSearch({ + search: debounceSearch, + type: type?.value, + genres: genre, + page: page, + sort: sort?.value, + format: format?.value, + season: season?.value, + seasonYear: year?.value, + }); + if (data?.media?.length === 0) { + setNextPage(false); + } else if (data !== null && page > 1) { + setData((prevData) => { + return [...(prevData ?? []), ...data?.media]; + }); + setNextPage(data?.pageInfo.hasNextPage); + } else { + setData(data?.media); + } + setNextPage(data?.pageInfo.hasNextPage); + setLoading(false); + } + + useEffect(() => { + setData(null); + setPage(1); + setNextPage(true); + advance(); + }, [ + debounceSearch, + type?.value, + sort?.value, + genre, + format?.value, + season?.value, + year?.value, + ]); + + useEffect(() => { + advance(); + }, [page]); + + useEffect(() => { + function handleScroll() { + if (page > 10 || !nextPage) { + window.removeEventListener("scroll", handleScroll); + return; + } + + if ( + window.innerHeight + window.pageYOffset >= + document.body.offsetHeight - 3 + ) { + setPage((prevPage) => prevPage + 1); + } + } + + window.addEventListener("scroll", handleScroll); + + return () => window.removeEventListener("scroll", handleScroll); + }, [page, nextPage]); + + const handleKeyDown = async (event) => { + if (event.key === "Enter") { + event.preventDefault(); + const inputValue = event.target.value; + if (inputValue === "") { + setQuery(null); + } else { + setQuery(inputValue); + } + } + }; + + function trash() { + setQuery(); + setGenre(); + setFormat(); + setSelectedSort(); + setSeason(); + setYear(); + router.push(`/en/search/${mediaType[index]?.value?.toLowerCase()}`); + } + + function handleVisible() { + setIsVisible(!isVisible); + } + + return ( + <> + <Head> + <title>Moopa - search</title> + <meta name="title" content="Search" /> + <meta name="description" content="Search your favourites Anime/Manga" /> + <link rel="icon" href="/svg/c.svg" /> + </Head> + <Navbar /> + {/* <NewNavbar session={session} /> */} + <main className="w-screen min-h-screen z-40"> + <div className="max-w-screen-xl flex flex-col gap-3 mx-auto"> + <div className="w-full flex justify-between items-end gap-2 my-3 lg:gap-10 px-5 xl:px-0 relative"> + <div className="hidden lg:flex items-end w-full gap-5 z-50"> + <InputSelect + inputRef={inputRef} + data={mediaType} + label="Search" + keyDown={handleKeyDown} + query={search} + setQuery={setQuery} + selected={type} + setSelected={setSelectedType} + /> + {/* GENRES */} + <MultiSelector + data={genreOptions} + other={tagsOption} + selected={genre} + setSelected={setGenre} + label="Genres" + inputRef={inputRef} + /> + {/* SORT */} + {/* <SingleSelector + data={sortOptions} + selected={sort} + setSelected={setSelectedSort} + label="Sort" + /> */} + {/* FORMAT */} + <SingleSelector + data={index === 0 ? animeFormatOptions : mangaFormatOptions} + selected={format} + setSelected={setFormat} + label="Format" + /> + {/* SEASON */} + <SingleSelector + data={seasonOptions} + selected={season} + setSelected={setSeason} + label="Season" + /> + {/* YEAR */} + <SingleSelector + data={yearOptions} + selected={year} + setSelected={setYear} + label="Year" + /> + </div> + <div className="w-full lg:hidden"> + <InputSelect + inputRef={inputRef} + data={mediaType} + label="Search" + keyDown={handleKeyDown} + query={search} + setQuery={setQuery} + selected={type} + setSelected={setSelectedType} + /> + </div> + + <div className="flex gap-2"> + <div + className="lg:hidden py-2 px-2 bg-secondary rounded flex justify-center items-center cursor-pointer hover:bg-opacity-75 transition-all duration-100 group" + onClick={handleVisible} + > + <Cog6ToothIcon className="w-5 h-5" /> + </div> + <div + className="py-2 px-2 bg-secondary rounded flex justify-center items-center cursor-pointer hover:bg-opacity-75 transition-all duration-100 group" + onClick={trash} + > + <TrashIcon className="w-5 h-5" /> + </div> + </div> + </div> + {isVisible && ( + <div className="lg:hidden w-full flex justify-center z-40"> + <div className="grid grid-cols-2 grid-rows-2 place-items-center w-full px-5 z-30 gap-4"> + {/* GENRES */} + <MultiSelector + data={genreOptions} + other={tagsOption} + selected={genre} + setSelected={setGenre} + label="Genres" + inputRef={inputRef} + /> + {/* SORT */} + {/* <SingleSelector + data={sortOptions} + selected={sort} + setSelected={setSelectedSort} + label="Sort" + /> */} + {/* FORMAT */} + <SingleSelector + data={index === 0 ? animeFormatOptions : mangaFormatOptions} + selected={format} + setSelected={setFormat} + label="Format" + /> + {/* SEASON */} + <SingleSelector + data={seasonOptions} + selected={season} + setSelected={setSeason} + label="Season" + /> + {/* YEAR */} + <SingleSelector + data={yearOptions} + selected={year} + setSelected={setYear} + label="Year" + /> + </div> + </div> + )} + {/* <div> */} + <div className="flex flex-col gap-14 items-center z-30"> + <AnimatePresence> + <div + key="card-keys" + className="grid pt-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6 justify-items-center grid-cols-2 xxs:grid-cols-3 w-screen px-2 xl:w-auto xl:gap-10 gap-2 xl:gap-y-24 gap-y-12 overflow-hidden" + > + {loading + ? "" + : !data?.length && ( + <div className="w-screen text-[#ff7f57] xl:col-start-3 col-start-2 items-center flex justify-center text-center font-bold font-karla xl:text-2xl"> + Oops!<br></br> Nothing's Found... + </div> + )} + {data && + data?.map((anime, index) => { + return ( + <m.div + initial={{ scale: 0.9 }} + animate={{ scale: 1, transition: { duration: 0.35 } }} + className="w-[146px] xxs:w-[115px] xs:w-[135px] xl:w-[185px]" + key={index} + > + <Link + href={ + anime.format === "MANGA" || anime.format === "NOVEL" + ? `/en/manga/${anime.id}` + : `/en/anime/${anime.id}` + } + title={anime.title.userPreferred} + > + <Image + className="object-cover bg-[#3B3C41] w-[146px] h-[208px] xxs:w-[115px] xxs:h-[163px] xs:w-[135px] xs:h-[192px] xl:w-[185px] xl:h-[265px] hover:scale-105 scale-100 transition-all cursor-pointer duration-200 ease-out rounded-[10px]" + src={anime.coverImage.extraLarge} + alt={anime.title.userPreferred} + width={500} + height={500} + /> + </Link> + <Link + href={`/en/anime/${anime.id}`} + title={anime.title.userPreferred} + > + <h1 className="font-outfit font-bold xl:text-base text-[15px] pt-4 line-clamp-2"> + {anime.status === "RELEASING" ? ( + <span className="dots bg-green-500" /> + ) : anime.status === "NOT_YET_RELEASED" ? ( + <span className="dots bg-red-500" /> + ) : null} + {anime.title.userPreferred} + </h1> + </Link> + <h2 className="font-outfit xl:text-[15px] text-[11px] font-light pt-2 text-[#8B8B8B]"> + {anime.format || <p>-</p>} ·{" "} + {anime.status || <p>-</p>} ·{" "} + {anime.episodes + ? `${anime.episodes || "N/A"} Episodes` + : `${anime.chapters || "N/A"} Chapters`} + </h2> + </m.div> + ); + })} + + {loading && ( + <> + {[1, 2, 4, 5, 6, 7, 8].map((item) => ( + <div + key={item} + className="flex flex-col w-[135px] xl:w-[185px] gap-5" + style={{ scale: 0.98 }} + > + <Skeleton className="h-[192px] w-[135px] xl:h-[265px] xl:w-[185px]" /> + <Skeleton width={110} height={30} /> + </div> + ))} + </> + )} + </div> + {!loading && page > 10 && nextPage && ( + <button + onClick={() => setPage((p) => p + 1)} + className="bg-secondary xl:w-[30%] w-[80%] h-10 rounded-md" + > + Load More + </button> + )} + </AnimatePresence> + </div> + {/* </div> */} + </div> + </main> + <Footer /> + </> + ); +} diff --git a/pages/en/search/[param].js b/pages/en/search/[param].js deleted file mode 100644 index abd4f04..0000000 --- a/pages/en/search/[param].js +++ /dev/null @@ -1,496 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import { AnimatePresence, motion as m } from "framer-motion"; -import Skeleton from "react-loading-skeleton"; -import { useRouter } from "next/router"; -import Link from "next/link"; -import Navbar from "../../../components/navbar"; -import Head from "next/head"; -import Footer from "../../../components/footer"; - -import Image from "next/image"; -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { aniAdvanceSearch } from "../../../lib/anilist/aniAdvanceSearch"; - -const genre = [ - "Action", - "Adventure", - "Comedy", - "Drama", - "Ecchi", - "Fantasy", - "Horror", - "Mahou Shoujo", - "Mecha", - "Music", - "Mystery", - "Psychological", - "Romance", - "Sci-Fi", - "Slice of Life", - "Sports", - "Supernatural", - "Thriller", -]; - -const types = ["ANIME", "MANGA"]; - -const sorts = [ - { name: "Title", value: "TITLE_ROMAJI" }, - { name: "Popularity", value: "POPULARITY_DESC" }, - { name: "Trending", value: "TRENDING_DESC" }, - { name: "Favourites", value: "FAVOURITES_DESC" }, - { name: "Average Score", value: "SCORE_DESC" }, - { name: "Date Added", value: "ID_DESC" }, - { name: "Release Date", value: "START_DATE_DESC" }, -]; - -export default function Card() { - const router = useRouter(); - - const [data, setData] = useState(); - const [loading, setLoading] = useState(true); - - let hasil = null; - let tipe = "ANIME"; - let s = undefined; - let y = NaN; - let gr = undefined; - - const query = router.query; - gr = query.genres; - - if (query.param !== "anime" && query.param !== "manga") { - hasil = query.param; - } else if (query.param === "anime") { - hasil = null; - tipe = "ANIME"; - if ( - query.season !== "WINTER" && - query.season !== "SPRING" && - query.season !== "SUMMER" && - query.season !== "FALL" - ) { - s = undefined; - y = NaN; - } else { - s = query.season; - y = parseInt(query.seasonYear); - } - } else if (query.param === "manga") { - hasil = null; - tipe = "MANGA"; - if ( - query.season !== "WINTER" && - query.season !== "SPRING" && - query.season !== "SUMMER" && - query.season !== "FALL" - ) { - s = undefined; - y = NaN; - } else { - s = query.season; - y = parseInt(query.seasonYear); - } - } - - // console.log(tags); - - const [search, setQuery] = useState(hasil); - const [type, setSelectedType] = useState(tipe); - // const [genres, setSelectedGenre] = useState(); - const [sort, setSelectedSort] = useState(); - - const [isVisible, setIsVisible] = useState(false); - - const inputRef = useRef(null); - - const [page, setPage] = useState(1); - const [nextPage, setNextPage] = useState(true); - - async function advance() { - setLoading(true); - const data = await aniAdvanceSearch({ - search: search, - type: type, - genres: gr, - page: page, - sort: sort, - season: s, - seasonYear: y, - }); - if (data?.media?.length === 0) { - setNextPage(false); - } else if (data !== null && page > 1) { - setData((prevData) => { - return [...(prevData ?? []), ...data?.media]; - }); - setNextPage(data?.pageInfo.hasNextPage); - } else { - setData(data?.media); - } - setNextPage(data?.pageInfo.hasNextPage); - setLoading(false); - } - - useEffect(() => { - setData(null); - setPage(1); - setNextPage(true); - advance(); - }, [search, type, sort, s, y, gr]); - - useEffect(() => { - advance(); - }, [page]); - - useEffect(() => { - function handleScroll() { - if (page > 10 || !nextPage) { - window.removeEventListener("scroll", handleScroll); - return; - } - - if ( - window.innerHeight + window.pageYOffset >= - document.body.offsetHeight - 3 - ) { - setPage((prevPage) => prevPage + 1); - } - } - - window.addEventListener("scroll", handleScroll); - - return () => window.removeEventListener("scroll", handleScroll); - }, [page, nextPage]); - - const handleKeyDown = async (event) => { - if (event.key === "Enter") { - event.preventDefault(); - const inputValue = event.target.value; - if (inputValue === "") { - setQuery(null); - } else { - setQuery(inputValue); - } - } - }; - - function trash() { - setQuery(null); - inputRef.current.value = ""; - // setSelectedGenre(null); - setSelectedSort(["POPULARITY_DESC"]); - router.push(`/en/search/${tipe.toLocaleLowerCase()}`); - } - - function handleVisible() { - setIsVisible(!isVisible); - } - - function handleTipe(e) { - setSelectedType(e.target.value); - router.push(`/en/search/${e.target.value.toLowerCase()}`); - } - - // ); - - return ( - <> - <Head> - <title>Moopa - search</title> - <link rel="icon" href="/c.svg" /> - </Head> - <div className="bg-primary"> - <Navbar /> - <div className="min-h-screen mt-10 mb-14 text-white items-center gap-5 xl:gap-0 flex flex-col"> - <div className="w-screen px-10 xl:w-[80%] xl:h-[10rem] flex text-center xl:items-end xl:pb-10 justify-center lg:gap-7 xl:gap-10 gap-3 font-karla font-light"> - <div className="text-start"> - <h1 className="font-bold xl:pb-5 pb-3 hidden lg:block text-md pl-1 font-outfit"> - TITLE - </h1> - <input - className="xl:w-[297px] md:w-[297px] lg:w-[230px] xl:h-[46px] h-[35px] xxs:w-[230px] xs:w-[280px] bg-secondary rounded-[10px] font-karla font-light text-[#ffffff89] text-center" - placeholder="search here..." - type="text" - onKeyDown={handleKeyDown} - ref={inputRef} - /> - </div> - - {/* TYPE */} - <div className="hidden lg:block text-start"> - <h1 className="font-bold xl:pb-5 pb-3 text-md pl-1 font-outfit"> - TYPE - </h1> - <div className="relative"> - <select - className="xl:w-[297px] xl:h-[46px] lg:h-[35px] lg:w-[230px] bg-secondary rounded-[10px] justify-between flex items-center text-center appearance-none" - value={type} - onChange={(e) => handleTipe(e)} - > - {types.map((option) => ( - <option key={option} value={option}> - {option} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - {/* SORT */} - <div className="hidden lg:block text-start"> - <h1 className="font-bold xl:pb-5 lg:pb-3 text-md pl-1 font-outfit"> - SORT - </h1> - <div className="relative"> - <select - className="xl:w-[297px] xl:h-[46px] lg:h-[35px] lg:w-[230px] bg-secondary rounded-[10px] flex items-center text-center appearance-none" - onChange={(e) => { - setSelectedSort(e.target.value); - setData(null); - }} - > - <option value={["POPULARITY_DESC"]}>Sort By</option> - {sorts.map((sort) => ( - <option key={sort.value} value={sort.value}> - {sort.name} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - {/* OPTIONS */} - <div className="flex lg:gap-7 text-center gap-3 items-end"> - <div - className="xl:w-[73px] w-[50px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] justify-center flex items-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 group" - onClick={handleVisible} - > - <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="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" - /> - </svg> - </div> - - {/* TRASH ICON */} - <div - className="xl:w-[73px] w-[50px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] justify-center flex items-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 group" - onClick={trash} - > - <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="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" - /> - </svg> - </div> - </div> - </div> - - <div className="w-screen xl:w-[64%] flex xl:justify-end xl:pl-0"> - <AnimatePresence> - {isVisible && ( - <m.div - key="imagine" - initial={{ opacity: 0, y: -10 }} - animate={{ opacity: 1, y: 0 }} - exit={{ opacity: 0, y: -10 }} - className="xl:pb-16" - > - <div className="text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col pb-5 lg:pb-0 "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - GENRE - </h1> - <div className="relative"> - <select - className="w-[195px] xl:w-[297px] xl:h-[46px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - onChange={(e) => { - // setSelectedGenre( - // e.target.value === "undefined" - // ? undefined - // : e.target.value - // ); - router.push( - `/en/search/${tipe.toLocaleLowerCase()}/?genres=${ - e.target.value - }` - ); - }} - > - <option value="undefined">Select a Genre</option> - {genre.map((option) => { - return ( - <option key={option} value={option}> - {option} - </option> - ); - })} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - <div className="xl:hidden text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col pb-5 "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - TYPE - </h1> - <div className="relative"> - <select - className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - value={type} - onChange={(e) => setSelectedType(e.target.value)} - > - {types.map((option) => ( - <option key={option} value={option}> - {option} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - - <div className="xl:hidden text-start items-center xl:items-start flex w-screen xl:w-auto px-8 xl:px-0 flex-row justify-between xl:flex-col "> - <h1 className="font-bold xl:pb-5 text-md pl-1 font-outfit"> - SORT - </h1> - <div className="relative"> - <select - className="w-[195px] h-[35px] bg-secondary rounded-[10px] flex items-center text-center cursor-pointer hover:bg-[#272b35] transition-all duration-300 appearance-none" - onChange={(e) => { - setSelectedSort(e.target.value); - }} - > - <option value={["POPULARITY_DESC"]}>Sort By</option> - {sorts.map((sort) => ( - <option key={sort.value} value={sort.value}> - {sort.name} - </option> - ))} - </select> - <ChevronDownIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> - </div> - </div> - </m.div> - )} - </AnimatePresence> - </div> - {gr && ( - <div className="lg:w-[70%] px-5 lg:px-4 w-screen lg:mb-6"> - <h1 className="font-bold text-[25px] font-karla"> - Looking for : {gr} - </h1> - </div> - )} - <div className="flex flex-col gap-14 items-center"> - <AnimatePresence> - <div - key="card-keys" - className="grid pt-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-6 justify-items-center grid-cols-2 xxs:grid-cols-3 w-screen px-2 xl:w-auto xl:gap-10 gap-2 xl:gap-y-24 gap-y-12 overflow-hidden" - > - {loading - ? "" - : !data?.length && ( - <div className="w-screen text-[#ff7f57] xl:col-start-3 col-start-2 items-center flex justify-center text-center font-bold font-karla xl:text-2xl"> - Oops!<br></br> Nothing's Found... - </div> - )} - {data && - data?.map((anime, index) => { - return ( - <m.div - initial={{ scale: 0.9 }} - animate={{ scale: 1, transition: { duration: 0.35 } }} - className="w-[146px] xxs:w-[115px] xs:w-[135px] xl:w-[185px]" - key={index} - > - <Link - href={ - anime.format === "MANGA" || anime.format === "NOVEL" - ? `/en/manga/${anime.id}` - : `/en/anime/${anime.id}` - } - title={anime.title.userPreferred} - > - <Image - className="object-cover bg-[#3B3C41] w-[146px] h-[208px] xxs:w-[115px] xxs:h-[163px] xs:w-[135px] xs:h-[192px] xl:w-[185px] xl:h-[265px] hover:scale-105 scale-100 transition-all cursor-pointer duration-200 ease-out rounded-[10px]" - src={anime.coverImage.extraLarge} - alt={anime.title.userPreferred} - width={500} - height={500} - /> - </Link> - <Link - href={`/en/anime/${anime.id}`} - title={anime.title.userPreferred} - > - <h1 className="font-outfit font-bold xl:text-base text-[15px] pt-4 line-clamp-2"> - {anime.status === "RELEASING" ? ( - <span className="dots bg-green-500" /> - ) : anime.status === "NOT_YET_RELEASED" ? ( - <span className="dots bg-red-500" /> - ) : null} - {anime.title.userPreferred} - </h1> - </Link> - <h2 className="font-outfit xl:text-[15px] text-[11px] font-light pt-2 text-[#8B8B8B]"> - {anime.format || <p>-</p>} ·{" "} - {anime.status || <p>-</p>} ·{" "} - {anime.episodes - ? `${anime.episodes || "N/A"} Episodes` - : `${anime.chapters || "N/A"} Chapters`} - </h2> - </m.div> - ); - })} - - {loading && ( - <> - {[1, 2, 4, 5, 6, 7, 8].map((item) => ( - <div - key={item} - className="flex flex-col w-[135px] xl:w-[185px] gap-5" - style={{ scale: 0.98 }} - > - <Skeleton className="h-[192px] w-[135px] xl:h-[265px] xl:w-[185px]" /> - <Skeleton width={110} height={30} /> - </div> - ))} - </> - )} - </div> - {!loading && page > 10 && nextPage && ( - <button - onClick={() => setPage((p) => p + 1)} - className="bg-secondary xl:w-[30%] w-[80%] h-10 rounded-md" - > - Load More - </button> - )} - </AnimatePresence> - </div> - </div> - <Footer /> - </div> - </> - ); -} |