import React, { useState, useContext, useMemo } from 'react'; import { FormattedMessage, defineMessages, FormattedNumber, FormattedDate, useIntl, } from 'react-intl'; import type { MovieDetails as MovieDetailsType } from '../../../server/models/Movie'; import useSWR from 'swr'; import { useRouter } from 'next/router'; import Button from '../Common/Button'; import type { MovieResult } from '../../../server/models/Search'; import Link from 'next/link'; import Slider from '../Slider'; import TitleCard from '../TitleCard'; import PersonCard from '../PersonCard'; import { LanguageContext } from '../../context/LanguageContext'; import LoadingSpinner from '../Common/LoadingSpinner'; import { useUser, Permission } from '../../hooks/useUser'; import { MediaStatus, MediaRequestStatus, } from '../../../server/constants/media'; import RequestModal from '../RequestModal'; import Badge from '../Common/Badge'; import ButtonWithDropdown from '../Common/ButtonWithDropdown'; import axios from 'axios'; import SlideOver from '../Common/SlideOver'; import RequestBlock from '../RequestBlock'; import TmdbLogo from '../../assets/tmdb_logo.svg'; import RTFresh from '../../assets/rt_fresh.svg'; import RTRotten from '../../assets/rt_rotten.svg'; import RTAudFresh from '../../assets/rt_aud_fresh.svg'; import RTAudRotten from '../../assets/rt_aud_rotten.svg'; import type { RTRating } from '../../../server/api/rottentomatoes'; import Error from '../../pages/_error'; import Head from 'next/head'; import globalMessages from '../../i18n/globalMessages'; import ExternalLinkBlock from '../ExternalLinkBlock'; import { sortCrewPriority } from '../../utils/creditHelpers'; const messages = defineMessages({ releasedate: 'Release Date', userrating: 'User Rating', status: 'Status', revenue: 'Revenue', budget: 'Budget', watchtrailer: 'Watch Trailer', originallanguage: 'Original Language', overview: 'Overview', runtime: '{minutes} minutes', cast: 'Cast', recommendations: 'Recommendations', similar: 'Similar Titles', cancelrequest: 'Cancel Request', available: 'Available', unavailable: 'Unavailable', request: 'Request', viewrequest: 'View Request', pending: 'Pending', overviewunavailable: 'Overview unavailable', manageModalTitle: 'Manage Movie', manageModalRequests: 'Requests', manageModalNoRequests: 'No Requests', manageModalClearMedia: 'Clear All Media Data', manageModalClearMediaWarning: 'This will remove all media data including all requests for this item. This action is irreversible. If this item exists in your Plex library, the media information will be recreated next sync.', approve: 'Approve', decline: 'Decline', studio: 'Studio', viewfullcrew: 'View Full Crew', }); interface MovieDetailsProps { movie?: MovieDetailsType; } interface SearchResult { page: number; totalResults: number; totalPages: number; results: MovieResult[]; } const MovieDetails: React.FC = ({ movie }) => { const { hasPermission } = useUser(); const router = useRouter(); const intl = useIntl(); const { locale } = useContext(LanguageContext); const [showRequestModal, setShowRequestModal] = useState(false); const [showManager, setShowManager] = useState(false); const { data, error, revalidate } = useSWR( `/api/v1/movie/${router.query.movieId}?language=${locale}`, { initialData: movie, } ); const { data: recommended, error: recommendedError } = useSWR( `/api/v1/movie/${router.query.movieId}/recommendations?language=${locale}` ); const { data: similar, error: similarError } = useSWR( `/api/v1/movie/${router.query.movieId}/similar?language=${locale}` ); const { data: ratingData } = useSWR( `/api/v1/movie/${router.query.movieId}/ratings` ); const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [ data, ]); if (!data && !error) { return ; } if (!data) { return ; } const activeRequest = data?.mediaInfo?.requests?.find( (request) => request.status === MediaRequestStatus.PENDING ); const trailerUrl = data.relatedVideos ?.filter((r) => r.type === 'Trailer') .sort((a, b) => a.size - b.size) .pop()?.url; const modifyRequest = async (type: 'approve' | 'decline') => { const response = await axios.get( `/api/v1/request/${activeRequest?.id}/${type}` ); if (response) { revalidate(); } }; const deleteMedia = async () => { if (data?.mediaInfo?.id) { await axios.delete(`/api/v1/media/${data?.mediaInfo?.id}`); revalidate(); } }; return (
{data.title} - Overseerr { revalidate(); setShowRequestModal(false); }} onCancel={() => setShowRequestModal(false)} /> setShowManager(false)} subText={data.title} >

{intl.formatMessage(messages.manageModalRequests)}

    {data.mediaInfo?.requests?.map((request) => (
  • revalidate()} />
  • ))} {(data.mediaInfo?.requests ?? []).length === 0 && (
  • {intl.formatMessage(messages.manageModalNoRequests)}
  • )}
{data?.mediaInfo && (
{intl.formatMessage(messages.manageModalClearMediaWarning)}
)}
{data.mediaInfo?.status === MediaStatus.AVAILABLE && ( {intl.formatMessage(globalMessages.available)} )} {data.mediaInfo?.status === MediaStatus.PROCESSING && ( {intl.formatMessage(globalMessages.unavailable)} )} {data.mediaInfo?.status === MediaStatus.PENDING && ( {intl.formatMessage(globalMessages.pending)} )}

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

{(data.runtime ?? 0) > 0 && ( <> {' '} |{' '} )} {data.genres.map((g) => g.name).join(', ')}
{trailerUrl && ( )} {(!data.mediaInfo || data.mediaInfo?.status === MediaStatus.UNKNOWN) && ( )} {activeRequest && ( } text={ <> } onClick={() => setShowRequestModal(true)} > {hasPermission(Permission.MANAGE_REQUESTS) && ( <> modifyRequest('approve')} > {intl.formatMessage(messages.approve)} modifyRequest('decline')} > {intl.formatMessage(messages.decline)} )} )} {hasPermission(Permission.MANAGE_REQUESTS) && ( )}

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

{sortedCrew.length > 0 && ( )}
{data.collection && ( )}
{(data.voteCount > 0 || ratingData) && (
{ratingData?.criticsRating && (ratingData?.criticsScore ?? 0) > 0 && ( <> {ratingData.criticsRating === 'Rotten' ? ( ) : ( )} {ratingData.criticsScore}% )} {ratingData?.audienceRating && (ratingData?.audienceScore ?? 0) > 0 && ( <> {ratingData.audienceRating === 'Spilled' ? ( ) : ( )} {ratingData.audienceScore}% )} {data.voteCount > 0 && ( <> {data.voteAverage}/10 )}
)}
{data.status}
{data.revenue > 0 && (
)} {data.budget > 0 && (
)} {data.spokenLanguages.some( (lng) => lng.iso_639_1 === data.originalLanguage ) && (
{ data.spokenLanguages.find( (lng) => lng.iso_639_1 === data.originalLanguage )?.name }
)} {data.productionCompanies[0] && (
{data.productionCompanies[0]?.name}
)}
( ))} /> {(recommended?.results ?? []).length > 0 && ( <> ( ))} /> )} {(similar?.results ?? []).length > 0 && ( <> ( ))} /> )}
); }; export default MovieDetails;