feat: enhance MovieRow component with drag-and-drop functionality for marking movies as watched or favorite; integrate framer-motion for improved animations and user interaction
This commit is contained in:
parent
cb0962f184
commit
9079a52778
|
|
@ -1,8 +1,11 @@
|
|||
"use client";
|
||||
import { formatter } from "@/helpers/formater";
|
||||
import { Movie } from "@/types/global";
|
||||
import Link from "next/link";
|
||||
import { FC } from "react";
|
||||
import { FaCalendar, FaClock, FaStar } from "react-icons/fa";
|
||||
import { FaCalendar, FaClock, FaStar, FaEye, FaHeart } from "react-icons/fa";
|
||||
import { motion, useAnimationControls, useMotionValue } from "framer-motion";
|
||||
import { useGlobalStore } from "@/app/store/globalStore";
|
||||
|
||||
type Props = {
|
||||
movie: Movie;
|
||||
|
|
@ -15,6 +18,11 @@ export const MovieRow: FC<Props> = ({
|
|||
isUpcoming = false,
|
||||
compact = false,
|
||||
}) => {
|
||||
const { movies, addMovie, updateMovie } = useGlobalStore();
|
||||
|
||||
const dragControls = useAnimationControls();
|
||||
const x = useMotionValue(0);
|
||||
|
||||
const daysSinceRelease = Math.abs(
|
||||
Math.floor(
|
||||
(new Date().getTime() - new Date(movie.release_date).getTime()) /
|
||||
|
|
@ -22,60 +30,129 @@ export const MovieRow: FC<Props> = ({
|
|||
)
|
||||
);
|
||||
|
||||
// Check if movie is already in store.
|
||||
const movieInStore = movies.find((m) => m.id === movie.id);
|
||||
const isWatched = movieInStore?.seen || false;
|
||||
const isFavorite = movieInStore?.favorite || false;
|
||||
|
||||
const handleMarkAsWatched = () => {
|
||||
if (movieInStore) {
|
||||
updateMovie(movie.id, { seen: !isWatched });
|
||||
} else {
|
||||
addMovie({ ...movie, seen: true, favorite: false });
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddToFavorites = () => {
|
||||
if (movieInStore) {
|
||||
updateMovie(movie.id, { favorite: !isFavorite, seen: true });
|
||||
} else {
|
||||
addMovie({ ...movie, seen: true, favorite: true });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragAction = () => {
|
||||
const threshold = 70;
|
||||
if (x.get() > threshold) {
|
||||
handleAddToFavorites();
|
||||
} else if (x.get() < -threshold) {
|
||||
handleMarkAsWatched();
|
||||
}
|
||||
dragControls.start({
|
||||
x: 0,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={`/film/${movie.id}`}
|
||||
className="flex items-center gap-4 p-3 rounded-lg bg-gray-800/30 hover:bg-gray-800/50 transition-colors group"
|
||||
>
|
||||
<div className="relative w-12 h-16 rounded overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={`https://image.tmdb.org/t/p/w154${movie.poster_path}`}
|
||||
alt={movie.title}
|
||||
className="object-cover inset-0"
|
||||
sizes="48px"
|
||||
/>
|
||||
<div className="relative overflow-hidden">
|
||||
{/* Background actions */}
|
||||
<div className="absolute inset-0 flex">
|
||||
<div className="absolute right-0 h-full w-24 bg-green-500/20 flex items-center justify-center cursor-pointer">
|
||||
<FaEye className="w-5 h-5 transition-colors text-green-500" />
|
||||
</div>
|
||||
|
||||
<div className="absolute left-0 h-full w-24 bg-red-500/20 flex items-center justify-center cursor-pointer">
|
||||
<FaHeart className="w-5 h-5 transition-colors text-red-500" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate group-hover:text-blue-400 transition-colors">
|
||||
{movie.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3 mt-1">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs">
|
||||
{isUpcoming ? (
|
||||
<FaCalendar className="w-3 h-3" />
|
||||
) : (
|
||||
<FaClock className="w-3 h-3" />
|
||||
)}
|
||||
<span>{formatter.formatDate(movie.release_date)}</span>
|
||||
<motion.div
|
||||
drag="x"
|
||||
style={{ x }}
|
||||
animate={dragControls}
|
||||
dragConstraints={{ left: -80, right: 80 }}
|
||||
dragElastic={0.01}
|
||||
dragMomentum={false}
|
||||
whileDrag={{ cursor: "grabbing" }}
|
||||
onDragEnd={handleDragAction}
|
||||
className="relative z-10"
|
||||
>
|
||||
<Link
|
||||
href={`/film/${movie.id}`}
|
||||
draggable={false}
|
||||
className="flex items-center gap-4 p-3 rounded-lg bg-gray-800 hover:bg-gray-800 transition-colors group"
|
||||
>
|
||||
<div className="relative w-12 h-16 rounded overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={`https://image.tmdb.org/t/p/w154${movie.poster_path}`}
|
||||
alt={movie.title}
|
||||
className="object-cover inset-0"
|
||||
sizes="48px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{!!movie.vote_average && (
|
||||
<div className="flex items-center gap-1 text-yellow-400 text-xs">
|
||||
<FaStar className="w-3 h-3 fill-current" />
|
||||
<span>{movie.vote_average.toFixed(1)}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate group-hover:text-blue-400 transition-colors">
|
||||
{movie.title}
|
||||
</h3>
|
||||
<div className="flex items-center gap-3 mt-1">
|
||||
<div className="flex items-center gap-1 text-gray-400 text-xs">
|
||||
{isUpcoming ? (
|
||||
<FaCalendar className="w-3 h-3" />
|
||||
) : (
|
||||
<FaClock className="w-3 h-3" />
|
||||
)}
|
||||
<span>{formatter.formatDate(movie.release_date)}</span>
|
||||
</div>
|
||||
|
||||
{!!movie.vote_average && (
|
||||
<div className="flex items-center gap-1 text-yellow-400 text-xs">
|
||||
<FaStar className="w-3 h-3 fill-current" />
|
||||
<span>{movie.vote_average.toFixed(1)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(isFavorite || movie.favorite) && (
|
||||
<div
|
||||
className="w-2 h-2 bg-red-500 rounded-full"
|
||||
title="Ulubione"
|
||||
/>
|
||||
)}
|
||||
|
||||
{(isWatched || movie.seen) && (
|
||||
<div
|
||||
className="w-2 h-2 bg-green-500 rounded-full"
|
||||
title="Obejrzane"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!compact && (
|
||||
<div
|
||||
className={`text-xs px-2 py-1 rounded-full font-medium ${
|
||||
isUpcoming
|
||||
? "bg-blue-500/20 text-blue-400"
|
||||
: "bg-green-500/20 text-green-400"
|
||||
}`}
|
||||
>
|
||||
{isUpcoming
|
||||
? `za ${daysSinceRelease} dni`
|
||||
: `od ${daysSinceRelease} dni`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{movie.favorite && (
|
||||
<div className="w-2 h-2 bg-red-500 rounded-full" title="Ulubione" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!compact && (
|
||||
<div
|
||||
className={`text-xs px-2 py-1 rounded-full font-medium ${
|
||||
isUpcoming
|
||||
? "bg-blue-500/20 text-blue-400"
|
||||
: "bg-green-500/20 text-green-400"
|
||||
}`}
|
||||
>
|
||||
{isUpcoming
|
||||
? `za ${daysSinceRelease} dni`
|
||||
: `od ${daysSinceRelease} dni`}
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
</Link>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue