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) => (
+
+ )}
+
+ );
+};