From ced9d0951e50f64e7bc9d604699fa08a8cf3fdd1 Mon Sep 17 00:00:00 2001 From: Lucas Teixeira Date: Tue, 27 Oct 2020 22:41:09 -0300 Subject: [PATCH] WJ-19 - New: Person and PersonList; WJ-19 - New: Map cast and crew; --- src/components/Person/dtos/ContainerProps.ts | 3 + src/components/Person/dtos/index.ts | 8 ++ src/components/Person/index.tsx | 33 +++++++ src/components/Person/styles.ts | 61 +++++++++++++ src/components/index.ts | 1 + src/containers/PersonList/dtos/index.ts | 12 +++ src/containers/PersonList/index.tsx | 61 +++++++++++++ src/containers/PersonList/styles.ts | 96 ++++++++++++++++++++ src/containers/index.ts | 1 + src/domains/Movie/api/Credits/Response.ts | 28 +----- src/domains/Movie/api/Credits/dtos/Cast.ts | 10 ++ src/domains/Movie/api/Credits/dtos/Crew.ts | 9 ++ src/domains/Movie/api/Details/Response.ts | 2 +- src/domains/Movie/api/Details/index.ts | 32 ++++++- src/screens/Movie/index.tsx | 10 +- src/screens/Movie/styles.ts | 1 + src/shared/utils/arrayToString.ts | 25 +++++ src/shared/utils/index.ts | 1 + 18 files changed, 362 insertions(+), 32 deletions(-) create mode 100644 src/components/Person/dtos/ContainerProps.ts create mode 100644 src/components/Person/dtos/index.ts create mode 100644 src/components/Person/index.tsx create mode 100644 src/components/Person/styles.ts create mode 100644 src/containers/PersonList/dtos/index.ts create mode 100644 src/containers/PersonList/index.tsx create mode 100644 src/containers/PersonList/styles.ts create mode 100644 src/domains/Movie/api/Credits/dtos/Cast.ts create mode 100644 src/domains/Movie/api/Credits/dtos/Crew.ts create mode 100644 src/shared/utils/arrayToString.ts diff --git a/src/components/Person/dtos/ContainerProps.ts b/src/components/Person/dtos/ContainerProps.ts new file mode 100644 index 0000000..a543f27 --- /dev/null +++ b/src/components/Person/dtos/ContainerProps.ts @@ -0,0 +1,3 @@ +export default interface ContainerProps { + large?: boolean; +} diff --git a/src/components/Person/dtos/index.ts b/src/components/Person/dtos/index.ts new file mode 100644 index 0000000..7c57e1d --- /dev/null +++ b/src/components/Person/dtos/index.ts @@ -0,0 +1,8 @@ +import ContainerProps from './ContainerProps'; + +export default interface PersonProps extends ContainerProps { + id: number; + profile: string; + name: string; + character: string; +} diff --git a/src/components/Person/index.tsx b/src/components/Person/index.tsx new file mode 100644 index 0000000..7f5b11c --- /dev/null +++ b/src/components/Person/index.tsx @@ -0,0 +1,33 @@ +import React, { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; + +import Route from 'routes/enums'; +import { + Container, + Profile, + NameContainer, + PersonName, + CharacterName, +} from './styles'; + +import Props from './dtos'; + +const Person: React.FC = ({ large = false, ...person }) => { + const history = useHistory(); + + const handleRedirect = useCallback(() => { + // history.push(`${Route.MOVIE}/${person.id}`); + }, [history, person.id]); + + return ( + + + + {person.name} + {person.character} + + + ); +}; + +export default Person; diff --git a/src/components/Person/styles.ts b/src/components/Person/styles.ts new file mode 100644 index 0000000..b34c50c --- /dev/null +++ b/src/components/Person/styles.ts @@ -0,0 +1,61 @@ +import styled from 'styled-components'; + +import { Color, PosterHeight, PosterWidth, Size } from 'shared/enums'; +import ContainerProps from './dtos/ContainerProps'; + +export const Container = styled.div` + position: relative; + width: ${props => (props.large ? PosterWidth.Large : PosterWidth.Default)}; + height: ${props => (props.large ? PosterHeight.Large : PosterHeight.Default)}; + border-radius: ${Size.Smallest}; + overflow: hidden; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.5); + + display: flex; + align-items: center; + justify-content: center; + + &:hover { + cursor: pointer; + + img { + width: 110%; + height: 110%; + } + } +`; + +export const Profile = styled.img` + margin-top: -5rem; + width: 100%; + height: 100%; + object-fit: cover; + transition: width 0.2s, height 0.2s; +`; + +export const NameContainer = styled.div` + position: absolute; + bottom: 0; + height: 7.4rem; + width: 100%; + background: ${Color.Fill}; + overflow: hidden; + padding: ${Size.Smallest}; +`; + +export const PersonName = styled.div` + font-size: ${Size.Small}; + color: ${Color.Secondary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export const CharacterName = styled.div` + font-size: ${Size.Small}; + color: ${Color.Text}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; diff --git a/src/components/index.ts b/src/components/index.ts index fdcc89e..b5236b6 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -5,4 +5,5 @@ export { default as EnvironmentLabel } from './EnvironmentLabel'; export { default as Input } from './Input'; export { default as Movie } from './Movie'; export { default as MovieHighlight } from './MovieHighlight'; +export { default as Person } from './Person'; export { default as Tooltip } from './Tooltip'; diff --git a/src/containers/PersonList/dtos/index.ts b/src/containers/PersonList/dtos/index.ts new file mode 100644 index 0000000..bfc1e5d --- /dev/null +++ b/src/containers/PersonList/dtos/index.ts @@ -0,0 +1,12 @@ +import DefaultProps from 'shared/dtos'; +import MovieProps from 'components/Movie/dtos'; +import FavoriteProps from 'domains/Favorites/api/List/Response'; +import { Color } from 'shared/enums'; + +export default interface Props extends DefaultProps { + title?: string; + data: any[]; + isLoading?: boolean; + loaderColor?: Color; + message?: string; +} diff --git a/src/containers/PersonList/index.tsx b/src/containers/PersonList/index.tsx new file mode 100644 index 0000000..33fa697 --- /dev/null +++ b/src/containers/PersonList/index.tsx @@ -0,0 +1,61 @@ +import React from 'react'; + +import { ReactComponent as Loading } from 'assets/loading.svg'; + +import { Wrapper } from 'components/Layout'; +import Person from 'components/Person'; +import { + Container, + Title, + LoadingContainer, + ListContainer, + ListContent, + MessageContainer, +} from './styles'; + +import Props from './dtos'; + +const PersonList: React.FC = ({ + theme, + background, + color, + title, + data, + isLoading = false, + loaderColor, + message = 'Não há resultados.', +}) => { + return ( + + + {title && ( + + {title} + + )} + + {isLoading && ( + + + + )} + + {!isLoading && data.length > 0 && ( + + + {data.map(person => ( + + ))} + + + )} + + {!isLoading && data.length === 0 && ( + {message} + )} + + + ); +}; + +export default PersonList; diff --git a/src/containers/PersonList/styles.ts b/src/containers/PersonList/styles.ts new file mode 100644 index 0000000..98b8426 --- /dev/null +++ b/src/containers/PersonList/styles.ts @@ -0,0 +1,96 @@ +import styled from 'styled-components'; + +import DefaultProps from 'shared/dtos'; +import { Container as DefaultContainer } from 'components/Layout'; +import { Color, PosterHeight, Size } from 'shared/enums'; +import { getColor } from 'shared/utils'; + +interface LoadingProps { + loaderColor?: string; +} + +export const Container = styled(DefaultContainer)` + padding: ${Size.Medium} 0; + display: flex; + flex-direction: column; +`; + +export const Title = styled.h2` + margin-bottom: ${Size.Small}; + color: ${props => getColor(props.theme, props.color)}; + + @media (max-width: 1280px) { + margin-left: ${Size.Medium}; + } +`; + +export const LoadingContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: calc(${PosterHeight.Default}); + + svg { + height: ${Size.Largest}; + width: ${Size.Largest}; + fill: ${props => getColor(props.theme, props.loaderColor)}; + } +`; + +export const ListContainer = styled.div` + height: calc(${PosterHeight.Default} + 5px); + overflow-y: hidden; + overflow-x: auto; + + scroll-snap-type: x mandatory; + -webkit-overflow-scrolling: touch; + scroll-behavior: smooth; + + -ms-overflow-style: none; + scrollbar-width: 10px; + &::-webkit-scrollbar { + /* visibility: visible; */ + display: none; + } +`; + +export const ListContent = styled.div` + display: inline-flex; + padding: 0 2px; + + @media (max-width: 1280px) { + margin-left: ${Size.Medium}; + margin-right: ${Size.Smallest}; + } + + & > div { + flex: 1 0 auto; + scroll-snap-align: start; + scroll-margin-left: ${Size.Large}; + margin-right: ${Size.Medium}; + + @media (max-width: 1280px) { + scroll-margin-left: ${Size.Medium}; + margin-right: ${Size.Smallest}; + } + } + + & > div:last-child { + margin-right: 0; + + @media (max-width: 1280px) { + margin-right: ${Size.Default}; + } + } +`; + +export const MessageContainer = styled.div` + display: flex; + align-items: center; + height: calc(${PosterHeight.Default} / 3); + color: ${Color.Text}; + + @media (max-width: 1280px) { + margin-left: ${Size.Medium}; + } +`; diff --git a/src/containers/index.ts b/src/containers/index.ts index a1a2bae..d40e035 100644 --- a/src/containers/index.ts +++ b/src/containers/index.ts @@ -2,3 +2,4 @@ export { default as Footer } from './Footer'; export { default as Header } from './Header'; export { default as Highlights } from './Highlights'; export { default as MovieList } from './MovieList'; +export { default as PersonList } from './PersonList'; diff --git a/src/domains/Movie/api/Credits/Response.ts b/src/domains/Movie/api/Credits/Response.ts index a71e2e2..cd876fb 100644 --- a/src/domains/Movie/api/Credits/Response.ts +++ b/src/domains/Movie/api/Credits/Response.ts @@ -1,25 +1,7 @@ +import Cast from 'domains/Movie/api/Credits/dtos/Cast'; +import Crew from 'domains/Movie/api/Credits/dtos/Crew'; + export default interface Response { - cast: [ - { - castId: number; - character: string; - creditId: string; - gender?: number; - id: number; - name: string; - order: number; - profilePath?: string; - }, - ]; - crew: [ - { - creditId: string; - department: string; - gender?: number; - id: number; - job: string; - name: string; - profilePath?: string; - }, - ]; + cast: Cast[]; + crew: Crew[]; } diff --git a/src/domains/Movie/api/Credits/dtos/Cast.ts b/src/domains/Movie/api/Credits/dtos/Cast.ts new file mode 100644 index 0000000..a647d31 --- /dev/null +++ b/src/domains/Movie/api/Credits/dtos/Cast.ts @@ -0,0 +1,10 @@ +export default interface Cast { + castId: number; + character: string; + creditId: string; + gender?: number; + id: number; + name: string; + order: number; + profile: string; +} diff --git a/src/domains/Movie/api/Credits/dtos/Crew.ts b/src/domains/Movie/api/Credits/dtos/Crew.ts new file mode 100644 index 0000000..dc1054b --- /dev/null +++ b/src/domains/Movie/api/Credits/dtos/Crew.ts @@ -0,0 +1,9 @@ +export default interface Crew { + creditId: string; + department: string; + gender?: number; + id: number; + job: string; + name: string; + profile: string; +} diff --git a/src/domains/Movie/api/Details/Response.ts b/src/domains/Movie/api/Details/Response.ts index 83461c7..adbd028 100644 --- a/src/domains/Movie/api/Details/Response.ts +++ b/src/domains/Movie/api/Details/Response.ts @@ -13,7 +13,7 @@ export default interface Response { name: string; }, ]; - genresNames: string[]; + genresNames: string; homepage?: string; id: number; originalTitle: string; diff --git a/src/domains/Movie/api/Details/index.ts b/src/domains/Movie/api/Details/index.ts index ab98a72..7fe9b30 100644 --- a/src/domains/Movie/api/Details/index.ts +++ b/src/domains/Movie/api/Details/index.ts @@ -4,8 +4,12 @@ import Params from 'domains/Movie/api/Details/Params'; import RawResponse from 'domains/Movie/api/Details/RawResponse'; import Response from 'domains/Movie/api/Details/Response'; import Recommendations from 'domains/Movie/api/Recommendations/Response'; +import Credits from 'domains/Movie/api/Credits/Response'; import formatDate from 'shared/utils/formatDate'; import formatTmdbImage from 'shared/utils/formatTmdbImage'; +import Crew from 'domains/Movie/api/Credits/dtos/Crew'; +import Cast from 'domains/Movie/api/Credits/dtos/Cast'; +import { arrayToString } from 'shared/utils'; const Details = async (movieId: number, params?: Params): Promise => { const response = await rawPopular(movieId, params); @@ -29,7 +33,7 @@ const parseResponse = (movie: RawResponse): Response => { overview: movie.overview, budget: movie.budget, genres: movie.genres, - genresNames: movie.genres.map(genre => genre.name), + genresNames: arrayToString(movie.genres, 'name'), id: movie.id, originalTitle: movie.original_title, title: movie.title, @@ -39,7 +43,6 @@ const parseResponse = (movie: RawResponse): Response => { runtime: `${movie.runtime} min`, tagline: movie.tagline, - credits: movie.credits, directorName: movie.credits?.crew.find( person => person.job.toUpperCase() === 'DIRECTOR', )?.name, @@ -60,7 +63,30 @@ const parseResponse = (movie: RawResponse): Response => { }), ) as Recommendations[]; - parsedMovie = { ...parsedMovie, recommendations }; + const cast = movie.credits?.cast.slice(0, 15).map(person => ({ + order: person.order, + id: person.id, + name: person.name, + character: person.character, + castId: person.cast_id, + creditId: person.credit_id, + gender: person.gender, + profile: formatTmdbImage({ value: person.profile_path }), + })) as Cast[]; + + const crew = movie.credits?.crew.map(person => ({ + id: person.id, + name: person.name, + job: person.job, + department: person.department, + creditId: person.credit_id, + gender: person.gender, + profile: formatTmdbImage({ value: person.profile_path }), + })) as Crew[]; + + const credits = { cast, crew }; + + parsedMovie = { ...parsedMovie, recommendations, credits }; return parsedMovie; }; diff --git a/src/screens/Movie/index.tsx b/src/screens/Movie/index.tsx index b4398ce..e6c786a 100644 --- a/src/screens/Movie/index.tsx +++ b/src/screens/Movie/index.tsx @@ -7,7 +7,7 @@ import { Color } from 'shared/enums'; import { Details } from 'domains/Movie/api'; import { ColumnLayout, Container, Movie as Poster } from 'components'; -import { Footer, Header, MovieList } from 'containers'; +import { Header, PersonList, MovieList, Footer } from 'containers'; import { ContentContainer, MovieContainer, @@ -88,18 +88,18 @@ const Movie: React.FC = () => { -