Skip to content
Merged

Feat #29

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions client/src/api/history.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,14 @@ export const getMovieHistory = async (movieId) => {
export const getHistoryBanner = async () => {
const res = await axiosInstance.get('/history');
return res.data;
}

export const addWatchedMovie = async (title) => {
const res = await axiosInstance.post('/history', { title });
return res;
}

export const removeMovieFromHistory = async (movieId) => {
const res = await axiosInstance.delete(`/history/movie/${movieId}`);
return res;
}
10 changes: 10 additions & 0 deletions client/src/api/watchList.api.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,14 @@ import axiosInstance from "./axiosInstance"
export const getIsMovieWatchListed = async (movieId) => {
const res = await axiosInstance.get(`/watchlist/movie/${movieId}`);
return res.data;
}

export const addMovieToWatchList = async (title) => {
const res = await axiosInstance.post('/watchlist', { title });
return res;
}

export const removeMovieFromWatchList = async (movieId) => {
const res = await axiosInstance.delete(`/watchlist/movie/${movieId}`);
return res;
}
74 changes: 63 additions & 11 deletions client/src/pages/MoviePage.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { useEffect, 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 { getMovieHistory } from "../api/history.api";
import { getIsMovieWatchListed } from "../api/watchList.api";
import { addWatchedMovie, getMovieHistory, removeMovieFromHistory } from "../api/history.api";
import { addMovieToWatchList, getIsMovieWatchListed, removeMovieFromWatchList } from "../api/watchList.api";
import { BookmarkIcon, CheckCircleIcon, ClockIcon } from "@heroicons/react/24/solid";

export default function MoviePage() {
const [movieData, setMovieData] = useState(null);
Expand All @@ -12,7 +14,7 @@ export default function MoviePage() {
const [inList, setInList] = useState(false);
const [reviewOpen, setReviewOpen] = useState(false);

const movieId = window.location.pathname.split("/movies/")[1];
const { movieId } = useParams();

useEffect(() => {
if (!movieId) return;
Expand All @@ -23,9 +25,11 @@ export default function MoviePage() {
getMovieHistory(movieId),
getIsMovieWatchListed(movieId)
]);

setMovieData(res[0]);
setWatched(res[1].data != null);
setInList(res[2].data.watchListed);

} catch (error) {
console.error("Error fetching movie: ", error);
} finally {
Expand All @@ -35,6 +39,50 @@ export default function MoviePage() {
fetchMovie();
}, [movieId]);

const handleWatchedToggle = async () => {
try {
if (watched) {
const res = await removeMovieFromHistory(movieData._id);
if (res.status === 200) {
setWatched(false);
}
} else {
const res = await addWatchedMovie(movieData.title);

if (res.status === 201) {
if (inList) setInList(false);

setWatched(true);

setMovieData((prev) => ({
...prev,
...res.data
}));
}
}
} catch (error) {
console.error("Error updating watch status: ", error);
}
}

const handleWatchListToggle = async () => {
try {
if (inList) {
const res = await removeMovieFromWatchList(movieData._id);
if (res.status === 200) {
setInList(false);
}
} else {
const res = await addMovieToWatchList(movieData.title);
if (res.status === 201) {
setInList(true);
}
}
} catch (error) {
console.error("Error updating watchlist status: ", error);
}
}

if (loading) return <Loader />;

const releaseYear = movieData ? new Date(movieData.releaseDate).getFullYear() : "";
Expand Down Expand Up @@ -108,7 +156,6 @@ export default function MoviePage() {
</motion.section>
</div>

{/* Action Buttons */}
<motion.aside
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
Expand All @@ -117,17 +164,19 @@ export default function MoviePage() {
>
<ActionButton
active={watched}
onClick={() => setWatched(!watched)}
activeClass="bg-(--interaction-color) text-white border-(--interaction-color)"
onClick={handleWatchedToggle}
activeClass="bg-amber-400 text-white border-amber-400"
label={watched ? "Watched" : "Mark as watched"}
icon={watched ? "✓" : "○"}
trueIcon={<ClockIcon className="size-5" />}
falseIcon={<CheckCircleIcon className="size-5" />}
/>
<ActionButton
active={inList}
onClick={() => setInList(!inList)}
onClick={handleWatchListToggle}
activeClass="bg-(--secondary-color) text-white border-(--secondary-color)"
label={inList ? "In Watchlist" : "Add to Watchlist"}
icon="+"
trueIcon={<BookmarkIcon className="size-5" />}
falseIcon={<BookmarkIcon className="size-5" />}
/>
<button
onClick={() => setReviewOpen(!reviewOpen)}
Expand All @@ -142,14 +191,17 @@ export default function MoviePage() {
);
}

function ActionButton({ active, onClick, activeClass, label, icon }) {
function ActionButton({ active, onClick, activeClass, label, trueIcon, falseIcon }) {
return (
<button
onClick={onClick}
className={`w-full flex items-center gap-4 px-6 py-4 rounded-2xl border-2 font-bold text-sm transition-all active:scale-95 ${active ? activeClass : "bg-white border-stone-100 text-stone-700 hover:border-stone-200 shadow-sm"
}`}
>
<span className="text-lg">{icon}</span>
{active ?
<span className="text-lg">{trueIcon}</span> :
<span className="text-lg">{falseIcon}</span>
}
{label}
</button>
);
Expand Down
2 changes: 1 addition & 1 deletion server/controllers/history.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export const removeWatchedMovie = async (req, res) => {
}

const deletedMovie = await History.findOneAndDelete({
_id: movieId,
movieId,
userId
}).populate("movieId", "title");

Expand Down
Loading