aboutsummaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorArtrix <[email protected]>2024-01-05 05:12:52 -0800
committerGitHub <[email protected]>2024-01-05 20:12:52 +0700
commit553fe1c71082b040e9f9667ad3e99acdb33990b2 (patch)
tree0c770c406c8ff934ce34d8b10dbae948a554a619 /components
parentmigrate to typescript (diff)
downloadmoopa-553fe1c71082b040e9f9667ad3e99acdb33990b2.tar.xz
moopa-553fe1c71082b040e9f9667ad3e99acdb33990b2.zip
feat: Implement a way to review/rate anime (#108)
* Make details cover lead back to anime page * Make 'markProgress' use object instead of param list * Import Link * Implement Rate modal * Pass session into useAniList Co-authored-by: Factiven <[email protected]> * Reimplement using markComplete & add toast for failure * redefined ratemodal * fix: home page client error * update version --------- Co-authored-by: Factiven <[email protected]>
Diffstat (limited to 'components')
-rw-r--r--components/anime/mobile/topSection.tsx2
-rw-r--r--components/home/schedule.js4
-rw-r--r--components/listEditor.tsx2
-rw-r--r--components/manga/panels/firstPanel.js18
-rw-r--r--components/manga/panels/secondPanel.js26
-rw-r--r--components/manga/panels/thirdPanel.js16
-rw-r--r--components/manga/rightBar.js2
-rw-r--r--components/shared/RateModal.tsx135
-rw-r--r--components/shared/changelogs.tsx78
-rw-r--r--components/watch/new-player/components/layouts/video-layout.tsx23
-rw-r--r--components/watch/new-player/player.tsx25
-rw-r--r--components/watch/primary/details.tsx21
12 files changed, 261 insertions, 91 deletions
diff --git a/components/anime/mobile/topSection.tsx b/components/anime/mobile/topSection.tsx
index 2d28c66..b5d4f62 100644
--- a/components/anime/mobile/topSection.tsx
+++ b/components/anime/mobile/topSection.tsx
@@ -65,7 +65,7 @@ export default function DetailTop({
<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">
{info ? (
- <img
+ <Image
src={
info?.coverImage?.extraLarge?.toString() ??
info?.coverImage?.toString()
diff --git a/components/home/schedule.js b/components/home/schedule.js
index 19260c2..df61eba 100644
--- a/components/home/schedule.js
+++ b/components/home/schedule.js
@@ -13,11 +13,13 @@ export default function Schedule({ data, scheduleData, anime, update }) {
"Schedule";
currentDay = currentDay.replace("Schedule", "");
- const { day, hours, minutes, seconds } = useCountdown(
+ const { countdown } = useCountdown(
anime[0]?.airingSchedule.nodes[0]?.airingAt * 1000 || Date.now(),
update
);
+ const {days: day, hours, minutes, seconds} = countdown;
+
const [currentPage, setCurrentPage] = useState(0);
const [days, setDay] = useState();
diff --git a/components/listEditor.tsx b/components/listEditor.tsx
index 2e180a1..045e254 100644
--- a/components/listEditor.tsx
+++ b/components/listEditor.tsx
@@ -2,7 +2,7 @@ import { useState, FormEvent } from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import { toast } from "sonner";
-import { AniListInfoTypes } from "@/types/info/AnilistInfoTypes";
+import { AniListInfoTypes } from "types/info/AnilistInfoTypes";
interface ListEditorProps {
animeId: number;
diff --git a/components/manga/panels/firstPanel.js b/components/manga/panels/firstPanel.js
index 8470fd0..0ceb2fb 100644
--- a/components/manga/panels/firstPanel.js
+++ b/components/manga/panels/firstPanel.js
@@ -66,13 +66,13 @@ export default function FirstPanel({
if (session) {
if (aniId?.length > 6) return;
const currentChapter = chapter.chapters?.find(
- (x) => x.id === currentId
+ (x) => x.id === currentId,
);
if (currentChapter) {
const chapterNumber =
currentChapter.number ??
chapter.chapters.indexOf(currentChapter) + 1;
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
console.log("marking progress");
}
}
@@ -142,14 +142,14 @@ export default function FirstPanel({
>
<Image
src={`https://aoi.moopa.live/utils/image-proxy?url=${encodeURIComponent(
- i.url
+ i.url,
)}${
i?.headers?.Referer
? `&headers=${encodeURIComponent(
- JSON.stringify(i?.headers)
+ JSON.stringify(i?.headers),
)}`
: `&headers=${encodeURIComponent(
- JSON.stringify(getHeaders(chapter.providerId))
+ JSON.stringify(getHeaders(chapter.providerId)),
)}`
}`}
alt={index}
@@ -213,10 +213,10 @@ export default function FirstPanel({
`/en/manga/read/${
chapter.providerId
}?id=${mangadexId}&chapterId=${encodeURIComponent(
- prevChapter?.id
+ prevChapter?.id,
)}${aniId?.length > 6 ? "" : `&anilist=${aniId}`}&num=${
prevChapter?.number
- }`
+ }`,
)
}
>
@@ -234,10 +234,10 @@ export default function FirstPanel({
`/en/manga/read/${
chapter.providerId
}?id=${mangadexId}&chapterId=${encodeURIComponent(
- nextChapter?.id
+ nextChapter?.id,
)}${aniId?.length > 6 ? "" : `&anilist=${aniId}`}&num=${
nextChapter?.number
- }`
+ }`,
)
}
>
diff --git a/components/manga/panels/secondPanel.js b/components/manga/panels/secondPanel.js
index 23a9da0..6ebc292 100644
--- a/components/manga/panels/secondPanel.js
+++ b/components/manga/panels/secondPanel.js
@@ -69,12 +69,12 @@ export default function SecondPanel({
if (index + 1 >= image.length - 4 && !hasRun.current) {
const current = chapterData.chapters?.find(
- (x) => x.id === currentChapter.id
+ (x) => x.id === currentChapter.id,
);
const chapterNumber = chapterData.chapters.indexOf(current) + 1;
if (chapterNumber) {
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
}
hasRun.current = true;
}
@@ -98,15 +98,15 @@ export default function SecondPanel({
if (index + 1 >= image.length - 4 && !hasRun.current) {
console.log("marking progress");
const current = chapterData.chapters?.find(
- (x) => x.id === currentChapter.id
+ (x) => x.id === currentChapter.id,
);
const chapterNumber = chapterData.chapters.indexOf(current) + 1;
if (chapterNumber) {
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
}
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
hasRun.current = true;
}
};
@@ -137,16 +137,16 @@ export default function SecondPanel({
height={500}
className="w-1/2 h-screen object-contain"
src={`https://aoi.moopa.live/utils/image-proxy?url=${encodeURIComponent(
- image[image.length - index - 2]?.url
+ image[image.length - index - 2]?.url,
)}${
image[image.length - index - 2]?.headers?.Referer
? `&headers=${encodeURIComponent(
JSON.stringify(
- image[image.length - index - 2]?.headers
- )
+ image[image.length - index - 2]?.headers,
+ ),
)}`
: `&headers=${encodeURIComponent(
- JSON.stringify(getHeaders(providerId))
+ JSON.stringify(getHeaders(providerId)),
)}`
}`}
alt="Manga Page"
@@ -158,14 +158,16 @@ export default function SecondPanel({
height={500}
className="w-1/2 h-screen object-contain"
src={`https://aoi.moopa.live/utils/image-proxy?url=${encodeURIComponent(
- image[image.length - index - 1]?.url
+ image[image.length - index - 1]?.url,
)}${
image[image.length - index - 1]?.headers?.Referer
? `&headers=${encodeURIComponent(
- JSON.stringify(image[image.length - index - 1]?.headers)
+ JSON.stringify(
+ image[image.length - index - 1]?.headers,
+ ),
)}`
: `&headers=${encodeURIComponent(
- JSON.stringify(getHeaders(providerId))
+ JSON.stringify(getHeaders(providerId)),
)}`
}`}
alt="Manga Page"
diff --git a/components/manga/panels/thirdPanel.js b/components/manga/panels/thirdPanel.js
index 77bb132..7c43f6e 100644
--- a/components/manga/panels/thirdPanel.js
+++ b/components/manga/panels/thirdPanel.js
@@ -66,12 +66,12 @@ export default function ThirdPanel({
}
if (index + 1 >= image.length - 2 && !hasRun.current) {
const current = chapterData.chapters?.find(
- (x) => x.id === currentChapter.id
+ (x) => x.id === currentChapter.id,
);
const chapterNumber = chapterData.chapters.indexOf(current) + 1;
if (chapterNumber) {
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
}
hasRun.current = true;
}
@@ -94,12 +94,12 @@ export default function ThirdPanel({
}
if (index + 1 >= image.length - 2 && !hasRun.current) {
const current = chapterData.chapters?.find(
- (x) => x.id === currentChapter.id
+ (x) => x.id === currentChapter.id,
);
const chapterNumber = chapterData.chapters.indexOf(current) + 1;
if (chapterNumber) {
- markProgress(aniId, chapterNumber);
+ markProgress({ mediaId: aniId, progress: chapterNumber });
}
hasRun.current = true;
@@ -128,14 +128,16 @@ export default function ThirdPanel({
className="w-full h-screen object-contain"
onClick={() => setMobileVisible(!mobileVisible)}
src={`https://aoi.moopa.live/utils/image-proxy?url=${encodeURIComponent(
- image[image.length - index - 1]?.url
+ image[image.length - index - 1]?.url,
)}${
image[image.length - index - 1]?.headers?.Referer
? `&headers=${encodeURIComponent(
- JSON.stringify(image[image.length - index - 1]?.headers)
+ JSON.stringify(
+ image[image.length - index - 1]?.headers,
+ ),
)}`
: `&headers=${encodeURIComponent(
- JSON.stringify(getHeaders(providerId))
+ JSON.stringify(getHeaders(providerId)),
)}`
}`}
alt="Manga Page"
diff --git a/components/manga/rightBar.js b/components/manga/rightBar.js
index 9672fc4..3da04d9 100644
--- a/components/manga/rightBar.js
+++ b/components/manga/rightBar.js
@@ -43,7 +43,7 @@ export default function RightBar({
parsedProgress === parseInt(parsedProgress) &&
parsedVolumeProgress === parseInt(parsedVolumeProgress)
) {
- markProgress(id, progress, status, volumeProgress);
+ markProgress({ mediaId: id, progress, stats: status, volumeProgress });
hasRun.current = true;
} else {
toast.error("Progress must be a whole number!");
diff --git a/components/shared/RateModal.tsx b/components/shared/RateModal.tsx
new file mode 100644
index 0000000..6231eaf
--- /dev/null
+++ b/components/shared/RateModal.tsx
@@ -0,0 +1,135 @@
+import { useAniList } from "@/lib/anilist/useAnilist";
+import { useWatchProvider } from "@/lib/context/watchPageProvider";
+import { useState } from "react";
+import { toast } from "sonner";
+
+type Props = {
+ toggle: boolean;
+ position: "top" | "bottom";
+ setToggle: (prev: any) => void;
+ session: any;
+};
+
+export default function RateModal({
+ toggle,
+ position,
+ setToggle,
+ session,
+}: Props) {
+ const [startRate, setStartRate] = useState(false);
+ const { markComplete } = useAniList(session);
+
+ const { dataMedia } = useWatchProvider();
+
+ async function handleSubmit(event: any) {
+ event.preventDefault();
+ const data = new FormData(event.target);
+ const rating = data.get("rating");
+ const notes = data.get("notes");
+ try {
+ await markComplete(dataMedia?.id, { notes, scoreRaw: rating });
+ toast.success("Successfully rated!");
+ setToggle((prev: any) => {
+ return {
+ ...prev,
+ isOpen: false,
+ };
+ });
+ } catch (error) {
+ toast.error("Failed to rate!");
+ }
+ }
+
+ function handleClose() {
+ setToggle((prev: any) => {
+ return {
+ ...prev,
+ isOpen: false,
+ };
+ });
+ }
+ return (
+ <>
+ <div
+ className={`w-full h-[20dvh] fixed bg-gradient-to-${
+ position === "top"
+ ? `b top-0 from-black/20`
+ : "t -bottom-5 from-black/40"
+ } to-transparent z-10 transition-all duration-200 ease-in-out ${
+ toggle ? "" : "opacity-0 pointer-events-none"
+ }`}
+ />
+ <div
+ style={{ width: startRate ? "300px" : "240px" }}
+ className={`${
+ position === "top"
+ ? toggle
+ ? `top-5`
+ : `-top-48`
+ : toggle
+ ? `bottom-10`
+ : `-bottom-48`
+ } fixed text-white font-semibold z-50 font-karla transition-all duration-300 ease-in-out left-1/2 transform -translate-x-1/2 bg-secondary p-3 rounded flex flex-col justify-center items-center gap-4`}
+ >
+ <p className="text-lg">What do you think?</p>
+ <div
+ className={`flex gap-2 font-medium text-center transition-all duration-200 ${
+ startRate
+ ? "scale-50 hidden pointer-events-none"
+ : "scale-100 opacity-100"
+ }`}
+ >
+ <button
+ onClick={() => setStartRate(true)}
+ className="w-[100px] py-1 bg-action/10 rounded text-action"
+ >
+ Rate Now
+ </button>
+ <button
+ onClick={handleClose}
+ className="w-[100px] py-1 border border-opacity-0 hover:border-opacity-10 rounded border-white"
+ >
+ Close
+ </button>
+ </div>
+ {startRate && (
+ <form
+ onSubmit={handleSubmit}
+ className="flex flex-col items-center gap-3 w-full"
+ >
+ <input
+ type="number"
+ min={1}
+ max={100}
+ required
+ name="rating"
+ placeholder="rate from 1-100"
+ className="appearance-none w-full text-white placeholder-zinc-400 bg-white/10 py-1 px-2 rounded text-sm"
+ />
+ <input
+ type="text"
+ name="notes"
+ placeholder="notes"
+ className="appearance-none w-full text-white placeholder-zinc-400 bg-white/10 py-1 px-2 rounded text-sm"
+ />
+ <div className="flex gap-2 w-full">
+ <button
+ type="submit"
+ className="w-full py-1 bg-action/10 hover:bg-action/20 rounded text-action"
+ >
+ Submit
+ </button>
+ <button
+ type="button"
+ onClick={handleClose}
+ className="w-full py-1 rounded hover:bg-white/20"
+ >
+ Cancel
+ </button>
+ </div>
+ </form>
+ )}
+ </div>
+ </>
+ );
+}
diff --git a/components/shared/changelogs.tsx b/components/shared/changelogs.tsx
index a7b0436..208b1ff 100644
--- a/components/shared/changelogs.tsx
+++ b/components/shared/changelogs.tsx
@@ -3,34 +3,46 @@ import Link from "next/link";
import { Fragment, useEffect, useRef, useState } from "react";
const web = {
- version: "v4.3.1",
+ version: "v4.4.0",
};
const logs = [
{
version: "v4.3.1",
- pre: true,
+ pre: false,
notes: null,
highlights: true,
changes: [
- "Fix: Auto Next Episode forcing to play sub even if dub is selected",
- "Fix: Episode metadata not showing after switching to dub",
- "Fix: Profile picture weirdly cropped",
- "Fix: Weird padding on the navbar in profile page",
- ],
- },
- {
- version: "v4.3.0",
- pre: true,
- notes: null,
- highlights: false,
- changes: [
- "Added changelogs section",
- "Added recommendations based on user lists",
- "New Player!",
- "And other minor bug fixes!",
+ "Added rate modal when user finished watching the whole series",
+ "Fix: only half of the episodes has episodes thumbnail",
+ "Fix: pressing back button in anime info page redirects user to the wrong page",
+ "Progressively migrate codebase to typescript",
],
},
+ // {
+ // version: "v4.3.1",
+ // pre: true,
+ // notes: null,
+ // highlights: false,
+ // changes: [
+ // "Fix: Auto Next Episode forcing to play sub even if dub is selected",
+ // "Fix: Episode metadata not showing after switching to dub",
+ // "Fix: Profile picture weirdly cropped",
+ // "Fix: Weird padding on the navbar in profile page",
+ // ],
+ // },
+ // {
+ // version: "v4.3.0",
+ // pre: true,
+ // notes: null,
+ // highlights: false,
+ // changes: [
+ // "Added changelogs section",
+ // "Added recommendations based on user lists",
+ // "New Player!",
+ // "And other minor bug fixes!",
+ // ],
+ // },
];
export default function ChangeLogs() {
@@ -146,11 +158,11 @@ export default function ChangeLogs() {
Hi! Welcome to the new changelogs section. Here you can
see a lists of the latest changes and updates to the site.
</p>
- <p className="inline-block text-sm italic my-2 text-gray-400">
+ {/* <p className="inline-block text-sm italic my-2 text-gray-400">
*This update is still in it's pre-release state, please
expect to see some bugs. If you find any, please report
them.
- </p>
+ </p> */}
</div>
{logs.map((x) => (
@@ -166,32 +178,6 @@ export default function ChangeLogs() {
</ChangelogsVersions>
))}
- {/* <div className="my-2 flex items-center justify-evenly">
- <div className="w-full h-[1px] bg-gradient-to-r from-white/5 to-white/40" />
- <p className="relative flex flex-1 whitespace-nowrap font-bold mx-2 font-inter">
- v4.3.0
- <span className="flex text-xs font-light font-roboto ml-1 italic">
- pre
- </span>
- </p>
- <div className="w-full h-[1px] bg-gradient-to-l from-white/5 to-white/40" />
- </div>
-
- <div className="flex flex-col gap-2 text-sm text-gray-200">
- <div>
- <p className="inline-block italic mb-2 text-gray-400">
- *This update is still in it's pre-release state, please
- expect to see some bugs. If you find any, please report
- them.
- </p>
-
- <p>- Added changelogs section</p>
- <p>- Added recommendations based on user lists</p>
- <p>- New Player!</p>
- <p>- And other minor bug fixes!</p>
- </div>
- </div> */}
-
<div className="mt-2 text-gray-400 text-sm">
<p>
see more changelogs{" "}
diff --git a/components/watch/new-player/components/layouts/video-layout.tsx b/components/watch/new-player/components/layouts/video-layout.tsx
index fa1f6c3..93e4629 100644
--- a/components/watch/new-player/components/layouts/video-layout.tsx
+++ b/components/watch/new-player/components/layouts/video-layout.tsx
@@ -17,13 +17,14 @@ import { Title } from "../title";
import { ChapterTitleComponent } from "../chapter-title";
import { useWatchProvider } from "@/lib/context/watchPageProvider";
import { Navigation } from "../../player";
-import BufferingIndicator from "../bufferingIndicator";
import { useEffect, useState } from "react";
+import RateModal from "@/components/shared/RateModal";
export interface VideoLayoutProps {
thumbnails?: string;
navigation?: Navigation;
host?: boolean;
+ session?: any;
}
function isMobileDevice() {
@@ -40,22 +41,40 @@ export function VideoLayout({
thumbnails,
navigation,
host = true,
+ session,
}: VideoLayoutProps) {
const [isMobile, setIsMobile] = useState(false);
- const { track } = useWatchProvider();
+ const { track, setRatingModalState, ratingModalState } = useWatchProvider();
const isFullscreen = useMediaState("fullscreen");
useEffect(() => {
setIsMobile(isMobileDevice());
}, []);
+ useEffect(() => {
+ setRatingModalState((prev: any) => {
+ return {
+ ...prev,
+ isFullscreen: isFullscreen,
+ };
+ });
+ }, [isFullscreen]);
+
return (
<>
<Gestures host={host} />
<Captions
className={`${captionStyles.captions} media-preview:opacity-0 media-controls:bottom-[85px] media-captions:opacity-100 absolute inset-0 bottom-2 z-10 select-none break-words opacity-0 transition-[opacity,bottom] duration-300`}
/>
+ {ratingModalState.isFullscreen && (
+ <RateModal
+ toggle={ratingModalState.isOpen}
+ setToggle={setRatingModalState}
+ position="top"
+ session={session}
+ />
+ )}
<Controls.Root
className={`${styles.controls} media-paused:bg-black/10 duration-200 media-controls:opacity-100 absolute inset-0 z-10 flex h-full w-full flex-col bg-gradient-to-t from-black/30 via-transparent to-black/30 opacity-0 transition-opacity`}
>
diff --git a/components/watch/new-player/player.tsx b/components/watch/new-player/player.tsx
index b98ff79..f2d11f7 100644
--- a/components/watch/new-player/player.tsx
+++ b/components/watch/new-player/player.tsx
@@ -12,7 +12,6 @@ import {
type MediaPlayerInstance,
Track,
MediaTimeUpdateEventDetail,
- MediaTimeUpdateEvent,
} from "@vidstack/react";
import { VideoLayout } from "./components/layouts/video-layout";
import { useWatchProvider } from "@/lib/context/watchPageProvider";
@@ -98,6 +97,7 @@ export default function VidStack({
playerState,
dataMedia,
autoNext,
+ setRatingModalState,
} = useWatchProvider();
const { qualities, duration } = useMediaStore(player);
@@ -379,7 +379,20 @@ export default function VidStack({
mark = 1;
setMarked(1);
console.log("marking progress");
- markProgress(dataMedia.id, navigation.playing.number);
+ // @ts-ignore Fix when convert useAnilist to typescript
+ markProgress({
+ mediaId: dataMedia.id,
+ progress: navigation.playing.number,
+ });
+
+ if (dataMedia.episodes === +navigation.playing?.number) {
+ setRatingModalState((prev: any) => {
+ return {
+ ...prev,
+ isOpen: true,
+ };
+ });
+ }
}
}
}
@@ -424,7 +437,7 @@ export default function VidStack({
return (
<MediaPlayer
key={id}
- className={`${style.player} player`}
+ className={`${style.player} player relative`}
title={
navigation?.playing?.title ||
`Episode ${navigation?.playing?.number}` ||
@@ -454,7 +467,11 @@ export default function VidStack({
<Track key={chapters} src={chapters} kind="chapters" default={true} />
)}
</MediaProvider>
- <VideoLayout thumbnails={track?.thumbnails} navigation={navigation} />
+ <VideoLayout
+ thumbnails={track?.thumbnails}
+ navigation={navigation}
+ session={sessions}
+ />
</MediaPlayer>
);
}
diff --git a/components/watch/primary/details.tsx b/components/watch/primary/details.tsx
index f20f8cf..dd739f2 100644
--- a/components/watch/primary/details.tsx
+++ b/components/watch/primary/details.tsx
@@ -4,6 +4,8 @@ import Skeleton from "react-loading-skeleton";
import DisqusComments from "../../disqus";
import { AniListInfoTypes } from "types/info/AnilistInfoTypes";
import { SessionTypes } from "pages/en";
+import Link from "next/link";
+import Image from "next/image";
type DetailsProps = {
info: AniListInfoTypes;
@@ -61,13 +63,18 @@ export default function Details({
<div className="pb-4 h-full flex">
<div className="aspect-[9/13] h-[240px]">
{info ? (
- <img
- src={info.coverImage.extraLarge}
- alt="Anime Cover"
- width={1000}
- height={1000}
- className="object-cover aspect-[9/13] h-[240px] rounded-md"
- />
+ <Link
+ className="hover:scale-105 hover:shadow-lg duration-300 ease-out"
+ href={`/en/anime/${id}`}
+ >
+ <Image
+ src={info.coverImage.extraLarge}
+ alt="Anime Cover"
+ width={1000}
+ height={1000}
+ className="object-cover aspect-[9/13] h-[240px] rounded-md"
+ />
+ </Link>
) : (
<Skeleton height={240} />
)}