Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
27 changes: 20 additions & 7 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.App {
text-align: center;
/* width: 70%; */
display: flex;
flex-direction: column;
min-height: 100vh;
}

.App-header {
Expand All @@ -11,12 +13,18 @@
justify-content: space-between;
color: white;
padding: 1.5rem;

position: fixed;
width: 100%;
z-index: 1000;
}

.App-main {
flex: 1;
margin-left: 200px; /* Match sidebar width */
padding-top: 80px; /* Match header height */
transition: margin-left 0.3s ease;
}

.App-footer {
background-color: #282c34;
display: flex;
Expand All @@ -25,12 +33,8 @@
justify-content: center;
color: white;
padding: 1.5rem;

/* position: fixed; */
width: 100%;
bottom: 0;
right: 0;
z-index: 1000;
margin-left: 200px; /* Match sidebar width */
}

@media (max-width: 900px) {
Expand All @@ -54,3 +58,12 @@
justify-content: center;
}
}
@media (max-width: 768px) {
.App-main {
margin-left: 60px; /* Match collapsed sidebar width */
}

.App-footer {
margin-left: 60px; /* Match collapsed sidebar width */
}
}
98 changes: 92 additions & 6 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,40 @@
import { useState } from 'react'
import { useState, useEffect } from 'react'
import './App.css'
import MovieList from './MovieList'
import SearchForm from './SearchForm'
import SortForm from './SortForm'
import SideBar from './SideBar'

const App = () => {
const [searchQuery, setSearchQuery] = useState('');
const [view, setView] = useState('nowPlaying'); // 'nowPlaying' or 'search'
const [sortBy, setSortBy] = useState('default');
const [currentPage, setCurrentPage] = useState('home');
const [favoriteMovies, setFavoriteMovies] = useState([]);
const [watchedMovies, setWatchedMovies] = useState([]);

// Load favorites and watched movies from localStorage on initial render
useEffect(() => {
const storedFavorites = localStorage.getItem('favoriteMovies');
const storedWatched = localStorage.getItem('watchedMovies');

if (storedFavorites) {
setFavoriteMovies(JSON.parse(storedFavorites));
}

if (storedWatched) {
setWatchedMovies(JSON.parse(storedWatched));
}
}, []);

// Save to localStorage whenever favorites or watched movies change
useEffect(() => {
localStorage.setItem('favoriteMovies', JSON.stringify(favoriteMovies));
}, [favoriteMovies]);

useEffect(() => {
localStorage.setItem('watchedMovies', JSON.stringify(watchedMovies));
}, [watchedMovies]);

const handleSearch = (query) => {
setSearchQuery(query);
Expand All @@ -27,17 +54,76 @@ const App = () => {
setSortBy(sortOption);
};

const handlePageChange = (page) => {
setCurrentPage(page);
// Reset to nowPlaying view when changing pages
if (page === 'home') {
setView('nowPlaying');
}
};

const toggleFavorite = (movie) => {
setFavoriteMovies(prevFavorites => {
const isAlreadyFavorite = prevFavorites.some(favMovie => favMovie.id === movie.id);

if (isAlreadyFavorite) {
return prevFavorites.filter(favMovie => favMovie.id !== movie.id);
} else {
return [...prevFavorites, movie];
}
});
};

const toggleWatched = (movie) => {
setWatchedMovies(prevWatched => {
const isAlreadyWatched = prevWatched.some(watchedMovie => watchedMovie.id === movie.id);

if (isAlreadyWatched) {
return prevWatched.filter(watchedMovie => watchedMovie.id !== movie.id);
} else {
return [...prevWatched, movie];
}
});
};

const isMovieFavorite = (movieId) => {
return favoriteMovies.some(movie => movie.id === movieId);
};

const isMovieWatched = (movieId) => {
return watchedMovies.some(movie => movie.id === movieId);
};

return (
<div className="App">
<header className="App-header">
<h1>Flixster</h1>
<SearchForm onSearch={handleSearch} onClear={handleClearSearch} />
<SortForm onSortChange={handleSortChange} />
{currentPage === 'home' && (
<>
<SearchForm onSearch={handleSearch} onClear={handleClearSearch} />
<SortForm onSortChange={handleSortChange} />
</>
)}
</header>
<sidebar className='App-sidebar'>

</sidebar>
<MovieList searchQuery={searchQuery} view={view} sortBy={sortBy} onViewToggle={handleViewToggle}/>
<SideBar currentPage={currentPage} onPageChange={handlePageChange} />

<main className="App-main">
<MovieList
searchQuery={searchQuery}
view={view}
sortBy={sortBy}
onViewToggle={handleViewToggle}
currentPage={currentPage}
favoriteMovies={favoriteMovies}
watchedMovies={watchedMovies}
onToggleFavorite={toggleFavorite}
onToggleWatched={toggleWatched}
isMovieFavorite={isMovieFavorite}
isMovieWatched={isMovieWatched}
Comment on lines +124 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try not to bubble up these events and restrict these events upto its own component to scale the application better.

/>
</main>

<footer className='App-footer'>
<p>Copyright © 2025 Flixster</p>
</footer>
Expand Down
61 changes: 59 additions & 2 deletions src/MovieCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,73 @@
transition: transform 0.3s ease,
box-shadow 0.3s ease;
background-color: #1a1a1a;
cursor: pointer;
/* z-index: */
position: relative;
}

.movie-card:hover {
transform: scale(1.03);
box-shadow: 0rem 0.15rem 0.9rem rgba(229, 9, 20);
}

.movie-card-content {
cursor: pointer;
}

.movie-card-actions {
position: absolute;
top: 10px;
right: 10px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 10;
}

.action-button {
width: 32px;
height: 32px;
border-radius: 50%;
border: none;
background-color: rgba(0, 0, 0, 0.6);
color: white;
font-size: 18px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
}

.action-button:hover {
background-color: rgba(0, 0, 0, 0.8);
transform: scale(1.1);
}

.favorite-button.active {
background-color: #fa5252;
color: white;
}

.watched-button.active {
background-color: #40c057;
color: white;
}

.favorite-button svg,
.watched-button svg {
transition: fill 0.3s ease, stroke 0.3s ease;
}

/* Already in your CSS — controls bg */
.favorite-button.active {
background-color: rgba(250, 82, 82, 0.2);
}

.watched-button.active {
background-color: rgba(64, 192, 87, 0.2);
}


.movie-poster {
width: 100%;
height: 300px;
Expand Down
87 changes: 76 additions & 11 deletions src/MovieCard.jsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,88 @@
import './MovieCard.css';

const MovieCard = ({ movie, onClick }) => {
const MovieCard = ({
movie,
onClick,
onToggleFavorite,
onToggleWatched,
isFavorite,
isWatched
}) => {
const posterBaseUrl = "https://image.tmdb.org/t/p/w500";

const handleClick = () => {
onClick(movie);
};

const handleFavoriteClick = (e) => {
e.stopPropagation();
onToggleFavorite(movie);
};

const handleWatchedClick = (e) => {
e.stopPropagation();
onToggleWatched(movie);
};

return (
<div className="movie-card" onClick={handleClick}>
<img
src={movie.poster_path ? `${posterBaseUrl}${movie.poster_path}` : '/placeholder-poster.svg'}
alt={`${movie.title} poster`}
className="movie-poster"
/>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<div className="movie-vote">
<span className="vote-average">{movie.vote_average.toFixed(1)}</span>
<div className="movie-card">
<div className="movie-card-actions">
<button
className={`action-button favorite-button ${isFavorite ? 'active' : ''}`}
onClick={handleFavoriteClick}
aria-label={isFavorite ? "Remove from favorites" : "Add to favorites"}
title={isFavorite ? "Remove from favorites" : "Add to favorites"}
>
<svg xmlns="http://www.w3.org/2000/svg" fill={isFavorite ? "#fa5252" : "none"} stroke={isFavorite ? "#fa5252" : "white"} viewBox="0 0 24 24" width="20" height="20">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5
2 6.42 3.42 5 5.5 5c1.74 0 3.41 1.01 4.13 2.44
C10.09 6.01 11.76 5 13.5 5
15.58 5 17 6.42 17 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
/>
</svg>
</button>

<button
className={`action-button watched-button ${isWatched ? 'active' : ''}`}
onClick={handleWatchedClick}
aria-label={isWatched ? "Remove from watched" : "Mark as watched"}
title={isWatched ? "Remove from watched" : "Mark as watched"}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill={isWatched ? "#40c057" : "none"}
stroke={isWatched ? "#40c057" : "white"}
viewBox="0 0 24 24"
width="20"
height="20"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1.5 12s4.5-7.5 10.5-7.5S22.5 12 22.5 12s-4.5 7.5-10.5 7.5S1.5 12 1.5 12z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
</button>

</div>

<div className="movie-card-content" onClick={handleClick}>
<img
src={movie.poster_path ? `${posterBaseUrl}${movie.poster_path}` : '/placeholder-poster.svg'}
alt={`${movie.title} poster`}
className="movie-poster"
/>
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
<div className="movie-vote">
<span className="vote-average">{movie.vote_average.toFixed(1)}</span>
</div>
</div>
</div>
</div>
Expand Down
Loading