/** * Forum Section - Community Discussions * Uses shared Supabase tables from pwap/web (forum_discussions, forum_categories) */ import { useState, useEffect } from 'react'; import { MessageCircle, ArrowLeft, ThumbsUp, ThumbsDown, MessageSquare, Clock, TrendingUp, Flame, RefreshCw, Search, Pin, Lock, Eye, AlertTriangle, Info, CheckCircle, Megaphone, Plus, Send, X, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useTelegram } from '@/hooks/useTelegram'; import { useAuth } from '@/contexts/AuthContext'; import { useForum, type ForumDiscussion, type ForumReply } from '@/hooks/useForum'; import { formatDistanceToNow } from 'date-fns'; import { useTranslation } from '@/i18n'; type SortBy = 'recent' | 'popular' | 'replies' | 'views'; export function ForumSection() { const { hapticImpact, hapticNotification, showAlert } = useTelegram(); const { user: authUser } = useAuth(); const { t } = useTranslation(); // Use authenticated user ID from backend, not initDataUnsafe const userId = authUser?.telegram_id?.toString() || ''; const userName = authUser?.first_name || 'Telegram User'; const { announcements, categories, discussions, loading, refreshData, fetchReplies, createDiscussion, createReply, voteOnDiscussion, voteOnReply, incrementViewCount, } = useForum(userId); const [view, setView] = useState<'list' | 'thread' | 'create'>('list'); const [selectedDiscussion, setSelectedDiscussion] = useState(null); const [sortBy, setSortBy] = useState('recent'); const [filterCategory, setFilterCategory] = useState('all'); const [searchQuery, setSearchQuery] = useState(''); // Thread view state const [replies, setReplies] = useState([]); const [loadingReplies, setLoadingReplies] = useState(false); const [replyText, setReplyText] = useState(''); const [submittingReply, setSubmittingReply] = useState(false); // Create discussion state const [newTitle, setNewTitle] = useState(''); const [newContent, setNewContent] = useState(''); const [newCategory, setNewCategory] = useState(''); const [newTags, setNewTags] = useState(''); const [submittingDiscussion, setSubmittingDiscussion] = useState(false); const handleOpenThread = async (discussion: ForumDiscussion) => { hapticImpact('light'); setSelectedDiscussion(discussion); setView('thread'); // Increment view count await incrementViewCount(discussion.id); // Load replies setLoadingReplies(true); const loadedReplies = await fetchReplies(discussion.id); setReplies(loadedReplies); setLoadingReplies(false); }; const handleBack = () => { hapticImpact('light'); setView('list'); setSelectedDiscussion(null); setReplies([]); setReplyText(''); }; const handleOpenCreate = () => { hapticImpact('medium'); setView('create'); // Set default category if available if (categories.length > 0 && !newCategory) { setNewCategory(categories[0].id); } }; const handleCloseCreate = () => { hapticImpact('light'); setView('list'); setNewTitle(''); setNewContent(''); setNewTags(''); }; const handleVoteDiscussion = async (voteType: 'upvote' | 'downvote') => { if (!selectedDiscussion || !userId) { showAlert(t('forum.loginToVote')); return; } hapticImpact('light'); try { await voteOnDiscussion(selectedDiscussion.id, userId, voteType); hapticNotification('success'); // Update local state const updatedDiscussion = discussions.find((d) => d.id === selectedDiscussion.id); if (updatedDiscussion) { setSelectedDiscussion(updatedDiscussion); } } catch { hapticNotification('error'); showAlert(t('forum.voteError')); } }; const handleVoteReply = async (replyId: string, voteType: 'upvote' | 'downvote') => { if (!userId) { showAlert(t('forum.loginToVote')); return; } hapticImpact('light'); try { await voteOnReply(replyId, userId, voteType); hapticNotification('success'); // Refresh replies if (selectedDiscussion) { const loadedReplies = await fetchReplies(selectedDiscussion.id); setReplies(loadedReplies); } } catch { hapticNotification('error'); } }; const handleSubmitReply = async () => { if (!selectedDiscussion || !replyText.trim() || !userId) { showAlert(t('forum.writeReply')); return; } if (selectedDiscussion.is_locked) { showAlert(t('forum.topicLocked')); return; } setSubmittingReply(true); hapticImpact('medium'); try { await createReply({ discussion_id: selectedDiscussion.id, content: replyText.trim(), author_id: userId, author_name: userName, }); setReplyText(''); hapticNotification('success'); // Refresh replies const loadedReplies = await fetchReplies(selectedDiscussion.id); setReplies(loadedReplies); } catch { hapticNotification('error'); showAlert(t('forum.replyError')); } finally { setSubmittingReply(false); } }; const handleSubmitDiscussion = async () => { if (!newTitle.trim() || !newContent.trim() || !newCategory || !userId) { showAlert(t('forum.fillAllFields')); return; } setSubmittingDiscussion(true); hapticImpact('medium'); try { const tags = newTags .split(',') .map((tag) => tag.trim()) .filter((tag) => tag.length > 0); await createDiscussion({ title: newTitle.trim(), content: newContent.trim(), category_id: newCategory, author_id: userId, author_name: userName, tags, }); hapticNotification('success'); showAlert(t('forum.topicCreated')); handleCloseCreate(); } catch { hapticNotification('error'); showAlert(t('forum.topicCreateError')); } finally { setSubmittingDiscussion(false); } }; const getAnnouncementStyle = (type: string) => { switch (type) { case 'critical': return { icon: AlertTriangle, bgClass: 'bg-red-500/20 border-red-500/40 text-red-300' }; case 'warning': return { icon: AlertTriangle, bgClass: 'bg-yellow-500/20 border-yellow-500/40 text-yellow-300', }; case 'success': return { icon: CheckCircle, bgClass: 'bg-green-500/20 border-green-500/40 text-green-300' }; default: return { icon: Info, bgClass: 'bg-blue-500/20 border-blue-500/40 text-blue-300' }; } }; // Filter and sort discussions const filteredDiscussions = discussions .filter((d) => { const matchesSearch = d.title.toLowerCase().includes(searchQuery.toLowerCase()) || d.content.toLowerCase().includes(searchQuery.toLowerCase()); const matchesCategory = filterCategory === 'all' || d.category?.name.toLowerCase() === filterCategory.toLowerCase(); return matchesSearch && matchesCategory; }) .sort((a, b) => { switch (sortBy) { case 'popular': return (b.upvotes || 0) - (a.upvotes || 0); case 'replies': return b.replies_count - a.replies_count; case 'views': return b.views_count - a.views_count; default: return new Date(b.last_activity_at).getTime() - new Date(a.last_activity_at).getTime(); } }); // Refresh selected discussion when discussions update const selectedDiscussionId = selectedDiscussion?.id; useEffect(() => { if (selectedDiscussionId) { const updated = discussions.find((d) => d.id === selectedDiscussionId); if (updated) { setSelectedDiscussion(updated); } } }, [discussions, selectedDiscussionId]); // Create Discussion View if (view === 'create') { return (

{t('forum.newTopic')}

{/* Category */}
{categories.map((cat) => ( ))}
{/* Title */}
setNewTitle(e.target.value)} placeholder={t('forum.topicTitlePlaceholder')} className="w-full px-4 py-3 bg-secondary rounded-lg text-foreground" maxLength={200} />
{/* Content */}