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:
parent
b08cdea130
commit
3809110a39
|
|
@ -7,13 +7,18 @@ export default async function Home() {
|
||||||
<SearchMovies />
|
<SearchMovies />
|
||||||
<MovieList
|
<MovieList
|
||||||
heading="Upcoming"
|
heading="Upcoming"
|
||||||
onlyUpcoming
|
filterUpcoming={1}
|
||||||
sort="releaseDate"
|
sort="releaseDate"
|
||||||
sortDirection="desc"
|
sortDirection="desc"
|
||||||
/>
|
/>
|
||||||
<MovieList heading="My Watchlist" onlyReleased showFilters />
|
<MovieList
|
||||||
<MovieList heading="Seen" onlySeen />
|
heading="My Watchlist"
|
||||||
<MovieList heading="Favorites" onlyFavorites />
|
filterReleased={1}
|
||||||
|
filterSeen={0}
|
||||||
|
showFilters
|
||||||
|
/>
|
||||||
|
<MovieList heading="Seen" filterSeen={1} />
|
||||||
|
<MovieList heading="Favorites" filterFavorites={1} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,20 @@
|
||||||
import { movies } from "@/lib/db/schema";
|
import { movies } from "@/lib/db/schema";
|
||||||
import { createContext, FC, use, useState } from "react";
|
import { createContext, FC, use, useState } from "react";
|
||||||
|
|
||||||
|
type Movie = typeof movies.$inferSelect;
|
||||||
|
|
||||||
type GlobalStore = {
|
type GlobalStore = {
|
||||||
movies: (typeof movies.$inferSelect)[];
|
movies: Movie[];
|
||||||
addMovie: (movie: typeof movies.$inferSelect) => void;
|
addMovie: (movie: Movie) => void;
|
||||||
deleteMovie: (id: number) => void;
|
deleteMovie: (id: number) => void;
|
||||||
|
updateMovie: (id: number, movie: Partial<Movie>) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const globalStore = createContext<GlobalStore>({
|
const globalStore = createContext<GlobalStore>({
|
||||||
movies: [],
|
movies: [],
|
||||||
addMovie: () => {},
|
addMovie: () => {},
|
||||||
deleteMovie: () => {},
|
deleteMovie: () => {},
|
||||||
|
updateMovie: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -22,7 +26,7 @@ type Props = {
|
||||||
export const GlobalStoreProvider: FC<Props> = ({ children, initialMovies }) => {
|
export const GlobalStoreProvider: FC<Props> = ({ children, initialMovies }) => {
|
||||||
const [movies, setMovies] = useState<GlobalStore["movies"]>(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;
|
if (movies.find((m) => m.id === movie.id)) return;
|
||||||
|
|
||||||
setMovies((prev) => [...prev, movie]);
|
setMovies((prev) => [...prev, movie]);
|
||||||
|
|
@ -32,8 +36,16 @@ export const GlobalStoreProvider: FC<Props> = ({ children, initialMovies }) => {
|
||||||
setMovies((prev) => prev.filter((m) => m.id !== id));
|
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 (
|
return (
|
||||||
<globalStore.Provider value={{ movies, addMovie, deleteMovie }}>
|
<globalStore.Provider
|
||||||
|
value={{ movies, addMovie, deleteMovie, updateMovie }}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</globalStore.Provider>
|
</globalStore.Provider>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { FC, useState } from "react";
|
import { FC } from "react";
|
||||||
import { ReadMore } from "../ReadMore";
|
import { ReadMore } from "../ReadMore";
|
||||||
import { addMovie, deleteMovie } from "@/lib/db";
|
import { addMovie, deleteMovie, updateMovie } from "@/lib/db";
|
||||||
import { useGlobalStore } from "@/app/store/globalStore";
|
import { useGlobalStore } from "@/app/store/globalStore";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -16,6 +16,8 @@ type Props = {
|
||||||
notes?: string;
|
notes?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buttonClass = "px-2 py-6 text-sm w-full transition-colors cursor-pointer";
|
||||||
|
|
||||||
export const MovieCard: FC<Props> = ({
|
export const MovieCard: FC<Props> = ({
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
|
|
@ -31,9 +33,12 @@ export const MovieCard: FC<Props> = ({
|
||||||
movies,
|
movies,
|
||||||
addMovie: addMovieToStore,
|
addMovie: addMovieToStore,
|
||||||
deleteMovie: deleteMovieFromStore,
|
deleteMovie: deleteMovieFromStore,
|
||||||
|
updateMovie: updateMovieInStore,
|
||||||
} = useGlobalStore();
|
} = useGlobalStore();
|
||||||
const alreadyInStore = movies.find((m) => m.id === id);
|
const alreadyInStore = movies.find((m) => m.id === id);
|
||||||
|
|
||||||
|
const isReleased = new Date(releaseDate) < new Date();
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
const movie = {
|
const movie = {
|
||||||
id,
|
id,
|
||||||
|
|
@ -55,6 +60,16 @@ export const MovieCard: FC<Props> = ({
|
||||||
deleteMovieFromStore(id);
|
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 (
|
return (
|
||||||
<div className="flex w-full shadow-md rounded-lg overflow-hidden mx-auto group/card">
|
<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">
|
<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>
|
||||||
</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 && (
|
{!alreadyInStore && (
|
||||||
<button
|
<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}
|
onClick={handleAdd}
|
||||||
>
|
>
|
||||||
Add to watchlist
|
Add to watchlist
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{alreadyInStore && (
|
{alreadyInStore && (
|
||||||
<button
|
<>
|
||||||
className="bg-primary/70 text-white px-4 py-2 rounded-md w-full hover:bg-primary transition-colors cursor-pointer"
|
{isReleased && (
|
||||||
onClick={handleRemove}
|
<button
|
||||||
>
|
className={`${buttonClass} bg-accent/70 text-white hover:bg-accent`}
|
||||||
Remove from watchlist
|
onClick={handleSeen}
|
||||||
</button>
|
>
|
||||||
|
{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>
|
</div>
|
||||||
<figure className="absolute inset-0 w-full bottom-[20%]">
|
<figure className="absolute inset-0 w-full bottom-[20%]">
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import { useGlobalStore } from "@/app/store/globalStore";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
heading: string;
|
heading: string;
|
||||||
onlySeen?: boolean;
|
filterSeen?: 0 | 1;
|
||||||
onlyFavorites?: boolean;
|
filterFavorites?: 0 | 1;
|
||||||
onlyUpcoming?: boolean;
|
filterUpcoming?: 0 | 1;
|
||||||
onlyReleased?: boolean;
|
filterReleased?: 0 | 1;
|
||||||
|
|
||||||
showFilters?: boolean;
|
showFilters?: boolean;
|
||||||
sort?: "title" | "releaseDate" | "popularity";
|
sort?: "title" | "releaseDate" | "popularity";
|
||||||
|
|
@ -17,10 +17,10 @@ type Props = {
|
||||||
|
|
||||||
export const MovieList: FC<Props> = ({
|
export const MovieList: FC<Props> = ({
|
||||||
heading,
|
heading,
|
||||||
onlyFavorites,
|
filterSeen,
|
||||||
onlySeen,
|
filterFavorites,
|
||||||
onlyUpcoming,
|
filterUpcoming,
|
||||||
onlyReleased,
|
filterReleased,
|
||||||
showFilters = false,
|
showFilters = false,
|
||||||
sort = "title",
|
sort = "title",
|
||||||
sortDirection = "asc",
|
sortDirection = "asc",
|
||||||
|
|
@ -31,11 +31,15 @@ export const MovieList: FC<Props> = ({
|
||||||
const { movies } = useGlobalStore();
|
const { movies } = useGlobalStore();
|
||||||
|
|
||||||
const filteredMovies = movies.filter((movie) => {
|
const filteredMovies = movies.filter((movie) => {
|
||||||
if (onlySeen) return movie.seen === 1;
|
let result = true;
|
||||||
if (onlyFavorites) return movie.favorite === 1;
|
if (typeof filterSeen === "number") result = movie.seen === filterSeen;
|
||||||
if (onlyUpcoming) return new Date(movie.releaseDate) > new Date();
|
if (typeof filterFavorites === "number")
|
||||||
if (onlyReleased) return new Date(movie.releaseDate) < new Date();
|
result = result && movie.favorite === filterFavorites;
|
||||||
return true;
|
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) => {
|
let sortedMovies = filteredMovies.sort((a, b) => {
|
||||||
|
|
|
||||||
|
|
@ -16,3 +16,10 @@ export const addMovie = async (movie: typeof movies.$inferInsert) => {
|
||||||
export const deleteMovie = async (id: number) => {
|
export const deleteMovie = async (id: number) => {
|
||||||
await db.delete(movies).where(eq(movies.id, id));
|
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));
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue