Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env
130 changes: 125 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,128 @@
# 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: **Carlos Escobar**

Currently, two official plugins are available:
Estimated time spent: 18 hours spent in total

- [@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: [Flixster Deployed Site](https://flixster-cr7f.onrender.com)

### Application Features

#### REQUIRED FEATURES

- [x] **Display Movies**
- [x] Users can view a list of current movies from The Movie Database API in a grid view.
- [x] Movie tiles should be reasonably sized (at least 6 playlists on your laptop when full screen; large enough that the playlist components detailed in the next feature are legible).
- [x] For each movie displayed, users can see the movie's:
- [x] Title
- [x] Poster image
- [x] Vote average
- [x] Users can load more current movies by clicking a button which adds more movies to the grid without reloading the entire page.
- [x] **Search Functionality**
- [x] Users can use a search bar to search for movies by title.
- [x] The search bar should include:
- [x] Text input field
- [x] Submit/Search button
- [x] Clear button
- [x] Movies with a title containing the search query in the text input field are displayed in a grid view when the user either:
- [x] Presses the Enter key
- [x] Clicks the Submit/Search button
- [x] Users can click the Clear button. When clicked:
- [x] Most recent search results are cleared from the text input field and the grid view and all current movies are displayed in a grid view
- [x] **Design Features**
- [x] Website implements all of the following accessibility features:
- [x] Semantic HTML
- [x] [Color contrast](https://webaim.org/resources/contrastchecker/)
- [x] Alt text for images
- [x] Website implements responsive web design.
- [x] Uses CSS Flexbox or CSS Grid
- [x] Movie tiles and images shrink/grow in response to window size
- [x] Users can click on a movie tile to view more details about a movie in a pop-up modal.
- [x] The pop-up window is centered in the screen and does not occupy the entire screen.
- [x] The pop-up window has a shadow to show that it is a pop-up and appears floating on the screen.
- [x] The backdrop of the pop-up appears darker or in a different shade than before. including:
- [x] The pop-up displays additional details about the moving including:
- [x] Runtime in minutes
- [x] Backdrop poster
- [x] Release date
- [x] Genres
- [x] An overview
- [x] Users can use a drop-down menu to sort movies.
- [x] Drop-down allows movies to be sorted by:
- [x] Title (alphabetic, A-Z)
- [x] Release date (chronologically, most recent to oldest)
- [x] Vote average (descending, highest to lowest)
- [x] When a sort option is clicked, movies display in a grid according to selected criterion.
- [x] Website displays:
- [x] Header section
- [x] Banner section
- [x] Search bar
- [x] Movie grid
- [x] Footer section
- [x] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: To ease the grading process, please use the [color contrast checker](https://webaim.org/resources/contrastchecker/) to demonstrate to the grading team that text and background colors on your website have appropriate contrast. The Contrast Ratio should be above 4.5:1 and should have a green box surrounding it.
- [x] **Deployment**
- [x] Website is deployed via Render.
- [x] **VIDEO WALKTHROUGH SPECIAL INSTRUCTIONS**: For ease of grading, please use the deployed version of your website when creating your walkthrough.

#### STRETCH FEATURES


- [x] **Embedded Movie Trailers**
- [x] Within the pop-up modal displaying a movie's details, the movie trailer is viewable.
- [x] When the trailer is clicked, users can play the movie trailer.
- [x] **Favorite Button**
- [x] For each movie displayed, users can favorite the movie.
- [x] There should be visual element (such as a heart icon) on each movie's tile to show whether or not the movie has been favorited.
- [x] If the movie is not favorited:
- [x] Clicking on the visual element should mark the movie as favorited
- [x] There should be visual feedback (such as the heart turning a different color) to show that the movie has been favorited by the user.
- [x] If the movie is already favorited:
- [x] Clicking on the visual element should mark the movie as *not* favorited.
- [x] There should be visual feedback (such as the heart turning a different color) to show that the movie has been unfavorited.
- [x] **Watched Checkbox**
- [x] For each movie displayed, users can mark the movie as watched.
- [x] There should be visual element (such as an eye icon) on each movie's tile to show whether or not the movie has been watched.
- [x] If the movie has not been watched:
- [x] Clicking on the visual element should mark the movie as watched
- [x] There should be visual feedback (such as the eye turning a different color) to show that the movie has been watched by the user.
- [x] If the movie is already watched:
- [x] Clicking on the visual element should mark the movie as *not* watched.
- [x] There should be visual feedback (such as the eye turning a different color) to show that the movie has not been watched.
- [x] **Sidebar**
- [x] The website includes a side navigation bar.
- [x] The sidebar has three pages:
- [x] Home
- [x] Favorites
- [x] Watched
- [x] The Home page displays all current movies in a grid view, the search bar, and the sort movies drop-down.
- [x] The Favorites page displays all favorited movies in a grid view.
- [x] The Watched page displays all watched movies in a grid view.

### Walkthrough Video

<div>
<a href="https://www.loom.com/share/f4bba1ed1ec64b80a91e72a3bb8e612c">
<img style="max-width:300px;" src="https://cdn.loom.com/sessions/thumbnails/f4bba1ed1ec64b80a91e72a3bb8e612c-44d85d6f509b8691-full-play.gif">
</a>
</div>

### 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 labs did prepare us for this project. An instance I can think of where it really helped was for the weather project where we had to display the different days in a card format. This was similar to the movie list and the card components.


* 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 improve the responsiveness of the page and make the UI nicer. After moving the sorting functionality to the sortMenu component, I lost the ability to sort the incoming movies whenever the 'load more' button was pressed, so that is also something I would love to bring back.


* 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?

The demo went great. I got to highlight how whenever one goes to the 'Favorites' or 'Watched' tab, the toolbar and load more button goes away. A peer of mine had the background of the modal set to the backdrop image of the movie which was really nice visually and something I would love to experiment with on the next project.


### Shout out

Shout out Noah Pyrzanowski
64 changes: 46 additions & 18 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,28 +1,56 @@
* {
margin: 0;
padding: 0;
}

.App {
text-align: center;
background-color: #2755AA;
display: flex;
flex-direction: row;
min-height: 100vh;
}

.App-header {
background-color: #282c34;
#title {
padding: .5vh 0vw;
margin-bottom: 1.5vh;
background-color: #8AAAE5;
}

#toolbar {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-evenly;
color: white;
padding: 20px;
flex-wrap: wrap;
justify-content: space-around;
}

@media (max-width: 600px) {
.movie-card {
width: 100%;
}
#search-helper-buttons {
display: flex;
}

.search-bar {
flex-direction: column;
gap: 10px;
}
.content-container {
display: flex;
flex-direction: column;
width: 85vw;
margin-left: 15vw;
}

.search-bar form {
flex-direction: column;
}
#load-more {
margin: 1vh auto 3vh;
border-radius: 5px;
color: black;
border: none;
font-size: 20px;
background-color: #2755AA;
text-decoration: underline;
}

#load-more:hover {
cursor: pointer;
}

footer {
text-align: center;
padding: 1vh;
margin-top: auto;
background-color: #8AAAE5;
}
141 changes: 138 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,147 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import './App.css'
import SearchForm from './components/SearchForm'
import SortMenu from './components/SortMenu'
import MovieList from './components/MovieList'
import Modal from './components/Modal'
import Sidebar from './components/Sidebar'

const App = () => {

const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState('');
const [page, setPage] = useState(1);
const [movieData, setMovieData] = useState([]);
const [totalPages, setTotalPages] = useState(0);
const [modalData, setModalData] = useState({});
const [favoritedMovies, setFavoritedMovies] = useState([]);
const [watchedMovies, setWatchedMovies] = useState([]);
const [activeView, setActiveView] = useState('home');

// Load API key from environment variable
const apiKey = import.meta.env.VITE_APP_API_KEY;

const fetchData = async () => {
try {
let response = null;
if (query) {
// Handle a query with spaces
const formattedQuery = query.split(' ').join('%20');
// Fetch data based on what user searched for
response = await fetch(`https://api.themoviedb.org/3/search/movie?api_key=${apiKey}&query=${formattedQuery}&include_adult=false&language=en-US&page=${page}`);
} else {
// Fetch from 'Now Playing'
response = await fetch(`https://api.themoviedb.org/3/movie/now_playing?api_key=${apiKey}&language=en-US&page=${page}`);
}
if (!response.ok) {
throw new Error(`Failed to fetch movie data: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Error fetching data: ', error);
}
};

// When query changes, fetch data starting at page 1
useEffect(() => {
setPage(1);
}, [query]);

// When user presses 'load more' or searches for a movie
useEffect(() => {
const fetchMovieData = async () => {
const data = await fetchData();
const movieInfoData = data.results;
setTotalPages(data.total_pages);

// Handle whether user pressed 'load more' or a new search was entered
if (page == 1) {
setMovieData(movieInfoData);
} else {
setMovieData(prev => [...prev, ...data.results]);
}
};
fetchMovieData();
}, [page, query]);

const handleQueryChange = (query) => {
setQuery(query);
};

// Clears the movie catalog back to top 20 now playing
const handleClear = () => {
setQuery('');
setPage(1);
};

const handlePageChange = () => {
setPage(prev => prev + 1);
};

// Callback function that updates modal data and opens modal
const updateModalData = (modalMovieInfo) => {
setModalData(modalMovieInfo);
setIsOpen(true);
};

// Handles whether to add or remove movie from Favorited/Watched lists
const updateMovies = (id, setMovies) => {
const movie = movieData.find(m => m.id == id);
setMovies(prev => {
return prev.includes(movie) ? prev.filter(m => m.id != id) : [...prev, movie];
})
};

const updateFavoritedMovies = (id) => {
updateMovies(id, setFavoritedMovies);
};

const updateWatchedMovies = (id) => {
updateMovies(id, setWatchedMovies);
};

const handleSort = (sortedMovieData) => {
setMovieData(sortedMovieData);
};

// Handles what movies to display as cards based on the active view
const moviesToDisplay = activeView == 'favorites' ? favoritedMovies : activeView == 'watched' ? watchedMovies : movieData;

return (
<div className="App">

<Sidebar setActiveView={setActiveView}/>
<div className="content-container">
<header>
<h1 id="title">Flixster &#x1F3A5;</h1>
</header>
<nav>
{/* Toolbar (search and sort) should only appear when in the home page */}
{activeView == 'home' && (
<div id="toolbar">
<SearchForm onQueryChange={handleQueryChange} onClear={handleClear}/>
<SortMenu movieData={movieData} onSort={handleSort}/>
</div>
)}
</nav>
<main>
<MovieList
movieData={moviesToDisplay}
favoritedMovies={favoritedMovies}
watchedMovies={watchedMovies}
updateModalData={updateModalData}
onButtonClick={{updateFavoritedMovies, updateWatchedMovies}}
/>
{/* 'Load more' button should only appear when in the home page
and when there are still more movies to retrieve*/}
{activeView == 'home' && page < totalPages && (
<button id="load-more" onClick={handlePageChange}>Load More</button>
)}
</main>
<Modal modalData={modalData} setIsOpen={setIsOpen} isOpen={isOpen}/>
<footer>2025 Flixter</footer>
</div>
</div>
)
}
};

export default App
13 changes: 13 additions & 0 deletions src/components/Button.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.feature-button {
border-radius: 0px;
background-color: white;
color: black;
border: none;
font-weight: lighter;
}

.feature-button:hover {
background-color: white;
color: black;
cursor: pointer;
}
Loading