import { useState, useRef, useEffect } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' import { marathonsApi } from '@/api' import type { Marathon, GameProposalMode } from '@/types' import { NeonButton, Input } from '@/components/ui' import { useToast } from '@/store/toast' import { X, Camera, Trash2, Loader2, Save, Globe, Lock, Users, UserCog, Sparkles, Zap } from 'lucide-react' const settingsSchema = z.object({ title: z.string().min(1, 'Название обязательно').max(100, 'Максимум 100 символов'), description: z.string().optional(), start_date: z.string().min(1, 'Дата начала обязательна'), is_public: z.boolean(), game_proposal_mode: z.enum(['all_participants', 'organizer_only']), auto_events_enabled: z.boolean(), }) type SettingsForm = z.infer interface MarathonSettingsModalProps { marathon: Marathon isOpen: boolean onClose: () => void onUpdate: (marathon: Marathon) => void } export function MarathonSettingsModal({ marathon, isOpen, onClose, onUpdate, }: MarathonSettingsModalProps) { const toast = useToast() const fileInputRef = useRef(null) const [isUploading, setIsUploading] = useState(false) const [isDeleting, setIsDeleting] = useState(false) const [coverPreview, setCoverPreview] = useState(null) const { register, handleSubmit, watch, setValue, reset, formState: { errors, isSubmitting, isDirty }, } = useForm({ resolver: zodResolver(settingsSchema), defaultValues: { title: marathon.title, description: marathon.description || '', start_date: marathon.start_date ? new Date(marathon.start_date).toISOString().slice(0, 16) : '', is_public: marathon.is_public, game_proposal_mode: marathon.game_proposal_mode as GameProposalMode, auto_events_enabled: marathon.auto_events_enabled, }, }) const isPublic = watch('is_public') const gameProposalMode = watch('game_proposal_mode') const autoEventsEnabled = watch('auto_events_enabled') // Reset form when marathon changes useEffect(() => { reset({ title: marathon.title, description: marathon.description || '', start_date: marathon.start_date ? new Date(marathon.start_date).toISOString().slice(0, 16) : '', is_public: marathon.is_public, game_proposal_mode: marathon.game_proposal_mode as GameProposalMode, auto_events_enabled: marathon.auto_events_enabled, }) setCoverPreview(null) }, [marathon, reset]) // Handle escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape' && isOpen) { onClose() } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [isOpen, onClose]) // Prevent body scroll when modal is open useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden' } else { document.body.style.overflow = '' } return () => { document.body.style.overflow = '' } }, [isOpen]) const onSubmit = async (data: SettingsForm) => { try { const updated = await marathonsApi.update(marathon.id, { title: data.title, description: data.description || undefined, start_date: new Date(data.start_date).toISOString(), is_public: data.is_public, game_proposal_mode: data.game_proposal_mode, auto_events_enabled: data.auto_events_enabled, }) onUpdate(updated) toast.success('Настройки сохранены') onClose() } catch (err: unknown) { const error = err as { response?: { data?: { detail?: string } } } toast.error(error.response?.data?.detail || 'Не удалось сохранить настройки') } } const handleCoverClick = () => { fileInputRef.current?.click() } const handleCoverChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return if (!file.type.startsWith('image/')) { toast.error('Файл должен быть изображением') return } if (file.size > 5 * 1024 * 1024) { toast.error('Максимальный размер файла 5 МБ') return } // Show preview immediately const previewUrl = URL.createObjectURL(file) setCoverPreview(previewUrl) setIsUploading(true) try { const updated = await marathonsApi.uploadCover(marathon.id, file) onUpdate(updated) toast.success('Обложка загружена') } catch (err: unknown) { const error = err as { response?: { data?: { detail?: string } } } toast.error(error.response?.data?.detail || 'Не удалось загрузить обложку') setCoverPreview(null) } finally { setIsUploading(false) // Reset input if (fileInputRef.current) { fileInputRef.current.value = '' } } } const handleDeleteCover = async () => { setIsDeleting(true) try { const updated = await marathonsApi.deleteCover(marathon.id) onUpdate(updated) setCoverPreview(null) toast.success('Обложка удалена') } catch (err: unknown) { const error = err as { response?: { data?: { detail?: string } } } toast.error(error.response?.data?.detail || 'Не удалось удалить обложку') } finally { setIsDeleting(false) } } if (!isOpen) return null const displayCover = coverPreview || marathon.cover_url return (
{/* Backdrop */}
{/* Modal */}
{/* Header */}

Настройки марафона

{/* Cover Image */}
{displayCover && !isUploading && !isDeleting && ( )}
{/* Title */} {/* Description */}