web / frontend /src /pages /MovieDetailPage.tsx
Chandima Prabhath
Track bun.lockb with Git LFS
cc2caf9
raw
history blame
8.92 kB
import React, { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import { Play, Plus, ThumbsUp, Share2 } from 'lucide-react';
import { getMovieMetadata, getGenresItems, getMovieCard } from '../lib/api';
import ContentRow from '../components/ContentRow';
import { useToast } from '@/hooks/use-toast';
const MovieDetailPage = () => {
const { title } = useParams<{ title: string }>();
const [movie, setMovie] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [similarMovies, setSimilarMovies] = useState<any[]>([]);
const { toast } = useToast();
useEffect(() => {
const fetchMovieData = async () => {
if (!title) return;
try {
setLoading(true);
const data = await getMovieMetadata(title);
setMovie(data);
const movieData = data.data;
console.log(movieData);
// Fetch similar movies based on individual genres
if (movieData.genres && movieData.genres.length > 0) {
const currentMovieName = movieData.name;
const moviesByGenre = await Promise.all(
movieData.genres.map(async (genre: any) => {
// Pass a single genre name for each call
const genreResult = await getGenresItems([genre.name], 'movie', 10, 1);
if (genreResult.movies && Array.isArray(genreResult.movies)) {
return genreResult.movies.map((movieItem: any) => {
const { title: similarTitle } = movieItem;
// Skip current movie
if (similarTitle === currentMovieName) return null;
return {
type: 'movie',
title: similarTitle,
};
});
}
return [];
})
);
// Flatten the array of arrays and remove null results
const flattenedMovies = moviesByGenre.flat().filter(Boolean);
// Remove duplicates based on the title
const uniqueMovies = Array.from(
new Map(flattenedMovies.map(movie => [movie.title, movie])).values()
);
setSimilarMovies(uniqueMovies);
}
} catch (error) {
console.error(`Error fetching movie details for ${title}:`, error);
toast({
title: "Error loading movie details",
description: "Please try again later",
variant: "destructive"
});
} finally {
setLoading(false);
}
};
fetchMovieData();
}, [title, toast]);
if (loading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-netflix-red"></div>
</div>
);
}
if (!movie) {
return (
<div className="pt-24 px-4 md:px-8 text-center min-h-screen">
<h1 className="text-3xl font-bold mb-4">Movie Not Found</h1>
<p className="text-netflix-gray mb-6">We couldn't find the movie you're looking for.</p>
<Link to="/movies" className="bg-netflix-red px-6 py-2 rounded font-medium">
Back to Movies
</Link>
</div>
);
}
// Use movieData fields from the new structure
const movieData = movie.data;
const runtime = movieData.runtime
? `${Math.floor(movieData.runtime / 60)}h ${movieData.runtime % 60}m`
: '';
const releaseYear = movieData.year || '';
const movieName = (movieData.translations?.nameTranslations?.find((t: any) => t.language === 'eng')?.name || movieData.name || '');
const overview =
movieData.overview ||
(movieData.translations?.overviewTranslations?.find((t: any) => t.language === 'eng')?.overview || '');
return (
<div className="pb-12 animate-fade-in">
{/* Hero backdrop */}
<div className="relative w-full h-[500px] md:h-[600px]">
<div className="absolute inset-0">
<img
src={movieData.image}
alt={movieName}
className="w-full h-full object-cover"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = '/placeholder.svg';
}}
/>
<div className="absolute inset-0 bg-gradient-to-t from-netflix-black via-netflix-black/60 to-transparent" />
<div className="absolute inset-0 bg-gradient-to-r from-netflix-black/80 via-netflix-black/40 to-transparent" />
</div>
</div>
{/* Movie details */}
<div className="px-4 md:px-8 -mt-60 relative z-10 max-w-7xl mx-auto">
<div className="flex flex-col md:flex-row gap-8">
{/* Poster */}
<div className="flex-shrink-0 hidden md:block">
<img
src={movieData.image}
alt={movieName}
className="w-64 h-96 object-cover rounded-md shadow-lg"
onError={(e) => {
const target = e.target as HTMLImageElement;
target.src = '/placeholder.svg';
}}
/>
</div>
{/* Details */}
<div className="flex-grow">
<h1 className="text-3xl md:text-4xl lg:text-5xl font-bold mb-3">{movieName}</h1>
<div className="flex flex-wrap items-center text-sm text-gray-300 mb-6">
{releaseYear && <span className="mr-3">{releaseYear}</span>}
{runtime && <span className="mr-3">{runtime}</span>}
{movieData.contentRatings && movieData.contentRatings.length > 0 && (
<span className="mr-3 bg-netflix-red/80 px-2 py-0.5 rounded text-xs">
{movieData.contentRatings[0].name}+
</span>
)}
</div>
<div className="flex flex-wrap items-center gap-2 my-4">
{movieData.genres &&
movieData.genres.map((genre: any) => (
<Link
key={genre.id}
to={`/movies?genre=${genre.name}`}
className="px-3 py-1 bg-netflix-gray/20 rounded-full text-sm hover:bg-netflix-gray/40 transition"
>
{genre.name}
</Link>
))}
</div>
<p className="text-gray-300 mb-8 max-w-3xl">{overview}</p>
<div className="flex flex-wrap gap-3 mb-8">
<Link
to={`/movie/${encodeURIComponent(title!)}/watch`}
className="flex items-center px-6 py-2 rounded bg-netflix-red text-white font-semibold hover:bg-red-700 transition"
>
<Play className="w-5 h-5 mr-2" /> Play
</Link>
<button className="flex items-center px-4 py-2 rounded bg-gray-700 text-white hover:bg-gray-600 transition">
<Plus className="w-5 h-5 mr-2" /> My List
</button>
<button className="flex items-center justify-center w-10 h-10 rounded-full bg-gray-700 text-white hover:bg-gray-600 transition">
<ThumbsUp className="w-5 h-5" />
</button>
<button className="flex items-center justify-center w-10 h-10 rounded-full bg-gray-700 text-white hover:bg-gray-600 transition">
<Share2 className="w-5 h-5" />
</button>
</div>
{/* Additional details */}
<div className="mb-6">
{movieData.translations?.nameTranslations?.find((t: any) => t.isPrimary) && (
<p className="text-gray-400 italic mb-4">
"{movieData.translations.nameTranslations.find((t: any) => t.isPrimary).tagline}"
</p>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{movieData.production_companies && movieData.production_companies.length > 0 && (
<div>
<h3 className="text-gray-400 font-semibold mb-1">Production</h3>
<p className="text-white">
{movieData.production_companies.map((company: any) => company.name).join(', ')}
</p>
</div>
)}
{movieData.spoken_languages && movieData.spoken_languages.length > 0 && (
<div>
<h3 className="text-gray-400 font-semibold mb-1">Languages</h3>
<p className="text-white">{movieData.spoken_languages.join(', ')}</p>
</div>
)}
</div>
</div>
</div>
</div>
{/* Similar Movies */}
{similarMovies.length > 0 && (
<div className="mt-16">
<ContentRow title="More Like This" items={similarMovies} />
</div>
)}
</div>
</div>
);
};
export default MovieDetailPage;