diff options
Diffstat (limited to 'components/manga')
| -rw-r--r-- | components/manga/chapters.js | 236 | ||||
| -rw-r--r-- | components/manga/info/mobile/mobileButton.js | 39 | ||||
| -rw-r--r-- | components/manga/info/mobile/topMobile.js | 16 | ||||
| -rw-r--r-- | components/manga/info/topSection.js | 107 | ||||
| -rw-r--r-- | components/manga/leftBar.js | 56 | ||||
| -rw-r--r-- | components/manga/mobile/bottomBar.js | 36 | ||||
| -rw-r--r-- | components/manga/mobile/hamburgerMenu.js | 228 | ||||
| -rw-r--r-- | components/manga/panels/firstPanel.js | 72 | ||||
| -rw-r--r-- | components/manga/panels/secondPanel.js | 61 | ||||
| -rw-r--r-- | components/manga/panels/thirdPanel.js | 46 | ||||
| -rw-r--r-- | components/manga/rightBar.js | 128 |
11 files changed, 397 insertions, 628 deletions
diff --git a/components/manga/chapters.js b/components/manga/chapters.js index fd7beea..2150686 100644 --- a/components/manga/chapters.js +++ b/components/manga/chapters.js @@ -1,13 +1,16 @@ import Link from "next/link"; import { useState, useEffect } from "react"; -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { setCookie } from "nookies"; +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "@heroicons/react/24/outline"; -const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { +const ChapterSelector = ({ chaptersData, data, setWatch, mangaId }) => { const [selectedProvider, setSelectedProvider] = useState( chaptersData[0]?.providerId || "" ); - const [selectedChapter, setSelectedChapter] = useState(""); + // const [selectedChapter, setSelectedChapter] = useState(""); const [chapters, setChapters] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [chaptersPerPage] = useState(10); @@ -16,13 +19,15 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const selectedChapters = chaptersData.find( (c) => c.providerId === selectedProvider ); - if (selectedChapters) { - setSelectedChapter(selectedChapters); - setFirstEp(selectedChapters); - } setChapters(selectedChapters?.chapters || []); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedProvider, chaptersData]); + useEffect(() => { + setCurrentPage(1); + }, [data.id]); + // Get current posts const indexOfLastChapter = currentPage * chaptersPerPage; const indexOfFirstChapter = indexOfLastChapter - chaptersPerPage; @@ -31,24 +36,6 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { indexOfLastChapter ); - // Change page - const paginate = (pageNumber) => setCurrentPage(pageNumber); - const nextPage = () => setCurrentPage((prev) => prev + 1); - const prevPage = () => setCurrentPage((prev) => prev - 1); - - function saveManga() { - localStorage.setItem( - "manga", - JSON.stringify({ manga: selectedChapter, data: data }) - ); - setCookie(null, "manga", data.id, { - maxAge: 24 * 60 * 60, - path: "/", - }); - } - - // console.log(selectedChapter); - // Create page numbers const pageNumbers = []; for (let i = 1; i <= Math.ceil(chapters.length / chaptersPerPage); i++) { @@ -59,7 +46,7 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const getDisplayedPageNumbers = (currentPage, totalPages, margin) => { const pageRange = [...Array(totalPages).keys()].map((i) => i + 1); - if (totalPages <= 10) { + if (totalPages <= 5) { return pageRange; } @@ -83,104 +70,147 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const displayedPageNumbers = getDisplayedPageNumbers( currentPage, pageNumbers.length, - 9 + 3 ); - // console.log(currentChapters); + useEffect(() => { + if (chapters) { + const getEpi = data?.nextAiringEpisode + ? chapters[data?.mediaListEntry?.progress] + : chapters[0]; + if (getEpi) { + const watchUrl = `/en/manga/read/${selectedProvider}?id=${mangaId}&chapterId=${encodeURIComponent( + getEpi.id + )}&anilist=${data.id}&num=${getEpi.number}`; + setWatch(watchUrl); + } else { + setWatch(null); + } + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chapters]); return ( - <div className="flex flex-col items-center z-40"> - <div className="flex flex-col w-full"> - <label htmlFor="provider" className="text-sm md:text-base font-medium"> - Select a Provider - </label> - <div className="relative w-full"> + <div className="flex flex-col gap-2 px-3"> + <div className="flex justify-between"> + <h1 className="text-[20px] lg:text-2xl font-bold font-karla"> + Chapters + </h1> + <div className="relative flex gap-2 items-center group"> <select id="provider" - className="w-full text-xs md:text-base cursor-pointer mt-2 p-2 focus:outline-none rounded-md appearance-none bg-secondary" + className="flex items-center text-sm gap-5 rounded-[3px] bg-secondary py-1 px-3 pr-8 font-karla appearance-none cursor-pointer outline-none focus: focus:ring-action group-hover: group-hover:ring-action" value={selectedProvider} onChange={(e) => setSelectedProvider(e.target.value)} > {/* <option value="">--Select a provider--</option> */} {chaptersData.map((provider, index) => ( - <option key={index} value={provider.providerId}> + <option key={provider.providerId} value={provider.providerId}> {provider.providerId} </option> ))} </select> - <ChevronDownIcon className="absolute md:right-5 right-3 md:bottom-2 m-auto md:w-6 md:h-6 bottom-[0.5rem] h-4 w-4" /> + <ChevronDownIcon className="absolute right-2 top-1/2 transform -translate-y-1/2 w-5 h-5 pointer-events-none" /> </div> </div> - <div className="mt-4 w-full py-5 flex justify-between gap-5"> - <button - onClick={prevPage} - disabled={currentPage === 1} - className={`w-24 py-1 shrink-0 rounded-md font-karla ${ - currentPage === 1 - ? "bg-[#1D1D20] text-[#313135]" - : `bg-secondary hover:bg-[#363639]` - }`} - > - Previous - </button> - <div className="flex gap-5 overflow-x-scroll scrollbar-thin scrollbar-thumb-secondary scrollbar-thumb- w-[420px] lg:w-auto"> - {displayedPageNumbers.map((number, index) => - number === "..." ? ( - <span key={index + 2} className="w-10 py-1 text-center"> - ... - </span> - ) : ( + + <div className="flex flex-col items-center z-40"> + <div className="mt-4 w-full"> + {currentChapters.map((chapter, index) => { + const isRead = chapter.number <= data?.mediaListEntry?.progress; + return ( + <Link + key={index} + href={`/en/manga/read/${selectedProvider}?id=${mangaId}&chapterId=${encodeURIComponent( + chapter.id + )}${data?.id?.length > 6 ? "" : `&anilist=${data.id}`}&num=${ + chapter.number + }`} + className={`flex gap-3 py-4 hover:bg-secondary odd:bg-secondary/30 even:bg-primary`} + > + <div className="flex w-full"> + <span className="shrink-0 px-4 text-center text-white/50"> + {chapter.number} + </span> + <p + className={`w-full line-clamp-1 ${ + isRead ? "text-[#5f5f5f]" : "text-white" + } + `} + > + {chapter.title || `Chapter ${chapter.number}`} + </p> + <p className="capitalize text-sm text-white/50 px-4"> + {selectedProvider} + </p> + </div> + </Link> + ); + })} + </div> + + <div className="flex flex-col mt-5 md:flex-row w-full sm:items-center sm:justify-between"> + <div className="flex-center"> + <p className="text-sm text-txt"> + Showing{" "} + <span className="font-medium">{indexOfFirstChapter + 1}</span> to{" "} + <span className="font-medium"> + {indexOfLastChapter > chapters.length + ? chapters.length + : indexOfLastChapter} + </span>{" "} + of <span className="font-medium">{chapters.length}</span> chapters + </p> + </div> + <div className="flex-center"> + <nav + className="isolate inline-flex space-x-1 rounded-md shadow-sm" + aria-label="Pagination" + > <button - key={number} - onClick={() => paginate(number)} - className={`w-10 shrink-0 py-1 rounded-md hover:bg-[#363639] ${ - number === currentPage ? "bg-[#363639]" : "bg-secondary" + onClick={() => setCurrentPage((prev) => prev - 1)} + disabled={currentPage === 1} + className={`relative inline-flex items-center rounded px-2 py-2 text-gray-400 hover:bg-secondary focus:z-20 focus:outline-offset-0 ${ + currentPage === 1 + ? "opacity-50 cursor-default pointer-events-none" + : "" }`} > - {number} + <span className="sr-only">Previous</span> + <ChevronLeftIcon className="h-5 w-5" aria-hidden="true" /> </button> - ) - )} - </div> - <button - onClick={nextPage} - disabled={currentPage === pageNumbers.length} - className={`w-24 py-1 shrink-0 rounded-md font-karla ${ - currentPage === pageNumbers.length - ? "bg-[#1D1D20] text-[#313135]" - : `bg-secondary hover:bg-[#363639]` - }`} - > - Next - </button> - </div> - <div className="mt-4 w-full"> - {currentChapters.map((chapter, index) => { - const isRead = chapter.number <= userManga?.progress; - return ( - <div key={index} className="p-2 border-b hover:bg-[#232325]"> - <Link - href={`/en/manga/read/${selectedProvider}?id=${ - data.id - }&chapterId=${encodeURIComponent(chapter.id)}`} - onClick={saveManga} + <div className="flex w-full gap-1 overflow-x-scroll scrollbar-thin scrollbar-thumb-image scrollbar-thumb-rounded"> + {displayedPageNumbers.map((pageNumber, index) => ( + <button + key={index} + onClick={() => setCurrentPage(pageNumber)} + disabled={pageNumber === "..."} + className={`relative rounded inline-flex items-center px-4 py-2 text-sm font-semibold text-txt hover:bg-secondary focus:z-20 focus:outline-offset-0 ${ + currentPage === pageNumber + ? "z-10 bg-secondary rounded text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-none" + : "" + }`} + > + {pageNumber} + </button> + ))} + </div> + <button + onClick={() => setCurrentPage((prev) => prev + 1)} + disabled={currentPage === pageNumbers.length} + className={`relative inline-flex items-center rounded px-2 py-2 text-gray-400 hover:bg-secondary focus:z-20 focus:outline-offset-0 ${ + currentPage === pageNumbers.length + ? "opacity-50 cursor-default" + : "" + }`} > - <h2 - className={`text-lg font-medium ${ - isRead ? "text-[#424245]" : "" - }`} - > - {chapter.title} - </h2> - <p - className={`text-[#59595d] ${isRead ? "text-[#313133]" : ""}`} - > - Updated At: {new Date(chapter.updatedAt).toLocaleString()} - </p> - </Link> - </div> - ); - })} + <span className="sr-only">Next</span> + <ChevronRightIcon className="h-5 w-5" aria-hidden="true" /> + </button> + </nav> + </div> + </div> </div> </div> ); diff --git a/components/manga/info/mobile/mobileButton.js b/components/manga/info/mobile/mobileButton.js deleted file mode 100644 index 0016b59..0000000 --- a/components/manga/info/mobile/mobileButton.js +++ /dev/null @@ -1,39 +0,0 @@ -import Link from "next/link"; -import AniList from "../../../media/aniList"; -import { BookOpenIcon } from "@heroicons/react/24/outline"; - -export default function MobileButton({ info, firstEp, saveManga }) { - return ( - <div className="md:hidden flex items-center gap-4 w-full pb-3"> - <button - disabled={!firstEp} - onClick={saveManga} - className={`${ - !firstEp - ? "pointer-events-none text-white/50 bg-secondary/50" - : "bg-secondary text-white" - } lg:w-full font-bold shadow-md shadow-secondary hover:bg-secondary/90 hover:text-white/50 rounded`} - > - <Link - href={`/en/manga/read/${firstEp?.providerId}?id=${ - info.id - }&chapterId=${encodeURIComponent( - firstEp?.chapters[firstEp.chapters.length - 1].id - )}`} - className="flex items-center text-xs font-karla gap-2 h-[30px] px-2" - > - <h1>Read Now</h1> - <BookOpenIcon className="w-4 h-4" /> - </Link> - </button> - <Link - href={`https://anilist.co/manga/${info.id}`} - className="flex-center rounded bg-secondary shadow-md shadow-secondary h-[30px] lg:px-4 px-2" - > - <div className="flex-center w-5 h-5"> - <AniList /> - </div> - </Link> - </div> - ); -} diff --git a/components/manga/info/mobile/topMobile.js b/components/manga/info/mobile/topMobile.js deleted file mode 100644 index 2e6b23a..0000000 --- a/components/manga/info/mobile/topMobile.js +++ /dev/null @@ -1,16 +0,0 @@ -import Image from "next/image"; - -export default function TopMobile({ info }) { - return ( - <div className="md:hidden"> - <Image - src={info.coverImage} - width={500} - height={500} - alt="cover image" - className="md:hidden absolute top-0 left-0 -translate-y-24 w-full h-[30rem] object-cover rounded-sm shadow-lg brightness-75" - /> - <div className="absolute top-0 left-0 w-full -translate-y-24 h-[32rem] bg-gradient-to-t from-primary to-transparent from-50%"></div> - </div> - ); -} diff --git a/components/manga/info/topSection.js b/components/manga/info/topSection.js deleted file mode 100644 index 45d5f11..0000000 --- a/components/manga/info/topSection.js +++ /dev/null @@ -1,107 +0,0 @@ -import Image from "next/image"; -import { BookOpenIcon } from "@heroicons/react/24/outline"; -import AniList from "../../media/aniList"; -import Link from "next/link"; -import TopMobile from "./mobile/topMobile"; -import MobileButton from "./mobile/mobileButton"; - -export default function TopSection({ info, firstEp, setCookie }) { - const slicedGenre = info.genres?.slice(0, 3); - - function saveManga() { - localStorage.setItem( - "manga", - JSON.stringify({ manga: firstEp, data: info }) - ); - - setCookie(null, "manga", info.id, { - maxAge: 24 * 60 * 60, - path: "/", - }); - } - - return ( - <div className="flex md:gap-5 w-[90%] xl:w-[70%] z-30"> - <TopMobile info={info} /> - <div className="hidden md:block w-[7rem] xs:w-[10rem] lg:w-[15rem] space-y-3 shrink-0 rounded-sm"> - <Image - src={info.coverImage} - width={500} - height={500} - priority - alt="cover image" - className="hidden md:block object-cover h-[10rem] xs:h-[14rem] lg:h-[22rem] rounded-sm shadow-lg shadow-[#1b1b1f] bg-[#34343b]/20" - /> - - <div className="hidden md:flex items-center justify-between w-full lg:gap-5 pb-3"> - <button - disabled={!firstEp} - onClick={saveManga} - className={`${ - !firstEp - ? "pointer-events-none text-white/50 bg-tersier/50" - : "bg-tersier text-white" - } lg:w-full font-bold shadow-md shadow-[#0E0E0F] hover:bg-tersier/90 hover:text-white/50 rounded-md`} - > - <Link - href={`/en/manga/read/${firstEp?.providerId}?id=${ - info.id - }&chapterId=${encodeURIComponent( - firstEp?.chapters[firstEp.chapters.length - 1].id - )}`} - className="flex items-center lg:justify-center text-sm lg:text-base font-karla gap-2 h-[35px] lg:h-[40px] px-2" - > - <h1>Read Now</h1> - <BookOpenIcon className="w-5 h-5" /> - </Link> - </button> - <Link - href={`https://anilist.co/manga/${info.id}`} - className="flex-center rounded-md bg-tersier shadow-md shadow-[#0E0E0F] h-[35px] lg:h-[40px] lg:px-4 px-2" - > - <div className="flex-center w-5 h-5"> - <AniList /> - </div> - </Link> - </div> - </div> - <div className="w-full flex flex-col justify-start z-40"> - <div className="md:h-1/2 py-2 md:py-5 flex flex-col md:gap-2 justify-end"> - <h1 className="title text-xl md:text-2xl xl:text-3xl text-white font-semibold font-karla line-clamp-1 text-start"> - {info.title?.romaji || info.title?.english || info.title?.native} - </h1> - <span className="flex flex-wrap text-xs lg:text-sm md:text-[#747478]"> - {slicedGenre && - slicedGenre.map((genre, index) => { - return ( - <div key={index} className="flex"> - {genre} - {index < slicedGenre?.length - 1 && ( - <span className="mx-2 text-sm text-[#747478]">•</span> - )} - </div> - ); - })} - </span> - </div> - - <MobileButton info={info} firstEp={firstEp} saveManga={saveManga} /> - - <div className="hidden md:block relative h-1/2"> - {/* <span className="font-semibold text-sm">Description</span> */} - <div - className={`relative group h-[8rem] lg:h-[12.5rem] text-sm lg:text-base overflow-y-scroll scrollbar-hide`} - > - <p - dangerouslySetInnerHTML={{ __html: info.description }} - className="pb-5 pt-2 leading-5" - /> - </div> - <div - className={`absolute bottom-0 w-full bg-gradient-to-b from-transparent to-secondary to-50% h-[2rem]`} - /> - </div> - </div> - </div> - ); -} diff --git a/components/manga/leftBar.js b/components/manga/leftBar.js index 17acd55..5a98115 100644 --- a/components/manga/leftBar.js +++ b/components/manga/leftBar.js @@ -1,14 +1,23 @@ +import { getHeaders, getRandomId } from "@/utils/imageUtils"; import { ArrowLeftIcon } from "@heroicons/react/24/solid"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -export function LeftBar({ data, page, info, currentId, setSeekPage }) { +export function LeftBar({ + data, + page, + info, + currentId, + setSeekPage, + number, + mediaId, + providerId, +}) { const router = useRouter(); function goBack() { router.push(`/en/manga/${info.id}`); } - // console.log(info); return ( <div className="hidden lg:block shrink-0 w-[16rem] h-screen overflow-y-auto scrollbar-none bg-secondary relative group"> <div className="grid"> @@ -37,23 +46,27 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) { <h1 className="font-bold xl:text-lg">Chapters</h1> <div className="px-2"> <div className="w-full text-sm xl:text-base px-1 h-[8rem] xl:h-[30vh] bg-[#161617] rounded-md overflow-auto scrollbar-thin scrollbar-thumb-[#363639] scrollbar-thumb-rounded-md hover:scrollbar-thumb-[#424245]"> - {data?.chapters?.map((x) => { + {data?.chapters?.map((x, index) => { return ( <div - key={x.id} + key={getRandomId()} className={`${ x.id === currentId && "text-action" } py-1 px-2 hover:bg-[#424245] rounded-sm`} > <Link - href={`/en/manga/read/${data.providerId}?id=${ - info.id - }&chapterId=${encodeURIComponent(x.id)}`} + href={`/en/manga/read/${ + data.providerId + }?id=${mediaId}&chapterId=${encodeURIComponent(x.id)}${ + info?.id?.length > 6 ? "" : `&anilist=${info?.id}` + }&num=${x.number}`} className="" > <h1 className="line-clamp-1"> - <span className="font-bold">{x.number}.</span>{" "} - {x.title} + <span className="font-bold"> + {x.number || index + 1}. + </span>{" "} + {x.title || `Chapter ${x.number || index + 1}`} </h1> </Link> </div> @@ -69,28 +82,37 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) { <div className="text-center w-full px-1 h-[30vh] bg-[#161617] rounded-md overflow-auto scrollbar-thin scrollbar-thumb-[#363639] scrollbar-thumb-rounded-md hover:scrollbar-thumb-[#424245]"> {Array.isArray(page) ? ( <div className="grid grid-cols-2 gap-5 py-4 px-2 place-items-center"> - {page?.map((x) => { + {page?.map((x, index) => { return ( <div - key={x.url} + key={getRandomId()} className="hover:bg-[#424245] cursor-pointer rounded-sm w-full" > <div className="flex flex-col items-center cursor-pointer" - onClick={() => setSeekPage(x.index)} + onClick={() => setSeekPage(index)} > <Image src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( x.url - )}&headers=${encodeURIComponent( - JSON.stringify({ Referer: x.headers.Referer }) - )}`} + )}${ + x?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(x?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} + // &headers=${encodeURIComponent( + // JSON.stringify({ Referer: x.headers.Referer }) + // )} alt="chapter image" width={100} height={200} className="w-full h-[120px] object-contain scale-90" /> - <h1>Page {x.index + 1}</h1> + <h1>Page {index + 1}</h1> </div> </div> ); @@ -98,7 +120,7 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) { </div> ) : ( <div className="py-4"> - <p>{page.error || "No Pages."}</p> + <p>{page?.error || "No Pages."}</p> </div> )} </div> diff --git a/components/manga/mobile/bottomBar.js b/components/manga/mobile/bottomBar.js index 6493dca..5b28de4 100644 --- a/components/manga/mobile/bottomBar.js +++ b/components/manga/mobile/bottomBar.js @@ -1,3 +1,4 @@ +import { getHeaders } from "@/utils/imageUtils"; import { ChevronLeftIcon, ChevronRightIcon, @@ -14,12 +15,15 @@ export default function BottomBar({ nextChapter, currentPage, chapter, - page, + data, setSeekPage, setIsOpen, + number, + mangadexId, }) { const [openPage, setOpenPage] = useState(false); const router = useRouter(); + return ( <div className={`fixed lg:hidden flex flex-col gap-3 z-50 h-auto w-screen ${ @@ -39,7 +43,9 @@ export default function BottomBar({ router.push( `/en/manga/read/${ chapter.providerId - }?id=${id}&chapterId=${encodeURIComponent(prevChapter)}` + }?id=${mangadexId}&chapterId=${encodeURIComponent( + prevChapter.id + )}${id > 6 ? "" : `&anilist=${id}`}&num=${prevChapter.number}` ) } > @@ -56,7 +62,9 @@ export default function BottomBar({ router.push( `/en/manga/read/${ chapter.providerId - }?id=${id}&chapterId=${encodeURIComponent(nextChapter)}` + }?id=${mangadexId}&chapterId=${encodeURIComponent( + nextChapter.id + )}${id > 6 ? "" : `&anilist=${id}`}&num=${nextChapter.number}` ) } > @@ -82,13 +90,14 @@ export default function BottomBar({ <RectangleStackIcon className="w-5 h-5" /> </button> </div> - <span className="flex bg-secondary shadow-lg ring-1 ring-black ring-opacity-5 p-2 rounded-md">{`${currentPage}/${page.length}`}</span> + <span className="flex bg-secondary shadow-lg ring-1 ring-black ring-opacity-5 p-2 rounded-md">{`${currentPage}/${data?.length}`}</span> </div> {openPage && ( <div className="bg-secondary flex justify-center h-full w-screen py-2"> <div className="flex overflow-scroll"> - {Array.isArray(page) ? ( - page.map((x) => { + {Array.isArray(data) ? ( + data.map((x, index) => { + const indx = index + 1; return ( <div key={x.url} @@ -101,9 +110,18 @@ export default function BottomBar({ <Image src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( x.url - )}&headers=${encodeURIComponent( - JSON.stringify({ Referer: x.headers.Referer }) - )}`} + )}${ + x?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(x?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(chapter.providerId)) + )}` + }`} + // &headers=${encodeURIComponent( + // JSON.stringify({ Referer: x.headers.Referer }) + // )} alt="chapter image" width={100} height={200} diff --git a/components/manga/mobile/hamburgerMenu.js b/components/manga/mobile/hamburgerMenu.js deleted file mode 100644 index fcdbcce..0000000 --- a/components/manga/mobile/hamburgerMenu.js +++ /dev/null @@ -1,228 +0,0 @@ -import React, { useState, useEffect } from "react"; -import Link from "next/link"; -import { useSession, signIn, signOut } from "next-auth/react"; -import Image from "next/image"; -import { parseCookies } from "nookies"; - -export default function HamburgerMenu() { - const { data: session } = useSession(); - const [isVisible, setIsVisible] = useState(false); - const [fade, setFade] = useState(false); - - const [lang, setLang] = useState("en"); - const [cookie, setCookies] = useState(null); - - const handleShowClick = () => { - setIsVisible(true); - setFade(true); - }; - - const handleHideClick = () => { - setIsVisible(false); - setFade(false); - }; - - 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"); - } - }, []); - return ( - <> - {!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> - )} - - {/* Mobile Menu */} - <div - className={`transition-all duration-150 ${ - fade ? "opacity-100" : "opacity-0" - } z-50`} - > - {isVisible && session && ( - <Link - href={`/${lang}/profile/${session?.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]" - > - <Image - src={session?.user.image.large} - alt="user avatar" - height={500} - width={500} - 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={`/${lang}/`} className=""> - <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={`/${lang}/`} - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - home - </Link> - </button> - <button className="group flex flex-col items-center"> - <Link href={`/${lang}/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={`/${lang}/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={`/${lang}/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={`/${lang}/search/anime`} - className="font-karla font-bold text-[#8BA0B2] group-hover:text-action" - > - search - </Link> - </button> - {session ? ( - <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/manga/panels/firstPanel.js b/components/manga/panels/firstPanel.js index f1ee859..596fa58 100644 --- a/components/manga/panels/firstPanel.js +++ b/components/manga/panels/firstPanel.js @@ -4,10 +4,13 @@ import { ArrowsPointingInIcon, ChevronLeftIcon, ChevronRightIcon, + PlusIcon, + MinusIcon, } from "@heroicons/react/24/outline"; import Image from "next/image"; import { useRouter } from "next/router"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders, getRandomId } from "@/utils/imageUtils"; export default function FirstPanel({ aniId, @@ -26,14 +29,20 @@ export default function FirstPanel({ mobileVisible, setMobileVisible, setCurrentPage, + number, + mangadexId, }) { const { markProgress } = useAniList(session); const [currentImageIndex, setCurrentImageIndex] = useState(0); const imageRefs = useRef([]); const scrollContainerRef = useRef(); + const [imageQuality, setImageQuality] = useState(80); + const router = useRouter(); + // console.log({ chapter }); + useEffect(() => { const handleScroll = () => { const scrollTop = scrollContainerRef.current.scrollTop; @@ -53,13 +62,17 @@ export default function FirstPanel({ } } - if (index === data.length - 3 && !hasRun.current) { + if (index === data?.length - 3 && !hasRun.current) { if (session) { + if (aniId?.length > 6) return; const currentChapter = chapter.chapters?.find( (x) => x.id === currentId ); if (currentChapter) { - markProgress(aniId, currentChapter.number); + const chapterNumber = + currentChapter.number ?? + chapter.chapters.indexOf(currentChapter) + 1; + markProgress(aniId, chapterNumber); console.log("marking progress"); } } @@ -82,8 +95,12 @@ export default function FirstPanel({ }); } }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, session, chapter]); + // console.log({ imageQuality }); + useEffect(() => { if (scrollContainerRef.current && seekPage !== currentImageIndex) { const targetImageRef = imageRefs.current[seekPage]; @@ -119,19 +136,26 @@ export default function FirstPanel({ {data && Array.isArray(data) && data?.length > 0 ? ( data.map((i, index) => ( <div - key={i.url} + key={getRandomId()} className="w-screen lg:h-auto lg:w-full" ref={(el) => (imageRefs.current[index] = el)} > <Image src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( i.url - )}&headers=${encodeURIComponent( - JSON.stringify({ Referer: i.headers.Referer }) - )}`} - alt={i.index} + )}${ + i?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(i?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(chapter.providerId)) + )}` + }`} + alt={index} width={500} height={500} + quality={imageQuality} onClick={() => setMobileVisible(!mobileVisible)} className="w-screen lg:w-full h-auto bg-[#bbb]" /> @@ -145,6 +169,26 @@ export default function FirstPanel({ )} </div> <div className="absolute hidden lg:flex bottom-5 left-5 gap-5"> + {/* <button + type="button" + disabled={imageQuality >= 100} + onClick={() => { + setImageQuality((prev) => (prev <= 100 ? prev + 10 : prev)); + }} + className="flex-center p-2 bg-secondary" + > + <PlusIcon className="w-5 h-5" /> + </button> + <button + type="button" + disabled={imageQuality <= 10} + onClick={() => { + setImageQuality((prev) => (prev >= 10 ? prev - 10 : prev)); + }} + className="flex-center p-2 bg-secondary" + > + <MinusIcon className="w-5 h-5" /> + </button> */} <span className="flex bg-secondary p-2 rounded-sm"> {visible ? ( <button type="button" onClick={() => setVisible(!visible)}> @@ -168,7 +212,11 @@ export default function FirstPanel({ router.push( `/en/manga/read/${ chapter.providerId - }?id=${aniId}&chapterId=${encodeURIComponent(prevChapter)}` + }?id=${mangadexId}&chapterId=${encodeURIComponent( + prevChapter?.id + )}${aniId?.length > 6 ? "" : `&anilist=${aniId}`}&num=${ + prevChapter?.number + }` ) } > @@ -185,7 +233,11 @@ export default function FirstPanel({ router.push( `/en/manga/read/${ chapter.providerId - }?id=${aniId}&chapterId=${encodeURIComponent(nextChapter)}` + }?id=${mangadexId}&chapterId=${encodeURIComponent( + nextChapter?.id + )}${aniId?.length > 6 ? "" : `&anilist=${aniId}`}&num=${ + nextChapter?.number + }` ) } > @@ -195,7 +247,7 @@ export default function FirstPanel({ </div> <span className="hidden lg:flex bg-secondary p-2 rounded-sm absolute bottom-5 right-5">{`Page ${ currentImageIndex + 1 - }/${data.length}`}</span> + }/${data?.length}`}</span> </section> ); } diff --git a/components/manga/panels/secondPanel.js b/components/manga/panels/secondPanel.js index 9323822..fa158b2 100644 --- a/components/manga/panels/secondPanel.js +++ b/components/manga/panels/secondPanel.js @@ -5,9 +5,11 @@ import { ArrowsPointingInIcon, } from "@heroicons/react/24/outline"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders } from "@/utils/imageUtils"; export default function SecondPanel({ aniId, + chapterData, data, hasRun, currentChapter, @@ -17,6 +19,7 @@ export default function SecondPanel({ visible, setVisible, session, + providerId, }) { const [index, setIndex] = useState(0); const [image, setImage] = useState(null); @@ -26,6 +29,7 @@ export default function SecondPanel({ useEffect(() => { setIndex(0); setSeekPage(0); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, currentId]); const seekToIndex = (newIndex) => { @@ -41,6 +45,7 @@ export default function SecondPanel({ useEffect(() => { seekToIndex(seekPage); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [seekPage]); useEffect(() => { @@ -63,13 +68,14 @@ export default function SecondPanel({ } if (index + 1 >= image.length - 4 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); - } + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; - markProgress(aniId, chapterNumber); + if (chapterNumber) { + markProgress(aniId, chapterNumber); + } hasRun.current = true; } } @@ -80,6 +86,7 @@ export default function SecondPanel({ return () => { window.removeEventListener("keydown", handleKeyDown); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index, image]); const handleNext = () => { @@ -90,10 +97,13 @@ export default function SecondPanel({ if (index + 1 >= image.length - 4 && !hasRun.current) { console.log("marking progress"); - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; + + if (chapterNumber) { + markProgress(aniId, chapterNumber); } markProgress(aniId, chapterNumber); @@ -107,6 +117,7 @@ export default function SecondPanel({ setSeekPage(index - 2); } }; + return ( <div className="flex-grow h-screen"> <div className="flex items-center w-full relative group"> @@ -127,11 +138,17 @@ export default function SecondPanel({ className="w-1/2 h-screen object-contain" src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 2]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 2]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 2]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify( + image[image.length - index - 2]?.headers + ) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" /> )} @@ -142,11 +159,15 @@ export default function SecondPanel({ className="w-1/2 h-screen object-contain" src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 1]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 1]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 1]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(image[image.length - index - 1]?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" /> </div> diff --git a/components/manga/panels/thirdPanel.js b/components/manga/panels/thirdPanel.js index d402f07..f13b49d 100644 --- a/components/manga/panels/thirdPanel.js +++ b/components/manga/panels/thirdPanel.js @@ -5,10 +5,12 @@ import { ArrowsPointingInIcon, } from "@heroicons/react/24/outline"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders } from "@/utils/imageUtils"; export default function ThirdPanel({ aniId, data, + chapterData, hasRun, currentId, currentChapter, @@ -20,6 +22,7 @@ export default function ThirdPanel({ scaleImg, setMobileVisible, mobileVisible, + providerId, }) { const [index, setIndex] = useState(0); const [image, setImage] = useState(null); @@ -28,6 +31,7 @@ export default function ThirdPanel({ useEffect(() => { setIndex(0); setSeekPage(0); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, currentId]); const seekToIndex = (newIndex) => { @@ -39,6 +43,7 @@ export default function ThirdPanel({ useEffect(() => { seekToIndex(seekPage); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [seekPage]); useEffect(() => { @@ -60,13 +65,14 @@ export default function ThirdPanel({ setSeekPage(index + 1); } if (index + 1 >= image.length - 2 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); - } + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; - markProgress(aniId, chapterNumber); + if (chapterNumber) { + markProgress(aniId, chapterNumber); + } hasRun.current = true; } } @@ -77,6 +83,8 @@ export default function ThirdPanel({ return () => { window.removeEventListener("keydown", handleKeyDown); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index, image]); const handleNext = () => { @@ -85,13 +93,15 @@ export default function ThirdPanel({ setSeekPage(index + 1); } if (index + 1 >= image.length - 2 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; + + if (chapterNumber) { + markProgress(aniId, chapterNumber); } - markProgress(aniId, chapterNumber); hasRun.current = true; } }; @@ -119,11 +129,15 @@ export default function ThirdPanel({ onClick={() => setMobileVisible(!mobileVisible)} src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 1]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 1]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 1]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(image[image.length - index - 1]?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" style={{ transform: `scale(${scaleImg})`, diff --git a/components/manga/rightBar.js b/components/manga/rightBar.js index 82d577d..9672fc4 100644 --- a/components/manga/rightBar.js +++ b/components/manga/rightBar.js @@ -4,16 +4,15 @@ import { } from "@heroicons/react/24/outline"; import { useEffect, useState } from "react"; import { useAniList } from "../../lib/anilist/useAnilist"; -import { toast } from "react-toastify"; import AniList from "../media/aniList"; import { signIn } from "next-auth/react"; +import { toast } from "sonner"; export default function RightBar({ id, hasRun, session, data, - error, currentChapter, paddingX, setPaddingX, @@ -47,19 +46,13 @@ export default function RightBar({ markProgress(id, progress, status, volumeProgress); hasRun.current = true; } else { - toast.error("Progress must be a whole number!", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - theme: "colored", - }); + toast.error("Progress must be a whole number!"); } } }; + // console.log({ id }); + const changeMode = (e) => { setLayout(Number(e.target.value)); // console.log(e.target.value); @@ -129,63 +122,72 @@ export default function RightBar({ </button> </div> </div> + {/* <div className="flex flex-col gap-3 w-full"> + <h1 className="font-karla font-bold xl:text-lg">Set Quality</h1> + </div> */} <div className="flex flex-col gap-3 w-full"> <h1 className="font-karla font-bold xl:text-lg">Tracking</h1> {session ? ( - <div className="flex flex-col gap-2"> - <div className="space-y-1"> - <label className="font-karla font-semibold text-gray-500 text-xs"> - Status - </label> - <div className="relative"> - <select - onChange={(e) => setStatus(e.target.value)} - className="w-full px-2 py-1 font-karla rounded-md bg-[#161617] appearance-none text-sm" - > - <option value="CURRENT">Reading</option> - <option value="PLANNING">Plan to Read</option> - <option value="COMPLETED">Completed</option> - <option value="REPEATING">Rereading</option> - <option value="PAUSED">Paused</option> - <option value="DROPPED">Dropped</option> - </select> - <ChevronDownIcon className="w-5 h-5 text-white absolute inset-0 my-auto mx-52" /> + id?.length > 6 ? ( + <p className="flex-center w-full py-2 font-karla"> + Not available on AniList + </p> + ) : ( + <div className="flex flex-col gap-2"> + <div className="space-y-1"> + <label className="font-karla font-semibold text-gray-500 text-xs"> + Status + </label> + <div className="relative"> + <select + onChange={(e) => setStatus(e.target.value)} + className="w-full px-2 py-1 font-karla rounded-md bg-[#161617] appearance-none text-sm" + > + <option value="CURRENT">Reading</option> + <option value="PLANNING">Plan to Read</option> + <option value="COMPLETED">Completed</option> + <option value="REPEATING">Rereading</option> + <option value="PAUSED">Paused</option> + <option value="DROPPED">Dropped</option> + </select> + <ChevronDownIcon className="w-5 h-5 text-white absolute inset-0 my-auto mx-52" /> + </div> </div> + <div className="space-y-1"> + <label className="font-karla font-semibold text-gray-500 text-xs"> + Chapter Progress + </label> + <input + id="chapter-progress" + type="number" + placeholder="0" + min={0} + value={progress} + onChange={(e) => setProgress(e.target.value)} + className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" + /> + </div> + <div className="space-y-1"> + <label className="font-karla font-semibold text-gray-500 text-xs"> + Volume Progress + </label> + <input + type="number" + placeholder="0" + min={0} + onChange={(e) => setVolumeProgress(e.target.value)} + className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" + /> + </div> + <button + type="button" + onClick={saveProgress} + className="w-full bg-[#424245] py-1 my-5 rounded-md text-white text-sm xl:text-base shadow-md font-karla font-semibold" + > + Save Progress + </button> </div> - <div className="space-y-1"> - <label className="font-karla font-semibold text-gray-500 text-xs"> - Chapter Progress - </label> - <input - id="chapter-progress" - type="number" - placeholder="0" - min={0} - value={progress} - onChange={(e) => setProgress(e.target.value)} - className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" - /> - </div> - <div className="space-y-1"> - <label className="font-karla font-semibold text-gray-500 text-xs"> - Volume Progress - </label> - <input - type="number" - placeholder="0" - min={0} - onChange={(e) => setVolumeProgress(e.target.value)} - className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" - /> - </div> - <button - type="button" - onClick={saveProgress} - className="w-full bg-[#424245] py-1 my-5 rounded-md text-white text-sm xl:text-base shadow-md font-karla font-semibold" - > - Save Progress - </button> - </div> + ) ) : ( <button type="button" |