import { useState, useEffect, useRef } from 'react' import { useParams, useNavigate, Link } from 'react-router-dom' import { marathonsApi, eventsApi, challengesApi } from '@/api' import type { Marathon, ActiveEvent, Challenge } from '@/types' import { Button, Card, CardContent } from '@/components/ui' import { useAuthStore } from '@/store/auth' import { useToast } from '@/store/toast' import { useConfirm } from '@/store/confirm' import { EventBanner } from '@/components/EventBanner' import { EventControl } from '@/components/EventControl' import { ActivityFeed, type ActivityFeedRef } from '@/components/ActivityFeed' import { Users, Calendar, Trophy, Play, Settings, Copy, Check, Loader2, Trash2, Globe, Lock, CalendarCheck, UserPlus, Gamepad2, ArrowLeft, Zap } from 'lucide-react' import { format } from 'date-fns' export function MarathonPage() { const { id } = useParams<{ id: string }>() const navigate = useNavigate() const user = useAuthStore((state) => state.user) const toast = useToast() const confirm = useConfirm() const [marathon, setMarathon] = useState(null) const [activeEvent, setActiveEvent] = useState(null) const [challenges, setChallenges] = useState([]) const [isLoading, setIsLoading] = useState(true) const [copied, setCopied] = useState(false) const [isDeleting, setIsDeleting] = useState(false) const [isJoining, setIsJoining] = useState(false) const [showEventControl, setShowEventControl] = useState(false) const activityFeedRef = useRef(null) useEffect(() => { loadMarathon() }, [id]) const loadMarathon = async () => { if (!id) return try { const data = await marathonsApi.get(parseInt(id)) setMarathon(data) // Load event data if marathon is active if (data.status === 'active' && data.my_participation) { const eventData = await eventsApi.getActive(parseInt(id)) setActiveEvent(eventData) // Load challenges for event control if organizer if (data.my_participation.role === 'organizer') { try { const challengesData = await challengesApi.list(parseInt(id)) setChallenges(challengesData) } catch { // Ignore if no challenges } } } } catch (error) { console.error('Failed to load marathon:', error) navigate('/marathons') } finally { setIsLoading(false) } } const refreshEvent = async () => { if (!id) return try { const eventData = await eventsApi.getActive(parseInt(id)) setActiveEvent(eventData) // Refresh activity feed when event changes activityFeedRef.current?.refresh() } catch (error) { console.error('Failed to refresh event:', error) } } const getInviteLink = () => { if (!marathon) return '' return `${window.location.origin}/invite/${marathon.invite_code}` } const copyInviteLink = () => { if (marathon) { navigator.clipboard.writeText(getInviteLink()) setCopied(true) setTimeout(() => setCopied(false), 2000) } } const handleDelete = async () => { if (!marathon) return const confirmed = await confirm({ title: 'Удалить марафон?', message: 'Все данные марафона будут удалены безвозвратно.', confirmText: 'Удалить', cancelText: 'Отмена', variant: 'danger', }) if (!confirmed) return setIsDeleting(true) try { await marathonsApi.delete(marathon.id) navigate('/marathons') } catch (error) { console.error('Failed to delete marathon:', error) toast.error('Не удалось удалить марафон') } finally { setIsDeleting(false) } } const handleJoinPublic = async () => { if (!marathon) return setIsJoining(true) try { const updated = await marathonsApi.joinPublic(marathon.id) setMarathon(updated) } catch (err: unknown) { const error = err as { response?: { data?: { detail?: string } } } toast.error(error.response?.data?.detail || 'Не удалось присоединиться') } finally { setIsJoining(false) } } if (isLoading || !marathon) { return (
) } const isOrganizer = marathon.my_participation?.role === 'organizer' || user?.role === 'admin' const isParticipant = !!marathon.my_participation const isCreator = marathon.creator.id === user?.id const canDelete = isCreator || user?.role === 'admin' return (
{/* Back button */} К списку марафонов
{/* Main content */}
{/* Header */}

{marathon.title}

{marathon.is_public ? ( <> Открытый ) : ( <> Закрытый )}
{marathon.description && (

{marathon.description}

)}
{/* Кнопка присоединиться для открытых марафонов */} {marathon.is_public && !isParticipant && marathon.status !== 'finished' && ( )} {/* Настройка для организаторов */} {marathon.status === 'preparing' && isOrganizer && ( )} {/* Предложить игру для участников (не организаторов) если разрешено */} {marathon.status === 'preparing' && isParticipant && !isOrganizer && marathon.game_proposal_mode === 'all_participants' && ( )} {marathon.status === 'active' && isParticipant && ( )} {canDelete && ( )}
{/* Stats */}
{marathon.participants_count}
Участников
{marathon.games_count}
Игр
{marathon.start_date ? format(new Date(marathon.start_date), 'd MMM') : '-'}
Начало
{marathon.end_date ? format(new Date(marathon.end_date), 'd MMM') : '-'}
Конец
{marathon.status === 'active' ? 'Активен' : marathon.status === 'preparing' ? 'Подготовка' : 'Завершён'}
Статус
{/* Active event banner */} {marathon.status === 'active' && activeEvent?.event && (
)} {/* Event control for organizers */} {marathon.status === 'active' && isOrganizer && (

Управление событиями

{showEventControl && activeEvent && ( )}
)} {/* Invite link */} {marathon.status !== 'finished' && (

Ссылка для приглашения

{getInviteLink()}

Поделитесь этой ссылкой с друзьями, чтобы они могли присоединиться к марафону

)} {/* My stats */} {marathon.my_participation && (

Ваша статистика

{marathon.my_participation.total_points}
Очков
{marathon.my_participation.current_streak}
Серия
{marathon.my_participation.drop_count}
Пропусков
)}
{/* Activity Feed - right sidebar */} {isParticipant && (
)}
) }