import React, { useState } from 'react'; import { Link, useParams } from 'react-router-dom'; import { usePermissions, useAuth, useContent } from '@/hooks'; import type { Post as ForumPost, Cell as ForumCell, VoteMessage } from '@opchan/core'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Skeleton } from '@/components/ui/skeleton'; import { LinkRenderer } from '@/components/ui/link-renderer'; import { RelevanceIndicator } from '@/components/ui/relevance-indicator'; import { ShareButton } from '@/components/ui/ShareButton'; import { ArrowLeft, MessageSquare, MessageCircle, ArrowUp, ArrowDown, RefreshCw, Shield, UserX, } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { CypherImage } from './ui/CypherImage'; import { AuthorDisplay } from './ui/author-display'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; const PostList = () => { const { cellId } = useParams<{ cellId: string }>(); // ✅ Use reactive hooks for data and actions const { createPost, vote, moderate, refresh, commentsByPost, cells, posts } = useContent(); const cell = cells.find((c: ForumCell) => c.id === cellId); const isCreatingPost = false; const isVoting = false; const { canPost, canVote, canModerate } = usePermissions(); const { currentUser } = useAuth(); const [newPostTitle, setNewPostTitle] = useState(''); const [newPostContent, setNewPostContent] = useState(''); if (!cellId) { return (
Back to Cells
{[...Array(3)].map((_, i) => (
))}
); } if (!cell) { return (
Back to Cells

Cell Not Found

The cell you're looking for doesn't exist.

); } const handleCreatePost = async (e: React.FormEvent) => { e.preventDefault(); if (!newPostContent.trim()) return; // ✅ All validation handled in hook const post = await createPost({ cellId, title: newPostTitle, content: newPostContent }); if (post) { setNewPostTitle(''); setNewPostContent(''); } }; // Handle keyboard shortcuts const handleKeyDown = (e: React.KeyboardEvent) => { // Enter inserts newline by default. Send on Ctrl+Enter or Shift+Enter. const isSendCombo = (e.ctrlKey || e.metaKey || e.shiftKey) && e.key === 'Enter'; if (isSendCombo) { e.preventDefault(); if (!isCreatingPost && newPostContent.trim() && newPostTitle.trim()) { handleCreatePost(e as React.FormEvent); } } }; const handleVotePost = async (postId: string, isUpvote: boolean) => { await vote({ targetId: postId, isUpvote }); }; const getPostVoteType = (postId: string) => { if (!currentUser) return null; const p = posts.find((p: ForumPost) => p.id === postId); if (!p) return null; const up = p.upvotes.some((v: VoteMessage) => v.author === currentUser.address); const down = p.downvotes.some((v: VoteMessage) => v.author === currentUser.address); return up ? 'upvote' : down ? 'downvote' : null; }; // ✅ Posts already filtered by hook based on user permissions const visiblePosts = posts .filter((p: ForumPost) => p.cellId === cellId) .sort((a: ForumPost, b: ForumPost) => { const ar = a.relevanceScore ?? 0; const br = b.relevanceScore ?? 0; return br - ar || b.timestamp - a.timestamp; }); const handleModerate = async (postId: string) => { const reason = window.prompt('Enter a reason for moderation (optional):') || undefined; if (!cell) return; // ✅ All validation handled in hook await moderate.post(cell.id, postId, reason); }; const handleUnmoderate = async (postId: string) => { const reason = window.prompt('Optional note for unmoderation?') || undefined; if (!cell) return; await moderate.unpost(cell.id, postId, reason); }; const handleModerateUser = async (userAddress: string) => { const reason = window.prompt('Reason for moderating this user? (optional)') || undefined; if (!cell) return; // ✅ All validation handled in hook await moderate.user(cell.id, userAddress, reason); }; return (
Back to Cells

{cell.name}

{cell.description}

{canPost && (

New Thread

setNewPostTitle(e.target.value)} className="mb-3 bg-cyber-muted/50 border-cyber-muted" disabled={isCreatingPost} />