import React, { useEffect, useState } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; import * as Kubikon3DApi from '../api/Kubikon3DService'; import { KT, KUBIKON_KEYFRAMES, skeletonStyle } from '../utils/kubikonTheme'; import RublocsLogo from '../components/RublocsLogo/RublocsLogo'; import Icon from '../editor/Icon'; /** * KubikonUserGames — публичный профиль автора со списком его опубликованных игр. * URL: /user/:userId * * Видны опубликованные игры автора (status='published'). * Дизайн в едином wow-стиле Рублокса. */ const KubikonUserGames = () => { const { userId } = useParams(); const navigate = useNavigate(); const [items, setItems] = useState([]); const [authorName, setAuthorName] = useState(''); const [loading, setLoading] = useState(true); useEffect(() => { let active = true; (async () => { try { const res = await Kubikon3DApi.getUserGames(userId); if (active) { setItems(res.data?.projects || []); setAuthorName(res.data?.author_username || ''); setLoading(false); } } catch (e) { if (active) setLoading(false); } })(); return () => { active = false; }; }, [userId]); const totalPlays = items.reduce((s, p) => s + (p.play_count || 0), 0); const totalLikes = items.reduce((s, p) => s + (p.likes_count || 0), 0); const initial = (authorName || '?').slice(0, 1).toUpperCase(); return (
{/* Sticky glass header */}
В ленту Рублокс
{/* Hero — большая аватарка + имя + статистика */}
{/* Floating shapes */}
{/* Avatar */}
{initial}
Профиль автора

{authorName || `Игрок #${userId}`}

{/* Статистика */}
= 2 && items.length <= 4) ? 'игры' : 'игр' } />
{/* Список игр — overlap с hero */}
{loading ? ( ) : items.length === 0 ? (
Пока нет опубликованных игр
У этого автора скоро здесь что-то появится
) : (
{items.map((p, i) => ( navigate(`/game/${p.id}`)} animateDelay={Math.min(i * 35, 600)} /> ))}
)}
); }; const StatPill = ({ icon, value, label }) => (
{value} {label}
); const FloatingShapes = () => ( <>
); const GameCard = ({ project, onClick, animateDelay = 0 }) => { const [hovered, setHovered] = useState(false); const p = project; return (
{ if (e.key === 'Enter') onClick(); }} onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} style={{ background: KT.bgPage, border: `1px solid ${hovered ? KT.accent : KT.border}`, borderRadius: KT.radiusLg, boxShadow: hovered ? KT.shadowLg : KT.shadow, transform: hovered ? 'translateY(-6px)' : 'translateY(0)', transition: 'all 280ms cubic-bezier(0.34, 1.56, 0.64, 1)', cursor: 'pointer', overflow: 'hidden', animation: `kubikonFadeIn 460ms cubic-bezier(0.34, 1.56, 0.64, 1) ${animateDelay}ms both`, }} >
{p.thumbnail ? ( {p.title} ) : (
)} {/* Умная лента: бейджа «ранг» больше нет — все опубликованные игры равны, их позицию определяет алгоритм в ленте. */} {p.age_rating || 12}+
{p.title}
{(p.play_count || 0).toLocaleString('ru')} {(p.likes_count || 0).toLocaleString('ru')}
); }; const SkeletonGrid = () => (
{Array.from({ length: 6 }).map((_, i) => (
))}
); export default KubikonUserGames;