import { useState, useEffect } from 'react' import { Send, Hash, MessageSquarePlus, AlertCircle } from 'lucide-react' import { apiFetch } from '@core/lib/api' import { Button } from '@core/components/ui/button' import { Input } from '@core/components/ui/input' import { Badge } from '@core/components/ui/badge' import { Skeleton } from '@core/components/ui/skeleton' import { ScrollArea } from '@core/components/ui/scroll-area' const CHANNELS = ['general', 'announcements', 'help', 'show-and-tell'] const CHANNEL_LABELS: Record = { general: 'General', announcements: 'Announcements', help: 'Help', 'show-and-tell': 'Show & Tell' } interface Post { id: string; title: string; description?: string; data?: Record; created_at: string } interface Message { id: string; content: string; direction: string; created_at: string } interface Thread { id: string } function ThreadPane({ post }: { post: Post }) { const [messages, setMessages] = useState([]) const [thread, setThread] = useState(null) const [loading, setLoading] = useState(true) const [replyText, setReplyText] = useState('') const [sending, setSending] = useState(false) useEffect(() => { setLoading(true) apiFetch(`/api/admin-data?action=list&entity=threads&target_id=${post.id}&type_slug=community_thread&limit=5`) .then(r => r.json()).then(j => { const t = (j?.data ?? j)?.[0] ?? null setThread(t) if (t) return apiFetch(`/api/admin-data?action=list&entity=messages&thread_id=${t.id}&limit=100`).then(r => r.json()) return [] }).then(raw => { const msgs = raw?.data ?? raw; setMessages(Array.isArray(msgs) ? msgs : []) }).catch(() => {}).finally(() => setLoading(false)) }, [post.id]) const handleSend = async () => { if (!replyText.trim()) return setSending(true) try { let activeThread = thread if (!activeThread?.id) { const res = await apiFetch('/api/admin-data?action=create&entity=threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entity: 'threads', type_slug: 'community_thread', target_type: 'items', target_id: post.id, status: 'open', }), }) const raw = await res.json() activeThread = raw?.data ?? raw if (activeThread?.id) setThread(activeThread) } if (!activeThread?.id) return const res = await apiFetch('/api/admin-data?action=create&entity=messages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ thread_id: activeThread.id, type_slug: 'community_reply', content: replyText.trim(), direction: 'outbound', sequence: messages.reduce((max, m) => Math.max(max, m.sequence || 0), 0) + 1, entity: 'messages', }), }) const raw = await res.json() const msg = raw?.data ?? raw if (msg?.id) setMessages(prev => [...prev, msg]) setReplyText('') } finally { setSending(false) } } return (

{post.title}

{post.description &&

{post.description}

}
{loading && } {!loading && messages.length === 0 &&

No replies yet.

} {messages.map(msg => (
{msg.content}
))}
setReplyText(e.target.value)} onKeyDown={e => e.key === 'Enter' && !e.shiftKey && handleSend()} className="flex-1" />
) } export default function CommunityPage() { const [activeChannel, setActiveChannel] = useState('general') const [selected, setSelected] = useState(null) const [search, setSearch] = useState('') const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { apiFetch('/api/admin-data?action=list&entity=items&type_slug=community_post&limit=500') .then(r => r.json()).then(j => setPosts(Array.isArray(j?.data) ? j.data : Array.isArray(j) ? j : [])).catch(() => setPosts([])).finally(() => setLoading(false)) }, []) const channelPosts = posts.filter(p => (p.data?.channel as string || 'general') === activeChannel) const filtered = channelPosts.filter(p => p.title?.toLowerCase().includes(search.toLowerCase())) const unanswered = posts.filter(p => !p.data?.reply_count) return (

Channels

{CHANNELS.map(ch => { const count = posts.filter(p => (p.data?.channel as string || 'general') === ch).length return ( ) })}

#{CHANNEL_LABELS[activeChannel]}

setSearch(e.target.value)} className="h-7 text-xs" />
{loading ?
{Array.from({ length: 4 }).map((_, i) => )}
: filtered.length === 0 ?

No discussions yet.

: filtered.map(post => ( )) }
{!selected ?

Select a discussion to reply

: }

Moderation

Unanswered ({unanswered.length})

{unanswered.length === 0 &&

All caught up.

} {unanswered.slice(0, 15).map(p => ( ))}
) }