feat: season/episode list on series details (#2967)
parent
67f3a3829e
commit
8a2acb7f2b
@ -0,0 +1,62 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
airedrelative: 'Aired {relativeTime}',
|
||||
airsrelative: 'Airs {relativeTime}',
|
||||
});
|
||||
|
||||
type AirDateBadgeProps = {
|
||||
airDate: string;
|
||||
};
|
||||
|
||||
const AirDateBadge = ({ airDate }: AirDateBadgeProps) => {
|
||||
const WEEK = 1000 * 60 * 60 * 24 * 8;
|
||||
const intl = useIntl();
|
||||
const dAirDate = new Date(airDate);
|
||||
const nowDate = new Date();
|
||||
const alreadyAired = dAirDate.getTime() < nowDate.getTime();
|
||||
|
||||
const compareWeek = new Date(
|
||||
alreadyAired ? Date.now() - WEEK : Date.now() + WEEK
|
||||
);
|
||||
|
||||
let showRelative = false;
|
||||
|
||||
if (
|
||||
(alreadyAired && dAirDate.getTime() > compareWeek.getTime()) ||
|
||||
(!alreadyAired && dAirDate.getTime() < compareWeek.getTime())
|
||||
) {
|
||||
showRelative = true;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Badge badgeType="light">
|
||||
{intl.formatDate(dAirDate, {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</Badge>
|
||||
{showRelative && (
|
||||
<Badge badgeType="light">
|
||||
{intl.formatMessage(
|
||||
alreadyAired ? messages.airedrelative : messages.airsrelative,
|
||||
{
|
||||
relativeTime: (
|
||||
<FormattedRelativeTime
|
||||
value={(dAirDate.getTime() - Date.now()) / 1000}
|
||||
numeric="auto"
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
),
|
||||
}
|
||||
)}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AirDateBadge;
|
@ -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;
|
Loading…
Reference in new issue