diff --git a/src/app/odkrywaj/gatunek/[id]/page.tsx b/src/app/odkrywaj/gatunek/[id]/page.tsx new file mode 100644 index 0000000..ace327a --- /dev/null +++ b/src/app/odkrywaj/gatunek/[id]/page.tsx @@ -0,0 +1,163 @@ +import { getMovieGenres, getMoviesByGenre } from "@/lib/tmdb/server"; +import { MovieList } from "@/components/molecules/MovieList"; +import { FaCalendar, FaFire } from "react-icons/fa"; +import { notFound } from "next/navigation"; +import { GenreList } from "@/components/molecules/GenreList"; +import { BackButton } from "@/components/atoms/BackButton"; + +// 6 hours cache for genre pages. +export const revalidate = 21600; + +type PageProps = { + params: Promise<{ id: string }>; +}; + +export default async function GenrePage({ params }: PageProps) { + const { id } = await params; + const genreId = parseInt(id); + + if (isNaN(genreId)) { + notFound(); + } + + const { genres } = await getMovieGenres(); + const genre = genres.find((g) => g.id === genreId); + + if (!genre) { + notFound(); + } + + const now = new Date(); + + // Get genre name and all movie data in parallel. + const [recentData, upcomingData, topRatedData] = await Promise.all([ + getMoviesByGenre(genreId, 1, { + sort_by: "release_date.desc", + ["release_date.lte"]: `${now.getFullYear()}-${ + now.getMonth() + 1 + }-${now.getDate()}`, + }), + getMoviesByGenre(genreId, 1, { + sort_by: "release_date.asc", + ["release_date.gte"]: `${now.getFullYear()}-${now.getMonth() + 1}-${ + now.getDate() + 1 + } `, + }), + getMoviesByGenre(genreId, 1, { + sort_by: "", + vote_count_gte: "100", + ["release_date.lte"]: `${now.getFullYear()}-${ + now.getMonth() + 1 + }-${now.getDate()}`, + }), + ]); + + // Convert TMDB movie format to our Movie type. + const convertMovies = (movies: any[]) => + movies.map((movie) => ({ + ...movie, + genre_ids: JSON.stringify(movie.genre_ids), + seen: false, + favorite: false, + })); + + const recentMovies = convertMovies(recentData.results); + const upcomingMovies = convertMovies(upcomingData.results); + const topRatedMovies = convertMovies(topRatedData.results); + + return ( + <> + {/* Hero section with genre name */} +
+ {/* Navigation */} +
+
+ +
+
+ +
+

+ {genre.name} +

+

+ Odkryj najlepsze filmy z kategorii {genre.name.toLowerCase()} +

+
+
+
+ + {/* Recent movies section */} +
+ } + colors="green" + showFilters={false} + displayType="list" + /> +
+ + {/* Top rated section */} + {upcomingMovies.length > 0 && ( +
+ } + colors="blue" + showFilters={false} + sortDirection="desc" + displayType="list" + /> +
+ )} + + {/* Top rated section */} + {topRatedMovies.length > 0 && ( +
+ } + colors="red" + showFilters={false} + displayType="list" + /> +
+ )} + + + + ); +} + +export async function generateMetadata({ params }: PageProps) { + const { id } = await params; + const genreId = parseInt(id); + + if (isNaN(genreId)) { + return { + title: "Gatunek nie znaleziony", + }; + } + + try { + const { genres } = await getMovieGenres(); + const genre = genres.find((g) => g.id === genreId); + + return { + title: genre ? `${genre.name} - Movie Box` : "Gatunek - Movie Box", + description: genre + ? `Odkryj najlepsze filmy z kategorii ${genre.name}. Najnowsze premiery, popularne tytuły i najwyżej oceniane produkcje.` + : "Przeglądaj filmy według gatunków", + }; + } catch (error) { + return { + title: "Gatunek - Movie Box", + }; + } +} diff --git a/src/components/molecules/HeroMovie/index.tsx b/src/components/molecules/HeroMovie/index.tsx index 7a2a163..cdeab8e 100644 --- a/src/components/molecules/HeroMovie/index.tsx +++ b/src/components/molecules/HeroMovie/index.tsx @@ -139,7 +139,7 @@ export const HeroMovie: FC = ({ movieDetails }) => {
- {new Date(movieDetails.release_date).getFullYear()} + {formatter.formatDate(movieDetails.release_date)}
)} diff --git a/src/components/molecules/MovieList/index.tsx b/src/components/molecules/MovieList/index.tsx index 153dda9..d588eb5 100644 --- a/src/components/molecules/MovieList/index.tsx +++ b/src/components/molecules/MovieList/index.tsx @@ -98,11 +98,10 @@ export const MovieList: FC = ({ return 0; }); - sortedMovies = sortedMovies.slice(0, loaded); - if (sortDirection === "desc") { sortedMovies = sortedMovies.reverse(); } + sortedMovies = sortedMovies.slice(0, loaded); const handleFilter = (key?: keyof typeof filter) => { setFilter({ diff --git a/src/components/molecules/TrackedMovies/index.tsx b/src/components/molecules/TrackedMovies/index.tsx index 27f5bea..e14068d 100644 --- a/src/components/molecules/TrackedMovies/index.tsx +++ b/src/components/molecules/TrackedMovies/index.tsx @@ -1,12 +1,26 @@ "use client"; import { FC } from "react"; import { useGlobalStore } from "@/app/store/globalStore"; -import Link from "next/link"; -import { FaCalendar, FaClock, FaStar } from "react-icons/fa"; +import { FaCalendar, FaClock } from "react-icons/fa"; import { MovieRow } from "@/components/atoms/MovieRow"; +import { Movie } from "@/types/global"; -export const TrackedMovies: FC = () => { - const { movies } = useGlobalStore(); +type Props = { + overrideMovies?: Movie[]; + daysLimit?: number; + labelCurrent?: string; + labelUpcoming?: string; +}; + +export const TrackedMovies: FC = ({ + overrideMovies, + daysLimit = 30, + labelCurrent = "Aktualnie w kinach", + labelUpcoming = "Nadchodzące premiery", +}) => { + const { movies: storeMovies } = useGlobalStore(); + + const movies = overrideMovies || storeMovies; if (movies.length === 0) { return null; @@ -24,7 +38,7 @@ export const TrackedMovies: FC = () => { return ( new Date(movie.release_date) <= today && - daysSinceRelease <= 30 && + daysSinceRelease <= daysLimit && !movie.seen ); }); @@ -48,7 +62,7 @@ export const TrackedMovies: FC = () => {

- Aktualnie w kinach ({sortedInCinema.length}) + {labelCurrent} ({sortedInCinema.length})

{sortedInCinema.map((movie) => ( @@ -62,7 +76,7 @@ export const TrackedMovies: FC = () => {

- Nadchodzące premiery ({sortedUpcoming.length}) + {labelUpcoming} ({sortedUpcoming.length})

{sortedUpcoming.map((movie) => ( diff --git a/src/lib/tmdb/server.ts b/src/lib/tmdb/server.ts index 7f18eca..438dd1d 100644 --- a/src/lib/tmdb/server.ts +++ b/src/lib/tmdb/server.ts @@ -89,3 +89,37 @@ export async function getPersonDetails( export async function getMovieGenres(): Promise<{ genres: Genre[] }> { return await fetchTmbd("/genre/movie/list?language=pl"); } + +type DiscoverMoviesOptions = Record; + +export async function discoverMovies( + options: DiscoverMoviesOptions +): Promise { + const params = new URLSearchParams(); + + // Default language and region for Polish users. + params.set("language", "pl-PL"); + params.set("region", "PL"); + params.set("with_original_language", "en|pl"); + + Object.entries(options).forEach(([key, value]) => { + if (value !== undefined) { + params.set(key, value.toString()); + } + }); + + return await fetchTmbd(`/discover/movie?${params.toString()}`); +} + +export async function getMoviesByGenre( + genreId: number, + page: number = 1, + options: DiscoverMoviesOptions = {} +): Promise { + return await discoverMovies({ + with_genres: genreId, + sort_by: "release_date.desc", + page, + ...options, + }); +}