Files
neomovies/src/app/tv/[id]/TVContent.tsx

222 lines
7.8 KiB
TypeScript
Raw Normal View History

2025-07-08 00:15:55 +03:00
'use client';
import { useState, useEffect, useRef } from 'react';
import Image from 'next/image';
2025-08-07 18:33:28 +00:00
import { tvShowsAPI } from '@/lib/neoApi';
2025-07-08 00:15:55 +03:00
import { getImageUrl } from '@/lib/neoApi';
2025-08-07 18:33:28 +00:00
import type { TVShowDetails } from '@/lib/neoApi';
2025-07-08 00:15:55 +03:00
import MoviePlayer from '@/components/MoviePlayer';
2025-07-17 22:03:56 +03:00
import TorrentSelector from '@/components/TorrentSelector';
2025-07-08 00:15:55 +03:00
import FavoriteButton from '@/components/FavoriteButton';
2025-07-08 16:46:00 +03:00
import Reactions from '@/components/Reactions';
2025-07-08 00:15:55 +03:00
import { formatDate } from '@/lib/utils';
import { PlayCircle, ArrowLeft } from 'lucide-react';
2025-07-17 23:18:50 +03:00
import { NextSeo } from 'next-seo';
2025-07-08 00:15:55 +03:00
interface TVContentProps {
showId: string;
initialShow: TVShowDetails;
}
export default function TVContent({ showId, initialShow }: TVContentProps) {
const [show] = useState<TVShowDetails>(initialShow);
2025-08-07 18:33:28 +00:00
const [externalIds, setExternalIds] = useState<any>(null);
2025-07-08 00:15:55 +03:00
const [imdbId, setImdbId] = useState<string | null>(null);
const [isPlayerFullscreen, setIsPlayerFullscreen] = useState(false);
const [isControlsVisible, setIsControlsVisible] = useState(false);
const controlsTimeoutRef = useRef<NodeJS.Timeout | null>(null);
useEffect(() => {
2025-08-07 18:33:28 +00:00
const fetchExternalIds = async () => {
2025-07-08 00:15:55 +03:00
try {
2025-08-07 18:33:28 +00:00
const data = await tvShowsAPI.getExternalIds(showId);
setExternalIds(data);
2025-07-08 00:15:55 +03:00
if (data?.imdb_id) {
setImdbId(data.imdb_id);
}
} catch (err) {
2025-08-07 18:33:28 +00:00
console.error('Error fetching external ids:', err);
2025-07-08 00:15:55 +03:00
}
};
if (initialShow.external_ids?.imdb_id) {
setImdbId(initialShow.external_ids.imdb_id);
} else {
2025-08-07 18:33:28 +00:00
fetchExternalIds();
2025-07-08 00:15:55 +03:00
}
}, [showId, initialShow.external_ids]);
const showControls = () => {
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
setIsControlsVisible(true);
controlsTimeoutRef.current = setTimeout(() => {
setIsControlsVisible(false);
}, 3000);
};
const handleOpenPlayer = () => {
setIsPlayerFullscreen(true);
showControls();
};
const handleClosePlayer = () => {
setIsPlayerFullscreen(false);
if (controlsTimeoutRef.current) {
clearTimeout(controlsTimeoutRef.current);
}
};
return (
<>
2025-07-17 23:18:50 +03:00
<NextSeo
title={`${show.name} смотреть онлайн`}
description={show.overview?.slice(0, 150)}
canonical={`https://neomovies.ru/tv/${show.id}`}
openGraph={{
url: `https://neomovies.ru/tv/${show.id}`,
images: [
{
url: getImageUrl(show.poster_path, 'w780'),
alt: show.name,
},
],
}}
/>
{/* schema.org TVSeries */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'TVSeries',
name: show.name,
image: getImageUrl(show.poster_path, 'w780'),
description: show.overview,
numberOfSeasons: show.number_of_seasons,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: show.vote_average,
ratingCount: show.vote_count,
},
}),
}}
/>
2025-07-08 00:15:55 +03:00
<div className="min-h-screen bg-background text-foreground px-4 py-6 md:px-6 lg:px-8">
<div className="w-full">
<div className="grid grid-cols-1 gap-8 md:grid-cols-3">
<div className="md:col-span-1">
<div className="sticky top-24 max-w-sm mx-auto md:max-w-none md:mx-0">
<div className="relative aspect-[2/3] w-full overflow-hidden rounded-lg shadow-lg">
<Image
src={getImageUrl(show.poster_path, 'w500')}
alt={`Постер сериала ${show.name}`}
fill
className="object-cover"
unoptimized
/>
</div>
</div>
</div>
<div className="md:col-span-2">
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
{show.name}
</h1>
{show.tagline && (
<p className="mt-1 text-lg text-muted-foreground">{show.tagline}</p>
)}
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2">
<span className="font-medium">Рейтинг: {show.vote_average.toFixed(1)}</span>
<span className="text-muted-foreground">|</span>
<span className="text-muted-foreground">Сезонов: {show.number_of_seasons}</span>
<span className="text-muted-foreground">|</span>
<span className="text-muted-foreground">{formatDate(show.first_air_date)}</span>
</div>
<div className="mt-4 flex flex-wrap gap-2">
{show.genres.map((genre) => (
<span key={genre.id} className="rounded-full bg-secondary text-secondary-foreground px-3 py-1 text-xs font-medium">
{genre.name}
</span>
))}
</div>
<div className="mt-6 space-y-4 text-base text-muted-foreground">
<p>{show.overview}</p>
</div>
<div className="mt-8 flex items-center gap-4">
{imdbId && (
<button
onClick={handleOpenPlayer}
className="md:hidden flex items-center justify-center gap-2 rounded-md bg-red-500 px-6 py-3 text-base font-semibold text-white shadow-sm hover:bg-red-600"
>
<PlayCircle size={20} />
<span>Смотреть</span>
</button>
)}
<FavoriteButton
mediaId={show.id.toString()}
mediaType="tv"
title={show.name}
posterPath={show.poster_path}
className="!bg-secondary !text-secondary-foreground hover:!bg-secondary/80"
2025-07-08 13:41:04 +03:00
showText={true}
2025-07-08 00:15:55 +03:00
/>
</div>
2025-07-08 16:46:00 +03:00
<div className="mt-8">
<Reactions mediaId={show.id.toString()} mediaType="tv" />
</div>
2025-07-08 00:15:55 +03:00
{imdbId && (
2025-07-17 22:18:25 +03:00
<div className="mt-10 space-y-4">
<div id="movie-player" className="hidden md:block rounded-lg bg-secondary/50 p-4 shadow-inner">
2025-07-08 00:15:55 +03:00
<MoviePlayer
id={show.id.toString()}
title={show.name}
poster={show.poster_path || ''}
imdbId={imdbId}
/>
2025-07-17 22:18:25 +03:00
</div>
<TorrentSelector
2025-07-17 22:03:56 +03:00
imdbId={imdbId}
type="tv"
2025-08-07 18:33:28 +00:00
title={show.name}
originalTitle={show.original_name}
year={show.first_air_date?.split('-')[0]}
2025-07-17 22:03:56 +03:00
/>
2025-07-08 00:15:55 +03:00
</div>
)}
</div>
</div>
</div>
</div>
{isPlayerFullscreen && imdbId && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black"
onMouseMove={showControls}
onClick={showControls}
>
<MoviePlayer
id={show.id.toString()}
title={show.name}
poster={show.poster_path || ''}
imdbId={imdbId}
isFullscreen={true}
/>
<button
onClick={handleClosePlayer}
className={`absolute top-1/2 left-4 -translate-y-1/2 z-50 rounded-full bg-black/50 p-2 text-white transition-opacity duration-300 hover:bg-black/75 ${isControlsVisible ? 'opacity-100' : 'opacity-0'}`}
aria-label="Назад"
>
<ArrowLeft size={24} />
</button>
</div>
)}
</>
);
}