mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useContext } from 'react';
|
import React, { useState, useContext, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
FormattedMessage,
|
FormattedMessage,
|
||||||
defineMessages,
|
defineMessages,
|
||||||
@@ -38,6 +38,7 @@ import Error from '../../pages/_error';
|
|||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import ExternalLinkBlock from '../ExternalLinkBlock';
|
import ExternalLinkBlock from '../ExternalLinkBlock';
|
||||||
|
import { sortCrewPriority } from '../../utils/creditHelpers';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
releasedate: 'Release Date',
|
releasedate: 'Release Date',
|
||||||
@@ -103,6 +104,10 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
`/api/v1/movie/${router.query.movieId}/ratings`
|
`/api/v1/movie/${router.query.movieId}/ratings`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!data && !error) {
|
if (!data && !error) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
}
|
}
|
||||||
@@ -134,7 +139,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bg-cover bg-center -mx-4 -mt-2 px-4 sm:px-8 pt-4 "
|
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover sm:px-8 "
|
||||||
style={{
|
style={{
|
||||||
height: 493,
|
height: 493,
|
||||||
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
||||||
@@ -159,21 +164,21 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
onClose={() => setShowManager(false)}
|
onClose={() => setShowManager(false)}
|
||||||
subText={data.title}
|
subText={data.title}
|
||||||
>
|
>
|
||||||
<h3 className="text-xl mb-2">
|
<h3 className="mb-2 text-xl">
|
||||||
{intl.formatMessage(messages.manageModalRequests)}
|
{intl.formatMessage(messages.manageModalRequests)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="bg-gray-600 shadow overflow-hidden rounded-md">
|
<div className="overflow-hidden bg-gray-600 rounded-md shadow">
|
||||||
<ul>
|
<ul>
|
||||||
{data.mediaInfo?.requests?.map((request) => (
|
{data.mediaInfo?.requests?.map((request) => (
|
||||||
<li
|
<li
|
||||||
key={`manage-request-${request.id}`}
|
key={`manage-request-${request.id}`}
|
||||||
className="border-b last:border-b-0 border-gray-700"
|
className="border-b border-gray-700 last:border-b-0"
|
||||||
>
|
>
|
||||||
<RequestBlock request={request} onUpdate={() => revalidate()} />
|
<RequestBlock request={request} onUpdate={() => revalidate()} />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{(data.mediaInfo?.requests ?? []).length === 0 && (
|
{(data.mediaInfo?.requests ?? []).length === 0 && (
|
||||||
<li className="text-center py-4 text-gray-400">
|
<li className="py-4 text-center text-gray-400">
|
||||||
{intl.formatMessage(messages.manageModalNoRequests)}
|
{intl.formatMessage(messages.manageModalNoRequests)}
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
@@ -188,21 +193,21 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(messages.manageModalClearMedia)}
|
{intl.formatMessage(messages.manageModalClearMedia)}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-sm text-gray-400 mt-2">
|
<div className="mt-2 text-sm text-gray-400">
|
||||||
{intl.formatMessage(messages.manageModalClearMediaWarning)}
|
{intl.formatMessage(messages.manageModalClearMediaWarning)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SlideOver>
|
</SlideOver>
|
||||||
<div className="flex flex-col items-center md:flex-row md:items-end pt-4">
|
<div className="flex flex-col items-center pt-4 md:flex-row md:items-end">
|
||||||
<div className="md:mr-4 flex-shrink-0">
|
<div className="flex-shrink-0 md:mr-4">
|
||||||
<img
|
<img
|
||||||
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="rounded md:rounded-lg shadow md:shadow-2xl w-32 md:w-52"
|
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-52"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-white flex flex-col md:mr-4 mt-4 md:mt-0 text-center md:text-left">
|
<div className="flex flex-col mt-4 text-center text-white md:mr-4 md:mt-0 md:text-left">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
|
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
|
||||||
<Badge badgeType="success">
|
<Badge badgeType="success">
|
||||||
@@ -224,7 +229,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
{data.title}{' '}
|
{data.title}{' '}
|
||||||
<span className="text-2xl">({data.releaseDate.slice(0, 4)})</span>
|
<span className="text-2xl">({data.releaseDate.slice(0, 4)})</span>
|
||||||
</h1>
|
</h1>
|
||||||
<span className="text-xs md:text-base mt-1 md:mt-0">
|
<span className="mt-1 text-xs md:text-base md:mt-0">
|
||||||
{(data.runtime ?? 0) > 0 && (
|
{(data.runtime ?? 0) > 0 && (
|
||||||
<>
|
<>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -237,7 +242,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
{data.genres.map((g) => g.name).join(', ')}
|
{data.genres.map((g) => g.name).join(', ')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex justify-end mt-4 md:mt-0">
|
<div className="flex justify-end flex-1 mt-4 md:mt-0">
|
||||||
{(!data.mediaInfo ||
|
{(!data.mediaInfo ||
|
||||||
data.mediaInfo?.status === MediaStatus.UNKNOWN) && (
|
data.mediaInfo?.status === MediaStatus.UNKNOWN) && (
|
||||||
<Button
|
<Button
|
||||||
@@ -382,7 +387,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex pt-8 text-white flex-col md:flex-row pb-4">
|
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
|
||||||
<div className="flex-1 md:mr-8">
|
<div className="flex-1 md:mr-8">
|
||||||
<h2 className="text-xl md:text-2xl">
|
<h2 className="text-xl md:text-2xl">
|
||||||
<FormattedMessage {...messages.overview} />
|
<FormattedMessage {...messages.overview} />
|
||||||
@@ -392,11 +397,26 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
? data.overview
|
? data.overview
|
||||||
: intl.formatMessage(messages.overviewunavailable)}
|
: intl.formatMessage(messages.overviewunavailable)}
|
||||||
</p>
|
</p>
|
||||||
|
<ul className="grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3">
|
||||||
|
{sortedCrew.slice(0, 6).map((person) => (
|
||||||
|
<li
|
||||||
|
className="flex flex-col col-span-1"
|
||||||
|
key={`crew-${person.job}-${person.id}`}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{person.job}</span>
|
||||||
|
<Link href={`/person/${person.id}`}>
|
||||||
|
<a className="text-gray-400 transition duration-300 hover:text-underline hover:text-gray-100">
|
||||||
|
{person.name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md:w-80 mt-8 md:mt-0">
|
<div className="w-full mt-8 md:w-80 md:mt-0">
|
||||||
<div className="bg-gray-900 rounded-lg shadow border border-gray-800">
|
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
|
||||||
{(data.voteCount > 0 || ratingData) && (
|
{(data.voteCount > 0 || ratingData) && (
|
||||||
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0 items-center justify-center">
|
<div className="flex items-center justify-center px-4 py-2 border-b border-gray-800 last:border-b-0">
|
||||||
{ratingData?.criticsRating &&
|
{ratingData?.criticsRating &&
|
||||||
(ratingData?.criticsScore ?? 0) > 0 && (
|
(ratingData?.criticsScore ?? 0) > 0 && (
|
||||||
<>
|
<>
|
||||||
@@ -407,7 +427,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<RTFresh className="w-6 mr-1" />
|
<RTFresh className="w-6 mr-1" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm mr-4 last:mr-0">
|
<span className="mr-4 text-sm text-gray-400 last:mr-0">
|
||||||
{ratingData.criticsScore}%
|
{ratingData.criticsScore}%
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -422,7 +442,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<RTAudFresh className="w-6 mr-1" />
|
<RTAudFresh className="w-6 mr-1" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm mr-4 last:mr-0">
|
<span className="mr-4 text-sm text-gray-400 last:mr-0">
|
||||||
{ratingData.audienceScore}%
|
{ratingData.audienceScore}%
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -432,7 +452,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<TmdbLogo className="w-6 mr-2" />
|
<TmdbLogo className="w-6 mr-2" />
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm">
|
<span className="text-sm text-gray-400">
|
||||||
{data.voteAverage}/10
|
{data.voteAverage}/10
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -443,7 +463,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.releasedate} />
|
<FormattedMessage {...messages.releasedate} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
value={new Date(data.releaseDate)}
|
value={new Date(data.releaseDate)}
|
||||||
year="numeric"
|
year="numeric"
|
||||||
@@ -456,7 +476,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.status} />
|
<FormattedMessage {...messages.status} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.status}
|
{data.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -465,7 +485,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.revenue} />
|
<FormattedMessage {...messages.revenue} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
currency="USD"
|
currency="USD"
|
||||||
style="currency"
|
style="currency"
|
||||||
@@ -479,7 +499,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.budget} />
|
<FormattedMessage {...messages.budget} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
<FormattedNumber
|
<FormattedNumber
|
||||||
currency="USD"
|
currency="USD"
|
||||||
style="currency"
|
style="currency"
|
||||||
@@ -495,7 +515,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.originallanguage} />
|
<FormattedMessage {...messages.originallanguage} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{
|
{
|
||||||
data.spokenLanguages.find(
|
data.spokenLanguages.find(
|
||||||
(lng) => lng.iso_639_1 === data.originalLanguage
|
(lng) => lng.iso_639_1 === data.originalLanguage
|
||||||
@@ -509,7 +529,7 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.studio} />
|
<FormattedMessage {...messages.studio} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.productionCompanies[0]?.name}
|
{data.productionCompanies[0]?.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -525,10 +545,10 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link href="/movie/[movieId]/cast" as={`/movie/${data.id}/cast`}>
|
<Link href="/movie/[movieId]/cast" as={`/movie/${data.id}/cast`}>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.cast} />
|
<FormattedMessage {...messages.cast} />
|
||||||
</span>
|
</span>
|
||||||
@@ -566,13 +586,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
/>
|
/>
|
||||||
{(recommended?.results ?? []).length > 0 && (
|
{(recommended?.results ?? []).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link
|
<Link
|
||||||
href="/movie/[movieId]/recommendations"
|
href="/movie/[movieId]/recommendations"
|
||||||
as={`/movie/${data.id}/recommendations`}
|
as={`/movie/${data.id}/recommendations`}
|
||||||
>
|
>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.recommendations} />
|
<FormattedMessage {...messages.recommendations} />
|
||||||
</span>
|
</span>
|
||||||
@@ -616,13 +636,13 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
|
|||||||
)}
|
)}
|
||||||
{(similar?.results ?? []).length > 0 && (
|
{(similar?.results ?? []).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link
|
<Link
|
||||||
href="/movie/[movieId]/similar"
|
href="/movie/[movieId]/similar"
|
||||||
as={`/movie/${data.id}/similar`}
|
as={`/movie/${data.id}/similar`}
|
||||||
>
|
>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.similar} />
|
<FormattedMessage {...messages.similar} />
|
||||||
</span>
|
</span>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useContext } from 'react';
|
import React, { useState, useContext, useMemo } from 'react';
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
@@ -30,6 +30,8 @@ import Head from 'next/head';
|
|||||||
import globalMessages from '../../i18n/globalMessages';
|
import globalMessages from '../../i18n/globalMessages';
|
||||||
import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb';
|
import { ANIME_KEYWORD_ID } from '../../../server/api/themoviedb';
|
||||||
import ExternalLinkBlock from '../ExternalLinkBlock';
|
import ExternalLinkBlock from '../ExternalLinkBlock';
|
||||||
|
import { sortCrewPriority } from '../../utils/creditHelpers';
|
||||||
|
import { Crew } from '../../../server/models/common';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
userrating: 'User Rating',
|
userrating: 'User Rating',
|
||||||
@@ -105,6 +107,10 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
`/api/v1/tv/${router.query.tvId}/ratings`
|
`/api/v1/tv/${router.query.tvId}/ratings`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sortedCrew = useMemo(() => sortCrewPriority(data?.credits.crew ?? []), [
|
||||||
|
data,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!data && !error) {
|
if (!data && !error) {
|
||||||
return <LoadingSpinner />;
|
return <LoadingSpinner />;
|
||||||
}
|
}
|
||||||
@@ -148,7 +154,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="bg-cover bg-center -mx-4 -mt-2 px-4 sm:px-8 pt-4 "
|
className="px-4 pt-4 -mx-4 -mt-2 bg-center bg-cover sm:px-8 "
|
||||||
style={{
|
style={{
|
||||||
height: 493,
|
height: 493,
|
||||||
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
backgroundImage: `linear-gradient(180deg, rgba(17, 24, 39, 0.47) 0%, rgba(17, 24, 39, 1) 100%), url(//image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data.backdropPath})`,
|
||||||
@@ -173,21 +179,21 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
onClose={() => setShowManager(false)}
|
onClose={() => setShowManager(false)}
|
||||||
subText={data.name}
|
subText={data.name}
|
||||||
>
|
>
|
||||||
<h3 className="text-xl mb-2">
|
<h3 className="mb-2 text-xl">
|
||||||
{intl.formatMessage(messages.manageModalRequests)}
|
{intl.formatMessage(messages.manageModalRequests)}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="bg-gray-600 shadow overflow-hidden rounded-md">
|
<div className="overflow-hidden bg-gray-600 rounded-md shadow">
|
||||||
<ul>
|
<ul>
|
||||||
{data.mediaInfo?.requests?.map((request) => (
|
{data.mediaInfo?.requests?.map((request) => (
|
||||||
<li
|
<li
|
||||||
key={`manage-request-${request.id}`}
|
key={`manage-request-${request.id}`}
|
||||||
className="border-b last:border-b-0 border-gray-700"
|
className="border-b border-gray-700 last:border-b-0"
|
||||||
>
|
>
|
||||||
<RequestBlock request={request} onUpdate={() => revalidate()} />
|
<RequestBlock request={request} onUpdate={() => revalidate()} />
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{(data.mediaInfo?.requests ?? []).length === 0 && (
|
{(data.mediaInfo?.requests ?? []).length === 0 && (
|
||||||
<li className="text-center py-4 text-gray-400">
|
<li className="py-4 text-center text-gray-400">
|
||||||
{intl.formatMessage(messages.manageModalNoRequests)}
|
{intl.formatMessage(messages.manageModalNoRequests)}
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
@@ -202,21 +208,21 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
>
|
>
|
||||||
{intl.formatMessage(messages.manageModalClearMedia)}
|
{intl.formatMessage(messages.manageModalClearMedia)}
|
||||||
</Button>
|
</Button>
|
||||||
<div className="text-sm text-gray-400 mt-2">
|
<div className="mt-2 text-sm text-gray-400">
|
||||||
{intl.formatMessage(messages.manageModalClearMediaWarning)}
|
{intl.formatMessage(messages.manageModalClearMediaWarning)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</SlideOver>
|
</SlideOver>
|
||||||
<div className="flex flex-col items-center md:flex-row md:items-end pt-4">
|
<div className="flex flex-col items-center pt-4 md:flex-row md:items-end">
|
||||||
<div className="md:mr-4 flex-shrink-0">
|
<div className="flex-shrink-0 md:mr-4">
|
||||||
<img
|
<img
|
||||||
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
src={`//image.tmdb.org/t/p/w600_and_h900_bestv2${data.posterPath}`}
|
||||||
alt=""
|
alt=""
|
||||||
className="rounded md:rounded-lg shadow md:shadow-2xl w-32 md:w-52"
|
className="w-32 rounded shadow md:rounded-lg md:shadow-2xl md:w-52"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-white flex flex-col md:mr-4 mt-4 md:mt-0 text-center md:text-left">
|
<div className="flex flex-col mt-4 text-center text-white md:mr-4 md:mt-0 md:text-left">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
|
{data.mediaInfo?.status === MediaStatus.AVAILABLE && (
|
||||||
<Badge badgeType="success">
|
<Badge badgeType="success">
|
||||||
@@ -242,16 +248,16 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<h1 className="text-2xl md:text-4xl">
|
<h1 className="text-2xl md:text-4xl">
|
||||||
<span>{data.name}</span>
|
<span>{data.name}</span>
|
||||||
{data.firstAirDate && (
|
{data.firstAirDate && (
|
||||||
<span className="text-2xl ml-2">
|
<span className="ml-2 text-2xl">
|
||||||
({data.firstAirDate.slice(0, 4)})
|
({data.firstAirDate.slice(0, 4)})
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
<span className="text-xs md:text-base mt-1 md:mt-0">
|
<span className="mt-1 text-xs md:text-base md:mt-0">
|
||||||
{data.genres.map((g) => g.name).join(', ')}
|
{data.genres.map((g) => g.name).join(', ')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex justify-end mt-4 md:mt-0">
|
<div className="flex justify-end flex-1 mt-4 md:mt-0">
|
||||||
{(!data.mediaInfo ||
|
{(!data.mediaInfo ||
|
||||||
data.mediaInfo.status === MediaStatus.UNKNOWN) && (
|
data.mediaInfo.status === MediaStatus.UNKNOWN) && (
|
||||||
<Button
|
<Button
|
||||||
@@ -391,7 +397,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex pt-8 text-white flex-col md:flex-row pb-4">
|
<div className="flex flex-col pt-8 pb-4 text-white md:flex-row">
|
||||||
<div className="flex-1 md:mr-8">
|
<div className="flex-1 md:mr-8">
|
||||||
<h2 className="text-xl md:text-2xl">
|
<h2 className="text-xl md:text-2xl">
|
||||||
<FormattedMessage {...messages.overview} />
|
<FormattedMessage {...messages.overview} />
|
||||||
@@ -401,11 +407,40 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
? data.overview
|
? data.overview
|
||||||
: intl.formatMessage(messages.overviewunavailable)}
|
: intl.formatMessage(messages.overviewunavailable)}
|
||||||
</p>
|
</p>
|
||||||
|
<ul className="grid grid-cols-2 gap-6 mt-6 sm:grid-cols-3">
|
||||||
|
{(data.createdBy.length > 0
|
||||||
|
? [
|
||||||
|
...data.createdBy.map(
|
||||||
|
(person): Partial<Crew> => ({
|
||||||
|
id: person.id,
|
||||||
|
job: 'Creator',
|
||||||
|
name: person.name,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
...sortedCrew,
|
||||||
|
]
|
||||||
|
: sortedCrew
|
||||||
|
)
|
||||||
|
.slice(0, 6)
|
||||||
|
.map((person) => (
|
||||||
|
<li
|
||||||
|
className="flex flex-col col-span-1"
|
||||||
|
key={`crew-${person.job}-${person.id}`}
|
||||||
|
>
|
||||||
|
<span className="font-bold">{person.job}</span>
|
||||||
|
<Link href={`/person/${person.id}`}>
|
||||||
|
<a className="text-gray-400 transition duration-300 hover:text-underline hover:text-gray-100">
|
||||||
|
{person.name}
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full md:w-80 mt-8 md:mt-0">
|
<div className="w-full mt-8 md:w-80 md:mt-0">
|
||||||
<div className="bg-gray-900 rounded-lg shadow border border-gray-800">
|
<div className="bg-gray-900 border border-gray-800 rounded-lg shadow">
|
||||||
{(data.voteCount > 0 || ratingData) && (
|
{(data.voteCount > 0 || ratingData) && (
|
||||||
<div className="flex px-4 py-2 border-b border-gray-800 last:border-b-0 items-center justify-center">
|
<div className="flex items-center justify-center px-4 py-2 border-b border-gray-800 last:border-b-0">
|
||||||
{ratingData?.criticsRating &&
|
{ratingData?.criticsRating &&
|
||||||
(ratingData?.criticsScore ?? 0) > 0 && (
|
(ratingData?.criticsScore ?? 0) > 0 && (
|
||||||
<>
|
<>
|
||||||
@@ -416,7 +451,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<RTFresh className="w-6 mr-1" />
|
<RTFresh className="w-6 mr-1" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm mr-4 last:mr-0">
|
<span className="mr-4 text-sm text-gray-400 last:mr-0">
|
||||||
{ratingData.criticsScore}%
|
{ratingData.criticsScore}%
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -431,7 +466,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<RTAudFresh className="w-6 mr-1" />
|
<RTAudFresh className="w-6 mr-1" />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm mr-4 last:mr-0">
|
<span className="mr-4 text-sm text-gray-400 last:mr-0">
|
||||||
{ratingData.audienceScore}%
|
{ratingData.audienceScore}%
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -441,7 +476,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<TmdbLogo className="w-6 mr-2" />
|
<TmdbLogo className="w-6 mr-2" />
|
||||||
</span>
|
</span>
|
||||||
<span className="text-gray-400 text-sm">
|
<span className="text-sm text-gray-400">
|
||||||
{data.voteAverage}/10
|
{data.voteAverage}/10
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
@@ -455,7 +490,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{intl.formatMessage(messages.showtype)}
|
{intl.formatMessage(messages.showtype)}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{intl.formatMessage(messages.anime)}
|
{intl.formatMessage(messages.anime)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -464,7 +499,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.status} />
|
<FormattedMessage {...messages.status} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.status}
|
{data.status}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -475,7 +510,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.originallanguage} />
|
<FormattedMessage {...messages.originallanguage} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{
|
{
|
||||||
data.spokenLanguages.find(
|
data.spokenLanguages.find(
|
||||||
(lng) => lng.iso_639_1 === data.originalLanguage
|
(lng) => lng.iso_639_1 === data.originalLanguage
|
||||||
@@ -489,7 +524,7 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
<FormattedMessage {...messages.network} />
|
<FormattedMessage {...messages.network} />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex-1 text-right text-gray-400 text-sm">
|
<span className="flex-1 text-sm text-right text-gray-400">
|
||||||
{data.networks.map((n) => n.name).join(', ')}
|
{data.networks.map((n) => n.name).join(', ')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,10 +540,10 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link href="/tv/[tvId]/cast" as={`/tv/${data.id}/cast`}>
|
<Link href="/tv/[tvId]/cast" as={`/tv/${data.id}/cast`}>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.cast} />
|
<FormattedMessage {...messages.cast} />
|
||||||
</span>
|
</span>
|
||||||
@@ -546,13 +581,13 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
/>
|
/>
|
||||||
{(recommended?.results ?? []).length > 0 && (
|
{(recommended?.results ?? []).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link
|
<Link
|
||||||
href="/tv/[tvId]/recommendations"
|
href="/tv/[tvId]/recommendations"
|
||||||
as={`/tv/${data.id}/recommendations`}
|
as={`/tv/${data.id}/recommendations`}
|
||||||
>
|
>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.recommendations} />
|
<FormattedMessage {...messages.recommendations} />
|
||||||
</span>
|
</span>
|
||||||
@@ -596,10 +631,10 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
|
|||||||
)}
|
)}
|
||||||
{(similar?.results ?? []).length > 0 && (
|
{(similar?.results ?? []).length > 0 && (
|
||||||
<>
|
<>
|
||||||
<div className="md:flex md:items-center md:justify-between mb-4 mt-6">
|
<div className="mt-6 mb-4 md:flex md:items-center md:justify-between">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<Link href="/tv/[tvId]/similar" as={`/tv/${data.id}/similar`}>
|
<Link href="/tv/[tvId]/similar" as={`/tv/${data.id}/similar`}>
|
||||||
<a className="inline-flex text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate items-center">
|
<a className="inline-flex items-center text-xl leading-7 text-gray-300 hover:text-white sm:text-2xl sm:leading-9 sm:truncate">
|
||||||
<span>
|
<span>
|
||||||
<FormattedMessage {...messages.similar} />
|
<FormattedMessage {...messages.similar} />
|
||||||
</span>
|
</span>
|
||||||
|
24
src/utils/creditHelpers.ts
Normal file
24
src/utils/creditHelpers.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Crew } from '../../server/models/common';
|
||||||
|
const priorityJobs = [
|
||||||
|
'Director',
|
||||||
|
'Creator',
|
||||||
|
'Screenplay',
|
||||||
|
'Writer',
|
||||||
|
'Composer',
|
||||||
|
'Editor',
|
||||||
|
'Producer',
|
||||||
|
'Co-Producer',
|
||||||
|
'Executive Producer',
|
||||||
|
'Animation',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const sortCrewPriority = (crew: Crew[]): Crew[] => {
|
||||||
|
return crew
|
||||||
|
.filter((person) => priorityJobs.includes(person.job))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aScore = priorityJobs.findIndex((job) => job.includes(a.job));
|
||||||
|
const bScore = priorityJobs.findIndex((job) => job.includes(b.job));
|
||||||
|
|
||||||
|
return aScore - bScore;
|
||||||
|
});
|
||||||
|
};
|
Reference in New Issue
Block a user