feat(frontend): add crew related movies/shows to person details page

pull/445/head
sct 4 years ago
parent 9a4da25faa
commit 12127a7763

@ -1,5 +1,5 @@
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useContext } from 'react'; import React, { useContext, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import type { PersonDetail } from '../../../server/models/Person'; import type { PersonDetail } from '../../../server/models/Person';
import type { PersonCombinedCreditsResponse } from '../../../server/interfaces/api/personInterfaces'; import type { PersonCombinedCreditsResponse } from '../../../server/interfaces/api/personInterfaces';
@ -11,6 +11,7 @@ import { LanguageContext } from '../../context/LanguageContext';
const messages = defineMessages({ const messages = defineMessages({
appearsin: 'Appears in', appearsin: 'Appears in',
crewmember: 'Crew Member',
ascharacter: 'as {character}', ascharacter: 'as {character}',
nobiography: 'No biography available.', nobiography: 'No biography available.',
}); });
@ -22,6 +23,7 @@ const PersonDetails: React.FC = () => {
const { data, error } = useSWR<PersonDetail>( const { data, error } = useSWR<PersonDetail>(
`/api/v1/person/${router.query.personId}` `/api/v1/person/${router.query.personId}`
); );
const [showBio, setShowBio] = useState(false);
const { const {
data: combinedCredits, data: combinedCredits,
@ -53,77 +55,151 @@ const PersonDetails: React.FC = () => {
return 1; return 1;
}); });
const sortedCrew = combinedCredits?.crew.sort((a, b) => {
const aDate =
a.mediaType === 'movie'
? a.releaseDate?.slice(0, 4) ?? 0
: a.firstAirDate?.slice(0, 4) ?? 0;
const bDate =
b.mediaType === 'movie'
? b.releaseDate?.slice(0, 4) ?? 0
: b.firstAirDate?.slice(0, 4) ?? 0;
if (aDate > bDate) {
return -1;
}
return 1;
});
const isLoading = !combinedCredits && !errorCombinedCredits; const isLoading = !combinedCredits && !errorCombinedCredits;
return ( return (
<> <>
<div className="flex mt-8 mb-8 flex-col md:flex-row items-center md:items-start"> <div className="flex flex-col items-center mt-8 mb-8 md:flex-row md:items-start">
{data.profilePath && ( {data.profilePath && (
<div <div
style={{ style={{
backgroundImage: `url(https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath})`, backgroundImage: `url(https://image.tmdb.org/t/p/w600_and_h900_bestv2${data.profilePath})`,
}} }}
className="rounded-full w-36 h-36 md:w-44 md:h-44 bg-cover bg-center mb-6 md:mb-0 mr-0 md:mr-6 flex-shrink-0" className="flex-shrink-0 mb-6 mr-0 bg-center bg-cover rounded-full w-36 h-36 md:w-44 md:h-44 md:mb-0 md:mr-6"
/> />
)} )}
<div className="text-gray-300 text-center md:text-left"> <div className="text-center text-gray-300 md:text-left">
<h1 className="text-3xl md:text-4xl text-white mb-4">{data.name}</h1> <h1 className="mb-4 text-3xl text-white md:text-4xl">{data.name}</h1>
<div> <div className="relative">
{data.biography {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
? data.biography <div
: intl.formatMessage(messages.nobiography)} className={`transition-max-height duration-300 ${
showBio
? 'overflow-visible extra-max-height'
: 'overflow-hidden max-h-44'
}`}
onClick={() => setShowBio((show) => !show)}
role="button"
tabIndex={-1}
>
<div className={showBio ? 'h-auto' : 'h-36'}>
{data.biography
? data.biography
: intl.formatMessage(messages.nobiography)}
</div>
{!showBio && (
<div className="absolute bottom-0 left-0 right-0 w-full h-8 bg-gradient-to-t from-gray-900" />
)}
</div>
</div> </div>
</div> </div>
</div> </div>
<div className="md:flex md:items-center md:justify-between mb-4 mt-6"> {(sortedCast ?? []).length > 0 && (
<div className="flex-1 min-w-0"> <>
<div className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center"> <div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<span>{intl.formatMessage(messages.appearsin)}</span> <div className="flex-1 min-w-0">
<div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
<span>{intl.formatMessage(messages.appearsin)}</span>
</div>
</div>
</div> </div>
</div> <ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
</div> {sortedCast?.map((media, index) => {
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8"> return (
{sortedCast?.map((media) => { <li
return ( key={`list-cast-item-${media.id}-${index}`}
<li className="flex flex-col items-center col-span-1 text-center"
key={`list-cast-item-${media.id}`} >
className="col-span-1 flex flex-col text-center items-center" <TitleCard
> id={media.id}
<TitleCard title={
id={media.id} media.mediaType === 'movie' ? media.title : media.name
title={media.mediaType === 'movie' ? media.title : media.name} }
userScore={media.voteAverage} userScore={media.voteAverage}
year={ year={
media.mediaType === 'movie' media.mediaType === 'movie'
? media.releaseDate ? media.releaseDate
: media.firstAirDate : media.firstAirDate
} }
image={media.posterPath} image={media.posterPath}
summary={media.overview} summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'} mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status} status={media.mediaInfo?.status}
canExpand canExpand
/> />
{media.character && ( {media.character && (
<div className="mt-2 text-gray-300 text-xs truncate w-36 sm:w-36 md:w-44 text-center"> <div className="mt-2 text-xs text-center text-gray-300 truncate w-36 sm:w-36 md:w-44">
{intl.formatMessage(messages.ascharacter, { {intl.formatMessage(messages.ascharacter, {
character: media.character, character: media.character,
})} })}
</div> </div>
)} )}
</li> </li>
); );
})} })}
{isLoading && </ul>
[...Array(20)].map((_item, i) => ( </>
<li )}
key={`placeholder-${i}`} {(sortedCrew ?? []).length > 0 && (
className="col-span-1 flex flex-col text-center items-center" <>
> <div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
<TitleCard.Placeholder canExpand /> <div className="flex-1 min-w-0">
</li> <div className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
))} <span>{intl.formatMessage(messages.crewmember)}</span>
</ul> </div>
</div>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{sortedCrew?.map((media, index) => {
return (
<li
key={`list-crew-item-${media.id}-${index}`}
className="flex flex-col items-center col-span-1 text-center"
>
<TitleCard
id={media.id}
title={
media.mediaType === 'movie' ? media.title : media.name
}
userScore={media.voteAverage}
year={
media.mediaType === 'movie'
? media.releaseDate
: media.firstAirDate
}
image={media.posterPath}
summary={media.overview}
mediaType={media.mediaType as 'movie' | 'tv'}
status={media.mediaInfo?.status}
canExpand
/>
{media.job && (
<div className="mt-2 text-xs text-center text-gray-300 truncate w-36 sm:w-36 md:w-44">
{media.job}
</div>
)}
</li>
);
})}
</ul>
</>
)}
{isLoading && <LoadingSpinner />}
</> </>
); );
}; };

@ -7,7 +7,7 @@ body {
} }
.plex-button { .plex-button {
@apply w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 transition ease-in-out duration-150 text-center disabled:opacity-50; @apply flex justify-center w-full px-4 py-2 text-sm font-medium text-center text-white transition duration-150 ease-in-out bg-indigo-600 border border-transparent rounded-md disabled:opacity-50;
background-color: #cc7b19; background-color: #cc7b19;
} }
@ -16,7 +16,7 @@ body {
} }
.titleCard { .titleCard {
@apply relative bg-cover rounded-lg bg-gray-800; @apply relative bg-gray-800 bg-cover rounded-lg;
padding-bottom: 150%; padding-bottom: 150%;
} }
@ -34,7 +34,12 @@ body {
} }
.error-message { .error-message {
@apply flex items-center justify-center text-center text-gray-300 relative top-0 left-0 bottom-0 right-0 h-screen flex-col; @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300;
}
/* Used for animating height */
.extra-max-height {
max-height: 100rem;
} }
/* Hide scrollbar for Chrome, Safari and Opera */ /* Hide scrollbar for Chrome, Safari and Opera */
@ -49,5 +54,5 @@ body {
} }
code { code {
@apply bg-gray-800 py-1 px-2 rounded-md; @apply px-2 py-1 bg-gray-800 rounded-md;
} }

@ -5,6 +5,9 @@ module.exports = {
purge: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'], purge: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}'],
theme: { theme: {
extend: { extend: {
transitionProperty: {
'max-height': 'max-height',
},
fontFamily: { fontFamily: {
sans: ['Inter var', ...defaultTheme.fontFamily.sans], sans: ['Inter var', ...defaultTheme.fontFamily.sans],
}, },

Loading…
Cancel
Save