From fd6932bf70ab34d7eaebdbf289efa7af57310258 Mon Sep 17 00:00:00 2001 From: Mark Sivan Tamakloe Date: Wed, 11 Jun 2025 10:24:24 -0700 Subject: [PATCH 1/6] hello --- src/App.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/App.css b/src/App.css index 0bf65669..db06e211 100644 --- a/src/App.css +++ b/src/App.css @@ -26,3 +26,5 @@ flex-direction: column; } } + +/* dfdf */ From 2e862488bfb1f6329f16fabfc9b88ad54c830bc4 Mon Sep 17 00:00:00 2001 From: Mark Sivan Tamakloe Date: Wed, 11 Jun 2025 10:34:49 -0700 Subject: [PATCH 2/6] added files --- .env | 1 + src/App.css | 111 ++++++++++++++++++++++++++++++++++++++-------- src/App.jsx | 55 ++++++++++++++++++++--- src/Modal.css | 23 ++++++++++ src/Modal.jsx | 18 ++++++++ src/MovieCard.css | 23 ++++++++++ src/MovieCard.jsx | 23 ++++++++++ src/MovieList.css | 10 +++++ src/MovieList.jsx | 31 +++++++++++++ src/Search.css | 34 ++++++++++++++ src/Search.jsx | 25 +++++++++++ src/Sort.css | 9 ++++ src/Sort.jsx | 37 ++++++++++++++++ src/index.css | 12 ++--- 14 files changed, 382 insertions(+), 30 deletions(-) create mode 100644 .env create mode 100644 src/Modal.css create mode 100644 src/Modal.jsx create mode 100644 src/MovieCard.css create mode 100644 src/MovieCard.jsx create mode 100644 src/MovieList.css create mode 100644 src/MovieList.jsx create mode 100644 src/Search.css create mode 100644 src/Search.jsx create mode 100644 src/Sort.css create mode 100644 src/Sort.jsx diff --git a/.env b/.env new file mode 100644 index 00000000..207eb8af --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_KEY=531687c8e8da8cb3eb85ddce1090cec8 diff --git a/src/App.css b/src/App.css index db06e211..431cb227 100644 --- a/src/App.css +++ b/src/App.css @@ -1,30 +1,105 @@ .App { text-align: center; -} + min-width: 700px; -.App-header { + + } + + + .App-header { background-color: #282c34; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-evenly; - color: white; padding: 20px; -} + padding-bottom: 50px; + + + border: green 3px solid; + } -@media (max-width: 600px) { + + @media (max-width: 600px) { .movie-card { width: 100%; } - .search-bar { - flex-direction: column; - gap: 10px; - } - .search-bar form { - flex-direction: column; - } -} + } + /* .search-bar { + display: inline; + width: 30%; + + + + + } */ + + + + + /* .search-bar form { + flex-direction: column; + } */ + + + /* input{ + width: 70%; + + + + + } */ + + + button{ + width: 30%; + } + + + .movie-container{ + display: flex; + flex-wrap: wrap; + justify-content: space-around; + padding: 10px; + border: 3px solid blue; + height: 100%; + } + + + .App-footer{ + justify-content: center; + display: flex; + border: 3px solid red; + height: 100%; + width: 100%; + + + + + } + + + .title{ + color: white + + + } + + + .controls-row{ + display: flex; + justify-content:space-between; + } + + + .search-section{ + margin-right: 80px; + } + + + .load-more button{ + width: 15%; + height: 7vh; + border-radius: 10px; + background-color: green; + -/* dfdf */ + } diff --git a/src/App.jsx b/src/App.jsx index dfa91584..57cb3f9e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,53 @@ -import { useState } from 'react' import './App.css' +import Search from './Search.jsx' +import Sort from './Sort.jsx' +import MovieList from './MovieList.jsx' -const App = () => { - return ( -
- -
- ) + +function App() { + return ( +
+ + +
+

🎥 Flixster

+ + +
+ + +
+ + +
+ + +
+
+ +
+
+ + + +
+ + +
+ + + + +
+
+

Footer

+
+
+ + +
+ ) } + export default App diff --git a/src/Modal.css b/src/Modal.css new file mode 100644 index 00000000..32d0c1f8 --- /dev/null +++ b/src/Modal.css @@ -0,0 +1,23 @@ +.modal-overlay { + display: none; + position: fixed; + z-index: 998; /* Increased z-index to be just below the form modal */ + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.9); /* Darker background for better contrast */ + overflow: hidden; /* Prevent scrolling of background content */ + } + .modal-content { + background-color:rgb(212, 190, 190); + margin: 5% auto; + padding: 2px; + border: 4px solid black; + width: 70%; + height: 80%; + border-radius: 50px; + padding-right: 50px; + box-shadow: 10px 10px 12px rgba(0,0,0,1.0); + position: relative; /* Ensure proper stacking context */ + } diff --git a/src/Modal.jsx b/src/Modal.jsx new file mode 100644 index 00000000..5961a3f1 --- /dev/null +++ b/src/Modal.jsx @@ -0,0 +1,18 @@ +import './Modal.css' + + +function Modal() { + return ( +
+ + +
+ ) + + + + +} + + +export default Modal; diff --git a/src/MovieCard.css b/src/MovieCard.css new file mode 100644 index 00000000..d9b7b90d --- /dev/null +++ b/src/MovieCard.css @@ -0,0 +1,23 @@ +.movie-card{ + display: block; + + + border: 2px solid black; + height: 400px; + width: 280px; + text-align: center; + border-radius: 20px; + } + + + .movie-card img{ + width: 100%; + height: 70%; + border-top-right-radius: 20px; + border-top-left-radius: 20px; + } + + + .movie-card p{ + font-size: 10px; + } diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx new file mode 100644 index 00000000..cb5e22a3 --- /dev/null +++ b/src/MovieCard.jsx @@ -0,0 +1,23 @@ +import './MovieCard.css' + + + + +function MovieCard(props){ + const posterUrl = `https://image.tmdb.org/t/p/w500${props.movie.poster_path}`; + + + return ( +
+ {props.movie.title}/ +

{props.movie.title}

+

⭐ {props.movie.vote_average}

+

{props.movie.release_date.substring(0,4)}

+
+ ); +} + + + + +export default MovieCard; diff --git a/src/MovieList.css b/src/MovieList.css new file mode 100644 index 00000000..248766de --- /dev/null +++ b/src/MovieList.css @@ -0,0 +1,10 @@ +.movie-list{ + display: flex; + border: 2px solid red; + height: 100%; + width: 100%; + flex-wrap: wrap; + gap: 60px; + padding: 40px; + justify-content: space-evenly; + } diff --git a/src/MovieList.jsx b/src/MovieList.jsx new file mode 100644 index 00000000..e18d2741 --- /dev/null +++ b/src/MovieList.jsx @@ -0,0 +1,31 @@ +import './MovieList.css' +import data from './data/data.js' +import MovieCard from './MovieCard.jsx' + + + + +function MovieList(){ + + + const movies = data.results; + return ( +
+ {movies.map((movie) =>( + + )) + + + } + + +
+ ); +} + + + + +export default MovieList; + + diff --git a/src/Search.css b/src/Search.css new file mode 100644 index 00000000..6cdf0c1f --- /dev/null +++ b/src/Search.css @@ -0,0 +1,34 @@ +.search-section{ + width: 40%; + display: flex; + + + + + } + + + + + input{ + width: 20vw; + height: 100%; + margin-left: 40px; + } + + + .search-button{ + width: 15vw; + height: 100%; + margin-left: 1vw; + margin-right: 1vw; + height : 130%; + } + + + .clear-button{ + width: 15vw; + height: 130% + } + + diff --git a/src/Search.jsx b/src/Search.jsx new file mode 100644 index 00000000..5aa7aeb1 --- /dev/null +++ b/src/Search.jsx @@ -0,0 +1,25 @@ +import './Search.css' +function Search() { + return ( +
+
+ +
+ + + + + + + + +
+ ) + + + + +} + + +export default Search; diff --git a/src/Sort.css b/src/Sort.css new file mode 100644 index 00000000..9c4d81ee --- /dev/null +++ b/src/Sort.css @@ -0,0 +1,9 @@ +label{ + color: aliceblue; + } + + + select{ + margin-left: 10px; + margin-right: 120px; + } diff --git a/src/Sort.jsx b/src/Sort.jsx new file mode 100644 index 00000000..1a3e81f0 --- /dev/null +++ b/src/Sort.jsx @@ -0,0 +1,37 @@ +import './Sort.css' +function Sort() { + return ( +
+
+
+ + + + + + +
+
+ + + + +
+ ) + + + + +} + + +export default Sort; diff --git a/src/index.css b/src/index.css index e1faed1a..5daf111f 100644 --- a/src/index.css +++ b/src/index.css @@ -2,18 +2,20 @@ body { margin: 0; font-family: Arial, sans-serif; background-color: #f4f4f4; -} + } -button { + + button { background-color: #282c34; color: white; cursor: pointer; font-size: 16px; font-weight: bold; transition: background-color 0.3s ease; -} + } + -button:hover { + button:hover { background-color: #777; color: white; -} + } From cd608fe7e823e844537f8129edc3ba1f6ad8cc6b Mon Sep 17 00:00:00 2001 From: Mark Sivan Tamakloe Date: Thu, 12 Jun 2025 10:12:28 -0700 Subject: [PATCH 3/6] included useState --- src/App.jsx | 10 +++++++++- src/MovieCard.jsx | 3 ++- src/MovieList.jsx | 26 ++++++++++++++++++++++---- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 57cb3f9e..d52c590b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,9 +2,16 @@ import './App.css' import Search from './Search.jsx' import Sort from './Sort.jsx' import MovieList from './MovieList.jsx' +import {useState} from 'react' function App() { + const [page, setPage] = useState(1) + + function increasePageNumber() { + setPage(page + 1) + } + return (
@@ -27,7 +34,8 @@ function App() {
- + +

Page {page}

diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index cb5e22a3..8f2ea270 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,4 +1,5 @@ import './MovieCard.css' +import Modal from './Modal.jsx' @@ -11,7 +12,7 @@ function MovieCard(props){
{props.movie.title}/

{props.movie.title}

-

⭐ {props.movie.vote_average}

+

⭐{props.movie.vote_average}

{props.movie.release_date.substring(0,4)}

); diff --git a/src/MovieList.jsx b/src/MovieList.jsx index e18d2741..c2061a2d 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -1,14 +1,34 @@ import './MovieList.css' import data from './data/data.js' import MovieCard from './MovieCard.jsx' +import {useState, useEffect} from 'react' -function MovieList(){ +function MovieList({pages = 1, searchTerm = ''}){ + // state to stores the movies fetched from the API + const [movies, setMovies] = useState([]) + + // state to track if data is currently being loaded + const [isLoading, setIsLoading] = useState(true) + + // state to store any error that might occur during the API fetch + const [error, setError] = useState(null) + + useEffect(() => { + const fetchMovies = async () => { + try { + const apiKey = import.meta.env.VITE_API_KEY; + const apiURL = searchTerm + ? `https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&query=${encodeURIComponent(searchTerm)}&page=${page}` + : `https://api.themoviedb.org/3/movie/popular?api_key=${apiKey}&language=en-US&page=${page}`; + + const response = await fetch(apiURL); + + if (!repose.ok) - const movies = data.results; return (
{movies.map((movie) =>( @@ -27,5 +47,3 @@ function MovieList(){ export default MovieList; - - From 87df980d3be8d740bf4f61a92f51c82314497320 Mon Sep 17 00:00:00 2001 From: Mark Sivan Tamakloe Date: Thu, 12 Jun 2025 14:20:51 -0700 Subject: [PATCH 4/6] added search feature --- package-lock.json | 17 +++++++++++ package.json | 1 + src/App.jsx | 18 +++++++----- src/MovieList.jsx | 31 ++++++++++++++++++-- src/Search.css | 50 ++++++++++++++++++++++++++------ src/Search.jsx | 72 ++++++++++++++++++++++++++++++++--------------- 6 files changed, 148 insertions(+), 41 deletions(-) diff --git a/package-lock.json b/package-lock.json index 92a683d2..7808a87d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "prettier": "^3.5.3", "vite": "^5.2.0" } }, @@ -3453,6 +3454,22 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", diff --git a/package.json b/package.json index eded5715..7d633af1 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "eslint-plugin-react": "^7.34.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "prettier": "^3.5.3", "vite": "^5.2.0" } } diff --git a/src/App.jsx b/src/App.jsx index d52c590b..3f754bf6 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,20 +8,25 @@ import {useState} from 'react' function App() { const [page, setPage] = useState(1) + const [searchTerm, setSearchTerm] = useState('') + function increasePageNumber() { setPage(page + 1) } - return ( -
- + function handleSearch(term) { + setPage(1) + setSearchTerm(term) + } + return ( +

🎥 Flixster

- +
@@ -31,7 +36,7 @@ function App() {
- +
@@ -57,5 +62,4 @@ function App() { ) } - -export default App +export default App; diff --git a/src/MovieList.jsx b/src/MovieList.jsx index c2061a2d..74ab1608 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -6,7 +6,7 @@ import {useState, useEffect} from 'react' -function MovieList({pages = 1, searchTerm = ''}){ +function MovieList({page = 1, searchTerm = ''}){ // state to stores the movies fetched from the API const [movies, setMovies] = useState([]) @@ -26,8 +26,33 @@ function MovieList({pages = 1, searchTerm = ''}){ const response = await fetch(apiURL); - if (!repose.ok) - + if (!response.ok){ + throw new Error('Failed to fetch movies'); + } + const data = await response.json(); + + if (page === 1){ + setMovies(data.results); + }else{ + setMovies(prevMovies => [...prevMovies, ...data.results]); + } + + setIsLoading(false); + } catch (err) { + setError(err.message); + setIsLoading(false); + } + }; + fetchMovies(); + }, [page, searchTerm]); // re-reun when page or searchTerm changes + + if (isLoading && page === 1){ + return
Loading...
; + } + + if (error){ + return
An error occurred: {error}
; + } return (
diff --git a/src/Search.css b/src/Search.css index 6cdf0c1f..40687958 100644 --- a/src/Search.css +++ b/src/Search.css @@ -9,14 +9,13 @@ - - input{ - width: 20vw; - height: 100%; - margin-left: 40px; + input { + width: 20vw; + height: 100%; + margin-left: 40px; + padding-right: 30px; } - .search-button{ width: 15vw; height: 100%; @@ -25,10 +24,43 @@ height : 130%; } + .search-button p{ + font-size: 100px; + } - .clear-button{ - width: 15vw; - height: 130% + + + .clear-search { + position: absolute; + right: 5px; + background: none; + border: none; + cursor: pointer; + height: 20px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + margin-left: 0; + } + + +.close{ + color: green; + font-size: 18px; +} + +.search-bar-container { + display: flex; + align-items: center; + width: 100%; } + .search-bar { + position: relative; + width: 100%; + display: flex; + align-items: center; + } diff --git a/src/Search.jsx b/src/Search.jsx index 5aa7aeb1..1e5bb0da 100644 --- a/src/Search.jsx +++ b/src/Search.jsx @@ -1,25 +1,53 @@ -import './Search.css' -function Search() { - return ( -
-
- -
- - - - - - - - -
- ) - - - - +import "./Search.css"; +import React, { useState } from "react"; + + + +function Search({ onSearch }) { + const [searchTerm, setSearchTerm] = useState(""); + + const handleSubmit = (event) => { + event.preventDefault(); + onSearch(searchTerm); + }; + + const handleClear = () => { + setSearchTerm(""); + onSearch(""); + }; + return ( +
+
+
+
+ setSearchTerm(e.target.value)} + /> + + {searchTerm && ( + + )} + + +
+ + +
+
+
+ ); } - export default Search; From 6b42567054941fdae3d084b15d571022ac7a4db6 Mon Sep 17 00:00:00 2001 From: Mark Sivan Tamakloe Date: Thu, 12 Jun 2025 14:32:55 -0700 Subject: [PATCH 5/6] added search feature --- src/Search.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Search.jsx b/src/Search.jsx index 1e5bb0da..62e9ffd6 100644 --- a/src/Search.jsx +++ b/src/Search.jsx @@ -27,7 +27,7 @@ function Search({ onSearch }) { onChange={(e) => setSearchTerm(e.target.value)} /> - {searchTerm && ( + {searchTerm && ( + +
+
+
+ +
+

{movie.title}

+ + {loading ? ( +

Loading details...

+ ) : ( + <> +
+

Release Date: {movie.release_date}

+ {movieDetails && ( +

Runtime: {formatRuntime(movieDetails.runtime)}

+ )} +

Rating: ⭐ {movie.vote_average.toFixed(1)}/10

+ {movieDetails && movieDetails.genres && ( +

Genres: {movieDetails.genres.map(g => g.name).join(', ')}

+ )} +
+ +
+

Overview

+

{movie.overview}

+
+ + {trailer && ( +
+

Trailer

+
+ +
+
+ )} + + )} +
+
+
+ ); +} export default Modal; diff --git a/src/MovieCard.css b/src/MovieCard.css index d9b7b90d..e1c9ddc2 100644 --- a/src/MovieCard.css +++ b/src/MovieCard.css @@ -1,4 +1,4 @@ -.movie-card{ +.card-content{ display: block; @@ -13,11 +13,16 @@ .movie-card img{ width: 100%; height: 70%; - border-top-right-radius: 20px; - border-top-left-radius: 20px; + border-top-right-radius: 18px; + border-top-left-radius: 18px; } .movie-card p{ font-size: 10px; + color: white; + } + + .movie-card{ + box-shadow: 10px black; } diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index 8f2ea270..97386b86 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -1,20 +1,41 @@ import './MovieCard.css' import Modal from './Modal.jsx' +import {useState} from 'react' function MovieCard(props){ + const [showModal, setShowModal] = useState(false); const posterUrl = `https://image.tmdb.org/t/p/w500${props.movie.poster_path}`; + const openModal = () => { + setShowModal(true); + }; + + const closeModal = () => { + setShowModal(false); + }; return (
- {props.movie.title}/ -

{props.movie.title}

-

⭐{props.movie.vote_average}

-

{props.movie.release_date.substring(0,4)}

+
+ {props.movie.title}/ +

{props.movie.title}

+

⭐{props.movie.vote_average}

+

{props.movie.release_date.substring(0,4)}

+
+ + {showModal &&( + + )}
+ + + ); } diff --git a/src/MovieList.css b/src/MovieList.css index 248766de..bcdb038f 100644 --- a/src/MovieList.css +++ b/src/MovieList.css @@ -1,6 +1,5 @@ .movie-list{ display: flex; - border: 2px solid red; height: 100%; width: 100%; flex-wrap: wrap; diff --git a/src/Search.css b/src/Search.css index 40687958..a22a8727 100644 --- a/src/Search.css +++ b/src/Search.css @@ -7,6 +7,11 @@ } + .search-bar-container { + display: flex; + gap: 2px; + } + input { @@ -17,16 +22,12 @@ } .search-button{ - width: 15vw; - height: 100%; + width: 55px; margin-left: 1vw; - margin-right: 1vw; - height : 130%; + border-radius: 50%; + height: 40px } - .search-button p{ - font-size: 100px; - } @@ -49,6 +50,7 @@ .close{ color: green; font-size: 18px; + margin-right: 60px; } .search-bar-container { @@ -59,8 +61,23 @@ .search-bar { position: relative; - width: 100%; display: flex; align-items: center; + height: 30px } + + .now-playing-button { + background-color: #0d253f; + + color: white; + border: none; + border-radius: 4px; + padding: 8px 12px; + margin-left: 10px; + cursor: pointer; + font-weight: 500; + transition: background-color 0.2s; + height: 40px; + white-space: nowrap; + } diff --git a/src/Search.jsx b/src/Search.jsx index 62e9ffd6..a464d700 100644 --- a/src/Search.jsx +++ b/src/Search.jsx @@ -15,6 +15,12 @@ function Search({ onSearch }) { setSearchTerm(""); onSearch(""); }; + + const handleNowPlaying = (event) => { + event.preventDefault(); + setSearchTerm(""); + onSearch(""); // Reset search results to default (popular movies) + }; return (
@@ -27,7 +33,7 @@ function Search({ onSearch }) { onChange={(e) => setSearchTerm(e.target.value)} /> - {searchTerm && ( + {searchTerm && ( )} - -
+
diff --git a/src/Sort.jsx b/src/Sort.jsx index 1a3e81f0..2ce1966f 100644 --- a/src/Sort.jsx +++ b/src/Sort.jsx @@ -9,16 +9,8 @@ function Sort() { - - - - - - - -
diff --git a/src/index.css b/src/index.css index 5daf111f..3ac430cc 100644 --- a/src/index.css +++ b/src/index.css @@ -2,9 +2,12 @@ body { margin: 0; font-family: Arial, sans-serif; background-color: #f4f4f4; + + } + button { background-color: #282c34; color: white;