From efea37cf1192f7de17286dd843b9e5ef9eac2446 Mon Sep 17 00:00:00 2001 From: chaaanuwu Date: Thu, 7 May 2026 17:19:23 +0530 Subject: [PATCH] feat: Add movie rating and review functionality in MoviePage component --- client/src/api/history.api.js | 5 + client/src/api/reviews.api.js | 4 + client/src/pages/MoviePage.jsx | 191 ++++++++++++++++++++--- server/controllers/history.controller.js | 2 +- server/controllers/review.controller.js | 20 +-- server/services/topRated.service.js | 2 - 6 files changed, 192 insertions(+), 32 deletions(-) diff --git a/client/src/api/history.api.js b/client/src/api/history.api.js index 4134769..6591a47 100644 --- a/client/src/api/history.api.js +++ b/client/src/api/history.api.js @@ -22,6 +22,11 @@ export const addWatchedMovie = async (title) => { return res; } +export const updateRating = async (movieId, rating) => { + const res = await axiosInstance.put(`/history/movie/${movieId}`, {rating}); + return res; +} + export const removeMovieFromHistory = async (movieId) => { const res = await axiosInstance.delete(`/history/movie/${movieId}`); return res; diff --git a/client/src/api/reviews.api.js b/client/src/api/reviews.api.js index 9eb3f59..6f3e0cc 100644 --- a/client/src/api/reviews.api.js +++ b/client/src/api/reviews.api.js @@ -8,6 +8,10 @@ export const getUserReviews = (userId) => { return axiosInstance.get(`/users/${userId}/reviews`); }; +export const addMovieReview = (movieId, review) => { + return axiosInstance.post(`/${movieId}/review`, review); +} + export const toggleLikeReview = (reviewId) => { return axiosInstance.patch(`/reviews/${reviewId}`); }; \ No newline at end of file diff --git a/client/src/pages/MoviePage.jsx b/client/src/pages/MoviePage.jsx index 27f1c6e..be6a7c3 100644 --- a/client/src/pages/MoviePage.jsx +++ b/client/src/pages/MoviePage.jsx @@ -1,21 +1,29 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useParams } from "react-router-dom"; import { motion } from "framer-motion"; import { getMovieById } from "../api/movie.api"; import Loader from "../components/ui/Loader"; -import { addWatchedMovie, getMovieHistory, removeMovieFromHistory } from "../api/history.api"; +import Modal from "../components/ui/Modal"; +import { addWatchedMovie, getMovieHistory, removeMovieFromHistory, updateRating } from "../api/history.api"; import { addMovieToWatchList, getIsMovieWatchListed, removeMovieFromWatchList } from "../api/watchList.api"; -import { BookmarkIcon, CheckCircleIcon, ClockIcon } from "@heroicons/react/24/solid"; +import { BookmarkIcon, ChatBubbleLeftRightIcon, CheckCircleIcon, ClockIcon, XMarkIcon } from "@heroicons/react/24/solid"; +import { StarIcon } from "lucide-react"; +import { addMovieReview } from "../api/reviews.api"; export default function MoviePage() { const [movieData, setMovieData] = useState(null); const [loading, setLoading] = useState(true); const [watched, setWatched] = useState(false); + const [historyEntry, setHistoryEntry] = useState(null); const [inList, setInList] = useState(false); const [reviewOpen, setReviewOpen] = useState(false); + const [rating, setRating] = useState(0); + const [hoverRating, setHoverRating] = useState(0); const { movieId } = useParams(); + const reviewRef = useRef(null); + useEffect(() => { if (!movieId) return; const fetchMovie = async () => { @@ -27,6 +35,8 @@ export default function MoviePage() { ]); setMovieData(res[0]); + setHistoryEntry(res[1].data); + setRating(res[1].data ? res[1].data.rating : 0); setWatched(res[1].data != null); setInList(res[2].data.watchListed); @@ -53,6 +63,7 @@ export default function MoviePage() { if (inList) setInList(false); setWatched(true); + setHistoryEntry(res.data); setMovieData((prev) => ({ ...prev, @@ -83,6 +94,53 @@ export default function MoviePage() { } } + const handleMovieRating = async (rating) => { + if (!watched) { + alert("You need to mark the movie as watched before rating."); + return; + } else { + try { + const res = await updateRating(historyEntry._id, rating); + if (res.status === 200) { + setMovieData((prev) => ({ + ...prev, + ...res.data + })); + } + } catch (error) { + console.error("Error updating rating: ", error); + } + } + } + +const handlePostReview = async () => { + if (!watched) { + alert("You need to mark the movie as watched before rating."); + return; + } + + if (!reviewRef.current || !reviewRef.current.value.trim()) { + alert("Please write a review."); + return; + } + + try { + await addMovieReview(movieData._id, { + review: reviewRef.current.value.trim() + }); + + setReviewOpen(false); + reviewRef.current.value = ""; + + } catch (error) { + if (error.response) { + alert(error.response.data.error); + } else { + console.error("Error posting review:", error); + } + } +}; + if (loading) return ; const releaseYear = movieData ? new Date(movieData.releaseDate).getFullYear() : ""; @@ -123,20 +181,69 @@ export default function MoviePage() { - {movieData?.title} {releaseYear} -
- {movieData?.originalLanguage?.toUpperCase()} - {movieData?.adult == true && 18+} + {movieData?.title} {releaseYear}
-
- {movieData?.genreNames.map((g) => ( - - {g} + {/* Metadata Badges */} +
+ + {movieData?.originalLanguage?.toUpperCase()} + + {movieData?.adult && ( + + 18+ - ))} + )} +
+
+ {movieData?.genreNames.map((g) => ( + + {g} + + ))} +
+
+ + {/* */} +
+
+

+ Your Rating +

+
setHoverRating(0)} + > + {[...Array(10)].map((_, i) => { + const starValue = i + 1; + const isActive = starValue <= (hoverRating || rating); + return ( + + ); + })} + {(hoverRating || rating) > 0 && ( + + {hoverRating || rating}/10 + + )} +
+
@@ -178,12 +285,58 @@ export default function MoviePage() { trueIcon={} falseIcon={} /> - +
+ + + {/* Modal for Review */} + +
+ + + +
+

Share your thoughts

+

How was the cinematography and pacing?

+
+ +
+