232 lines
10 KiB
JavaScript
232 lines
10 KiB
JavaScript
import React, { useEffect, useState, useCallback } from 'react';
|
||
import { Link } from 'react-router-dom';
|
||
import { getLatestReviews, listMedia, listUsersRankedByReviews, getFileUrl, getMediaCount, getReviewsCount } from '../../services/pocketbaseService';
|
||
import { FaStar, FaHeart, FaComment, FaCrown, FaMedal } from 'react-icons/fa';
|
||
import GridMotionMobile from '../components/GridMotionMobile';
|
||
import CountUp from '../../components/reactbits/TextAnimations/CountUp/CountUp';
|
||
import { motion, AnimatePresence } from 'framer-motion';
|
||
import RotatingText from '../../components/reactbits/TextAnimations/RotatingText/RotatingText';
|
||
|
||
const MobileHome = () => {
|
||
const [popularMedia, setPopularMedia] = useState([]);
|
||
const [latestReviews, setLatestReviews] = useState([]);
|
||
const [topUsers, setTopUsers] = useState([]);
|
||
const [stats, setStats] = useState({ mediaCount: 0, reviewsCount: 0 });
|
||
const [posters, setPosters] = useState([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState(null);
|
||
const [isDataReady, setIsDataReady] = useState(false);
|
||
|
||
useEffect(() => {
|
||
const fetchData = async () => {
|
||
try {
|
||
setLoading(true);
|
||
setError(null);
|
||
const [mediaData, reviewsData, usersData, mediaCount, reviewsCount] = await Promise.all([
|
||
listMedia(null, 1, 40, null, false, true, '-review_count'),
|
||
getLatestReviews(5),
|
||
listUsersRankedByReviews(3),
|
||
getMediaCount(),
|
||
getReviewsCount()
|
||
]);
|
||
setPopularMedia(mediaData?.data || []);
|
||
setLatestReviews(reviewsData || []);
|
||
setTopUsers(usersData || []);
|
||
setStats({
|
||
mediaCount: parseInt(mediaCount) || 0,
|
||
reviewsCount: parseInt(reviewsCount) || 0
|
||
});
|
||
setPosters((mediaData?.data || []).filter(item => item.poster).map(item => getFileUrl(item, 'poster')));
|
||
setIsDataReady(true);
|
||
setLoading(false);
|
||
console.log(mediaData?.data)
|
||
} catch (err) {
|
||
setError('Не удалось загрузить данные');
|
||
setLoading(false);
|
||
}
|
||
};
|
||
fetchData();
|
||
}, []);
|
||
|
||
if (loading || !isDataReady) {
|
||
return (
|
||
<div className="min-h-screen bg-campfire-dark flex flex-col justify-center items-center">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-campfire-amber mb-4"></div>
|
||
<span className="text-campfire-light">Загрузка...</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="min-h-screen bg-campfire-dark flex items-center justify-center p-4">
|
||
<div className="bg-status-error/20 text-status-error p-4 rounded-lg text-center">
|
||
{error}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// Для GridMotionMobile: минимум 48 постеров
|
||
const minPosters = 48;
|
||
let postersForGrid = posters;
|
||
console.log(posters)
|
||
if (posters.length < minPosters) {
|
||
const placeholder = '/placeholder-poster.jpg';
|
||
postersForGrid = [
|
||
...posters,
|
||
...Array(minPosters - posters.length).fill(placeholder)
|
||
];
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-campfire-dark pb-8">
|
||
{/* GridMotion + статистика */}
|
||
<section className="relative h-[500px] mb-8 overflow-hidden">
|
||
<div className="absolute inset-0 z-0 pointer-events-none">
|
||
<GridMotionMobile items={postersForGrid} gradientColor="rgba(251, 191, 36, 0.08)" />
|
||
</div>
|
||
<div className="absolute inset-0 from-campfire-dark/0 via-campfire-dark/60 to-campfire-dark z-10"></div>
|
||
<div className="relative z-20 flex flex-col items-center justify-center h-full px-4">
|
||
<div className="text-center max-w-md mx-auto">
|
||
<h2 className="text-xl font-bold text-campfire-light mb-4 drop-shadow-lg">
|
||
<div className="flex flex-col items-center justify-center gap-2">
|
||
<span>Составляй рецензии на</span>
|
||
<div className="w-28 text-center">
|
||
<RotatingText
|
||
texts={["Фильмы", "Сериалы", "Игры", "Аниме"]}
|
||
className="inline-block text-campfire-amber drop-shadow-[0_0_8px_rgba(255,51,0,0.6)]"
|
||
rotationInterval={1500}
|
||
auto={true}
|
||
loop={true}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</h2>
|
||
<div className="flex flex-col items-center justify-center w-full max-w-xs mx-auto">
|
||
<div className="bg-campfire-darker/80 backdrop-blur-md p-4 rounded-lg text-center shadow-md mb-4 w-full">
|
||
<h3 className="text-campfire-light text-xs mb-1">Медиа в каталоге</h3>
|
||
<CountUp to={stats.mediaCount} className="text-xl font-bold text-campfire-amber" duration={2} start={0} />
|
||
</div>
|
||
<div className="bg-campfire-darker/80 backdrop-blur-md p-4 rounded-lg text-center shadow-md w-full">
|
||
<h3 className="text-campfire-light text-xs mb-1">Рецензий написано</h3>
|
||
<CountUp to={stats.reviewsCount} className="text-xl font-bold text-campfire-amber" duration={2} start={0} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Топ рецензенты */}
|
||
<section className="px-4 mb-8">
|
||
<h2 className="text-xl font-bold text-campfire-light mb-4">Топ рецензенты</h2>
|
||
<div className="flex justify-center items-end gap-4">
|
||
{topUsers.map((user, idx) => (
|
||
<div key={user.id} className="flex flex-col items-center w-1/3">
|
||
<div className={`relative ${idx === 0 ? 'scale-110' : ''}`}>
|
||
<div className={`w-20 h-20 rounded-full overflow-hidden border-2 ${idx === 0 ? 'border-campfire-amber' : idx === 1 ? 'border-campfire-ash/30' : 'border-campfire-ember/30'} mb-2`}>
|
||
<img
|
||
src={getFileUrl(user, 'profile_picture') || '/default-avatar.png'}
|
||
alt={user.username}
|
||
className="w-full h-full object-cover"
|
||
/>
|
||
</div>
|
||
<div className={`absolute -top-2 -right-2 w-6 h-6 rounded-full flex items-center justify-center ${idx === 0 ? 'bg-campfire-amber text-campfire-dark' : 'bg-campfire-ash/30 text-campfire-light'}`}>
|
||
<span className="font-bold">{idx + 1}</span>
|
||
</div>
|
||
</div>
|
||
<Link to={`/profile/${user.login}`} className="text-campfire-light hover:text-campfire-amber font-medium text-sm">
|
||
{user.username}
|
||
</Link>
|
||
<span className="text-campfire-ash text-xs">{user.review_count} рецензий</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<div className="text-center mt-4">
|
||
<Link to="/rating" className="text-campfire-amber hover:underline text-sm">Посмотреть рейтинг</Link>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Последние обзоры */}
|
||
<section className="px-4 mb-8">
|
||
<h2 className="text-xl font-bold text-campfire-light mb-4">Последние обзоры</h2>
|
||
<div className="space-y-4">
|
||
{latestReviews.map((review) => (
|
||
<Link
|
||
key={review.id}
|
||
to={`/media/${review.expand?.media_id?.path}`}
|
||
className="block bg-campfire-charcoal/20 backdrop-blur-md border border-campfire-ash/30 rounded-lg p-4 hover:bg-campfire-charcoal/30 transition-colors"
|
||
>
|
||
<div className="flex items-start gap-4">
|
||
<img
|
||
src={getFileUrl(review.expand?.media_id, 'poster')}
|
||
alt={review.expand?.media_id?.title}
|
||
className="w-16 h-24 object-cover rounded-lg"
|
||
/>
|
||
<div className="flex-1">
|
||
<h3 className="text-base font-semibold text-campfire-light mb-1">
|
||
{review.expand?.media_id?.title}
|
||
</h3>
|
||
<div className="flex items-center gap-2 mb-2">
|
||
<div className="flex items-center text-campfire-amber">
|
||
<FaStar className="mr-1" />
|
||
<span>{review.rating}</span>
|
||
</div>
|
||
<span className="text-campfire-ash">•</span>
|
||
<span className="text-campfire-ash text-xs">
|
||
{new Date(review.created).toLocaleDateString()}
|
||
</span>
|
||
</div>
|
||
<p className="text-campfire-light/80 line-clamp-2">
|
||
{review.text}
|
||
</p>
|
||
<div className="flex items-center gap-4 mt-2">
|
||
<div className="flex items-center text-campfire-ash">
|
||
<FaHeart className="mr-1" />
|
||
<span>{review.likes?.length || 0}</span>
|
||
</div>
|
||
<div className="flex items-center text-campfire-ash">
|
||
<FaComment className="mr-1" />
|
||
<span>{review.comments?.length || 0}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Популярные медиа */}
|
||
<section className="px-4 mb-8">
|
||
<h2 className="text-xl font-bold text-campfire-light mb-4">Популярные медиа</h2>
|
||
<div className="grid grid-cols-2 gap-4">
|
||
{popularMedia.filter(media => media?.path).map((media) => (
|
||
<Link
|
||
key={media.id}
|
||
to={`/media/${media.path}`}
|
||
className="block bg-campfire-charcoal/20 backdrop-blur-md border border-campfire-ash/30 rounded-lg overflow-hidden hover:bg-campfire-charcoal/30 transition-colors"
|
||
>
|
||
<img
|
||
src={getFileUrl(media, 'poster')}
|
||
alt={media.title}
|
||
className="w-full aspect-[2/3] object-cover"
|
||
/>
|
||
<div className="p-3">
|
||
<h3 className="text-campfire-light font-semibold line-clamp-1">
|
||
{media.title}
|
||
</h3>
|
||
<div className="flex items-center text-campfire-amber mt-1">
|
||
<FaStar className="mr-1" />
|
||
<span>{media.average_rating?.toFixed(1) || '0.0'}</span>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</section>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default MobileHome;
|