Refactor MovieCard and MovieList components: streamline props by integrating Movie type, enhance filtering logic, and improve UI responsiveness with a new Dropdown for sorting options.
This commit is contained in:
parent
556bb38589
commit
186e98262b
|
|
@ -3,20 +3,10 @@ import { MovieList } from "@/components/molecules/MovieList";
|
||||||
export default async function Home() {
|
export default async function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MovieList
|
<MovieList heading="Upcoming" filterUpcoming sortDirection="desc" />
|
||||||
heading="Upcoming"
|
<MovieList heading="My Watchlist" filterReleased />
|
||||||
filterUpcoming={1}
|
<MovieList heading="Seen" filterSeen />
|
||||||
sort="releaseDate"
|
<MovieList heading="Favorites" filterFavorites />
|
||||||
sortDirection="desc"
|
|
||||||
/>
|
|
||||||
<MovieList
|
|
||||||
heading="My Watchlist"
|
|
||||||
filterReleased={1}
|
|
||||||
filterSeen={0}
|
|
||||||
showFilters
|
|
||||||
/>
|
|
||||||
<MovieList heading="Seen" filterSeen={1} />
|
|
||||||
<MovieList heading="Favorites" filterFavorites={1} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,59 +6,34 @@ import { useGlobalStore } from "@/app/store/globalStore";
|
||||||
import { MdFavorite, MdFavoriteBorder, MdOutlinePostAdd } from "react-icons/md";
|
import { MdFavorite, MdFavoriteBorder, MdOutlinePostAdd } from "react-icons/md";
|
||||||
import { RxEyeClosed, RxEyeOpen } from "react-icons/rx";
|
import { RxEyeClosed, RxEyeOpen } from "react-icons/rx";
|
||||||
import { IoMdRemoveCircleOutline } from "react-icons/io";
|
import { IoMdRemoveCircleOutline } from "react-icons/io";
|
||||||
|
import { Movie } from "@/types/global";
|
||||||
|
import { FaFire } from "react-icons/fa";
|
||||||
|
import { RiCalendarCheckLine, RiCalendarScheduleLine } from "react-icons/ri";
|
||||||
|
|
||||||
type Props = {
|
type Props = Movie & {
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
overview: string;
|
|
||||||
releaseDate: string;
|
|
||||||
popularity: number;
|
|
||||||
imagePath: string;
|
|
||||||
seen: boolean;
|
|
||||||
favorite: boolean;
|
|
||||||
notes?: string;
|
|
||||||
layout?: "default" | "zeus";
|
layout?: "default" | "zeus";
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonClass =
|
const buttonClass =
|
||||||
"p-4 text-sm transition-colors cursor-pointer text-center group/toggle";
|
"p-4 text-sm transition-colors cursor-pointer text-center group/toggle";
|
||||||
|
|
||||||
export const MovieCard: FC<Props> = ({
|
export const MovieCard: FC<Props> = ({ layout = "default", ...movie }) => {
|
||||||
id,
|
|
||||||
title,
|
|
||||||
releaseDate,
|
|
||||||
popularity,
|
|
||||||
overview,
|
|
||||||
imagePath,
|
|
||||||
seen,
|
|
||||||
favorite,
|
|
||||||
notes,
|
|
||||||
layout = "default",
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
movies,
|
movies,
|
||||||
addMovie: addMovieToStore,
|
addMovie: addMovieToStore,
|
||||||
deleteMovie: deleteMovieFromStore,
|
deleteMovie: deleteMovieFromStore,
|
||||||
updateMovie: updateMovieInStore,
|
updateMovie: updateMovieInStore,
|
||||||
} = useGlobalStore();
|
} = useGlobalStore();
|
||||||
console.log(movies);
|
const { id, title, overview, popularity, release_date, poster_path } = movie;
|
||||||
const alreadyInStore = movies.find((m) => m.id === id);
|
const alreadyInStore = movies.find((m) => m.id === id);
|
||||||
|
|
||||||
const isReleased = new Date(releaseDate) < new Date();
|
const isReleased = new Date(release_date) < new Date();
|
||||||
const iconSize = 64;
|
const iconSize = 48;
|
||||||
|
|
||||||
|
const seen = movie.seen;
|
||||||
|
const favorite = movie.favorite;
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
const movie = {
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
overview,
|
|
||||||
popularity,
|
|
||||||
releaseDate,
|
|
||||||
posterPath: imagePath,
|
|
||||||
seen: 0,
|
|
||||||
favorite: 0,
|
|
||||||
notes: "",
|
|
||||||
};
|
|
||||||
await addMovie(movie);
|
await addMovie(movie);
|
||||||
addMovieToStore(movie);
|
addMovieToStore(movie);
|
||||||
};
|
};
|
||||||
|
|
@ -69,13 +44,13 @@ export const MovieCard: FC<Props> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSeen = async () => {
|
const handleSeen = async () => {
|
||||||
await updateMovie(id, { seen: seen ? 0 : 1 });
|
await updateMovie(id, { seen: !seen });
|
||||||
updateMovieInStore(id, { seen: seen ? 0 : 1 });
|
updateMovieInStore(id, { seen: !seen });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFavorite = async () => {
|
const handleFavorite = async () => {
|
||||||
await updateMovie(id, { favorite: favorite ? 0 : 1 });
|
await updateMovie(id, { favorite: !favorite });
|
||||||
updateMovieInStore(id, { favorite: favorite ? 0 : 1 });
|
updateMovieInStore(id, { favorite: !favorite });
|
||||||
};
|
};
|
||||||
|
|
||||||
if (layout === "zeus") {
|
if (layout === "zeus") {
|
||||||
|
|
@ -83,19 +58,15 @@ export const MovieCard: FC<Props> = ({
|
||||||
<article className="flex flex-col w-full shadow-md rounded-lg overflow-hidden bg-white">
|
<article className="flex flex-col w-full shadow-md rounded-lg overflow-hidden bg-white">
|
||||||
<figure className="relative ">
|
<figure className="relative ">
|
||||||
<img
|
<img
|
||||||
className="w-full object-cover"
|
className="w-full object-cover xl:h-[420px]"
|
||||||
style={{ height: "420px" }}
|
src={`http://image.tmdb.org/t/p/w342${poster_path}`}
|
||||||
src={`http://image.tmdb.org/t/p/w342/${imagePath}`}
|
|
||||||
/>
|
/>
|
||||||
<span
|
<span className="absolute inset-0 bg-black/30 backdrop-blur-md opacity-0 hover-any:opacity-100 transition-opacity duration-300 flex items-center justify-center cursor-pointer">
|
||||||
className="absolute inset-0 bg-black/30 backdrop-blur-md opacity-0 hover:opacity-100 transition-opacity duration-300 flex items-center justify-center cursor-pointer"
|
{!alreadyInStore && (
|
||||||
onClick={() => {
|
<button className={buttonClass} onClick={handleAdd}>
|
||||||
if (!alreadyInStore) {
|
<MdOutlinePostAdd size={64} color="white" />
|
||||||
handleAdd();
|
</button>
|
||||||
}
|
)}
|
||||||
}}
|
|
||||||
>
|
|
||||||
{!alreadyInStore && <MdOutlinePostAdd size={64} color="white" />}
|
|
||||||
{alreadyInStore && (
|
{alreadyInStore && (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<>
|
<>
|
||||||
|
|
@ -151,12 +122,25 @@ export const MovieCard: FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="absolute top-0 right-0 bg-black/50 px-2 py-1 rounded-bl-lg">
|
||||||
|
<p className="text-sm text-white flex items-center gap-1">
|
||||||
|
<FaFire />
|
||||||
|
{popularity}
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
</figure>
|
</figure>
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<h2 className="text-xl leading-[1.1] font-bold">{title}</h2>
|
<h2 className="text-xl leading-[1.1] font-bold">{title}</h2>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 mt-2">{releaseDate}</p>
|
<p
|
||||||
|
className={`text-sm mt-2 flex items-center gap-1 leading-[1.1] ${
|
||||||
|
isReleased ? "text-green-700" : "text-yellow-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isReleased ? <RiCalendarCheckLine /> : <RiCalendarScheduleLine />}
|
||||||
|
{release_date}
|
||||||
|
</p>
|
||||||
<div className="text-xs text-gray-400 mt-4">
|
<div className="text-xs text-gray-400 mt-4">
|
||||||
<ReadMore text={overview} />
|
<ReadMore text={overview} />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -190,7 +174,7 @@ export const MovieCard: FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="text-sm text-gray-400">Release date:</div>
|
<div className="text-sm text-gray-400">Release date:</div>
|
||||||
<div className="release">{releaseDate}</div>
|
<div className="release">{release_date}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -235,7 +219,7 @@ export const MovieCard: FC<Props> = ({
|
||||||
<figure className="absolute inset-0 w-full bottom-[20%]">
|
<figure className="absolute inset-0 w-full bottom-[20%]">
|
||||||
<img
|
<img
|
||||||
className="w-full h-96 object-cover"
|
className="w-full h-96 object-cover"
|
||||||
src={`http://image.tmdb.org/t/p/w342/${imagePath}`}
|
src={`http://image.tmdb.org/t/p/w342${poster_path}`}
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import { MovieCard } from "@/components/atoms/MovieCard";
|
import { MovieCard } from "@/components/atoms/MovieCard";
|
||||||
|
import { Movie } from "@/types/global";
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { useGlobalStore } from "@/app/store/globalStore";
|
||||||
|
import { Dropdown } from "@/components/atoms/Dropdown";
|
||||||
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
heading: string;
|
heading?: string;
|
||||||
filterSeen?: 0 | 1;
|
overrideMovies?: Movie[];
|
||||||
filterFavorites?: 0 | 1;
|
|
||||||
filterUpcoming?: 0 | 1;
|
|
||||||
filterReleased?: 0 | 1;
|
|
||||||
|
|
||||||
|
filterSeen?: boolean;
|
||||||
|
filterFavorites?: boolean;
|
||||||
|
filterUpcoming?: boolean;
|
||||||
|
filterReleased?: boolean;
|
||||||
|
|
||||||
|
fluid?: boolean;
|
||||||
showFilters?: boolean;
|
showFilters?: boolean;
|
||||||
sort?: "title" | "releaseDate" | "popularity";
|
sort?: "title" | "releaseDate" | "popularity";
|
||||||
sortDirection?: "asc" | "desc";
|
sortDirection?: "asc" | "desc";
|
||||||
|
|
@ -17,28 +23,32 @@ type Props = {
|
||||||
|
|
||||||
export const MovieList: FC<Props> = ({
|
export const MovieList: FC<Props> = ({
|
||||||
heading,
|
heading,
|
||||||
|
overrideMovies,
|
||||||
filterSeen,
|
filterSeen,
|
||||||
filterFavorites,
|
filterFavorites,
|
||||||
filterUpcoming,
|
filterUpcoming,
|
||||||
filterReleased,
|
filterReleased,
|
||||||
showFilters = false,
|
fluid = false,
|
||||||
sort = "title",
|
showFilters = true,
|
||||||
|
sort = "releaseDate",
|
||||||
sortDirection = "asc",
|
sortDirection = "asc",
|
||||||
}) => {
|
}) => {
|
||||||
|
const { movies: storeMovies } = useGlobalStore();
|
||||||
const [filter, setFilter] = useState<"title" | "releaseDate" | "popularity">(
|
const [filter, setFilter] = useState<"title" | "releaseDate" | "popularity">(
|
||||||
sort
|
sort
|
||||||
);
|
);
|
||||||
const { movies } = useGlobalStore();
|
const [parent] = useAutoAnimate();
|
||||||
|
|
||||||
|
const movies = overrideMovies || storeMovies;
|
||||||
|
|
||||||
const filteredMovies = movies.filter((movie) => {
|
const filteredMovies = movies.filter((movie) => {
|
||||||
let result = true;
|
let result = true;
|
||||||
if (typeof filterSeen === "number") result = movie.seen === filterSeen;
|
if (filterSeen) result = !!movie.seen;
|
||||||
if (typeof filterFavorites === "number")
|
if (filterFavorites) result = result && !!movie.favorite;
|
||||||
result = result && movie.favorite === filterFavorites;
|
if (filterUpcoming)
|
||||||
if (typeof filterUpcoming === "number")
|
result = result && new Date(movie.release_date) > new Date();
|
||||||
result = result && new Date(movie.releaseDate) > new Date();
|
if (filterReleased)
|
||||||
if (typeof filterReleased === "number")
|
result = result && new Date(movie.release_date) < new Date();
|
||||||
result = result && new Date(movie.releaseDate) < new Date();
|
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -46,7 +56,7 @@ export const MovieList: FC<Props> = ({
|
||||||
if (filter === "title") return a.title.localeCompare(b.title);
|
if (filter === "title") return a.title.localeCompare(b.title);
|
||||||
if (filter === "releaseDate")
|
if (filter === "releaseDate")
|
||||||
return (
|
return (
|
||||||
new Date(b.releaseDate).getTime() - new Date(a.releaseDate).getTime()
|
new Date(b.release_date).getTime() - new Date(a.release_date).getTime()
|
||||||
);
|
);
|
||||||
if (filter === "popularity") return b.popularity - a.popularity;
|
if (filter === "popularity") return b.popularity - a.popularity;
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -56,32 +66,24 @@ export const MovieList: FC<Props> = ({
|
||||||
sortedMovies = sortedMovies.reverse();
|
sortedMovies = sortedMovies.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSort = (sort: "title" | "releaseDate" | "popularity") => {
|
|
||||||
setFilter(sort);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="my-4 md:my-10">
|
<section className="my-4 md:my-10">
|
||||||
<div className="container">
|
<div className={`${fluid ? "max-w-full" : "container"}`}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12 md:col-10">
|
{heading && (
|
||||||
<h2 className="text-2xl font-bold">{heading}</h2>
|
<div className="col-12 md:col-10 flex gap-2 items-center">
|
||||||
</div>
|
{showFilters && (
|
||||||
{showFilters && (
|
<Dropdown
|
||||||
<div className="col-12 md:col-2">
|
items={[
|
||||||
<select
|
{ label: "Title", value: "title" },
|
||||||
className="bg-accent/70 text-white px-4 py-2 rounded-md w-full hover:bg-primary transition-colors cursor-pointer"
|
{ label: "Release Date", value: "releaseDate" },
|
||||||
value={filter}
|
{ label: "Popularity", value: "popularity" },
|
||||||
onChange={(e) =>
|
]}
|
||||||
handleSort(
|
defaultValue={filter}
|
||||||
e.target.value as "title" | "releaseDate" | "popularity"
|
callback={(value) => setFilter(value as "title")}
|
||||||
)
|
/>
|
||||||
}
|
)}
|
||||||
>
|
<h2 className="text-2xl font-bold">{heading}</h2>
|
||||||
<option value="title">Title</option>
|
|
||||||
<option value="releaseDate">Release Date</option>
|
|
||||||
<option value="popularity">Popularity</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -89,16 +91,12 @@ export const MovieList: FC<Props> = ({
|
||||||
<p className="text-text/60 text-sm">No movies found</p>
|
<p className="text-text/60 text-sm">No movies found</p>
|
||||||
)}
|
)}
|
||||||
{filteredMovies.length > 0 && (
|
{filteredMovies.length > 0 && (
|
||||||
<div className="grid grid-auto-cols-282 gap-6 mt-8 justify-center">
|
<div
|
||||||
|
className="grid grid-cols-1 xs:grid-cols-2 sm:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-y-6 gap-3 sm:gap-6 mt-8 justify-center"
|
||||||
|
ref={parent}
|
||||||
|
>
|
||||||
{sortedMovies.map((movie) => (
|
{sortedMovies.map((movie) => (
|
||||||
<MovieCard
|
<MovieCard key={movie.id} layout="zeus" {...movie} />
|
||||||
key={movie.id}
|
|
||||||
layout="zeus"
|
|
||||||
{...movie}
|
|
||||||
imagePath={movie.posterPath || ""}
|
|
||||||
seen={movie.seen === 1}
|
|
||||||
favorite={movie.favorite === 1}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue