2025-01-05 01:43:34 +00:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
|
|
import Link from 'next/link';
|
|
|
|
|
|
import Image from 'next/image';
|
2025-07-07 18:22:51 +03:00
|
|
|
|
import { useRouter } from 'next/navigation';
|
2025-08-10 22:20:58 +00:00
|
|
|
|
import { neoApi, getImageUrl } from '@/lib/neoApi';
|
2025-07-08 00:15:55 +03:00
|
|
|
|
import { Loader2, HeartCrack } from 'lucide-react';
|
2025-01-05 01:43:34 +00:00
|
|
|
|
|
|
|
|
|
|
interface Favorite {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
mediaId: string;
|
|
|
|
|
|
mediaType: 'movie' | 'tv';
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
posterPath: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function FavoritesPage() {
|
|
|
|
|
|
const [favorites, setFavorites] = useState<Favorite[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
2025-07-07 18:22:51 +03:00
|
|
|
|
const router = useRouter();
|
2025-01-05 01:43:34 +00:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const fetchFavorites = async () => {
|
2025-07-07 18:22:51 +03:00
|
|
|
|
const token = localStorage.getItem('token');
|
|
|
|
|
|
if (!token) {
|
2025-08-10 22:20:58 +00:00
|
|
|
|
router.replace('/login');
|
2025-01-05 01:43:34 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
2025-08-10 22:20:58 +00:00
|
|
|
|
const response = await neoApi.get('/api/v1/favorites');
|
2025-08-11 11:38:00 +03:00
|
|
|
|
const items = Array.isArray(response.data)
|
|
|
|
|
|
? response.data.map((m: any) => ({
|
|
|
|
|
|
id: m.id,
|
|
|
|
|
|
mediaId: String(m.id),
|
|
|
|
|
|
mediaType: 'movie' as const,
|
|
|
|
|
|
title: m.title ?? m.name ?? '',
|
|
|
|
|
|
posterPath: m.poster_path ?? '',
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [];
|
|
|
|
|
|
setFavorites(items);
|
2025-08-10 22:20:58 +00:00
|
|
|
|
} catch (error: any) {
|
2025-07-07 18:22:51 +03:00
|
|
|
|
console.error('Failed to fetch favorites:', error);
|
2025-08-10 22:20:58 +00:00
|
|
|
|
// Редиректим только при явном 401
|
|
|
|
|
|
if (error?.response?.status === 401) {
|
|
|
|
|
|
localStorage.removeItem('token');
|
|
|
|
|
|
localStorage.removeItem('userName');
|
|
|
|
|
|
localStorage.removeItem('userEmail');
|
|
|
|
|
|
router.replace('/login');
|
|
|
|
|
|
}
|
2025-01-05 01:43:34 +00:00
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
fetchFavorites();
|
2025-07-07 18:22:51 +03:00
|
|
|
|
}, [router]);
|
2025-01-05 01:43:34 +00:00
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return (
|
2025-07-08 00:15:55 +03:00
|
|
|
|
<div className="min-h-screen bg-background flex items-center justify-center">
|
|
|
|
|
|
<Loader2 className="h-16 w-16 animate-spin text-accent" />
|
|
|
|
|
|
</div>
|
2025-01-05 01:43:34 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (favorites.length === 0) {
|
|
|
|
|
|
return (
|
2025-07-08 00:15:55 +03:00
|
|
|
|
<div className="min-h-screen bg-background flex items-center justify-center">
|
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
|
<HeartCrack size={80} className="mx-auto mb-6 text-gray-400" />
|
|
|
|
|
|
<h1 className="text-3xl font-bold text-foreground mb-4">Избранное пусто</h1>
|
|
|
|
|
|
<p className="text-lg text-gray-600 dark:text-gray-400 mb-8">
|
|
|
|
|
|
У вас пока нет избранных фильмов и сериалов
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<Link
|
|
|
|
|
|
href="/"
|
|
|
|
|
|
className="inline-flex items-center justify-center px-6 py-3 text-base font-medium text-white bg-accent rounded-lg hover:bg-accent/90 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
Найти фильмы
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-01-05 01:43:34 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2025-07-08 00:15:55 +03:00
|
|
|
|
<div className="min-h-screen bg-background">
|
|
|
|
|
|
<div className="container mx-auto px-4 py-8">
|
|
|
|
|
|
<div className="mb-8">
|
|
|
|
|
|
<h1 className="text-4xl font-bold text-foreground mb-4">Избранное</h1>
|
|
|
|
|
|
<p className="text-lg text-gray-600 dark:text-gray-400">
|
|
|
|
|
|
Ваша коллекция любимых фильмов и сериалов
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-6">
|
|
|
|
|
|
{favorites.map(favorite => (
|
|
|
|
|
|
<Link
|
|
|
|
|
|
key={`${favorite.mediaType}-${favorite.mediaId}`}
|
|
|
|
|
|
href={`/${favorite.mediaType === 'movie' ? 'movie' : 'tv'}/${favorite.mediaId}`}
|
|
|
|
|
|
className="group"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="overflow-hidden rounded-xl bg-gray-100 dark:bg-gray-800 shadow-sm transition-all duration-300 group-hover:shadow-lg group-hover:-translate-y-1">
|
|
|
|
|
|
<div className="relative aspect-[2/3] w-full">
|
|
|
|
|
|
<Image
|
|
|
|
|
|
src={favorite.posterPath ? getImageUrl(favorite.posterPath) : '/images/placeholder.jpg'}
|
|
|
|
|
|
alt={favorite.title}
|
|
|
|
|
|
fill
|
|
|
|
|
|
sizes="(max-width: 640px) 50vw, (max-width: 768px) 33vw, (max-width: 1024px) 25vw, (max-width: 1280px) 20vw, 16vw"
|
|
|
|
|
|
className="object-cover"
|
|
|
|
|
|
unoptimized
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="mt-3 px-1">
|
|
|
|
|
|
<h3 className="font-semibold text-base text-foreground truncate group-hover:text-accent transition-colors">
|
|
|
|
|
|
{favorite.title}
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
|
|
|
|
{favorite.mediaType === 'movie' ? 'Фильм' : 'Сериал'}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Link>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-01-05 01:43:34 +00:00
|
|
|
|
);
|
2025-08-10 22:20:58 +00:00
|
|
|
|
}
|