From 03b00ad399c5ff47759cb361cd1930bcccf062d2 Mon Sep 17 00:00:00 2001 From: Norbert Maciaszek Date: Fri, 15 Aug 2025 17:14:16 +0200 Subject: [PATCH] Refactor homepage to include multiple movie sections: integrate new Carousel component for displaying now playing, upcoming, popular, and trending movies. Update data fetching to utilize multiple TMDB endpoints for enhanced content variety and user experience. --- src/app/(withGlobalData)/odkrywaj/page.tsx | 84 +++++++-- src/components/molecules/Carousel/index.tsx | 179 ++++++++++++++++++++ 2 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 src/components/molecules/Carousel/index.tsx diff --git a/src/app/(withGlobalData)/odkrywaj/page.tsx b/src/app/(withGlobalData)/odkrywaj/page.tsx index d76067e..9494601 100644 --- a/src/app/(withGlobalData)/odkrywaj/page.tsx +++ b/src/app/(withGlobalData)/odkrywaj/page.tsx @@ -1,30 +1,84 @@ -import { getNowPlayingMovies } from "@/lib/tmdb/server"; +import { + getNowPlayingMovies, + getPopularMovies, + getTrendingMovies, + getUpcomingMovies, +} from "@/lib/tmdb/server"; import { Hero } from "@/components/organisms/Hero"; +import { Carousel } from "@/components/molecules/Carousel"; +import { MovieCard } from "@/components/atoms/MovieCard"; // 12 hours export const revalidate = 43200; export default async function Home() { - // Get trending movies and popular movies for hero carousel. - const data = await getNowPlayingMovies(); - - // Combine first 3 trending and first 2 popular movies for variety. - const dataRaw = data.results.slice(0, 5); + const [nowPlayingData, popularData, trendingData, upcomingData] = + await Promise.all([ + getNowPlayingMovies(), + getPopularMovies(), + getTrendingMovies(), + getUpcomingMovies(), + ]); // Convert TMDB movie format to our Movie type. - const heroMovies = dataRaw.map((movie) => ({ - ...movie, - genre_ids: JSON.stringify(movie.genre_ids), - seen: false, - favorite: false, - })); + const convertMovies = (movies: any[]) => + movies.map((movie) => ({ + ...movie, + genre_ids: JSON.stringify(movie.genre_ids), + seen: false, + favorite: false, + })); + + const heroMovies = convertMovies(trendingData.results.slice(0, 5)); + const popularMovies = convertMovies(popularData.results); + const trendingMovies = convertMovies(trendingData.results).slice(5); + const nowPlayingMovies = convertMovies(nowPlayingData.results); + const upcomingMovies = convertMovies(upcomingData.results); return ( <> -
-

Popularne filmy

- {/* Add other homepage content here */} + +
+
+

Teraz w kinach

+ + {nowPlayingMovies.map((movie) => ( + + ))} + +
+ +
+

+ Nadchodzące filmy +

+ + {upcomingMovies.map((movie) => ( + + ))} + +
+ +
+

+ Popularne filmy +

+ + {popularMovies.map((movie) => ( + + ))} + +
+ +
+

Trendy dnia

+ + {trendingMovies.map((movie) => ( + + ))} + +
); diff --git a/src/components/molecules/Carousel/index.tsx b/src/components/molecules/Carousel/index.tsx new file mode 100644 index 0000000..26aaa34 --- /dev/null +++ b/src/components/molecules/Carousel/index.tsx @@ -0,0 +1,179 @@ +"use client"; +import { FC, ReactNode, useRef, useState, useCallback, useEffect } from "react"; +import { FaChevronLeft, FaChevronRight } from "react-icons/fa"; + +type CarouselOptions = { + itemsPerView?: number; + itemsPerViewMobile?: number; + itemsPerViewTablet?: number; + gap?: number; + showArrows?: boolean; + showDots?: boolean; + autoScroll?: boolean; + autoScrollInterval?: number; +}; + +type Props = { + children: ReactNode[]; + options?: CarouselOptions; + className?: string; +}; + +export const Carousel: FC = ({ + children, + options = {}, + className = "", +}) => { + const { + itemsPerView = 4, + itemsPerViewMobile = 2, + itemsPerViewTablet = 3, + gap = 20, + showArrows = true, + showDots = true, + autoScroll = false, + autoScrollInterval = 5000, + } = options; + + const [currentIndex, setCurrentIndex] = useState(0); + const [isTransitioning, setIsTransitioning] = useState(false); + const [itemsVisible, setItemsVisible] = useState(itemsPerView); + const carouselRef = useRef(null); + + const totalItems = children.length; + const maxIndex = Math.max(0, totalItems - itemsVisible); + + // Responsive items per view. + useEffect(() => { + const updateItemsPerView = () => { + if (window.innerWidth < 640) { + setItemsVisible(itemsPerViewMobile); + } else if (window.innerWidth < 1024) { + setItemsVisible(itemsPerViewTablet); + } else { + setItemsVisible(itemsPerView); + } + }; + + updateItemsPerView(); + window.addEventListener("resize", updateItemsPerView); + return () => window.removeEventListener("resize", updateItemsPerView); + }, [itemsPerView, itemsPerViewMobile, itemsPerViewTablet]); + + const nextSlide = useCallback(() => { + if (isTransitioning) return; + + setIsTransitioning(true); + setCurrentIndex((prev) => { + return Math.min(prev + itemsVisible, maxIndex); + }); + + setTimeout(() => setIsTransitioning(false), 300); + }, [isTransitioning, totalItems, maxIndex]); + + const prevSlide = useCallback(() => { + if (isTransitioning) return; + + setIsTransitioning(true); + setCurrentIndex((prev) => { + return Math.max(prev - itemsVisible, 0); + }); + + setTimeout(() => setIsTransitioning(false), 300); + }, [isTransitioning, totalItems]); + + const goToSlide = useCallback( + (index: number) => { + if (isTransitioning || index === currentIndex) return; + + setIsTransitioning(true); + setCurrentIndex(index); + setTimeout(() => setIsTransitioning(false), 300); + }, + [currentIndex, isTransitioning] + ); + + // Auto-scroll functionality. + useEffect(() => { + if (!autoScroll || totalItems <= itemsVisible) return; + + const interval = setInterval(nextSlide, autoScrollInterval); + return () => clearInterval(interval); + }, [autoScroll, autoScrollInterval, nextSlide, totalItems, itemsVisible]); + + // Calculate transform for slide positioning. + const getTransform = () => { + return `translateX(-${currentIndex * (100 / itemsVisible)}%)`; + }; + + const canGoPrev = currentIndex > 0; + const canGoNext = currentIndex < maxIndex; + + return ( +
+ {/* Carousel container */} +
+
+ {children.map((child, index) => ( +
+ {child} +
+ ))} +
+
+ + {/* Navigation arrows */} + {showArrows && totalItems > itemsVisible && ( + <> + + + + )} + + {/* Dot indicators */} + {showDots && totalItems > itemsVisible && ( +
+ {Array.from({ length: Math.ceil(totalItems / itemsVisible) }).map( + (_, index) => ( +
+ )} +
+ ); +};