Skip to content

Commit f6a9a1b

Browse files
authored
Merge pull request #77 from peeweetje/fix-issue-adding-shows-to-my-list
Fix issue adding shows to my list
2 parents e9eb9b6 + cc46623 commit f6a9a1b

File tree

6 files changed

+157
-81
lines changed

6 files changed

+157
-81
lines changed

netflix-create-react-vite-app/src/components/movie-list/MovieRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const MovieRow = ({ title, movies }: MovieRowProps) => {
8484
title={movie.title}
8585
vote_average={movie.vote_average}
8686
id={movie.id}
87-
media_type='movie'
87+
media_type={movie.media_type ? movie.media_type : 'movie'}
8888
/>
8989
</div>
9090
) : null

netflix-create-react-vite-app/src/components/movie-list/movieList.tsx

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,10 @@
11
import { useTranslation } from 'react-i18next';
2-
import {
3-
FlexWrapper,
4-
CardWrapper,
5-
RemoveButton,
6-
StyledLink,
7-
} from './movie.styles';
2+
import { FlexWrapper, CardWrapper, StyledLink } from './movie.styles';
83
import { imageUrl } from '../../utils/api';
94
import type { MovieResult } from '../../utils/types/types';
105
import { Card } from '../card/card';
116

12-
13-
export const MovieList = ({
14-
movies,
15-
removeFromList,
16-
}: {
17-
movies: MovieResult[];
18-
removeFromList?: (item: MovieResult) => void;
19-
}) => {
7+
export const MovieList = ({ movies }: { movies: MovieResult[] }) => {
208
const { t } = useTranslation();
219

2210
return (
@@ -37,11 +25,6 @@ export const MovieList = ({
3725
media_type='movie'
3826
/>
3927
</StyledLink>
40-
{removeFromList && (
41-
<RemoveButton onClick={() => removeFromList(result)}>
42-
Remove
43-
</RemoveButton>
44-
)}
4528
</CardWrapper>
4629
)
4730
)}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { useState, useEffect } from 'react';
2+
import { VITE_API_KEY } from '../utils/api';
3+
import { MovieResult } from '../utils/types/types';
4+
import { useMyList, MyListItem } from '../context/myListContext';
5+
6+
interface UseLocalListDetailsReturn {
7+
localMovies: MovieResult[];
8+
localLoading: boolean;
9+
localError: string | null;
10+
failedItems: MyListItem[];
11+
removalNotice: string | null;
12+
}
13+
14+
export const useLocalListDetails = (): UseLocalListDetailsReturn => {
15+
const { myList, removeFromList } = useMyList();
16+
const [localMovies, setLocalMovies] = useState<MovieResult[]>([]);
17+
const [localLoading, setLocalLoading] = useState<boolean>(false);
18+
const [localError, setLocalError] = useState<string | null>(null);
19+
const [failedItems, setFailedItems] = useState<MyListItem[]>([]);
20+
const [removalNotice, setRemovalNotice] = useState<string | null>(null);
21+
22+
useEffect(() => {
23+
if (!myList || myList.length === 0) {
24+
setLocalMovies([]);
25+
setFailedItems([]);
26+
return;
27+
}
28+
29+
// Filter for valid items only
30+
const validList = myList.filter(
31+
(item) =>
32+
['movie', 'tv'].includes(item.media_type) && typeof item.id === 'number'
33+
);
34+
35+
if (validList.length === 0) {
36+
setLocalMovies([]);
37+
setFailedItems(myList); // All items are invalid
38+
return;
39+
}
40+
41+
const abortController = new AbortController();
42+
43+
const fetchLocalMovies = async () => {
44+
setLocalLoading(true);
45+
setLocalError(null);
46+
setFailedItems([]); // Reset failed items on each fetch
47+
48+
try {
49+
const movies: MovieResult[] = [];
50+
const failed: any[] = [];
51+
52+
for (const item of validList) {
53+
if (abortController.signal.aborted) break;
54+
const url = `https://api.themoviedb.org/3/${item.media_type}/${item.id}?api_key=${VITE_API_KEY}`;
55+
const res = await fetch(url, { signal: abortController.signal });
56+
57+
if (!res.ok) {
58+
console.warn(
59+
'Failed to fetch:',
60+
url,
61+
'Status:',
62+
res.status,
63+
'Item:',
64+
item
65+
);
66+
failed.push(item);
67+
// Batch remove failed items and show notice
68+
if (failed.length > 0) {
69+
failed.forEach(item => removeFromList(item));
70+
const failedTitles = failed.map(item => item.title || item.name || 'Unknown Title').join(', ');
71+
const sanitizedTitle = (
72+
item.title ||
73+
item.name ||
74+
'Unknown Title'
75+
).replace(/[<>]/g, '');
76+
setRemovalNotice(
77+
`"${sanitizedTitle}" was removed from your list because it could not be loaded.`
78+
);
79+
setTimeout(() => setRemovalNotice(null), 4000);
80+
}
81+
continue;
82+
}
83+
const data = await res.json();
84+
const movieData =
85+
item.media_type === 'tv' ? { ...data, title: data.name } : data;
86+
movies.push(movieData);
87+
}
88+
89+
setLocalMovies(movies);
90+
setFailedItems(failed);
91+
} catch (err) {
92+
if (!abortController.signal.aborted) {
93+
setLocalError('Failed to fetch your list. Please try again later.');
94+
}
95+
} finally {
96+
if (!abortController.signal.aborted) {
97+
setLocalLoading(false);
98+
}
99+
}
100+
};
101+
102+
fetchLocalMovies();
103+
104+
return () => {
105+
abortController.abort();
106+
};
107+
}, [myList, removeFromList]);
108+
109+
return {
110+
localMovies,
111+
localLoading,
112+
localError,
113+
failedItems,
114+
removalNotice,
115+
};
116+
};

netflix-create-react-vite-app/src/pages/my-list/myList.styles.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export const MovieTitle = styled.h3`
3333
font-weight: 500;
3434
`;
3535

36+
export const RemovalNotice = styled.div`
37+
color: ${({ theme }) => theme.colors.orange};
38+
margin-bottom: ${({ theme }) => theme.space[6]};
39+
`;
40+
3641
export const RemoveButton = styled.button`
3742
background: ${(props) => props.theme.colors.blue};
3843
color: ${(props) => props.theme.colors.white};

netflix-create-react-vite-app/src/pages/my-list/myList.tsx

Lines changed: 23 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
2-
import {VITE_API_KEY} from '../../utils/api';
1+
import React from 'react';
32
import {
43
MyListContainer,
54
MoviesGrid,
@@ -8,70 +7,41 @@ import {
87
MovieTitle,
98
CardWrapper,
109
RemoveButton,
10+
RemovalNotice,
1111
} from './myList.styles';
12-
import { MovieResult } from '../../utils/types/types';
1312
import { useMyList } from '../../context/myListContext';
1413
import { NavbarHeader } from '../../components/navbarmenu/navbarheader/navbarHeader';
1514
import { Card } from '../../components/card/card';
16-
17-
15+
import { useLocalListDetails } from '../../hooks/useLocalListDetails';
1816

1917
export const MyList = () => {
2018
const { myList, removeFromList } = useMyList();
21-
const [localMovies, setLocalMovies] = useState<MovieResult[]>([]);
22-
const [localLoading, setLocalLoading] = useState<boolean>(false);
23-
const [localError, setLocalError] = useState<string | null>(null);
24-
25-
// Fetch details for local My List
26-
useEffect(() => {
27-
const abortController = new AbortController();
28-
const fetchLocalMovies = async () => {
29-
setLocalLoading(true);
30-
setLocalError(null);
31-
try {
32-
const movies: MovieResult[] = [];
33-
for (const item of myList) {
34-
if (
35-
!['movie', 'tv'].includes(item.media_type) ||
36-
typeof item.id !== 'number'
37-
) {
38-
console.warn('Skipping invalid item:', item);
39-
continue;
40-
}
41-
const url = `https://api.themoviedb.org/3/${item.media_type}/${item.id}?api_key=${VITE_API_KEY}`;
42-
const res = await fetch(url, { signal: abortController.signal });
43-
if (!res.ok) {
44-
console.warn('Failed to fetch:', url, res.status);
45-
continue;
46-
}
47-
const data = await res.json();
48-
if (item.media_type === 'tv') {
49-
data.title = data.name;
50-
}
51-
movies.push(data);
52-
}
53-
setLocalMovies(movies);
54-
} catch (err) {
55-
if (!abortController.signal.aborted) {
56-
setLocalError('Failed to fetch your list. Please try again later.');
57-
}
58-
} finally {
59-
if (!abortController.signal.aborted) {
60-
setLocalLoading(false);
61-
}
62-
}
63-
};
64-
fetchLocalMovies();
65-
return () => {
66-
abortController.abort();
67-
};
68-
}, [myList]);
19+
const { localMovies, localLoading, localError, failedItems, removalNotice } =
20+
useLocalListDetails();
6921

7022
return (
7123
<>
7224
<NavbarHeader />
7325
<MyListContainer>
7426
<h1>My List</h1>
27+
{removalNotice && (
28+
<RemovalNotice >
29+
{removalNotice}
30+
</RemovalNotice>
31+
)}
32+
{failedItems.length > 0 && (
33+
<div style={{ color: 'red' }}>
34+
<p>Some items could not be loaded:</p>
35+
<ul>
36+
{failedItems.map((item, idx) => (
37+
<li key={idx}>
38+
{item.title || item.name || 'Unknown Title'} (ID: {item.id},
39+
Type: {item.media_type})
40+
</li>
41+
))}
42+
</ul>
43+
</div>
44+
)}
7545
{localLoading ? (
7646
<p>Loading...</p>
7747
) : localError ? (

netflix-create-react-vite-app/src/pages/popular-trending/popularAndTrending.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,16 @@ export const PopularAndTrending = () => {
5050
setSearchQuery(e.target.value);
5151
};
5252

53-
const mapShowToMovie = (show: ShowResult): MovieResult => ({
54-
id: show.id,
55-
poster_path: show.poster_path,
56-
overview: show.overview,
57-
title: show.name,
58-
vote_average: show.vote_average,
59-
});
60-
53+
const mapShowToMovie = (
54+
show: ShowResult
55+
): MovieResult & { media_type: 'tv' } => ({
56+
id: show.id,
57+
poster_path: show.poster_path,
58+
overview: show.overview,
59+
title: show.name,
60+
vote_average: show.vote_average,
61+
media_type: 'tv', // Add this line
62+
});
6163

6264
return (
6365
<>

0 commit comments

Comments
 (0)