Skip to content

Commit 32297ea

Browse files
authored
Fix: Favorites API error #2 (#3)
1 parent bb2fa62 commit 32297ea

6 files changed

Lines changed: 259 additions & 65 deletions

File tree

frontend/src/components/common/MediaCard.js

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,27 @@ import {
1010
Tooltip,
1111
CircularProgress,
1212
} from "@mui/material";
13-
import { Bookmark, BookmarkBorder } from "@mui/icons-material";
13+
import {
14+
Bookmark,
15+
BookmarkBorder,
16+
Favorite,
17+
FavoriteBorder,
18+
} from "@mui/icons-material";
1419
import { useAuth } from "@/context/AuthContext";
1520

1621
export default function MediaCard({ media, showWatchlistButton = true }) {
1722
const router = useRouter();
1823
const { isAuthenticated } = useAuth();
1924
const [inWatchlist, setInWatchlist] = useState(false);
25+
const [isFavorite, setIsFavorite] = useState(false);
2026
const [loading, setLoading] = useState(false);
27+
const [favLoading, setFavLoading] = useState(false);
2128

2229
useEffect(() => {
2330
// Check if media is in watchlist when component mounts
24-
if (isAuthenticated && showWatchlistButton) {
25-
checkWatchlistStatus();
31+
if (isAuthenticated) {
32+
if (showWatchlistButton) checkWatchlistStatus();
33+
checkFavoriteStatus();
2634
}
2735
}, [isAuthenticated, media._id]);
2836

@@ -45,6 +53,51 @@ export default function MediaCard({ media, showWatchlistButton = true }) {
4553
}
4654
};
4755

56+
const checkFavoriteStatus = async () => {
57+
try {
58+
const token = localStorage.getItem("token");
59+
if (!token) return;
60+
61+
const response = await fetch(`/api/v1/favorites/check/${media._id}`, {
62+
headers: {
63+
Authorization: `Bearer ${token}`,
64+
},
65+
});
66+
const data = await response.json();
67+
if (data.success) {
68+
setIsFavorite(data.inFavorites);
69+
}
70+
} catch (error) {
71+
console.error("Error checking favorite status:", error);
72+
}
73+
};
74+
75+
const handleFavoriteToggle = async (e) => {
76+
e.stopPropagation();
77+
if (!isAuthenticated) return;
78+
79+
setFavLoading(true);
80+
try {
81+
const token = localStorage.getItem("token");
82+
const response = await fetch(`/api/v1/favorites/${media._id}`, {
83+
method: "POST",
84+
headers: {
85+
Authorization: `Bearer ${token}`,
86+
"Content-Type": "application/json",
87+
},
88+
});
89+
90+
const data = await response.json();
91+
if (data.success) {
92+
setIsFavorite(!isFavorite);
93+
}
94+
} catch (error) {
95+
console.error("Error toggling favorite:", error);
96+
} finally {
97+
setFavLoading(false);
98+
}
99+
};
100+
48101
const handleWatchlistToggle = async (e) => {
49102
e.stopPropagation();
50103

@@ -116,7 +169,7 @@ export default function MediaCard({ media, showWatchlistButton = true }) {
116169
"&:hover .card-overlay": {
117170
opacity: 1,
118171
},
119-
"&:hover .watchlist-button": {
172+
"&:hover .watchlist-button, &:hover .favorite-button": {
120173
opacity: 1,
121174
},
122175
}}
@@ -132,6 +185,40 @@ export default function MediaCard({ media, showWatchlistButton = true }) {
132185
}}
133186
/>
134187

188+
{/* Favorite Button */}
189+
{isAuthenticated && (
190+
<Tooltip
191+
title={isFavorite ? "Remove from favorites" : "Add to favorites"}
192+
>
193+
<IconButton
194+
className="favorite-button"
195+
onClick={handleFavoriteToggle}
196+
disabled={favLoading}
197+
sx={{
198+
position: "absolute",
199+
top: 8,
200+
left: 8,
201+
backgroundColor: "rgba(0, 0, 0, 0.7)",
202+
color: isFavorite ? "#ff4081" : "#fff",
203+
opacity: 0,
204+
transition: "all 0.3s ease",
205+
"&:hover": {
206+
backgroundColor: "rgba(0, 0, 0, 0.9)",
207+
transform: "scale(1.1)",
208+
},
209+
}}
210+
>
211+
{favLoading ? (
212+
<CircularProgress size={20} sx={{ color: "#fff" }} />
213+
) : isFavorite ? (
214+
<Favorite />
215+
) : (
216+
<FavoriteBorder />
217+
)}
218+
</IconButton>
219+
</Tooltip>
220+
)}
221+
135222
{/* Watchlist Button */}
136223
{showWatchlistButton && isAuthenticated && (
137224
<Tooltip
Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Box, Typography } from '@mui/material';
2-
import MediaCard from '@/components/common/MediaCard';
1+
import { Box, Typography } from "@mui/material";
2+
import MediaCard from "@/components/common/MediaCard";
33

44
export default function FavoritesSection({ favorites = [] }) {
55
return (
@@ -8,42 +8,42 @@ export default function FavoritesSection({ favorites = [] }) {
88
variant="h5"
99
sx={{
1010
fontWeight: 600,
11-
color: '#fff',
11+
color: "#fff",
1212
mb: 3,
13-
fontSize: { xs: '1.25rem', md: '1.5rem' },
13+
fontSize: { xs: "1.25rem", md: "1.5rem" },
1414
}}
1515
>
1616
Your Favorites
1717
</Typography>
1818
{favorites.length > 0 ? (
1919
<Box
2020
sx={{
21-
display: 'flex',
21+
display: "flex",
2222
gap: 3,
23-
overflowX: 'auto',
23+
overflowX: "auto",
2424
pb: 2,
25-
'&::-webkit-scrollbar': {
25+
"&::-webkit-scrollbar": {
2626
height: 8,
2727
},
28-
'&::-webkit-scrollbar-track': {
29-
backgroundColor: 'rgba(255, 255, 255, 0.05)',
28+
"&::-webkit-scrollbar-track": {
29+
backgroundColor: "rgba(255, 255, 255, 0.05)",
3030
borderRadius: 4,
3131
},
32-
'&::-webkit-scrollbar-thumb': {
33-
backgroundColor: 'rgba(255, 255, 255, 0.2)',
32+
"&::-webkit-scrollbar-thumb": {
33+
backgroundColor: "rgba(255, 255, 255, 0.2)",
3434
borderRadius: 4,
3535
},
3636
}}
3737
>
3838
{favorites.map((favorite) => (
39-
<MediaCard key={favorite.id} media={favorite} />
39+
<MediaCard key={favorite._id} media={favorite} />
4040
))}
4141
</Box>
4242
) : (
4343
<Typography
4444
sx={{
45-
color: 'rgba(255, 255, 255, 0.6)',
46-
textAlign: 'center',
45+
color: "rgba(255, 255, 255, 0.6)",
46+
textAlign: "center",
4747
py: 4,
4848
}}
4949
>
@@ -53,4 +53,3 @@ export default function FavoritesSection({ favorites = [] }) {
5353
</Box>
5454
);
5555
}
56-

frontend/src/pages/profile.js

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,42 @@
1-
import Head from 'next/head';
2-
import { useEffect, useState } from 'react';
3-
import { useRouter } from 'next/router';
4-
import { Box, Container, CircularProgress } from '@mui/material';
5-
import { useAuth } from '@/context/AuthContext';
6-
import api from '@/lib/api';
7-
import Footer from '@/components/Footer';
8-
import ProfileHeader from '@/components/profile/ProfileHeader';
9-
import AccountInformationPanel from '@/components/profile/AccountInformationPanel';
10-
import LogoutButton from '@/components/profile/LogoutButton';
11-
import FavoritesSection from '@/components/profile/FavoritesSection';
1+
import Head from "next/head";
2+
import { useEffect, useState } from "react";
3+
import { useRouter } from "next/router";
4+
import { Box, Container, CircularProgress } from "@mui/material";
5+
import { useAuth } from "@/context/AuthContext";
6+
import api from "@/lib/api";
7+
import Footer from "@/components/Footer";
8+
import ProfileHeader from "@/components/profile/ProfileHeader";
9+
import AccountInformationPanel from "@/components/profile/AccountInformationPanel";
10+
import LogoutButton from "@/components/profile/LogoutButton";
11+
import FavoritesSection from "@/components/profile/FavoritesSection";
1212

1313
export default function Profile() {
1414
const { isAuthenticated, loading: authLoading, logout } = useAuth();
1515
const router = useRouter();
1616
const [user, setUser] = useState(null);
1717
const [loading, setLoading] = useState(true);
18-
const [error, setError] = useState('');
18+
const [error, setError] = useState("");
1919
const [favorites, setFavorites] = useState([]);
2020

2121
useEffect(() => {
2222
if (!authLoading && !isAuthenticated) {
23-
router.push('/');
23+
router.push("/");
2424
}
2525
}, [isAuthenticated, authLoading, router]);
2626

2727
useEffect(() => {
2828
const fetchUser = async () => {
2929
try {
3030
setLoading(true);
31-
const response = await api.get('/api/v1/current/user');
32-
setUser(response.data.user);
31+
const response = await api.get("/api/v1/current/user");
32+
const userData = response.data.user;
33+
setUser(userData);
34+
if (userData.preferences?.favoriteMedia) {
35+
setFavorites(userData.preferences.favoriteMedia);
36+
}
3337
} catch (error) {
34-
console.error('Error fetching user:', error);
35-
setError('Failed to load user profile');
38+
console.error("Error fetching user:", error);
39+
setError("Failed to load user profile");
3640
} finally {
3741
setLoading(false);
3842
}
@@ -48,22 +52,30 @@ export default function Profile() {
4852
// Refresh user data when page becomes visible (e.g., returning from subscription page)
4953
useEffect(() => {
5054
const handleVisibilityChange = () => {
51-
if (document.visibilityState === 'visible' && isAuthenticated && !loading) {
55+
if (
56+
document.visibilityState === "visible" &&
57+
isAuthenticated &&
58+
!loading
59+
) {
5260
const fetchUser = async () => {
5361
try {
54-
const response = await api.get('/api/v1/current/user');
55-
setUser(response.data.user);
62+
const response = await api.get("/api/v1/current/user");
63+
const userData = response.data.user;
64+
setUser(userData);
65+
if (userData.preferences?.favoriteMedia) {
66+
setFavorites(userData.preferences.favoriteMedia);
67+
}
5668
} catch (error) {
57-
console.error('Error refreshing user:', error);
69+
console.error("Error refreshing user:", error);
5870
}
5971
};
6072
fetchUser();
6173
}
6274
};
6375

64-
document.addEventListener('visibilitychange', handleVisibilityChange);
76+
document.addEventListener("visibilitychange", handleVisibilityChange);
6577
return () => {
66-
document.removeEventListener('visibilitychange', handleVisibilityChange);
78+
document.removeEventListener("visibilitychange", handleVisibilityChange);
6779
};
6880
}, [isAuthenticated, loading]);
6981

@@ -73,23 +85,23 @@ export default function Profile() {
7385
// await api.put(`/api/v1/update/user/${user._id}`, updatedUser);
7486
setUser({ ...user, ...updatedUser });
7587
} catch (error) {
76-
console.error('Error updating user:', error);
77-
setError('Failed to update profile');
88+
console.error("Error updating user:", error);
89+
setError("Failed to update profile");
7890
}
7991
};
8092

8193
if (authLoading || loading) {
8294
return (
8395
<Box
8496
sx={{
85-
minHeight: '100vh',
86-
display: 'flex',
87-
alignItems: 'center',
88-
justifyContent: 'center',
89-
backgroundColor: '#000',
97+
minHeight: "100vh",
98+
display: "flex",
99+
alignItems: "center",
100+
justifyContent: "center",
101+
backgroundColor: "#000",
90102
}}
91103
>
92-
<CircularProgress sx={{ color: '#ffd700' }} />
104+
<CircularProgress sx={{ color: "#ffd700" }} />
93105
</Box>
94106
);
95107
}
@@ -107,29 +119,29 @@ export default function Profile() {
107119

108120
<Box
109121
sx={{
110-
minHeight: '100vh',
111-
backgroundColor: '#000',
122+
minHeight: "100vh",
123+
backgroundColor: "#000",
112124
pt: { xs: 10, md: 12 },
113125
pb: 8,
114-
position: 'relative',
126+
position: "relative",
115127
backgroundImage: 'url("/hero_bg.jpg")',
116-
backgroundSize: 'cover',
117-
backgroundPosition: 'center',
118-
backgroundRepeat: 'no-repeat',
119-
backgroundAttachment: 'fixed',
120-
'&::before': {
128+
backgroundSize: "cover",
129+
backgroundPosition: "center",
130+
backgroundRepeat: "no-repeat",
131+
backgroundAttachment: "fixed",
132+
"&::before": {
121133
content: '""',
122-
position: 'absolute',
134+
position: "absolute",
123135
top: 0,
124136
left: 0,
125137
right: 0,
126138
bottom: 0,
127-
backgroundColor: 'rgba(0, 0, 0, 0.3)',
139+
backgroundColor: "rgba(0, 0, 0, 0.3)",
128140
zIndex: 0,
129141
},
130142
}}
131143
>
132-
<Container maxWidth="lg" sx={{ position: 'relative', zIndex: 1 }}>
144+
<Container maxWidth="lg" sx={{ position: "relative", zIndex: 1 }}>
133145
<ProfileHeader user={user} />
134146

135147
<AccountInformationPanel user={user} onUpdate={handleUpdateUser} />

0 commit comments

Comments
 (0)