Comments
{commentsLoading ? (
Loading comments...
diff --git a/frontend/src/pages/TopRated.jsx b/frontend/src/pages/TopRated.jsx
new file mode 100644
index 0000000..6658edf
--- /dev/null
+++ b/frontend/src/pages/TopRated.jsx
@@ -0,0 +1,181 @@
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { motion } from "framer-motion";
+import { getTopRatedFilms } from "../api/films";
+
+const img = {
+ poster: (path) => path ? `https://image.tmdb.org/t/p/w500${path}` : '/images/no-image-300x450.png',
+};
+
+const ratingOf = (m) => {
+ const r = m?.rating ?? m?.vote_average;
+ const n = parseFloat(r);
+ return !isNaN(n) ? n.toFixed(1) : "N/A";
+};
+
+const yearOf = (m) => (m?.release_date ? m.release_date.slice(0, 4) : "—");
+
+function MovieCard({ movie, onClick, index }) {
+ const rating = ratingOf(movie);
+ const numericRating = parseFloat(rating);
+
+ // Rating color based on score
+ const getRatingColor = (rating) => {
+ if (rating >= 8.0) return "text-green-600 dark:text-green-400";
+ if (rating >= 7.0) return "text-yellow-600 dark:text-yellow-400";
+ return "text-orange-600 dark:text-orange-400";
+ };
+
+ return (
+
onClick?.(movie)}
+ className="group text-left rounded-xl border border-gray-200/50 dark:border-white/10 bg-white/60 dark:bg-gray-800/40 hover:bg-white/80 dark:hover:bg-gray-800/60 hover:border-gray-300 dark:hover:border-white/20 hover:shadow-lg transition-all duration-200 overflow-hidden relative"
+ whileHover={{ y: -2 }}
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ delay: index * 0.1 }}
+ >
+
+
})
+ {/* Ranking badge */}
+
+ #{index + 1}
+
+ {/* Rating badge */}
+
+ ⭐ {rating}
+
+ {/* Quality indicator for high ratings */}
+ {numericRating >= 8.0 && (
+
+ 🏆 CLASSIC
+
+ )}
+
+
+
{movie.title}
+
{yearOf(movie)}
+
+
+ ⭐ {rating}
+
+ {movie.vote_count && (
+
+ {movie.vote_count.toLocaleString()} votes
+
+ )}
+
+ {/* Genre display if available */}
+ {movie.genre_names && movie.genre_names.length > 0 && (
+
+
+ {movie.genre_names[0]}
+
+
+ )}
+
+
+ );
+}
+
+function SkeletonCard() {
+ return (
+
+ );
+}
+
+export default function TopRated() {
+ const navigate = useNavigate();
+ const [movies, setMovies] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchTopRatedMovies = async () => {
+ try {
+ setLoading(true);
+ const response = await getTopRatedFilms();
+ setMovies(response.data || []);
+ } catch (err) {
+ console.error('Error fetching top rated movies:', err);
+ setError('Failed to load top rated movies');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTopRatedMovies();
+ }, []);
+
+ if (error) {
+ return (
+
+
+
+
Top Rated Movies
+
{error}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ 🏆 Top Rated Movies
+
+
+ The highest-rated movies of all time, curated by our community and critics
+
+
+
+ {loading ? (
+
+ {Array.from({ length: 18 }).map((_, i) => (
+
+ ))}
+
+ ) : movies.length === 0 ? (
+
+
🏆
+
No Top Rated Movies Found
+
+ Something went wrong loading the top rated movies. Please try again later.
+
+
+
+ ) : (
+
+ {movies.map((movie, index) => (
+ navigate(`/movies/${m.id}`)}
+ />
+ ))}
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/TopTrending.jsx b/frontend/src/pages/TopTrending.jsx
new file mode 100644
index 0000000..5e2bec4
--- /dev/null
+++ b/frontend/src/pages/TopTrending.jsx
@@ -0,0 +1,156 @@
+import React, { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { motion } from "framer-motion";
+import { getTrendingFilms } from "../api/films";
+
+const img = {
+ poster: (path) => path ? `https://image.tmdb.org/t/p/w500${path}` : '/images/no-image-300x450.png',
+};
+
+const ratingOf = (m) => {
+ const r = m?.rating ?? m?.vote_average;
+ const n = parseFloat(r);
+ return !isNaN(n) ? n.toFixed(1) : "N/A";
+};
+
+const yearOf = (m) => (m?.release_date ? m.release_date.slice(0, 4) : "—");
+
+function MovieCard({ movie, onClick, index }) {
+ return (
+
onClick?.(movie)}
+ className="group text-left rounded-xl border border-gray-200/50 dark:border-white/10 bg-white/60 dark:bg-gray-800/40 hover:bg-white/80 dark:hover:bg-gray-800/60 hover:border-gray-300 dark:hover:border-white/20 hover:shadow-lg transition-all duration-200 overflow-hidden relative"
+ whileHover={{ y: -2 }}
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ delay: index * 0.1 }}
+ >
+
+
})
+ {/* Trending badge */}
+
+ #{index + 1}
+
+
+ 🔥 Trending
+
+
+
+
{movie.title}
+
{yearOf(movie)}
+
+
+ ⭐ {ratingOf(movie)}
+
+ {movie.popularity && (
+
+ {Math.round(movie.popularity)} views
+
+ )}
+
+
+
+ );
+}
+
+function SkeletonCard() {
+ return (
+
+ );
+}
+
+export default function TopTrending() {
+ const navigate = useNavigate();
+ const [movies, setMovies] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchTrendingMovies = async () => {
+ try {
+ setLoading(true);
+ const response = await getTrendingFilms();
+ setMovies(response.data || []);
+ } catch (err) {
+ console.error('Error fetching trending movies:', err);
+ setError('Failed to load trending movies');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchTrendingMovies();
+ }, []);
+
+ if (error) {
+ return (
+
+
+
+
Top Trending
+
{error}
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ 🔥 Top Trending Movies
+
+
+ The hottest movies everyone is talking about right now
+
+
+
+ {loading ? (
+
+ {Array.from({ length: 18 }).map((_, i) => (
+
+ ))}
+
+ ) : movies.length === 0 ? (
+
+
🔥
+
No Trending Movies Found
+
+ Something went wrong loading the trending movies. Please try again later.
+
+
+
+ ) : (
+
+ {movies.map((movie, index) => (
+ navigate(`/movies/${m.id}`)}
+ />
+ ))}
+
+ )}
+
+
+ );
+}
\ No newline at end of file