diff --git a/app/discover/page.tsx b/app/discover/page.tsx index f381ac6..b30408f 100644 --- a/app/discover/page.tsx +++ b/app/discover/page.tsx @@ -4,22 +4,20 @@ import { cookies } from "next/headers"; import config from "@/config"; import { Metadata } from "next"; -type Status = "idle" | "pending" | "success" | "error" - +type Status = "idle" | "pending" | "success" | "error"; const { apiUrl, token } = config; - export const metadata: Metadata = { title: "Vilm - Discover Movies and Tv Shows ", - description: 'Discover movies and tv shows.', -} + description: "Discover movies and tv shows.", +}; export default async function Page() { const { data, status } = await getDiscover(); - if (status === 'pending') { - return "Loading..." + if (status === "pending") { + return "Loading..."; } return (
@@ -32,8 +30,11 @@ export default async function Page() { ); } - -async function getDiscover(): Promise<{ data: Response | null, status: Status, error: string | null }> { +async function getDiscover(): Promise<{ + data: Response | null; + status: Status; + error: string | null; +}> { const apiToken = cookies().get("API_TOKEN")?.value ?? token; let status: Status = "idle"; let data: Response | null = null; @@ -41,23 +42,21 @@ async function getDiscover(): Promise<{ data: Response | null, status status = "pending"; try { - const response = await fetch(`${apiUrl}/trending/all/day`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - Authorization: `Bearer ${apiToken}`, - }, - }); + const response = await fetch(`${apiUrl}/trending/all/day`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${apiToken}`, + }, + }); if (!response.ok) { - throw new Error('Failed to fetch the movie data'); + throw new Error("Failed to fetch the movie data"); } data = await response.json(); status = "success"; - } catch (err) { console.error(err); status = "error"; @@ -66,4 +65,3 @@ async function getDiscover(): Promise<{ data: Response | null, status return { data, status, error }; } - diff --git a/app/layout.tsx b/app/layout.tsx index 91d0a7c..17f082c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,37 +3,36 @@ import "../src/index.css"; import Navbar from "@/components/Navbar"; import Footer from "@/components/Footer"; import { Toaster } from "@/components/ui/toaster"; -import { Metadata } from 'next'; +import { Metadata } from "next"; export const metadata: Metadata = { - title: 'Vilm', - description: 'Get movies and tv shows information.', + title: "Vilm", + description: "Get movies and tv shows information.", openGraph: { - siteName: 'Vilm', - type: 'website', - url: 'https://vilm-react.vercel.app/', - title: 'Vilm', - description: 'Get movies and tv shows information.', + siteName: "Vilm", + type: "website", + url: "https://vilm-react.vercel.app/", + title: "Vilm", + description: "Get movies and tv shows information.", images: [ { - url: '/og-image.jpg', + url: "/og-image.jpg", }, ], }, twitter: { - card: 'summary_large_image', - creator: '@nnivxix', - title: 'Get movies and tv shows information.', - description: 'Get movies and tv shows information.', - images: ['/og-image.jpg'], + card: "summary_large_image", + creator: "@nnivxix", + title: "Get movies and tv shows information.", + description: "Get movies and tv shows information.", + images: ["/og-image.jpg"], }, }; - export default function RootLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }) { return ( @@ -72,8 +71,6 @@ export default function RootLayout({ href="/backdrop-fallback.png" type="image/png" /> - - @@ -83,4 +80,4 @@ export default function RootLayout({ ); -} \ No newline at end of file +} diff --git a/app/page.tsx b/app/page.tsx index 264d885..5ba2029 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,9 +1,9 @@ -"use client" +"use client"; import type { FormEvent } from "react"; import { buttonVariants } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { Suspense, useState } from "react"; -import { Input } from "@/components/ui/input" +import { Input } from "@/components/ui/input"; import Link from "next/link"; import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; @@ -40,9 +40,11 @@ function SearchForm() { } export default function Page() { - return ( -
+
@@ -52,10 +54,16 @@ export default function Page() { -

- Or -

- Discover +

Or

+ + {" "} + Discover{" "} +
diff --git a/app/search/page.tsx b/app/search/page.tsx index e7d7ed4..632b516 100644 --- a/app/search/page.tsx +++ b/app/search/page.tsx @@ -1,38 +1,33 @@ -import { SearchForm } from '@/components/SearchForm' -import { Suspense } from 'react' - +import { SearchForm } from "@/components/SearchForm"; +import { Suspense } from "react"; interface Props { - searchParams: { [key: string]: string | string[] | undefined } + searchParams: { [key: string]: string | string[] | undefined }; } - export async function generateMetadata({ searchParams }: Props) { const title = searchParams.title; const type = () => { const paramType = searchParams.type; - if (paramType === 'tv') return "Tv Shows" - if (paramType === 'movie') return "Movies" - return "" + if (paramType === "tv") return "Tv Shows"; + if (paramType === "movie") return "Movies"; + return ""; }; if (title) { return { - title: `Vilm - Search ${type()} for "${title}" ` - } + title: `Vilm - Search ${type()} for "${title}" `, + }; } return { - title: "Vilm - Search" - } - + title: "Vilm - Search", + }; } export default async function Page() { return ( - ) + ); } - - diff --git a/app/setting/page.tsx b/app/setting/page.tsx index 331d874..4df2c7e 100644 --- a/app/setting/page.tsx +++ b/app/setting/page.tsx @@ -1,6 +1,6 @@ -"use client" +"use client"; -import { ChangeEvent, FormEvent, useState } from "react" +import { ChangeEvent, FormEvent, useState } from "react"; import { Clipboard } from "lucide-react"; import { useToast } from "@/components/ui/use-toast"; import useHead from "@/hooks/useHead"; @@ -14,23 +14,23 @@ import { deleteCookie, getCookie, setCookie } from "cookies-next"; export default function Page() { const [form, setForm] = useState<{ - token?: string | null + token?: string | null; }>({ - token: getCookie('API_TOKEN') ?? '' - }) + token: getCookie("API_TOKEN") ?? "", + }); const { toast } = useToast(); const { setAccount, setIsAuthenticated } = useAccountStore(); const handleClipboard = async () => { const copiedText = await navigator.clipboard.readText(); - setForm((prevFormData) => ({ ...prevFormData, token: copiedText })) - } + setForm((prevFormData) => ({ ...prevFormData, token: copiedText })); + }; const handleChange = (e: ChangeEvent) => { const { name, value } = e.target; - setForm((prevFormData) => ({ ...prevFormData, [name]: value })) - } + setForm((prevFormData) => ({ ...prevFormData, [name]: value })); + }; const submitForm = async (e: FormEvent) => { e.preventDefault(); @@ -39,8 +39,8 @@ export default function Page() { toast({ title: "Success", - description: "Data updated succesfully." - }) + description: "Data updated succesfully.", + }); try { const { error } = await $fetch("/authentication", { @@ -50,16 +50,14 @@ export default function Page() { defaultToken: false, }); - - if (!form.token) { toast({ title: "Success", - description: "Data updated succesfully to be null." - }) + description: "Data updated succesfully to be null.", + }); setAccount(null); setIsAuthenticated(false); - deleteCookie("API_TOKEN") + deleteCookie("API_TOKEN"); return; } @@ -67,8 +65,8 @@ export default function Page() { if (!error) { toast({ title: "Success", - description: "Data updated succesfully." - }) + description: "Data updated succesfully.", + }); const { data } = await $fetch("/account", { headers: { Authorization: "Bearer " + form.token, @@ -76,13 +74,13 @@ export default function Page() { defaultToken: false, }); - const date = new Date() - date.setFullYear(date.getFullYear() + 10) + const date = new Date(); + date.setFullYear(date.getFullYear() + 10); setAccount(data); setIsAuthenticated(true); - setCookie('API_TOKEN', (form.token as string), { - expires: date + setCookie("API_TOKEN", form.token as string, { + expires: date, }); return; } @@ -90,40 +88,53 @@ export default function Page() { toast({ title: "Error", description: error?.status_message, - }) - throw new Error(error?.status_message) - - - + }); + throw new Error(error?.status_message); } catch (error) { - console.error(error) + console.error(error); } - } - + }; useHead({ - title: 'Vilm - Settings', + title: "Vilm - Settings", meta: { - description: 'Here you can manage your settings' - } + description: "Here you can manage your settings", + }, }); return (
-

- Settings -

-

Here you can manage your setting.

+

Settings

+

Here you can manage your setting.

- - - How to get API Token -

Don't worry, we won't store your API token to our server (Vilm), we'll store the token to LocalStorage.

+ + + + How to get API Token + +

+ Don't worry, we won't store your API token to our server (Vilm), + we'll store the token to LocalStorage. +

@@ -131,5 +142,5 @@ export default function Page() {
- ) + ); } diff --git a/app/show/movie/[id]/page.tsx b/app/show/movie/[id]/page.tsx index 3a145db..458ce73 100644 --- a/app/show/movie/[id]/page.tsx +++ b/app/show/movie/[id]/page.tsx @@ -1,12 +1,22 @@ import { cookies } from "next/headers"; import type { Metadata } from "next"; -import type { MovieResponse, SimilarMixed, AccountStates } from "@/types/response"; +import type { + MovieResponse, + SimilarMixed, + AccountStates, +} from "@/types/response"; import type { Media } from "@/types/media"; import type { Provider } from "@/types/providers"; import runtimeDuration from "@/utils/runtime-duration"; import getYear from "@/utils/get-year"; import Genres from "@/components/Genres"; -import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; import pickRandomImages from "@/utils/pick-random-images"; import RImage from "@/components/RImage"; import imageUrl from "@/utils/image-url"; @@ -17,26 +27,24 @@ import SimilarCardItem from "@/components/SimilarCardItem"; import AddToWatchlistButton from "@/components/AddToWatchlistButton"; import getVideo from "@/utils/get-video"; import config from "@/config"; - +import { notFound } from "next/navigation"; +import { Suspense } from "react"; interface Params { - params: { id: string } + params: { id: string }; } interface Authentication { success: boolean; status_code: number; status_message: string; } -type Status = "idle" | "pending" | "success" | "error" - +type Status = "idle" | "pending" | "success" | "error"; const { apiUrl, token } = config; -export async function generateMetadata( - { params }: Params -): Promise { +export async function generateMetadata({ params }: Params): Promise { const { movie } = await getMovie(params.id); - const title = `Vilm - ${movie?.title}` + const title = `Vilm - ${movie?.title}`; const description = movie?.overview; if (movie) { @@ -47,156 +55,163 @@ export async function generateMetadata( openGraph: { title, description, - images: [image] + images: [image], }, twitter: { title, description, images: [image], - card: "summary" - } - } + card: "summary", + }, + }; } else { return { - title: "Vilm" - } + title: "Vilm", + }; } } export default async function Page({ params }: Params) { - const { movie, status } = await getMovie(params.id) - const states = await getStates(params.id) - const isAuthenticated = await authenticateUser() - + const { movie, status } = await getMovie(params.id); + const states = await getStates(params.id); + const isAuthenticated = await authenticateUser(); - if (status === 'pending') { - return
loading...
; - } - - if (status === 'error') { - return
error
; + if (status === "error") { + return notFound(); } return ( -
- {!!movie && ( -
-
-

- {movie.original_title}{" "} -

-

- Duration: {runtimeDuration(movie.runtime)} |{" "} - {getYear(movie.release_date)} -

-

- {movie.overview} -

- {!!movie.genres.length && - } + loading...}> +
+ {!!movie && ( +
+
+

+ {movie.original_title}{" "} +

+

+ Duration: {runtimeDuration(movie.runtime)} |{" "} + {getYear(movie.release_date)} +

+

+ {movie.overview} +

+ {!!movie.genres.length && } - {!!movie.images?.backdrops.length && ( - - - {pickRandomImages(movie.images.backdrops as Media[], 7).map( - (image: Media, index) => ( - - - - - - - - - - - ) - )} - - + + {pickRandomImages(movie.images.backdrops as Media[], 7).map( + (image: Media, index) => ( + + + + + + + + + + + ) + )} + + + + + )} + {!!movie.videos?.results.length && ( + - - - )} - {!!movie.videos?.results.length && ( - - )} - {isAuthenticated && states ? ( - - ) : null} -
+ ) : null} +
-
- -
- )} +
+ +
+ )} - + - {/* Similar Movies */} + {/* Similar Movies */} -
-

- Similar Movies:{" "} -

- {movie?.similar?.results?.length ? ( - movie?.similar?.results?.map((movie) => ( - - ))) : - ( +
+

+ Similar Movies:{" "} +

+ {movie?.similar?.results?.length ? ( + movie?.similar?.results?.map((movie) => ( + + )) + ) : (

No Similiar Movies Shown yet.

- )} +
+
-
-
+ ); } -async function getMovie(movieId: string): Promise<{ movie: MovieResponse | null, status: Status, error: string | null }> { +async function getMovie( + movieId: string +): Promise<{ + movie: MovieResponse | null; + status: Status; + error: string | null; +}> { const apiToken = cookies().get("API_TOKEN")?.value ?? token; let status: Status = "idle"; let movie: MovieResponse | null = null; @@ -204,23 +219,24 @@ async function getMovie(movieId: string): Promise<{ movie: MovieResponse | null, status = "pending"; try { - const response = await fetch(`${apiUrl}/movie/${movieId}?append_to_response=genre,images,videos,watch/providers,similar&language=en-US&include_image_language=en,null`, + const response = await fetch( + `${apiUrl}/movie/${movieId}?append_to_response=genre,images,videos,watch/providers,similar&language=en-US&include_image_language=en,null`, { method: "GET", headers: { "Content-Type": "application/json", - "Accept": "application/json", + Accept: "application/json", Authorization: `Bearer ${apiToken}`, }, - }); + } + ); if (!response.ok) { - throw new Error('Failed to fetch the movie data'); + throw new Error("Failed to fetch the movie data"); } movie = await response.json(); status = "success"; - } catch (err) { console.error(err); status = "error"; @@ -230,39 +246,35 @@ async function getMovie(movieId: string): Promise<{ movie: MovieResponse | null, return { movie, status, error }; } - async function getStates(movieId: string) { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); try { - const response = await fetch(`${apiUrl}/movie/${movieId}/account_states`, - { - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - Authorization: `Bearer ${apiToken?.value}`, - } - }) + const response = await fetch(`${apiUrl}/movie/${movieId}/account_states`, { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${apiToken?.value}`, + }, + }); - const states = await response.json() + const states = await response.json(); return states as AccountStates; - } catch (error) { - console.error(error) + console.error(error); } } async function authenticateUser(): Promise { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/authentication`, { method: "GET", headers: { - "accept": "application/json", + accept: "application/json", Authorization: `Bearer ${apiToken?.value}`, }, - }) - const isAuthenticated: Authentication = await response.json() + }); + const isAuthenticated: Authentication = await response.json(); return isAuthenticated.success; - -} \ No newline at end of file +} diff --git a/app/show/tv/[id]/page.tsx b/app/show/tv/[id]/page.tsx index 200da3f..f15fd04 100644 --- a/app/show/tv/[id]/page.tsx +++ b/app/show/tv/[id]/page.tsx @@ -2,7 +2,13 @@ import type { TvResponse, SimilarMixed, AccountStates } from "@/types/response"; import type { Media } from "@/types/media"; import type { Provider } from "@/types/providers"; import Genres from "@/components/Genres"; -import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; import pickRandomImages from "@/utils/pick-random-images"; import RImage from "@/components/RImage"; import imageUrl from "@/utils/image-url"; @@ -17,25 +23,23 @@ import AddToWatchlistButton from "@/components/AddToWatchlistButton"; import config from "@/config"; import { cookies } from "next/headers"; import type { Metadata } from "next"; +import { notFound } from "next/navigation"; interface Params { - params: { id: string } + params: { id: string }; } interface Authentication { success: boolean; status_code: number; status_message: string; } -type Status = "idle" | "pending" | "success" | "error" +type Status = "idle" | "pending" | "success" | "error"; +const { apiUrl, token } = config; -const { apiUrl, token } = config - -export async function generateMetadata( - { params }: Params -): Promise { +export async function generateMetadata({ params }: Params): Promise { const { tv } = await getTvShow(params.id); - const title = `Vilm - ${tv?.name}` + const title = `Vilm - ${tv?.name}`; const description = tv?.overview; if (tv) { @@ -46,34 +50,32 @@ export async function generateMetadata( openGraph: { title, description, - images: [image] + images: [image], }, twitter: { title, description, images: [image], - card: "summary" - } - } + card: "summary", + }, + }; } else { return { - title: "Vilm" - } + title: "Vilm", + }; } } export default async function Page({ params }: Params) { - const { tv, status, error } = await getTvShow(params.id) - const states = await getStates(params.id) - const isAuthenticated = await authenticateUser() + const { tv, status } = await getTvShow(params.id); + const states = await getStates(params.id); + const isAuthenticated = await authenticateUser(); - - - if (status === 'pending') { + if (status === "pending") { return
loading...
; } - if (status === 'error') { - return
{error}
; + if (status === "error") { + return notFound(); } return ( @@ -88,10 +90,10 @@ export default async function Page({ params }: Params) { Number of Season: {tv.number_of_seasons} | Number of Episodes:{" "} {tv.number_of_episodes} -

{tv.overview}

- {tv.genres.length && ( - - )} +

+ {tv.overview} +

+ {tv.genres.length && } {!!tv.images?.backdrops.length && ( @@ -114,7 +116,7 @@ export default async function Page({ params }: Params) { alt={image.file_path} /> - + )} {isAuthenticated && states ? ( - + ) : null}
@@ -172,10 +178,7 @@ export default async function Page({ params }: Params) {

Seasons:

{tv.seasons.map((season: Season, index) => ( - + ))} @@ -188,7 +191,9 @@ export default async function Page({ params }: Params) { )} - +

Similar Tvs:

@@ -198,21 +203,21 @@ export default async function Page({ params }: Params) { media="tv" card={movie as SimilarMixed} key={movie.id} - /> - ))) : ( + )) + ) : (

No Similiar Tv Shows Shown yet.

- )}
); } - -async function getTvShow(movieId: string): Promise<{ tv: TvResponse | null, status: Status, error: string | null }> { +async function getTvShow( + movieId: string +): Promise<{ tv: TvResponse | null; status: Status; error: string | null }> { const apiToken = cookies().get("API_TOKEN")?.value ?? token; let status: Status = "idle"; let tv: TvResponse | null = null; @@ -220,23 +225,24 @@ async function getTvShow(movieId: string): Promise<{ tv: TvResponse | null, stat status = "pending"; try { - const response = await fetch(`${apiUrl}/tv/${movieId}?append_to_response=images,videos,watch/providers,similar&language=en-US&include_image_language=en,null`, + const response = await fetch( + `${apiUrl}/tv/${movieId}?append_to_response=images,videos,watch/providers,similar&language=en-US&include_image_language=en,null`, { method: "GET", headers: { "Content-Type": "application/json", - "Accept": "application/json", + Accept: "application/json", Authorization: `Bearer ${apiToken}`, }, - }); + } + ); if (!response.ok) { - throw new Error('Failed to fetch the tv data'); + throw new Error("Failed to fetch the tv data"); } tv = await response.json(); status = "success"; - } catch (err) { console.error(err); status = "error"; @@ -246,39 +252,35 @@ async function getTvShow(movieId: string): Promise<{ tv: TvResponse | null, stat return { tv, status, error }; } - async function getStates(movieId: string) { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); try { - const response = await fetch(`${apiUrl}/movie/${movieId}/account_states`, - { - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - Authorization: `Bearer ${apiToken?.value}`, - } - }) + const response = await fetch(`${apiUrl}/movie/${movieId}/account_states`, { + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${apiToken?.value}`, + }, + }); - const states = await response.json() + const states = await response.json(); return states as AccountStates; - } catch (error) { - console.error(error) + console.error(error); } } async function authenticateUser(): Promise { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/authentication`, { method: "GET", headers: { - "accept": "application/json", + accept: "application/json", Authorization: `Bearer ${apiToken?.value}`, }, - }) - const isAuthenticated: Authentication = await response.json() + }); + const isAuthenticated: Authentication = await response.json(); return isAuthenticated.success; - -} \ No newline at end of file +} diff --git a/app/watchlist/layout.tsx b/app/watchlist/layout.tsx index b21ebc0..4f57a14 100644 --- a/app/watchlist/layout.tsx +++ b/app/watchlist/layout.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import React from "react"; // import "../src/index.css"; import { usePathname } from "next/navigation"; @@ -8,40 +8,42 @@ import Link from "next/link"; export default function RootLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }) { - const pathname = usePathname(); const isActivePath = (path: string) => { return pathname?.includes(path); - } + }; const paths = [ { location: "/watchlist/movie", - label: 'Movies' + label: "Movies", }, { location: "/watchlist/tv", - label: 'Tv Shows' + label: "Tv Shows", }, - ] + ]; return ( -
-
-

Watchlist

-
+
+
+

Watchlist

+
{paths.map((path, index) => { return ( - - ) + ); })}
{children}
- ); -} \ No newline at end of file +} diff --git a/app/watchlist/movie/page.tsx b/app/watchlist/movie/page.tsx index 360a9e0..93a50ec 100644 --- a/app/watchlist/movie/page.tsx +++ b/app/watchlist/movie/page.tsx @@ -1,10 +1,10 @@ -import type { SimpleMovie } from "@/types/movie" -import type { Response } from "@/types/response" +import type { SimpleMovie } from "@/types/movie"; +import type { Response } from "@/types/response"; import type { Metadata } from "next"; import type { Account } from "@/contexts/AccountContext/AccountProvider"; import Link from "next/link"; import { Suspense } from "react"; -import { cookies } from 'next/headers' +import { cookies } from "next/headers"; import BackdropCard from "@/components/BackdropCard"; import Pagination from "@/components/Pagination"; import paginationPages from "@/utils/pagination-pages"; @@ -20,13 +20,14 @@ const { apiUrl } = config; export const metadata: Metadata = { title: "Vilm - Movies Watchlist ", - description: 'Here you can manage watchlist movies.', -} - -export default async function Page({ searchParams }: - { - searchParams: { [key: string]: string | string[] | undefined } - }) { + description: "Here you can manage watchlist movies.", +}; + +export default async function Page({ + searchParams, +}: { + searchParams: { [key: string]: string | string[] | undefined }; +}) { const currentPage = searchParams.page ?? "1"; const isAuthenticated = await authenticateUser(); @@ -37,41 +38,44 @@ export default async function Page({ searchParams }: return (

Not Logged in

-

Please add your token on setting page.

+

+ Please add your token on{" "} + + setting page. + +

- ); } - const pages = paginationPages(Number(currentPage), movies?.total_pages as number) + const pages = paginationPages( + Number(currentPage), + movies?.total_pages as number + ); return (
-
- {movies?.results.length && ( +
+ {movies?.results.length && movies.results.map((movie, index) => ( media={movie} title={movie.title} key={index} - className="lg:col-span-1 md:col-span-2 col-span-3" /> - )) - ) - - } + className="lg:col-span-1 md:col-span-2 col-span-3" + /> + ))}
- {pages.length && ( - - )} + {pages.length && }
- ) + ); } async function getAccount() { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/account`, { headers: { @@ -82,38 +86,39 @@ async function getAccount() { const data: Account = await response.json(); - return data + return data; } - async function getWatchlistMovies(accountId: number, currentPage: string) { - const apiToken = cookies().get("API_TOKEN") - - const response = await fetch(`${apiUrl}/account/${accountId}/watchlist/movies?page=${currentPage}&sort_by=created_at.desc`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - Authorization: `Bearer ${apiToken?.value}`, - }, - }) + const apiToken = cookies().get("API_TOKEN"); + + const response = await fetch( + `${apiUrl}/account/${accountId}/watchlist/movies?page=${currentPage}&sort_by=created_at.desc`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${apiToken?.value}`, + }, + } + ); const movies: Response = await response.json(); - return movies + return movies; } async function authenticateUser(): Promise { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/authentication`, { method: "GET", headers: { - "accept": "application/json", + accept: "application/json", Authorization: `Bearer ${apiToken?.value}`, }, - }) + }); - const isAuthenticated: Authentication = await response.json() + const isAuthenticated: Authentication = await response.json(); return isAuthenticated.success; - -} \ No newline at end of file +} diff --git a/app/watchlist/tv/page.tsx b/app/watchlist/tv/page.tsx index 6583f86..a5022dd 100644 --- a/app/watchlist/tv/page.tsx +++ b/app/watchlist/tv/page.tsx @@ -1,10 +1,10 @@ -import type { SimpleTv } from "@/types/tv" -import type { Response } from "@/types/response" +import type { SimpleTv } from "@/types/tv"; +import type { Response } from "@/types/response"; import type { Metadata } from "next"; import type { Account } from "@/contexts/AccountContext/AccountProvider"; import Link from "next/link"; import { Suspense } from "react"; -import { cookies } from 'next/headers' +import { cookies } from "next/headers"; import BackdropCard from "@/components/BackdropCard"; import Pagination from "@/components/Pagination"; import paginationPages from "@/utils/pagination-pages"; @@ -20,11 +20,13 @@ const { apiUrl } = config; export const metadata: Metadata = { title: "Vilm - Tv Shows Watchlist ", - description: 'Here you can manage watchlist tv shows.', -} + description: "Here you can manage watchlist tv shows.", +}; -export default async function Page({ searchParams }: { - searchParams: { [key: string]: string | string[] | undefined } +export default async function Page({ + searchParams, +}: { + searchParams: { [key: string]: string | string[] | undefined }; }) { const currentPage = searchParams.page ?? "1"; @@ -35,64 +37,65 @@ export default async function Page({ searchParams }: { // const { isAuthenticated } = useAccountStore() // const { data: tv } = useFetch>(`/account/9578292/watchlist/tv`) - if (!isAuthenticated) { return (

Not Logged in

-

Please add your token on setting page.

+

+ Please add your token on{" "} + + setting page. + +

- ) + ); } - const pages = paginationPages(Number(currentPage), tv?.total_pages as number) - + const pages = paginationPages(Number(currentPage), tv?.total_pages as number); return (
-
- {tv?.results.length && ( +
+ {tv?.results.length && tv.results.map((tv, index) => ( media={tv} title={tv.name} key={index} - className="lg:col-span-1 md:col-span-2 col-span-3" /> - )) - - ) - - } + className="lg:col-span-1 md:col-span-2 col-span-3" + /> + ))}
- {pages.length && ( - - )} + {pages.length && }
- ) + ); } async function getWatchlistTv(accountId: number, currentPage: string) { - const apiToken = cookies().get("API_TOKEN") - - const response = await fetch(`${apiUrl}/account/${accountId}/watchlist/tv?page=${currentPage}&sort_by=created_at.desc`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Accept": "application/json", - Authorization: `Bearer ${apiToken?.value}`, - }, - }) + const apiToken = cookies().get("API_TOKEN"); + + const response = await fetch( + `${apiUrl}/account/${accountId}/watchlist/tv?page=${currentPage}&sort_by=created_at.desc`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${apiToken?.value}`, + }, + } + ); const movies: Response = await response.json(); - return movies + return movies; } async function getAccount() { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/account`, { headers: { @@ -103,21 +106,20 @@ async function getAccount() { const data: Account = await response.json(); - return data + return data; } async function authenticateUser(): Promise { - const apiToken = cookies().get("API_TOKEN") + const apiToken = cookies().get("API_TOKEN"); const response = await fetch(`${apiUrl}/authentication`, { method: "GET", headers: { - "accept": "application/json", + accept: "application/json", Authorization: `Bearer ${apiToken?.value}`, }, - }) + }); - const isAuthenticated: Authentication = await response.json() + const isAuthenticated: Authentication = await response.json(); return isAuthenticated.success; - } diff --git a/src/components/AddToWatchlistButton.tsx b/src/components/AddToWatchlistButton.tsx index 47f7e77..7f55c4d 100644 --- a/src/components/AddToWatchlistButton.tsx +++ b/src/components/AddToWatchlistButton.tsx @@ -1,46 +1,52 @@ -"use client" -import { useAccountStore } from '@/stores/account'; -import type { AccountStates } from '@/types/response'; -import { ResponseMessage } from '@/utils/$fetch'; -import { useToast } from "@/components/ui/use-toast" -import { useState } from 'react'; -import { Button } from './ui/button'; -import $fetch from '@/utils/$fetch'; +"use client"; +import { useAccountStore } from "@/stores/account"; +import type { AccountStates } from "@/types/response"; +import { ResponseMessage } from "@/utils/$fetch"; +import { useToast } from "@/components/ui/use-toast"; +import { useState } from "react"; +import { Button } from "./ui/button"; +import $fetch from "@/utils/$fetch"; interface Props { states: AccountStates; - type: 'movie' | 'tv'; + type: "movie" | "tv"; mediaId: number | string; } export default function AddToWatchlistButton({ states, type, mediaId }: Props) { const { account } = useAccountStore(); const { toast } = useToast(); - const [isWatchlisted, setIsWatchlisted] = useState(states.watchlist) + const [isWatchlisted, setIsWatchlisted] = useState(states.watchlist); const addToWatchlist = async () => { try { - const { data } = await $fetch(`/account/${account?.id}/watchlist`, { - method: 'POST', - body: { - "media_type": type, - "media_id": mediaId, - "watchlist": isWatchlisted === true ? false : true, + const { data } = await $fetch( + `/account/${account?.id}/watchlist`, + { + method: "POST", + body: { + media_type: type, + media_id: mediaId, + watchlist: isWatchlisted === true ? false : true, + }, } - }); + ); setIsWatchlisted(!isWatchlisted); toast({ - description: data.status_message - }) + description: data.status_message, + }); } catch (e) { - console.error(e) + console.error(e); } }; - return ( - - ) + ); } diff --git a/src/components/BackdropCard.tsx b/src/components/BackdropCard.tsx index d189be0..b299c6a 100644 --- a/src/components/BackdropCard.tsx +++ b/src/components/BackdropCard.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import Link from "next/link"; import type { ComponentPropsWithRef } from "react"; import RImage from "./RImage"; @@ -17,25 +17,31 @@ export interface SimpleBaseMedia { vote_count: number; } - -interface BackdropCardProps extends ComponentPropsWithRef<'div'> { +interface BackdropCardProps extends ComponentPropsWithRef<"div"> { media: T; title: string; } -export default function BackdropCard({ media, title, ...props }: BackdropCardProps) { - const isMovieType = Object.prototype.hasOwnProperty.call(media, 'video'); +export default function BackdropCard({ + media, + title, + ...props +}: BackdropCardProps) { + const isMovieType = Object.prototype.hasOwnProperty.call(media, "video"); return ( -
- - + @@ -44,5 +50,5 @@ export default function BackdropCard({ media, title,
- ) + ); } diff --git a/src/components/CardItem.tsx b/src/components/CardItem.tsx index 9414c88..0bba959 100644 --- a/src/components/CardItem.tsx +++ b/src/components/CardItem.tsx @@ -5,7 +5,6 @@ import RImage from "./RImage"; import imageUrl from "../utils/image-url"; import { buttonVariants } from "./ui/button"; - interface CardItemProps { movie: MovieTv; media?: string; @@ -28,7 +27,10 @@ export default function CardItem({ movie, media }: CardItemProps) {

{movieTitle}

{movie.overview}

- + View
diff --git a/src/components/Genres.tsx b/src/components/Genres.tsx index 8d16927..2aca600 100644 --- a/src/components/Genres.tsx +++ b/src/components/Genres.tsx @@ -1,8 +1,8 @@ -"use client" -import type { Genre } from '@/types/media' +"use client"; +import type { Genre } from "@/types/media"; interface GenresProps { - genres: Genre[] + genres: Genre[]; } export default function Genres({ genres }: GenresProps) { @@ -17,5 +17,5 @@ export default function Genres({ genres }: GenresProps) { ))}
- ) + ); } diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 2fe69e5..7aecb48 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,21 +1,26 @@ -"use client" +"use client"; -import { getCookie } from "cookies-next" +import { getCookie } from "cookies-next"; import { LibraryBig, Settings } from "lucide-react"; import Link from "next/link"; -import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "./ui/dropdown-menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"; import gravatarUrl from "@/utils/gravatar-url"; import { type Account, useAccountStore } from "@/stores/account"; import config from "@/config"; import { useEffect } from "react"; - const { apiUrl } = config; - export default function Navbar() { - const token = getCookie("API_TOKEN") + const token = getCookie("API_TOKEN"); const { account, setAccount, setIsAuthenticated } = useAccountStore(); useEffect(() => { @@ -31,18 +36,16 @@ export default function Navbar() { const data: Account = await response.json(); - if (!data.id) { - setIsAuthenticated(false) - setAccount(null) + setIsAuthenticated(false); + setAccount(null); } else { setAccount(data); - setIsAuthenticated(true) + setIsAuthenticated(true); } - }; getAccount(); - }, [setAccount, setIsAuthenticated, token]) + }, [setAccount, setIsAuthenticated, token]); return (
@@ -57,9 +60,7 @@ export default function Navbar() { - - {"G"} - + {"G"} diff --git a/src/components/Pagination.tsx b/src/components/Pagination.tsx index c9a67ae..1995556 100644 --- a/src/components/Pagination.tsx +++ b/src/components/Pagination.tsx @@ -1,7 +1,7 @@ -"use client" +"use client"; -import { usePathname, useSearchParams } from "next/navigation" -import Link from 'next/link' +import { usePathname, useSearchParams } from "next/navigation"; +import Link from "next/link"; export default function Pagination({ pages }: { pages: (number | string)[] }) { const pathname = usePathname(); @@ -9,26 +9,31 @@ export default function Pagination({ pages }: { pages: (number | string)[] }) { return (
-
- {pages.length && pages.map((page, index) => ( - typeof page === "number" ? ( - - {page} - - ) : ( - - )) - )} +
+ {pages.length && + pages.map((page, index) => + typeof page === "number" ? ( + + {page} + + ) : ( + + ) + )}
- ) + ); } diff --git a/src/components/PopupYoutubeTrailer.tsx b/src/components/PopupYoutubeTrailer.tsx index 929d5fe..f34bc13 100644 --- a/src/components/PopupYoutubeTrailer.tsx +++ b/src/components/PopupYoutubeTrailer.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog"; export default function PopupYoutubeTrailer({ video }: { video: string }) { diff --git a/src/components/RImage.tsx b/src/components/RImage.tsx index bea7164..6d6104f 100644 --- a/src/components/RImage.tsx +++ b/src/components/RImage.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useState, type ImgHTMLAttributes, type SyntheticEvent } from "react"; import type { TypeImage } from "../hooks/useImageFallback"; @@ -13,7 +13,6 @@ export default function RImage({ src, alt, type, ...props }: ImageProps) { const { fallback } = useImageFallback(type); const [loaded, setLoaded] = useState(false); - const imageFallback = (event: SyntheticEvent) => { event.currentTarget.src = fallback; }; @@ -21,7 +20,7 @@ export default function RImage({ src, alt, type, ...props }: ImageProps) { if (loaded) return; event.currentTarget.src = src; - setLoaded(true) + setLoaded(true); }; return ( diff --git a/src/components/SearchForm.tsx b/src/components/SearchForm.tsx index 8b5017f..41362d0 100644 --- a/src/components/SearchForm.tsx +++ b/src/components/SearchForm.tsx @@ -1,16 +1,21 @@ -"use client" +"use client"; import CardItem from "@/components/CardItem"; import { Input } from "@/components/ui/input"; -import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import useFetch from "@/hooks/useFetch"; import type { MovieTv, Response } from "@/types/response"; import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; import { useEffect, useState, type FormEvent } from "react"; - - export function SearchForm() { const searchParams = useSearchParams(); const router = useRouter(); @@ -98,7 +103,4 @@ export function SearchForm() {
); - } - - diff --git a/src/components/SimilarCardItem.tsx b/src/components/SimilarCardItem.tsx index 97fc189..63bb2e3 100644 --- a/src/components/SimilarCardItem.tsx +++ b/src/components/SimilarCardItem.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import type { SimilarMovie, SimilarTv } from "@/types/response"; import { Star } from "lucide-react"; import { buttonVariants } from "./ui/button"; @@ -31,7 +31,10 @@ export default function SimilarCardItem<

{movieTitle}

{card.overview}

- + View
diff --git a/src/components/WatchProviderContainer.tsx b/src/components/WatchProviderContainer.tsx index 8c87f26..ffb98a9 100644 --- a/src/components/WatchProviderContainer.tsx +++ b/src/components/WatchProviderContainer.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import type { ProvidersResponse } from "@/types/providers"; import WatchProvider from "./WatchProvider"; import getProviders from "@/utils/get-providers"; diff --git a/src/contexts/AccountContext/AccountProvider.tsx b/src/contexts/AccountContext/AccountProvider.tsx index e8c9e75..f3a08cf 100644 --- a/src/contexts/AccountContext/AccountProvider.tsx +++ b/src/contexts/AccountContext/AccountProvider.tsx @@ -1,30 +1,30 @@ -"use client" -import { createContext, useState, useEffect, } from "react" +"use client"; +import { createContext, useState, useEffect } from "react"; // import $localStorage from "@/utils/$local-storage" -import $fetch from "@/utils/$fetch" -import { getCookie } from "cookies-next" +import $fetch from "@/utils/$fetch"; +import { getCookie } from "cookies-next"; export interface Account { - avatar: Avatar - id: number - iso_639_1: string - iso_3166_1: string - name: string - include_adult: boolean - username: string + avatar: Avatar; + id: number; + iso_639_1: string; + iso_3166_1: string; + name: string; + include_adult: boolean; + username: string; } export interface Avatar { - gravatar: Gravatar - tmdb: Tmdb + gravatar: Gravatar; + tmdb: Tmdb; } export interface Gravatar { - hash: string + hash: string; } export interface Tmdb { - avatar_path?: string + avatar_path?: string; } type AccountProviderState = { @@ -32,24 +32,21 @@ type AccountProviderState = { isAuthenticated: boolean; setAccount: React.Dispatch>; setIsAuthenticated: React.Dispatch>; -} +}; export const AccountProviderContext = createContext({ account: null, isAuthenticated: false, setAccount: () => null, - setIsAuthenticated: () => false -}) - -export function AccountProvider({ - children -}: { - children: React.ReactNode -}) { + setIsAuthenticated: () => false, +}); +export function AccountProvider({ children }: { children: React.ReactNode }) { const token = getCookie("API_TOKEN"); const [account, setAccount] = useState(null); - const [isAuthenticated, setIsAuthenticated] = useState(!!account?.username); + const [isAuthenticated, setIsAuthenticated] = useState( + !!account?.username + ); useEffect(() => { const getAccount = async () => { @@ -64,11 +61,10 @@ export function AccountProvider({ setAccount(data); if (error?.success === false) { - setIsAuthenticated(false) + setIsAuthenticated(false); } else { - setIsAuthenticated(true) + setIsAuthenticated(true); } - }; getAccount(); }, [token]); @@ -86,4 +82,3 @@ export function AccountProvider({ ); } - diff --git a/src/contexts/ThemeContext/ThemeProvider.tsx b/src/contexts/ThemeContext/ThemeProvider.tsx index 7316886..182f304 100644 --- a/src/contexts/ThemeContext/ThemeProvider.tsx +++ b/src/contexts/ThemeContext/ThemeProvider.tsx @@ -1,63 +1,64 @@ -type Theme = "dark" | "light" | "system"; - -type ThemeProviderProps = { - children: React.ReactNode; - defaultTheme?: Theme; - storageKey?: string; -}; - -type ThemeProviderState = { - theme: Theme; - setTheme: (theme: Theme) => void; -}; - -const initialState: ThemeProviderState = { - theme: "system", - setTheme: () => null, -}; - -export const ThemeProviderContext = createContext(initialState); - -export function ThemeProvider({ - children, - defaultTheme = "dark", - storageKey = "vilm-theme", - ...props -}: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme - ); - - useEffect(() => { - const root = window.document.documentElement; - - root.classList.remove("light", "dark"); - - if (theme === "system") { - const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") - .matches - ? "dark" - : "light"; - - root.classList.add(systemTheme); - return; - } - - root.classList.add(theme); - }, [theme]); - - const value = { - theme, - setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme); - setTheme(theme); - }, - }; - - return ( - - {children} - - ); -} - +type Theme = "dark" | "light" | "system"; + +type ThemeProviderProps = { + children: React.ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +}; + +export const ThemeProviderContext = + createContext(initialState); + +export function ThemeProvider({ + children, + defaultTheme = "dark", + storageKey = "vilm-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + diff --git a/src/contexts/ThemeContext/useTheme.ts b/src/contexts/ThemeContext/useTheme.ts index b4afdf5..633acb8 100644 --- a/src/contexts/ThemeContext/useTheme.ts +++ b/src/contexts/ThemeContext/useTheme.ts @@ -1,8 +1,8 @@ -export const useTheme = () => { - const context = useContext(ThemeProviderContext); - - if (context === undefined) - throw new Error("useTheme must be used within a ThemeProvider"); - - return context; -}; +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider"); + + return context; +}; diff --git a/src/layouts/WatchlistLayout.tsx b/src/layouts/WatchlistLayout.tsx index 0309a44..b5dd10a 100644 --- a/src/layouts/WatchlistLayout.tsx +++ b/src/layouts/WatchlistLayout.tsx @@ -1,38 +1,45 @@ -import React from 'react' - -export default function WatchlistLayout({ children }: { - children: React.ReactNode -}) { - const { pathname } = useLocation(); - const isActivePath = (path: string) => { - return pathname.includes(path); - } - const paths = [ - { - location: "/watchlist/movie", - label: 'Movies' - }, - { - location: "/watchlist/tv", - label: 'Tv Shows' - }, - ] - - return ( -
-
-

Watchlist

-
- {paths.map((path, index) => { - return ( - - ) - })} -
-
- {children} -
- ) -} +import React from "react"; + +export default function WatchlistLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { pathname } = useLocation(); + const isActivePath = (path: string) => { + return pathname.includes(path); + }; + const paths = [ + { + location: "/watchlist/movie", + label: "Movies", + }, + { + location: "/watchlist/tv", + label: "Tv Shows", + }, + ]; + + return ( +
+
+

Watchlist

+
+ {paths.map((path, index) => { + return ( + + ); + })} +
+
+ {children} +
+ ); +} + diff --git a/src/lib/utils.ts b/src/lib/utils.ts index d084cca..4355309 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,6 +1,7 @@ -import { type ClassValue, clsx } from "clsx" -import { twMerge } from "tailwind-merge" - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)) -} +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + diff --git a/src/types/media.ts b/src/types/media.ts index c495877..d15963a 100644 --- a/src/types/media.ts +++ b/src/types/media.ts @@ -1,48 +1,48 @@ -/** Images */ - -export interface Images { - backdrops: Media[]; - id: number; - logos: Media[]; - posters: Media[]; -} - -export interface Media { - aspect_ratio: number; - height: number; - iso_639_1: string; - file_path: string; - vote_average: number; - vote_count: number; - width: number; -} - -/** Videos */ - -export interface Video { - iso_639_1: string; - iso_3166_1: string; - name: string; - key: string; - site: string; - size: number; - type: string; - official: boolean; - published_at: string; - id: string; -} -export type VideoType = - | `${Video["type"]}` - | "Clip" - | "Featurette" - | "Teaser" - | "Trailer"; - -export type GroupedVideo = { - [key in string & VideoType]: Video[]; -}; - -export interface Genre { - id: number; - name: string; -} +/** Images */ + +export interface Images { + backdrops: Media[]; + id: number; + logos: Media[]; + posters: Media[]; +} + +export interface Media { + aspect_ratio: number; + height: number; + iso_639_1: string; + file_path: string; + vote_average: number; + vote_count: number; + width: number; +} + +/** Videos */ + +export interface Video { + iso_639_1: string; + iso_3166_1: string; + name: string; + key: string; + site: string; + size: number; + type: string; + official: boolean; + published_at: string; + id: string; +} +export type VideoType = + | `${Video["type"]}` + | "Clip" + | "Featurette" + | "Teaser" + | "Trailer"; + +export type GroupedVideo = { + [key in string & VideoType]: Video[]; +}; + +export interface Genre { + id: number; + name: string; +} diff --git a/src/types/movie.ts b/src/types/movie.ts index 43297d7..fd9c99c 100644 --- a/src/types/movie.ts +++ b/src/types/movie.ts @@ -1,62 +1,63 @@ -import type { SimpleBaseMedia } from "@/components/BackdropCard"; -import type { Genre } from "./media"; - -export interface Movie { - adult: boolean; - backdrop_path: string; - belongs_to_collection?: BelongsToCollection; - budget: number; - genres: Genre[]; - homepage: string; - id: number; - imdb_id: string; - original_language: string; - original_title: string; - overview: string; - popularity: number; - poster_path: string; - production_companies: ProductionCompany[]; - production_countries: ProductionCountry[]; - release_date: string; - revenue: number; - runtime: number; - spoken_languages: SpokenLanguage[]; - status: string; - tagline: string; - title: string; - video: boolean; - vote_average: number; - vote_count: number; -} - -export interface BelongsToCollection { - id: number; - name: string; - poster_path: string; - backdrop_path: string; -} - -export interface ProductionCompany { - id: number; - logo_path: string; - name: string; - origin_country: string; -} - -export interface ProductionCountry { - iso_3166_1: string; - name: string; -} - -export interface SpokenLanguage { - english_name: string; - iso_639_1: string; - name: string; -} - -export interface SimpleMovie extends SimpleBaseMedia { - original_title: string; - release_date: string; - title: string; - video: boolean; -} +import type { SimpleBaseMedia } from "@/components/BackdropCard"; +import type { Genre } from "./media"; + +export interface Movie { + adult: boolean; + backdrop_path: string; + belongs_to_collection?: BelongsToCollection; + budget: number; + genres: Genre[]; + homepage: string; + id: number; + imdb_id: string; + original_language: string; + original_title: string; + overview: string; + popularity: number; + poster_path: string; + production_companies: ProductionCompany[]; + production_countries: ProductionCountry[]; + release_date: string; + revenue: number; + runtime: number; + spoken_languages: SpokenLanguage[]; + status: string; + tagline: string; + title: string; + video: boolean; + vote_average: number; + vote_count: number; +} + +export interface BelongsToCollection { + id: number; + name: string; + poster_path: string; + backdrop_path: string; +} + +export interface ProductionCompany { + id: number; + logo_path: string; + name: string; + origin_country: string; +} + +export interface ProductionCountry { + iso_3166_1: string; + name: string; +} + +export interface SpokenLanguage { + english_name: string; + iso_639_1: string; + name: string; +} + +export interface SimpleMovie extends SimpleBaseMedia { + original_title: string; + release_date: string; + title: string; + video: boolean; +} + diff --git a/src/types/providers.ts b/src/types/providers.ts index 4c76749..5051687 100644 --- a/src/types/providers.ts +++ b/src/types/providers.ts @@ -1,30 +1,31 @@ -export interface ProvidersResponse { - id: number; - results: Provider; -} - -export interface Provider { - [country: string]: CountryData; -} - -export interface ProviderInfo { - logo_path: string; - provider_id: number; - provider_name: string; - display_priority: number; -} - -export interface CountryData { - link?: string; - rent?: ProviderInfo[]; - buy?: ProviderInfo[]; - ads?: ProviderInfo[]; - flatrate?: ProviderInfo[]; -} - -export interface WatchType { - rent?: ProviderInfo[]; - buy?: ProviderInfo[]; - ads?: ProviderInfo[]; - flatrate?: ProviderInfo[]; -} +export interface ProvidersResponse { + id: number; + results: Provider; +} + +export interface Provider { + [country: string]: CountryData; +} + +export interface ProviderInfo { + logo_path: string; + provider_id: number; + provider_name: string; + display_priority: number; +} + +export interface CountryData { + link?: string; + rent?: ProviderInfo[]; + buy?: ProviderInfo[]; + ads?: ProviderInfo[]; + flatrate?: ProviderInfo[]; +} + +export interface WatchType { + rent?: ProviderInfo[]; + buy?: ProviderInfo[]; + ads?: ProviderInfo[]; + flatrate?: ProviderInfo[]; +} + diff --git a/src/types/response.ts b/src/types/response.ts index 0ad9592..de4a2db 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -1,86 +1,87 @@ -import type { Images, Video } from "./media"; -import type { Movie } from "./movie"; -import type { ProvidersResponse } from "./providers"; -import type { Tv } from "./tv"; - -export interface Response { - page: number; - results: T; - total_pages?: number; - total_results?: number; -} - -export interface MovieTv { - adult: boolean; - backdrop_path: string; - id: number; - title?: string; - original_language: string; - original_title?: string; - overview: string; - poster_path: string; - media_type: string; - genre_ids: number[]; - popularity: number; - release_date?: string; - video?: boolean; - vote_average: number; - vote_count: number; - name?: string; - original_name?: string; - first_air_date?: string; - origin_country?: string[]; -} - -export interface SimilarTv { - adult: boolean; - backdrop_path: string; - genre_ids: number[]; - id: number; - origin_country: string[]; - original_language: string; - original_name: string; - overview: string; - popularity: number; - poster_path: string; - first_air_date: string; - name: string; - vote_average: number; - vote_count: number; -} - -export interface SimilarMovie { - adult: boolean; - backdrop_path: string; - genre_ids: number[]; - id: number; - original_language: string; - original_title: string; - overview: string; - popularity: number; - poster_path: string; - release_date: string; - title: string; - video: boolean; - vote_average: number; - vote_count: number; -} - -export interface AccountStates { - id: number; - favorite: boolean; - rated: boolean; - watchlist: boolean; -} - -export interface AppendResponse { - images: Images; - videos: Response; - account_states: AccountStates; - "watch/providers": ProvidersResponse; - similar: Response; -} - -export interface SimilarMixed extends SimilarMovie, SimilarTv {} -export interface MovieResponse extends Movie, AppendResponse {} -export interface TvResponse extends Tv, AppendResponse {} +import type { Images, Video } from "./media"; +import type { Movie } from "./movie"; +import type { ProvidersResponse } from "./providers"; +import type { Tv } from "./tv"; + +export interface Response { + page: number; + results: T; + total_pages?: number; + total_results?: number; +} + +export interface MovieTv { + adult: boolean; + backdrop_path: string; + id: number; + title?: string; + original_language: string; + original_title?: string; + overview: string; + poster_path: string; + media_type: string; + genre_ids: number[]; + popularity: number; + release_date?: string; + video?: boolean; + vote_average: number; + vote_count: number; + name?: string; + original_name?: string; + first_air_date?: string; + origin_country?: string[]; +} + +export interface SimilarTv { + adult: boolean; + backdrop_path: string; + genre_ids: number[]; + id: number; + origin_country: string[]; + original_language: string; + original_name: string; + overview: string; + popularity: number; + poster_path: string; + first_air_date: string; + name: string; + vote_average: number; + vote_count: number; +} + +export interface SimilarMovie { + adult: boolean; + backdrop_path: string; + genre_ids: number[]; + id: number; + original_language: string; + original_title: string; + overview: string; + popularity: number; + poster_path: string; + release_date: string; + title: string; + video: boolean; + vote_average: number; + vote_count: number; +} + +export interface AccountStates { + id: number; + favorite: boolean; + rated: boolean; + watchlist: boolean; +} + +export interface AppendResponse { + images: Images; + videos: Response; + account_states: AccountStates; + "watch/providers": ProvidersResponse; + similar: Response; +} + +export interface SimilarMixed extends SimilarMovie, SimilarTv {} +export interface MovieResponse extends Movie, AppendResponse {} +export interface TvResponse extends Tv, AppendResponse {} + diff --git a/src/types/tv.ts b/src/types/tv.ts index 8bd5ff9..8d6c0c6 100644 --- a/src/types/tv.ts +++ b/src/types/tv.ts @@ -1,120 +1,121 @@ -import type { SimpleBaseMedia } from "@/components/BackdropCard"; -import type { Genre } from "./media"; - -export interface Tv { - adult: boolean; - backdrop_path: string; - created_by: CreatedBy[]; - episode_run_time: number[]; - first_air_date: string; - genres: Genre[]; - homepage: string; - id: number; - in_production: boolean; - languages: string[]; - last_air_date: string; - last_episode_to_air: LastEpisodeToAir; - name: string; - next_episode_to_air: NextEpisodeToAir; - networks: Network[]; - number_of_episodes: number; - number_of_seasons: number; - origin_country: string[]; - original_language: string; - original_name: string; - overview: string; - popularity: number; - poster_path: string; - production_companies: ProductionCompany[]; - production_countries: ProductionCountry[]; - seasons: Season[]; - spoken_languages: SpokenLanguage[]; - status: string; - tagline: string; - type: string; - vote_average: number; - vote_count: number; -} - -export interface CreatedBy { - id: number; - credit_id: string; - name: string; - gender: number; - profile_path?: string; -} - -export interface LastEpisodeToAir { - id: number; - name: string; - overview: string; - vote_average: number; - vote_count: number; - air_date: string; - episode_number: number; - episode_type: string; - production_code: string; - runtime: number; - season_number: number; - show_id: number; - still_path: string; -} - -export interface NextEpisodeToAir { - id: number; - name: string; - overview: string; - vote_average: number; - vote_count: number; - air_date: string; - episode_number: number; - episode_type: string; - production_code: string; - runtime: number; - season_number: number; - show_id: number; - still_path: string; -} - -export interface Network { - id: number; - logo_path: string; - name: string; - origin_country: string; -} - -export interface ProductionCompany { - id: number; - logo_path?: string; - name: string; - origin_country: string; -} - -export interface ProductionCountry { - iso_3166_1: string; - name: string; -} - -export interface Season { - air_date: string; - episode_count: number; - id: number; - name: string; - overview: string; - poster_path: string; - season_number: number; - vote_average: number; -} - -export interface SpokenLanguage { - english_name: string; - iso_639_1: string; - name: string; -} - -export interface SimpleTv extends SimpleBaseMedia { - origin_country: string[]; - original_name: string; - first_air_date: string; - name: string; -} +import type { SimpleBaseMedia } from "@/components/BackdropCard"; +import type { Genre } from "./media"; + +export interface Tv { + adult: boolean; + backdrop_path: string; + created_by: CreatedBy[]; + episode_run_time: number[]; + first_air_date: string; + genres: Genre[]; + homepage: string; + id: number; + in_production: boolean; + languages: string[]; + last_air_date: string; + last_episode_to_air: LastEpisodeToAir; + name: string; + next_episode_to_air: NextEpisodeToAir; + networks: Network[]; + number_of_episodes: number; + number_of_seasons: number; + origin_country: string[]; + original_language: string; + original_name: string; + overview: string; + popularity: number; + poster_path: string; + production_companies: ProductionCompany[]; + production_countries: ProductionCountry[]; + seasons: Season[]; + spoken_languages: SpokenLanguage[]; + status: string; + tagline: string; + type: string; + vote_average: number; + vote_count: number; +} + +export interface CreatedBy { + id: number; + credit_id: string; + name: string; + gender: number; + profile_path?: string; +} + +export interface LastEpisodeToAir { + id: number; + name: string; + overview: string; + vote_average: number; + vote_count: number; + air_date: string; + episode_number: number; + episode_type: string; + production_code: string; + runtime: number; + season_number: number; + show_id: number; + still_path: string; +} + +export interface NextEpisodeToAir { + id: number; + name: string; + overview: string; + vote_average: number; + vote_count: number; + air_date: string; + episode_number: number; + episode_type: string; + production_code: string; + runtime: number; + season_number: number; + show_id: number; + still_path: string; +} + +export interface Network { + id: number; + logo_path: string; + name: string; + origin_country: string; +} + +export interface ProductionCompany { + id: number; + logo_path?: string; + name: string; + origin_country: string; +} + +export interface ProductionCountry { + iso_3166_1: string; + name: string; +} + +export interface Season { + air_date: string; + episode_count: number; + id: number; + name: string; + overview: string; + poster_path: string; + season_number: number; + vote_average: number; +} + +export interface SpokenLanguage { + english_name: string; + iso_639_1: string; + name: string; +} + +export interface SimpleTv extends SimpleBaseMedia { + origin_country: string[]; + original_name: string; + first_air_date: string; + name: string; +} + diff --git a/src/utils/get-providers.ts b/src/utils/get-providers.ts index de30693..3343681 100644 --- a/src/utils/get-providers.ts +++ b/src/utils/get-providers.ts @@ -1,110 +1,111 @@ -import type { - CountryData, - Provider, - ProviderInfo, - WatchType, -} from "@/types/providers"; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function uniqBy(arr: T[], key: (item: T) => any): T[] { - const seen = new Set(); - return arr.filter((item) => { - const k = key(item); - return seen.has(k) ? false : seen.add(k); - }); -} - -function getProviders(results: Provider) { - const providers: WatchType = { - buy: [], - rent: [], - ads: [], - flatrate: [], - }; - - for (const country in results) { - const countryData = results[country]; - if (countryData.rent) { - providers.rent = providers.rent?.concat( - uniqBy(countryData.rent, (item) => item.provider_name) - ); - } - if (countryData.ads) { - providers.ads = providers.rent?.concat( - uniqBy(countryData.ads, (item) => item.provider_name) - ); - } - if (countryData.flatrate) { - providers.flatrate = providers.rent?.concat( - uniqBy(countryData.flatrate, (item) => item.provider_name) - ); - } - if (countryData.buy) { - providers.buy = providers.buy?.concat( - uniqBy(countryData.buy, (item) => item.provider_name) - ); - } - } - - return filterUniqueProviders(providers); -} - -function filterUniqueProviders(data: CountryData): WatchType { - const filteredProviders: WatchType = {}; - - if (data.buy) { - filteredProviders.buy = data.buy.reduce((acc: ProviderInfo[], current) => { - const existingIndex = acc.findIndex( - (item: ProviderInfo) => item.provider_id === current.provider_id - ); - if (existingIndex === -1) { - acc.push(current); - } - return acc; - }, []); - } - - if (data.rent) { - filteredProviders.rent = data.rent.reduce( - (acc: ProviderInfo[], current) => { - const existingIndex = acc.findIndex( - (item: ProviderInfo) => item.provider_id === current.provider_id - ); - if (existingIndex === -1) { - acc.push(current); - } - return acc; - }, - [] - ); - } - if (data.ads) { - filteredProviders.ads = data.ads.reduce((acc: ProviderInfo[], current) => { - const existingIndex = acc.findIndex( - (item: ProviderInfo) => item.provider_id === current.provider_id - ); - if (existingIndex === -1) { - acc.push(current); - } - return acc; - }, []); - } - if (data.flatrate) { - filteredProviders.flatrate = data.flatrate.reduce( - (acc: ProviderInfo[], current) => { - const existingIndex = acc.findIndex( - (item) => item.provider_id === current.provider_id - ); - if (existingIndex === -1) { - acc.push(current); - } - return acc; - }, - [] - ); - } - - return filteredProviders; -} - -export default getProviders; +import type { + CountryData, + Provider, + ProviderInfo, + WatchType, +} from "@/types/providers"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function uniqBy(arr: T[], key: (item: T) => any): T[] { + const seen = new Set(); + return arr.filter((item) => { + const k = key(item); + return seen.has(k) ? false : seen.add(k); + }); +} + +function getProviders(results: Provider) { + const providers: WatchType = { + buy: [], + rent: [], + ads: [], + flatrate: [], + }; + + for (const country in results) { + const countryData = results[country]; + if (countryData.rent) { + providers.rent = providers.rent?.concat( + uniqBy(countryData.rent, (item) => item.provider_name) + ); + } + if (countryData.ads) { + providers.ads = providers.rent?.concat( + uniqBy(countryData.ads, (item) => item.provider_name) + ); + } + if (countryData.flatrate) { + providers.flatrate = providers.rent?.concat( + uniqBy(countryData.flatrate, (item) => item.provider_name) + ); + } + if (countryData.buy) { + providers.buy = providers.buy?.concat( + uniqBy(countryData.buy, (item) => item.provider_name) + ); + } + } + + return filterUniqueProviders(providers); +} + +function filterUniqueProviders(data: CountryData): WatchType { + const filteredProviders: WatchType = {}; + + if (data.buy) { + filteredProviders.buy = data.buy.reduce((acc: ProviderInfo[], current) => { + const existingIndex = acc.findIndex( + (item: ProviderInfo) => item.provider_id === current.provider_id + ); + if (existingIndex === -1) { + acc.push(current); + } + return acc; + }, []); + } + + if (data.rent) { + filteredProviders.rent = data.rent.reduce( + (acc: ProviderInfo[], current) => { + const existingIndex = acc.findIndex( + (item: ProviderInfo) => item.provider_id === current.provider_id + ); + if (existingIndex === -1) { + acc.push(current); + } + return acc; + }, + [] + ); + } + if (data.ads) { + filteredProviders.ads = data.ads.reduce((acc: ProviderInfo[], current) => { + const existingIndex = acc.findIndex( + (item: ProviderInfo) => item.provider_id === current.provider_id + ); + if (existingIndex === -1) { + acc.push(current); + } + return acc; + }, []); + } + if (data.flatrate) { + filteredProviders.flatrate = data.flatrate.reduce( + (acc: ProviderInfo[], current) => { + const existingIndex = acc.findIndex( + (item) => item.provider_id === current.provider_id + ); + if (existingIndex === -1) { + acc.push(current); + } + return acc; + }, + [] + ); + } + + return filteredProviders; +} + +export default getProviders; + diff --git a/src/utils/get-video.ts b/src/utils/get-video.ts index 4419bbe..3351c35 100644 --- a/src/utils/get-video.ts +++ b/src/utils/get-video.ts @@ -1,37 +1,38 @@ -import type { GroupedVideo, Video, VideoType } from "@/types/media"; - -function getVideo(videos: Video[]): Video | null { - const filteredByGroup = groupBy