/** * Universal Comment Section * Works on any content type: posts, news, bot intel, scans, etc. */ import { useState, useEffect } from 'react'; import { useAppStore } from '../store/appStore'; import { api } from '../services/api'; import { MessageSquare, Send, ThumbsUp, Trash2, Loader2, User, CornerDownRight, } from 'lucide-react'; interface Comment { id: string; body: string; user_id: string; author_email: string; author_wallet: string; upvotes: number; created_at: string; parent_id?: string | null; } interface CommentSectionProps { contentType: string; contentId: string; title?: string; } export default function CommentSection({ contentType, contentId, title = 'Comments' }: CommentSectionProps) { const [comments, setComments] = useState([]); const [newComment, setNewComment] = useState(''); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const isAuthenticated = useAppStore((state) => state.isAuthenticated); const currentUser = useAppStore((state) => state.user); const loadComments = async () => { try { setLoading(true); const res = await api.listComments(contentType, contentId); setComments(res.comments || []); setError(null); } catch (e: any) { setError(e.message || 'Failed to load comments'); } finally { setLoading(false); } }; useEffect(() => { loadComments(); }, [contentType, contentId]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!newComment.trim() || !isAuthenticated) return; setSubmitting(true); try { await api.createComment(contentType, contentId, newComment.trim()); setNewComment(''); await loadComments(); } catch (e: any) { setError(e.response?.data?.detail || 'Failed to post comment'); } finally { setSubmitting(false); } }; const handleUpvote = async (commentId: string) => { try { await api.upvoteComment(commentId); await loadComments(); } catch (e) { console.error('Upvote failed:', e); } }; const handleDelete = async (commentId: string) => { if (!confirm('Delete this comment?')) return; try { await api.deleteComment(commentId); await loadComments(); } catch (e) { console.error('Delete failed:', e); } }; const topLevel = comments.filter((c) => !c.parent_id); const replies = (parentId: string) => comments.filter((c) => c.parent_id === parentId); const formatTime = (dateStr: string) => { const d = new Date(dateStr); return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }; const renderComment = (comment: Comment, isReply = false) => (
{comment.author_email || comment.author_wallet || 'Anonymous'} {formatTime(comment.created_at)}

{comment.body}

{currentUser?.id === comment.user_id && ( )}
{/* Replies */} {replies(comment.id).map((reply) => renderComment(reply, true))}
); return (

{title} ({comments.length})

{loading ? (
Loading comments...
) : error ? (
{error}
) : (
{topLevel.map((c) => renderComment(c))} {comments.length === 0 && (
No comments yet. Be the first to share your thoughts.
)}
)} {/* Comment Input */} {isAuthenticated ? (