From d3739c1d19706c91a0296d5fa5c7e53ebae8e51c Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Tue, 11 Jun 2024 20:30:21 -0700 Subject: [PATCH 1/8] Basic Wireframe Created --- key.env | 1 + src/App.css | 10 ++++++++ src/App.jsx | 22 +++++++++++++---- src/HomePageComponents/Filter.jsx | 12 ++++++++++ src/HomePageComponents/Header.jsx | 12 ++++++++++ src/HomePageComponents/LoadMoreButton.jsx | 12 ++++++++++ src/HomePageComponents/MovieCard.jsx | 16 +++++++++++++ .../MovieCardsContainer.jsx | 11 +++++++++ src/HomePageComponents/Sort.jsx | 24 +++++++++++++++++++ src/index.css | 15 ++++++++++++ 10 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 key.env create mode 100644 src/HomePageComponents/Filter.jsx create mode 100644 src/HomePageComponents/Header.jsx create mode 100644 src/HomePageComponents/LoadMoreButton.jsx create mode 100644 src/HomePageComponents/MovieCard.jsx create mode 100644 src/HomePageComponents/MovieCardsContainer.jsx create mode 100644 src/HomePageComponents/Sort.jsx diff --git a/key.env b/key.env new file mode 100644 index 00000000..0ee90d1d --- /dev/null +++ b/key.env @@ -0,0 +1 @@ +VITE_API_KEY=4230b01717a30e627d99da67b6a68895 diff --git a/src/App.css b/src/App.css index 0bf65669..be408e53 100644 --- a/src/App.css +++ b/src/App.css @@ -26,3 +26,13 @@ flex-direction: column; } } + +.Header { + border-style: dashed; + border-color: black; +} + +.MovieCard { + border-style: dashed; + border-color: black; +} diff --git a/src/App.jsx b/src/App.jsx index 48215b3f..884a29e0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,22 @@ -import { useState } from 'react' +// import { useState } from 'react' import './App.css' +import Filter from './HomePageComponents/Filter' +import Header from './HomePageComponents/Header' +import MovieCardsContainer from './HomePageComponents/MovieCardsContainer' +import Sort from './HomePageComponents/Sort' +import LoadMoreButton from './HomePageComponents/LoadMoreButton' const App = () => { -
- -
+ return ( + +
+
+ + + + +
+ ) } -export default App +export default App; diff --git a/src/HomePageComponents/Filter.jsx b/src/HomePageComponents/Filter.jsx new file mode 100644 index 00000000..7fcceabb --- /dev/null +++ b/src/HomePageComponents/Filter.jsx @@ -0,0 +1,12 @@ +import { useState } from 'react' + +const Filter = () => { + return ( + +
+ Filter +
+ ) +} + +export default Filter diff --git a/src/HomePageComponents/Header.jsx b/src/HomePageComponents/Header.jsx new file mode 100644 index 00000000..dc8f4bf7 --- /dev/null +++ b/src/HomePageComponents/Header.jsx @@ -0,0 +1,12 @@ +import { useState } from 'react' + +const Header = () => { + return ( +
+

Flixster

+ +
+ ) +} + +export default Header diff --git a/src/HomePageComponents/LoadMoreButton.jsx b/src/HomePageComponents/LoadMoreButton.jsx new file mode 100644 index 00000000..09c35174 --- /dev/null +++ b/src/HomePageComponents/LoadMoreButton.jsx @@ -0,0 +1,12 @@ +import { useState } from 'react' + +const LoadMoreButton = () => { + return ( + +
+ +
+ ) +} + +export default LoadMoreButton; diff --git a/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx new file mode 100644 index 00000000..dcf2448e --- /dev/null +++ b/src/HomePageComponents/MovieCard.jsx @@ -0,0 +1,16 @@ +import { useState } from 'react' + +const MovieCard = () => { + return ( + +
+ +
+

Movie Title

+

Movie Rating

+
+
+ ) +} + +export default MovieCard; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx new file mode 100644 index 00000000..fbe7476b --- /dev/null +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -0,0 +1,11 @@ +import MovieCard from './MovieCard'; + +const MovieCardsContainer = () => { + return ( +
+ +
+ ) +} + +export default MovieCardsContainer; diff --git a/src/HomePageComponents/Sort.jsx b/src/HomePageComponents/Sort.jsx new file mode 100644 index 00000000..54281100 --- /dev/null +++ b/src/HomePageComponents/Sort.jsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react'; + +function DropdownMenu() { + // State to keep track of the selected option + const [selectedOption, setSelectedOption] = useState(''); + + // Function to handle when an option is selected + const handleSelectChange = (event) => { + setSelectedOption(event.target.value); + }; + + return ( +
+ +
+ ); +} + +export default DropdownMenu; diff --git a/src/index.css b/src/index.css index e1faed1a..8bc39d7d 100644 --- a/src/index.css +++ b/src/index.css @@ -17,3 +17,18 @@ button:hover { background-color: #777; color: white; } + +header { + border-style: dotted; + border-color: black; +} + +.movieContainer { + border-style: dotted; + border-color: black; +} + +.movieCard { + border-style: dotted; + border-color: black; +} From 82840e373674fa6b2ce8921be0a62fdc9023d65c Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 10:46:07 -0700 Subject: [PATCH 2/8] Key loaded data --- key.env => .env | 0 src/HomePageComponents/MovieCard.jsx | 13 ++++--- .../MovieCardsContainer.jsx | 35 +++++++++++++++++-- 3 files changed, 39 insertions(+), 9 deletions(-) rename key.env => .env (100%) diff --git a/key.env b/.env similarity index 100% rename from key.env rename to .env diff --git a/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx index dcf2448e..69df67e6 100644 --- a/src/HomePageComponents/MovieCard.jsx +++ b/src/HomePageComponents/MovieCard.jsx @@ -1,16 +1,15 @@ -import { useState } from 'react' +import React from 'react'; -const MovieCard = () => { +const MovieCard = ({ movie }) => { return ( -
- + {movie.title}
-

Movie Title

-

Movie Rating

+

{movie.title}

+

Rating: {movie.vote_average}

- ) + ); } export default MovieCard; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index fbe7476b..6ec2bbaa 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -1,11 +1,42 @@ +import React, { useState, useEffect } from 'react'; import MovieCard from './MovieCard'; const MovieCardsContainer = () => { + const [movies, setMovies] = useState([]); + + useEffect(() => { + const fetchMovies = async () => { + try { + const apiKey = import.meta.env.VITE_API_KEY; + if (!apiKey) { + console.error('API key is undefined. Check .env file and make sure it is loaded correctly.'); + return; + } + const response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const data = await response.json(); + setMovies(data.results || []); + } catch (error) { + console.error('Error fetching data: ', error); + } + }; + + fetchMovies(); + }, []); + return (
- + {movies.length > 0 ? ( + movies.map(movie => ( + + )) + ) : ( +

No movies found or data is still loading.

+ )}
- ) + ); } export default MovieCardsContainer; From 989a294ad65bc3f5f4d9dafd8967f37b97769bb2 Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 11:22:29 -0700 Subject: [PATCH 3/8] Load More button working --- src/App.jsx | 1 - src/HomePageComponents/LoadMoreButton.jsx | 9 ++++----- src/HomePageComponents/MovieCardsContainer.jsx | 13 ++++++++++--- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 884a29e0..9f5cd865 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,7 +14,6 @@ const App = () => { - ) } diff --git a/src/HomePageComponents/LoadMoreButton.jsx b/src/HomePageComponents/LoadMoreButton.jsx index 09c35174..d81c5645 100644 --- a/src/HomePageComponents/LoadMoreButton.jsx +++ b/src/HomePageComponents/LoadMoreButton.jsx @@ -1,12 +1,11 @@ -import { useState } from 'react' +import React from 'react'; -const LoadMoreButton = () => { +const LoadMoreButton = ({ onLoadMore }) => { return ( -
- +
- ) + ); } export default LoadMoreButton; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index 6ec2bbaa..69ed1395 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -1,8 +1,10 @@ import React, { useState, useEffect } from 'react'; import MovieCard from './MovieCard'; +import LoadMoreButton from './LoadMoreButton'; // Import LoadMoreButton const MovieCardsContainer = () => { const [movies, setMovies] = useState([]); + const [page, setPage] = useState(1); // State to keep track of the current page useEffect(() => { const fetchMovies = async () => { @@ -12,19 +14,23 @@ const MovieCardsContainer = () => { console.error('API key is undefined. Check .env file and make sure it is loaded correctly.'); return; } - const response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}`); + const response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&api_key=${apiKey}`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); - setMovies(data.results || []); + setMovies(prevMovies => [...prevMovies, ...data.results]); // Append new movies to the existing list } catch (error) { console.error('Error fetching data: ', error); } }; fetchMovies(); - }, []); + }, [page]); // Depend on page state + + const handleLoadMore = () => { + setPage(prevPage => prevPage + 1); // Increment page number + }; return (
@@ -35,6 +41,7 @@ const MovieCardsContainer = () => { ) : (

No movies found or data is still loading.

)} +
); } From a9b6c36e33aa9121d2d2b476752093f4b9b080c8 Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 13:47:19 -0700 Subject: [PATCH 4/8] soritng and search bar --- src/App.jsx | 42 +++++++---- src/HomePageComponents/Filter.jsx | 2 + src/HomePageComponents/Header.jsx | 64 +++++++++++++++-- .../MovieCardsContainer.jsx | 71 +++++++++++-------- src/HomePageComponents/Sort.jsx | 15 ++-- 5 files changed, 140 insertions(+), 54 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 9f5cd865..4e8512d8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,21 +1,37 @@ -// import { useState } from 'react' -import './App.css' -import Filter from './HomePageComponents/Filter' -import Header from './HomePageComponents/Header' -import MovieCardsContainer from './HomePageComponents/MovieCardsContainer' -import Sort from './HomePageComponents/Sort' -import LoadMoreButton from './HomePageComponents/LoadMoreButton' +import React, { useState } from 'react'; +import './App.css'; +import Filter from './HomePageComponents/Filter'; +import Header from './HomePageComponents/Header'; +import MovieCardsContainer from './HomePageComponents/MovieCardsContainer'; +import DropdownMenu from './HomePageComponents/Sort'; +import LoadMoreButton from './HomePageComponents/LoadMoreButton'; const App = () => { - return ( + const [page, setPage] = useState(1); + const [searchQuery, setSearchQuery] = useState(''); + const [sortOption, setSortOption] = useState(''); + + const handleLoadMore = () => { + setPage(prevPage => prevPage + 1); + }; + + const handleSearch = (query) => { + setSearchQuery(query); + setPage(1); + }; + const handleSortChange = (option) => { + setSortOption(option); + }; + + return (
-
- - - +
+ + +
- ) + ); } export default App; diff --git a/src/HomePageComponents/Filter.jsx b/src/HomePageComponents/Filter.jsx index 7fcceabb..944dec0d 100644 --- a/src/HomePageComponents/Filter.jsx +++ b/src/HomePageComponents/Filter.jsx @@ -10,3 +10,5 @@ const Filter = () => { } export default Filter + + diff --git a/src/HomePageComponents/Header.jsx b/src/HomePageComponents/Header.jsx index dc8f4bf7..57c6ee6e 100644 --- a/src/HomePageComponents/Header.jsx +++ b/src/HomePageComponents/Header.jsx @@ -1,12 +1,66 @@ -import { useState } from 'react' +import React, { useState, useEffect } from 'react'; + +const Header = ({ onSearch }) => { + const [searchQuery, setSearchQuery] = useState(''); + const [suggestions, setSuggestions] = useState([]); + + const handleInputChange = async (event) => { + const query = event.target.value; + setSearchQuery(query); + + if (query.length > 2) { + const apiKey = import.meta.env.VITE_API_KEY; + const url = `https://api.themoviedb.org/3/search/movie?query=${encodeURIComponent(query)}&api_key=${apiKey}`; + const response = await fetch(url); + const data = await response.json(); + setSuggestions(data.results); + } else { + setSuggestions([]); + if (query.length === 0) { + onSearch(''); // Trigger fetching "now playing" movies when search is cleared + } + } + }; + + const handleSearch = (query) => { + onSearch(query); + setSuggestions([]); // Clear suggestions after search + }; + + const handleKeyPress = (event) => { + if (event.key === 'Enter') { + handleSearch(searchQuery); + } + }; -const Header = () => { return (

Flixster

- + + + {suggestions.length > 0 && ( +
    + {suggestions.map((movie, index) => ( +
  • { + setSearchQuery(movie.title); + handleSearch(movie.title); // Pass the movie title directly to handleSearch + }}> + {movie.title} + {movie.title} +
  • + ))} +
+ )}
- ) + ); } -export default Header +export default Header; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index 69ed1395..bd53caae 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -1,47 +1,62 @@ import React, { useState, useEffect } from 'react'; import MovieCard from './MovieCard'; -import LoadMoreButton from './LoadMoreButton'; // Import LoadMoreButton -const MovieCardsContainer = () => { - const [movies, setMovies] = useState([]); - const [page, setPage] = useState(1); // State to keep track of the current page +const MovieCardsContainer = ({ searchQuery, sortOption }) => { + const [allMovies, setAllMovies] = useState([]); + const [displayMovies, setDisplayMovies] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 20; // Number of items you want per page useEffect(() => { - const fetchMovies = async () => { - try { - const apiKey = import.meta.env.VITE_API_KEY; - if (!apiKey) { - console.error('API key is undefined. Check .env file and make sure it is loaded correctly.'); - return; - } - const response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?language=en-US&page=${page}&api_key=${apiKey}`); - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + const fetchAllMovies = async () => { + let fetchedMovies = []; + let page = 1; + let hasMore = true; + const apiKey = import.meta.env.VITE_API_KEY; + const baseUrl = 'https://api.themoviedb.org/3'; + while (hasMore) { + let url = `${baseUrl}/movie/now_playing?language=en-US&page=${page}&api_key=${apiKey}`; + if (searchQuery) { + url = `${baseUrl}/search/movie?query=${encodeURIComponent(searchQuery)}&page=${page}&api_key=${apiKey}`; } + const response = await fetch(url); const data = await response.json(); - setMovies(prevMovies => [...prevMovies, ...data.results]); // Append new movies to the existing list - } catch (error) { - console.error('Error fetching data: ', error); + const newMovies = data.results.filter(movie => !fetchedMovies.some(m => m.id === movie.id)); // Filter out duplicates + fetchedMovies = [...fetchedMovies, ...newMovies]; + hasMore = data.page < data.total_pages; // Check if there are more pages + page += 1; } + setAllMovies(fetchedMovies); + setDisplayMovies(applySorting(fetchedMovies, sortOption).slice(0, itemsPerPage)); }; - fetchMovies(); - }, [page]); // Depend on page state + fetchAllMovies(); + }, [searchQuery, sortOption]); + + const applySorting = (movies, sortOption) => { + if (sortOption === 'alphabetical') { + return [...movies].sort((a, b) => a.title.localeCompare(b.title)); + } else if (sortOption === 'release-date') { + return [...movies].sort((a, b) => new Date(b.release_date) - new Date(a.release_date)); + } else if (sortOption === 'rating') { + return [...movies].sort((a, b) => b.vote_average - a.vote_average); + } + return movies; + }; const handleLoadMore = () => { - setPage(prevPage => prevPage + 1); // Increment page number + const nextPage = currentPage + 1; + setCurrentPage(nextPage); + const nextItems = applySorting(allMovies, sortOption).slice(0, nextPage * itemsPerPage); + setDisplayMovies(nextItems); }; return (
- {movies.length > 0 ? ( - movies.map(movie => ( - - )) - ) : ( -

No movies found or data is still loading.

- )} - + {displayMovies.map(movie => ( + + ))} +
); } diff --git a/src/HomePageComponents/Sort.jsx b/src/HomePageComponents/Sort.jsx index 54281100..b85f4769 100644 --- a/src/HomePageComponents/Sort.jsx +++ b/src/HomePageComponents/Sort.jsx @@ -1,21 +1,20 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; -function DropdownMenu() { - // State to keep track of the selected option +function DropdownMenu({ onSortChange }) { const [selectedOption, setSelectedOption] = useState(''); - // Function to handle when an option is selected const handleSelectChange = (event) => { setSelectedOption(event.target.value); + onSortChange(event.target.value); // Pass the selected option back to the parent }; return (
); From c1d7c1602c55cf8a2fd4e4b26bd020ceb693b41a Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 14:19:52 -0700 Subject: [PATCH 5/8] watched and checked boxes added --- package-lock.json | 66 +++++++++++++++++-- package.json | 4 ++ src/HomePageComponents/MovieCard.jsx | 21 ++++-- .../MovieCardsContainer.jsx | 37 ++++++++++- 4 files changed, 115 insertions(+), 13 deletions(-) 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/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx index 69df67e6..1f67d90f 100644 --- a/src/HomePageComponents/MovieCard.jsx +++ b/src/HomePageComponents/MovieCard.jsx @@ -1,15 +1,22 @@ import React from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faHeart as farHeart } from '@fortawesome/free-regular-svg-icons'; +import { faHeart as fasHeart } from '@fortawesome/free-solid-svg-icons'; -const MovieCard = ({ movie }) => { +const MovieCard = ({ movie, isFavorite, isWatched, onToggleFavorite, onToggleWatched }) => { return (
- {movie.title} -
-

{movie.title}

-

Rating: {movie.vote_average}

-
+

{movie.title}

+ {movie.title} + +
); -} +}; export default MovieCard; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index bd53caae..5edbf13c 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -5,7 +5,9 @@ const MovieCardsContainer = ({ searchQuery, sortOption }) => { const [allMovies, setAllMovies] = useState([]); const [displayMovies, setDisplayMovies] = useState([]); const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 20; // Number of items you want per page + const [favorites, setFavorites] = useState(new Set()); // State for favorited movies + const [watched, setWatched] = useState(new Set()); // State for watched movies + const itemsPerPage = 20; // Number of items per page useEffect(() => { const fetchAllMovies = async () => { @@ -44,6 +46,30 @@ const MovieCardsContainer = ({ searchQuery, sortOption }) => { return movies; }; + const toggleFavorite = (movieId) => { + setFavorites(prev => { + const newFavorites = new Set(prev); + if (newFavorites.has(movieId)) { + newFavorites.delete(movieId); + } else { + newFavorites.add(movieId); + } + return new Set(newFavorites); + }); + }; + + const toggleWatched = (movieId) => { + setWatched(prev => { + const newWatched = new Set(prev); + if (newWatched.has(movieId)) { + newWatched.delete(movieId); + } else { + newWatched.add(movieId); + } + return new Set(newWatched); + }); + }; + const handleLoadMore = () => { const nextPage = currentPage + 1; setCurrentPage(nextPage); @@ -54,7 +80,14 @@ const MovieCardsContainer = ({ searchQuery, sortOption }) => { return (
{displayMovies.map(movie => ( - + toggleFavorite(movie.id)} + onToggleWatched={() => toggleWatched(movie.id)} + /> ))}
From 63ffaf827ae5db0be2b8cc1a7cc5837b94eeeb4a Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 20:05:39 -0700 Subject: [PATCH 6/8] sort now fetching from the API --- src/App.jsx | 9 +- src/HomePageComponents/Filter.jsx | 37 +++-- src/HomePageComponents/MovieCard.jsx | 3 + .../MovieCardsContainer.jsx | 135 ++++++++++-------- src/HomePageComponents/Sort.jsx | 5 +- 5 files changed, 109 insertions(+), 80 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 4e8512d8..755a0a52 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,19 +1,14 @@ import React, { useState } from 'react'; -import './App.css'; -import Filter from './HomePageComponents/Filter'; + import Header from './HomePageComponents/Header'; import MovieCardsContainer from './HomePageComponents/MovieCardsContainer'; import DropdownMenu from './HomePageComponents/Sort'; -import LoadMoreButton from './HomePageComponents/LoadMoreButton'; const App = () => { const [page, setPage] = useState(1); const [searchQuery, setSearchQuery] = useState(''); const [sortOption, setSortOption] = useState(''); - const handleLoadMore = () => { - setPage(prevPage => prevPage + 1); - }; const handleSearch = (query) => { setSearchQuery(query); @@ -27,8 +22,6 @@ const App = () => { return (
- -
); diff --git a/src/HomePageComponents/Filter.jsx b/src/HomePageComponents/Filter.jsx index 944dec0d..b8693e2a 100644 --- a/src/HomePageComponents/Filter.jsx +++ b/src/HomePageComponents/Filter.jsx @@ -1,14 +1,31 @@ -import { useState } from 'react' +import React, { useState, useEffect } from 'react'; -const Filter = () => { - return ( - -
- Filter -
- ) -} +const Filter = ({ onFilterChange }) => { + const [favorited, setFavorited] = useState(false); + const [watched, setWatched] = useState(false); -export default Filter + const handleFilterUpdate = () => { + onFilterChange({ favorited, watched }); + }; + return ( +
+ + +
+ ); +}; +export default Filter; diff --git a/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx index 1f67d90f..1e4fe863 100644 --- a/src/HomePageComponents/MovieCard.jsx +++ b/src/HomePageComponents/MovieCard.jsx @@ -8,6 +8,9 @@ const MovieCard = ({ movie, isFavorite, isWatched, onToggleFavorite, onToggleWat

{movie.title}

{movie.title} +
+ Rating: {movie.vote_average} / 10 +
diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index 5edbf13c..056e0c87 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -1,92 +1,107 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import MovieCard from './MovieCard'; +import Filter from './Filter'; +import DropdownMenu from './Sort'; -const MovieCardsContainer = ({ searchQuery, sortOption }) => { +const MovieCardsContainer = ({ searchQuery }) => { const [allMovies, setAllMovies] = useState([]); const [displayMovies, setDisplayMovies] = useState([]); const [currentPage, setCurrentPage] = useState(1); - const [favorites, setFavorites] = useState(new Set()); // State for favorited movies - const [watched, setWatched] = useState(new Set()); // State for watched movies - const itemsPerPage = 20; // Number of items per page + const [favorites, setFavorites] = useState(new Set()); + const [watched, setWatched] = useState(new Set()); + const [filterSettings, setFilterSettings] = useState({ favorited: false, watched: false }); + const [sortOption, setSortOption] = useState(''); + const itemsPerPage = 20; useEffect(() => { - const fetchAllMovies = async () => { - let fetchedMovies = []; - let page = 1; - let hasMore = true; - const apiKey = import.meta.env.VITE_API_KEY; - const baseUrl = 'https://api.themoviedb.org/3'; - while (hasMore) { - let url = `${baseUrl}/movie/now_playing?language=en-US&page=${page}&api_key=${apiKey}`; - if (searchQuery) { - url = `${baseUrl}/search/movie?query=${encodeURIComponent(searchQuery)}&page=${page}&api_key=${apiKey}`; - } - const response = await fetch(url); - const data = await response.json(); - const newMovies = data.results.filter(movie => !fetchedMovies.some(m => m.id === movie.id)); // Filter out duplicates - fetchedMovies = [...fetchedMovies, ...newMovies]; - hasMore = data.page < data.total_pages; // Check if there are more pages - page += 1; - } - setAllMovies(fetchedMovies); - setDisplayMovies(applySorting(fetchedMovies, sortOption).slice(0, itemsPerPage)); - }; - - fetchAllMovies(); + fetchMovies(1); }, [searchQuery, sortOption]); - const applySorting = (movies, sortOption) => { + const fetchMovies = useCallback(async (page) => { + const apiKey = import.meta.env.VITE_API_KEY; + const baseUrl = 'https://api.themoviedb.org/3'; + let url = `${baseUrl}/discover/movie?language=en-US&page=${page}&api_key=${apiKey}`; + if (sortOption === 'alphabetical') { - return [...movies].sort((a, b) => a.title.localeCompare(b.title)); + url += '&sort_by=original_title.asc'; } else if (sortOption === 'release-date') { - return [...movies].sort((a, b) => new Date(b.release_date) - new Date(a.release_date)); + url += '&sort_by=release_date.desc'; } else if (sortOption === 'rating') { - return [...movies].sort((a, b) => b.vote_average - a.vote_average); + url += '&sort_by=vote_average.desc'; } - return movies; - }; - - const toggleFavorite = (movieId) => { - setFavorites(prev => { - const newFavorites = new Set(prev); - if (newFavorites.has(movieId)) { - newFavorites.delete(movieId); - } else { - newFavorites.add(movieId); - } - return new Set(newFavorites); - }); - }; - const toggleWatched = (movieId) => { - setWatched(prev => { - const newWatched = new Set(prev); - if (newWatched.has(movieId)) { - newWatched.delete(movieId); + if (searchQuery) { + url += `&query=${encodeURIComponent(searchQuery)}`; + } + + // Debugging output to check the final URL + console.log("Fetching movies with URL:", url); + + try { + const response = await fetch(url); + const data = await response.json(); + + // Debugging output to check the data returned from the API + console.log("API response data:", data); + + setAllMovies(data.results); + updateDisplayMovies(data.results); + } catch (error) { + console.error("Failed to fetch movies:", error); + } +}, [searchQuery, sortOption]); + +const updateDisplayMovies = useCallback((movies) => { + let filteredMovies = movies.filter(movie => + (!filterSettings.favorited || favorites.has(movie.id)) && + (!filterSettings.watched || watched.has(movie.id)) + ); + + console.log("Filtered movies:", filteredMovies); // Debugging output + + setDisplayMovies(filteredMovies.slice(0, currentPage * itemsPerPage)); +}, [favorites, watched, filterSettings, currentPage]); + + const handleFilterChange = useCallback((settings) => { + setFilterSettings(settings); + updateDisplayMovies(allMovies); + }, [allMovies, updateDisplayMovies]); + + const toggleSet = useCallback((setId, movieId) => { + setId(prev => { + const newSet = new Set(prev); + if (newSet.has(movieId)) { + newSet.delete(movieId); } else { - newWatched.add(movieId); + newSet.add(movieId); } - return new Set(newWatched); + return new Set(newSet); }); - }; + }, []); - const handleLoadMore = () => { + const handleLoadMore = useCallback(() => { const nextPage = currentPage + 1; setCurrentPage(nextPage); - const nextItems = applySorting(allMovies, sortOption).slice(0, nextPage * itemsPerPage); - setDisplayMovies(nextItems); - }; + fetchMovies(nextPage); + }, [currentPage, fetchMovies]); + + const handleSortChange = useCallback((newSortOption) => { + console.log("Sort option changed to:", newSortOption); + setSortOption(newSortOption); + }, []); return (
+ + {displayMovies.map(movie => ( toggleFavorite(movie.id)} - onToggleWatched={() => toggleWatched(movie.id)} + onToggleFavorite={() => toggleSet(setFavorites, movie.id)} + onToggleWatched={() => toggleSet(setWatched, movie.id)} /> ))} diff --git a/src/HomePageComponents/Sort.jsx b/src/HomePageComponents/Sort.jsx index b85f4769..26b2a039 100644 --- a/src/HomePageComponents/Sort.jsx +++ b/src/HomePageComponents/Sort.jsx @@ -4,8 +4,9 @@ function DropdownMenu({ onSortChange }) { const [selectedOption, setSelectedOption] = useState(''); const handleSelectChange = (event) => { - setSelectedOption(event.target.value); - onSortChange(event.target.value); // Pass the selected option back to the parent + const newSortOption = event.target.value; + setSelectedOption(newSortOption); + onSortChange(newSortOption); // Notify the parent component of the change }; return ( From 616e82216faf85f5b8bd12a73e706602f9b2beed Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Wed, 12 Jun 2024 20:59:19 -0700 Subject: [PATCH 7/8] filter working --- src/HomePageComponents/Filter.jsx | 26 +++++++------ src/HomePageComponents/MovieCard.jsx | 20 +++++----- .../MovieCardsContainer.jsx | 38 +++++++++---------- 3 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/HomePageComponents/Filter.jsx b/src/HomePageComponents/Filter.jsx index b8693e2a..0d23ebd1 100644 --- a/src/HomePageComponents/Filter.jsx +++ b/src/HomePageComponents/Filter.jsx @@ -4,25 +4,27 @@ const Filter = ({ onFilterChange }) => { const [favorited, setFavorited] = useState(false); const [watched, setWatched] = useState(false); - const handleFilterUpdate = () => { + useEffect(() => { onFilterChange({ favorited, watched }); - }; + }, [favorited, watched, onFilterChange]); return (
); diff --git a/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx index 1e4fe863..5ea09a9e 100644 --- a/src/HomePageComponents/MovieCard.jsx +++ b/src/HomePageComponents/MovieCard.jsx @@ -6,18 +6,18 @@ import { faHeart as fasHeart } from '@fortawesome/free-solid-svg-icons'; const MovieCard = ({ movie, isFavorite, isWatched, onToggleFavorite, onToggleWatched }) => { return (
-

{movie.title}

- {movie.title} +

{movie?.title}

+ {movie?.title}
- Rating: {movie.vote_average} / 10 + + + Rating: {movie?.vote_average} / 10
- -
); }; diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index 056e0c87..272d297d 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -13,10 +13,23 @@ const MovieCardsContainer = ({ searchQuery }) => { const [sortOption, setSortOption] = useState(''); const itemsPerPage = 20; + const updateDisplayMovies = useCallback((movies) => { + let filteredMovies = movies.filter(movie => + (!filterSettings.favorited || favorites.has(movie.id)) && + (!filterSettings.watched || watched.has(movie.id)) + ); + console.log("Filtered movies after applying filters:", filteredMovies); + setDisplayMovies(filteredMovies.slice(0, currentPage * itemsPerPage)); + }, [favorites, watched, filterSettings, currentPage]); + useEffect(() => { fetchMovies(1); }, [searchQuery, sortOption]); + useEffect(() => { + updateDisplayMovies(allMovies); + }, [filterSettings, allMovies, updateDisplayMovies]); + const fetchMovies = useCallback(async (page) => { const apiKey = import.meta.env.VITE_API_KEY; const baseUrl = 'https://api.themoviedb.org/3'; @@ -34,38 +47,23 @@ const MovieCardsContainer = ({ searchQuery }) => { url += `&query=${encodeURIComponent(searchQuery)}`; } - // Debugging output to check the final URL console.log("Fetching movies with URL:", url); try { const response = await fetch(url); const data = await response.json(); - - // Debugging output to check the data returned from the API console.log("API response data:", data); - setAllMovies(data.results); updateDisplayMovies(data.results); } catch (error) { console.error("Failed to fetch movies:", error); } -}, [searchQuery, sortOption]); - -const updateDisplayMovies = useCallback((movies) => { - let filteredMovies = movies.filter(movie => - (!filterSettings.favorited || favorites.has(movie.id)) && - (!filterSettings.watched || watched.has(movie.id)) - ); - - console.log("Filtered movies:", filteredMovies); // Debugging output - - setDisplayMovies(filteredMovies.slice(0, currentPage * itemsPerPage)); -}, [favorites, watched, filterSettings, currentPage]); + }, [searchQuery, sortOption, updateDisplayMovies]); const handleFilterChange = useCallback((settings) => { + console.log("Filter settings updated to:", settings); setFilterSettings(settings); - updateDisplayMovies(allMovies); - }, [allMovies, updateDisplayMovies]); + }, []); const toggleSet = useCallback((setId, movieId) => { setId(prev => { @@ -75,9 +73,11 @@ const updateDisplayMovies = useCallback((movies) => { } else { newSet.add(movieId); } + console.log(`Toggled ${setId === setFavorites ? 'favorite' : 'watched'} for movie ID: ${movieId}`); return new Set(newSet); }); - }, []); + updateDisplayMovies(allMovies); + }, [allMovies, updateDisplayMovies]); const handleLoadMore = useCallback(() => { const nextPage = currentPage + 1; From 1261a59eefcc294f020ff022a383557a8c142211 Mon Sep 17 00:00:00 2001 From: Marcela Billingslea Durini Date: Thu, 13 Jun 2024 07:33:50 -0700 Subject: [PATCH 8/8] nav bar fixed --- src/HomePageComponents/MovieCardsContainer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx index 272d297d..87355b1d 100644 --- a/src/HomePageComponents/MovieCardsContainer.jsx +++ b/src/HomePageComponents/MovieCardsContainer.jsx @@ -44,7 +44,7 @@ const MovieCardsContainer = ({ searchQuery }) => { } if (searchQuery) { - url += `&query=${encodeURIComponent(searchQuery)}`; + url = `${baseUrl}/search/movie?query=${encodeURIComponent(searchQuery)}&page=${page}&api_key=${apiKey}`; } console.log("Fetching movies with URL:", url);