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