Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TMDB assignment is completed #8

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions app/components/CastSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"use client";

import React, { useState, useEffect, useRef } from "react";

// Define the Cast type
interface Cast {
id: number;
name: string;
profile_path: string;
}

const CastSlider: React.FC = () => {
const [scrollPosition, setScrollPosition] = useState<number>(0);
const carouselRef = useRef<HTMLDivElement | null>(null);
const [castsData, setCastsData] = useState<Cast[]>([]);
const itemWidth = 240;

// Fetch Cast Data from TMDB API
useEffect(() => {
const getFeaturedCastsData = async () => {
try {
const options = {
method: "GET",
headers: {
accept: "application/json",
Authorization:
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0MGVmNmVkZWU1OGE4ZDQ1ZjcyOTI2MmI4NjJkMDgyNiIsIm5iZiI6MTcyNzE4MjUxMC4xNTg4NDIsInN1YiI6IjY2ZjJiNGIxYzIzNzI1OGU0YzI2ZjE3MiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.tAdwtXU_CQNI-Kp3o6wdgJasuxyYO3jVWNKiVu9fApE",
},
};

const response = await fetch(
"https://api.themoviedb.org/3/person/popular?language=en-US&page=1",
options
);
const data = await response.json();
setCastsData(data.results || []);
// console.log(data.results)
} catch (error) {
console.error("Failed to fetch cast data:", error);
}
};

getFeaturedCastsData();
}, []);

// Handle Carousel Scroll
useEffect(() => {
if (carouselRef.current) {
carouselRef.current.scrollTo({
left: scrollPosition,
behavior: "smooth",
});
}
}, [scrollPosition]);

const handlePrevClick = (): void => {
setScrollPosition((prev) => Math.max(prev - itemWidth, 0));
};

const handleNextClick = (): void => {
if (carouselRef.current) {
const maxScroll =
carouselRef.current.scrollWidth - carouselRef.current.clientWidth;
setScrollPosition((prev) => Math.min(prev + itemWidth, maxScroll));
}
};

if (castsData.length == 0) {
return <h1>Data fetching...</h1>;
}

return (
<div className="container p-10 overflow-hidden relative">
<div className="flex justify-between mb-4">
<p className="text-2xl pl-10 font-bold">Featured Casts</p>
<div className="flex items-center gap-1 text-red-500 text-sm font-bold pr-10 hover:cursor-pointer">
<p>See more</p>
<i className="fa-solid fa-chevron-right"></i>
</div>
</div>

<div className="flex items-center justify-between">
{/* Left arrow button */}
<button onClick={handlePrevClick} className="text-3xl mr-5">
<i className="fa-solid fa-chevron-left"></i>
</button>

{/* Carousel */}
<div
ref={carouselRef}
className="flex items-center gap-8 overflow-x-hidden p-4 relative flex-grow"
style={{ scrollSnapType: "x mandatory" }}
>
{castsData.map((item) => (
<div key={item.id} className="flex-shrink-0 w-[240px]">
<img
src={`https://image.tmdb.org/t/p/w500/${item.profile_path}`}
alt={item.name}
className="object-cover w-full h-80 "
/>
<p className="text-sm font-bold mt-1">{item.name}</p>
</div>
))}
</div>

{/* Right arrow button */}
<button onClick={handleNextClick} className="text-3xl ml-5">
<i className="fa-solid fa-chevron-right"></i>
</button>
</div>
</div>
);
};

export default CastSlider;
27 changes: 27 additions & 0 deletions app/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

export default function Footer() {
return (
<div className="flex justify-center items-center flex-col my-10 gap-7">
{/* social media icons */}
<div className="flex gap-7">
<i className="fa-brands fa-square-facebook text-2xl cursor-pointer"></i>
<i className="fa-brands fa-instagram text-2xl cursor-pointer"></i>
<i className="fa-brands fa-twitter text-2xl cursor-pointer"></i>
<i className="fa-brands fa-youtube text-2xl cursor-pointer"></i>
</div>

{/* seo pages */}
<div className="flex gap-7">
<p className="text-sm font-bold cursor-pointer">Conditions of Use</p>
<p className="text-sm font-bold cursor-pointer">Privacy & Policy</p>
<p className="text-sm font-bold cursor-pointer">Press Room</p>
</div>

{/* copyright */}
<p className="text-gray-500 text-sm font-bold">
© 2021 MovieBox by Adriana Eka Prayudha
</p>
</div>
);
}
44 changes: 44 additions & 0 deletions app/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import Image from "next/image";

export default function Header() {
return (
<header className="absolute top-0 left-0 w-full z-10">
<nav className="bg-transparent flex items-center justify-around p-3">
{/* logo */}
<div className="flex items-center gap-5 font-bold text-2xl">
<div className="relative w-12 h-12">
<Image
src="/logo.svg"
alt="Next.js Logo"
fill
className="object-contain cursor-pointer"
/>
</div>

<p className="text-white cursor-pointer">MovieBox</p>
</div>

{/* Search box */}
<div className="border-gray-200 border hover:border-white hover:border-2 rounded-md px-2 py-1">
<input
type="text"
placeholder="What do you want to watch?"
className="bg-transparent p-1 text-sm w-96 focus:outline-none text-white placeholder:text-white"
/>
<i className="fa-solid fa-magnifying-glass text-white text-sm"></i>
</div>

{/* sign in and menu button*/}
<div className="flex gap-5 items-center">
<p className="text-white text-sm font-bold cursor-pointer">Sign in</p>

<div className="rounded-full bg-[#BF123C] w-10 h-10 flex justify-center items-center cursor-pointer">
<i className="fa-regular fa-equals text-white text-xl"></i>
</div>
</div>
</nav>
</header>
);
}
158 changes: 158 additions & 0 deletions app/components/MovieSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"use client";

import React, { useState, useEffect, useRef } from "react";

// Defining the Movie types
interface Movie {
id: number;
title: string;
backdrop_path: string;
release_date: string;
vote_average: number;
vote_count: number;
}

const MovieSlider: React.FC = () => {
const [scrollPosition, setScrollPosition] = useState<number>(0);
const [maxScroll, setMaxScroll] = useState<number>(0); //used to Track max scroll
const carouselRef = useRef<HTMLDivElement | null>(null);
const [movieData, setMovieData] = useState<Movie[]>([]);
const itemWidth = 240;

// Fetch movie data from TMDB API
useEffect(() => {
const getFeaturedMovieData = async () => {
try {
const options = {
method: "GET",
headers: {
accept: "application/json",
Authorization:
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI0MGVmNmVkZWU1OGE4ZDQ1ZjcyOTI2MmI4NjJkMDgyNiIsIm5iZiI6MTcyNzE4MjUxMC4xNTg4NDIsInN1YiI6IjY2ZjJiNGIxYzIzNzI1OGU0YzI2ZjE3MiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.tAdwtXU_CQNI-Kp3o6wdgJasuxyYO3jVWNKiVu9fApE",
},
};

const response = await fetch(
"https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=1",
options
);
const data = await response.json();
setMovieData(data.results || []);
// console.log(data.results);
} catch (error) {
console.error("Error fetching movie data:", error);
}
};

getFeaturedMovieData();
}, []);

// Update maxScroll whenever the movieData or carouselRef changes
useEffect(() => {
if (carouselRef.current) {
const updateMaxScroll = () => {
const max =
carouselRef.current!.scrollWidth - carouselRef.current!.clientWidth;
setMaxScroll(max);
};
updateMaxScroll();
window.addEventListener("resize", updateMaxScroll); // Handle window resize
return () => window.removeEventListener("resize", updateMaxScroll);
}
}, [movieData]);

// Handle Carousel Scroll
useEffect(() => {
if (carouselRef.current) {
carouselRef.current.scrollTo({
left: scrollPosition,
behavior: "smooth",
});
}
}, [scrollPosition]);

const handlePrevClick = (): void => {
setScrollPosition((prev) => Math.max(prev - itemWidth, 0));
};

const handleNextClick = (): void => {
setScrollPosition((prev) => Math.min(prev + itemWidth, maxScroll));
};

if (movieData.length == 0) {
return <h1>Data fetching...</h1>;
}

return (
<div className="container p-10 overflow-hidden relative">

<div className="flex justify-between mb-4">
<p className="text-2xl pl-14 font-bold">Featured Movies</p>
<div className="flex items-center gap-1 text-red-500 text-sm font-bold pr-10 hover:cursor-pointer">
<p>See more</p>
<i className="fa-solid fa-chevron-right"></i>
</div>
</div>

<div className="flex items-center justify-between">
{/* Left arrow button */}
<button onClick={handlePrevClick} className="text-3xl mr-5">
<i className="fa-solid fa-chevron-left"></i>
</button>

{/* Carousel */}
<div
ref={carouselRef}
className="flex items-center gap-8 overflow-x-hidden p-4 relative flex-grow"
style={{ scrollSnapType: "x mandatory" }}
>
{movieData.map((item) => (
<div key={item.id} className="flex-shrink-0 w-[240px]">
<div className="relative">
<img
src={`https://image.tmdb.org/t/p/w500/${item.backdrop_path}`}
alt={item.title}
className="h-80 w-60 object-cover"
/>
<div className="absolute top-2 right-2 p-1 px-2 bg-gray-300 rounded-full">
<i
className="fa-solid fa-heart"
style={{ color: "white" }}
></i>
</div>
</div>

<div className="p-2 flex flex-col gap-2">
<p className="text-xs font-bold text-gray-500">
{item.release_date} - Current
</p>
<p className="text-lg font-bold">{item.title}</p>

<div className="flex items-center text-xs font-semibold justify-between">
<div className="flex gap-1">
<img src={"/imdb.jpeg"} alt="imdb logo" className="h-4" />
<p>{item.vote_average}/100</p>
</div>
<div>
<p>🍎 {item.vote_count}</p>
</div>
</div>

<p className="text-xs font-bold text-gray-500">
Action, Adventure, and Horror
</p>
</div>
</div>
))}
</div>

{/* Right arrow button */}
<button onClick={handleNextClick} className="text-3xl ml-5">
<i className="fa-solid fa-chevron-right"></i>
</button>
</div>
</div>
);
};

export default MovieSlider;
Loading