import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { IconX, IconMessageFilled, IconThumbUpFilled, IconThumbDownFilled, IconSend2, IconSearch, IconTrashFilled, IconFlagFilled } from '@tabler/icons-react' import { motion, AnimatePresence } from 'motion/react' import * as api from '../api' import type { CommunityClueItem, ClueSubmissionItem } from '../api' import Tooltip from './ui/Tooltip' import { useFocusTrap } from '../hooks/useFocusTrap' export default function CommunityCluesPanel({ onClose }: { onClose: () => void }) { const trapRef = useFocusTrap(true) const { t } = useTranslation() const [word, setWord] = useState('') const [clues, setClues] = useState([]) const [newClue, setNewClue] = useState('') const [submitting, setSubmitting] = useState(false) const [submissions, setSubmissions] = useState([]) const [tab, setTab] = useState<'browse' | 'mine'>('browse') const [searched, setSearched] = useState(false) const [reportingId, setReportingId] = useState(null) const [reportReason, setReportReason] = useState('') const [reported, setReported] = useState>(new Set()) useEffect(() => { if (tab === 'mine') { api.fetchMySubmissions().then(setSubmissions) } }, [tab]) const search = async () => { if (!word.trim()) return const d = await api.fetchCommunityClues(word.trim()) setClues(d.clues || []) setSearched(true) } const submitClue = async () => { if (!word.trim() || !newClue.trim()) return setSubmitting(true) try { await api.submitClue(word.trim(), newClue.trim()) setNewClue('') const d = await api.fetchCommunityClues(word.trim()) setClues(d.clues || []) } catch { /* ignored */ } setSubmitting(false) } const vote = async (id: number, v: number) => { await api.voteClue(id, v) const d = await api.fetchCommunityClues(word.trim()) setClues(d.clues || []) } const deleteSubmission = async (id: number) => { await api.deleteSubmission(id) setSubmissions(prev => prev.filter(s => s.id !== id)) } const submitReport = async (id: number) => { try { await api.submitReport('clue', String(id), reportReason || undefined) setReported(prev => new Set(prev).add(id)) } catch { /* ignored */ } setReportingId(null) setReportReason('') } return ( e.stopPropagation()} role="dialog" aria-modal="true" aria-label={t('community.title')} initial={{ opacity: 0, scale: 0.96, y: 10 }} animate={{ opacity: 1, scale: 1, y: 0 }} exit={{ opacity: 0, scale: 0.96, y: 10 }} transition={{ duration: 0.2 }}>

{t('community.title')}

{tab === 'browse' ? ( <>
setWord(e.target.value.toUpperCase().replace(/[^A-Z]/g, ''))} onKeyDown={e => e.key === 'Enter' && search()} />
{searched && word.trim() && (
setNewClue(e.target.value)} maxLength={500} onKeyDown={e => e.key === 'Enter' && submitClue()} />
{clues.length === 0 && (

{t('community.noCluesYet', { word })}

)} {clues.map(c => (
{c.clue} {c.origin_domain && ( {c.origin_domain} )}
{c.status !== 'pending' && ( {c.status} )} {!reported.has(c.id) && ( )} {reported.has(c.id) && ( {t('common.reported')} )}
{reportingId === c.id && (
setReportReason(e.target.value)} onKeyDown={e => e.key === 'Enter' && submitReport(c.id)} maxLength={200} />
)}
))}
)}
) : (
{submissions.length === 0 && (

{t('community.noSubmissions')}

)} {submissions.map(s => (
{s.word}
{s.clue}
{s.upvotes - s.downvotes > 0 ? '+' : ''}{s.upvotes - s.downvotes} {s.status}
))}
)}
) }