From d67e34c75cb3c3574c4ced7ba4e23be5fbbe07c1 Mon Sep 17 00:00:00 2001 From: Norbert Maciaszek Date: Fri, 22 Aug 2025 19:17:48 +0200 Subject: [PATCH] feat: enhance GlobalStoreProvider and MovieList components with display type management and loading spinner; integrate local storage for display preferences and improve user experience during initial render --- src/app/store/globalStore.tsx | 35 ++++++++++++++++++-- src/components/molecules/MovieList/index.tsx | 34 +++++++++++-------- src/components/organisms/Navbar/index.tsx | 4 +-- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/app/store/globalStore.tsx b/src/app/store/globalStore.tsx index cd9d568..b288038 100644 --- a/src/app/store/globalStore.tsx +++ b/src/app/store/globalStore.tsx @@ -1,7 +1,9 @@ "use client"; +import { Spinner } from "@/components/atoms/Spinner"; +import { useLocalStorage } from "@/hooks/useLocalStorage"; import { addMovieToDB, deleteMovieFromDB, updateMovieInDB } from "@/lib/db"; import { movies } from "@/lib/db/schema"; -import { createContext, FC, use, useState } from "react"; +import { createContext, FC, use, useEffect, useState } from "react"; type Movie = typeof movies.$inferSelect; @@ -10,6 +12,8 @@ type GlobalStore = { 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({ @@ -17,6 +21,8 @@ const globalStore = createContext({ addMovie: () => {}, deleteMovie: () => {}, updateMovie: () => {}, + displayType: "grid", + setDisplayType: () => {}, }); type Props = { @@ -29,7 +35,17 @@ export const GlobalStoreProvider: FC = ({ 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; @@ -52,9 +68,22 @@ export const GlobalStoreProvider: FC = ({ return ( - {children} + {firstRender ? ( +
+ +
+ ) : ( + children + )}
); }; diff --git a/src/components/molecules/MovieList/index.tsx b/src/components/molecules/MovieList/index.tsx index 636d951..88c5226 100644 --- a/src/components/molecules/MovieList/index.tsx +++ b/src/components/molecules/MovieList/index.tsx @@ -14,9 +14,9 @@ type Props = { heading?: string; icon?: ReactNode; colors?: keyof typeof colorsMap; - displayType?: "grid" | "list"; overrideMovies?: Movie[]; + overrideDisplayType?: "grid" | "list"; showFilters?: boolean; filterSeen?: 0 | 1; @@ -47,12 +47,16 @@ export const MovieList: FC = ({ sort: sortType = "releaseDate", sortDirection = "asc", loadMore = false, - displayType = "grid", + overrideDisplayType, }) => { - const { movies: storeMovies } = useGlobalStore(); + const { + movies: storeMovies, + displayType: displayTypeInitial, + setDisplayType, + } = useGlobalStore(); const movies = overrideMovies || storeMovies; + const displayType = overrideDisplayType || displayTypeInitial; - const [display, setDisplay] = useState<"grid" | "list">(displayType); const [filter, setFilter] = useState({ seen: filterSeenInitial, favorites: filterFavoritesInitial, @@ -205,15 +209,17 @@ export const MovieList: FC = ({ defaultValue={sort} callback={(value) => setSort(value as "title")} /> - + {!overrideDisplayType && ( + + )} )} @@ -227,7 +233,7 @@ export const MovieList: FC = ({ ref={parent} > {sortedMovies.map((movie) => - display === "grid" ? ( + displayType === "grid" ? ( ) : ( diff --git a/src/components/organisms/Navbar/index.tsx b/src/components/organisms/Navbar/index.tsx index 5d73f36..47e0229 100644 --- a/src/components/organisms/Navbar/index.tsx +++ b/src/components/organisms/Navbar/index.tsx @@ -1,7 +1,7 @@ "use client"; import Link from "next/link"; import { useState } from "react"; -import { HiSearch, HiHome, HiViewGrid } from "react-icons/hi"; +import { HiSearch, HiHome, HiSparkles } from "react-icons/hi"; import { Search } from "./components/Search"; import { usePathname } from "next/navigation"; @@ -17,7 +17,7 @@ const navigationItems = [ { label: "Odkrywaj", href: "/odkrywaj", - icon: HiViewGrid, + icon: HiSparkles, emoji: "🎬", color: "from-purple-500 to-pink-600", description: "Znajdź nowe filmy",