From ffd3ca466aa650e58ef50f2d1cd9f7bfa545376d Mon Sep 17 00:00:00 2001 From: Gabriella Gabriel Toby Date: Wed, 12 Jun 2024 11:06:16 -0700 Subject: [PATCH 1/6] feat: sorting sorts the current stored movies --- .env | 1 + src/App.css | 1 + src/App.jsx | 26 +++++++++-- src/components/Header.css | 44 +++++++++++++++++++ src/components/Header.jsx | 24 +++++++++++ src/components/ModalContainer.jsx | 0 src/components/MovieCards.jsx | 13 ++++++ src/components/MovieList.jsx | 26 +++++++++++ src/components/NowPlayingScreen.css | 0 src/components/NowPlayingScreen.jsx | 67 +++++++++++++++++++++++++++++ src/components/SearchScreen.jsx | 46 ++++++++++++++++++++ src/components/movieCards.css | 36 ++++++++++++++++ 12 files changed, 280 insertions(+), 4 deletions(-) create mode 100644 .env create mode 100644 src/components/Header.css create mode 100644 src/components/Header.jsx create mode 100644 src/components/ModalContainer.jsx create mode 100644 src/components/MovieCards.jsx create mode 100644 src/components/MovieList.jsx create mode 100644 src/components/NowPlayingScreen.css create mode 100644 src/components/NowPlayingScreen.jsx create mode 100644 src/components/SearchScreen.jsx create mode 100644 src/components/movieCards.css diff --git a/.env b/.env new file mode 100644 index 00000000..bc925fa4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_KEY= dae9a6b98e10cd92e717f89b3d61b7c5 \ No newline at end of file diff --git a/src/App.css b/src/App.css index 0bf65669..02f9967a 100644 --- a/src/App.css +++ b/src/App.css @@ -26,3 +26,4 @@ flex-direction: column; } } + diff --git a/src/App.jsx b/src/App.jsx index 48215b3f..17a65816 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,28 @@ import { useState } from 'react' import './App.css' +import Header from "./components/Header"; +import.meta.env.VITE_API_KEY; +import NowPlayingScreen from './components/NowPlayingScreen'; +import SearchScreen from './components/SearchScreen.jsx'; const App = () => { -
- -
+ const [isSearching, setSearching] = useState(false); + const [criteria, setCriteria] = useState("") + + const handleNewCriteria = (criteriaFromHeader) => { + setCriteria(criteriaFromHeader) + } + + return ( +
+
+ {!isSearching ? + + : + + } +
+ ); } -export default App +export default App; diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 00000000..0ee2c1bf --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,44 @@ +.header { + background-color: #ff0000; + padding: 10px 20px; + display: flex; + justify-content: space-between; + align-items: center; + color: white; +} + +.header h1 { + margin: 0; +} + +.toggle-button { + display: flex; + align-items: center; +} + +.header input { + padding: 5px; + font-size: 16px; + border: none; + border-radius: 5px; + margin-right: 10px; +} + +.header button { + background-color: white; + color: #ff0000; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; + margin-right: 10px; +} + +.header .sort-by { + background-color: #ff0000; + color: white; + border: none; + padding: 5px 10px; + border-radius: 5px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..7cb696b2 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,24 @@ + +//import './Header.css'; // Import the CSS file for the header + +import React from 'react'; +import './Header.css'; + +function Header({ setSearching, sortMovies }) { + return ( +
+

Flixster

+
+ + + +
+
+ ); +} + +export default Header; \ No newline at end of file diff --git a/src/components/ModalContainer.jsx b/src/components/ModalContainer.jsx new file mode 100644 index 00000000..e69de29b diff --git a/src/components/MovieCards.jsx b/src/components/MovieCards.jsx new file mode 100644 index 00000000..5e8b3468 --- /dev/null +++ b/src/components/MovieCards.jsx @@ -0,0 +1,13 @@ +import "./movieCards.css" + +function MovieCard (props) { + return( +
+ +

{props.title}

+

Rating: {props.rating}

+
+ ); +} + +export default MovieCard; \ No newline at end of file diff --git a/src/components/MovieList.jsx b/src/components/MovieList.jsx new file mode 100644 index 00000000..2b1d8e2c --- /dev/null +++ b/src/components/MovieList.jsx @@ -0,0 +1,26 @@ +//import {useEffect, useState} from "react"; +import MovieCards from "./MovieCards" +import "./movieCards.css" + +function MovieList(props){ + + + function createCards(card, index){ + return( + + ) + } + + return( +
+ {props.data.map(createCards)} +
+ ) +} + +export default MovieList; diff --git a/src/components/NowPlayingScreen.css b/src/components/NowPlayingScreen.css new file mode 100644 index 00000000..e69de29b diff --git a/src/components/NowPlayingScreen.jsx b/src/components/NowPlayingScreen.jsx new file mode 100644 index 00000000..065e95b4 --- /dev/null +++ b/src/components/NowPlayingScreen.jsx @@ -0,0 +1,67 @@ +import {useEffect, useState} from "react"; +import MovieList from "./MovieList"; +import Header from "./Header" + + +function NowPlayingScreen({ criteriaFromHeader }){ + const [isSearching, setSearching] = useState(false); + const [movies, setMovies]=useState([]); + const [pageNumber, setPageNumber]=useState(1); + const[url, setUrl]=useState(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`) + + + useEffect(()=>{ + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + + fetch(url, options) + .then(response => response.json()) + .then(response => { + handleSortMovies(criteriaFromHeader, movies.concat(response.results)) + // setMovies(prevMovies =>prevMovies) + }) + .catch(err => console.error(err)); + }, [url, pageNumber]); + + function loadMore(){ + setPageNumber(prevPageNumber => prevPageNumber+1) + setUrl(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber + 1}`); + //useEffect(); + } + + function handleSortMovies(criteria, parmaMovies = movies){ + + const sortedMovies = [...parmaMovies].sort((a, b) => { + + if (criteria === 'title') { + return a.title.localeCompare(b.title); + } else if (criteria === 'date') { + return new Date(a.release_date) - new Date(b.release_date); + } else if (criteria === 'rating') { + return b.vote_average - a.vote_average; + } + return 0; + }); + setMovies(sortedMovies); + } + + useEffect(function() { + handleSortMovies(criteriaFromHeader) + }, [criteriaFromHeader]) + + return( +
+ + +
+ + ) + +} + +export default NowPlayingScreen; \ No newline at end of file diff --git a/src/components/SearchScreen.jsx b/src/components/SearchScreen.jsx new file mode 100644 index 00000000..77e2525d --- /dev/null +++ b/src/components/SearchScreen.jsx @@ -0,0 +1,46 @@ +import {useState} from "react"; +import MovieList from "./MovieList"; + +function SearchScreen(){ + const[query, setSearchQuery]= useState('') + const[movies, setMovies]= useState([]); + const[pageNumber, setPageNumber]= useState(1) + + function handleSearch(searchQuery){ + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + + fetch(`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=${pageNumber}`, options) + .then(response => response.json()) + .then(response => setMovies(response.results)) + .catch(err => console.error(err)); + } + + function loadMore(){ + setPageNumber(prevPageNumber => prevPageNumber+1) + + } + + return( +
+ setSearchQuery(e.target.value.toLowerCase())} + /> + + + + + +
+ ) +} + +export default SearchScreen; \ No newline at end of file diff --git a/src/components/movieCards.css b/src/components/movieCards.css new file mode 100644 index 00000000..57ddccc7 --- /dev/null +++ b/src/components/movieCards.css @@ -0,0 +1,36 @@ + + +.movie-card { + background-color: #ff0000; + color: white; + width: 200px; + margin: 10px; + padding: 10px; + border-radius: 10px; + text-align: center; +} +.movie-card:hover { + transform: scale(1.0); + box-shadow: 0 7px 14px black; + +} +.movie-card img { + width: 100%; + border-radius: 10px; +} + +.movie-card h2 { + font-size: 18px; + margin: 10px 0 5px; +} + +.movie-card p { + margin: 5px 0; +} + +.movieList { + display: flex; + flex-wrap: wrap; + justify-content: center; + padding: 20px; +} \ No newline at end of file From 285ac3ab06ece51c8dde6a0478ccb7827617ebe8 Mon Sep 17 00:00:00 2001 From: Gabriella Gabriel Toby Date: Wed, 12 Jun 2024 11:17:08 -0700 Subject: [PATCH 2/6] "" --- .gitignore | 1 + src/components/NowPlayingScreen.jsx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index a547bf36..7ceb59f8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +.env diff --git a/src/components/NowPlayingScreen.jsx b/src/components/NowPlayingScreen.jsx index 065e95b4..e9952872 100644 --- a/src/components/NowPlayingScreen.jsx +++ b/src/components/NowPlayingScreen.jsx @@ -4,7 +4,7 @@ import Header from "./Header" function NowPlayingScreen({ criteriaFromHeader }){ - const [isSearching, setSearching] = useState(false); + const const [movies, setMovies]=useState([]); const [pageNumber, setPageNumber]=useState(1); const[url, setUrl]=useState(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`) @@ -23,7 +23,6 @@ function NowPlayingScreen({ criteriaFromHeader }){ .then(response => response.json()) .then(response => { handleSortMovies(criteriaFromHeader, movies.concat(response.results)) - // setMovies(prevMovies =>prevMovies) }) .catch(err => console.error(err)); }, [url, pageNumber]); From 373fc5ae3802d212d947ba126c607a61612c436f Mon Sep 17 00:00:00 2001 From: Gabriella Gabriel Toby Date: Wed, 12 Jun 2024 11:20:45 -0700 Subject: [PATCH 3/6] removed env file --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index bc925fa4..00000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -VITE_API_KEY= dae9a6b98e10cd92e717f89b3d61b7c5 \ No newline at end of file From 245af4f54ad4a1137d98b9a4d9bef8794178985b Mon Sep 17 00:00:00 2001 From: Gabriella Gabriel Toby Date: Wed, 12 Jun 2024 15:19:49 -0700 Subject: [PATCH 4/6] Modal overlay --- src/components/ModalContainer.css | 33 +++++++++++++++++++++++++++++ src/components/ModalContainer.jsx | 23 ++++++++++++++++++++ src/components/MovieCards.jsx | 26 ++++++++++++++++++----- src/components/NowPlayingScreen.jsx | 6 ++++-- src/components/SearchScreen.jsx | 7 +++--- 5 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 src/components/ModalContainer.css diff --git a/src/components/ModalContainer.css b/src/components/ModalContainer.css new file mode 100644 index 00000000..127cc648 --- /dev/null +++ b/src/components/ModalContainer.css @@ -0,0 +1,33 @@ +.modal-overlay { + position: absolute; + top: 25%; + left: 25%; + background: rgba(0, 0, 0, 0.7); + width: 50%; + + + justify-content: center; + align-items: center; + z-index: 1000; + } + + + .modal-content { + background: white; + padding: 20px; + border-radius: 5px; + position: relative; + } + + .close-button { + color:black; + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + } + + \ No newline at end of file diff --git a/src/components/ModalContainer.jsx b/src/components/ModalContainer.jsx index e69de29b..81a910a3 100644 --- a/src/components/ModalContainer.jsx +++ b/src/components/ModalContainer.jsx @@ -0,0 +1,23 @@ +import "./ModalContainer.css"; +import React from "react"; + +function ModalContainer(props){ + return( +
+
+ +
+
Title:{props.data.title}
+ +
Original Title:
+
Overview: {props.data.overview}
+
Release Date: {props.data.releaseDate}
+
+
+
+ ); +} + +export default ModalContainer; \ No newline at end of file diff --git a/src/components/MovieCards.jsx b/src/components/MovieCards.jsx index 5e8b3468..e921bfea 100644 --- a/src/components/MovieCards.jsx +++ b/src/components/MovieCards.jsx @@ -1,12 +1,28 @@ import "./movieCards.css" +import { useState } from "react"; +import ModalContainer from "./ModalContainer"; function MovieCard (props) { + const[isModalOpen, setModal]=useState(false) + + function openModal(){ + setModal(true) + } + function closeModal() + { + setModal(false) + } return( -
- -

{props.title}

-

Rating: {props.rating}

-
+ <> +
+ +

{props.title}

+

Rating: {props.rating}

+
+ {isModalOpen ? : null} + + + ); } diff --git a/src/components/NowPlayingScreen.jsx b/src/components/NowPlayingScreen.jsx index e9952872..c40b93af 100644 --- a/src/components/NowPlayingScreen.jsx +++ b/src/components/NowPlayingScreen.jsx @@ -4,18 +4,20 @@ import Header from "./Header" function NowPlayingScreen({ criteriaFromHeader }){ - const + const [movies, setMovies]=useState([]); const [pageNumber, setPageNumber]=useState(1); const[url, setUrl]=useState(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`) + const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN; + useEffect(()=>{ const options = { method: 'GET', headers: { accept: 'application/json', - Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + Authorization: `Bearer ${ACCESS_TOKEN}` } }; diff --git a/src/components/SearchScreen.jsx b/src/components/SearchScreen.jsx index 77e2525d..b83b44c0 100644 --- a/src/components/SearchScreen.jsx +++ b/src/components/SearchScreen.jsx @@ -5,13 +5,14 @@ function SearchScreen(){ const[query, setSearchQuery]= useState('') const[movies, setMovies]= useState([]); const[pageNumber, setPageNumber]= useState(1) + const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN; function handleSearch(searchQuery){ const options = { method: 'GET', headers: { accept: 'application/json', - Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + Authorization: `Bearer ${ACCESS_TOKEN}` } }; @@ -23,9 +24,9 @@ function SearchScreen(){ function loadMore(){ setPageNumber(prevPageNumber => prevPageNumber+1) - + } - + return(
Date: Fri, 14 Jun 2024 15:17:24 -0700 Subject: [PATCH 5/6] stretch features done --- index.html | 1 + src/App.css | 4 +- src/App.jsx | 46 ++++++++++- src/components/FavMovies.jsx | 10 +++ src/components/Header.css | 6 +- src/components/Header.jsx | 11 +-- src/components/ModalContainer.css | 119 ++++++++++++++++++++-------- src/components/ModalContainer.jsx | 56 ++++++++++--- src/components/MovieCards.jsx | 30 +++++-- src/components/MovieList.jsx | 9 ++- src/components/NowPlayingScreen.jsx | 32 ++++++-- src/components/SearchScreen.jsx | 55 +++++++++---- src/components/SideBar.jsx | 65 +++++++++++++++ src/components/movieCards.css | 31 ++++++-- src/components/sideBar.css | 15 ++++ 15 files changed, 388 insertions(+), 102 deletions(-) create mode 100644 src/components/FavMovies.jsx create mode 100644 src/components/SideBar.jsx create mode 100644 src/components/sideBar.css diff --git a/index.html b/index.html index 0abcfe5f..de73b4dd 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,7 @@ + Flixster diff --git a/src/App.css b/src/App.css index 02f9967a..376d2ad8 100644 --- a/src/App.css +++ b/src/App.css @@ -17,12 +17,12 @@ width: 100%; } - .search-bar { + .searchBar { flex-direction: column; gap: 10px; } - .search-bar form { + .searchBar form { flex-direction: column; } } diff --git a/src/App.jsx b/src/App.jsx index 17a65816..9c34dda0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import './App.css' import Header from "./components/Header"; import.meta.env.VITE_API_KEY; @@ -7,19 +7,57 @@ import SearchScreen from './components/SearchScreen.jsx'; const App = () => { const [isSearching, setSearching] = useState(false); - const [criteria, setCriteria] = useState("") + const [criteria, setCriteria] = useState(""); + + const [isFave, setFave] = useState([]); + const [isWatched, setWatched] = useState([]); + + function loadFromLocalStorage() { + let savedLists = localStorage.getItem("userLists"); + if (savedLists == undefined || savedLists == null ||savedLists == "") { + return; + } + let ListsObject = JSON.parse(savedLists); + setFave(ListsObject.favList); + setWatched(ListsObject.watchedList); + } + + /** + * If the watchlist are now empty, check for stored (usually after a page load) + * else: put the current state into localstorage + */ + useEffect(() => { + if (isWatched.length == 0 && isFave.length == 0) { + loadFromLocalStorage() + } else { + localStorage.setItem("userLists", JSON.stringify( + { + favList: isFave, + watchedList: isWatched + } + )) + } + }, [isFave, isWatched]) const handleNewCriteria = (criteriaFromHeader) => { setCriteria(criteriaFromHeader) } + const handleSetFav = (newVal) => { + setFave(newVal); + } + + const handleSetWatched = (newVal) => { + setWatched(newVal); + } + return (
{!isSearching ? - + : - + }
); diff --git a/src/components/FavMovies.jsx b/src/components/FavMovies.jsx new file mode 100644 index 00000000..2276bd91 --- /dev/null +++ b/src/components/FavMovies.jsx @@ -0,0 +1,10 @@ +function FavMovies(props){ + return( +
+ +

{props.title}

+
+ ) +} + +export default FavMovies \ No newline at end of file diff --git a/src/components/Header.css b/src/components/Header.css index 0ee2c1bf..f85f9aa9 100644 --- a/src/components/Header.css +++ b/src/components/Header.css @@ -1,5 +1,5 @@ .header { - background-color: #ff0000; + background-color:red; padding: 10px 20px; display: flex; justify-content: space-between; @@ -11,7 +11,7 @@ margin: 0; } -.toggle-button { +.toggleButton { display: flex; align-items: center; } @@ -34,7 +34,7 @@ margin-right: 10px; } -.header .sort-by { +.header .sortBy { background-color: #ff0000; color: white; border: none; diff --git a/src/components/Header.jsx b/src/components/Header.jsx index 7cb696b2..30dc11bb 100644 --- a/src/components/Header.jsx +++ b/src/components/Header.jsx @@ -1,17 +1,14 @@ - -//import './Header.css'; // Import the CSS file for the header - -import React from 'react'; import './Header.css'; -function Header({ setSearching, sortMovies }) { +function Header({setSearching,sortMovies }) { + return (

Flixster

-
+
- sortMovies(e.target.value)}> diff --git a/src/components/ModalContainer.css b/src/components/ModalContainer.css index 127cc648..d39e5820 100644 --- a/src/components/ModalContainer.css +++ b/src/components/ModalContainer.css @@ -1,33 +1,86 @@ -.modal-overlay { - position: absolute; - top: 25%; - left: 25%; - background: rgba(0, 0, 0, 0.7); - width: 50%; - - - justify-content: center; - align-items: center; - z-index: 1000; - } - - - .modal-content { - background: white; - padding: 20px; - border-radius: 5px; - position: relative; - } - - .close-button { - color:black; - position: absolute; - top: 10px; - right: 10px; - background: none; - border: none; - font-size: 24px; - cursor: pointer; - } - - \ No newline at end of file +.modalOverlay { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + backdrop-filter: blur(5px); + overflow: auto; + background: rgba(0, 0, 0, 0.75); +} + +.modalContent { + background: #141414; + margin: auto; + margin-top: 10%; + padding: 20px; + border-radius: 10px; + width: 50%; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + position: relative; + color: white; + overflow: auto; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.modalContent div { + margin-bottom: 10px; +} + +.closeButton { + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: white; +} + +.modalBody { + margin-top: 20px; +} + +.modalBody div { + margin-bottom: 10px; + font-size: 16px; +} + +.modalBody img { + border-radius: 5px; +} + +.modalBody div:first-child { + font-size: 24px; + font-weight: bold; + color: #e50914; +} + +.modalBody div:nth-child(3) { + font-size: 18px; + color: #e50914; +} + +.modalContent div:nth-child(4){ + color:white; +} + +.modalContent div:nth-child(5){ + color:white; +} + +button { + background-color: #e50914; + color: white; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s; + font-weight: bold; +} + +button:hover { + background-color: #ff1e1e; +} \ No newline at end of file diff --git a/src/components/ModalContainer.jsx b/src/components/ModalContainer.jsx index 81a910a3..35948180 100644 --- a/src/components/ModalContainer.jsx +++ b/src/components/ModalContainer.jsx @@ -1,20 +1,50 @@ import "./ModalContainer.css"; -import React from "react"; +import {useEffect, useState} from "react"; function ModalContainer(props){ + const[trailerUrl, setTrailerUrl]= useState("") + const apiKey = import.meta.env.VITE_API_KEY; + + useEffect(()=> { + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + + fetch(`https://api.themoviedb.org/3/movie/${props.data.id}/videos?api_key=${apiKey}`, options) + .then(response => response.json()) + .then(response => response.results.find( + (video) => video.site === "YouTube" && video.type === "Trailer" + )) + .then((trailer) => setTrailerUrl(`https://www.youtube.com/embed/${trailer.key}`)) + .catch(err => console.error(err)); + }, [props.data.id, apiKey]) + + return( -
-
- -
-
Title:{props.data.title}
- -
Original Title:
-
Overview: {props.data.overview}
-
Release Date: {props.data.releaseDate}
-
+
+
+ +
+
Title:{props.data.title}
+ +
Original Title: {props.data.title}
+
Overview: {props.data.overview}
+
Release Date: {props.data.releaseDate}
+
+ +
); diff --git a/src/components/MovieCards.jsx b/src/components/MovieCards.jsx index e921bfea..38f276de 100644 --- a/src/components/MovieCards.jsx +++ b/src/components/MovieCards.jsx @@ -1,10 +1,13 @@ import "./movieCards.css" import { useState } from "react"; import ModalContainer from "./ModalContainer"; + function MovieCard (props) { const[isModalOpen, setModal]=useState(false) - + const[isFav, setIsFave] = useState(false); + const[watched, setWatched]= useState(false) + function openModal(){ setModal(true) } @@ -12,17 +15,34 @@ function MovieCard (props) { { setModal(false) } + return( <> -
- +
+

{props.title}

Rating: {props.rating}

+
+
+ +
+
+ +
+
{isModalOpen ? : null} - - ); } diff --git a/src/components/MovieList.jsx b/src/components/MovieList.jsx index 2b1d8e2c..d3075bf9 100644 --- a/src/components/MovieList.jsx +++ b/src/components/MovieList.jsx @@ -1,10 +1,7 @@ -//import {useEffect, useState} from "react"; import MovieCards from "./MovieCards" import "./movieCards.css" function MovieList(props){ - - function createCards(card, index){ return( props.favMovies(card.id)} + watchedMovies={() =>props.watchedMovies(card.id)} /> ) } diff --git a/src/components/NowPlayingScreen.jsx b/src/components/NowPlayingScreen.jsx index c40b93af..b9495c0a 100644 --- a/src/components/NowPlayingScreen.jsx +++ b/src/components/NowPlayingScreen.jsx @@ -1,17 +1,33 @@ +/* eslint-disable react/prop-types */ import {useEffect, useState} from "react"; import MovieList from "./MovieList"; -import Header from "./Header" +import SideBar from "./SideBar"; +const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN; -function NowPlayingScreen({ criteriaFromHeader }){ - +function NowPlayingScreen({criteriaFromHeader, setFave, isFave, setWatched, isWatched}){ const [movies, setMovies]=useState([]); const [pageNumber, setPageNumber]=useState(1); const[url, setUrl]=useState(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`) + function addToFave(movieId){ + if(isFave.includes(movieId)){ + setFave(prevIds => prevIds.filter(prevId => prevId !== movieId)) + + } else{ + setFave(prevId => [...prevId, movieId]) + } + } - const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN; + function addToWatched(movieId){ + if(isWatched.includes(movieId)){ + setWatched(prevIds => prevIds.filter(prevId => prevId !== movieId)) + } else { + setWatched(prevId => [...prevId, movieId]) + } + } + useEffect(()=>{ const options = { method: 'GET', @@ -25,6 +41,7 @@ function NowPlayingScreen({ criteriaFromHeader }){ .then(response => response.json()) .then(response => { handleSortMovies(criteriaFromHeader, movies.concat(response.results)) + }) .catch(err => console.error(err)); }, [url, pageNumber]); @@ -32,7 +49,6 @@ function NowPlayingScreen({ criteriaFromHeader }){ function loadMore(){ setPageNumber(prevPageNumber => prevPageNumber+1) setUrl(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber + 1}`); - //useEffect(); } function handleSortMovies(criteria, parmaMovies = movies){ @@ -56,13 +72,13 @@ function NowPlayingScreen({ criteriaFromHeader }){ }, [criteriaFromHeader]) return( -
- +
+ +
) - } export default NowPlayingScreen; \ No newline at end of file diff --git a/src/components/SearchScreen.jsx b/src/components/SearchScreen.jsx index b83b44c0..8d016065 100644 --- a/src/components/SearchScreen.jsx +++ b/src/components/SearchScreen.jsx @@ -1,10 +1,34 @@ -import {useState} from "react"; +/* eslint-disable react/prop-types */ +import { useState} from "react"; import MovieList from "./MovieList"; +import SideBar from "./SideBar"; -function SearchScreen(){ +function SearchScreen({setFave, isFave, setWatched, isWatched}){ const[query, setSearchQuery]= useState('') const[movies, setMovies]= useState([]); const[pageNumber, setPageNumber]= useState(1) + + + function addToFave(movieId){ + if(isFave.includes(movieId)){ + setFave(prevIds => prevIds.filter(prevId => prevId !== movieId)) + + } else{ + setFave(prevId => [...prevId, movieId]) + } + } + + function addToWatched(movieId){ + if(isWatched.includes(movieId)){ + setWatched(prevIds => prevIds.filter(prevId => prevId !== movieId)) + + } else { + setWatched(prevId => [...prevId, movieId]) + } + } + + + const ACCESS_TOKEN = import.meta.env.VITE_ACCESS_TOKEN; function handleSearch(searchQuery){ @@ -23,23 +47,22 @@ function SearchScreen(){ } function loadMore(){ - setPageNumber(prevPageNumber => prevPageNumber+1) - + setPageNumber(prevPageNumber => prevPageNumber+1) } return( -
- setSearchQuery(e.target.value.toLowerCase())} - /> - - - - - +
+ + setSearchQuery(e.target.value.toLowerCase())} + /> + + + +
) } diff --git a/src/components/SideBar.jsx b/src/components/SideBar.jsx new file mode 100644 index 00000000..97037543 --- /dev/null +++ b/src/components/SideBar.jsx @@ -0,0 +1,65 @@ +/* eslint-disable react/prop-types */ +import "./sideBar.css"; +import FavMovies from "./FavMovies"; +import { useEffect, useState } from "react"; + +function SideBar(props) { + const [favMovieElements, setFavMovieElements] = useState([]); + const [watchedMovieElements, setWatchedMovieElements] = useState([]); + + async function getMovie(movieId) { + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + + let response = await fetch(`https://api.themoviedb.org/3/movie/${movieId}`, options) + response = response.json(); + return response; + } + + async function MovieCard(movieId) { + + + const movieIndex = props.movies.findIndex((item) => item.id === movieId) + let movie; + if (movieIndex == -1) { + movie = await getMovie(movieId); + } else { + movie = props.movies[movieIndex] + } + + return movie; + } + + + useEffect(() => { + Promise.all(props.fave.map((id) => (MovieCard(id)))).then((values) => { + setFavMovieElements(values) + }); + Promise.all(props.watched.map((id) => (MovieCard(id)))).then((values) => { + setWatchedMovieElements(values) + }); + + + }, [props.watched, props.fave]) + + return ( +
+

Favorites

+ {favMovieElements.map((movie, index) => { + return () + })} +

Watched

+ {watchedMovieElements.map((movie, index) => { + return () + })} + +
+ ); +}; + +export default SideBar; \ No newline at end of file diff --git a/src/components/movieCards.css b/src/components/movieCards.css index 57ddccc7..26cb8650 100644 --- a/src/components/movieCards.css +++ b/src/components/movieCards.css @@ -1,6 +1,4 @@ - - -.movie-card { +.movieCard { background-color: #ff0000; color: white; width: 200px; @@ -9,22 +7,32 @@ border-radius: 10px; text-align: center; } -.movie-card:hover { +.movieCard:hover { transform: scale(1.0); box-shadow: 0 7px 14px black; } -.movie-card img { +.movieCard img { width: 100%; border-radius: 10px; } -.movie-card h2 { +.favoriteWatchedIcon{ + display:flex; +} +.favoriteWatchedIcon button{ + background-color:red; + cursor: pointer; + font-size: 16px; + font-weight: bold; + transition: background-color 0.3s ease; +} +.movieCard h2 { font-size: 18px; margin: 10px 0 5px; } -.movie-card p { +.movieCard p { margin: 5px 0; } @@ -33,4 +41,11 @@ flex-wrap: wrap; justify-content: center; padding: 20px; -} \ No newline at end of file + margin-left: 13vw; +} + +.favoriteIcon { + cursor: pointer; + font-size: 15px; + color: #ffd700; /* Gold color for favorite star */ + } \ No newline at end of file diff --git a/src/components/sideBar.css b/src/components/sideBar.css new file mode 100644 index 00000000..f7f370fd --- /dev/null +++ b/src/components/sideBar.css @@ -0,0 +1,15 @@ +.sidebar { + height: 100vh; + width: 250px; + position: fixed; + top: 0; + left: 0; + background-color: red; + padding-top: 20px; + overflow: auto; + } + + .sidebar h2 { + color: white; + text-align: center; + } \ No newline at end of file From 5338fa4704e01784bfb8123a19d6a5ebfd0c192d Mon Sep 17 00:00:00 2001 From: Gabriella Gabriel Toby Date: Fri, 14 Jun 2024 19:32:11 -0700 Subject: [PATCH 6/6] added footer --- src/App.jsx | 2 ++ src/components/Footer.css | 59 +++++++++++++++++++++++++++++++++++++++ src/components/Footer.jsx | 29 +++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 src/components/Footer.css create mode 100644 src/components/Footer.jsx diff --git a/src/App.jsx b/src/App.jsx index 9c34dda0..ad60a4c5 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,6 +4,7 @@ import Header from "./components/Header"; import.meta.env.VITE_API_KEY; import NowPlayingScreen from './components/NowPlayingScreen'; import SearchScreen from './components/SearchScreen.jsx'; +import Footer from './components/Footer.jsx'; const App = () => { const [isSearching, setSearching] = useState(false); @@ -59,6 +60,7 @@ const App = () => { : } +
); } diff --git a/src/components/Footer.css b/src/components/Footer.css new file mode 100644 index 00000000..a95a4929 --- /dev/null +++ b/src/components/Footer.css @@ -0,0 +1,59 @@ +.footer { + background-color: #141414; + color: white; + padding: 20px 0; + text-align: center; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +.footer-content { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; +} + +.footer-links { + display: flex; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 20px; +} + +.footer-link { + color: #e50914; + margin: 0 10px; + text-decoration: none; + transition: color 0.3s ease; +} + +.footer-link:hover { + color: #ff1e1e; +} + +.footer-social { + margin-bottom: 20px; +} + +.social-link { + color: white; + margin: 0 10px; + text-decoration: none; + font-size: 18px; + transition: color 0.3s ease; +} + +.social-link:hover { + color: #e50914; +} + +.footer-bottom { + margin-top: 20px; +} + +.footer-bottom p { + margin: 0; + font-size: 14px; + color: #888; +} \ No newline at end of file diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx new file mode 100644 index 00000000..9e03c515 --- /dev/null +++ b/src/components/Footer.jsx @@ -0,0 +1,29 @@ +import './Footer.css'; + +const Footer = () => { + return ( + + ); +}; + +export default Footer; \ No newline at end of file