diff --git a/.env b/.env
new file mode 100644
index 00000000..0ee90d1d
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+VITE_API_KEY=4230b01717a30e627d99da67b6a68895
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/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..755a0a52 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,10 +1,30 @@
-import { useState } from 'react'
-import './App.css'
+import React, { useState } from 'react';
+
+import Header from './HomePageComponents/Header';
+import MovieCardsContainer from './HomePageComponents/MovieCardsContainer';
+import DropdownMenu from './HomePageComponents/Sort';
const App = () => {
-
-
-
+ const [page, setPage] = useState(1);
+ const [searchQuery, setSearchQuery] = useState('');
+ const [sortOption, setSortOption] = useState('');
+
+
+ const handleSearch = (query) => {
+ setSearchQuery(query);
+ setPage(1);
+ };
+
+ const handleSortChange = (option) => {
+ setSortOption(option);
+ };
+
+ 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..0d23ebd1
--- /dev/null
+++ b/src/HomePageComponents/Filter.jsx
@@ -0,0 +1,33 @@
+import React, { useState, useEffect } from 'react';
+
+const Filter = ({ onFilterChange }) => {
+ const [favorited, setFavorited] = useState(false);
+ const [watched, setWatched] = useState(false);
+
+ useEffect(() => {
+ onFilterChange({ favorited, watched });
+ }, [favorited, watched, onFilterChange]);
+
+ return (
+
+
+
+
+ );
+};
+
+export default Filter;
diff --git a/src/HomePageComponents/Header.jsx b/src/HomePageComponents/Header.jsx
new file mode 100644
index 00000000..57c6ee6e
--- /dev/null
+++ b/src/HomePageComponents/Header.jsx
@@ -0,0 +1,66 @@
+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);
+ }
+ };
+
+ return (
+
+ );
+}
+
+export default Header;
diff --git a/src/HomePageComponents/LoadMoreButton.jsx b/src/HomePageComponents/LoadMoreButton.jsx
new file mode 100644
index 00000000..d81c5645
--- /dev/null
+++ b/src/HomePageComponents/LoadMoreButton.jsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const LoadMoreButton = ({ onLoadMore }) => {
+ return (
+
+
+
+ );
+}
+
+export default LoadMoreButton;
diff --git a/src/HomePageComponents/MovieCard.jsx b/src/HomePageComponents/MovieCard.jsx
new file mode 100644
index 00000000..5ea09a9e
--- /dev/null
+++ b/src/HomePageComponents/MovieCard.jsx
@@ -0,0 +1,25 @@
+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, isFavorite, isWatched, onToggleFavorite, onToggleWatched }) => {
+ return (
+
+
{movie?.title}
+

+
+
+
+ Rating: {movie?.vote_average} / 10
+
+
+ );
+};
+
+export default MovieCard;
diff --git a/src/HomePageComponents/MovieCardsContainer.jsx b/src/HomePageComponents/MovieCardsContainer.jsx
new file mode 100644
index 00000000..87355b1d
--- /dev/null
+++ b/src/HomePageComponents/MovieCardsContainer.jsx
@@ -0,0 +1,112 @@
+import React, { useState, useEffect, useCallback } from 'react';
+import MovieCard from './MovieCard';
+import Filter from './Filter';
+import DropdownMenu from './Sort';
+
+const MovieCardsContainer = ({ searchQuery }) => {
+ const [allMovies, setAllMovies] = useState([]);
+ const [displayMovies, setDisplayMovies] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ 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;
+
+ 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';
+ let url = `${baseUrl}/discover/movie?language=en-US&page=${page}&api_key=${apiKey}`;
+
+ if (sortOption === 'alphabetical') {
+ url += '&sort_by=original_title.asc';
+ } else if (sortOption === 'release-date') {
+ url += '&sort_by=release_date.desc';
+ } else if (sortOption === 'rating') {
+ url += '&sort_by=vote_average.desc';
+ }
+
+ if (searchQuery) {
+ url = `${baseUrl}/search/movie?query=${encodeURIComponent(searchQuery)}&page=${page}&api_key=${apiKey}`;
+ }
+
+ console.log("Fetching movies with URL:", url);
+
+ try {
+ const response = await fetch(url);
+ const data = await response.json();
+ console.log("API response data:", data);
+ setAllMovies(data.results);
+ updateDisplayMovies(data.results);
+ } catch (error) {
+ console.error("Failed to fetch movies:", error);
+ }
+ }, [searchQuery, sortOption, updateDisplayMovies]);
+
+ const handleFilterChange = useCallback((settings) => {
+ console.log("Filter settings updated to:", settings);
+ setFilterSettings(settings);
+ }, []);
+
+ const toggleSet = useCallback((setId, movieId) => {
+ setId(prev => {
+ const newSet = new Set(prev);
+ if (newSet.has(movieId)) {
+ newSet.delete(movieId);
+ } 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;
+ setCurrentPage(nextPage);
+ fetchMovies(nextPage);
+ }, [currentPage, fetchMovies]);
+
+ const handleSortChange = useCallback((newSortOption) => {
+ console.log("Sort option changed to:", newSortOption);
+ setSortOption(newSortOption);
+ }, []);
+
+ return (
+
+
+
+ {displayMovies.map(movie => (
+ toggleSet(setFavorites, movie.id)}
+ onToggleWatched={() => toggleSet(setWatched, movie.id)}
+ />
+ ))}
+
+
+ );
+}
+
+export default MovieCardsContainer;
diff --git a/src/HomePageComponents/Sort.jsx b/src/HomePageComponents/Sort.jsx
new file mode 100644
index 00000000..26b2a039
--- /dev/null
+++ b/src/HomePageComponents/Sort.jsx
@@ -0,0 +1,24 @@
+import React, { useState, useEffect } from 'react';
+
+function DropdownMenu({ onSortChange }) {
+ const [selectedOption, setSelectedOption] = useState('');
+
+ const handleSelectChange = (event) => {
+ const newSortOption = event.target.value;
+ setSelectedOption(newSortOption);
+ onSortChange(newSortOption); // Notify the parent component of the change
+ };
+
+ 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;
+}