aboutsummaryrefslogtreecommitdiff
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
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]>
-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
-rw-r--r--lib/anilist/useAnilist.js53
-rw-r--r--lib/context/watchPageProvider.js7
-rw-r--r--package.json2
-rw-r--r--pages/api/v2/episode/[id].tsx16
-rw-r--r--pages/api/v2/etc/recent/[page].tsx2
-rw-r--r--pages/en/anime/watch/[...info].js27
-rw-r--r--pages/en/index.tsx92
-rw-r--r--pages/en/schedule/index.tsx10
-rw-r--r--release.md7
-rw-r--r--styles/globals.css11
-rw-r--r--tailwind.config.js (renamed from tailwind.config.cjs)0
-rw-r--r--tsconfig.json3
24 files changed, 395 insertions, 187 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} />
)}
diff --git a/lib/anilist/useAnilist.js b/lib/anilist/useAnilist.js
index 323dd29..36c1496 100644
--- a/lib/anilist/useAnilist.js
+++ b/lib/anilist/useAnilist.js
@@ -4,15 +4,21 @@ export const useAniList = (session) => {
const accessToken = session?.user?.token;
const fetchGraphQL = async (query, variables) => {
- const response = await fetch("https://graphql.anilist.co/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
- },
- body: JSON.stringify({ query, variables }),
- });
- return response.json();
+ try {
+ const response = await fetch("https://graphql.anilist.co/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ ...(accessToken && { Authorization: `Bearer ${accessToken}` }),
+ },
+ body: JSON.stringify({ query, variables }),
+ });
+ return response.json();
+ } catch (error) {
+ toast.error("An error occurred, please try again later", {
+ position: "bottom-right",
+ });
+ }
};
const quickSearch = async ({ search, type, isAdult = false }) => {
@@ -103,18 +109,22 @@ export const useAniList = (session) => {
return data;
};
- const markComplete = async (mediaId) => {
+ const markComplete = async (mediaId, { notes, scoreRaw }) => {
if (!accessToken) return;
const completeQuery = `
- mutation($mediaId: Int) {
- SaveMediaListEntry(mediaId: $mediaId, status: COMPLETED) {
+ mutation($mediaId: Int, $notes: String, $scoreRaw: Int) {
+ SaveMediaListEntry(mediaId: $mediaId, status: COMPLETED, scoreRaw: $scoreRaw, notes: $notes) {
id
mediaId
status
}
}
`;
- const data = await fetchGraphQL(completeQuery, { mediaId });
+ const data = await fetchGraphQL(completeQuery, {
+ mediaId,
+ scoreRaw,
+ notes,
+ });
console.log({ Complete: data });
};
@@ -162,11 +172,18 @@ export const useAniList = (session) => {
return data;
};
- const markProgress = async (mediaId, progress, stats, volumeProgress) => {
+ const markProgress = async ({
+ mediaId,
+ progress,
+ stats,
+ volumeProgress,
+ scoreRaw = 0,
+ notes,
+ }) => {
if (!accessToken) return;
const progressWatched = `
- mutation($mediaId: Int, $progress: Int, $status: MediaListStatus, $progressVolumes: Int, $lists: [String], $repeat: Int) {
- SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status, progressVolumes: $progressVolumes, customLists: $lists, repeat: $repeat) {
+ mutation($mediaId: Int, $progress: Int, $status: MediaListStatus, $progressVolumes: Int, $lists: [String], $repeat: Int, $scoreRaw: Int, $notes: String) {
+ SaveMediaListEntry(mediaId: $mediaId, progress: $progress, status: $status, progressVolumes: $progressVolumes, customLists: $lists, repeat: $repeat, scoreRaw: $scoreRaw, notes: $notes) {
id
mediaId
progress
@@ -214,6 +231,8 @@ export const useAniList = (session) => {
status,
progressVolumes: volumeProgress,
lists,
+ scoreRaw,
+ notes,
};
if (videoEpisode === mediaEpisode) {
@@ -235,6 +254,8 @@ export const useAniList = (session) => {
progress,
status: stats,
progressVolumes: volumeProgress,
+ scoreRaw,
+ notes,
};
await fetchGraphQL(progressWatched, variables);
diff --git a/lib/context/watchPageProvider.js b/lib/context/watchPageProvider.js
index c305710..b7d78b3 100644
--- a/lib/context/watchPageProvider.js
+++ b/lib/context/watchPageProvider.js
@@ -16,6 +16,11 @@ export const WatchPageProvider = ({ children }) => {
const [userData, setUserData] = useState(null);
const [dataMedia, setDataMedia] = useState(null);
+ const [ratingModalState, setRatingModalState] = useState({
+ isOpen: false,
+ isFullscreen: false,
+ });
+
const [track, setTrack] = useState(null);
return (
@@ -39,6 +44,8 @@ export const WatchPageProvider = ({ children }) => {
setDataMedia,
autoNext,
setAutoNext,
+ ratingModalState,
+ setRatingModalState,
}}
>
{children}
diff --git a/package.json b/package.json
index 3ea7c4c..6f77d24 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "moopa",
- "version": "4.3.1",
+ "version": "4.4.0",
"private": true,
"founder": "Factiven",
"scripts": {
diff --git a/pages/api/v2/episode/[id].tsx b/pages/api/v2/episode/[id].tsx
index b646126..ddf5635 100644
--- a/pages/api/v2/episode/[id].tsx
+++ b/pages/api/v2/episode/[id].tsx
@@ -139,10 +139,6 @@ async function fetchAnify(id?: string) {
async function fetchCoverImage(id: string, available = false) {
try {
- if (!process.env.API_KEY) {
- return [];
- }
-
if (available) {
return null;
}
@@ -171,10 +167,10 @@ export default async function handler(
) {
const { id, releasing = "false", dub = false, refresh = null } = req.query;
- // if releasing is true then cache for 1 hour, if it false cache for 1 month;
+ // if releasing is true then cache for 3 hour, if it false cache for 1 month;
let cacheTime = null;
if (releasing === "true") {
- cacheTime = 60 * 60; // 1 hour
+ cacheTime = 60 * 60 * 3; // 3 hours
} else if (releasing === "false") {
cacheTime = 60 * 60 * 24 * 30; // 1 month
}
@@ -210,7 +206,7 @@ export default async function handler(
meta = null;
}
- if (refresh) {
+ if (refresh !== null) {
await redis.del(`episode:${id}`);
} else {
cached = await redis.get(`episode:${id}`);
@@ -262,12 +258,6 @@ export default async function handler(
fetchCoverImage(id, meta),
]);
- // const hasImage = consumet.map((i) =>
- // i.episodes?.sub?.some(
- // (e) => e.img !== null || !e.img.includes("https://s4.anilist.co/")
- // )
- // );
-
let subDub = "sub";
if (dub) {
subDub = "dub";
diff --git a/pages/api/v2/etc/recent/[page].tsx b/pages/api/v2/etc/recent/[page].tsx
index e49591c..4e3bc98 100644
--- a/pages/api/v2/etc/recent/[page].tsx
+++ b/pages/api/v2/etc/recent/[page].tsx
@@ -1,5 +1,5 @@
import { rateLimitStrict, redis } from "@/lib/redis";
-import { AnifyRecentEpisode } from "@/utils/types";
+import { AnifyRecentEpisode } from "types";
import axios from "axios";
import { NextApiRequest, NextApiResponse } from "next";
diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js
index dc1f412..259ebee 100644
--- a/pages/en/anime/watch/[...info].js
+++ b/pages/en/anime/watch/[...info].js
@@ -5,6 +5,7 @@ import EpisodeLists from "@/components/watch/secondary/episodeLists";
import { getServerSession } from "next-auth";
import { useWatchProvider } from "@/lib/context/watchPageProvider";
import { authOptions } from "../../../api/auth/[...nextauth]";
+import { useAniList } from "@/lib/anilist/useAnilist";
import { createList, createUser, getEpisode } from "@/prisma/user";
import Link from "next/link";
import MobileNav from "@/components/shared/MobileNav";
@@ -18,6 +19,7 @@ import Head from "next/head";
import VidStack from "@/components/watch/new-player/player";
import { useRouter } from "next/router";
import { Spinner } from "@vidstack/react";
+import RateModal from "@/components/shared/RateModal";
export async function getServerSideProps(context) {
let userData = null;
@@ -32,11 +34,11 @@ export async function getServerSideProps(context) {
}
let proxy;
- proxy = process.env.PROXY_URI;
+ proxy = process.env.PROXY_URI || null;
if (proxy && proxy.endsWith("/")) {
proxy = proxy.slice(0, -1);
}
- const disqus = process.env.DISQUS_SHORTNAME;
+ const disqus = process.env.DISQUS_SHORTNAME || null;
const [aniId, provider] = query?.info;
const watchId = query?.id;
@@ -149,7 +151,8 @@ export default function Watch({
const [open, setOpen] = useState(false);
const [isOpen, setIsOpen] = useState(false);
- const { setAutoNext } = useWatchProvider();
+ const { setAutoNext, ratingModalState, setRatingModalState } =
+ useWatchProvider();
const [onList, setOnList] = useState(false);
@@ -494,6 +497,14 @@ export default function Watch({
</Modal>
<BugReportForm isOpen={isOpen} setIsOpen={setIsOpen} />
<main className="w-screen h-full">
+ {!ratingModalState.isFullscreen && (
+ <RateModal
+ toggle={ratingModalState.isOpen}
+ setToggle={setRatingModalState}
+ position="bottom"
+ session={sessions}
+ />
+ )}
<Navbar
scrollP={20}
withNav={true}
@@ -614,11 +625,6 @@ export default function Watch({
id="secondary"
className={`relative ${theaterMode ? "pt-5" : "pt-4 lg:pt-0"}`}
>
- {/* <div className="w-full h-[150px] text-black p-3">
- <span className="bg-white w-full h-full flex-center">
- ad banner
- </span>
- </div> */}
<EpisodeLists
info={info}
session={sessions}
@@ -641,10 +647,7 @@ export default function Watch({
function SpinLoader() {
return (
<div className="pointer-events-none absolute inset-0 z-50 flex h-full w-full items-center justify-center">
- <Spinner.Root
- className="text-white animate-spin opacity-100"
- size={84}
- >
+ <Spinner.Root className="text-white animate-spin opacity-100" size={84}>
<Spinner.Track className="opacity-25" width={8} />
<Spinner.TrackFill className="opacity-75" width={8} />
</Spinner.Root>
diff --git a/pages/en/index.tsx b/pages/en/index.tsx
index 4141015..faead42 100644
--- a/pages/en/index.tsx
+++ b/pages/en/index.tsx
@@ -19,6 +19,7 @@ import { getGreetings } from "@/utils/getGreetings";
import { redis } from "@/lib/redis";
import { Navbar } from "@/components/shared/NavBar";
import UserRecommendation from "@/components/home/recommendation";
+import { useRouter } from "next/router";
export async function getServerSideProps() {
let cachedData;
@@ -28,7 +29,7 @@ export async function getServerSideProps() {
}
if (cachedData) {
- const { genre, detail, populars } = JSON.parse(cachedData);
+ const { genre, detail, populars, firstTrend } = JSON.parse(cachedData);
const upComing = await getUpcomingAnime();
return {
props: {
@@ -36,6 +37,7 @@ export async function getServerSideProps() {
detail,
populars,
upComing,
+ firstTrend,
},
};
} else {
@@ -56,6 +58,7 @@ export async function getServerSideProps() {
genre: genreDetail.props,
detail: trendingDetail.props,
populars: popularDetail.props,
+ firstTrend: trendingDetail.props.data[0],
}), // set cache for 2 hours
"EX",
60 * 60 * 2
@@ -70,6 +73,7 @@ export async function getServerSideProps() {
detail: trendingDetail.props,
populars: popularDetail.props,
upComing,
+ firstTrend: trendingDetail.props.data[0],
},
};
}
@@ -80,6 +84,7 @@ type HomeProps = {
detail: any;
populars: any;
upComing: any;
+ firstTrend: any;
};
export interface SessionTypes {
@@ -106,7 +111,12 @@ interface Image {
medium: string;
}
-export default function Home({ detail, populars, upComing }: HomeProps) {
+export default function Home({
+ detail,
+ populars,
+ upComing,
+ firstTrend,
+}: HomeProps) {
const { data: sessions }: any = useSession();
const userSession: SessionTypes = sessions?.user;
@@ -126,6 +136,8 @@ export default function Home({ detail, populars, upComing }: HomeProps) {
});
const { anime: release } = GetMedia(sessions);
+ const router = useRouter();
+
const [schedules, setSchedules] = useState(null);
const [anime, setAnime] = useState([]);
@@ -195,7 +207,6 @@ export default function Home({ detail, populars, upComing }: HomeProps) {
const [prog, setProg] = useState<any[] | null>();
const popular = populars?.data;
- const data = detail.data[0];
useEffect(() => {
async function userData() {
@@ -324,6 +335,10 @@ export default function Home({ detail, populars, upComing }: HomeProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userSession?.name, currentAnime, plan]);
+ function removeHtmlTags(text: string): string {
+ return text?.replace(/<[^>]+>/g, "");
+ }
+
return (
<Fragment>
<Head>
@@ -370,54 +385,41 @@ export default function Home({ detail, populars, upComing }: HomeProps) {
<Navbar paddingY="pt-2 lg:pt-10" withNav={true} home={true} />
<div className="h-auto w-screen bg-[#141519] text-[#dbdcdd]">
- {/* PC / TABLET */}
- <div className=" hidden justify-center lg:flex my-16">
- <div className="relative grid grid-rows-2 items-center lg:flex lg:h-[467px] lg:w-[80%] lg:justify-between">
- <div className="row-start-2 flex h-full flex-col gap-7 lg:w-[55%] lg:justify-center">
- <h1 className="w-[85%] font-outfit font-extrabold lg:text-[34px] line-clamp-2">
- {data.title.english || data.title.romaji || data.title.native}
- </h1>
- <p
- className="font-roboto font-light lg:text-[18px] line-clamp-5"
- dangerouslySetInnerHTML={{ __html: data?.description }}
- />
-
- <div className="lg:pt-5 flex">
- <Link
- href={`/en/anime/${data.id}`}
- className="rounded-sm p-3 text-md font-karla font-light ring-1 ring-[#FF7F57]"
+ <div className="hidden lg:flex w-full justify-center my-16">
+ <div className="flex justify-between w-[80%] h-[470px]">
+ <div className="flex flex-col items-start justify-center w-[55%] gap-5">
+ <p className="font-outfit font-extrabold text-[34px] line-clamp-2 leading-10">
+ {firstTrend?.title?.english || firstTrend?.title?.romaji}
+ </p>
+ <p className="font-roboto font-light lg:text-[18px] line-clamp-3 tracking-wide">
+ {removeHtmlTags(firstTrend?.description)}
+ </p>
+ {firstTrend && (
+ <button
+ onClick={() => {
+ router.push(`/en/anime/${firstTrend?.id}`);
+ }}
+ className="p-3 text-md font-karla font-light ring-1 ring-action/50 rounded"
>
START WATCHING
- </Link>
- </div>
+ </button>
+ )}
</div>
- <div className="z-10 row-start-1 flex justify-center ">
- <div className="relative lg:h-[467px] lg:w-[322px] lg:scale-100">
- <div className="absolute bg-gradient-to-t from-[#141519] to-transparent lg:h-[467px] lg:w-[322px]" />
-
- <Image
- draggable={false}
- src={data.coverImage?.extraLarge || data.image}
- alt={`cover ${data.title.english || data.title.romaji}`}
- width={1200}
- height={1200}
- priority
- className="rounded-tl-xl rounded-tr-xl object-cover bg-blend-overlay lg:h-[467px] lg:w-[322px]"
- />
- </div>
+ <div className="relative block h-[467px] w-[322px]">
+ <div className="absolute bg-gradient-to-t from-primary to-transparent w-full h-full inset-0 z-20" />
+ <Image
+ src={firstTrend?.coverImage?.extraLarge || firstTrend?.image}
+ alt={`cover ${
+ firstTrend?.title?.english || firstTrend?.title?.romaji
+ }`}
+ fill
+ sizes="100%"
+ quality={100}
+ className="object-cover rounded z-10"
+ />
</div>
</div>
</div>
- {/* <div className="relative w-screen h-screen overflow-hidden">
- <iframe
- width="560"
- height="315"
- src="https://www.youtube.com/embed/VVfdqw-qvNE?autoplay=1&controls=0&rel=0&mute=1"
- frameborder="0"
- allowfullscreen
- className="absolute w-screen h-screen top-0 scale-[115%] left-0 z-0"
- />
- </div> */}
{sessions && (
<div className="flex items-center justify-center lg:bg-none mt-4 lg:mt-0 w-screen">
diff --git a/pages/en/schedule/index.tsx b/pages/en/schedule/index.tsx
index aa30259..42a28c6 100644
--- a/pages/en/schedule/index.tsx
+++ b/pages/en/schedule/index.tsx
@@ -1,3 +1,5 @@
+// @ts-nocheck
+
import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import Link from "next/link";
@@ -15,7 +17,6 @@ import {
import { scheduleQuery } from "@/lib/graphql/query";
import MobileNav from "@/components/shared/MobileNav";
-import { useSession } from "next-auth/react";
import { redis } from "@/lib/redis";
import Head from "next/head";
import { Navbar } from "@/components/shared/NavBar";
@@ -344,7 +345,7 @@ export default function Schedule({ schedule }: any) {
{/* {!isAired(time) && <p>Airing Next</p>} */}
<p
className={`absolute left-0 h-1.5 w-1.5 rounded-full ${
- isAired(time) ? "bg-action" : "bg-gray-600" // Add a class for currently airing anime
+ isAired(+time) ? "bg-action" : "bg-gray-600" // Add a class for currently airing anime
}`}
></p>
</div>
@@ -371,14 +372,15 @@ export default function Schedule({ schedule }: any) {
{m.title.romaji}
</h1>
<p className="text-gray-400 group-hover:text-action/80 transition-all duration-200 ease-out">
- Ep {s.episode} {timeStamptoHour(s.airingAt)}
+ Ep {s?.episode}{" "}
+ {timeStamptoHour(s.airingAt)}
</p>
</div>
</Link>
<p
key={`p_${s.id}_${index}`}
className={`absolute translate-x-full top-1/2 -translate-y-1/2 h-full w-0.5 ${
- isAired(time) ? "bg-action" : "bg-gray-600" // Add a class for currently airing anime
+ isAired(+time) ? "bg-action" : "bg-gray-600" // Add a class for currently airing anime
}`}
></p>
</>
diff --git a/release.md b/release.md
index d525bf0..3191b52 100644
--- a/release.md
+++ b/release.md
@@ -2,8 +2,11 @@
This document contains a summary of all significant changes made to this release.
-## 🎉 Update v4.2.5
+## 🎉 Update v4.4.0
### What's Changed
-- fix: Gogoanime episode id is null
+- 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
diff --git a/styles/globals.css b/styles/globals.css
index 10645f0..79d6080 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -587,3 +587,14 @@ pre code {
.chat > span {
@apply font-karla w-full italic text-white/70;
}
+/* Chrome, Safari, Edge, Opera */
+input::-webkit-outer-spin-button,
+input::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+/* Firefox */
+input[type="number"] {
+ -moz-appearance: textfield;
+}
diff --git a/tailwind.config.cjs b/tailwind.config.js
index e072608..e072608 100644
--- a/tailwind.config.cjs
+++ b/tailwind.config.js
diff --git a/tsconfig.json b/tsconfig.json
index 2838c72..c1fa3ea 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -27,8 +27,7 @@
"pages/api/v2/episode/[id].tsx",
"utils/schedulesUtils.ts",
"pages/middleware.js",
- "pages/api/auth/[...nextauth].ts",
- "components/anime/mobile/reused/infoChip.tsx"
+ "pages/api/auth/[...nextauth].ts"
],
"exclude": ["node_modules"]
}