Skip to content

Commit e4e94fe

Browse files
committed
Add a reusable component for the detail page.
1 parent 19485b6 commit e4e94fe

File tree

11 files changed

+157
-134
lines changed

11 files changed

+157
-134
lines changed

netflix-create-react-vite-app/src/app/App.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import React from 'react';
22
import { Route, Routes } from 'react-router-dom';
33
import { Homepage } from '../pages/home/homePage';
4-
import { MovieDetail } from '../pages/movies/movie-details/movieDetail';
4+
import { MediaDetail } from '../components/details/MediaDetail';
55
import { Movies } from '../pages/movies/movies';
66
import { MyList } from '../pages/my-list/myList';
77
import { PopularAndTrending } from '../pages/popular-trending/popularAndTrending';
88
import { Shows } from '../pages/shows/shows';
9+
910
import { GlobalStyle } from '../styles/global';
1011

1112
export const App = () => {
@@ -18,7 +19,8 @@ export const App = () => {
1819
<Route element={<Movies />} path='/movies' />
1920
<Route element={<PopularAndTrending />} path='/popular-trending' />
2021
<Route element={<MyList />} path='/my-list' />
21-
<Route element={<MovieDetail />} path='/movies/:id' />
22+
<Route element={<MediaDetail type="movie" />} path='/movies/:id' />
23+
<Route element={<MediaDetail type="tv" />} path='/shows/:id' />
2224
</Routes>
2325
</>
2426
);

netflix-create-react-vite-app/src/components/card/card.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type CardProps = {
1717
key: number;
1818
id: number;
1919
media_type: 'movie' | 'tv';
20+
onClick?: () => void;
2021
};
2122

2223
export const Card = ({
@@ -27,11 +28,13 @@ export const Card = ({
2728
vote_average,
2829
id,
2930
media_type,
31+
onClick,
3032
}: CardProps) => {
3133
const [isFlipped, setIsFlipped] = useState(false);
3234

3335
return (
3436
<CardContainer
37+
onClick={onClick}
3538
onMouseEnter={() => setIsFlipped(true)}
3639
onMouseLeave={() => setIsFlipped(false)}
3740
>

netflix-create-react-vite-app/src/pages/movies/movie-details/movieDetail.tsx renamed to netflix-create-react-vite-app/src/components/details/MediaDetail.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,55 @@
1+
12
import React, { useEffect, useState } from 'react';
2-
import styled from 'styled-components';
33
import { useParams } from 'react-router-dom';
4-
import { Spinner } from '../../../components/spinner/spinner';
5-
import { imageUrl, VITE_API_KEY } from '../../../utils/api';
4+
import { Spinner } from '../spinner/spinner';
5+
import { imageUrl, VITE_API_KEY } from '../../utils/api';
66
import {
77
StyledContainer,
88
CastSection,
99
CastList,
1010
MainColumns,
1111
LeftColumn,
1212
RightColumn,
13-
} from './movieDetails-styles';
13+
} from './details-styles';
1414
import { CastMember } from './castMember';
15-
import { MoviePoster } from './MoviePoster';
16-
import { MovieInfo } from './movieInfo';
15+
import { MediaPoster } from './MediaPoster';
16+
import { MediaInfo } from './MediaInfo';
17+
18+
interface MediaDetailProps {
19+
type: 'movie' | 'tv';
20+
}
1721

18-
export const MovieDetail = () => {
22+
export const MediaDetail: React.FC<MediaDetailProps> = ({ type }) => {
1923
const { id } = useParams<{ id: string }>();
20-
const [movie, setMovie] = useState<any>(null);
24+
const [media, setMedia] = useState<any>(null);
2125
const [loading, setLoading] = useState(true);
2226
const [error, setError] = useState<string | null>(null);
2327
const [cast, setCast] = useState<any[]>([]);
2428

2529
useEffect(() => {
26-
const fetchMovie = async () => {
30+
const fetchMedia = async () => {
2731
try {
2832
const response = await fetch(
29-
`https://api.themoviedb.org/3/movie/${id}?api_key=${VITE_API_KEY}`
33+
`https://api.themoviedb.org/3/${type}/${id}?api_key=${VITE_API_KEY}`
3034
);
3135
if (!response.ok) throw new Error('Network response was not ok');
3236
const data = await response.json();
33-
setMovie(data);
37+
setMedia(data);
3438
} catch (err) {
35-
setError('Failed to fetch movie details. Please try again later.');
39+
setError(`Failed to fetch ${type} details. Please try again later.`);
3640
} finally {
3741
setLoading(false);
3842
}
3943
};
40-
fetchMovie();
41-
}, [id]);
44+
fetchMedia();
45+
}, [id, type]);
4246

4347
useEffect(() => {
4448
const fetchCast = async () => {
4549
if (!id) return;
4650
try {
4751
const response = await fetch(
48-
`https://api.themoviedb.org/3/movie/${id}/credits?api_key=${VITE_API_KEY}`
52+
`https://api.themoviedb.org/3/${type}/${id}/credits?api_key=${VITE_API_KEY}`
4953
);
5054
if (!response.ok) throw new Error('Failed to fetch cast');
5155
const data = await response.json();
@@ -55,7 +59,7 @@ export const MovieDetail = () => {
5559
}
5660
};
5761
fetchCast();
58-
}, [id]);
62+
}, [id, type]);
5963

6064
if (loading)
6165
return (
@@ -70,16 +74,16 @@ export const MovieDetail = () => {
7074
<p>{error}</p>
7175
</div>
7276
);
73-
if (!movie) return null;
77+
if (!media) return null;
7478

7579
return (
7680
<StyledContainer>
7781
<MainColumns>
7882
<LeftColumn>
79-
<MoviePoster
80-
title={movie.title}
81-
posterPath={movie.poster_path}
82-
tagline={movie.tagline}
83+
<MediaPoster
84+
title={media.title || media.name}
85+
posterPath={media.poster_path}
86+
tagline={media.tagline}
8387
imageUrl={imageUrl}
8488
/>
8589
</LeftColumn>
@@ -103,7 +107,7 @@ export const MovieDetail = () => {
103107
</CastList>
104108
</CastSection>
105109
)}
106-
<MovieInfo movie={movie} />
110+
<MediaInfo media={media} type={type} />
107111
</RightColumn>
108112
</MainColumns>
109113
</StyledContainer>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
import { InfoColumnsWrapper, InfoColumn, InfoText, InfoLabel } from './details-styles';
3+
4+
interface MediaInfoProps {
5+
media: any;
6+
type: 'movie' | 'tv';
7+
}
8+
9+
export const MediaInfo: React.FC<MediaInfoProps> = ({ media, type }) => (
10+
<InfoColumnsWrapper>
11+
<InfoColumn>
12+
<InfoText>
13+
<InfoLabel>Status:</InfoLabel> {media.status}
14+
</InfoText>
15+
<InfoText>
16+
<InfoLabel>Original Language:</InfoLabel> {media.original_language}
17+
</InfoText>
18+
{type === 'movie' ? (
19+
<>
20+
<InfoText>
21+
<InfoLabel>Release Date:</InfoLabel> {media.release_date}
22+
</InfoText>
23+
<InfoText>
24+
<InfoLabel>Runtime:</InfoLabel> {media.runtime} min
25+
</InfoText>
26+
</>
27+
) : (
28+
<>
29+
<InfoText>
30+
<InfoLabel>First Air Date:</InfoLabel> {media.first_air_date}
31+
</InfoText>
32+
<InfoText>
33+
<InfoLabel>Last Air Date:</InfoLabel> {media.last_air_date}
34+
</InfoText>
35+
<InfoText>
36+
<InfoLabel>Number of Seasons:</InfoLabel> {media.number_of_seasons}
37+
</InfoText>
38+
<InfoText>
39+
<InfoLabel>Number of Episodes:</InfoLabel> {media.number_of_episodes}
40+
</InfoText>
41+
</>
42+
)}
43+
<InfoText>
44+
<InfoLabel>Genres:</InfoLabel>{' '}
45+
{media.genres?.map((g: any) => g.name).join(', ')}
46+
</InfoText>
47+
<InfoText>
48+
<InfoLabel>Rating:</InfoLabel> {media.vote_average}
49+
</InfoText>
50+
<InfoText>
51+
<InfoLabel>Vote Count:</InfoLabel> {media.vote_count}
52+
</InfoText>
53+
</InfoColumn>
54+
55+
</InfoColumnsWrapper>
56+
);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
import { PosterContainer, PosterImage, Title, Tagline } from './details-styles';
3+
4+
interface MediaPosterProps {
5+
title: string;
6+
posterPath: string;
7+
tagline: string;
8+
imageUrl: string;
9+
}
10+
11+
export const MediaPoster: React.FC<MediaPosterProps> = ({
12+
title,
13+
posterPath,
14+
tagline,
15+
imageUrl,
16+
}) => (
17+
<PosterContainer>
18+
<Title>{title}</Title>
19+
<PosterImage src={`${imageUrl}${posterPath}`} alt={title} />
20+
<Tagline>{tagline}</Tagline>
21+
</PosterContainer>
22+
);

netflix-create-react-vite-app/src/pages/movies/movie-details/castMember.tsx renamed to netflix-create-react-vite-app/src/components/details/castMember.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
CastName,
55
CastCharacter,
66
CastImageFallback,
7-
} from './movieDetails-styles';
7+
} from './details-styles';
88
import { CastMemberProps } from '../../utils/types/types';
99

1010

netflix-create-react-vite-app/src/pages/movies/movie-details/movieDetails-styles.tsx renamed to netflix-create-react-vite-app/src/components/details/details-styles.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const StyledContainer = styled.div`
1111
`;
1212

1313
export const PosterImage = styled.img`
14+
margin-top: ${({ theme }) => theme.space[4]};
1415
width: 300px;
1516
border-radius: ${({ theme }) => theme.borderRadius[2]};
1617
`;
@@ -19,30 +20,31 @@ export const DetailFlex = styled.div`
1920
display: flex;
2021
justify-content: flex-start;
2122
align-items: center;
22-
gap: ${({ theme }) => theme.space[4]};
2323
margin-bottom: ${({ theme }) => theme.space[4]};
2424
`;
2525

2626
export const InfoColumnsWrapper = styled.div`
2727
display: flex;
28-
flex-direction: row;
29-
gap: ${({ theme }) => theme.space[4]};
28+
flex-direction: column;
3029
height: 100%;
31-
align-items: stretch;
30+
3231
`;
3332

3433
export const InfoColumn = styled.div`
3534
display: flex;
3635
flex-direction: column;
37-
justify-content: space-between;
36+
justify-content: space-evenly;
3837
flex: 1;
3938
min-width: 0;
39+
line-height:${({ theme }) => theme.lineHeight[3]};
40+
padding: ${({ theme }) => theme.space[3]};
41+
border: 1px solid ${({ theme }) => theme.colors.grey[2]};
42+
4043
`;
4144

4245
export const InfoText = styled.p`
43-
margin: 0.5rem 0;
46+
margin:${({ theme }) => theme.space[1]} ;
4447
font-size: ${({ theme }) => theme.fontSize[3]};
45-
4648
`;
4749

4850
export const InfoLabel = styled.span`
@@ -54,6 +56,7 @@ export const CastSection = styled.div`
5456
display: flex;
5557
flex-direction: column;
5658
align-items: center;
59+
margin-bottom: ${({ theme }) => theme.space[6]};
5760
`;
5861

5962
export const CastList = styled.div`
@@ -70,7 +73,7 @@ export const ImageColumn = styled.div`
7073
align-items: center;
7174
`;
7275

73-
export const TagLine = styled.p`
76+
export const Tagline = styled.p`
7477
width: 300px;
7578
flex-wrap: wrap;
7679
margin-top: ${({ theme }) => theme.space[3]};
@@ -83,7 +86,7 @@ export const TagLine = styled.p`
8386
export const MainColumns = styled.div`
8487
display: flex;
8588
flex-direction: row;
86-
gap: ${({ theme }) => theme.space[11]};
89+
gap: ${({ theme }) => theme.space[4]};
8790
align-items: flex-start;
8891
`;
8992

@@ -100,7 +103,7 @@ export const RightColumn = styled.div`
100103
flex: 1;
101104
`;
102105

103-
export const MovieTitle = styled.h2`
106+
export const Title = styled.h2`
104107
width: 300px;
105108
flex-wrap: wrap;
106109
align-items: center;
@@ -150,3 +153,10 @@ export const CastImageFallback = styled.div`
150153
color: ${({ theme }) => theme.colors.blue};
151154
font-size: 14px;
152155
`;
156+
157+
export const PosterContainer = styled.div`
158+
display: flex;
159+
flex-direction: column;
160+
align-items: center;
161+
justify-content:center;
162+
`;

netflix-create-react-vite-app/src/pages/movies/movie-details/MoviePoster.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)