WatchlistPage.jsx
WatchlistPage.jsx
import { useState, useEffect } from "react";
import "../styles/WatchlistPage.css";
import { Link } from "react-router-dom";
export default function WatchlistPage() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [isMenuOpen, setIsMenuOpen] = useState(false);
useEffect(() => {
const fetchMovies = async () => {
try {
const response = await fetch("/api/watchlist/");
if (!response.ok) {
throw new Error("Failed to fetch movies");
}
const data = await response.json();
setMovies(data);
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
};
fetchMovies();
}, []);
if (loading) {
return (
<div className="watchlist-page">
<h2>Loading...</h2>
</div>
);
}
if (error) {
return (
<div className="watchlist-page">
<h2>Error: {error}</h2>
</div>
);
}
return (
<div className="watchlist-page">
{/* Header Section */}
<header className="header">
<div className={`navbar-container ${isMenuOpen ? 'flipped' : ''}`}>
<div className="navbar-front">
<div
className="menu-icon"
onClick={(e) => {
e.stopPropagation();
setIsMenuOpen(!isMenuOpen);
}}
>
<span className="menu-bar"></span>
<span className="menu-bar"></span>
<span className="menu-bar"></span>
</div>
<h1>WATCHLIST</h1>
<div className="user-profile">
<span className="profile-icon">👤</span>
<span className="username">JOE76</span>
</div>
</div>
<div className="navbar-back">
<div
className="menu-icon"
onClick={(e) => {
e.stopPropagation();
setIsMenuOpen(!isMenuOpen);
}}
>
<span className="menu-bar"></span>
<span className="menu-bar"></span>
<span className="menu-bar"></span>
</div>
<ul className="menu-options">
<li><Link to="/signin">Sign In</Link></li>
<li><Link to="/signup">Sign Up</Link></li>
<li><Link to="/">Home</Link></li>
</ul>
</div>
</div>
</header>
{/* Circle Container Under Navbar and Movie Cards */}
<div className="circle-one-container">
<div className="circle-one"></div>
</div>
<div className="circle-two-container">
<div className="circle-two"></div>
</div>
{/* Movie List Section */}
<div className="movie-list">
{movies.length > 0 ? (
movies.map((movie) => (
<div key={movie.id || movie.title || Math.random()} className="movie-card">
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
{movie.poster_url && (
<img
src={movie.poster_url}
alt={`Poster för ${movie.title}`}
/>
)}
{movie.trailer_url && (
<p>
<a
href={movie.trailer_url}
target="_blank"
rel="noopener noreferrer"
>
▶️ Se trailer
</a>
</p>
)}
<div className="movie-details">
<span className="rating">
IMDb {movie.imdbRating || "N/A"}
</span>
<span className="year">{movie.release_year}</span>
<span className="duration">{movie.length_minutes} min</span>
</div>
<p className="genres-text">
{movie.genre ? movie.genre.split(", ").join(", ") : "N/A"}
</p>
<button
className="watchlist-btn"
onClick={() => alert(`${movie.title} has been added to your watchlist!`)}
>
❤️ Watchlist
</button>
</div>
</div>
))
) : (
<p>No movies in your watchlist.</p>
)}
</div>
<Link to="/" className="back-to-home">
Back to Home
</Link>
</div>
);
}Stylingen:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Bebas Neue", sans-serif;
}
/* styles/WatchlistPage.css */
.watchlist-page {
background: linear-gradient(135deg, #1a2526 0%, #222831 100%);
color: #fff;
min-height: 100vh;
padding: 20px;
font-family: "Poppins", sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
.header {
margin-bottom: 30px;
width: 100%;
max-width: 1200px;
position: relative;
z-index: 10; /* Navbar above everything */
}
.navbar-container {
width: 100%;
height: 60px;
perspective: 1000px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 10px 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.navbar-front,
.navbar-back {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
backface-visibility: hidden;
display: flex;
align-items: center;
justify-content: space-between;
transition: transform 0.6s ease;
transform-style: preserve-3d;
border-radius: 15px;
padding: 0;
}
.navbar-front {
transform: rotateX(0deg);
z-index: 2;
}
.navbar-back {
transform: rotateX(180deg);
z-index: 1;
background: rgba(255, 255, 255, 0.2);
}
.navbar-container.flipped .navbar-front {
transform: rotateX(180deg);
z-index: 1;
}
.navbar-container.flipped .navbar-back {
transform: rotateX(0deg);
z-index: 2;
}
.menu-icon {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 24px;
height: 18px;
cursor: pointer;
margin-left: 20px;
z-index: 3;
}
.menu-bar {
width: 100%;
height: 3px;
background: white;
border-radius: 2px;
}
.navbar-front h1,
.navbar-back h1 {
font-size: 24px;
font-weight: 600;
color: #fff;
margin: 0 0 0 20px;
}
.user-profile {
display: flex;
align-items: center;
gap: 8px;
margin-right: 20px;
}
.profile-icon {
font-size: 24px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 5px;
}
.username {
font-size: 16px;
font-weight: 500;
color: #e0e0e0;
}
.menu-options {
list-style: none;
padding: 0;
margin: 0 20px 0 0;
display: flex;
gap: 20px;
width: 100%;
justify-content: flex-end;
}
.menu-options li a {
text-decoration: none;
color: #fff;
font-size: 16px;
font-weight: 500;
}
.menu-options li a:hover {
color: #e0e0e0;
}
.circle-one-container {
position: fixed;
top: -2rem; /* Starting position */
right: -5rem;
width: 30rem;
height: 30rem;
z-index: 1; /* Below both navbar (10) and movie-list (0) */
animation: float 4s ease-in-out infinite; /* Animation added */
}
.circle-one {
width: 100%;
height: 100%;
background-image: linear-gradient(160deg, #0093e9 0%, #80d0c7 100%);
border-radius: 50%;
}
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(20px);
}
100% {
transform: translateY(0);
}
}
.circle-two-container {
position: fixed;
bottom: -2rem; /* Starting position */
left: -5rem;
width: 25rem;
height: 25rem;
z-index: 1;
animation: float 4s ease-in-out infinite;
}
.circle-two {
width: 100%;
height: 100%;
background-color: #fbda61;
background-image: linear-gradient(45deg, #fbda61 0%, #ff5acd 100%);
border-radius: 50%;
}
.watchlist-page {
position: relative;
min-height: 100vh;
}
.movie-list {
position: relative;
z-index: 1;
}
/* Movie List Styles */
.movie-list {
display: flex;
gap: 30px;
justify-content: center;
flex-wrap: wrap;
width: 100%;
max-width: 1200px;
}
.movie-card {
width: 20rem;
padding: 15px;
display: flex;
flex-direction: column;
text-align: center;
position: relative;
transition: transform 0.3s ease;
cursor: pointer;
}
.movie-card:hover {
transform: translateY(-5px);
}
.movie-info {
display: flex;
flex-direction: column; /* Ensure vertical stacking of info */
align-items: center; /* Center-align content */
}
.movie-info img {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 15px;
margin-bottom: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.movie-info p a {
color: #0086ac;
text-decoration: none;
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
padding: 5px 10px;
transition: background 0.3s ease;
margin: 5px;
}
.movie-title {
font-size: 18px;
font-weight: 500;
margin: 5px 0; /* Reduced margin for tighter spacing */
color: #000000;
}
.movie-details {
display: flex;
justify-content: space-between;
font-size: 14px;
margin: 0 10px 5px; /* Reduced bottom margin */
padding: 0.5rem;
}
.rating {
background: rgb(255, 208, 0);
color: black;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
}
.year,
.duration {
color: #000000;
margin: 0.5rem;
}
.genres-text {
font-size: 12px;
color: #fff;
margin: 5px 10px;
padding: 0.5rem;
}
.watchlist-btn {
background: #222831;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fbbf24;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
margin: 5px; /* Adjusted margin for consistency */
padding: 5px 10px;
border-radius: 8px;
transition: background 0.3s ease;
}
.watchlist-btn:hover {
background: #3b495f;
}
/* Back to Home Link (unchanged) */
.back-to-home {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: #fff;
text-decoration: none;
font-size: 16px;
font-weight: 500;
transition: background 0.3s ease;
}
.back-to-home:hover {
background: rgba(255, 255, 255, 0.2);
}
/* Loading and Error Styles (unchanged) */
.watchlist-page h2 {
font-size: 20px;
font-weight: 500;
color: #e0e0e0;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px 30px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-top: 50px;
}
.watchlist-page p {
font-size: 16px;
color: #000000;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 10px 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-top: 20px;
}
Modified code for watchlistPage.jsx
import { useState, useEffect } from "react";
import "../styles/WatchlistPage.css";
import { Link } from "react-router-dom";
export default function WatchlistPage() {
const [movies, setMovies] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [showWatchlist, setShowWatchlist] = useState(false);
useEffect(() => {
// Check if the watchlist button was clicked (stored in localStorage)
const watchlistClicked = localStorage.getItem("watchlistClicked") === "true";
setShowWatchlist(watchlistClicked);
const fetchMovies = async () => {
try {
const response = await fetch("/api/watchlist/");
if (!response.ok) {
throw new Error("Failed to fetch movies");
}
const data = await response.json();
setMovies(data);
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
};
if (watchlistClicked) {
fetchMovies();
} else {
setLoading(false); // No need to fetch if watchlist isn't clicked
}
}, []);
if (loading) {
return (
<div className="watchlist-page">
<h2>Loading...</h2>
</div>
);
}
if (error) {
return (
<div className="watchlist-page">
<h2>Error: {error}</h2>
</div>
);
}
return (
<div className="watchlist-page">
<div className="movie-list">
{showWatchlist && movies.length > 0 ? (
movies.map((movie) => (
<div key={movie.id || movie.title || Math.random()} className="movie-card">
<div className="movie-info">
<h3 className="movie-title">{movie.title}</h3>
{movie.poster_url && (
<img
src={movie.poster_url}
alt={`Poster för ${movie.title}`}
/>
)}
<div className="movie-details">
<span className="rating">
IMDb {movie.imdbRating || "N/A"}
</span>
<span className="year">{movie.release_year}</span>
<span className="duration">{movie.length_minutes} min</span>
</div>
</div>
</div>
))
) : (
<div className="empty-watchlist">
<h3>Your watchlist is empty !</h3>
<p>Visite the main page to add movies to your watchlist</p>
</div>
)}
</div>
<Link to="/" className="back-to-home">
Back to Home
</Link>
</div>
);
}Styling for watch list
.watchlist-page {
background: linear-gradient(135deg, #1a2526 0%, #222831 100%);
color: #fff;
min-height: 100vh;
padding: 20px;
font-family: "Poppins", sans-serif;
display: flex;
flex-direction: column;
align-items: center;
}
/* .header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
width: 100%;
max-width: 1200px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 10px 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.menu-icon {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 24px;
height: 18px;
cursor: pointer;
}
.menu-bar {
width: 100%;
height: 3px;
background: white;
border-radius: 2px;
}
.header h1 {
font-size: 24px;
font-weight: 600;
background: white;
-webkit-text-fill-color: transparent;
}
.user-profile {
display: flex;
align-items: center;
gap: 8px;
}
.profile-icon {
font-size: 24px;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
padding: 5px;
}
.username {
font-size: 16px;
font-weight: 500;
color: #e0e0e0;
} */
/* Movie List Styles */
.empty-watchlist {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 500;
}
.empty-watchlist p {
font-size: 16px;
color: #fff !important;
margin-top: 10px;
}
.movie-list {
position: relative;
z-index: 1;
}
/* Movie List Styles */
.movie-list {
display: flex;
gap: 30px;
justify-content: center;
flex-wrap: wrap;
width: 100%;
max-width: 1200px;
}
.movie-card {
width: 20rem;
padding: 15px;
display: flex;
flex-direction: column;
text-align: center;
position: relative;
transition: transform 0.3s ease;
cursor: pointer;
}
.movie-card:hover {
transform: translateY(-5px);
}
.movie-info {
display: flex;
flex-direction: column; /* Ensure vertical stacking of info */
align-items: center; /* Center-align content */
}
.movie-info img {
width: 100%;
height: 300px;
object-fit: cover;
border-radius: 15px;
margin-bottom: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.movie-info p a {
color: #0086ac;
text-decoration: none;
font-size: 14px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
padding: 5px 10px;
transition: background 0.3s ease;
margin: 5px;
}
.movie-title {
font-size: 18px;
font-weight: 500;
margin: 5px 0; /* Reduced margin for tighter spacing */
color: #000000;
}
.movie-details {
display: flex;
justify-content: space-between;
font-size: 14px;
margin: 0 10px 5px; /* Reduced bottom margin */
padding: 0.5rem;
}
.rating {
background: rgb(255, 208, 0);
color: black;
padding: 2px 6px;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
}
.year,
.duration {
color: #000000;
margin: 0.5rem;
}
.genres-text {
font-size: 12px;
color: #fff;
margin: 5px 10px;
padding: 0.5rem;
}
.watchlist-btn {
background: #222831;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #fbbf24;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
margin: 5px; /* Adjusted margin for consistency */
padding: 5px 10px;
border-radius: 8px;
transition: background 0.3s ease;
}
.watchlist-btn:hover {
background: #3b495f;
}
/* Back to Home Link (unchanged) */
.back-to-home {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: #fff;
text-decoration: none;
font-size: 16px;
font-weight: 500;
transition: background 0.3s ease;
}
.back-to-home:hover {
background: rgba(255, 255, 255, 0.2);
}
/* Loading and Error Styles (unchanged) */
.watchlist-page h2 {
font-size: 20px;
font-weight: 500;
color: #e0e0e0;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 15px 30px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-top: 50px;
}
.watchlist-page p {
font-size: 16px;
color: #000000;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 10px 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
margin-top: 20px;
}