mirror of
https://gitlab.com/foxixus/neomovies.git
synced 2025-10-27 17:38:50 +05:00
fix: исправлен плеер для фильмов
This commit is contained in:
10
.env
Normal file
10
.env
Normal file
@@ -0,0 +1,10 @@
|
||||
NEXT_PUBLIC_API_URL=https://neomovies-api.vercel.app
|
||||
MONGODB_URI=mongodb+srv://neomoviesmail:Vfhreif1@neo-movies.nz1e2.mongodb.net/database
|
||||
JWT_SECRET=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
NEXTAUTH_SECRET=eyJhdWQiOiI4ZmU3ODhlYmI5ZDAwNjZiNjQ2MWZkwNDlkNzU4ZDQxOTQwYzA3NjlhNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.x50tvcW
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
GMAIL_USER=neo.movies.mail@gmail.com
|
||||
GMAIL_APP_PASSWORD=togh lhlg zadn dywe
|
||||
NEXT_PUBLIC_TMDB_API_KEY=8feg88ebi9d0066b6461fa7993c23771b
|
||||
NEXT_PUBLIC_TMDB_ACCESS_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI4ZmU3ODhlYmI5ZDAwNjZiNjQ2MWZhNzk5M2MyMzcxYiIsIm5iZiI6MTcyMzQwMTM3My4yMDgsInN1YiI6IjY2YjkwNDlkNzU4ZDQxOTQwYzA3NjlhNSIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.x50tvcWDdBTEhtwRb3dE7aEe9qu4sXV_qOjLMn_Vmew
|
||||
NEXT_PUBLIC_LUMEX_URL=https://p.lumex.site/k1GbtOF2cX6p
|
||||
@@ -21,10 +21,22 @@ const nextConfig = {
|
||||
hostname: 'image.tmdb.org',
|
||||
pathname: '/**',
|
||||
},
|
||||
// Локальная разработка
|
||||
{
|
||||
protocol: 'http',
|
||||
hostname: 'localhost',
|
||||
port: '3010',
|
||||
port: '3000',
|
||||
pathname: '/images/**',
|
||||
},
|
||||
// Продакшен на Vercel
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'neomovies-api.vercel.app',
|
||||
pathname: '/images/**',
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'neo-movies.vercel.app',
|
||||
pathname: '/images/**',
|
||||
}
|
||||
],
|
||||
@@ -39,6 +51,18 @@ const nextConfig = {
|
||||
experimental: {
|
||||
scrollRestoration: true,
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/api/:path*',
|
||||
headers: [
|
||||
{ key: 'Access-Control-Allow-Origin', value: '*' },
|
||||
{ key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, OPTIONS' },
|
||||
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"@vercel/analytics": "^1.0.1",
|
||||
"@tabler/icons-react": "^3.26.0",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
|
||||
66
src/app/api/mobile/auth/login/route.ts
Normal file
66
src/app/api/mobile/auth/login/route.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { compare } from 'bcryptjs';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
const isPasswordValid = await compare(password, user.password);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный пароль' },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
if (!user.verified) {
|
||||
return NextResponse.json(
|
||||
{ error: 'EMAIL_NOT_VERIFIED' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Создаем JWT токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: user.verified,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
process.env.NEXTAUTH_SECRET!,
|
||||
{ expiresIn: '30d' }
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
user: {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: user.verified,
|
||||
isAdmin: user.isAdmin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка входа' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
61
src/app/api/mobile/auth/register/route.ts
Normal file
61
src/app/api/mobile/auth/register/route.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { hash } from 'bcryptjs';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, password, name } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
|
||||
// Проверяем, существует ли пользователь
|
||||
const existingUser = await db.collection('users').findOne({ email });
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email уже зарегистрирован' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Хешируем пароль
|
||||
const hashedPassword = await hash(password, 12);
|
||||
|
||||
// Генерируем код подтверждения
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Создаем пользователя
|
||||
const result = await db.collection('users').insertOne({
|
||||
email,
|
||||
password: hashedPassword,
|
||||
name,
|
||||
verified: false,
|
||||
verificationCode,
|
||||
verificationExpires,
|
||||
isAdmin: false,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
// Отправляем код подтверждения
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({
|
||||
id: result.insertedId.toString(),
|
||||
email,
|
||||
name,
|
||||
verified: false,
|
||||
isAdmin: false
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка регистрации' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
56
src/app/api/mobile/auth/resend-code/route.ts
Normal file
56
src/app/api/mobile/auth/resend-code/route.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import { sendVerificationEmail } from '@/lib/mailer';
|
||||
|
||||
function generateVerificationCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({ email });
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Пользователь не найден' },
|
||||
{ status: 404 }
|
||||
);
|
||||
}
|
||||
|
||||
if (user.verified) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Email уже подтвержден' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Генерируем новый код
|
||||
const verificationCode = generateVerificationCode();
|
||||
const verificationExpires = new Date(Date.now() + 10 * 60 * 1000); // 10 минут
|
||||
|
||||
// Обновляем код в базе
|
||||
await db.collection('users').updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: {
|
||||
verificationCode,
|
||||
verificationExpires
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Отправляем новый код
|
||||
await sendVerificationEmail(email, verificationCode);
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Resend code error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка отправки кода' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
62
src/app/api/mobile/auth/verify/route.ts
Normal file
62
src/app/api/mobile/auth/verify/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { connectToDatabase } from '@/lib/mongodb';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const { email, code } = await req.json();
|
||||
|
||||
const { db } = await connectToDatabase();
|
||||
const user = await db.collection('users').findOne({
|
||||
email,
|
||||
verificationCode: code,
|
||||
verificationExpires: { $gt: new Date() }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неверный код или код истек' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Подтверждаем email
|
||||
await db.collection('users').updateOne(
|
||||
{ _id: user._id },
|
||||
{
|
||||
$set: { verified: true },
|
||||
$unset: { verificationCode: "", verificationExpires: "" }
|
||||
}
|
||||
);
|
||||
|
||||
// Создаем JWT токен
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: true,
|
||||
isAdmin: user.isAdmin
|
||||
},
|
||||
process.env.NEXTAUTH_SECRET!,
|
||||
{ expiresIn: '30d' }
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
token,
|
||||
user: {
|
||||
id: user._id.toString(),
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
verified: true,
|
||||
isAdmin: user.isAdmin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Verification error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка верификации' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
45
src/app/api/movies/[id]/external-ids/route.ts
Normal file
45
src/app/api/movies/[id]/external-ids/route.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const headersList = headers();
|
||||
const response = await fetch(
|
||||
`https://neomovies-api.vercel.app/movies/${params.id}/external-ids`,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Создаем новый Response с нужными заголовками
|
||||
return new NextResponse(JSON.stringify(data), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching external IDs:', error);
|
||||
return new NextResponse(
|
||||
JSON.stringify({ error: 'Failed to fetch external IDs' }),
|
||||
{
|
||||
status: 500,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
192
src/app/categories/page.tsx
Normal file
192
src/app/categories/page.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { moviesAPI } from '@/lib/api';
|
||||
import MovieCard from '@/components/MovieCard';
|
||||
import { Movie } from '@/lib/api';
|
||||
|
||||
interface Genre {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// Styled Components
|
||||
const Container = styled.div`
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem;
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1.5rem;
|
||||
color: #fff;
|
||||
`;
|
||||
|
||||
const GenreButtons = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const GenreButton = styled.button<{ $active?: boolean }>`
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
background: ${props => props.$active ? '#3182ce' : 'rgba(255, 255, 255, 0.1)'};
|
||||
color: ${props => props.$active ? '#fff' : 'rgba(255, 255, 255, 0.8)'};
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: ${props => props.$active ? '#2b6cb0' : 'rgba(255, 255, 255, 0.2)'};
|
||||
}
|
||||
`;
|
||||
|
||||
const MovieGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (min-width: 640px) {
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 200px;
|
||||
`;
|
||||
|
||||
const Spinner = styled.div`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(255, 255, 255, 0.1);
|
||||
border-left-color: #3182ce;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorMessage = styled.div`
|
||||
color: #fc8181;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: rgba(252, 129, 129, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
margin: 2rem 0;
|
||||
`;
|
||||
|
||||
export default function CategoriesPage() {
|
||||
const [genres, setGenres] = useState<Genre[]>([]);
|
||||
const [selectedGenre, setSelectedGenre] = useState<number | null>(null);
|
||||
const [movies, setMovies] = useState<Movie[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Загрузка жанров при монтировании
|
||||
useEffect(() => {
|
||||
const fetchGenres = async () => {
|
||||
setError(null);
|
||||
try {
|
||||
console.log('Fetching genres...');
|
||||
const response = await moviesAPI.getGenres();
|
||||
console.log('Genres response:', response.data);
|
||||
|
||||
if (response.data.genres && response.data.genres.length > 0) {
|
||||
setGenres(response.data.genres);
|
||||
setSelectedGenre(response.data.genres[0].id);
|
||||
} else {
|
||||
setError('Не удалось загрузить жанры');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching genres:', error);
|
||||
setError('Ошибка при загрузке жанров');
|
||||
}
|
||||
};
|
||||
fetchGenres();
|
||||
}, []);
|
||||
|
||||
// Загрузка фильмов при изменении выбранного жанра
|
||||
useEffect(() => {
|
||||
const fetchMoviesByGenre = async () => {
|
||||
if (!selectedGenre) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
console.log('Fetching movies for genre:', selectedGenre);
|
||||
const response = await moviesAPI.getMoviesByGenre(selectedGenre);
|
||||
console.log('Movies response:', {
|
||||
total: response.data.results?.length,
|
||||
first: response.data.results?.[0]
|
||||
});
|
||||
|
||||
if (response.data.results) {
|
||||
setMovies(response.data.results);
|
||||
} else {
|
||||
setError('Не удалось загрузить фильмы');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching movies:', error);
|
||||
setError('Ошибка при загрузке фильмов');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchMoviesByGenre();
|
||||
}, [selectedGenre]);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Категории фильмов</Title>
|
||||
<ErrorMessage>{error}</ErrorMessage>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Title>Категории фильмов</Title>
|
||||
|
||||
{/* Кнопки жанров */}
|
||||
<GenreButtons>
|
||||
{genres.map((genre) => (
|
||||
<GenreButton
|
||||
key={genre.id}
|
||||
$active={selectedGenre === genre.id}
|
||||
onClick={() => setSelectedGenre(genre.id)}
|
||||
>
|
||||
{genre.name}
|
||||
</GenreButton>
|
||||
))}
|
||||
</GenreButtons>
|
||||
|
||||
{/* Сетка фильмов */}
|
||||
{loading ? (
|
||||
<LoadingContainer>
|
||||
<Spinner />
|
||||
</LoadingContainer>
|
||||
) : (
|
||||
<MovieGrid>
|
||||
{movies.map((movie) => (
|
||||
<MovieCard key={movie.id} movie={movie} />
|
||||
))}
|
||||
</MovieGrid>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Inter } from 'next/font/google';
|
||||
import './globals.css';
|
||||
import { ClientLayout } from '@/components/ClientLayout';
|
||||
import type { Metadata } from 'next';
|
||||
import { Analytics } from "@vercel/analytics/react";
|
||||
|
||||
const inter = Inter({ subsets: ['latin', 'cyrillic'] });
|
||||
|
||||
@@ -22,6 +23,7 @@ export default function RootLayout({
|
||||
</head>
|
||||
<body className={inter.className} suppressHydrationWarning>
|
||||
<ClientLayout>{children}</ClientLayout>
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { moviesAPI } from '@/lib/api';
|
||||
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 {
|
||||
@@ -137,9 +138,9 @@ export default function MovieContent({ movieId, initialMovie }: MovieContentProp
|
||||
useEffect(() => {
|
||||
const fetchImdbId = async () => {
|
||||
try {
|
||||
const newImdbId = await moviesAPI.getImdbId(movieId);
|
||||
if (newImdbId) {
|
||||
setImdbId(newImdbId);
|
||||
const { data } = await moviesAPI.getMovie(movieId);
|
||||
if (data?.imdb_id) {
|
||||
setImdbId(data.imdb_id);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching IMDb ID:', err);
|
||||
@@ -164,7 +165,7 @@ export default function MovieContent({ movieId, initialMovie }: MovieContentProp
|
||||
<Info>
|
||||
<InfoItem>Рейтинг: {movie.vote_average.toFixed(1)}</InfoItem>
|
||||
<InfoItem>Длительность: {movie.runtime} мин.</InfoItem>
|
||||
<InfoItem>Дата выхода: {new Date(movie.release_date).toLocaleDateString('ru-RU')}</InfoItem>
|
||||
<InfoItem>Дата выхода: {formatDate(movie.release_date)}</InfoItem>
|
||||
</Info>
|
||||
<GenreList>
|
||||
{movie.genres.map(genre => (
|
||||
|
||||
@@ -106,11 +106,11 @@ export default function MoviePlayer({ id, title, poster, imdbId }: MoviePlayerPr
|
||||
setError(null);
|
||||
|
||||
if (!imdbId) {
|
||||
const newImdbId = await moviesAPI.getImdbId(id);
|
||||
if (!newImdbId) {
|
||||
const { data } = await moviesAPI.getMovie(id);
|
||||
if (!data?.imdb_id) {
|
||||
throw new Error('IMDb ID не найден');
|
||||
}
|
||||
imdbId = newImdbId;
|
||||
imdbId = data.imdb_id;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching IMDb ID:', err);
|
||||
|
||||
@@ -118,7 +118,7 @@ export const moviesAPI = {
|
||||
|
||||
// Получение IMDB ID
|
||||
getImdbId(id: string | number) {
|
||||
return neoApi.get(`/movies/${id}/external-ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
|
||||
return neoApi.get(`/movies/${id}/external_ids`, { timeout: 30000 }).then(res => res.data.imdb_id);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,9 +16,30 @@ export const formatDate = (dateString: string | Date | undefined | null) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
|
||||
let date: Date;
|
||||
|
||||
if (typeof dateString === 'string') {
|
||||
// Пробуем разные форматы даты
|
||||
if (dateString.includes('T')) {
|
||||
// ISO формат
|
||||
date = new Date(dateString);
|
||||
} else if (dateString.includes('-')) {
|
||||
// YYYY-MM-DD формат
|
||||
const [year, month, day] = dateString.split('-').map(Number);
|
||||
date = new Date(year, month - 1, day);
|
||||
} else if (dateString.includes('.')) {
|
||||
// DD.MM.YYYY формат
|
||||
const [day, month, year] = dateString.split('.').map(Number);
|
||||
date = new Date(year, month - 1, day);
|
||||
} else {
|
||||
date = new Date(dateString);
|
||||
}
|
||||
} else {
|
||||
date = dateString;
|
||||
}
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
console.error('Invalid date:', dateString);
|
||||
return 'Нет даты';
|
||||
}
|
||||
|
||||
@@ -28,7 +49,7 @@ export const formatDate = (dateString: string | Date | undefined | null) => {
|
||||
day: 'numeric',
|
||||
}).format(date) + ' г.';
|
||||
} catch (error) {
|
||||
console.error('Error formatting date:', error);
|
||||
console.error('Error formatting date:', error, dateString);
|
||||
return 'Нет даты';
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user