diff options
Diffstat (limited to 'components/shared')
| -rw-r--r-- | components/shared/MobileNav.js | 8 | ||||
| -rw-r--r-- | components/shared/NavBar.js | 265 | ||||
| -rw-r--r-- | components/shared/bugReport.js | 200 | ||||
| -rw-r--r-- | components/shared/footer.js | 223 |
4 files changed, 692 insertions, 4 deletions
diff --git a/components/shared/MobileNav.js b/components/shared/MobileNav.js index 6dd1e64..d0f29c2 100644 --- a/components/shared/MobileNav.js +++ b/components/shared/MobileNav.js @@ -1,12 +1,12 @@ import { MagnifyingGlassIcon } from "@heroicons/react/20/solid"; -import { CalendarIcon, ClockIcon, HomeIcon } from "@heroicons/react/24/outline"; -import { signIn, signOut } from "next-auth/react"; +import { CalendarIcon, HomeIcon } from "@heroicons/react/24/outline"; +import { signIn, signOut, useSession } from "next-auth/react"; import Image from "next/image"; import Link from "next/link"; -import { useRouter } from "next/router"; import { useState } from "react"; -export default function MobileNav({ sessions, hideProfile = false }) { +export default function MobileNav({ hideProfile = false }) { + const { data: sessions } = useSession(); const [isVisible, setIsVisible] = useState(false); const handleShowClick = () => { diff --git a/components/shared/NavBar.js b/components/shared/NavBar.js new file mode 100644 index 0000000..42fcff0 --- /dev/null +++ b/components/shared/NavBar.js @@ -0,0 +1,265 @@ +import { useSearch } from "@/lib/hooks/isOpenState"; +import { getCurrentSeason } from "@/utils/getTimes"; +import { ArrowLeftIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid"; +import { UserIcon } from "@heroicons/react/24/solid"; +import { signIn, signOut, useSession } from "next-auth/react"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; + +const getScrollPosition = (el = window) => ({ + x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft, + y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop, +}); + +export function NewNavbar({ + info, + scrollP = 200, + toTop = false, + withNav = false, + paddingY = "py-3", + home = false, + back = false, + manga = false, + shrink = false, +}) { + const { data: session } = useSession(); + const router = useRouter(); + const [scrollPosition, setScrollPosition] = useState(); + const { setIsOpen } = useSearch(); + + const year = new Date().getFullYear(); + const season = getCurrentSeason(); + + useEffect(() => { + const handleScroll = () => { + setScrollPosition(getScrollPosition()); + }; + + // Add a scroll event listener when the component mounts + window.addEventListener("scroll", handleScroll); + + // Clean up the event listener when the component unmounts + return () => { + window.removeEventListener("scroll", handleScroll); + }; + }, []); + return ( + <> + <nav + className={`${home ? "" : "fixed"} z-[200] top-0 px-5 w-full ${ + scrollPosition?.y >= scrollP + ? home + ? "" + : `bg-tersier shadow-tersier shadow-sm ${ + shrink ? "py-1" : `${paddingY}` + }` + : `${paddingY}` + } transition-all duration-200 ease-linear`} + > + <div + className={`flex items-center justify-between mx-auto ${ + home ? "lg:max-w-[90%] gap-10" : "max-w-screen-2xl" + }`} + > + <div + className={`flex items-center ${ + withNav ? `${home ? "" : "w-[20%]"} gap-8` : " w-full gap-4" + }`} + > + {info ? ( + <> + <button + type="button" + className="flex-center w-7 h-7 text-white" + onClick={() => { + back + ? router.back() + : manga + ? router.push("/en/search/manga") + : router.push("/en"); + }} + > + <ArrowLeftIcon className="w-full h-full" /> + </button> + <span + className={`font-inter font-semibold w-[50%] line-clamp-1 select-none ${ + scrollPosition?.y >= scrollP + 80 + ? "opacity-100" + : "opacity-0" + } transition-all duration-200 ease-linear`} + > + {info.title.romaji} + </span> + </> + ) : ( + // <></> + <Link + href={"/en"} + className={`flex-center font-outfit font-semibold pb-2 ${ + home ? "text-4xl text-action" : "text-white text-3xl" + }`} + > + moopa + </Link> + )} + </div> + + {withNav && ( + <ul + className={`hidden w-full items-center gap-10 pt-2 font-outfit text-[14px] lg:pt-0 lg:flex ${ + home ? "justify-start" : "justify-center" + }`} + > + <li> + <Link + 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" + className="hover:text-action/80 transition-all duration-150 ease-linear" + > + Manga + </Link> + </li> + <li> + <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> + + {!session && ( + <li> + <button + onClick={() => signIn("AniListProvider")} + 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 + </button> + </li> + )} + {session && ( + <li className="text-center"> + <Link + href={`/en/profile/${session?.user.name}`} + className="hover:text-action/80 transition-all duration-150 ease-linear" + > + My List + </Link> + </li> + )} + </ul> + )} + + <div className="flex w-[20%] justify-end items-center gap-4"> + <button + type="button" + onClick={() => setIsOpen(true)} + className="flex-center w-[26px] h-[26px]" + > + <svg + xmlns="http://www.w3.org/2000/svg" + width="32" + height="32" + viewBox="0 0 24 24" + > + <path + fill="none" + stroke="currentColor" + strokeLinecap="round" + strokeLinejoin="round" + strokeWidth="2" + d="M15 15l6 6m-11-4a7 7 0 110-14 7 7 0 010 14z" + ></path> + </svg> + </button> + {/* <div + className="bg-white" + // title={sessions ? "Go to Profile" : "Login With AniList"} + > */} + {session ? ( + <div className="w-7 h-7 relative flex flex-col items-center group"> + <button + type="button" + onClick={() => + router.push(`/en/profile/${session?.user.name}`) + } + className="rounded-full bg-white/30 overflow-hidden" + > + <Image + src={session?.user.image.large} + alt="avatar" + width={50} + height={50} + className="w-full h-full object-cover" + /> + </button> + <div className="hidden absolute z-50 w-28 text-center -bottom-20 text-white shadow-2xl opacity-0 bg-secondary p-1 py-2 rounded-md font-karla font-light invisible group-hover:visible group-hover:opacity-100 duration-300 transition-all md:grid place-items-center gap-1"> + <Link + href={`/en/profile/${session?.user.name}`} + className="hover:text-action" + > + Profile + </Link> + <button + type="button" + onClick={() => signOut("AniListProvider")} + className="hover:text-action" + > + Log out + </button> + </div> + </div> + ) : ( + <button + type="button" + onClick={() => signIn("AniListProvider")} + title="Login With AniList" + className="w-7 h-7 bg-white/30 rounded-full overflow-hidden" + > + <UserIcon className="w-full h-full translate-y-1" /> + </button> + )} + {/* </div> */} + </div> + </div> + </nav> + {toTop && ( + <button + type="button" + onClick={() => { + window.scrollTo({ + top: 0, + behavior: "smooth", + }); + }} + className={`${ + scrollPosition?.y >= 180 + ? "-translate-x-6 opacity-100" + : "translate-x-[100%] opacity-0" + } transform transition-all duration-300 ease-in-out fixed bottom-24 lg:bottom-14 right-0 z-[500]`} + > + <ArrowUpCircleIcon className="w-10 h-10 text-white" /> + </button> + )} + </> + ); +} diff --git a/components/shared/bugReport.js b/components/shared/bugReport.js new file mode 100644 index 0000000..9b99016 --- /dev/null +++ b/components/shared/bugReport.js @@ -0,0 +1,200 @@ +import { Fragment, useState } from "react"; +import { Dialog, Listbox, Transition } from "@headlessui/react"; +import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; +import { toast } from "react-toastify"; + +const severityOptions = [ + { id: 1, name: "Low" }, + { id: 2, name: "Medium" }, + { id: 3, name: "High" }, + { id: 4, name: "Critical" }, +]; + +const BugReportForm = ({ isOpen, setIsOpen }) => { + const [bugDescription, setBugDescription] = useState(""); + const [severity, setSeverity] = useState(severityOptions[0]); + + function closeModal() { + setIsOpen(false); + setBugDescription(""); + setSeverity(severityOptions[0]); + } + + const handleSubmit = async (e) => { + e.preventDefault(); + + const bugReport = { + desc: bugDescription, + severity: severity.name, + url: window.location.href, + createdAt: new Date().toISOString(), + }; + + try { + const res = await fetch("/api/v2/admin/bug-report", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + data: bugReport, + }), + }); + + const json = await res.json(); + toast.success(json.message, { + hideProgressBar: true, + theme: "colored", + }); + closeModal(); + } catch (err) { + console.log(err); + toast.error("Something went wrong: " + err.message, { + hideProgressBar: true, + theme: "colored", + }); + } + }; + + return ( + <> + <Transition appear show={isOpen} as={Fragment}> + <Dialog as="div" className="relative z-[200]" onClose={closeModal}> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in duration-200" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <div className="fixed inset-0 bg-black bg-opacity-90" /> + </Transition.Child> + + <div className="fixed inset-0 overflow-y-auto"> + <div className="flex min-h-full items-center justify-center p-4 "> + <Transition.Child + as={Fragment} + enter="ease-out duration-300" + enterFrom="opacity-0 scale-95" + enterTo="opacity-100 scale-100" + leave="ease-in duration-200" + leaveFrom="opacity-100 scale-100" + leaveTo="opacity-0 scale-95" + > + <Dialog.Panel className="w-full max-w-md transition-all"> + <div className="bg-secondary p-6 rounded-lg shadow-xl"> + <h2 className={`text-action text-2xl font-semibold mb-4`}> + Report a Bug + </h2> + <form onSubmit={handleSubmit}> + <div className="space-y-4"> + <div> + <label + htmlFor="bugDescription" + className={`block text-txt text-sm font-medium mb-2`} + > + Bug Description + </label> + <textarea + id="bugDescription" + name="bugDescription" + rows="4" + className={`w-full bg-image text-txt rounded-md border border-txt focus:ring-action focus:border-action transition duration-300 focus:outline-none py-2 px-3`} + placeholder="Describe the bug you encountered..." + value={bugDescription} + onChange={(e) => setBugDescription(e.target.value)} + required + ></textarea> + </div> + <Listbox value={severity} onChange={setSeverity}> + <div className="relative mt-1"> + <label + htmlFor="severity" + className={`block text-txt text-sm font-medium mb-2`} + > + Severity + </label> + <Listbox.Button + type="button" + className="relative w-full cursor-pointer hover:shadow-xl hover:scale-[1.01] transition-all rounded-lg bg-image py-2 pl-3 pr-10 text-left shadow-md sm:text-base duration-300" + > + <span className="block truncate text-white font-semibold"> + {severity.name} + </span> + <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"> + <ChevronDownIcon + className="h-5 w-5 text-gray-400" + aria-hidden="true" + /> + </span> + </Listbox.Button> + <Transition + as={Fragment} + leave="transition ease-in duration-100" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <Listbox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-image py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"> + {severityOptions.map((person, personIdx) => ( + <Listbox.Option + key={personIdx} + className={({ active }) => + `relative cursor-default select-none py-2 pl-10 pr-4 ${ + active + ? "bg-secondary/50 text-white" + : "text-gray-400" + }` + } + value={person} + > + {({ selected }) => ( + <> + <span + className={`block truncate ${ + selected + ? "font-medium text-white" + : "font-normal" + }`} + > + {person.name} + </span> + {selected ? ( + <span className="absolute inset-y-0 left-0 flex items-center pl-3 text-action"> + <CheckIcon + className="h-5 w-5" + aria-hidden="true" + /> + </span> + ) : null} + </> + )} + </Listbox.Option> + ))} + </Listbox.Options> + </Transition> + </div> + </Listbox> + </div> + <div className="mt-4"> + <button + type="submit" + className={`w-full bg-action text-white py-2 px-4 rounded-md font-semibold hover:bg-action/80 focus:ring focus:ring-action focus:outline-none transition duration-300`} + > + Submit Bug Report + </button> + </div> + </form> + </div> + </Dialog.Panel> + </Transition.Child> + </div> + </div> + </Dialog> + </Transition> + </> + ); +}; + +export default BugReportForm; diff --git a/components/shared/footer.js b/components/shared/footer.js new file mode 100644 index 0000000..91af5a8 --- /dev/null +++ b/components/shared/footer.js @@ -0,0 +1,223 @@ +import Link from "next/link"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { parseCookies, setCookie } from "nookies"; +import Image from "next/image"; + +function Footer() { + const [year] = useState(new Date().getFullYear()); + const [season] = useState(getCurrentSeason()); + + const [lang, setLang] = useState("en"); + const [checked, setChecked] = useState(false); + const [cookie, setCookies] = useState(null); + + 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"); + setChecked(false); + } else if (lang === "id") { + setLang("id"); + setChecked(true); + } + }, []); + + function switchLang() { + setChecked(!checked); + if (checked) { + console.log("switching to en"); + setCookie(null, "lang", "en", { + maxAge: 365 * 24 * 60 * 60, + path: "/", + }); + router.push("/en"); + } else { + router.push("/id"); + } + } + + return ( + <footer className="flex-col w-full"> + <div className="text-[#dbdcdd] z-40 bg-[#0c0d10] lg:flex lg:h-[12rem] w-full lg:items-center lg:justify-between"> + <div className="mx-auto flex w-[90%] lg:w-[95%] xl:w-[80%] flex-col space-y-10 py-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0"> + <div className="flex flex-col gap-2"> + {/* <div className="flex items-center gap-2"> */} + {/* <Image + src="/svg/c.svg" + alt="Website Logo" + width={100} + height={100} + className="w-10 h-10" + /> */} + <div className="flex gap-2 font-outfit text-4xl">moopa</div> + <p className="font-karla lg:text-[0.8rem] text-[0.65rem] text-[#9c9c9c] lg:w-[520px] italic"> + This site does not store any files on our server, we only linked + to the media which is hosted on 3rd party services. + </p> + {/* </div> */} + </div> + <div className="flex flex-col gap-10 lg:flex-row lg:items-end lg:gap-[9.06rem] text-[#a7a7a7] text-sm lg:text-end"> + <div className="flex flex-col gap-10 font-karla font-bold lg:flex-row lg:gap-[5.94rem]"> + <ul className="flex flex-col gap-y-[0.7rem] "> + <li className="cursor-pointer hover:text-action"> + <Link + href={`/${lang}/search/anime?season=${season}&year=${year}`} + > + This Season + </Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href={`/${lang}/search/anime`}>Popular Anime</Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href={`/${lang}/search/manga`}>Popular Manga</Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href={`/donate`}>Donate</Link> + </li> + </ul> + <ul className="flex flex-col gap-y-[0.7rem]"> + <li className="cursor-pointer hover:text-action"> + <Link href={`/${lang}/search/anime?format=MOVIE`}> + Movies + </Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href={`/${lang}/search/anime?format=TV`}>TV Shows</Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href={`/${lang}/dmca`}>DMCA</Link> + </li> + <li className="cursor-pointer hover:text-action"> + <Link href="https://github.com/DevanAbinaya/Ani-Moopa"> + Github + </Link> + </li> + </ul> + </div> + </div> + </div> + </div> + <div className="bg-tersier border-t border-white/5"> + <div className="mx-auto flex w-[90%] lg:w-[95%] xl:w-[80%] flex-col pb-6 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:py-0"> + <p className="flex items-center gap-1 font-karla lg:text-[0.81rem] text-[0.7rem] text-[#CCCCCC] py-3"> + © {new Date().getFullYear()} moopa.live | Website Made by{" "} + <span className="text-white font-bold">Factiven</span> + </p> + <div className="flex items-center gap-5"> + {/* Github Icon */} + <Link + href="https://github.com/Ani-Moopa/Moopa" + className="w-5 h-5 hover:opacity-75" + > + <svg + xmlns="http://www.w3.org/2000/svg" + fill="#fff" + viewBox="0 0 20 20" + > + <g> + <g + fill="none" + fillRule="evenodd" + stroke="none" + strokeWidth="1" + > + <g fill="#fff" transform="translate(-140 -7559)"> + <g transform="translate(56 160)"> + <path d="M94 7399c5.523 0 10 4.59 10 10.253 0 4.529-2.862 8.371-6.833 9.728-.507.101-.687-.219-.687-.492 0-.338.012-1.442.012-2.814 0-.956-.32-1.58-.679-1.898 2.227-.254 4.567-1.121 4.567-5.059 0-1.12-.388-2.034-1.03-2.752.104-.259.447-1.302-.098-2.714 0 0-.838-.275-2.747 1.051a9.396 9.396 0 00-2.505-.345 9.375 9.375 0 00-2.503.345c-1.911-1.326-2.751-1.051-2.751-1.051-.543 1.412-.2 2.455-.097 2.714-.639.718-1.03 1.632-1.03 2.752 0 3.928 2.335 4.808 4.556 5.067-.286.256-.545.708-.635 1.371-.57.262-2.018.715-2.91-.852 0 0-.529-.985-1.533-1.057 0 0-.975-.013-.068.623 0 0 .655.315 1.11 1.5 0 0 .587 1.83 3.369 1.21.005.857.014 1.665.014 1.909 0 .271-.184.588-.683.493-3.974-1.355-6.839-5.199-6.839-9.729 0-5.663 4.478-10.253 10-10.253"></path> + </g> + </g> + </g> + </g> + </svg> + </Link> + {/* Discord Icon */} + <Link + href="https://discord.gg/v5fjSdKwr2" + className="w-6 h-6 hover:opacity-75" + > + <svg + xmlns="http://www.w3.org/2000/svg" + preserveAspectRatio="xMidYMid" + viewBox="0 -28.5 256 256" + > + <path + fill="#fff" + d="M216.856 16.597A208.502 208.502 0 00164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 00-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0079.735 175.3a136.413 136.413 0 01-21.846-10.632 108.636 108.636 0 005.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 005.355 4.237 136.07 136.07 0 01-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36zM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18z" + ></path> + </svg> + </Link> + + {/* Kofi */} + <Link href="/donate" className="w-6 h-6 hover:opacity-75"> + <svg + xmlns="http://www.w3.org/2000/svg" + fill="#fff" + viewBox="0 0 24 24" + > + <path d="M23.881 8.948c-.773-4.085-4.859-4.593-4.859-4.593H.723c-.604 0-.679.798-.679.798s-.082 7.324-.022 11.822c.164 2.424 2.586 2.672 2.586 2.672s8.267-.023 11.966-.049c2.438-.426 2.683-2.566 2.658-3.734 4.352.24 7.422-2.831 6.649-6.916zm-11.062 3.511c-1.246 1.453-4.011 3.976-4.011 3.976s-.121.119-.31.023c-.076-.057-.108-.09-.108-.09-.443-.441-3.368-3.049-4.034-3.954-.709-.965-1.041-2.7-.091-3.71.951-1.01 3.005-1.086 4.363.407 0 0 1.565-1.782 3.468-.963 1.904.82 1.832 3.011.723 4.311zm6.173.478c-.928.116-1.682.028-1.682.028V7.284h1.77s1.971.551 1.971 2.638c0 1.913-.985 2.667-2.059 3.015z"></path> + </svg> + </Link> + + <label + className="flex items-center relative w-max cursor-pointer select-none text-txt" + title="Switch to ID" + > + <input + type="checkbox" + checked={checked} + onChange={() => switchLang()} + className="appearance-none transition-colors cursor-pointer w-14 h-5 rounded-full focus:outline-none focus:ring-offset-2 focus:ring-offset-black focus:ring-action bg-secondary" + /> + <span className="absolute font-medium text-xs uppercase right-2 text-action"> + {" "} + EN{" "} + </span> + <span className="absolute font-medium text-xs uppercase right-[2.1rem] text-action"> + {" "} + ID{" "} + </span> + <span className="w-6 h-6 right-[2.1rem] absolute rounded-full transform transition-transform bg-gray-200" /> + </label> + </div> + </div> + </div> + </footer> + ); +} + +export default Footer; + +function getCurrentSeason() { + const now = new Date(); + const month = now.getMonth() + 1; // getMonth() returns 0-based index + + switch (month) { + case 12: + case 1: + case 2: + return "WINTER"; + case 3: + case 4: + case 5: + return "SPRING"; + case 6: + case 7: + case 8: + return "SUMMER"; + case 9: + case 10: + case 11: + return "FALL"; + default: + return "UNKNOWN SEASON"; + } +} |