diff --git a/package-lock.json b/package-lock.json index 201923c..858f33b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", - "swr": "^2.3.6" + "swr": "^2.3.6", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -1593,7 +1594,7 @@ "version": "19.1.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.9.tgz", "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1703,7 +1704,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-uri-to-buffer": { @@ -2770,6 +2771,35 @@ "engines": { "node": ">=18" } + }, + "node_modules/zustand": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 0d11da1..f7a77f5 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "react": "19.1.0", "react-dom": "19.1.0", "react-icons": "^5.5.0", - "swr": "^2.3.6" + "swr": "^2.3.6", + "zustand": "^5.0.8" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/src/app/aktor/[id]/page.tsx b/src/app/aktor/[id]/page.tsx index 8b93b43..1fee3ee 100644 --- a/src/app/aktor/[id]/page.tsx +++ b/src/app/aktor/[id]/page.tsx @@ -1,10 +1,10 @@ -import { MovieCard } from "@/components/atoms/MovieCard"; -import { ActorHero } from "@/components/molecules/ActorHero"; -import { Carousel } from "@/components/molecules/Carousel"; -import { Gallery } from "@/components/molecules/Gallery"; -import { convertToMovie } from "@/helpers/convertToMovie"; -import { TMDB } from "@/lib/tmdb"; -import { FaStar } from "react-icons/fa"; +import { MovieCard } from '@/components/atoms/MovieCard'; +import { ActorHero } from '@/components/molecules/ActorHero'; +import { Carousel } from '@/components/molecules/Carousel'; +import { Gallery } from '@/components/molecules/Gallery'; +import { convertToMovie } from '@/helpers/convertToMovie'; +import { TMDB } from '@/lib/tmdb'; +import { FaStar } from 'react-icons/fa'; export default async function Page({ params, @@ -32,7 +32,7 @@ export default async function Page({ new Date(b.release_date).getTime() - new Date(a.release_date).getTime() ) - .map((movie) => { + .map(movie => { const convertedMovie = convertToMovie(movie); if (!convertedMovie) return null; return ; diff --git a/src/app/api/movies/route.ts b/src/app/api/movies/route.ts deleted file mode 100644 index 9fc81b4..0000000 --- a/src/app/api/movies/route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DB_getMovies } from '@/lib/db/pb'; -import { NextResponse } from 'next/server'; - -export const GET = async () => { - const movies = await DB_getMovies(); - const res = NextResponse.json(movies); - - return res; -}; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a184fd2..b928920 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,8 +2,8 @@ import './globals.css'; import { Navbar } from '@/components/organisms/Navbar'; import { AuroraBackground } from '@/components/effects'; -import { GlobalStoreProvider } from './store/globalStore'; import { DB_getMovies } from '@/lib/db/pb'; +import { GlobalProvider } from './store/global'; export default async function RootLayout({ children, @@ -23,11 +23,11 @@ export default async function RootLayout({ - +
{children}
-
+ ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 4ff9b89..42b5ce8 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,7 +7,7 @@ export default async function Home() { return ( <> - + diff --git a/src/app/store/global.tsx b/src/app/store/global.tsx new file mode 100644 index 0000000..49afda8 --- /dev/null +++ b/src/app/store/global.tsx @@ -0,0 +1,107 @@ +'use client'; +import { Spinner } from '@/components/atoms/Spinner'; +import { DB_addMovie, DB_deleteMovie, DB_updateMovie } from '@/lib/db/pb'; +import { + createContext, + FC, + useContext, + useEffect, + useRef, + useState, +} from 'react'; +import { create, useStore } from 'zustand'; +import { persist } from 'zustand/middleware'; + +type Props = { + initialMovies: Movie[]; +}; + +type State = { + movies: Movie[]; + setMovies: (movies: Movie[]) => void; + addMovie: (movie: Movie) => void; + deleteMovie: (id: number) => void; + updateMovie: (id: number, movie: Partial) => void; + displayType: 'grid' | 'list'; + setDisplayType: (type: 'grid' | 'list') => void; +}; + +type Store = ReturnType; + +const createStore = ({ initialMovies }: Props) => { + return create()( + persist( + (set, get) => ({ + movies: initialMovies, + setMovies: (movies: Movie[]) => set({ movies }), + + addMovie: (movie: Movie) => { + if (get().movies.find(m => m.id == movie.id)) return; + + DB_addMovie(movie); + set(state => ({ movies: [...state.movies, movie] })); + }, + deleteMovie: (id: number) => { + DB_deleteMovie(id); + set(state => ({ + movies: state.movies.filter(m => m.id != id), + })); + }, + updateMovie: (id: number, movie: Partial) => { + DB_updateMovie(id, movie); + set(state => ({ + movies: state.movies.map(m => + m.id == id ? { ...m, ...movie } : m + ), + })); + }, + + displayType: 'grid', + setDisplayType: (type: 'grid' | 'list') => set({ displayType: type }), + }), + { + name: 'global', + partialize: state => ({ displayType: state.displayType }), + } + ) + ); +}; + +export const GlobalContext = createContext(null); + +export const GlobalProvider: FC< + { + children: React.ReactNode; + } & Props +> = ({ children, initialMovies = [] }) => { + const store = useRef(createStore({ initialMovies })); + const [firstRender, setFirstRender] = useState(true); + + useEffect(() => { + if (firstRender) { + setFirstRender(false); + } + }, [firstRender]); + + if (firstRender) { + return ( +
+ +
+ ); + } + + return ( + + {children} + + ); +}; + +export const useGlobalStore = (selector: (state: State) => T): T => { + const store = useContext(GlobalContext); + if (!store) throw new Error('GlobalStore not found'); + return useStore(store, selector); +}; + +export const useGlobalZustandStore = useGlobalStore; diff --git a/src/app/store/globalStore.tsx b/src/app/store/globalStore.tsx deleted file mode 100644 index 5d0610c..0000000 --- a/src/app/store/globalStore.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client'; -import { Spinner } from '@/components/atoms/Spinner'; -import { useLocalStorage } from '@/hooks/useLocalStorage'; -import { DB_addMovie, DB_deleteMovie, DB_updateMovie } from '@/lib/db/pb'; -import { createContext, FC, use, useEffect, useState } from 'react'; - -type GlobalStore = { - movies: Movie[]; - addMovie: (movie: Movie) => void; - deleteMovie: (id: number) => void; - updateMovie: (id: number, movie: Partial) => void; - displayType: 'grid' | 'list'; - setDisplayType: (type: 'grid' | 'list') => void; -}; - -const globalStore = createContext({ - movies: [], - addMovie: () => {}, - deleteMovie: () => {}, - updateMovie: () => {}, - displayType: 'grid', - setDisplayType: () => {}, -}); - -type Props = { - children: React.ReactNode; - initialMovies?: Movie[]; -}; - -export const GlobalStoreProvider: FC = ({ - children, - initialMovies = [], -}) => { - // Optimistic update - const [firstRender, setFirstRender] = useState(true); - const [movies, setMovies] = useState(initialMovies); - const [displayType, setDisplayType] = useLocalStorage< - GlobalStore['displayType'] - >('displayType', 'grid'); - - useEffect(() => { - if (firstRender) { - setFirstRender(false); - } - }, [firstRender]); - - const addMovie = async (movie: Movie) => { - if (movies.find(m => m.id === movie.id)) return; - - DB_addMovie(movie); - setMovies(prev => [...prev, movie]); - }; - - const deleteMovie = async (id: number) => { - DB_deleteMovie(id); - setMovies(prev => prev.filter(m => m.id !== id)); - }; - - const updateMovie = async (id: number, movie: Partial) => { - DB_updateMovie(id, movie); - setMovies(prev => prev.map(m => (m.id === id ? { ...m, ...movie } : m))); - }; - - return ( - - {firstRender ? ( -
- -
- ) : ( - children - )} -
- ); -}; - -export const useGlobalStore = () => { - return use(globalStore); -}; diff --git a/src/components/atoms/MovieCard/index.tsx b/src/components/atoms/MovieCard/index.tsx index 90aea29..de29a17 100644 --- a/src/components/atoms/MovieCard/index.tsx +++ b/src/components/atoms/MovieCard/index.tsx @@ -1,11 +1,11 @@ 'use client'; import { FC } from 'react'; -import { useGlobalStore } from '@/app/store/globalStore'; import { FaFire, FaPlusCircle, FaTrash } from 'react-icons/fa'; import Link from 'next/link'; import { RxEyeOpen } from 'react-icons/rx'; import { MdFavorite } from 'react-icons/md'; import { RiCalendarCheckLine, RiCalendarScheduleLine } from 'react-icons/ri'; +import { useGlobalStore } from '@/app/store/global'; type Props = Movie & { showDayCounter?: boolean; @@ -17,16 +17,14 @@ export const MovieCard: FC = ({ simpleToggle = false, ...movie }) => { - const { - movies, - addMovie: addMovieToStore, - deleteMovie: deleteMovieFromStore, - updateMovie: updateMovieInStore, - } = useGlobalStore(); + const movies = useGlobalStore(state => state.movies); + const addMovie = useGlobalStore(state => state.addMovie); + const deleteMovie = useGlobalStore(state => state.deleteMovie); + const updateMovie = useGlobalStore(state => state.updateMovie); const { vote_average, popularity, poster_path, title, overview } = movie; const { id } = movie; - const alreadyInStore = movies.find(m => m.id === id); + const alreadyInStore = movies.find(m => m.id == id); const seen = alreadyInStore?.seen || movie.seen; const favorite = alreadyInStore?.favorite || movie.favorite; @@ -40,22 +38,22 @@ export const MovieCard: FC = ({ : 'from-red-400 to-pink-400'; const handleAdd = () => { - addMovieToStore(movie); + addMovie(movie); }; const handleRemove = () => { - deleteMovieFromStore(id); + deleteMovie(id); }; const handleSeen = () => { - updateMovieInStore(id, { + updateMovie(id, { seen: !movie.seen, favorite: false, }); }; const handleFavorite = () => { - updateMovieInStore(id, { + updateMovie(id, { favorite: !movie.favorite, seen: movie.seen || !movie.favorite, }); diff --git a/src/components/atoms/MovieRow/index.tsx b/src/components/atoms/MovieRow/index.tsx index 9176853..da40164 100644 --- a/src/components/atoms/MovieRow/index.tsx +++ b/src/components/atoms/MovieRow/index.tsx @@ -3,8 +3,7 @@ import { formatter } from '@/helpers/formater'; import Link from 'next/link'; import { FC } from 'react'; import { FaCalendar, FaClock, FaStar, FaEye, FaHeart } from 'react-icons/fa'; -import { motion, useAnimationControls, useMotionValue } from 'framer-motion'; -import { useGlobalStore } from '@/app/store/globalStore'; +import { useGlobalStore } from '@/app/store/global'; type Props = { movie: Movie; @@ -17,10 +16,7 @@ export const MovieRow: FC = ({ isUpcoming = false, compact = false, }) => { - const { movies, addMovie, updateMovie } = useGlobalStore(); - - const dragControls = useAnimationControls(); - const x = useMotionValue(0); + const movies = useGlobalStore(state => state.movies); const daysSinceRelease = Math.abs( Math.floor( @@ -30,128 +26,77 @@ export const MovieRow: FC = ({ ); // Check if movie is already in store. - const movieInStore = movies.find(m => m.id === movie.id); + 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 (
- {/* Background actions */} -
-
- -
- -
- -
-
- - - -
- {movie.title} -
+
+ {movie.title} +
-
-

- {movie.title} -

-
-
- {isUpcoming ? ( - - ) : ( - - )} - {formatter.formatDate(movie.release_date)} +
+

+ {movie.title} +

+
+
+ {isUpcoming ? ( + + ) : ( + + )} + {formatter.formatDate(movie.release_date)} +
+ + {!!movie.vote_average && ( +
+ + {movie.vote_average.toFixed(1)}
+ )} - {!!movie.vote_average && ( -
- - {movie.vote_average.toFixed(1)} -
- )} + {(isFavorite || movie.favorite) && ( +
+ )} - {(isFavorite || movie.favorite) && ( -
- )} - - {(isWatched || movie.seen) && ( -
- )} -
+ {(isWatched || movie.seen) && ( +
+ )}
+
- {!compact && ( -
- {isUpcoming - ? `za ${daysSinceRelease} dni` - : `od ${daysSinceRelease} dni`} -
- )} - - + {!compact && ( +
+ {isUpcoming + ? `za ${daysSinceRelease} dni` + : `od ${daysSinceRelease} dni`} +
+ )} +
); }; diff --git a/src/components/molecules/ActorHero/index.tsx b/src/components/molecules/ActorHero/index.tsx index c613b77..1aa4bd3 100644 --- a/src/components/molecules/ActorHero/index.tsx +++ b/src/components/molecules/ActorHero/index.tsx @@ -1,9 +1,9 @@ -"use client"; +'use client'; -import { BackButton } from "@/components/atoms/BackButton"; -import { formatter } from "@/helpers/formater"; -import { PersonDetailsRich } from "@/lib/tmdb/types"; -import { FC } from "react"; +import { BackButton } from '@/components/atoms/BackButton'; +import { formatter } from '@/helpers/formater'; +import { PersonDetailsRich } from '@/lib/tmdb/types'; +import { FC } from 'react'; import { FaCalendarAlt, FaMapMarkerAlt, @@ -15,7 +15,7 @@ import { FaTwitter, FaYoutube, FaTiktok, -} from "react-icons/fa"; +} from 'react-icons/fa'; type Props = { personDetails: PersonDetailsRich; @@ -37,13 +37,13 @@ export const ActorHero: FC = ({ personDetails }) => { const getGenderText = (gender: number) => { switch (gender) { case 1: - return "Kobieta"; + return 'Kobieta'; case 2: - return "Mężczyzna"; + return 'Mężczyzna'; case 3: - return "Niebinarne"; + return 'Niebinarne'; default: - return "Nie określono"; + return 'Nie określono'; } }; @@ -65,7 +65,7 @@ export const ActorHero: FC = ({ personDetails }) => { src={ personDetails.profile_path ? `https://image.tmdb.org/t/p/w500${personDetails.profile_path}` - : "/api/placeholder/400/600" + : '/api/placeholder/400/600' } alt={personDetails.name} className="w-80 h-auto rounded-2xl shadow-2xl shadow-purple-500/20 group-hover:shadow-purple-500/40 transition-all duration-500" @@ -88,9 +88,9 @@ export const ActorHero: FC = ({ personDetails }) => { {calculateAge( personDetails.birthday, personDetails.deathday - )}{" "} + )}{' '} lat - {personDetails.deathday && " w chwili śmierci"}) + {personDetails.deathday && ' w chwili śmierci'}) )} @@ -114,8 +114,8 @@ export const ActorHero: FC = ({ personDetails }) => { {personDetails.also_known_as.length > 0 && (

- Znany również jako:{" "} - {personDetails.also_known_as.slice(0, 3).join(", ")} + Znany również jako:{' '} + {personDetails.also_known_as.slice(0, 3).join(', ')}

)} @@ -171,7 +171,7 @@ export const ActorHero: FC = ({ personDetails }) => {
{personDetails.biography - .split("\n\n") + .split('\n\n') .map((paragraph, index) => (

{paragraph}

))} @@ -185,7 +185,7 @@ export const ActorHero: FC = ({ personDetails }) => {

Linki

-
+
{Object.entries(personDetails.external_ids).map( ([key, value]) => { if (!(key in externalIdsMap) || !value) { @@ -222,32 +222,32 @@ export const ActorHero: FC = ({ personDetails }) => { const externalIdsMap = { facebook_id: { - label: "Facebook", + label: 'Facebook', icon: , url: (id: string) => `https://www.facebook.com/${id}`, }, instagram_id: { - label: "Instagram", + label: 'Instagram', icon: , url: (id: string) => `https://www.instagram.com/${id}`, }, twitter_id: { - label: "Twitter", + label: 'Twitter', icon: , url: (id: string) => `https://www.twitter.com/${id}`, }, tiktok_id: { - label: "TikTok", + label: 'TikTok', icon: , url: (id: string) => `https://www.tiktok.com/${id}`, }, youtube_id: { - label: "YouTube", + label: 'YouTube', icon: , url: (id: string) => `https://www.youtube.com/${id}`, }, imdb_id: { - label: "IMDb", + label: 'IMDb', icon: , url: (id: string) => `https://www.imdb.com/name/${id}`, }, diff --git a/src/components/molecules/HeroMovie/index.tsx b/src/components/molecules/HeroMovie/index.tsx index 517025a..ee38b7a 100644 --- a/src/components/molecules/HeroMovie/index.tsx +++ b/src/components/molecules/HeroMovie/index.tsx @@ -1,10 +1,9 @@ -"use client"; -import { BackButton } from "@/components/atoms/BackButton"; -import { Button } from "@/components/atoms/Button"; -import { GenreLabel } from "@/components/atoms/GenreLabel"; -import { MovieDetailsRich } from "@/lib/tmdb/types"; -import { useGlobalStore } from "@/app/store/globalStore"; -import { FC } from "react"; +'use client'; +import { BackButton } from '@/components/atoms/BackButton'; +import { Button } from '@/components/atoms/Button'; +import { GenreLabel } from '@/components/atoms/GenreLabel'; +import { MovieDetailsRich } from '@/lib/tmdb/types'; +import { FC } from 'react'; import { FaHeart, FaBookmark, @@ -12,19 +11,23 @@ import { FaCalendar, FaGlobe, FaEye, -} from "react-icons/fa"; -import { convertToMovie } from "@/helpers/convertToMovie"; -import { formatter } from "@/helpers/formater"; +} from 'react-icons/fa'; +import { convertToMovie } from '@/helpers/convertToMovie'; +import { formatter } from '@/helpers/formater'; +import { useGlobalStore } from '@/app/store/global'; type Props = { movieDetails: MovieDetailsRich; }; export const HeroMovie: FC = ({ movieDetails }) => { - const { movies, addMovie, deleteMovie, updateMovie } = useGlobalStore(); + const movies = useGlobalStore(state => state.movies); + const addMovie = useGlobalStore(state => state.addMovie); + const deleteMovie = useGlobalStore(state => state.deleteMovie); + const updateMovie = useGlobalStore(state => state.updateMovie); // Check if movie is in store and get its state. - const movieInStore = movies.find((m) => m.id === movieDetails.id); + const movieInStore = movies.find(m => m.id == movieDetails.id); const isInStore = !!movieInStore; const isFavorite = movieInStore?.favorite || false; const isSeen = movieInStore?.seen || false; @@ -115,8 +118,8 @@ export const HeroMovie: FC = ({ movieDetails }) => { key={i} className={`text-2xl ${ i < Math.round(movieDetails.vote_average / 2) - ? "text-yellow-400" - : "text-gray-600" + ? 'text-yellow-400' + : 'text-gray-600' }`} > ★ @@ -173,7 +176,7 @@ export const HeroMovie: FC = ({ movieDetails }) => { Gatunki
- {movieDetails.genres.map((genre) => ( + {movieDetails.genres.map(genre => ( = ({ movieDetails }) => { gradient={ isInStore ? { - from: "from-purple-600 hover:from-purple-500", - to: "to-emerald-600 hover:to-emerald-500", + from: 'from-purple-600 hover:from-purple-500', + to: 'to-emerald-600 hover:to-emerald-500', } : { - from: "from-purple-600 hover:from-purple-500", - to: "to-pink-600 hover:to-pink-500", + from: 'from-purple-600 hover:from-purple-500', + to: 'to-pink-600 hover:to-pink-500', } } className={`flex items-center gap-3`} onClick={handleAddToList} > - {isInStore ? "Usuń z listy" : "Dodaj do listy"} + {isInStore ? 'Usuń z listy' : 'Dodaj do listy'}
)} diff --git a/src/components/molecules/MovieCast/index.tsx b/src/components/molecules/MovieCast/index.tsx index 5d65800..2547a5d 100644 --- a/src/components/molecules/MovieCast/index.tsx +++ b/src/components/molecules/MovieCast/index.tsx @@ -1,10 +1,10 @@ -"use client"; -import Link from "next/link"; -import { Button } from "@/components/atoms/Button"; -import { MovieDetailsRich } from "@/lib/tmdb/types"; -import { FC, useState } from "react"; -import { FaDollarSign } from "react-icons/fa"; -import { formatter } from "@/helpers/formater"; +'use client'; +import Link from 'next/link'; +import { Button } from '@/components/atoms/Button'; +import { MovieDetailsRich } from '@/lib/tmdb/types'; +import { FC, useState } from 'react'; +import { FaDollarSign } from 'react-icons/fa'; +import { formatter } from '@/helpers/formater'; type Props = { movieDetails: MovieDetailsRich; @@ -13,16 +13,10 @@ type Props = { export const MovieCast: FC = ({ movieDetails }) => { const [limit, setLimit] = useState(8); const director = movieDetails?.credits.crew.find( - (member) => member.job === "Director" + member => member.job === 'Director' ); const mainCast = movieDetails?.credits.cast.slice(0, limit) || []; - const formatCurrency = (amount: number) => - new Intl.NumberFormat("pl-PL", { - style: "currency", - currency: "USD", - }).format(amount); - return (
@@ -32,7 +26,7 @@ export const MovieCast: FC = ({ movieDetails }) => {

Obsada

- {mainCast.map((actor) => ( + {mainCast.map(actor => ( = ({ movieDetails }) => {
{actor.name} = ({ movieDetails }) => {
{movieDetails.production_companies .slice(0, 3) - .map((company) => ( + .map(company => (

{company.name}

diff --git a/src/components/molecules/MovieList/index.tsx b/src/components/molecules/MovieList/index.tsx index 90dbad7..e0e85a3 100644 --- a/src/components/molecules/MovieList/index.tsx +++ b/src/components/molecules/MovieList/index.tsx @@ -1,13 +1,14 @@ 'use client'; import { FC, ReactNode, useState } from 'react'; import { MovieCard } from '@/components/atoms/MovieCard'; -import { useGlobalStore } from '@/app/store/globalStore'; import { Dropdown } from '@/components/atoms/Dropdown'; import { useAutoAnimate } from '@formkit/auto-animate/react'; import { Button } from '@/components/atoms/Button'; import { Label } from '@/components/atoms/Label'; import { FaList } from 'react-icons/fa'; import { MovieRow } from '@/components/atoms/MovieRow'; +import { useGlobalStore } from '@/app/store/global'; +import { SearchInput } from '@/components/atoms/SearchInput'; type Props = { heading?: string; @@ -17,6 +18,7 @@ type Props = { overrideMovies?: Movie[]; overrideDisplayType?: 'grid' | 'list'; + showSearch?: boolean; showFilters?: boolean; filterSeen?: 0 | 1; filterFavorites?: 0 | 1; @@ -25,7 +27,7 @@ type Props = { fluid?: boolean; showSorting?: boolean; - sort?: 'title' | 'releaseDate' | 'popularity'; + sort?: 'title' | 'releaseDate' | 'popularity' | 'voteAverage'; sortDirection?: 'asc' | 'desc'; loadMore?: boolean; @@ -37,6 +39,7 @@ export const MovieList: FC = ({ colors = 'white', overrideMovies, showFilters = true, + showSearch = false, filterSeen: filterSeenInitial, filterFavorites: filterFavoritesInitial, filterUpcoming: filterUpcomingInitial, @@ -48,23 +51,23 @@ export const MovieList: FC = ({ loadMore = false, overrideDisplayType, }) => { - const { - movies: storeMovies, - displayType: displayTypeInitial, - setDisplayType, - } = useGlobalStore(); + const storeMovies = useGlobalStore(state => state.movies); + const displayTypeInitial = useGlobalStore(state => state.displayType); + const setDisplayType = useGlobalStore(state => state.setDisplayType); + const movies = overrideMovies || storeMovies; const displayType = overrideDisplayType || displayTypeInitial; const [filter, setFilter] = useState({ + search: '', seen: filterSeenInitial, favorites: filterFavoritesInitial, upcoming: filterUpcomingInitial, released: filterReleasedInitial, }); - const [sort, setSort] = useState<'title' | 'releaseDate' | 'popularity'>( - sortType - ); + const [sort, setSort] = useState< + 'title' | 'releaseDate' | 'popularity' | 'voteAverage' + >(sortType); const [loaded, setLoaded] = useState(8); const [parent] = useAutoAnimate(); @@ -88,6 +91,9 @@ export const MovieList: FC = ({ result = result && filter.released ? releaseDate < today : releaseDate > today; } + if (filter.search) { + result = movie.title.toLowerCase().includes(filter.search.toLowerCase()); + } return result; }); @@ -98,6 +104,7 @@ export const MovieList: FC = ({ new Date(b.release_date).getTime() - new Date(a.release_date).getTime() ); if (sort === 'popularity') return b.popularity - a.popularity; + if (sort === 'voteAverage') return b.vote_average - a.vote_average; return 0; }); @@ -108,6 +115,7 @@ export const MovieList: FC = ({ const handleFilter = (key?: keyof typeof filter) => { setFilter({ + search: '', seen: filterSeenInitial, favorites: filterFavoritesInitial, upcoming: filterUpcomingInitial, @@ -168,6 +176,7 @@ export const MovieList: FC = ({ } onClick={() => setFilter({ + search: '', seen: 0, released: 1, favorites: filterFavoritesInitial, @@ -215,6 +224,7 @@ export const MovieList: FC = ({ { label: 'Tytuł', value: 'title' }, { label: 'Data premiery', value: 'releaseDate' }, { label: 'Popularność', value: 'popularity' }, + { label: 'Ocena', value: 'voteAverage' }, ]} defaultValue={sort} callback={value => setSort(value as 'title')} @@ -223,6 +233,15 @@ export const MovieList: FC = ({ )}
)} + {showSearch && ( +
+ setFilter({ ...filter, search: value })} + /> +
+ )} {filteredMovies.length === 0 && (

Brak filmów

)} diff --git a/src/components/molecules/RandomMovie/index.tsx b/src/components/molecules/RandomMovie/index.tsx index fe3bd86..4f0f39a 100644 --- a/src/components/molecules/RandomMovie/index.tsx +++ b/src/components/molecules/RandomMovie/index.tsx @@ -1,8 +1,8 @@ 'use client'; import { FC, useMemo, useState } from 'react'; -import { useGlobalStore } from '@/app/store/globalStore'; import { Button } from '@/components/atoms/Button'; import { FaDice } from 'react-icons/fa'; +import { useGlobalStore } from '@/app/store/global'; import Link from 'next/link'; type StoreFilter = 'all' | 'not_seen' | 'released' | 'favorites' | 'to_watch'; @@ -20,7 +20,7 @@ export const RandomMovie: FC = ({ colors = 'purple', className = '', }) => { - const { movies } = useGlobalStore(); + const movies = useGlobalStore(state => state.movies); const [selectedMovie, setSelectedMovie] = useState(null); // Filter movies based on the selected store filter. diff --git a/src/components/molecules/TrackedMovies/index.tsx b/src/components/molecules/TrackedMovies/index.tsx index 0cea020..1a1851e 100644 --- a/src/components/molecules/TrackedMovies/index.tsx +++ b/src/components/molecules/TrackedMovies/index.tsx @@ -1,8 +1,8 @@ 'use client'; import { FC } from 'react'; -import { useGlobalStore } from '@/app/store/globalStore'; import { FaCalendar, FaClock } from 'react-icons/fa'; import { MovieRow } from '@/components/atoms/MovieRow'; +import { useGlobalStore } from '@/app/store/global'; type Props = { overrideMovies?: Movie[]; @@ -17,7 +17,7 @@ export const TrackedMovies: FC = ({ labelCurrent = 'Aktualnie w kinach', labelUpcoming = 'Nadchodzące premiery', }) => { - const { movies: storeMovies } = useGlobalStore(); + const storeMovies = useGlobalStore(state => state.movies); const movies = overrideMovies || storeMovies; diff --git a/src/components/organisms/Hero/index.tsx b/src/components/organisms/Hero/index.tsx index 0978e84..2d47cb6 100644 --- a/src/components/organisms/Hero/index.tsx +++ b/src/components/organisms/Hero/index.tsx @@ -8,9 +8,9 @@ import { FaMinus, } from 'react-icons/fa'; import { RiCalendarCheckLine, RiCalendarScheduleLine } from 'react-icons/ri'; -import { useGlobalStore } from '@/app/store/globalStore'; import Link from 'next/link'; import { Button } from '@/components/atoms/Button'; +import { useGlobalStore } from '@/app/store/global'; type Props = { movies: Movie[]; @@ -28,11 +28,9 @@ export const Hero: FC = ({ const [currentIndex, setCurrentIndex] = useState(0); const [isTransitioning, setIsTransitioning] = useState(false); - const { - movies: storedMovies, - addMovie: addMovieToStore, - deleteMovie: deleteMovieInStore, - } = useGlobalStore(); + const storedMovies = useGlobalStore(state => state.movies); + const addMovie = useGlobalStore(state => state.addMovie); + const deleteMovie = useGlobalStore(state => state.deleteMovie); const currentMovie = movies[currentIndex]; @@ -92,11 +90,11 @@ export const Hero: FC = ({ }, [autoRotate, rotateInterval, nextSlide, movies.length]); const handleAdd = async () => { - addMovieToStore(currentMovie); + addMovie(currentMovie); }; const handleRemove = async () => { - deleteMovieInStore(id); + deleteMovie(id); }; return (