diff options
| author | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-12-24 13:03:54 +0700 |
| commit | 50a0f0240d7fef133eb5acc1bea2b1168b08e9db (patch) | |
| tree | 307e09e505580415a58d64b5fc3580e9235869f1 /components/anime/mobile | |
| parent | Update README.md (#104) (diff) | |
| download | moopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.tar.xz moopa-50a0f0240d7fef133eb5acc1bea2b1168b08e9db.zip | |
migrate to typescript
Diffstat (limited to 'components/anime/mobile')
| -rw-r--r-- | components/anime/mobile/reused/infoChip.tsx (renamed from components/anime/mobile/reused/infoChip.js) | 23 | ||||
| -rw-r--r-- | components/anime/mobile/topSection.tsx (renamed from components/anime/mobile/topSection.js) | 325 |
2 files changed, 209 insertions, 139 deletions
diff --git a/components/anime/mobile/reused/infoChip.js b/components/anime/mobile/reused/infoChip.tsx index 41def85..80ebf83 100644 --- a/components/anime/mobile/reused/infoChip.js +++ b/components/anime/mobile/reused/infoChip.tsx @@ -1,7 +1,20 @@ -import React from "react"; -import { getFormat } from "../../../../utils/getFormat"; +import React, { CSSProperties, FC } from "react"; +import { getFormat } from "@/utils/getFormat"; -export default function InfoChip({ info, color, className }) { +interface Info { + episodes?: number; + averageScore?: number; + format?: string; + status?: string; +} + +interface InfoChipProps { + info: Info; + color: any; + className: string; +} + +const InfoChip: FC<InfoChipProps> = ({ info, color, className }) => { return ( <div className={`flex-wrap w-full justify-start md:pt-1 gap-4 ${className}`} @@ -40,4 +53,6 @@ export default function InfoChip({ info, color, className }) { )} </div> ); -} +}; + +export default InfoChip; diff --git a/components/anime/mobile/topSection.js b/components/anime/mobile/topSection.tsx index 6780da5..2d28c66 100644 --- a/components/anime/mobile/topSection.js +++ b/components/anime/mobile/topSection.tsx @@ -11,26 +11,36 @@ import { convertSecondsToTime } from "@/utils/getTimes"; import Link from "next/link"; import InfoChip from "./reused/infoChip"; import Description from "./reused/description"; -import { NewNavbar } from "@/components/shared/NavBar"; +import Skeleton from "react-loading-skeleton"; +import { AniListInfoTypes } from "types/info/AnilistInfoTypes"; + +type DetailTopProps = { + info?: AniListInfoTypes | null; + statuses?: any; + handleOpen: () => void; + watchUrl: string | undefined; + progress?: number; + color?: string | null; +}; export default function DetailTop({ info, - statuses, + statuses = undefined, handleOpen, watchUrl, progress, color, -}) { +}: DetailTopProps) { const router = useRouter(); const [readMore, setReadMore] = useState(false); const [showAll, setShowAll] = useState(false); - const isAnime = info.type === "ANIME"; + const isAnime = info?.type === "ANIME"; useEffect(() => { setReadMore(false); - }, [info.id]); + }, [info?.id]); const handleShareClick = async () => { try { @@ -51,78 +61,150 @@ export default function DetailTop({ return ( <div className="gap-6 w-full px-3 pt-4 md:pt-10 flex flex-col items-center justify-center"> - <NewNavbar info={info} /> - {/* MAIN */} <div className="flex flex-col md:flex-row w-full items-center md:items-end gap-5 pt-12"> <div className="shrink-0 w-[180px] h-[250px] rounded overflow-hidden"> - <img - src={info?.coverImage?.extraLarge || info?.coverImage} - alt="poster anime" - width={300} - height={300} - className="w-full h-full object-cover" - /> + {info ? ( + <img + src={ + info?.coverImage?.extraLarge?.toString() ?? + info?.coverImage?.toString() + } + alt="poster anime" + width={300} + height={300} + className="w-full h-full object-cover" + /> + ) : ( + <Skeleton className="h-full" /> + )} </div> <div className="flex flex-col gap-4 items-center md:items-start justify-end w-full"> - <div className="flex flex-col gap-1 text-center md:text-start"> + <div className="flex flex-col gap-1 text-center md:text-start w-full"> <h3 className="font-karla text-lg capitalize leading-none"> {info?.season?.toLowerCase() || getMonth(info?.startDate?.month)}{" "} - {info.seasonYear || info?.startDate?.year} + {info?.seasonYear || info?.startDate?.year} + {!info && <Skeleton height={14} width={140} />} </h3> <h1 className="font-outfit font-extrabold text-2xl md:text-4xl line-clamp-2 text-white"> {info?.title?.romaji || info?.title?.english} + {!info && <Skeleton height={35} width={340} className="" />} </h1> <h2 className="font-karla line-clamp-1 text-sm md:text-lg md:pb-2 font-light text-white/70"> - {info.title?.english} + {info?.title?.english} </h2> - <InfoChip info={info} color={color} className="hidden md:flex" /> - {info?.description && ( - <Description - info={info} - readMore={readMore} - setReadMore={setReadMore} - className="md:block hidden" - /> + {info && ( + <InfoChip info={info} color={color} className="hidden md:flex" /> + )} + {info ? ( + info?.description && ( + <Description + info={info} + readMore={readMore} + setReadMore={setReadMore} + className="md:block hidden" + /> + ) + ) : ( + <div className="w-full md:px-0 md:block hidden"> + <Skeleton className="h-[80px] w-[700px]" /> + </div> )} </div> </div> </div> <div className="hidden md:flex gap-5 items-center justify-start w-full"> - <button - type="button" - onClick={() => router.push(watchUrl)} - className={`${ - !watchUrl ? "opacity-30 pointer-events-none" : "" - } w-[180px] flex-center text-lg font-karla font-semibold gap-2 border-black border-opacity-10 text-black rounded-full py-1 px-4 bg-white hover:opacity-80`} - > - {isAnime ? ( - <PlayIcon className="w-5 h-5" /> - ) : ( - <BookOpenIcon className="w-5 h-5" /> - )} - {progress > 0 ? ( - statuses?.value === "COMPLETED" ? ( - isAnime ? ( - "Rewatch" + {info ? ( + <button + type="button" + onClick={() => router.push(watchUrl ?? "#")} + className={`${ + !watchUrl ? "opacity-30 pointer-events-none" : "" + } w-[180px] flex-center text-lg font-karla font-semibold gap-2 border-black border-opacity-10 text-black rounded-full py-1 px-4 bg-white hover:opacity-80`} + > + {isAnime ? ( + <PlayIcon className="w-5 h-5" /> + ) : ( + <BookOpenIcon className="w-5 h-5" /> + )} + {progress && progress > 0 ? ( + statuses?.value === "COMPLETED" ? ( + isAnime ? ( + "Rewatch" + ) : ( + "Reread" + ) + ) : !watchUrl && info?.nextAiringEpisode ? ( + <span> + {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "} + </span> ) : ( - "Reread" + "Continue" ) - ) : !watchUrl && info?.nextAiringEpisode ? ( - <span> - {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "} - </span> + ) : isAnime ? ( + "Watch Now" ) : ( - "Continue" - ) - ) : isAnime ? ( - "Watch Now" + "Read Now" + )} + </button> + ) : ( + <div className="h-10 w-[180px] bg-secondary flex-center text-lg font-karla font-semibold gap-2 border-black border-opacity-10 text-black rounded-full" /> + )} + <div className="flex gap-2"> + {info ? ( + <button + type="button" + className="flex-center group relative w-10 h-10 bg-secondary rounded-full" + onClick={() => handleOpen()} + > + <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> + Add to List + </span> + <PlusIcon className="w-5 h-5" /> + </button> ) : ( - "Read Now" + <div className="w-10 h-10 bg-secondary rounded-full" /> )} - </button> - <div className="flex gap-2"> + {info ? ( + <button + type="button" + className="flex-center group relative w-10 h-10 bg-secondary rounded-full" + onClick={handleShareClick} + > + <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> + Share {isAnime ? "Anime" : "Manga"} + </span> + <ShareIcon className="w-5 h-5" /> + </button> + ) : ( + <div className="w-10 h-10 bg-secondary rounded-full" /> + )} + {info ? ( + <a + target="_blank" + rel="noopener noreferrer" + href={`https://anilist.co/${info.type.toLowerCase()}/${info.id}`} + className="flex-center group relative w-10 h-10 bg-secondary rounded-full" + > + <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> + See on AniList + </span> + <Image + src="/svg/anilist-icon.svg" + alt="anilist_icon" + width={20} + height={20} + /> + </a> + ) : ( + <div className="w-10 h-10 bg-secondary rounded-full" /> + )} + </div> + </div> + + <div className="md:hidden flex gap-2 items-center justify-center w-[90%]"> + {info ? ( <button type="button" className="flex-center group relative w-10 h-10 bg-secondary rounded-full" @@ -133,6 +215,46 @@ export default function DetailTop({ </span> <PlusIcon className="w-5 h-5" /> </button> + ) : ( + <div className="w-10 h-10 bg-secondary rounded-full" /> + )} + {info ? ( + <button + type="button" + onClick={() => router.push(watchUrl ?? "#")} + className={`${ + !watchUrl ? "opacity-30 pointer-events-none" : "" + } flex items-center text-lg font-karla font-semibold gap-1 border-black border-opacity-10 text-black rounded-full py-2 px-4 bg-white`} + > + {isAnime ? ( + <PlayIcon className="w-5 h-5" /> + ) : ( + <BookOpenIcon className="w-5 h-5" /> + )} + {progress && progress > 0 ? ( + statuses?.value === "COMPLETED" ? ( + isAnime ? ( + "Rewatch" + ) : ( + "Reread" + ) + ) : !watchUrl && info?.nextAiringEpisode ? ( + <span> + {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "} + </span> + ) : ( + "Continue" + ) + ) : isAnime ? ( + "Watch Now" + ) : ( + "Read Now" + )} + </button> + ) : ( + <div className="h-10 w-32 bg-secondary flex-center text-lg font-karla font-semibold gap-2 border-black border-opacity-10 text-black rounded-full" /> + )} + {info ? ( <button type="button" className="flex-center group relative w-10 h-10 bg-secondary rounded-full" @@ -143,81 +265,12 @@ export default function DetailTop({ </span> <ShareIcon className="w-5 h-5" /> </button> - <a - target="_blank" - rel="noopener noreferrer" - href={`https://anilist.co/${info.type.toLowerCase()}/${info.id}`} - className="flex-center group relative w-10 h-10 bg-secondary rounded-full" - > - <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> - See on AniList - </span> - <Image - src="/svg/anilist-icon.svg" - alt="anilist_icon" - width={20} - height={20} - /> - </a> - </div> + ) : ( + <div className="w-10 h-10 bg-secondary rounded-full" /> + )} </div> - <div className="md:hidden flex gap-2 items-center justify-center w-[90%]"> - <button - type="button" - className="flex-center group relative w-10 h-10 bg-secondary rounded-full" - onClick={() => handleOpen()} - > - <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> - Add to List - </span> - <PlusIcon className="w-5 h-5" /> - </button> - <button - type="button" - onClick={() => router.push(watchUrl)} - className={`${ - !watchUrl ? "opacity-30 pointer-events-none" : "" - } flex items-center text-lg font-karla font-semibold gap-1 border-black border-opacity-10 text-black rounded-full py-2 px-4 bg-white`} - > - {isAnime ? ( - <PlayIcon className="w-5 h-5" /> - ) : ( - <BookOpenIcon className="w-5 h-5" /> - )} - {progress > 0 ? ( - statuses?.value === "COMPLETED" ? ( - isAnime ? ( - "Rewatch" - ) : ( - "Reread" - ) - ) : !watchUrl && info?.nextAiringEpisode ? ( - <span> - {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "} - </span> - ) : ( - "Continue" - ) - ) : isAnime ? ( - "Watch Now" - ) : ( - "Read Now" - )} - </button> - <button - type="button" - className="flex-center group relative w-10 h-10 bg-secondary rounded-full" - onClick={handleShareClick} - > - <span className="absolute pointer-events-none z-40 opacity-0 -translate-y-8 group-hover:-translate-y-10 group-hover:opacity-100 font-karla shadow-tersier shadow-md whitespace-nowrap bg-secondary px-2 py-1 rounded transition-all duration-200 ease-out"> - Share {isAnime ? "Anime" : "Manga"} - </span> - <ShareIcon className="w-5 h-5" /> - </button> - </div> - - {info.nextAiringEpisode?.timeUntilAiring && ( + {info && info.nextAiringEpisode?.timeUntilAiring && ( <p className="md:hidden"> Episode {info.nextAiringEpisode.episode} in{" "} <span className="font-bold"> @@ -226,7 +279,7 @@ export default function DetailTop({ </p> )} - {info?.description && ( + {info && info?.description && ( <Description info={info} readMore={readMore} @@ -235,13 +288,15 @@ export default function DetailTop({ /> )} - <InfoChip - info={info} - color={color} - className={`${readMore ? "flex" : "hidden"} md:hidden`} - /> + {info && ( + <InfoChip + info={info} + color={color} + className={`${readMore ? "flex" : "hidden"} md:hidden`} + /> + )} - {info?.relations?.edges?.length > 0 && ( + {info && info?.relations?.edges?.length > 0 && ( <div className="w-screen md:w-full"> <div className="flex justify-between items-center p-3 md:p-0"> {info?.relations?.edges?.length > 0 && ( @@ -288,7 +343,7 @@ export default function DetailTop({ <div className="w-[90px] bg-image rounded-l-md shrink-0"> <Image src={rel.coverImage.extraLarge} - alt={rel.id} + alt={rel.id.toString()} height={500} width={500} className="object-cover h-full w-full shrink-0 rounded-l-md" @@ -314,7 +369,7 @@ export default function DetailTop({ ); } -function getMonth(month) { +function getMonth(month: number | undefined) { if (!month) return ""; const formattedMonth = new Date(0, month).toLocaleString("default", { month: "long", |