feat: enhance actor details page with MovieGallery and Carousel components; implement movie conversion helper for improved movie data handling

This commit is contained in:
Norbert Maciaszek 2025-08-18 01:04:59 +02:00
parent cf7ec070fd
commit 3c286e705c
8 changed files with 73 additions and 37 deletions

View File

@ -1,5 +1,10 @@
import { MovieCard } from "@/components/atoms/MovieCard";
import { ActorHero } from "@/components/molecules/ActorHero";
import { Carousel } from "@/components/molecules/Carousel";
import { MovieGallery } from "@/components/molecules/MovieGallery";
import { convertToMovie } from "@/helpers/convertToMovie";
import { TMDB } from "@/lib/tmdb";
import { FaStar } from "react-icons/fa";
export default async function Page({
params,
@ -9,10 +14,29 @@ export default async function Page({
const actorId = Number((await params).id);
const personDetails = await TMDB.getPersonDetails(actorId);
const images = {
backdrops: personDetails.images.profiles,
posters: [],
logos: [],
};
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900">
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 pb-16">
<ActorHero personDetails={personDetails} />
<MovieGallery images={images} movieTitle={personDetails.name} />
<div className="container">
<Carousel
heading={`Filmy z udziałem ${personDetails.name}`}
icon={<FaStar />}
colors="purple"
>
{personDetails.movie_credits.cast.map((movie) => {
const convertedMovie = convertToMovie(movie);
if (!convertedMovie) return null;
return <MovieCard key={movie.id} {...convertedMovie} />;
})}
</Carousel>
</div>
</div>
);
}

View File

@ -9,7 +9,7 @@ export default async function Page({
params: Promise<{ id: string }>;
}) {
const movieId = Number((await params).id);
const movieDetails = await TMDB.getMovieDetailsRich(movieId);
const movieDetails = await TMDB.getMovieDetails(movieId);
return (
<div className="min-h-screen mt-16">

View File

@ -16,7 +16,7 @@ type Props = Movie & {
};
export const MovieCard: FC<Props> = ({
layout = "minimal",
layout = "aurora",
showDayCounter = true,
simpleToggle = false,
...movie
@ -73,6 +73,8 @@ export const MovieCard: FC<Props> = ({
simpleToggle,
buttonClass,
iconSize,
favorite: alreadyInStore?.favorite || movie.favorite,
seen: alreadyInStore?.seen || movie.seen,
};
switch (layout) {

View File

@ -65,7 +65,7 @@ export const AuroraLayout: FC<AuroraLayoutProps> = ({
>
<Link href={`/film/${id}`}>
<img
className="w-full h-full object-cover transition-all duration-700 group-hover:scale-110 group-hover:brightness-110"
className="w-full h-full object-cover transition-all duration-700 group-hover:scale-110 group-hover:brightness-110 bg-gradient-to-b from-purple-600/50 to-emerald-600"
src={`http://image.tmdb.org/t/p/w342${poster_path}`}
alt={title}
/>

View File

@ -202,6 +202,7 @@ export const ActorHero: FC<Props> = ({ personDetails }) => {
];
return (
<a
key={key}
href={url(value as string)}
target="_blank"
rel="noopener noreferrer"
@ -257,14 +258,4 @@ const externalIdsMap = {
icon: <FaImdb />,
url: (id: string) => `https://www.imdb.com/name/${id}`,
},
tvrage_id: {
label: "TVRage",
icon: null,
url: (id: string) => `https://www.tvrage.com/people/${id}`,
},
wikidata_id: {
label: "Wikidata",
icon: null,
url: (id: string) => `https://www.wikidata.org/wiki/${id}`,
},
};

View File

@ -13,6 +13,7 @@ import {
FaGlobe,
FaEye,
} from "react-icons/fa";
import { convertToMovie } from "@/helpers/convertToMovie";
type Props = {
movieDetails: MovieDetailsRich;
@ -33,37 +34,24 @@ export const HeroMovie: FC<Props> = ({ movieDetails }) => {
return `${hours}h ${mins}m`;
};
// Convert TMDB movie to our Movie type.
const convertToMovie = () => ({
id: movieDetails.id,
title: movieDetails.title,
adult: movieDetails.adult,
backdrop_path: movieDetails.backdrop_path || "",
genre_ids: movieDetails.genres.map((g) => g.id).join(","),
original_language: movieDetails.original_language,
original_title: movieDetails.original_title,
overview: movieDetails.overview || "",
popularity: movieDetails.popularity,
poster_path: movieDetails.poster_path || "",
release_date: movieDetails.release_date,
video: movieDetails.video,
vote_average: movieDetails.vote_average,
vote_count: movieDetails.vote_count,
favorite: false,
seen: false,
});
const convertedMovie = convertToMovie(movieDetails);
// Convert TMDB movie to our Movie type.
const handleAddToList = () => {
if (isInStore) {
deleteMovie(movieDetails.id);
} else {
addMovie(convertToMovie());
if (convertedMovie) {
addMovie(convertedMovie);
}
}
};
const handleToggleFavorite = () => {
if (!isInStore) {
addMovie({ ...convertToMovie(), favorite: true });
if (convertedMovie) {
addMovie({ ...convertedMovie, favorite: true });
}
} else {
updateMovie(movieDetails.id, { favorite: !isFavorite });
}
@ -71,7 +59,9 @@ export const HeroMovie: FC<Props> = ({ movieDetails }) => {
const handleToggleSeen = () => {
if (!isInStore) {
addMovie({ ...convertToMovie(), seen: true });
if (convertedMovie) {
addMovie({ ...convertedMovie, seen: true });
}
} else {
updateMovie(movieDetails.id, { seen: !isSeen });
}

View File

@ -19,7 +19,7 @@ export const RecommendedMovies: FC<Props> = ({ movies }) => {
<Carousel
heading="Rekomendowane filmy"
icon={<FaStar />}
iconColor="bg-gradient-to-r from-yellow-500 to-orange-500"
colors="yellow"
>
{movies.results.map((movie) => (
<MovieCard

View File

@ -0,0 +1,29 @@
import { Movie } from "@/types/global";
export const convertToMovie = (
movie: any,
override?: Partial<Movie>
): Movie | null => {
if (!movie.id) {
return null;
}
return {
id: movie.id,
title: movie.title,
adult: movie.adult,
backdrop_path: movie.backdrop_path || "",
genre_ids: movie.genres?.join(",") || "",
original_language: movie.original_language,
original_title: movie.original_title,
overview: movie.overview || "",
popularity: movie.popularity,
poster_path: movie.poster_path || "",
release_date: movie.release_date,
video: movie.video,
vote_average: movie.vote_average,
vote_count: movie.vote_count,
favorite: false,
seen: false,
};
};