import React, { useEffect, useRef, useState } from 'react'; import { Copy, Image, Plus, Trash2, Upload } from 'lucide-react'; import { gamesApi, type GameQuizConfig, type MatchUpConfig, type MatchUpGroup } from '../../../services/api/games'; import { uploadMediaFile } from '../../../services/api/media'; import { gameQuizUrl, matchUpUrl } from '../../utils/gameUrls'; import { Field, Grid, HelperText, IS, Label, PanelShell, SS } from '../panels/PanelShell'; type Kind = 'matchUp' | 'quiz'; const buttonStyle: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '8px 10px', border: '1px solid var(--border)', borderRadius: 'var(--radius-button)', background: 'var(--card)', color: 'var(--foreground)', cursor: 'pointer', fontSize: 'var(--text-sm)' }; const sectionTitleStyle: React.CSSProperties = { color: '#163f7a', fontSize: '20px', fontWeight: 700, lineHeight: 1.3, margin: 0, }; const sectionStyle: React.CSSProperties = { display: 'grid', gap: 16, }; const groupStyle: React.CSSProperties = { borderTop: '1px solid var(--border)', paddingTop: 18, display: 'grid', gap: 14, }; const groupHeaderStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, minHeight: 24, }; const groupTileCountStyle: React.CSSProperties = { color: 'var(--muted)', fontSize: 'var(--text-sm)', lineHeight: 1.4, whiteSpace: 'nowrap', }; const fieldLabelStyle: React.CSSProperties = { color: '#27364a', fontSize: '15px', fontWeight: 600, }; const helperStyle: React.CSSProperties = { marginTop: -4, }; const descriptionStyle: React.CSSProperties = { ...IS, minHeight: 72, resize: 'vertical', lineHeight: 1.45, }; function emptyGroups(size: number): MatchUpGroup[] { return Array.from({ length: size }, (_, i) => ({ title: `Group ${i + 1}`, description: '', items: Array.from({ length: size }, () => ({ text: '', imageUrl: '' })) })); } function emptyQuestions(count: number) { return Array.from({ length: count }, () => ({ question: '', options: ['', '', '', ''], correctIndex: 0, points: 5 })); } export function GameManagePanel({ kind, eventId, eventName, canWrite, onClose, onChanged }: { kind: Kind; eventId: string; eventName?: string; canWrite: boolean; onClose: () => void; onChanged?: () => void }) { const [matchUp, setMatchUp] = useState(null); const [quiz, setQuiz] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [uploadingBackground, setUploadingBackground] = useState(false); const [error, setError] = useState(''); const [copied, setCopied] = useState(false); const backgroundInputRef = useRef(null); const isMatchUp = kind === 'matchUp'; const config = isMatchUp ? matchUp : quiz; const link = config?.publicToken ? (isMatchUp ? matchUpUrl(config.publicToken) : gameQuizUrl(config.publicToken)) : ''; const title = isMatchUp ? 'Match Up' : 'Quiz'; useEffect(() => { void (async () => { setLoading(true); const res = isMatchUp ? await gamesApi.getMatchUp(eventId) : await gamesApi.getQuiz(eventId); if (res.error) setError(res.error); else if (res.data) isMatchUp ? setMatchUp(res.data) : setQuiz(res.data); setLoading(false); })(); }, [eventId, isMatchUp]); const save = async () => { if (!config || !canWrite) return; setSaving(true); setError(''); const res = isMatchUp ? await gamesApi.updateMatchUp(eventId, { name: matchUp!.name, backgroundImage: matchUp!.backgroundImage, enabled: matchUp!.enabled, gridSize: matchUp!.gridSize, groups: matchUp!.groups.map((group) => ({ title: group.title, description: group.description || '', items: group.items.map((item) => ({ text: item.text, imageUrl: '', })), })), }) : await gamesApi.updateQuiz(eventId, { name: quiz!.name, backgroundImage: quiz!.backgroundImage, enabled: quiz!.enabled, questions: quiz!.questions, }); if (res.error) { setError(res.error); } else { isMatchUp ? setMatchUp(res.data) : setQuiz(res.data); onChanged?.(); } setSaving(false); }; const uploadBackground = async (file?: File) => { if (!file || !canWrite) return; setUploadingBackground(true); setError(''); try { const result = await uploadMediaFile(file, { eventId, module: `games/${kind}/background`, visibility: 'public' }); if (result.url) { if (isMatchUp) setMatchUp((current) => current ? { ...current, backgroundImage: result.url } : current); else setQuiz((current) => current ? { ...current, backgroundImage: result.url } : current); } } catch (err) { setError(err instanceof Error ? err.message : 'Background upload failed'); } finally { setUploadingBackground(false); } }; const removeBackground = () => { if (!canWrite) return; if (isMatchUp) { setMatchUp((current) => current ? { ...current, backgroundImage: '', } : current ); } else { setQuiz((current) => current ? { ...current, backgroundImage: '', } : current ); } setError(''); }; // const uploadTileImage = async ( // file: File | undefined, // groupIndex: number, // tileIndex: number // ) => { // if (!file || !canWrite || !matchUp) return; // setError(''); // try { // const result = await uploadMediaFile(file, { // eventId, // module: 'games/matchUp/tile', // visibility: 'public', // }); // if (result.url) { // setMatchUp((current) => { // if (!current) return current; // return { // ...current, // groups: current.groups.map((group, gi) => // gi === groupIndex // ? { // ...group, // items: group.items.map((item, ii) => // ii === tileIndex // ? { // ...item, // imageUrl: result.url, // } // : item // ), // } // : group // ), // }; // }); // } // } catch (err) { // setError( // err instanceof Error // ? err.message // : 'Tile image upload failed' // ); // } // }; const copyLink = async () => { if (!link) return; await navigator.clipboard.writeText(link); setCopied(true); window.setTimeout(() => setCopied(false), 1500); }; const setSize = (size: 3 | 4 | 5) => setMatchUp((current) => current ? { ...current, gridSize: size, groups: emptyGroups(size) } : current); const addQuestion = () => setQuiz((current) => current ? { ...current, questions: [...current.questions, ...emptyQuestions(1)] } : current); const questions = quiz?.questions || []; const groups = matchUp?.groups || []; const canSave = Boolean(config) && !loading && !saving && canWrite; return void save()} panelWidth="min(920px, 100vw)"> {loading ? (

Loading...

) : config ? (

Game details

{config.backgroundImage ? ( Background preview ) : null}
{isMatchUp && matchUp ? (

Puzzle structure

{groups.map((group, gi) => (
{/* GROUP HEADER */}
{/* CONNECTION TITLE */} setMatchUp({ ...matchUp, groups: groups.map((g, i) => i === gi ? { ...g, title: e.target.value, } : g ), }) } style={IS} placeholder="Connection title" /> {/* CONNECTION DESCRIPTION */}