Skip to content

Instantly share code, notes, and snippets.

@Zamay
Created July 7, 2025 20:07
Show Gist options
  • Save Zamay/8e273c4b2e00454567194f8e0f1ebbb5 to your computer and use it in GitHub Desktop.
Save Zamay/8e273c4b2e00454567194f8e0f1ebbb5 to your computer and use it in GitHub Desktop.
import React, { useState, useEffect, useRef } from 'react';
import { Search, Film, Tv, Star, Bookmark, Home, Play, ArrowLeft, Menu, X, Loader, Download, Settings, Info, Clock, Heart, Filter, ChevronDown, ChevronUp, User, Grid, List } from 'lucide-react';
const HDRezkaMobile = () => {
const [currentPage, setCurrentPage] = useState('home');
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [bookmarks, setBookmarks] = useState([]);
const [watchHistory, setWatchHistory] = useState([]);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [selectedMovie, setSelectedMovie] = useState(null);
const [movies, setMovies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [currentVideoUrl, setCurrentVideoUrl] = useState(null);
const [showPlayer, setShowPlayer] = useState(false);
const [viewMode, setViewMode] = useState('grid');
const [sortBy, setSortBy] = useState('rating');
const [filterOpen, setFilterOpen] = useState(false);
const [selectedYear, setSelectedYear] = useState('all');
const [selectedGenre, setSelectedGenre] = useState('all');
const [isOnline, setIsOnline] = useState(navigator.onLine);
const [installPrompt, setInstallPrompt] = useState(null);
const [showInstallBanner, setShowInstallBanner] = useState(false);
const searchInputRef = useRef(null);
// Слухаємо статус мережі
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
// PWA install handler
useEffect(() => {
const handleBeforeInstallPrompt = (e) => {
e.preventDefault();
setInstallPrompt(e);
setShowInstallBanner(true);
};
window.addEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
return () => {
window.removeEventListener('beforeinstallprompt', handleBeforeInstallPrompt);
};
}, []);
const installApp = async () => {
if (installPrompt) {
const result = await installPrompt.prompt();
if (result.outcome === 'accepted') {
setShowInstallBanner(false);
}
setInstallPrompt(null);
}
};
// Функція для парсингу контенту з HDRezka
const parseHDRezkaContent = async (query = '', category = 'all') => {
setIsLoading(true);
try {
// Симуляція запиту до HDRezka (реальний парсинг потребує проксі-сервера)
await new Promise(resolve => setTimeout(resolve, 1000));
const demoMovies = getDemoMovies();
let filteredMovies = demoMovies;
if (query) {
filteredMovies = demoMovies.filter(movie =>
movie.title.toLowerCase().includes(query.toLowerCase()) ||
movie.description.toLowerCase().includes(query.toLowerCase())
);
}
if (category !== 'all') {
filteredMovies = filteredMovies.filter(movie => movie.type === category);
}
// Сортування
filteredMovies.sort((a, b) => {
switch (sortBy) {
case 'rating':
return b.rating - a.rating;
case 'year':
return b.year - a.year;
case 'title':
return a.title.localeCompare(b.title);
default:
return 0;
}
});
return filteredMovies;
} catch (error) {
console.error('Помилка парсингу:', error);
return getDemoMovies();
} finally {
setIsLoading(false);
}
};
// Розширені демо дані
const getDemoMovies = () => {
return [
{
id: 1,
title: "Истребитель демонов",
type: "anime",
year: 2019,
rating: 8.7,
genre: "Экшен",
poster: "https://images.unsplash.com/photo-1578662996442-48f60103fc96?w=300&h=450&fit=crop",
description: "Танджиро Камадо — добрый мальчик, который живёт со своей семьей в горах и зарабатывает на жизнь продажей угля. Однажды он возвращается домой и обнаруживает, что вся его семья была убита демоном.",
episodes: 26,
seasons: 3,
duration: "24м",
link: "https://hdrezka.me/animation/adventures/30522-istrebitel-demonov.html",
trailer: "https://www.youtube.com/embed/VQGCKyvzIM4",
cast: ["Нацуки Ханаэ", "Сатоми Сато", "Хиро Симоно"],
director: "Харуо Сотодзаки",
studio: "Ufotable"
},
{
id: 2,
title: "Мстители: Финал",
type: "movie",
year: 2019,
rating: 8.4,
genre: "Фантастика",
poster: "https://images.unsplash.com/photo-1440404653325-ab127d49abc1?w=300&h=450&fit=crop",
description: "Оставшиеся в живых Мстители и их союзники должны разработать новый план, который поможет им противостоять разрушительным действиям могущественного титана Таноса.",
duration: "3ч 1м",
link: "https://hdrezka.me/films/fiction/1234-avengers-endgame.html",
trailer: "https://www.youtube.com/embed/TcMBFSGVi1c",
cast: ["Роберт Дауни мл.", "Крис Эванс", "Марк Руффало"],
director: "Энтони Руссо, Джо Руссо",
studio: "Marvel Studios"
},
{
id: 3,
title: "Игра престолов",
type: "series",
year: 2011,
rating: 9.2,
genre: "Драма",
poster: "https://images.unsplash.com/photo-1518709268805-4e9042af2176?w=300&h=450&fit=crop",
description: "Борьба за власть в Семи Королевствах Вестероса. Благородные семьи сражаются за контроль над Железным троном, в то время как древнее зло пробуждается на севере.",
seasons: 8,
episodes: 73,
duration: "57м",
link: "https://hdrezka.me/series/drama/5678-game-of-thrones.html",
trailer: "https://www.youtube.com/embed/rlR4PJn8b8I",
cast: ["Питер Динклэйдж", "Лена Хиди", "Эмилия Кларк"],
director: "Дэвид Бениофф, Д. Б. Вайсс",
studio: "HBO"
},
{
id: 4,
title: "Во все тяжкие",
type: "series",
year: 2008,
rating: 9.5,
genre: "Драма",
poster: "https://images.unsplash.com/photo-1489599162163-36f1e05c0c90?w=300&h=450&fit=crop",
description: "Школьный учитель химии Уолтер Уайт узнаёт, что болен раком лёгких. Решив обеспечить семью деньгами, он начинает варить наркотики вместе со своим бывшим учеником.",
seasons: 5,
episodes: 62,
duration: "47м",
link: "https://hdrezka.me/series/drama/breaking-bad.html",
trailer: "https://www.youtube.com/embed/HhesaQXLuRY",
cast: ["Брайан Крэнстон", "Аарон Пол", "Анна Ганн"],
director: "Винс Гиллиган",
studio: "AMC"
},
{
id: 5,
title: "Дюна",
type: "movie",
year: 2021,
rating: 8.0,
genre: "Фантастика",
poster: "https://images.unsplash.com/photo-1446776877081-d282a0f896e2?w=300&h=450&fit=crop",
description: "Наследник знаменитого дома Атрейдесов Пол отправляется вместе с семьёй на самую опасную планету во Вселенной — Арракис, чтобы обеспечить будущее своего рода.",
duration: "2ч 35м",
link: "https://hdrezka.me/films/fiction/dune-2021.html",
trailer: "https://www.youtube.com/embed/n9xhJrPXop4",
cast: ["Тимоти Шаламе", "Ребекка Фергюсон", "Оскар Айзек"],
director: "Дени Вильнёв",
studio: "Warner Bros."
},
{
id: 6,
title: "Атака титанов",
type: "anime",
year: 2013,
rating: 9.0,
genre: "Экшен",
poster: "https://images.unsplash.com/photo-1578662015295-4d45d3c7c16d?w=300&h=450&fit=crop",
description: "Человечество живёт в городах, окружённых огромными стенами, защищающими от титанов — гигантских гуманоидных существ, пожирающих людей.",
episodes: 87,
seasons: 4,
duration: "24м",
link: "https://hdrezka.me/animation/drama/attack-on-titan.html",
trailer: "https://www.youtube.com/embed/LHtdKWJdif4",
cast: ["Юки Кадзи", "Марина Иноуэ", "Есимаса Хосоя"],
director: "Тэцуро Араки",
studio: "Wit Studio"
}
];
};
// Функція для отримання відео URL
const getVideoUrl = async (movieLink) => {
try {
// Імітація завантаження плеєра
await new Promise(resolve => setTimeout(resolve, 2000));
return `https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1`;
} catch (error) {
console.error('Помилка отримання відео:', error);
return null;
}
};
// Завантаження контенту при першому відкритті
useEffect(() => {
loadContent();
// Завантаження збережених даних
const savedBookmarks = JSON.parse(localStorage.getItem('hdrezka_bookmarks') || '[]');
const savedHistory = JSON.parse(localStorage.getItem('hdrezka_history') || '[]');
setBookmarks(savedBookmarks);
setWatchHistory(savedHistory);
}, []);
// Збереження даних
useEffect(() => {
localStorage.setItem('hdrezka_bookmarks', JSON.stringify(bookmarks));
}, [bookmarks]);
useEffect(() => {
localStorage.setItem('hdrezka_history', JSON.stringify(watchHistory));
}, [watchHistory]);
const loadContent = async () => {
const content = await parseHDRezkaContent();
setMovies(content);
};
// Пошук з дебаунсом
useEffect(() => {
const searchTimeout = setTimeout(() => {
if (searchQuery.length > 0) {
searchMovies();
} else {
// Якщо пошук порожній, показуємо всі фільми без loading
const demoMovies = getDemoMovies();
setMovies(demoMovies);
}
}, 500); // Збільшуємо debounce до 500ms
return () => clearTimeout(searchTimeout);
}, [searchQuery, selectedCategory, sortBy]);
// const searchMovies = async () => {
// const results = await parseHDRezkaContent(searchQuery, selectedCategory);
// setMovies(results);
// };
const searchMovies = async () => {
// Не показуємо loading для коротких пошукових запитів
if (searchQuery.length > 2) {
setIsLoading(true);
}
try {
// Симуляція запиту (зменшуємо час очікування)
await new Promise(resolve => setTimeout(resolve, 300));
const demoMovies = getDemoMovies();
let filteredMovies = demoMovies;
if (searchQuery) {
filteredMovies = demoMovies.filter(movie =>
movie.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
movie.description.toLowerCase().includes(searchQuery.toLowerCase())
);
}
if (selectedCategory !== 'all') {
filteredMovies = filteredMovies.filter(movie => movie.type === selectedCategory);
}
// Сортування
filteredMovies.sort((a, b) => {
switch (sortBy) {
case 'rating':
return b.rating - a.rating;
case 'year':
return b.year - a.year;
case 'title':
return a.title.localeCompare(b.title);
default:
return 0;
}
});
setMovies(filteredMovies);
} catch (error) {
console.error('Помилка пошуку:', error);
setMovies(getDemoMovies());
} finally {
setIsLoading(false);
}
};
const categories = [
{ id: 'all', name: 'Все', icon: Home },
{ id: 'movie', name: 'Фильмы', icon: Film },
{ id: 'series', name: 'Сериалы', icon: Tv },
{ id: 'anime', name: 'Аниме', icon: Star }
];
const genres = [
{ id: 'all', name: 'Все жанры' },
{ id: 'action', name: 'Экшен' },
{ id: 'drama', name: 'Драма' },
{ id: 'comedy', name: 'Комедия' },
{ id: 'horror', name: 'Ужасы' },
{ id: 'sci-fi', name: 'Фантастика' },
{ id: 'romance', name: 'Романтика' }
];
const years = [
{ id: 'all', name: 'Все годы' },
{ id: '2024', name: '2024' },
{ id: '2023', name: '2023' },
{ id: '2022', name: '2022' },
{ id: '2021', name: '2021' },
{ id: '2020', name: '2020' }
];
const sortOptions = [
{ id: 'rating', name: 'По рейтингу' },
{ id: 'year', name: 'По году' },
{ id: 'title', name: 'По названию' }
];
const filteredMovies = movies.filter(movie => {
const matchesCategory = selectedCategory === 'all' || movie.type === selectedCategory;
const matchesSearch = movie.title.toLowerCase().includes(searchQuery.toLowerCase());
const matchesYear = selectedYear === 'all' || movie.year.toString() === selectedYear;
const matchesGenre = selectedGenre === 'all' || movie.genre.toLowerCase().includes(selectedGenre.toLowerCase());
return matchesCategory && matchesSearch && matchesYear && matchesGenre;
});
const toggleBookmark = (movie) => {
setBookmarks(prev => {
const isBookmarked = prev.find(b => b.id === movie.id);
if (isBookmarked) {
return prev.filter(b => b.id !== movie.id);
} else {
return [...prev, movie];
}
});
};
const addToHistory = (movie) => {
setWatchHistory(prev => {
const filtered = prev.filter(h => h.id !== movie.id);
return [{ ...movie, watchedAt: new Date().toISOString() }, ...filtered].slice(0, 50);
});
};
const isBookmarked = (movieId) => {
return bookmarks.some(b => b.id === movieId);
};
const playMovie = async (movie) => {
addToHistory(movie);
const videoUrl = await getVideoUrl(movie.link);
if (videoUrl) {
setCurrentVideoUrl(videoUrl);
setShowPlayer(true);
}
};
// Компонент банеру встановлення
const InstallBanner = () => {
if (!showInstallBanner) return null;
return (
<div className="bg-gradient-to-r from-red-600 to-red-700 text-white p-4 flex items-center justify-between">
<div className="flex items-center gap-3">
<Download className="h-5 w-5" />
<div>
<p className="font-semibold">Установить приложение</p>
<p className="text-sm opacity-90">Быстрый доступ к HDRezka</p>
</div>
</div>
<div className="flex gap-2">
<button
onClick={installApp}
className="bg-white text-red-600 px-4 py-2 rounded font-semibold text-sm hover:bg-gray-100"
>
Установить
</button>
<button
onClick={() => setShowInstallBanner(false)}
className="p-2 hover:bg-red-800 rounded"
>
<X className="h-4 w-4" />
</button>
</div>
</div>
);
};
// Компонент фільтрів
const FilterPanel = () => {
if (!filterOpen) return null;
return (
<div className="bg-gray-800 p-4 border-t border-gray-700">
<div className="space-y-4">
<div>
<label className="block text-sm font-medium mb-2">Сортировка</label>
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="w-full p-2 bg-gray-700 rounded border border-gray-600 text-white"
>
{sortOptions.map(option => (
<option key={option.id} value={option.id}>{option.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-2">Жанр</label>
<select
value={selectedGenre}
onChange={(e) => setSelectedGenre(e.target.value)}
className="w-full p-2 bg-gray-700 rounded border border-gray-600 text-white"
>
{genres.map(genre => (
<option key={genre.id} value={genre.id}>{genre.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium mb-2">Год</label>
<select
value={selectedYear}
onChange={(e) => setSelectedYear(e.target.value)}
className="w-full p-2 bg-gray-700 rounded border border-gray-600 text-white"
>
{years.map(year => (
<option key={year.id} value={year.id}>{year.name}</option>
))}
</select>
</div>
</div>
</div>
);
};
// Компонент статусу мережі
const NetworkStatus = () => {
if (isOnline) return null;
return (
<div className="bg-yellow-600 text-white p-2 text-center text-sm">
Нет подключения к интернету
</div>
);
};
// Компонент відеоплеєра
const VideoPlayer = ({ videoUrl, movie, onClose }) => (
<div className="fixed inset-0 bg-black z-50 flex flex-col">
<div className="bg-gray-800 p-4 flex items-center justify-between">
<h2 className="text-white font-semibold truncate">{movie.title}</h2>
<button
onClick={onClose}
className="text-white hover:text-gray-300 p-2"
>
<X className="h-6 w-6" />
</button>
</div>
<div className="flex-1 bg-black flex items-center justify-center">
<iframe
src={videoUrl}
className="w-full h-full"
allowFullScreen
frameBorder="0"
allow="autoplay; fullscreen"
/>
</div>
</div>
);
const MovieCard = ({ movie }) => (
<div
className={`bg-gray-800 rounded-lg overflow-hidden shadow-lg cursor-pointer transform transition-all duration-300 hover:scale-105 hover:shadow-2xl ${
viewMode === 'list' ? 'flex flex-row h-32' : 'flex flex-col'
}`}
onClick={() => {
setSelectedMovie(movie);
setCurrentPage('movie');
}}
>
<div className={`relative ${viewMode === 'list' ? 'w-24 flex-shrink-0' : 'w-full'}`}>
<img
src={movie.poster}
alt={movie.title}
className={`object-cover ${viewMode === 'list' ? 'w-full h-full' : 'w-full h-48'}`}
/>
<div className="absolute top-2 right-2 bg-black bg-opacity-70 text-white px-2 py-1 rounded text-xs">
⭐ {movie.rating}
</div>
<button
onClick={(e) => {
e.stopPropagation();
toggleBookmark(movie);
}}
className={`absolute top-2 left-2 p-1 rounded-full ${
isBookmarked(movie.id) ? 'bg-red-500' : 'bg-black bg-opacity-50'
} text-white hover:bg-red-600 transition-colors`}
>
<Bookmark className="h-3 w-3" />
</button>
</div>
<div className={`p-3 ${viewMode === 'list' ? 'flex-1' : ''}`}>
<h3 className={`text-white font-semibold mb-1 ${viewMode === 'list' ? 'text-sm' : 'text-base'} line-clamp-2`}>
{movie.title}
</h3>
<div className={`flex items-center justify-between text-gray-400 ${viewMode === 'list' ? 'text-xs' : 'text-sm'}`}>
<span>{movie.year}</span>
<span className="capitalize">{movie.type}</span>
</div>
{movie.duration && (
<div className={`text-gray-400 ${viewMode === 'list' ? 'text-xs' : 'text-sm'} mt-1`}>
{movie.duration}
</div>
)}
{movie.seasons && (
<div className={`text-gray-400 ${viewMode === 'list' ? 'text-xs' : 'text-sm'} mt-1`}>
{movie.seasons} сезон{movie.seasons > 1 ? (movie.seasons > 4 ? 'ов' : 'а') : ''} • {movie.episodes} серий
</div>
)}
</div>
</div>
);
const MovieDetail = ({ movie }) => (
<div className="min-h-screen bg-gray-900 text-white">
<div className="relative">
<img
src={movie.poster}
alt={movie.title}
className="w-full h-64 object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-gray-900 via-transparent to-transparent" />
<button
onClick={() => {
setCurrentPage('home');
setSelectedMovie(null);
}}
className="absolute top-4 left-4 p-2 bg-black bg-opacity-50 rounded-full text-white hover:bg-opacity-70 transition-colors"
>
<ArrowLeft className="h-6 w-6" />
</button>
<button
onClick={() => toggleBookmark(movie)}
className={`absolute top-4 right-4 p-2 rounded-full ${
isBookmarked(movie.id) ? 'bg-red-500' : 'bg-black bg-opacity-50'
} text-white hover:bg-red-600 transition-colors`}
>
<Bookmark className="h-6 w-6" />
</button>
</div>
<div className="p-6">
<h1 className="text-2xl font-bold mb-2">{movie.title}</h1>
<div className="flex items-center gap-4 mb-4 text-gray-400">
<span>{movie.year}</span>
<span>⭐ {movie.rating}</span>
<span className="capitalize">{movie.type}</span>
<span>{movie.genre}</span>
</div>
<div className="flex gap-3 mb-6">
<button
onClick={() => playMovie(movie)}
className="flex-1 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 px-6 rounded-lg flex items-center justify-center gap-2 transition-colors"
>
<Play className="h-5 w-5" />
Смотреть
</button>
{movie.trailer && (
<button
onClick={() => window.open(movie.trailer, '_blank')}
className="bg-gray-700 hover:bg-gray-600 text-white font-semibold py-3 px-6 rounded-lg flex items-center justify-center gap-2 transition-colors"
>
<Play className="h-5 w-5" />
Трейлер
</button>
)}
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">Описание</h3>
<p className="text-gray-300 leading-relaxed">{movie.description}</p>
</div>
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">Информация</h3>
<div className="space-y-2 text-gray-300">
<div><span className="text-gray-400">Режиссёр:</span> {movie.director}</div>
<div><span className="text-gray-400">Студия:</span> {movie.studio}</div>
<div><span className="text-gray-400">В главных ролях:</span> {movie.cast?.join(', ')}</div>
{movie.duration && (
<div><span className="text-gray-400">Длительность:</span> {movie.duration}</div>
)}
</div>
</div>
{movie.seasons && (
<div className="mb-6">
<h3 className="text-lg font-semibold mb-3">Сезоны</h3>
<div className="grid grid-cols-2 gap-2">
{Array.from({ length: movie.seasons }, (_, i) => (
<button
key={i}
className="bg-gray-700 hover:bg-gray-600 text-white py-2 px-4 rounded transition-colors"
>
Сезон {i + 1}
</button>
))}
</div>
</div>
)}
</div>
</div>
);
const HomePage = () => (
<div className="min-h-screen bg-gray-900 text-white">
<NetworkStatus />
<InstallBanner />
{/* Header */}
<div className="bg-gray-800 p-4">
<div className="flex items-center justify-between mb-4">
<h1 className="text-xl font-bold">HDRezka Mobile</h1>
<div className="flex items-center gap-2">
<button
onClick={() => setViewMode(viewMode === 'grid' ? 'list' : 'grid')}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
{viewMode === 'grid' ? <List className="h-5 w-5" /> : <Grid className="h-5 w-5" />}
</button>
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
</div>
{/* Search */}
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-gray-400" />
<input
ref={searchInputRef}
type="text"
placeholder="Поиск фильмов и сериалов..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-3 bg-gray-700 text-white rounded-lg focus:ring-2 focus:ring-red-500 focus:outline-none"
/>
</div>
{/* Filter Toggle */}
<div className="flex items-center justify-between">
<div className="flex gap-2 overflow-x-auto">
{categories.map(category => {
const Icon = category.icon;
return (
<button
key={category.id}
onClick={() => setSelectedCategory(category.id)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg whitespace-nowrap transition-colors ${
selectedCategory === category.id
? 'bg-red-600 text-white'
: 'bg-gray-700 text-gray-300 hover:bg-gray-600'
}`}
>
<Icon className="h-4 w-4" />
<span className="text-sm">{category.name}</span>
</button>
);
})}
</div>
<button
onClick={() => setFilterOpen(!filterOpen)}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
<Filter className="h-5 w-5" />
</button>
</div>
</div>
{/* Mobile Menu */}
{isMenuOpen && (
<div className="bg-gray-800 p-4 border-t border-gray-700">
<div className="space-y-2">
<button
onClick={() => {
setCurrentPage('bookmarks');
setIsMenuOpen(false);
}}
className="w-full flex items-center gap-2 p-3 bg-gray-700 text-gray-300 hover:bg-gray-600 rounded-lg transition-colors"
>
<Bookmark className="h-5 w-5" />
<span>Закладки ({bookmarks.length})</span>
</button>
<button
onClick={() => {
setCurrentPage('history');
setIsMenuOpen(false);
}}
className="w-full flex items-center gap-2 p-3 bg-gray-700 text-gray-300 hover:bg-gray-600 rounded-lg transition-colors"
>
<Clock className="h-5 w-5" />
<span>История ({watchHistory.length})</span>
</button>
<button
onClick={() => {
setCurrentPage('settings');
setIsMenuOpen(false);
}}
className="w-full flex items-center gap-2 p-3 bg-gray-700 text-gray-300 hover:bg-gray-600 rounded-lg transition-colors"
>
<Settings className="h-5 w-5" />
<span>Настройки</span>
</button>
</div>
</div>
)}
<FilterPanel />
{/* Content */}
<div className="p-4">
{isLoading ? (
<div className="flex items-center justify-center py-12">
<Loader className="h-8 w-8 animate-spin text-red-500" />
</div>
) : (
<>
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">
{selectedCategory === 'all' ? 'Популярные' : categories.find(c => c.id === selectedCategory)?.name}
</h2>
<span className="text-gray-400 text-sm">
{filteredMovies.length} результат{filteredMovies.length !== 1 ? 'ов' : ''}
</span>
</div>
<div className={`${viewMode === 'grid' ? 'grid grid-cols-2 gap-4' : 'space-y-4'}`}>
{filteredMovies.map(movie => (
<MovieCard key={movie.id} movie={movie} />
))}
</div>
{filteredMovies.length === 0 && !isLoading && (
<div className="text-center py-12">
<Film className="h-16 w-16 text-gray-600 mx-auto mb-4" />
<p className="text-gray-400">Ничего не найдено</p>
</div>
)}
</>
)}
</div>
</div>
);
const BookmarksPage = () => (
<div className="min-h-screen bg-gray-900 text-white">
<div className="bg-gray-800 p-4 flex items-center gap-4">
<button
onClick={() => setCurrentPage('home')}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
<ArrowLeft className="h-6 w-6" />
</button>
<h1 className="text-xl font-bold">Закладки</h1>
</div>
<div className="p-4">
{bookmarks.length === 0 ? (
<div className="text-center py-12">
<Bookmark className="h-16 w-16 text-gray-600 mx-auto mb-4" />
<p className="text-gray-400">Нет сохраненных фильмов</p>
</div>
) : (
<div className="grid grid-cols-2 gap-4">
{bookmarks.map(movie => (
<MovieCard key={movie.id} movie={movie} />
))}
</div>
)}
</div>
</div>
);
const HistoryPage = () => (
<div className="min-h-screen bg-gray-900 text-white">
<div className="bg-gray-800 p-4 flex items-center gap-4">
<button
onClick={() => setCurrentPage('home')}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
<ArrowLeft className="h-6 w-6" />
</button>
<h1 className="text-xl font-bold">История просмотров</h1>
</div>
<div className="p-4">
{watchHistory.length === 0 ? (
<div className="text-center py-12">
<Clock className="h-16 w-16 text-gray-600 mx-auto mb-4" />
<p className="text-gray-400">История просмотров пуста</p>
</div>
) : (
<div className="space-y-4">
{watchHistory.map(movie => (
<div key={movie.id} className="flex items-center gap-4 bg-gray-800 p-4 rounded-lg">
<img
src={movie.poster}
alt={movie.title}
className="w-16 h-24 object-cover rounded"
/>
<div className="flex-1">
<h3 className="font-semibold mb-1">{movie.title}</h3>
<p className="text-gray-400 text-sm">{movie.year} • {movie.type}</p>
<p className="text-gray-500 text-xs mt-1">
Просмотрено: {new Date(movie.watchedAt).toLocaleDateString()}
</p>
</div>
<button
onClick={() => playMovie(movie)}
className="p-2 bg-red-600 hover:bg-red-700 rounded-lg transition-colors"
>
<Play className="h-5 w-5" />
</button>
</div>
))}
</div>
)}
</div>
</div>
);
const SettingsPage = () => (
<div className="min-h-screen bg-gray-900 text-white">
<div className="bg-gray-800 p-4 flex items-center gap-4">
<button
onClick={() => setCurrentPage('home')}
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
>
<ArrowLeft className="h-6 w-6" />
</button>
<h1 className="text-xl font-bold">Настройки</h1>
</div>
<div className="p-4 space-y-4">
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="font-semibold mb-2">Приложение</h3>
<div className="space-y-2 text-sm text-gray-300">
<p>Версия: 1.0.0</p>
<p>Статус: {isOnline ? 'Онлайн' : 'Оффлайн'}</p>
<p>Закладки: {bookmarks.length}</p>
<p>История: {watchHistory.length}</p>
</div>
</div>
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="font-semibold mb-2">Действия</h3>
<div className="space-y-2">
<button
onClick={() => {
setBookmarks([]);
alert('Закладки очищены');
}}
className="w-full text-left p-2 text-red-400 hover:bg-gray-700 rounded"
>
Очистить закладки
</button>
<button
onClick={() => {
setWatchHistory([]);
alert('История очищена');
}}
className="w-full text-left p-2 text-red-400 hover:bg-gray-700 rounded"
>
Очистить историю
</button>
</div>
</div>
<div className="bg-gray-800 p-4 rounded-lg">
<h3 className="font-semibold mb-2">О приложении</h3>
<p className="text-sm text-gray-300">
HDRezka Mobile - неофициальный клиент для просмотра фильмов и сериалов.
Все материалы принадлежат их правообладателям.
</p>
</div>
</div>
</div>
);
return (
<div className="max-w-md mx-auto bg-gray-900 min-h-screen">
{showPlayer && currentVideoUrl && selectedMovie && (
<VideoPlayer
videoUrl={currentVideoUrl}
movie={selectedMovie}
onClose={() => {
setShowPlayer(false);
setCurrentVideoUrl(null);
}}
/>
)}
{currentPage === 'home' && <HomePage />}
{currentPage === 'bookmarks' && <BookmarksPage />}
{currentPage === 'history' && <HistoryPage />}
{currentPage === 'settings' && <SettingsPage />}
{currentPage === 'movie' && selectedMovie && <MovieDetail movie={selectedMovie} />}
</div>
);
};
export default HDRezkaMobile;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment