mirror of
https://gitlab.com/foxixus/neomovies.git
synced 2025-10-28 18:08:49 +05:00
full change ui and small fixes
This commit is contained in:
@@ -1,234 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { moviesAPI } from '@/lib/neoApi';
|
||||
import { getImageUrl } from '@/lib/neoApi';
|
||||
import type { MovieDetails } from '@/lib/api';
|
||||
import { useSettings } from '@/hooks/useSettings';
|
||||
import MoviePlayer from '@/components/MoviePlayer';
|
||||
import FavoriteButton from '@/components/FavoriteButton';
|
||||
import { formatDate } from '@/lib/utils';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
kbox: any;
|
||||
}
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
padding: 0 24px;
|
||||
`;
|
||||
|
||||
const Content = styled.div`
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const MovieInfo = styled.div`
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const PosterContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const Poster = styled.img`
|
||||
width: 300px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 200px;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
width: 160px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Details = styled.div`
|
||||
flex: 1;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
color: white;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Info = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const InfoItem = styled.span`
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 0.9rem;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 0.8rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.35rem 0.6rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const GenreList = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const Genre = styled.span`
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
background: rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
`;
|
||||
|
||||
const Tagline = styled.div`
|
||||
font-style: italic;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Overview = styled.p`
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
line-height: 1.6;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 0.95rem;
|
||||
text-align: justify;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
`;
|
||||
|
||||
const ActionButtons = styled.div`
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const WatchButton = styled.button`
|
||||
background: #e50914;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
@media (max-width: 480px) {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #f40612;
|
||||
}
|
||||
`;
|
||||
|
||||
const PlayerSection = styled.div`
|
||||
margin-top: 2rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
`;
|
||||
|
||||
const ErrorContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400px;
|
||||
color: #ff4444;
|
||||
`;
|
||||
|
||||
import { useParams } from 'next/navigation';
|
||||
import { PlayCircle, ArrowLeft } from 'lucide-react';
|
||||
|
||||
interface MovieContentProps {
|
||||
movieId: string;
|
||||
@@ -236,9 +16,11 @@ interface MovieContentProps {
|
||||
}
|
||||
|
||||
export default function MovieContent({ movieId, initialMovie }: MovieContentProps) {
|
||||
const { settings } = useSettings();
|
||||
const [movie] = useState<MovieDetails>(initialMovie);
|
||||
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(() => {
|
||||
const fetchImdbId = async () => {
|
||||
@@ -254,62 +36,135 @@ export default function MovieContent({ movieId, initialMovie }: MovieContentProp
|
||||
fetchImdbId();
|
||||
}, [movieId]);
|
||||
|
||||
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 (
|
||||
<Container>
|
||||
<Content>
|
||||
<MovieInfo>
|
||||
<PosterContainer>
|
||||
<Poster
|
||||
src={getImageUrl(movie.poster_path)}
|
||||
alt={movie.title}
|
||||
loading="eager"
|
||||
/>
|
||||
</PosterContainer>
|
||||
<>
|
||||
<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">
|
||||
{/* Left Column: Poster */}
|
||||
<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(movie.poster_path, 'w500')}
|
||||
alt={`Постер фильма ${movie.title}`}
|
||||
fill
|
||||
className="object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Details>
|
||||
<Title>{movie.title}</Title>
|
||||
<Info>
|
||||
<InfoItem>Рейтинг: {movie.vote_average.toFixed(1)}</InfoItem>
|
||||
<InfoItem>Длительность: {movie.runtime} мин.</InfoItem>
|
||||
<InfoItem>Дата выхода: {formatDate(movie.release_date)}</InfoItem>
|
||||
</Info>
|
||||
<GenreList>
|
||||
{movie.genres.map(genre => (
|
||||
<Genre key={genre.id}>{genre.name}</Genre>
|
||||
))}
|
||||
</GenreList>
|
||||
{movie.tagline && <Tagline>{movie.tagline}</Tagline>}
|
||||
<Overview>{movie.overview}</Overview>
|
||||
|
||||
<ActionButtons>
|
||||
{imdbId && (
|
||||
<WatchButton
|
||||
onClick={() => document.getElementById('movie-player')?.scrollIntoView({ behavior: 'smooth' })}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5.14V19.14L19 12.14L8 5.14Z" fill="currentColor" />
|
||||
</svg>
|
||||
Смотреть
|
||||
</WatchButton>
|
||||
{/* Middle Column: Details */}
|
||||
<div className="md:col-span-2">
|
||||
<h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
|
||||
{movie.title}
|
||||
</h1>
|
||||
{movie.tagline && (
|
||||
<p className="mt-1 text-lg text-muted-foreground">{movie.tagline}</p>
|
||||
)}
|
||||
<FavoriteButton
|
||||
mediaId={movie.id.toString()}
|
||||
mediaType="movie"
|
||||
title={movie.title}
|
||||
posterPath={movie.poster_path}
|
||||
/>
|
||||
</ActionButtons>
|
||||
</Details>
|
||||
</MovieInfo>
|
||||
|
||||
{imdbId && (
|
||||
<PlayerSection id="movie-player">
|
||||
<MoviePlayer
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
</PlayerSection>
|
||||
)}
|
||||
</Content>
|
||||
</Container>
|
||||
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||
<span className="font-medium">Рейтинг: {movie.vote_average.toFixed(1)}</span>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{movie.runtime} мин.</span>
|
||||
<span className="text-muted-foreground">|</span>
|
||||
<span className="text-muted-foreground">{formatDate(movie.release_date)}</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{movie.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>{movie.overview}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex items-center gap-4">
|
||||
{/* Mobile-only Watch Button */}
|
||||
{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={movie.id.toString()}
|
||||
mediaType="movie"
|
||||
title={movie.title}
|
||||
posterPath={movie.poster_path}
|
||||
className="!bg-secondary !text-secondary-foreground hover:!bg-secondary/80"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Desktop-only Embedded Player */}
|
||||
{imdbId && (
|
||||
<div id="movie-player" className="mt-10 hidden md:block rounded-lg bg-secondary/50 p-4 shadow-inner">
|
||||
<MoviePlayer
|
||||
id={movie.id.toString()}
|
||||
title={movie.title}
|
||||
poster={movie.poster_path || ''}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fullscreen Player for Mobile */}
|
||||
{isPlayerFullscreen && imdbId && (
|
||||
<div
|
||||
className="fixed inset-0 z-50 flex items-center justify-center bg-black"
|
||||
onMouseMove={showControls}
|
||||
onClick={showControls}
|
||||
>
|
||||
<MoviePlayer
|
||||
id={movie.id.toString()}
|
||||
title={movie.title}
|
||||
poster={movie.poster_path || ''}
|
||||
imdbId={imdbId}
|
||||
/>
|
||||
<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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user