-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from alfaruqii/dev
Dev
- Loading branch information
Showing
85 changed files
with
2,696 additions
and
1,340 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// __mocks__/zustand.ts | ||
import * as zustand from "zustand"; | ||
import { act } from "@testing-library/react"; | ||
|
||
const { create: actualCreate, createStore: actualCreateStore } = | ||
jest.requireActual<typeof zustand>("zustand"); | ||
|
||
// a variable to hold reset functions for all stores declared in the app | ||
export const storeResetFns = new Set<() => void>(); | ||
|
||
const createUncurried = <T>(stateCreator: zustand.StateCreator<T>) => { | ||
const store = actualCreate(stateCreator); | ||
const initialState = store.getInitialState(); | ||
storeResetFns.add(() => { | ||
store.setState(initialState, true); | ||
}); | ||
return store; | ||
}; | ||
|
||
// when creating a store, we get its initial state, create a reset function and add it in the set | ||
export const create = (<T>(stateCreator: zustand.StateCreator<T>) => { | ||
console.log("zustand create mock"); | ||
|
||
// to support curried version of create | ||
return typeof stateCreator === "function" | ||
? createUncurried(stateCreator) | ||
: createUncurried; | ||
}) as typeof zustand.create; | ||
|
||
const createStoreUncurried = <T>(stateCreator: zustand.StateCreator<T>) => { | ||
const store = actualCreateStore(stateCreator); | ||
const initialState = store.getInitialState(); | ||
storeResetFns.add(() => { | ||
store.setState(initialState, true); | ||
}); | ||
return store; | ||
}; | ||
|
||
// when creating a store, we get its initial state, create a reset function and add it in the set | ||
export const createStore = (<T>(stateCreator: zustand.StateCreator<T>) => { | ||
console.log("zustand createStore mock"); | ||
|
||
// to support curried version of createStore | ||
return typeof stateCreator === "function" | ||
? createStoreUncurried(stateCreator) | ||
: createStoreUncurried; | ||
}) as typeof zustand.createStore; | ||
|
||
// reset all stores after each test run | ||
afterEach(() => { | ||
act(() => { | ||
storeResetFns.forEach((resetFn) => { | ||
resetFn(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,29 @@ | ||
import HeroMediaCarousel from "@/components/hero/HeroMediaCarousel"; | ||
import { AnimeContainerCard } from "../components/card/animecard/AnimeContainerCard"; | ||
import AnimeContainerCard from "../components/card/animecard/AnimeContainerCard"; | ||
import { AnimeServiceV1, AnimeServiceV2 } from "../services"; | ||
|
||
export default async function Home() { | ||
const { data: dataTrending } = await AnimeServiceV2.getTrendingAnime(); | ||
const { data: dataRecent } = await AnimeServiceV1.getRecentEpisode(); | ||
const { data: dataRecent } = await AnimeServiceV1.getRecentEpisodeGogo(); | ||
const { data: dataPopular } = await AnimeServiceV2.getPopularAnime(); | ||
|
||
return ( | ||
<> | ||
<div className="flex flex-col gap-4 min-h-fit"> | ||
<HeroMediaCarousel items={dataTrending.results} /> | ||
<div className="p-4 sm:p-0"> | ||
<AnimeContainerCard containerTitle="Recently Updated 🎬" animes={dataRecent.results} /> | ||
<AnimeContainerCard | ||
containerTitle="Recently Updated 🎬" | ||
animes={dataRecent.results} | ||
/> | ||
</div> | ||
<div className="p-4 sm:p-0"> | ||
<AnimeContainerCard containerTitle="Most Popular 💯" animes={dataPopular.results} /> | ||
<AnimeContainerCard | ||
containerTitle="Most Popular 💯" | ||
animes={dataPopular.results} | ||
/> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
|
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,15 @@ | ||
import SkeletonEpisodeNum from '@/components/skeleton/SkeletonEpisodeNum'; | ||
import SkeletonMediaPlayer from '@/components/skeleton/SkeletonMediaPlayer'; | ||
import SkeletonEpisodeNum from "@/components/skeleton/SkeletonEpisodeNum"; | ||
import SkeletonMediaPlayer from "@/components/skeleton/SkeletonMediaPlayer"; | ||
|
||
function loading() { | ||
function Loading() { | ||
return ( | ||
<> | ||
<div className="flex flex-col gap-6 p-6 lg:flex-row"> | ||
<div className="flex flex-col gap-6 p-6 lg:grid lg:grid-cols-5"> | ||
<SkeletonMediaPlayer /> | ||
<SkeletonEpisodeNum /> | ||
</div> | ||
</> | ||
) | ||
); | ||
} | ||
|
||
export default loading | ||
|
||
export default Loading; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// WatchPage.tsx | ||
"use client"; | ||
import useSWR from "swr"; | ||
import EpisodesComponent from "@/components/watch/episodes/EpisodesComponent"; | ||
import Media from "@/components/media/Media"; | ||
import Error from "@/error"; | ||
import Loading from "./loading"; | ||
import { AnimeDetails, AnimeInfo } from "@/types/anime.type"; | ||
import { useState, useEffect, useCallback } from "react"; | ||
|
||
type WatchPageParams = { | ||
searchParams: { id: string; ep: string; isDub?: string }; | ||
}; | ||
|
||
const doesIdNumber = (id: unknown): boolean => Number.isInteger(Number(id)); | ||
|
||
// Fetch Anime Info based on ID | ||
const fetchAnimeInfoV1 = async (idProvider: string) => { | ||
if (!idProvider) return null; | ||
const res = await fetch(`/api/anime-infov1?query=${idProvider}`); | ||
return res.json(); | ||
}; | ||
|
||
const fetchAnimeInfoV2 = async (id: string) => { | ||
if (doesIdNumber(id)) { | ||
const res = await fetch(`/api/anime-infov2?query=${id}`); | ||
return res.json(); | ||
} | ||
}; | ||
|
||
function WatchPage({ searchParams }: WatchPageParams) { | ||
const { id, ep, isDub } = searchParams; | ||
|
||
const [episodeId, setEpisodeId] = useState<string>(""); | ||
const [isLoading, setIsLoading] = useState(true); | ||
|
||
// Fetch data from API v2 and API v1 separately | ||
const { data: animeInfoV2, error: errorInfoV2 } = useSWR<AnimeInfo>( | ||
doesIdNumber(id) ? id : null, | ||
fetchAnimeInfoV2 | ||
); | ||
|
||
const idProvider = isDub | ||
? animeInfoV2?.id_provider.idGogoDub | ||
: animeInfoV2?.id_provider.idGogo || id; | ||
|
||
const { data: animeInfoV1, error: errorInfoV1 } = useSWR<AnimeDetails>( | ||
idProvider ? [idProvider, "v1"] : null, | ||
() => fetchAnimeInfoV1(idProvider!) | ||
); | ||
|
||
// Set the initial episode ID once the anime info and episodes are loaded | ||
useEffect(() => { | ||
if (animeInfoV1?.episodes && ep) { | ||
const selectedEpisode = animeInfoV1?.episodes.find( | ||
(e) => Number(e.number) === Number(ep) | ||
); | ||
if (selectedEpisode) { | ||
setEpisodeId(selectedEpisode.id); | ||
setIsLoading(false); | ||
} | ||
} | ||
}, [animeInfoV1, ep]); | ||
|
||
// Handle episode change through the EpisodesComponent | ||
const handleEpisodeChange = useCallback((newEpisodeId: string) => { | ||
setEpisodeId(newEpisodeId); | ||
}, []); | ||
|
||
// Handle loading and error states | ||
if (errorInfoV2 || errorInfoV1) return <Error />; | ||
if (!animeInfoV2 && doesIdNumber(id)) return <Loading />; | ||
if (isLoading || !animeInfoV1) return <Loading />; | ||
|
||
return ( | ||
<div className="flex flex-col gap-6 p-6 lg:grid lg:grid-cols-5"> | ||
<Media | ||
title={animeInfoV1.title} | ||
poster={animeInfoV1.image} | ||
episodeId={episodeId} | ||
ep={ep} | ||
/> | ||
<EpisodesComponent | ||
id={id} | ||
item={animeInfoV1} | ||
ep={ep} | ||
isDub={isDub} | ||
handleEpisodeChange={handleEpisodeChange} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export default WatchPage; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,25 @@ | ||
import { NextRequest, NextResponse } from 'next/server' | ||
import { AnimeServiceV1 } from '@/services' | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import { AnimeServiceV1 } from "@/services"; | ||
|
||
export async function GET(request: NextRequest) { | ||
const searchParams = request.nextUrl.searchParams | ||
const query = searchParams.get('query') | ||
const searchParams = request.nextUrl.searchParams; | ||
const query = searchParams.get("query"); | ||
|
||
if (!query) { | ||
return NextResponse.json({ error: 'Query parameter is required' }, { status: 400 }) | ||
return NextResponse.json( | ||
{ error: "Query parameter is required" }, | ||
{ status: 400 } | ||
); | ||
} | ||
|
||
try { | ||
const { data } = await AnimeServiceV1.getAnimeInfoV1(query) | ||
return NextResponse.json(data) | ||
const { data } = await AnimeServiceV1.getAnimeInfoV1Gogo(query); | ||
return NextResponse.json(data); | ||
} catch (error) { | ||
console.error('Error searching anime:', error) | ||
return NextResponse.json({ error: 'Failed to search anime' }, { status: 500 }) | ||
console.error("Error searching anime:", error); | ||
return NextResponse.json( | ||
{ error: "Failed to search anime" }, | ||
{ status: 500 } | ||
); | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { NextRequest, NextResponse } from "next/server"; | ||
import { AnimeServiceV2 } from "@/services"; | ||
|
||
export async function GET(request: NextRequest) { | ||
const searchParams = request.nextUrl.searchParams; | ||
const query = searchParams.get("query"); | ||
|
||
if (!query) { | ||
return NextResponse.json( | ||
{ error: "Query parameter is required" }, | ||
{ status: 400 } | ||
); | ||
} | ||
|
||
try { | ||
const { data } = await AnimeServiceV2.getAnimeInfoV2(query); | ||
return NextResponse.json(data); | ||
} catch (error) { | ||
console.error("Error searching anime:", error); | ||
return NextResponse.json( | ||
{ error: "Failed to search anime" }, | ||
{ status: 500 } | ||
); | ||
} | ||
} |
Oops, something went wrong.