Merge remote-tracking branch 'overseerr/develop' into develop

This commit is contained in:
notfakie
2022-09-01 18:11:15 +12:00
473 changed files with 15548 additions and 8433 deletions

View File

@@ -0,0 +1,62 @@
import AirDateBadge from '@app/components/AirDateBadge';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import type { SeasonWithEpisodes } from '@server/models/Tv';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
const messages = defineMessages({
somethingwentwrong: 'Something went wrong while retrieving season data.',
});
type SeasonProps = {
seasonNumber: number;
tvId: number;
};
const Season = ({ seasonNumber, tvId }: SeasonProps) => {
const intl = useIntl();
const { data, error } = useSWR<SeasonWithEpisodes>(
`/api/v1/tv/${tvId}/season/${seasonNumber}`
);
if (!data && !error) {
return <LoadingSpinner />;
}
if (!data) {
return <div>{intl.formatMessage(messages.somethingwentwrong)}</div>;
}
return (
<div className="flex flex-col justify-center divide-y divide-gray-700">
{data.episodes
.slice()
.reverse()
.map((episode) => {
return (
<div
className="flex flex-col space-y-4 py-4 xl:flex-row xl:space-y-4 xl:space-x-4"
key={`season-${seasonNumber}-episode-${episode.episodeNumber}`}
>
<div className="flex-1">
<div className="flex flex-col space-y-2 xl:flex-row xl:items-center xl:space-y-0 xl:space-x-2">
<h3 className="text-lg">{episode.name}</h3>
<AirDateBadge airDate={episode.airDate} />
</div>
{episode.overview && <p>{episode.overview}</p>}
</div>
{episode.stillPath && (
<img
className="h-auto w-full rounded-lg xl:h-32 xl:w-auto"
src={`https://image.tmdb.org/t/p/original/${episode.stillPath}`}
alt=""
/>
)}
</div>
);
})}
</div>
);
};
export default Season;

View File

@@ -1,20 +1,19 @@
import Header from '@app/components/Common/Header';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import PersonCard from '@app/components/PersonCard';
import Error from '@app/pages/_error';
import type { TvDetails } from '@server/models/Tv';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { TvDetails } from '../../../../server/models/Tv';
import Error from '../../../pages/_error';
import Header from '../../Common/Header';
import LoadingSpinner from '../../Common/LoadingSpinner';
import PageTitle from '../../Common/PageTitle';
import PersonCard from '../../PersonCard';
const messages = defineMessages({
fullseriescast: 'Full Series Cast',
});
const TvCast: React.FC = () => {
const TvCast = () => {
const router = useRouter();
const intl = useIntl();
const { data, error } = useSWR<TvDetails>(`/api/v1/tv/${router.query.tvId}`);

View File

@@ -1,20 +1,19 @@
import Header from '@app/components/Common/Header';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import PersonCard from '@app/components/PersonCard';
import Error from '@app/pages/_error';
import type { TvDetails } from '@server/models/Tv';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { TvDetails } from '../../../../server/models/Tv';
import Error from '../../../pages/_error';
import Header from '../../Common/Header';
import LoadingSpinner from '../../Common/LoadingSpinner';
import PageTitle from '../../Common/PageTitle';
import PersonCard from '../../PersonCard';
const messages = defineMessages({
fullseriescrew: 'Full Series Crew',
});
const TvCrew: React.FC = () => {
const TvCrew = () => {
const router = useRouter();
const intl = useIntl();
const { data, error } = useSWR<TvDetails>(`/api/v1/tv/${router.query.tvId}`);

View File

@@ -1,21 +1,20 @@
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
import Error from '@app/pages/_error';
import type { TvResult } from '@server/models/Search';
import type { TvDetails } from '@server/models/Tv';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { TvResult } from '../../../server/models/Search';
import { TvDetails } from '../../../server/models/Tv';
import useDiscover from '../../hooks/useDiscover';
import Error from '../../pages/_error';
import Header from '../Common/Header';
import ListView from '../Common/ListView';
import PageTitle from '../Common/PageTitle';
const messages = defineMessages({
recommendations: 'Recommendations',
});
const TvRecommendations: React.FC = () => {
const TvRecommendations = () => {
const router = useRouter();
const intl = useIntl();
const { data: tvData } = useSWR<TvDetails>(`/api/v1/tv/${router.query.tvId}`);

View File

@@ -1,21 +1,20 @@
import Header from '@app/components/Common/Header';
import ListView from '@app/components/Common/ListView';
import PageTitle from '@app/components/Common/PageTitle';
import useDiscover from '@app/hooks/useDiscover';
import Error from '@app/pages/_error';
import type { TvResult } from '@server/models/Search';
import type { TvDetails } from '@server/models/Tv';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
import type { TvResult } from '../../../server/models/Search';
import type { TvDetails } from '../../../server/models/Tv';
import useDiscover from '../../hooks/useDiscover';
import Error from '../../pages/_error';
import Header from '../Common/Header';
import ListView from '../Common/ListView';
import PageTitle from '../Common/PageTitle';
const messages = defineMessages({
similar: 'Similar Series',
});
const TvSimilar: React.FC = () => {
const TvSimilar = () => {
const router = useRouter();
const intl = useIntl();
const { data: tvData } = useSWR<TvDetails>(`/api/v1/tv/${router.query.tvId}`);

View File

@@ -1,3 +1,33 @@
import RTAudFresh from '@app/assets/rt_aud_fresh.svg';
import RTAudRotten from '@app/assets/rt_aud_rotten.svg';
import RTFresh from '@app/assets/rt_fresh.svg';
import RTRotten from '@app/assets/rt_rotten.svg';
import TmdbLogo from '@app/assets/tmdb_logo.svg';
import Badge from '@app/components/Common/Badge';
import Button from '@app/components/Common/Button';
import CachedImage from '@app/components/Common/CachedImage';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import PageTitle from '@app/components/Common/PageTitle';
import type { PlayButtonLink } from '@app/components/Common/PlayButton';
import PlayButton from '@app/components/Common/PlayButton';
import Tooltip from '@app/components/Common/Tooltip';
import ExternalLinkBlock from '@app/components/ExternalLinkBlock';
import IssueModal from '@app/components/IssueModal';
import ManageSlideOver from '@app/components/ManageSlideOver';
import MediaSlider from '@app/components/MediaSlider';
import PersonCard from '@app/components/PersonCard';
import RequestButton from '@app/components/RequestButton';
import RequestModal from '@app/components/RequestModal';
import Slider from '@app/components/Slider';
import StatusBadge from '@app/components/StatusBadge';
import Season from '@app/components/TvDetails/Season';
import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import { sortCrewPriority } from '@app/utils/creditHelpers';
import { Disclosure, Transition } from '@headlessui/react';
import {
ArrowCircleRightIcon,
CogIcon,
@@ -5,46 +35,22 @@ import {
FilmIcon,
PlayIcon,
} from '@heroicons/react/outline';
import { ChevronUpIcon } from '@heroicons/react/solid';
import type { RTRating } from '@server/api/rottentomatoes';
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
import { IssueStatus } from '@server/constants/issue';
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import { MediaServerType } from '@server/constants/server';
import type { Crew } from '@server/models/common';
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
import { hasFlag } from 'country-flag-icons';
import 'country-flag-icons/3x2/flags.css';
import getConfig from 'next/config';
import Link from 'next/link';
import { useRouter } from 'next/router';
import React, { useEffect, useMemo, useState } from 'react';
import { useEffect, 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 { MediaServerType } from '../../../server/constants/server';
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';
import getConfig from 'next/config';
const messages = defineMessages({
firstAirDate: 'First Air Date',
@@ -69,13 +75,22 @@ const messages = defineMessages({
streamingproviders: 'Currently Streaming On',
productioncountries:
'Production {countryCount, plural, one {Country} other {Countries}}',
reportissue: 'Report an Issue',
manageseries: 'Manage Series',
seasonstitle: 'Seasons',
episodeCount: '{episodeCount, plural, one {# Episode} other {# Episodes}}',
seasonnumber: 'Season {seasonNumber}',
status4k: '4K {status}',
rtcriticsscore: 'Rotten Tomatoes Tomatometer',
rtaudiencescore: 'Rotten Tomatoes Audience Score',
tmdbuserscore: 'TMDB User Score',
});
interface TvDetailsProps {
tv?: TvDetailsType;
}
const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
const TvDetails = ({ tv }: TvDetailsProps) => {
const settings = useSettings();
const { user, hasPermission } = useUser();
const router = useRouter();
@@ -109,6 +124,32 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
setShowManager(router.query.manage == '1' ? true : false);
}, [router.query.manage]);
const [plexUrl, setPlexUrl] = useState(data?.mediaInfo?.mediaUrl);
const [plexUrl4k, setPlexUrl4k] = useState(data?.mediaInfo?.mediaUrl4k);
useEffect(() => {
if (data) {
if (
settings.currentSettings.mediaServerType === MediaServerType.PLEX &&
(/iPad|iPhone|iPod/.test(navigator.userAgent) ||
(navigator.userAgent === 'MacIntel' && navigator.maxTouchPoints > 1))
) {
setPlexUrl(data.mediaInfo?.iOSPlexUrl);
setPlexUrl4k(data.mediaInfo?.iOSPlexUrl4k);
} else {
setPlexUrl(data.mediaInfo?.mediaUrl);
setPlexUrl4k(data.mediaInfo?.mediaUrl4k);
}
}
}, [
data,
data?.mediaInfo?.iOSPlexUrl,
data?.mediaInfo?.iOSPlexUrl4k,
data?.mediaInfo?.mediaUrl,
data?.mediaInfo?.mediaUrl4k,
settings.currentSettings.mediaServerType,
]);
if (!data && !error) {
return <LoadingSpinner />;
}
@@ -120,28 +161,28 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
const mediaLinks: PlayButtonLink[] = [];
if (
data.mediaInfo?.mediaUrl &&
plexUrl &&
hasPermission([Permission.REQUEST, Permission.REQUEST_TV], {
type: 'or',
})
) {
mediaLinks.push({
text: getAvalaibleMediaServerName(),
url: data.mediaInfo?.mediaUrl,
url: plexUrl,
svg: <PlayIcon />,
});
}
if (
settings.currentSettings.series4kEnabled &&
data.mediaInfo?.mediaUrl4k &&
plexUrl4k &&
hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], {
type: 'or',
})
) {
mediaLinks.push({
text: getAvalaible4kMediaServerName(),
url: data.mediaInfo?.mediaUrl4k,
url: plexUrl4k,
svg: <PlayIcon />,
});
}
@@ -208,7 +249,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
seasonCount <=
(
data.mediaInfo?.seasons.filter(
(season) => season.status === MediaStatus.AVAILABLE
(season) =>
season.status === MediaStatus.AVAILABLE ||
season.status === MediaStatus.PARTIALLY_AVAILABLE
) ?? []
).length;
@@ -216,7 +259,9 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
seasonCount <=
(
data.mediaInfo?.seasons.filter(
(season) => season.status4k === MediaStatus.AVAILABLE
(season) =>
season.status4k === MediaStatus.AVAILABLE ||
season.status4k === MediaStatus.PARTIALLY_AVAILABLE
) ?? []
).length;
@@ -325,7 +370,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
inProgress={(data.mediaInfo?.downloadStatus ?? []).length > 0}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={data.mediaInfo?.mediaUrl}
plexUrl={plexUrl}
serviceUrl={data.mediaInfo?.serviceUrl}
/>
{settings.currentSettings.series4kEnabled &&
hasPermission(
@@ -346,11 +392,12 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
}
tmdbId={data.mediaInfo?.tmdbId}
mediaType="tv"
plexUrl={data.mediaInfo?.mediaUrl4k}
plexUrl={plexUrl4k}
serviceUrl={data.mediaInfo?.serviceUrl4k}
/>
)}
</div>
<h1>
<h1 data-testid="media-title">
{data.name}{' '}
{data.firstAirDate && (
<span className="media-year">
@@ -396,38 +443,42 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
type: 'or',
}
) && (
<Button
buttonType="warning"
className="ml-2 first:ml-0"
onClick={() => setShowIssueModal(true)}
>
<ExclamationIcon className="w-5" />
</Button>
<Tooltip content={intl.formatMessage(messages.reportissue)}>
<Button
buttonType="warning"
onClick={() => setShowIssueModal(true)}
className="ml-2 first:ml-0"
>
<ExclamationIcon />
</Button>
</Tooltip>
)}
{hasPermission(Permission.MANAGE_REQUESTS) && data.mediaInfo && (
<Button
buttonType="default"
className="relative ml-2 first:ml-0"
onClick={() => setShowManager(true)}
>
<CogIcon className="!mr-0" />
{hasPermission(
[Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
{
type: 'or',
}
) &&
(
data.mediaInfo?.issues.filter(
(issue) => issue.status === IssueStatus.OPEN
) ?? []
).length > 0 && (
<>
<div className="absolute -right-1 -top-1 h-3 w-3 rounded-full bg-red-600" />
<div className="absolute -right-1 -top-1 h-3 w-3 animate-ping rounded-full bg-red-600" />
</>
)}
</Button>
<Tooltip content={intl.formatMessage(messages.manageseries)}>
<Button
buttonType="ghost"
onClick={() => setShowManager(true)}
className="relative ml-2 first:ml-0"
>
<CogIcon className="!mr-0" />
{hasPermission(
[Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES],
{
type: 'or',
}
) &&
(
data.mediaInfo?.issues.filter(
(issue) => issue.status === IssueStatus.OPEN
) ?? []
).length > 0 && (
<>
<div className="absolute -right-1 -top-1 h-3 w-3 rounded-full bg-red-600" />
<div className="absolute -right-1 -top-1 h-3 w-3 animate-ping rounded-full bg-red-600" />
</>
)}
</Button>
</Tooltip>
)}
</div>
</div>
@@ -476,6 +527,174 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
</div>
</>
)}
<h2 className="py-4">{intl.formatMessage(messages.seasonstitle)}</h2>
<div className="flex w-full flex-col space-y-2">
{data.seasons
.slice()
.reverse()
.filter((season) => season.seasonNumber !== 0)
.map((season) => {
const show4k =
settings.currentSettings.series4kEnabled &&
hasPermission(
[
Permission.MANAGE_REQUESTS,
Permission.REQUEST_4K,
Permission.REQUEST_4K_TV,
],
{
type: 'or',
}
);
const mSeason = (data.mediaInfo?.seasons ?? []).find(
(s) =>
season.seasonNumber === s.seasonNumber &&
s.status !== MediaStatus.UNKNOWN
);
const mSeason4k = (data.mediaInfo?.seasons ?? []).find(
(s) =>
season.seasonNumber === s.seasonNumber &&
s.status4k !== MediaStatus.UNKNOWN
);
const request = (data.mediaInfo?.requests ?? []).find(
(r) =>
!!r.seasons.find(
(s) => s.seasonNumber === season.seasonNumber
) && !r.is4k
);
const request4k = (data.mediaInfo?.requests ?? []).find(
(r) =>
!!r.seasons.find(
(s) => s.seasonNumber === season.seasonNumber
) && r.is4k
);
return (
<Disclosure key={`season-discoslure-${season.seasonNumber}`}>
{({ open }) => (
<>
<Disclosure.Button
className={`mt-2 flex w-full items-center justify-between space-x-2 border-gray-700 bg-gray-800 px-4 py-2 text-gray-200 ${
open
? 'rounded-t-md border-t border-l border-r'
: 'rounded-md border'
}`}
>
<div className="flex flex-1 items-center space-x-2 text-lg">
<span>
{intl.formatMessage(messages.seasonnumber, {
seasonNumber: season.seasonNumber,
})}
</span>
<Badge badgeType="dark">
{intl.formatMessage(messages.episodeCount, {
episodeCount: season.episodeCount,
})}
</Badge>
</div>
{((!mSeason &&
request?.status === MediaRequestStatus.APPROVED) ||
mSeason?.status === MediaStatus.PROCESSING) && (
<Badge badgeType="primary">
{intl.formatMessage(globalMessages.requested)}
</Badge>
)}
{((!mSeason &&
request?.status === MediaRequestStatus.PENDING) ||
mSeason?.status === MediaStatus.PENDING) && (
<Badge badgeType="warning">
{intl.formatMessage(globalMessages.pending)}
</Badge>
)}
{mSeason?.status ===
MediaStatus.PARTIALLY_AVAILABLE && (
<Badge badgeType="success">
{intl.formatMessage(
globalMessages.partiallyavailable
)}
</Badge>
)}
{mSeason?.status === MediaStatus.AVAILABLE && (
<Badge badgeType="success">
{intl.formatMessage(globalMessages.available)}
</Badge>
)}
{((!mSeason4k &&
request4k?.status ===
MediaRequestStatus.APPROVED) ||
mSeason4k?.status4k === MediaStatus.PROCESSING) &&
show4k && (
<Badge badgeType="primary">
{intl.formatMessage(messages.status4k, {
status: intl.formatMessage(
globalMessages.requested
),
})}
</Badge>
)}
{((!mSeason4k &&
request4k?.status === MediaRequestStatus.PENDING) ||
mSeason?.status4k === MediaStatus.PENDING) &&
show4k && (
<Badge badgeType="warning">
{intl.formatMessage(messages.status4k, {
status: intl.formatMessage(
globalMessages.pending
),
})}
</Badge>
)}
{mSeason4k?.status4k ===
MediaStatus.PARTIALLY_AVAILABLE &&
show4k && (
<Badge badgeType="success">
{intl.formatMessage(messages.status4k, {
status: intl.formatMessage(
globalMessages.partiallyavailable
),
})}
</Badge>
)}
{mSeason4k?.status4k === MediaStatus.AVAILABLE &&
show4k && (
<Badge badgeType="success">
{intl.formatMessage(messages.status4k, {
status: intl.formatMessage(
globalMessages.available
),
})}
</Badge>
)}
<ChevronUpIcon
className={`${
open ? 'rotate-180 transform' : ''
} h-6 w-6 text-gray-500`}
/>
</Disclosure.Button>
<Transition
show={open}
enter="transition duration-100 ease-out"
enterFrom="transform opacity-0"
enterTo="transform opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform opacity-100"
leaveTo="transform opacity-0"
// Not sure why this transition is adding a margin without this here
style={{ margin: '0px' }}
>
<Disclosure.Panel className="w-full rounded-b-md border-b border-l border-r border-gray-700 px-4 pb-2">
<Season
tvId={data.id}
seasonNumber={season.seasonNumber}
/>
</Disclosure.Panel>
</Transition>
</>
)}
</Disclosure>
);
})}
</div>
</div>
<div className="media-overview-right">
<div className="media-facts">
@@ -484,30 +703,55 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
(ratingData?.audienceRating && !!ratingData?.audienceScore)) && (
<div className="media-ratings">
{ratingData?.criticsRating && !!ratingData?.criticsScore && (
<span className="media-rating">
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="mr-1 w-6" />
) : (
<RTFresh className="mr-1 w-6" />
)}
{ratingData.criticsScore}%
</span>
<Tooltip
content={intl.formatMessage(messages.rtcriticsscore)}
>
<a
href={ratingData.url}
className="media-rating"
target="_blank"
rel="noreferrer"
>
{ratingData.criticsRating === 'Rotten' ? (
<RTRotten className="mr-1 w-6" />
) : (
<RTFresh className="mr-1 w-6" />
)}
<span>{ratingData.criticsScore}%</span>
</a>
</Tooltip>
)}
{ratingData?.audienceRating && !!ratingData?.audienceScore && (
<span className="media-rating">
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="mr-1 w-6" />
) : (
<RTAudFresh className="mr-1 w-6" />
)}
{ratingData.audienceScore}%
</span>
<Tooltip
content={intl.formatMessage(messages.rtaudiencescore)}
>
<a
href={ratingData.url}
className="media-rating"
target="_blank"
rel="noreferrer"
>
{ratingData.audienceRating === 'Spilled' ? (
<RTAudRotten className="mr-1 w-6" />
) : (
<RTAudFresh className="mr-1 w-6" />
)}
<span>{ratingData.audienceScore}%</span>
</a>
</Tooltip>
)}
{!!data.voteCount && (
<span className="media-rating">
<TmdbLogo className="mr-2 w-6" />
{data.voteAverage}/10
</span>
<Tooltip content={intl.formatMessage(messages.tmdbuserscore)}>
<a
href={`https://www.themoviedb.org/tv/${data.id}?language=${locale}`}
className="media-rating"
target="_blank"
rel="noreferrer"
>
<TmdbLogo className="mr-1 w-6" />
<span>{Math.round(data.voteAverage * 10)}%</span>
</a>
</Tooltip>
)}
</div>
)}