import { ArrowCircleRightIcon, CloudIcon, CogIcon, ExclamationIcon, FilmIcon, PlayIcon, TicketIcon, } from '@heroicons/react/outline'; import { ChevronDoubleDownIcon, ChevronDoubleUpIcon, } from '@heroicons/react/solid'; import { hasFlag } from 'country-flag-icons'; import 'country-flag-icons/3x2/flags.css'; import { uniqBy } from 'lodash'; import Link from 'next/link'; import { useRouter } from 'next/router'; import React, { useEffect, useMemo, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import useSWR from 'swr'; import type { RTRating } from '../../../server/api/rottentomatoes'; import { IssueStatus } from '../../../server/constants/issue'; import { MediaStatus } from '../../../server/constants/media'; import type { MovieDetails as MovieDetailsType } from '../../../server/models/Movie'; 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 type { PlayButtonLink } from '../Common/PlayButton'; import PlayButton 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 Slider from '../Slider'; import StatusBadge from '../StatusBadge'; const messages = defineMessages({ originaltitle: 'Original Title', releasedate: '{releaseCount, plural, one {Release Date} other {Release Dates}}', revenue: 'Revenue', budget: 'Budget', watchtrailer: 'Watch Trailer', originallanguage: 'Original Language', overview: 'Overview', runtime: '{minutes} minutes', cast: 'Cast', recommendations: 'Recommendations', similar: 'Similar Titles', overviewunavailable: 'Overview unavailable.', studio: '{studioCount, plural, one {Studio} other {Studios}}', viewfullcrew: 'View Full Crew', playonplex: 'Play on Plex', play4konplex: 'Play in 4K on Plex', markavailable: 'Mark as Available', mark4kavailable: 'Mark as Available in 4K', showmore: 'Show More', showless: 'Show Less', streamingproviders: 'Currently Streaming On', productioncountries: 'Production {countryCount, plural, one {Country} other {Countries}}', }); interface MovieDetailsProps { movie?: MovieDetailsType; } const MovieDetails: React.FC = ({ movie }) => { const settings = useSettings(); const { user, hasPermission } = useUser(); const router = useRouter(); const intl = useIntl(); const { locale } = useLocale(); const [showManager, setShowManager] = useState( router.query.manage == '1' ? true : false ); const minStudios = 3; const [showMoreStudios, setShowMoreStudios] = useState(false); const [showIssueModal, setShowIssueModal] = useState(false); const { data, error, mutate: revalidate, } = useSWR(`/api/v1/movie/${router.query.movieId}`, { fallbackData: movie, }); const { data: ratingData } = useSWR( `/api/v1/movie/${router.query.movieId}/ratings` ); const sortedCrew = useMemo( () => sortCrewPriority(data?.credits.crew ?? []), [data] ); useEffect(() => { setShowManager(router.query.manage == '1' ? true : false); }, [router.query.manage]); const [plexUrl, setPlexUrl] = useState(data?.mediaInfo?.plexUrl); const [plexUrl4k, setPlexUrl4k] = useState(data?.mediaInfo?.plexUrl4k); useEffect(() => { if (data) { if ( /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.userAgent === 'MacIntel' && navigator.maxTouchPoints > 1) ) { setPlexUrl(data.mediaInfo?.iOSPlexUrl); setPlexUrl4k(data.mediaInfo?.iOSPlexUrl4k); } else { setPlexUrl(data.mediaInfo?.plexUrl); setPlexUrl4k(data.mediaInfo?.plexUrl4k); } } }, [ data, data?.mediaInfo?.iOSPlexUrl, data?.mediaInfo?.iOSPlexUrl4k, data?.mediaInfo?.plexUrl, data?.mediaInfo?.plexUrl4k, ]); if (!data && !error) { return ; } if (!data) { return ; } const showAllStudios = data.productionCompanies.length <= minStudios + 1; const mediaLinks: PlayButtonLink[] = []; if ( plexUrl && hasPermission([Permission.REQUEST, Permission.REQUEST_MOVIE], { type: 'or', }) ) { mediaLinks.push({ text: intl.formatMessage(messages.playonplex), url: plexUrl, svg: , }); } if ( settings.currentSettings.movie4kEnabled && plexUrl4k && hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], { type: 'or', }) ) { mediaLinks.push({ text: intl.formatMessage(messages.play4konplex), url: 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 releases = data.releases.results.find( (r) => r.iso_3166_1 === region )?.release_dates; // Release date types: // 1. Premiere // 2. Theatrical (limited) // 3. Theatrical // 4. Digital // 5. Physical // 6. TV const filteredReleases = uniqBy( releases?.filter((r) => r.type > 2 && r.type < 6), 'type' ); const movieAttributes: React.ReactNode[] = []; const certification = releases?.find((r) => r.certification)?.certification; if (certification) { movieAttributes.push( {certification} ); } if (data.runtime) { movieAttributes.push( intl.formatMessage(messages.runtime, { minutes: data.runtime }) ); } if (data.genres.length) { movieAttributes.push( data.genres .map((g) => ( {g.name} )) .reduce((prev, curr) => ( <> {intl.formatMessage(globalMessages.delimitedlist, { a: prev, b: curr, })} )) ); } const streamingProviders = data?.watchProviders?.find((provider) => provider.iso_3166_1 === region) ?.flatrate ?? []; return (
{data.backdropPath && (
)} setShowIssueModal(false)} show={showIssueModal} mediaType="movie" tmdbId={data.id} /> { setShowManager(false); router.push({ pathname: router.pathname, query: { movieId: router.query.movieId }, }); }} revalidate={() => revalidate()} show={showManager} />
0} tmdbId={data.mediaInfo?.tmdbId} mediaType="movie" plexUrl={data.mediaInfo?.plexUrl} /> {settings.currentSettings.movie4kEnabled && hasPermission( [ Permission.MANAGE_REQUESTS, Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE, ], { type: 'or', } ) && ( 0 } tmdbId={data.mediaInfo?.tmdbId} mediaType="movie" plexUrl={data.mediaInfo?.plexUrl4k} /> )}

{data.title}{' '} {data.releaseDate && ( ({data.releaseDate.slice(0, 4)}) )}

{movieAttributes.length > 0 && movieAttributes .map((t, k) => {t}) .reduce((prev, curr) => ( <> {prev} | {curr} ))}
revalidate()} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || (settings.currentSettings.movie4kEnabled && hasPermission( [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], { type: 'or', } ) && data.mediaInfo?.status4k === MediaStatus.AVAILABLE)) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { type: 'or', } ) && ( )} {hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && ( )}
{data.tagline &&
{data.tagline}
}

{intl.formatMessage(messages.overview)}

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

{sortedCrew.length > 0 && ( <>
    {sortedCrew.slice(0, 6).map((person) => (
  • {person.job} {person.name}
  • ))}
)}
{data.collection && (
)}
{(!!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.originalTitle && data.originalLanguage !== locale.slice(0, 2) && (
{intl.formatMessage(messages.originaltitle)} {data.originalTitle}
)}
{intl.formatMessage(globalMessages.status)} {data.status}
{filteredReleases && filteredReleases.length > 0 ? (
{intl.formatMessage(messages.releasedate, { releaseCount: filteredReleases.length, })} {filteredReleases.map((r, i) => ( {r.type === 3 ? ( // Theatrical ) : r.type === 4 ? ( // Digital ) : ( // Physical )} {intl.formatDate(r.release_date, { year: 'numeric', month: 'long', day: 'numeric', })} ))}
) : ( data.releaseDate && (
{intl.formatMessage(messages.releasedate, { releaseCount: 1, })} {intl.formatDate(data.releaseDate, { year: 'numeric', month: 'long', day: 'numeric', })}
) )} {data.revenue > 0 && (
{intl.formatMessage(messages.revenue)} {intl.formatNumber(data.revenue, { currency: 'USD', style: 'currency', })}
)} {data.budget > 0 && (
{intl.formatMessage(messages.budget)} {intl.formatNumber(data.budget, { currency: 'USD', style: 'currency', })}
)} {data.originalLanguage && ( )} {data.productionCountries.length > 0 && (
{intl.formatMessage(messages.productioncountries, { countryCount: data.productionCountries.length, })} {data.productionCountries.map((c) => { return ( {hasFlag(c.iso_3166_1) && ( )} {intl.formatDisplayName(c.iso_3166_1, { type: 'region', fallback: 'none', }) ?? c.name} ); })}
)} {data.productionCompanies.length > 0 && (
{intl.formatMessage(messages.studio, { studioCount: data.productionCompanies.length, })} {data.productionCompanies .slice( 0, showAllStudios || showMoreStudios ? data.productionCompanies.length : minStudios ) .map((s) => { return ( {s.name} ); })} {!showAllStudios && ( )}
)} {!!streamingProviders.length && (
{intl.formatMessage(messages.streamingproviders)} {streamingProviders.map((p) => { return ( {p.name} ); })}
)}
{data.credits.cast.length > 0 && ( <> ( ))} /> )}
); }; export default MovieDetails;