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
This commit is contained in:
		
							parent
							
								
									50aa22ee6c
								
							
						
					
					
						commit
						d67e34c75c
					
				|  | @ -1,7 +1,9 @@ | ||||||
| "use client"; | "use client"; | ||||||
|  | import { Spinner } from "@/components/atoms/Spinner"; | ||||||
|  | import { useLocalStorage } from "@/hooks/useLocalStorage"; | ||||||
| import { addMovieToDB, deleteMovieFromDB, updateMovieInDB } from "@/lib/db"; | import { addMovieToDB, deleteMovieFromDB, updateMovieInDB } from "@/lib/db"; | ||||||
| import { movies } from "@/lib/db/schema"; | 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; | type Movie = typeof movies.$inferSelect; | ||||||
| 
 | 
 | ||||||
|  | @ -10,6 +12,8 @@ type GlobalStore = { | ||||||
|   addMovie: (movie: Movie) => void; |   addMovie: (movie: Movie) => void; | ||||||
|   deleteMovie: (id: number) => void; |   deleteMovie: (id: number) => void; | ||||||
|   updateMovie: (id: number, movie: Partial<Movie>) => void; |   updateMovie: (id: number, movie: Partial<Movie>) => void; | ||||||
|  |   displayType: "grid" | "list"; | ||||||
|  |   setDisplayType: (type: "grid" | "list") => void; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const globalStore = createContext<GlobalStore>({ | const globalStore = createContext<GlobalStore>({ | ||||||
|  | @ -17,6 +21,8 @@ const globalStore = createContext<GlobalStore>({ | ||||||
|   addMovie: () => {}, |   addMovie: () => {}, | ||||||
|   deleteMovie: () => {}, |   deleteMovie: () => {}, | ||||||
|   updateMovie: () => {}, |   updateMovie: () => {}, | ||||||
|  |   displayType: "grid", | ||||||
|  |   setDisplayType: () => {}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| type Props = { | type Props = { | ||||||
|  | @ -29,7 +35,17 @@ export const GlobalStoreProvider: FC<Props> = ({ | ||||||
|   initialMovies = [], |   initialMovies = [], | ||||||
| }) => { | }) => { | ||||||
|   // Optimistic update
 |   // Optimistic update
 | ||||||
|  |   const [firstRender, setFirstRender] = useState(true); | ||||||
|   const [movies, setMovies] = useState<GlobalStore["movies"]>(initialMovies); |   const [movies, setMovies] = useState<GlobalStore["movies"]>(initialMovies); | ||||||
|  |   const [displayType, setDisplayType] = useLocalStorage< | ||||||
|  |     GlobalStore["displayType"] | ||||||
|  |   >("displayType", "grid"); | ||||||
|  | 
 | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (firstRender) { | ||||||
|  |       setFirstRender(false); | ||||||
|  |     } | ||||||
|  |   }, [firstRender]); | ||||||
| 
 | 
 | ||||||
|   const addMovie = async (movie: Movie) => { |   const addMovie = async (movie: Movie) => { | ||||||
|     if (movies.find((m) => m.id === movie.id)) return; |     if (movies.find((m) => m.id === movie.id)) return; | ||||||
|  | @ -52,9 +68,22 @@ export const GlobalStoreProvider: FC<Props> = ({ | ||||||
| 
 | 
 | ||||||
|   return ( |   return ( | ||||||
|     <globalStore.Provider |     <globalStore.Provider | ||||||
|       value={{ movies, addMovie, deleteMovie, updateMovie }} |       value={{ | ||||||
|  |         movies, | ||||||
|  |         addMovie, | ||||||
|  |         deleteMovie, | ||||||
|  |         updateMovie, | ||||||
|  |         displayType, | ||||||
|  |         setDisplayType, | ||||||
|  |       }} | ||||||
|     > |     > | ||||||
|       {children} |       {firstRender ? ( | ||||||
|  |         <div className="flex justify-center items-center h-screen bg-black/80"> | ||||||
|  |           <Spinner /> | ||||||
|  |         </div> | ||||||
|  |       ) : ( | ||||||
|  |         children | ||||||
|  |       )} | ||||||
|     </globalStore.Provider> |     </globalStore.Provider> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -14,9 +14,9 @@ type Props = { | ||||||
|   heading?: string; |   heading?: string; | ||||||
|   icon?: ReactNode; |   icon?: ReactNode; | ||||||
|   colors?: keyof typeof colorsMap; |   colors?: keyof typeof colorsMap; | ||||||
|   displayType?: "grid" | "list"; |  | ||||||
| 
 | 
 | ||||||
|   overrideMovies?: Movie[]; |   overrideMovies?: Movie[]; | ||||||
|  |   overrideDisplayType?: "grid" | "list"; | ||||||
| 
 | 
 | ||||||
|   showFilters?: boolean; |   showFilters?: boolean; | ||||||
|   filterSeen?: 0 | 1; |   filterSeen?: 0 | 1; | ||||||
|  | @ -47,12 +47,16 @@ export const MovieList: FC<Props> = ({ | ||||||
|   sort: sortType = "releaseDate", |   sort: sortType = "releaseDate", | ||||||
|   sortDirection = "asc", |   sortDirection = "asc", | ||||||
|   loadMore = false, |   loadMore = false, | ||||||
|   displayType = "grid", |   overrideDisplayType, | ||||||
| }) => { | }) => { | ||||||
|   const { movies: storeMovies } = useGlobalStore(); |   const { | ||||||
|  |     movies: storeMovies, | ||||||
|  |     displayType: displayTypeInitial, | ||||||
|  |     setDisplayType, | ||||||
|  |   } = useGlobalStore(); | ||||||
|   const movies = overrideMovies || storeMovies; |   const movies = overrideMovies || storeMovies; | ||||||
|  |   const displayType = overrideDisplayType || displayTypeInitial; | ||||||
| 
 | 
 | ||||||
|   const [display, setDisplay] = useState<"grid" | "list">(displayType); |  | ||||||
|   const [filter, setFilter] = useState({ |   const [filter, setFilter] = useState({ | ||||||
|     seen: filterSeenInitial, |     seen: filterSeenInitial, | ||||||
|     favorites: filterFavoritesInitial, |     favorites: filterFavoritesInitial, | ||||||
|  | @ -205,15 +209,17 @@ export const MovieList: FC<Props> = ({ | ||||||
|                   defaultValue={sort} |                   defaultValue={sort} | ||||||
|                   callback={(value) => setSort(value as "title")} |                   callback={(value) => setSort(value as "title")} | ||||||
|                 /> |                 /> | ||||||
|  |                 {!overrideDisplayType && ( | ||||||
|                   <Button |                   <Button | ||||||
|                     theme="glass" |                     theme="glass" | ||||||
|                     size="icon" |                     size="icon" | ||||||
|                     onClick={() => |                     onClick={() => | ||||||
|                     setDisplay(display === "grid" ? "list" : "grid") |                       setDisplayType(displayType === "grid" ? "list" : "grid") | ||||||
|                     } |                     } | ||||||
|                   > |                   > | ||||||
|                     <FaList /> |                     <FaList /> | ||||||
|                   </Button> |                   </Button> | ||||||
|  |                 )} | ||||||
|               </div> |               </div> | ||||||
|             )} |             )} | ||||||
|           </div> |           </div> | ||||||
|  | @ -227,7 +233,7 @@ export const MovieList: FC<Props> = ({ | ||||||
|             ref={parent} |             ref={parent} | ||||||
|           > |           > | ||||||
|             {sortedMovies.map((movie) => |             {sortedMovies.map((movie) => | ||||||
|               display === "grid" ? ( |               displayType === "grid" ? ( | ||||||
|                 <MovieCard key={movie.id} layout="aurora" {...movie} /> |                 <MovieCard key={movie.id} layout="aurora" {...movie} /> | ||||||
|               ) : ( |               ) : ( | ||||||
|                 <MovieRow key={movie.id} movie={movie} compact /> |                 <MovieRow key={movie.id} movie={movie} compact /> | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| "use client"; | "use client"; | ||||||
| import Link from "next/link"; | import Link from "next/link"; | ||||||
| import { useState } from "react"; | 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 { Search } from "./components/Search"; | ||||||
| import { usePathname } from "next/navigation"; | import { usePathname } from "next/navigation"; | ||||||
| 
 | 
 | ||||||
|  | @ -17,7 +17,7 @@ const navigationItems = [ | ||||||
|   { |   { | ||||||
|     label: "Odkrywaj", |     label: "Odkrywaj", | ||||||
|     href: "/odkrywaj", |     href: "/odkrywaj", | ||||||
|     icon: HiViewGrid, |     icon: HiSparkles, | ||||||
|     emoji: "🎬", |     emoji: "🎬", | ||||||
|     color: "from-purple-500 to-pink-600", |     color: "from-purple-500 to-pink-600", | ||||||
|     description: "Znajdź nowe filmy", |     description: "Znajdź nowe filmy", | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue