CampFireCritics/src/mobile/pages/MobileHome.jsx

232 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;