Refactor movie filtering and management features: update MovieList and MovieCard components to use new filter props; enhance global store with updateMovie functionality; implement movie state updates for seen and favorite statuses.

This commit is contained in:
Norbert Maciaszek 2025-08-05 22:30:53 +02:00
parent b08cdea130
commit 3809110a39
5 changed files with 93 additions and 31 deletions

View File

@ -7,13 +7,18 @@ export default async function Home() {
<SearchMovies />
<MovieList
heading="Upcoming"
onlyUpcoming
filterUpcoming={1}
sort="releaseDate"
sortDirection="desc"
/>
<MovieList heading="My Watchlist" onlyReleased showFilters />
<MovieList heading="Seen" onlySeen />
<MovieList heading="Favorites" onlyFavorites />
<MovieList
heading="My Watchlist"
filterReleased={1}
filterSeen={0}
showFilters
/>
<MovieList heading="Seen" filterSeen={1} />
<MovieList heading="Favorites" filterFavorites={1} />
</main>
);
}

View File

@ -2,16 +2,20 @@
import { movies } from "@/lib/db/schema";
import { createContext, FC, use, useState } from "react";
type Movie = typeof movies.$inferSelect;
type GlobalStore = {
movies: (typeof movies.$inferSelect)[];
addMovie: (movie: typeof movies.$inferSelect) => void;
movies: Movie[];
addMovie: (movie: Movie) => void;
deleteMovie: (id: number) => void;
updateMovie: (id: number, movie: Partial<Movie>) => void;
};
const globalStore = createContext<GlobalStore>({
movies: [],
addMovie: () => {},
deleteMovie: () => {},
updateMovie: () => {},
});
type Props = {
@ -22,7 +26,7 @@ type Props = {
export const GlobalStoreProvider: FC<Props> = ({ children, initialMovies }) => {
const [movies, setMovies] = useState<GlobalStore["movies"]>(initialMovies);
const addMovie = async (movie: GlobalStore["movies"][number]) => {
const addMovie = async (movie: Movie) => {
if (movies.find((m) => m.id === movie.id)) return;
setMovies((prev) => [...prev, movie]);
@ -32,8 +36,16 @@ export const GlobalStoreProvider: FC<Props> = ({ children, initialMovies }) => {
setMovies((prev) => prev.filter((m) => m.id !== id));
};
const updateMovie = async (id: number, movie: Partial<Movie>) => {
setMovies((prev) =>
prev.map((m) => (m.id === id ? { ...m, ...movie } : m))
);
};
return (
<globalStore.Provider value={{ movies, addMovie, deleteMovie }}>
<globalStore.Provider
value={{ movies, addMovie, deleteMovie, updateMovie }}
>
{children}
</globalStore.Provider>
);

View File

@ -1,7 +1,7 @@
"use client";
import { FC, useState } from "react";
import { FC } from "react";
import { ReadMore } from "../ReadMore";
import { addMovie, deleteMovie } from "@/lib/db";
import { addMovie, deleteMovie, updateMovie } from "@/lib/db";
import { useGlobalStore } from "@/app/store/globalStore";
type Props = {
@ -16,6 +16,8 @@ type Props = {
notes?: string;
};
const buttonClass = "px-2 py-6 text-sm w-full transition-colors cursor-pointer";
export const MovieCard: FC<Props> = ({
id,
title,
@ -31,9 +33,12 @@ export const MovieCard: FC<Props> = ({
movies,
addMovie: addMovieToStore,
deleteMovie: deleteMovieFromStore,
updateMovie: updateMovieInStore,
} = useGlobalStore();
const alreadyInStore = movies.find((m) => m.id === id);
const isReleased = new Date(releaseDate) < new Date();
const handleAdd = async () => {
const movie = {
id,
@ -55,6 +60,16 @@ export const MovieCard: FC<Props> = ({
deleteMovieFromStore(id);
};
const handleSeen = async () => {
await updateMovie(id, { seen: seen ? 0 : 1 });
updateMovieInStore(id, { seen: seen ? 0 : 1 });
};
const handleFavorite = async () => {
await updateMovie(id, { favorite: favorite ? 0 : 1 });
updateMovieInStore(id, { favorite: favorite ? 0 : 1 });
};
return (
<div className="flex w-full shadow-md rounded-lg overflow-hidden mx-auto group/card">
<div className="overflow-hidden rounded-xl relative movie-item text-white movie-card">
@ -85,22 +100,41 @@ export const MovieCard: FC<Props> = ({
</div>
</div>
</div>
<div className="absolute top-0 z-10 bg-transparent inset-x-0 group-hover/card:bg-white/50 transition-all opacity-0 group-hover/card:opacity-100 flex justify-center">
{/* <div className="absolute top-0 z-10 bg-transparent inset-x-0 group-hover/card:bg-white/50 transition-all opacity-0 group-hover/card:opacity-100 flex flex-col justify-center"> */}
<div className="absolute top-0 z-10 bg-transparent inset-0 group-hover/card:bg-black/50 transition-all opacity-0 group-hover/card:opacity-100 flex flex-col justify-center">
{!alreadyInStore && (
<button
className="bg-primary/70 text-white px-4 py-2 rounded-md w-full hover:bg-primary transition-colors cursor-pointer"
className={`${buttonClass} bg-primary/70 text-white hover:bg-primary`}
onClick={handleAdd}
>
Add to watchlist
</button>
)}
{alreadyInStore && (
<button
className="bg-primary/70 text-white px-4 py-2 rounded-md w-full hover:bg-primary transition-colors cursor-pointer"
onClick={handleRemove}
>
Remove from watchlist
</button>
<>
{isReleased && (
<button
className={`${buttonClass} bg-accent/70 text-white hover:bg-accent`}
onClick={handleSeen}
>
{seen ? "Mark as unseen" : "Mark as seen"}
</button>
)}
<button
className={`${buttonClass} bg-amber-400/70 text-white hover:bg-amber-500`}
onClick={handleFavorite}
>
{favorite ? "Remove favorite" : "Add to favorites"}
</button>
<button
className={`${buttonClass} bg-primary/70 text-white hover:bg-primary`}
onClick={handleRemove}
>
Remove from watchlist
</button>
</>
)}
</div>
<figure className="absolute inset-0 w-full bottom-[20%]">

View File

@ -5,10 +5,10 @@ import { useGlobalStore } from "@/app/store/globalStore";
type Props = {
heading: string;
onlySeen?: boolean;
onlyFavorites?: boolean;
onlyUpcoming?: boolean;
onlyReleased?: boolean;
filterSeen?: 0 | 1;
filterFavorites?: 0 | 1;
filterUpcoming?: 0 | 1;
filterReleased?: 0 | 1;
showFilters?: boolean;
sort?: "title" | "releaseDate" | "popularity";
@ -17,10 +17,10 @@ type Props = {
export const MovieList: FC<Props> = ({
heading,
onlyFavorites,
onlySeen,
onlyUpcoming,
onlyReleased,
filterSeen,
filterFavorites,
filterUpcoming,
filterReleased,
showFilters = false,
sort = "title",
sortDirection = "asc",
@ -31,11 +31,15 @@ export const MovieList: FC<Props> = ({
const { movies } = useGlobalStore();
const filteredMovies = movies.filter((movie) => {
if (onlySeen) return movie.seen === 1;
if (onlyFavorites) return movie.favorite === 1;
if (onlyUpcoming) return new Date(movie.releaseDate) > new Date();
if (onlyReleased) return new Date(movie.releaseDate) < new Date();
return true;
let result = true;
if (typeof filterSeen === "number") result = movie.seen === filterSeen;
if (typeof filterFavorites === "number")
result = result && movie.favorite === filterFavorites;
if (typeof filterUpcoming === "number")
result = result && new Date(movie.releaseDate) > new Date();
if (typeof filterReleased === "number")
result = result && new Date(movie.releaseDate) < new Date();
return result;
});
let sortedMovies = filteredMovies.sort((a, b) => {

View File

@ -16,3 +16,10 @@ export const addMovie = async (movie: typeof movies.$inferInsert) => {
export const deleteMovie = async (id: number) => {
await db.delete(movies).where(eq(movies.id, id));
};
export const updateMovie = async (
id: number,
movie: Partial<typeof movies.$inferInsert>
) => {
await db.update(movies).set(movie).where(eq(movies.id, id));
};