import { ArrowCircleRightIcon, CogIcon, ExclamationIcon, FilmIcon, PlayIcon, } from '@heroicons/react/outline'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import type { RTRating } from '../../../server/api/rottentomatoes'; import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb/constants'; import { IssueStatus } from '../../../server/constants/issue'; import { MediaStatus } from '../../../server/constants/media'; import { Crew } from '../../../server/models/common'; import { TvDetails as TvDetailsType } from '../../../server/models/Tv'; import RTAudFresh from '../../assets/rt_aud_fresh.svg'; import RTAudRotten from '../../assets/rt_aud_rotten.svg'; import RTFresh from '../../assets/rt_fresh.svg'; import RTRotten from '../../assets/rt_rotten.svg'; import TmdbLogo from '../../assets/tmdb_logo.svg'; import useLocale from '../../hooks/useLocale'; import useSettings from '../../hooks/useSettings'; import { Permission, useUser } from '../../hooks/useUser'; import globalMessages from '../../i18n/globalMessages'; import Error from '../../pages/_error'; import { sortCrewPriority } from '../../utils/creditHelpers'; import Button from '../Common/Button'; import CachedImage from '../Common/CachedImage'; import LoadingSpinner from '../Common/LoadingSpinner'; import PageTitle from '../Common/PageTitle'; import PlayButton, { PlayButtonLink } from '../Common/PlayButton'; import ExternalLinkBlock from '../ExternalLinkBlock'; import IssueModal from '../IssueModal'; import ManageSlideOver from '../ManageSlideOver'; import MediaSlider from '../MediaSlider'; import PersonCard from '../PersonCard'; import RequestButton from '../RequestButton'; import RequestModal from '../RequestModal'; import Slider from '../Slider'; import StatusBadge from '../StatusBadge'; const messages = defineMessages({ firstAirDate: 'First Air Date', nextAirDate: 'Next Air Date', originallanguage: 'Original Language', overview: 'Overview', cast: 'Cast', recommendations: 'Recommendations', similar: 'Similar Series', watchtrailer: 'Watch Trailer', overviewunavailable: 'Overview unavailable.', originaltitle: 'Original Title', showtype: 'Series Type', anime: 'Anime', network: '{networkCount, plural, one {Network} other {Networks}}', viewfullcrew: 'View Full Crew', playonplex: 'Play on Plex', play4konplex: 'Play in 4K on Plex', seasons: '{seasonCount, plural, one {# Season} other {# Seasons}}', episodeRuntime: 'Episode Runtime', episodeRuntimeMinutes: '{runtime} minutes', streamingproviders: 'Currently Streaming On', }); interface TvDetailsProps { tv?: TvDetailsType; } const TvDetails: React.FC = ({ tv }) => { const settings = useSettings(); const { user, hasPermission } = useUser(); const router = useRouter(); const intl = useIntl(); const { locale } = useLocale(); const [showRequestModal, setShowRequestModal] = useState(false); const [showManager, setShowManager] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/tv/${router.query.tvId}`, { initialData: tv, } ); const { data: ratingData } = useSWR( `/api/v1/tv/${router.query.tvId}/ratings` ); const sortedCrew = useMemo( () => sortCrewPriority(data?.credits.crew ?? []), [data] ); if (!data && !error) { return ; } if (!data) { return ; } const mediaLinks: PlayButtonLink[] = []; if (data.mediaInfo?.plexUrl) { mediaLinks.push({ text: intl.formatMessage(messages.playonplex), url: data.mediaInfo?.plexUrl, svg: , }); } if ( data.mediaInfo?.plexUrl4k && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { type: 'or', }) ) { mediaLinks.push({ text: intl.formatMessage(messages.play4konplex), url: data.mediaInfo?.plexUrl4k, svg: , }); } const trailerUrl = data.relatedVideos ?.filter((r) => r.type === 'Trailer') .sort((a, b) => a.size - b.size) .pop()?.url; if (trailerUrl) { mediaLinks.push({ text: intl.formatMessage(messages.watchtrailer), url: trailerUrl, svg: , }); } const region = user?.settings?.region ? user.settings.region : settings.currentSettings.region ? settings.currentSettings.region : 'US'; const seriesAttributes: React.ReactNode[] = []; const contentRating = data.contentRatings.results.find( (r) => r.iso_3166_1 === region )?.rating; if (contentRating) { seriesAttributes.push( {contentRating} ); } const seasonCount = data.seasons.filter( (season) => season.seasonNumber !== 0 ).length; if (seasonCount) { seriesAttributes.push( intl.formatMessage(messages.seasons, { seasonCount: seasonCount }) ); } if (data.genres.length) { seriesAttributes.push( data.genres .map((g) => ( {g.name} )) .reduce((prev, curr) => ( <> {intl.formatMessage(globalMessages.delimitedlist, { a: prev, b: curr, })} )) ); } const isComplete = seasonCount <= ( data.mediaInfo?.seasons.filter( (season) => season.status === MediaStatus.AVAILABLE ) ?? [] ).length; const is4kComplete = seasonCount <= ( data.mediaInfo?.seasons.filter( (season) => season.status4k === MediaStatus.AVAILABLE ) ?? [] ).length; const streamingProviders = data?.watchProviders?.find((provider) => provider.iso_3166_1 === region) ?.flatrate ?? []; return (
{data.backdropPath && (
)} setShowIssueModal(false)} show={showIssueModal} mediaType="tv" tmdbId={data.id} /> { revalidate(); setShowRequestModal(false); }} onCancel={() => setShowRequestModal(false)} /> setShowManager(false)} revalidate={() => revalidate()} show={showManager} />
0} plexUrl={data.mediaInfo?.plexUrl} serviceUrl={ hasPermission(Permission.ADMIN) ? data.mediaInfo?.serviceUrl : undefined } /> {settings.currentSettings.series4kEnabled && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { type: 'or', }) && ( 0 } plexUrl={data.mediaInfo?.plexUrl4k} serviceUrl={ hasPermission(Permission.ADMIN) ? data.mediaInfo?.serviceUrl4k : undefined } /> )}

{data.name}{' '} {data.firstAirDate && ( ({data.firstAirDate.slice(0, 4)}) )}

{seriesAttributes.length > 0 && seriesAttributes .map((t, k) => {t}) .reduce((prev, curr) => ( <> {prev} | {curr} ))}
revalidate()} tmdbId={data?.id} media={data?.mediaInfo} isShowComplete={isComplete} is4kShowComplete={is4kComplete} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || data.mediaInfo?.status4k === MediaStatus.AVAILABLE || data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || data?.mediaInfo?.status4k === MediaStatus.PARTIALLY_AVAILABLE) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { type: 'or', } ) && ( )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}
{data.tagline &&
{data.tagline}
}

{intl.formatMessage(messages.overview)}

{data.overview ? data.overview : intl.formatMessage(messages.overviewunavailable)}

{sortedCrew.length > 0 && ( <>
    {(data.createdBy.length > 0 ? [ ...data.createdBy.map( (person): Partial => ({ id: person.id, job: 'Creator', name: person.name, }) ), ...sortedCrew, ] : sortedCrew ) .slice(0, 6) .map((person) => (
  • {person.job} {person.name}
  • ))}
)}
{(!!data.voteCount || (ratingData?.criticsRating && !!ratingData?.criticsScore) || (ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
{ratingData?.criticsRating && !!ratingData?.criticsScore && ( {ratingData.criticsRating === 'Rotten' ? ( ) : ( )} {ratingData.criticsScore}% )} {ratingData?.audienceRating && !!ratingData?.audienceScore && ( {ratingData.audienceRating === 'Spilled' ? ( ) : ( )} {ratingData.audienceScore}% )} {!!data.voteCount && ( {data.voteAverage}/10 )}
)} {data.originalName && data.originalLanguage !== locale.slice(0, 2) && (
{intl.formatMessage(messages.originaltitle)} {data.originalName}
)} {data.keywords.some( (keyword) => keyword.id === ANIME_KEYWORD_ID ) && (
{intl.formatMessage(messages.showtype)} {intl.formatMessage(messages.anime)}
)}
{intl.formatMessage(globalMessages.status)} {data.status}
{data.firstAirDate && (
{intl.formatMessage(messages.firstAirDate)} {intl.formatDate(data.firstAirDate, { year: 'numeric', month: 'long', day: 'numeric', })}
)} {data.nextEpisodeToAir && data.nextEpisodeToAir.airDate !== data.firstAirDate && (
{intl.formatMessage(messages.nextAirDate)} {intl.formatDate(data.nextEpisodeToAir.airDate, { year: 'numeric', month: 'long', day: 'numeric', })}
)} {data.episodeRunTime.length > 0 && (
{intl.formatMessage(messages.episodeRuntime)} {intl.formatMessage(messages.episodeRuntimeMinutes, { runtime: data.episodeRunTime[0], })}
)} {data.originalLanguage && ( )} {data.networks.length > 0 && (
{intl.formatMessage(messages.network, { networkCount: data.networks.length, })} {data.networks .map((n) => ( {n.name} )) .reduce((prev, curr) => ( <> {prev}, {curr} ))}
)} {!!streamingProviders.length && (
{intl.formatMessage(messages.streamingproviders)} {streamingProviders.map((p) => { return ( {p.name} ); })}
)}
{data.credits.cast.length > 0 && ( <> ( ))} /> )}
); }; export default TvDetails;