From e841551507d3956e660edf78b75ea861598b9f5f Mon Sep 17 00:00:00 2001 From: zaraduruji <143117655+zaraduruji@users.noreply.github.com> Date: Wed, 12 Jun 2024 18:48:29 -0500 Subject: [PATCH 1/5] completed searching feature (#1) Co-authored-by: Zara Duruji --- src/App.css | 51 +++++++++++++++++-------- src/App.jsx | 27 +++++++++++-- src/SearchBar.jsx | 10 +++++ src/Searching.css | 31 +++++++++++++++ src/Searching.jsx | 96 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 src/SearchBar.jsx create mode 100644 src/Searching.css create mode 100644 src/Searching.jsx diff --git a/src/App.css b/src/App.css index 0bf65669..d6ba11f3 100644 --- a/src/App.css +++ b/src/App.css @@ -1,15 +1,44 @@ .App { - text-align: center; + display: flex; + flex-direction: column; + min-height: 100vh; + background-image: url(/public/purplebackground.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + color: rgb(255, 255, 255); } -.App-header { - background-color: #282c34; +main { + flex: 1; + padding: 20px; + margin: 20px; + background-color: rgba(255, 255, 255, 0.2); /* Slightly lighten the main content for readability */ + border-radius: 8px; display: flex; - flex-direction: row; - align-items: center; - justify-content: space-evenly; + flex-direction: column; + min-height: 100vh; + background-image: url(/public/purplebackground.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + color: rgb(255, 255, 255); +} + +button { + padding: 10px 20px; + font-size: 16px; + background-color: #7d3b8a; /* Darker purple */ color: white; - padding: 20px; + border: none; + border-radius: 4px; + cursor: pointer; + margin: 20px auto; + display: block; +} + +button:hover { + background-color: #9c4bb5; /* Lighter purple */ } @media (max-width: 600px) { @@ -17,12 +46,4 @@ width: 100%; } - .search-bar { - flex-direction: column; - gap: 10px; - } - - .search-bar form { - flex-direction: column; - } } diff --git a/src/App.jsx b/src/App.jsx index 48215b3f..31b48591 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,31 @@ -import { useState } from 'react' +import { useState, useEffect} from 'react' import './App.css' +import Header from '/src/Header' +import MovieList from '/src/MovieList' +import NowPlaying from './NowPlaying' +import Searching from './Searching' +import Modal from './Modal' + const App = () => { -
- + const[isSearching, setIsSearching] =useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + const handleClose = () => { + return; + } + + return ( +
+
+
+ {isSearching ? ( + + ) : } + +
+ ) } export default App diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx new file mode 100644 index 00000000..210d1ba5 --- /dev/null +++ b/src/SearchBar.jsx @@ -0,0 +1,10 @@ +function SearchBar({setSearchQuery}){ + return( +
+ {setSearchQuery(e.target.value)}}/> + +
+ ) +} + +export default SearchBar; diff --git a/src/Searching.css b/src/Searching.css new file mode 100644 index 00000000..61345642 --- /dev/null +++ b/src/Searching.css @@ -0,0 +1,31 @@ +.search-container { + display: flex; + align-items: center; + margin-right: 20px; + } + + .search-input { + padding: 8px; + font-size: 16px; + border: none; + border-radius: 4px; + margin-right: 8px; + outline: none; + background-color: #eee; + color: #333; + } + + .search-button { + padding: 8px 16px; + font-size: 16px; + background-color: #7d3b8a; /* Darker purple */ + color: white; + border-radius: 4px; + cursor: pointer; + margin-right: 10px; + border-color: #9c4bb5; + } + + .search-button:hover { + background-color: #9c4bb5; /* Lighter purple */ + } diff --git a/src/Searching.jsx b/src/Searching.jsx new file mode 100644 index 00000000..34ba9538 --- /dev/null +++ b/src/Searching.jsx @@ -0,0 +1,96 @@ +import { useState, useEffect} from 'react' +import './NowPlaying.css' +import Header from '/src/Header' +import MovieList from '/src/MovieList' +import SearchBar from './SearchBar' + + +const Searching = () => { + const[movies, setMovies] =useState([]); + const [pageNumber, setPageNumber]=useState(1); + const [searchQuery, setSearchQuery] = useState(''); + + useEffect(()=>{ + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxM2EzMjIyN2ViZDA2NDIxMmVhODU3NWI1ODI2NWEyNSIsInN1YiI6IjY2Njc2NGZkN2U0MTRkODIzM2I5Yzg1ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.NUYxsfy9MWnIfFGPrQ-wwi-OZ7nHi0Z8t1M0Dj62tl0' + } + }; + + fetch(`https://api.themoviedb.org/3/search/movie?query=${searchQuery}&include_adult=false&language=en-US&page=1`, options) + .then(response => response.json()) + .then(response => setMovies(response.results)) + // .then(response => setMovies(prevMovies =>prevMovies.concat(response.results))) + .catch(err => console.error(err)); +}, [searchQuery]); + + + // fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`, options) + // .then(response => response.json()) + // .then(response => setMovies(prevMovies =>prevMovies.concat(response.results))) + // .catch(err => console.error(err)); + // }, [pageNumber]); + + function loadNewPages(){ + setPageNumber(prevPageNumber => prevPageNumber+1) + } + + + return ( +
+ +
+ + + {/* */} +
+ + +
+ ) + +} + + +export default Searching; + + + + + + + + + + + + + + + + + + + + + + + + + + + +// const options = { +// method: 'GET', +// headers: { +// accept: 'application/json', +// Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxM2EzMjIyN2ViZDA2NDIxMmVhODU3NWI1ODI2NWEyNSIsInN1YiI6IjY2Njc2NGZkN2U0MTRkODIzM2I5Yzg1ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.NUYxsfy9MWnIfFGPrQ-wwi-OZ7nHi0Z8t1M0Dj62tl0' +// } +// }; + +// fetch(`https://api.themoviedb.org/3/search/movie?query=${searching}&include_adult=false&language=en-US&page=1`, options) +// .then(response => response.json()) +// .then(response => console.log(response)) +// .catch(err => console.error(err)); From 585e13101598199c62cc51524b8c8d0a72bc47ea Mon Sep 17 00:00:00 2001 From: Zara Duruji Date: Thu, 13 Jun 2024 13:34:44 -0700 Subject: [PATCH 2/5] Header Feature --- .env | 1 + package-lock.json | 66 ++++++++++++++++- package.json | 4 + public/purplebackground.jpg | Bin 0 -> 49633 bytes src/App.css | 79 ++++++++++++++++++++ src/App.jsx | 49 ++++++------ src/Header.css | 53 +++++++++++++ src/Header.jsx | 20 +++++ src/Modal.css | 90 ++++++++++++++++++++++ src/Modal.jsx | 144 ++++++++++++++++++++++++++++++++++++ src/MovieCard.css | 67 +++++++++++++++++ src/MovieCard.jsx | 41 ++++++++++ src/MovieList.css | 6 ++ src/MovieList.jsx | 45 +++++++++++ src/NowPlaying.css | 49 ++++++++++++ src/NowPlaying.jsx | 80 ++++++++++++++++++++ src/SearchBar.jsx | 2 +- src/Searching.css | 38 +++++----- src/SortBy.css | 14 ++++ src/SortBy.jsx | 18 +++++ 20 files changed, 817 insertions(+), 49 deletions(-) create mode 100644 .env create mode 100644 public/purplebackground.jpg create mode 100644 src/Header.css create mode 100644 src/Header.jsx 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/NowPlaying.css create mode 100644 src/NowPlaying.jsx create mode 100644 src/SortBy.css create mode 100644 src/SortBy.jsx diff --git a/.env b/.env new file mode 100644 index 00000000..f5fdb484 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +VITE_API_KEY=13a32227ebd064212ea8575b58265a25 diff --git a/package-lock.json b/package-lock.json index 92a683d2..e8a5bca8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "flixster", "version": "0.0.0", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/react-fontawesome": "^0.2.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -811,6 +815,63 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.5.2.tgz", + "integrity": "sha512-gBxPg3aVO6J0kpfHNILc+NMhXnqHumFxOmjYCFfOiLZfwhnnfhtsdA2hfJlDnj+8PjAs6kKQPenOTKj3Rf7zHw==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.5.2.tgz", + "integrity": "sha512-5CdaCBGl8Rh9ohNdxeeTMxIj8oc3KNBgIeLMvJosBMdslK/UnEB8rzyDRrbKdL1kDweqBPo4GT9wvnakHWucZw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.5.2.tgz", + "integrity": "sha512-iabw/f5f8Uy2nTRtJ13XZTS1O5+t+anvlamJ3zJGLEVE2pKsAWhPv2lq01uQlfgCX7VaveT3EVs515cCN9jRbw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.5.2.tgz", + "integrity": "sha512-QWFZYXFE7O1Gr1dTIp+D6UcFUF0qElOnZptpi7PBUMylJh+vFmIedVe1Ir6RM1t2tEQLLSV1k7bR4o92M+uqlw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.5.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3193,7 +3254,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3457,7 +3517,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -3519,8 +3578,7 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-refresh": { "version": "0.14.0", diff --git a/package.json b/package.json index eded5715..304d67da 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,10 @@ "preview": "vite preview" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.2", + "@fortawesome/free-regular-svg-icons": "^6.5.2", + "@fortawesome/free-solid-svg-icons": "^6.5.2", + "@fortawesome/react-fontawesome": "^0.2.2", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/public/purplebackground.jpg b/public/purplebackground.jpg new file mode 100644 index 0000000000000000000000000000000000000000..71a44c731344b814c7b61ecd0e466d104383830a GIT binary patch literal 49633 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 literal 0 HcmV?d00001 diff --git a/src/App.css b/src/App.css index d6ba11f3..484d85fd 100644 --- a/src/App.css +++ b/src/App.css @@ -41,9 +41,88 @@ button:hover { background-color: #9c4bb5; /* Lighter purple */ } +/* src/App.css */ + +.toggle-button { + background-color: #4b2e83; /* Purple background */ + color: white; + border: none; + padding: 10px 20px; + font-size: 16px; + border-radius: 5px; + cursor: pointer; + margin-bottom: 20px; /* Adjust as needed */ +} + +.toggle-button:hover { + background-color: #7856b7; /* Darker purple on hover */ +} +@media (max-width: 1024px) { + main { + padding: 15px; + margin: 15px; + } + + .toggle-button { + font-size: 14px; + padding: 8px 16px; + } + + button { + font-size: 14px; + padding: 8px 16px; + } +} + +@media (max-width: 768px) { + .header { + flex-direction: column; + align-items: center; + } + + .header-right { + margin-top: 10px; + } + + main { + padding: 10px; + margin: 10px; + } + + .toggle-button { + font-size: 12px; + padding: 6px 12px; + } + + button { + font-size: 12px; + padding: 6px 12px; + } + + .movie-card { + width: 90%; + margin: 10px auto; + } +} + @media (max-width: 600px) { .movie-card { width: 100%; + margin: 5px auto; + } + + main { + padding: 5px; + margin: 5px; + } + + .toggle-button { + font-size: 10px; + padding: 5px 10px; } + button { + font-size: 10px; + padding: 5px 10px; + } } diff --git a/src/App.jsx b/src/App.jsx index 31b48591..2b66b622 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,31 +1,32 @@ -import { useState, useEffect} from 'react' -import './App.css' -import Header from '/src/Header' -import MovieList from '/src/MovieList' -import NowPlaying from './NowPlaying' -import Searching from './Searching' -import Modal from './Modal' - +import { useState } from 'react'; +import './App.css'; +import Header from './Header'; +import NowPlaying from './NowPlaying'; +import Searching from './Searching'; const App = () => { - const[isSearching, setIsSearching] =useState(false); - const [searchQuery, setSearchQuery] = useState(''); + const [isSearching, setIsSearching] = useState(false); + const [sortCriteria, setSortCriteria] = useState('release_date'); + + const toggleScreen = () => { + setIsSearching(prevState => !prevState); + }; - const handleClose = () => { - return; - } + const handleSortChange = (criteria) => { + setSortCriteria(criteria); + }; return (
-
-
- {isSearching ? ( - - ) : } - -
-
- ) -} +
+
+ + {isSearching ? : } +
+
+ ); +}; -export default App +export default App; diff --git a/src/Header.css b/src/Header.css new file mode 100644 index 00000000..7a02da81 --- /dev/null +++ b/src/Header.css @@ -0,0 +1,53 @@ +/* src/components/Header.css */ + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + background-color: rgba(102, 51, 153, 0.8); /* Purple with transparency */ + color: white; + border-radius: 8px; + margin: 20px; + } + + .header h1 { + margin: 0; + } + + .controls-container { + display: flex; + align-items: center; + } + + .search-container { + display: flex; + align-items: center; + margin-right: 20px; + } + + .search-input { + padding: 8px; + font-size: 16px; + border: none; + border-radius: 4px; + margin-right: 8px; + outline: none; + background-color: #eee; + color: #333; + } + + .search-button { + padding: 8px 16px; + font-size: 16px; + background-color: #7d3b8a; /* Darker purple */ + color: white; + border-radius: 4px; + cursor: pointer; + margin-right: 10px; + border-color: #9c4bb5; + } + + .search-button:hover { + background-color: #9c4bb5; /* Lighter purple */ + } diff --git a/src/Header.jsx b/src/Header.jsx new file mode 100644 index 00000000..5d78e3d6 --- /dev/null +++ b/src/Header.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import './Header.css'; +import SortBy from './SortBy'; + +function Header({ setIsSearching, sortMovies }) { + return ( +
+

Flixster

+
+ +
+
+ ); +} + +export default Header; diff --git a/src/Modal.css b/src/Modal.css new file mode 100644 index 00000000..265f3ac0 --- /dev/null +++ b/src/Modal.css @@ -0,0 +1,90 @@ +/* src/Modal.css */ + +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.8); /* Slightly darker overlay */ + display: flex; + justify-content: center; + align-items: center; + z-index: 1000; + } + + .modal { + background-color: #663399; /* Purple with no transparency */ + border-radius: 8px; + padding: 20px; + max-width: 900px; + width: 90%; + height: 80%; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + text-align: center; + color: white; + position: relative; + display: flex; + flex-direction: column; + } + + .modal-close { + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + font-size: 24px; + color: white; + cursor: pointer; + } + + .modal-header { + display: flex; + flex-direction: row; + } + + .modal-image { + width: 300px; + height: auto; + border-radius: 8px; + margin-right: 20px; + } + + .modal-info { + flex: 1; + text-align: left; + } + + .modal-title { + font-size: 1.5em; + margin: 10px 0; + } + + .modal-rating { + color: #ffcc00; /* Bright yellow for rating */ + margin-bottom: 10px; + font-weight: bold; + } + + .modal-overview { + color: #f0f0f0; + } + + .modal-content { + margin-top: 20px; + flex: 1; + background-color: #4b2e83; /* Darker purple for contrast */ + border-radius: 8px; + padding: 20px; + overflow-y: auto; + } + + .movie-trailer { + background-color: #333; /* Placeholder background color */ + height: 100%; + display: flex; + justify-content: center; + align-items: center; + color: white; + } diff --git a/src/Modal.jsx b/src/Modal.jsx new file mode 100644 index 00000000..d1b83096 --- /dev/null +++ b/src/Modal.jsx @@ -0,0 +1,144 @@ +// import { useEffect, useState } from 'react'; +// import './Modal.css'; + +// function Modal({ movie, onClose }) { +// const [movieInfo, setMovieInfo] = useState(undefined); +// const [trailerURL, setTrailerURL] = useState(""); +// const apiKey = import.meta.env.VITE_API_KEY; + + +// // useEffect(() => { +// // const options = { +// // method: 'GET', +// // headers: { +// // accept: 'application/json', +// // Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxM2EzMjIyN2ViZDA2NDIxMmVhODU3NWI1ODI2NWEyNSIsInN1YiI6IjY2Njc2NGZkN2U0MTRkODIzM2I5Yzg1ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.NUYxsfy9MWnIfFGPrQ-wwi-OZ7nHi0Z8t1M0Dj62tl0' +// // } +// // }; + +// // fetch(`https://api.themoviedb.org/3/movie/${movie.id}/videos?api_key=${apiKey}`, options) +// // .then(response => response.json()) +// // .then(response => console.log(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)); +// // }, [movie.id, apiKey]); + +// useEffect(() => { +// const options = { +// method: 'GET', +// headers: { +// accept: 'application/json', +// Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' +// } +// }; + +// fetch(`https://api.themoviedb.org/3/movie/${movie}?language=en-US`, options) +// .then(response => response.json()) +// .then(response => setMovieInfo(response)) + +// .catch(err => console.error(err)); +// }, [movie]); + +// const handleOverlayClick = (event) => { +// if (event.target === event.currentTarget) { +// onClose(); +// } +// }; + +// return ( +//
+//
+// +// {movieInfo && ( +// <> +// {movieInfo.title} +//

{movieInfo.title}

+//

Rating: {movieInfo.vote_average}

+// + +//

{movieInfo.overview}

+// + +// +// )} +//
+//
+// ); +// } + +// export default Modal; + +import { useEffect, useState } from 'react'; +import './Modal.css'; + +function Modal({ movie, onClose }) { + const [movieInfo, setMovieInfo] = useState(undefined); + 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/${movie}?language=en-US`, options) + .then(response => response.json()) + .then(response => { + setMovieInfo(response); + console.log("response",response); + + return response.id; + }) + .then(movieID => { + fetch(`https://api.themoviedb.org/3/movie/${movieID}/videos?api_key=${apiKey}`) + .then(response => response.json()) + .then(response => { + const trailer = response.results.find( + (video) => video.site === "YouTube" && video.type === "Trailer" + ); + console.log("trailer",trailer); + if (trailer) { + setTrailerURL(`https://www.youtube.com/embed/${trailer.key}`); + } + }) + .catch(err => console.error(err)); + }) + .catch(err => console.error(err)); + }, [movie, apiKey]); + + const handleOverlayClick = (event) => { + if (event.target === event.currentTarget) { + onClose(); + } + }; + + return ( +
+
+ + {movieInfo && ( + <> + {movieInfo.title} +

{movieInfo.title}

+

Rating: {movieInfo.vote_average}

+ {trailerURL ? ( + + ) : ( +

No trailer available

+ )} +

{movieInfo.overview}

+ + )} +
+
+ ); +} + +export default Modal; diff --git a/src/MovieCard.css b/src/MovieCard.css new file mode 100644 index 00000000..9251be7e --- /dev/null +++ b/src/MovieCard.css @@ -0,0 +1,67 @@ +.movie-card { + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 8px; + padding: 16px; + text-align: center; + background-color: rgba(102, 51, 153, 0.8); /* Purple with transparency */ + color: white; + margin: 10px; + transition: transform 0.3s, box-shadow 0.3s; + position: relative; + cursor: pointer; +} + +.movie-card:hover { + transform: scale(1.05); + box-shadow: 0 8px 16px rgb(0, 0, 0); /* Darker shadow */ +} + +.moviePoster { + width: 100%; + height: auto; + border-radius: 4px; +} + +.movie-title { + font-size: 1.2em; + margin: 10px 0; +} + +.movie-rating { + color: #ccc; + margin-bottom: 10px; /* Add space below the rating */ +} + +.favorite-button { + position: absolute; + top: 10px; + right: 10px; + background: none; + border: none; + font-size: 1.5em; + cursor: pointer; + color: rgb(255, 1, 1); + transition: color 0.3s; +} + +.favorite-button .fa-solid { + color: #ff0000; /* Red color for favorite */ +} + +.favorite-button .fa-regular { + color: rgb(255, 32, 32); /* Default color */ +} + +.watched-button { + position: relative; + background: none; + border: none; + font-size: 1em; + cursor: pointer; + color: rgba(255, 255, 255, 0.5); + transition: color 0.3s; +} + +.watched-button.watched { + color: #00ff00; /* Green color for watched */ +} diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx new file mode 100644 index 00000000..303b9786 --- /dev/null +++ b/src/MovieCard.jsx @@ -0,0 +1,41 @@ +import React, { useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faHeart as regularHeart } from "@fortawesome/free-regular-svg-icons"; +import { faHeart as solidHeart } from "@fortawesome/free-solid-svg-icons"; +import "./MovieCard.css"; + +function MovieCard(props) { + const [isFavorite, setIsFavorite] = useState(false); + const [isWatched, setIsWatched] = useState(false); + + function handleModal() { + console.log("clicked"); + props.getMovieInfo(props.movieId); + } + + const toggleFavorite = (e) => { + e.stopPropagation(); + setIsFavorite(prevState => !prevState); + }; + + const toggleWatched = (e) => { + e.stopPropagation(); + setIsWatched(prevState => !prevState); + }; + + return ( +
+ {props.title} +

{props.title}

+

Rating: {props.rating}

+ + +
+ ); +} + +export default MovieCard; diff --git a/src/MovieList.css b/src/MovieList.css new file mode 100644 index 00000000..a579505c --- /dev/null +++ b/src/MovieList.css @@ -0,0 +1,6 @@ +.movie-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 16px; + padding: 20px; + } diff --git a/src/MovieList.jsx b/src/MovieList.jsx new file mode 100644 index 00000000..5054a4b9 --- /dev/null +++ b/src/MovieList.jsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from "react"; +import React from "react"; +import MovieCard from "./MovieCard"; +import "./MovieList.css"; + +function MovieList(props) { + + + + function createCard(card) { + + return ( + + ) + } + + return ( +
+ {/* {props.data.map(createCard)} */} + {props.data.map(card => + + )} +
+ ) +} + +export default MovieList; diff --git a/src/NowPlaying.css b/src/NowPlaying.css new file mode 100644 index 00000000..feb0ece3 --- /dev/null +++ b/src/NowPlaying.css @@ -0,0 +1,49 @@ +.now-playing { + display: flex; + flex-direction: column; + min-height: 100vh; + background-image: url(/public/purplebackground.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + color: rgb(255, 255, 255); + } + + main { + flex: 1; + padding: 20px; + margin: 20px; + background-color: rgba(255, 255, 255, 0.2); /* Slightly lighten the main content for readability */ + border-radius: 8px; + display: flex; + flex-direction: column; + min-height: 100vh; + background-image: url(/public/purplebackground.jpg); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + color: rgb(255, 255, 255); + } + + button { + padding: 10px 20px; + font-size: 16px; + background-color: #7d3b8a; /* Darker purple */ + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + margin: 20px auto; + display: block; + } + + button:hover { + background-color: #9c4bb5; /* Lighter purple */ + } + + @media (max-width: 600px) { + .movie-card { + width: 100%; + } + + } diff --git a/src/NowPlaying.jsx b/src/NowPlaying.jsx new file mode 100644 index 00000000..c75f3518 --- /dev/null +++ b/src/NowPlaying.jsx @@ -0,0 +1,80 @@ +import { useState, useEffect } from 'react'; +import './NowPlaying.css'; +import MovieList from './MovieList'; +import Modal from './Modal'; + +const NowPlaying = ({ sortCriteria }) => { + const [movies, setMovies] = useState([]); + const [pageNumber, setPageNumber] = useState(1); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedMovie, setSelectedMovie] = useState(null); + + useEffect(() => { + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + console.log("fetching new movies") + fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`, options) + .then(response => response.json()) + // .then(response => handleSortMovies(sortCriteria, movies.concat(response.results))) + .then(response => { + console.log("new movies", response.results) + return response + }) + .then(response => setMovies(movies.concat(response.results))) + .catch(err => console.error(err)); + }, [pageNumber]); + + useEffect(() => { + console.log("sorting movies") + handleSortMovies(sortCriteria); + }, [sortCriteria]) + + const loadNewPages = () => { + setPageNumber(prevPageNumber => prevPageNumber + 1); + }; + + const getMovieInfo = (movie) => { + setIsModalOpen(true); + setSelectedMovie(movie); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setSelectedMovie(null); + }; + + const handleSortMovies = (criteria, movieList = movies) => { + const copyMovies = [...movieList]; + copyMovies.sort((a, b) => { + if (criteria === 'title') { + return a.title.localeCompare(b.title); + } else if (criteria === 'release_date') { + return new Date(a.release_date) - new Date(b.release_date); + } else if (criteria === 'vote_average') { + return b.vote_average - a.vote_average; + } + return 0; + }); + + console.log("sorted movies", copyMovies, movieList) + + setMovies(copyMovies); + }; + + return ( +
+
+ {isModalOpen && } + + +
+
+ ); +}; + +export default NowPlaying; diff --git a/src/SearchBar.jsx b/src/SearchBar.jsx index 210d1ba5..e3b3e328 100644 --- a/src/SearchBar.jsx +++ b/src/SearchBar.jsx @@ -1,8 +1,8 @@ +import './Searching.css'; function SearchBar({setSearchQuery}){ return(
{setSearchQuery(e.target.value)}}/> -
) } diff --git a/src/Searching.css b/src/Searching.css index 61345642..5171159f 100644 --- a/src/Searching.css +++ b/src/Searching.css @@ -1,31 +1,29 @@ +/* Searching.css */ + .search-container { display: flex; - align-items: center; - margin-right: 20px; + justify-content: center; + margin-top: 20px; } .search-input { - padding: 8px; - font-size: 16px; - border: none; + width: 50%; + padding: 12px 20px; + margin: 8px 0; + box-sizing: border-box; + border: 2px solid #4b2e83; border-radius: 4px; - margin-right: 8px; - outline: none; - background-color: #eee; - color: #333; + background-color: #f8f8f8; + font-size: 16px; + color: #4b2e83; } - .search-button { - padding: 8px 16px; - font-size: 16px; - background-color: #7d3b8a; /* Darker purple */ - color: white; - border-radius: 4px; - cursor: pointer; - margin-right: 10px; - border-color: #9c4bb5; + .search-input::placeholder { + color: #4b2e83; } - .search-button:hover { - background-color: #9c4bb5; /* Lighter purple */ + .search-input:focus { + outline: none; + border-color: #6f42c1; + background-color: #fff; } diff --git a/src/SortBy.css b/src/SortBy.css new file mode 100644 index 00000000..b04c48f4 --- /dev/null +++ b/src/SortBy.css @@ -0,0 +1,14 @@ +.sort-by { + padding: 8px; + font-size: 16px; + border: none; + border-radius: 4px; + outline: none; + background-color: #7d3b8a; /* Darker purple */ + color: white; + cursor: pointer; + } + + .sort-by:hover { + background-color: #9c4bb5; /* Lighter purple */ + } diff --git a/src/SortBy.jsx b/src/SortBy.jsx new file mode 100644 index 00000000..88bbbd86 --- /dev/null +++ b/src/SortBy.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import './SortBy.css'; + +function SortBy({ setSortOption }) { + const handleSortChange = (e) => { + setSortOption(e.target.value); + }; + + return ( + + ); +} + +export default SortBy; From f18a5608cd1ba98f2ff6de58f7f6c809e6b05c7d Mon Sep 17 00:00:00 2001 From: Zara Duruji Date: Fri, 14 Jun 2024 16:13:09 -0700 Subject: [PATCH 3/5] Update --- src/App.jsx | 69 +++++++++++++++++++++++++-- src/Footer.css | 10 ++++ src/Footer.jsx | 12 +++++ src/Header.css | 1 + src/Header.jsx | 11 +++-- src/Modal.css | 15 +++++- src/MovieCard.jsx | 45 +++++++----------- src/MovieList.jsx | 37 ++++++++------- src/NowPlaying.jsx | 113 ++++++++++++++++++++------------------------- src/Searching.jsx | 9 +++- src/Sidebar.css | 46 ++++++++++++++++++ src/Sidebar.jsx | 32 +++++++++++++ 12 files changed, 281 insertions(+), 119 deletions(-) create mode 100644 src/Footer.css create mode 100644 src/Footer.jsx create mode 100644 src/Sidebar.css create mode 100644 src/Sidebar.jsx diff --git a/src/App.jsx b/src/App.jsx index 2b66b622..ac468d23 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,30 +1,93 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import './App.css'; import Header from './Header'; import NowPlaying from './NowPlaying'; import Searching from './Searching'; +import Sidebar from './Sidebar'; +import Footer from './Footer'; const App = () => { const [isSearching, setIsSearching] = useState(false); - const [sortCriteria, setSortCriteria] = useState('release_date'); + const [sortCriteria, setSortCriteria] = useState(''); + const [favorites, setFavorites] = useState([]); + const [watched, setWatched] = useState([]); + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const [movies, setMovies] = useState([]); + const [pageNumber, setPageNumber] = useState(1); const toggleScreen = () => { setIsSearching(prevState => !prevState); }; const handleSortChange = (criteria) => { + console.log(criteria); + setMovies([]) setSortCriteria(criteria); }; + const toggleSidebar = () => { + setIsSidebarOpen(prevState => !prevState); + }; + + const handleSetFavorites = (newVal) => { + setFavorites(newVal) + } + + const handleSetWatched = (newVal) => { + setWatched(newVal) + } + + const loadNewPages = () => { + setPageNumber(prevPageNumber => prevPageNumber + 1); + }; + + useEffect(() => { + const options = { + method: 'GET', + headers: { + accept: 'application/json', + Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' + } + }; + + fetch(`https://api.themoviedb.org/3/discover/movie?include_adult=false&include_video=false&language=en-US&page=${pageNumber}&sort_by=${sortCriteria}`, options) + .then(response => response.json()) + .then(response => setMovies(movies.concat(response.results))) + .catch(err => console.error(err)); + }, [pageNumber, sortCriteria]); + + return (
+ + {isSidebarOpen && }
- {isSearching ? : } + {isSearching ? ( + + ) : ( + + )}
+
); }; diff --git a/src/Footer.css b/src/Footer.css new file mode 100644 index 00000000..972b412a --- /dev/null +++ b/src/Footer.css @@ -0,0 +1,10 @@ +/* Footer.css */ +.footer { + background-color: #4b2e83; /* Purple background */ + color: white; + text-align: center; + padding: 10px 0; + position: fixed; + width: 100%; + bottom: 0; + } diff --git a/src/Footer.jsx b/src/Footer.jsx new file mode 100644 index 00000000..d43ab8c9 --- /dev/null +++ b/src/Footer.jsx @@ -0,0 +1,12 @@ +import React from 'react'; +import './Footer.css'; + +function Footer() { + return ( +
+

© 2024 Flixster. All rights reserved.

+
+ ); +} + +export default Footer; diff --git a/src/Header.css b/src/Header.css index 7a02da81..2ea10244 100644 --- a/src/Header.css +++ b/src/Header.css @@ -13,6 +13,7 @@ .header h1 { margin: 0; + margin-left: 20px; } .controls-container { diff --git a/src/Header.jsx b/src/Header.jsx index 5d78e3d6..65499a11 100644 --- a/src/Header.jsx +++ b/src/Header.jsx @@ -1,15 +1,20 @@ -import React from 'react'; import './Header.css'; import SortBy from './SortBy'; function Header({ setIsSearching, sortMovies }) { + + + return (

Flixster

diff --git a/src/Modal.css b/src/Modal.css index 265f3ac0..86aca4aa 100644 --- a/src/Modal.css +++ b/src/Modal.css @@ -26,6 +26,7 @@ position: relative; display: flex; flex-direction: column; + overflow: hidden; } .modal-close { @@ -42,10 +43,11 @@ .modal-header { display: flex; flex-direction: row; + align-items: center; } .modal-image { - width: 300px; + width: 200px; height: auto; border-radius: 8px; margin-right: 20px; @@ -69,6 +71,7 @@ .modal-overview { color: #f0f0f0; + margin-top: 10px; } .modal-content { @@ -78,13 +81,21 @@ border-radius: 8px; padding: 20px; overflow-y: auto; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } .movie-trailer { + width: 100%; + height: 315px; background-color: #333; /* Placeholder background color */ - height: 100%; display: flex; justify-content: center; align-items: center; color: white; + margin-top: 20px; + border-radius: 8px; + overflow: hidden; } diff --git a/src/MovieCard.jsx b/src/MovieCard.jsx index 303b9786..fa52123b 100644 --- a/src/MovieCard.jsx +++ b/src/MovieCard.jsx @@ -5,37 +5,24 @@ import { faHeart as solidHeart } from "@fortawesome/free-solid-svg-icons"; import "./MovieCard.css"; function MovieCard(props) { - const [isFavorite, setIsFavorite] = useState(false); - const [isWatched, setIsWatched] = useState(false); - function handleModal() { - console.log("clicked"); - props.getMovieInfo(props.movieId); - } + function handleModal() { + props.getMovieInfo(props.movieId); + } - const toggleFavorite = (e) => { - e.stopPropagation(); - setIsFavorite(prevState => !prevState); - }; - - const toggleWatched = (e) => { - e.stopPropagation(); - setIsWatched(prevState => !prevState); - }; - - return ( -
- {props.title} -

{props.title}

-

Rating: {props.rating}

- - -
- ); + return ( +
+ {props.title} +

{props.title}

+

Rating: {props.rating}

+ + +
+ ); } export default MovieCard; diff --git a/src/MovieList.jsx b/src/MovieList.jsx index 5054a4b9..777547e2 100644 --- a/src/MovieList.jsx +++ b/src/MovieList.jsx @@ -5,27 +5,26 @@ import "./MovieList.css"; function MovieList(props) { + const toggleFavorite = (e, isAdd, movie) => { + e.stopPropagation(); + if (isAdd) { + props.setFavorites([...props.favorites, movie]); + } else { + props.setFavorites(props.favorites.filter(fav => fav.id !== movie.id)); + } + }; - - function createCard(card) { - - return ( - - ) - } + const toggleWatched = (e, isAdd, movie) => { + e.stopPropagation(); + if (isAdd) { + props.setWatched([...props.watched, movie]); + } else { + props.setWatched(props.watched.filter(watch => watch.id !== movie.id)); + } + }; return (
- {/* {props.data.map(createCard)} */} {props.data.map(card => )}
diff --git a/src/NowPlaying.jsx b/src/NowPlaying.jsx index c75f3518..3403ab18 100644 --- a/src/NowPlaying.jsx +++ b/src/NowPlaying.jsx @@ -3,78 +3,65 @@ import './NowPlaying.css'; import MovieList from './MovieList'; import Modal from './Modal'; -const NowPlaying = ({ sortCriteria }) => { - const [movies, setMovies] = useState([]); - const [pageNumber, setPageNumber] = useState(1); - const [isModalOpen, setIsModalOpen] = useState(false); - const [selectedMovie, setSelectedMovie] = useState(null); +const NowPlaying = ({ sortCriteria, favorites, setFavorites, watched, setWatched, movies, loadNewPages }) => { - useEffect(() => { - const options = { - method: 'GET', - headers: { - accept: 'application/json', - Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJkYWU5YTZiOThlMTBjZDkyZTcxN2Y4OWIzZDYxYjdjNSIsInN1YiI6IjY2NjY1MTQ1Y2M3MDc0ZDliNjFjMWM2ZiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.IDf1-04fWbMoc-zzed3BAcZLflL14UG-mdjcZobVjxA' - } - }; - console.log("fetching new movies") - fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${pageNumber}`, options) - .then(response => response.json()) - // .then(response => handleSortMovies(sortCriteria, movies.concat(response.results))) - .then(response => { - console.log("new movies", response.results) - return response - }) - .then(response => setMovies(movies.concat(response.results))) - .catch(err => console.error(err)); - }, [pageNumber]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedMovie, setSelectedMovie] = useState(null); - useEffect(() => { - console.log("sorting movies") - handleSortMovies(sortCriteria); - }, [sortCriteria]) + console.log(sortCriteria); +// if sortCriteria === '' or "nowplaying" then sort by popularity.desc - const loadNewPages = () => { - setPageNumber(prevPageNumber => prevPageNumber + 1); - }; - const getMovieInfo = (movie) => { - setIsModalOpen(true); - setSelectedMovie(movie); - }; - const handleCloseModal = () => { - setIsModalOpen(false); - setSelectedMovie(null); - }; - const handleSortMovies = (criteria, movieList = movies) => { - const copyMovies = [...movieList]; - copyMovies.sort((a, b) => { - if (criteria === 'title') { - return a.title.localeCompare(b.title); - } else if (criteria === 'release_date') { - return new Date(a.release_date) - new Date(b.release_date); - } else if (criteria === 'vote_average') { - return b.vote_average - a.vote_average; - } - return 0; - }); +// useEffect(() => { +// handleSortMovies(sortCriteria); +// }, [sortCriteria]); - console.log("sorted movies", copyMovies, movieList) - setMovies(copyMovies); - }; - return ( -
-
- {isModalOpen && } - - -
-
- ); + const getMovieInfo = (movie) => { + setIsModalOpen(true); + setSelectedMovie(movie); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setSelectedMovie(null); + }; + +// const handleSortMovies = (criteria, movieList = movies) => { +// const copyMovies = [...movieList]; +// copyMovies.sort((a, b) => { +// if (criteria === 'title') { +// return a.title.localeCompare(b.title); +// } else if (criteria === 'release_date') { +// return new Date(a.release_date) - new Date(b.release_date); +// } else if (criteria === 'vote_average') { +// return b.vote_average - a.vote_average; +// } +// return 0; +// }); + +// setMovies(copyMovies); +// }; + + return ( +
+
+ {isModalOpen && } + + +
+
+ ); }; export default NowPlaying; diff --git a/src/Searching.jsx b/src/Searching.jsx index 34ba9538..bc5356ff 100644 --- a/src/Searching.jsx +++ b/src/Searching.jsx @@ -5,7 +5,7 @@ import MovieList from '/src/MovieList' import SearchBar from './SearchBar' -const Searching = () => { +const Searching = ({favorites, setFavorites, watched, setWatched}) => { const[movies, setMovies] =useState([]); const [pageNumber, setPageNumber]=useState(1); const [searchQuery, setSearchQuery] = useState(''); @@ -42,7 +42,12 @@ const Searching = () => {
- + {/* */}
diff --git a/src/Sidebar.css b/src/Sidebar.css new file mode 100644 index 00000000..efab5002 --- /dev/null +++ b/src/Sidebar.css @@ -0,0 +1,46 @@ +/* src/Sidebar.css */ + +.sidebar { + position: fixed; + top: 0; + left: 0; + width: 250px; + height: 100%; + background-color: rgba(75, 46, 131, 0.9); + color: white; + padding: 20px; + overflow-y: auto; + z-index: 1; +} + +.sidebar h2 { + font-size: 1.5em; + margin-bottom: 10px; +} + +.sidebar ul { + list-style: none; + padding: 0; +} + +.sidebar ul li { + margin-bottom: 10px; +} + +.sidebar-toggle { + position: absolute; + top: 20px; + left: 20px; + font-size: 1.5em; + background: none; + border: none; + color: white; + cursor: pointer; + margin-top: 6px; +} + +.closeSidebarButton { + +margin: 0; +float: right; +} diff --git a/src/Sidebar.jsx b/src/Sidebar.jsx new file mode 100644 index 00000000..68162d53 --- /dev/null +++ b/src/Sidebar.jsx @@ -0,0 +1,32 @@ +import React from 'react'; +import './Sidebar.css'; + +const Sidebar = ({ favorites, watched, closeSideBar }) => { + return ( +
+ +

Favorites

+ {favorites.length === 0 ? ( +

No favorite movies.

+ ) : ( +
    + {favorites.map((movie, i) => ( +
  • {movie.title}
  • + ))} +
+ )} +

Watched

+ {watched.length === 0 ? ( +

No watched movies.

+ ) : ( +
    + {watched.map((movie, i) => ( +
  • {movie.title}
  • + ))} +
+ )} +
+ ); +}; + +export default Sidebar; From 742e20f440b3e1e79557d89c631c080e3144a32e Mon Sep 17 00:00:00 2001 From: zaraduruji <143117655+zaraduruji@users.noreply.github.com> Date: Fri, 14 Jun 2024 16:32:19 -0700 Subject: [PATCH 4/5] Update README.md --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f768e33f..37f929ec 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,79 @@ -# React + Vite +## Unit Assignment: Flixster -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +Submitted by: Zara Duruji -Currently, two official plugins are available: +Estimated time spent: **#** 40 Hours -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +Deployed Application (optional): [Flixster Deployed Site] https://flixster-starter-yhyt.onrender.com/ + +### Application Features + +#### CORE FEATURES + + +- [❌] **Display Movies** + - [ ❌] Users can view a list of current movies from The Movie Database API. + - [❌ ] For each movie displayed, users can see its title, poster image, and votes. + - [❌ ] Users can load more current movies by clicking a button at the bottom of the list (page should not be reloaded). +- [ ❌] **Search Functionality** + - [ ❌] Users can search for movies and view the results in a grid. + - [ ❌] Users can clear results and view previous current movies displayed. +- [ ❌] **Accessibility Features** + - ❌[ ] Website implements accessibility features (semantic HTML, color contrast, font sizing, alt text for images). +- [❌ ] **Responsive Design** + - [❌ ] Website implements responsive web design. +- [ ❌] **Movie Details** + - [ ❌] Users can view more details about a movie in a popup, such as runtime in minutes, backdrop poster, release date, genres, and/or an overview. +- [ ❌] **Sorting Options** + - [❌ ] Users can click on a filter by drop down to sort product by type (alphabetic, release date, rating). +- [ ❌] **Layout** + - [ ❌] Website displays header, banner, search, movie grid, about, contact, and footer section. + +#### STRETCH FEATURES + +- [❌ ] **Deployment** + - [ ❌] Website is deployed via Render. +- [ ❌] **Embedded Movie Trailers** + - [❌ ] Within the popup displaying a movie's details, users can play the movie trailer. +- [ ❌] **Watched Checkbox** + - [ ❌] For each movie displayed, users can mark the movie as watched. +- [ ❌] **Favorite Button** + - [❌ ] For each movie displayed, users can favorite the movie. +- [ ❌] **Sidebar** + - [ ❌] Users can open a sidebar + - [ ❌] The sidebar displays the user's favorited and watched movies + +### Walkthrough Video + +https://www.loom.com/share/e8dce04c0a6f4880bbdc819e4c7a160b?sid=dc874578-555a-4fad-b506-c0ad1992b39f + +### Reflection + +* Did the topics discussed in your labs prepare you to complete the assignment? Be specific, which features in your weekly assignment did you feel unprepared to complete? + +The topics discussed in the labs provided a good foundation for completing the assignment, particularly in managing state with hooks, integrating APIs, and structuring the application with reusable components. However, there were areas where I felt less prepared. Designing responsive and visually appealing components like the modal and sidebar was challenging due to limited coverage of advanced CSS and styling techniques. Managing state across multiple components, especially for dynamic updates to the favorites and watched lists, was more complex than expected. Although basic event handling was covered, implementing features like toggling favorites and marking movies as watched required more advanced knowledge. Error handling with API requests and integrating external libraries like FontAwesome for icons were also areas where additional guidance would have been helpful. Overall, while the labs were beneficial, more focus on these advanced topics would have better prepared me for the assignment. + +* If you had more time, what would you have done differently? Would you have added additional features? Changed the way your project responded to a particular event, etc. + +If I had more time, I would have made several improvements and added additional features to enhance the project. Firstly, I would refine the user interface to ensure a more cohesive and visually appealing design, paying particular attention to responsiveness across different devices. This includes optimizing the layout of the modal and sidebar components and ensuring smooth animations for opening and closing these elements. + +Additionally, I would implement more robust error handling for API requests, providing clear feedback to users in case of network issues or data retrieval errors. Enhancing the state management logic to handle complex interactions more efficiently would also be a priority, ensuring a seamless user experience when toggling favorites and marking movies as watched. + +I would consider adding features such as user authentication to personalize the experience and allow users to save their favorite movies and watched lists across sessions. Another feature could be a recommendation system based on the user's viewing history and preferences. + +Finally, I would refine the event-handling mechanisms to make interactions more intuitive and responsive, ensuring that the project responds smoothly to user actions such as sorting, searching, and interacting with movie cards. These enhancements would significantly improve the overall functionality and user experience of the project. + +* Reflect on your project demo, what went well? Were there things that maybe didn't go as planned? Did you notice something that your peer did that you would like to try next time? + +Reflecting on my project demo, I was particularly pleased with the implementation of the modal, which provided a detailed and engaging way to display movie information and trailers. The modal design and functionality turned out to be intuitive and visually appealing, enhancing the overall user experience. + +However, I did encounter challenges while implementing the sidebar. Ensuring it updated correctly with favorite and watched movies, as well as making it responsive and user-friendly, proved to be more complex than anticipated. This was an area where things didn't go entirely as planned, and it highlighted the need for better state management and event handling in my project. + +During the demo, I noticed a peer who had seamlessly integrated a more advanced sorting and filtering system, which greatly enhanced the usability of their project. This is something I would like to try in my next project, as it would provide users with a more dynamic and interactive experience. Their approach to sorting and filtering was efficient and user-friendly, and incorporating a similar feature would be a valuable addition to my own projects in the future. + +### Open-source libraries used + +- [Add any links to open-source libraries used in your project.] + +### Shout out +Instructor Sammy From 000dff7e667cd7b00599a24b87f61b1e114b498d Mon Sep 17 00:00:00 2001 From: zaraduruji <143117655+zaraduruji@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:31:33 -0700 Subject: [PATCH 5/5] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 37f929ec..92f149d5 100644 --- a/README.md +++ b/README.md @@ -44,8 +44,7 @@ Deployed Application (optional): [Flixster Deployed Site] https://flixster-start - [ ❌] The sidebar displays the user's favorited and watched movies ### Walkthrough Video - -https://www.loom.com/share/e8dce04c0a6f4880bbdc819e4c7a160b?sid=dc874578-555a-4fad-b506-c0ad1992b39f +
### Reflection