import React, { useState } from 'react'; import { Link, useParams, useNavigate } from 'react-router-dom'; import { usePost, usePostComments, useForumActions, usePermissions, useUserVotes, usePostBookmark, } from '@/hooks'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { ArrowLeft, ArrowUp, ArrowDown, Clock, MessageCircle, Send, Loader2, } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { RelevanceIndicator } from './ui/relevance-indicator'; import { AuthorDisplay } from './ui/author-display'; import { BookmarkButton } from './ui/bookmark-button'; import CommentCard from './CommentCard'; import { usePending, usePendingVote } from '@/hooks/usePending'; const PostDetail = () => { const { postId } = useParams<{ postId: string }>(); const navigate = useNavigate(); // ✅ Use reactive hooks for data and actions const post = usePost(postId); const comments = usePostComments(postId); const { createComment, votePost, moderateComment, moderateUser, isCreatingComment, isVoting, } = useForumActions(); const { canVote, canComment, canModerate } = usePermissions(); const userVotes = useUserVotes(); const { isBookmarked, loading: bookmarkLoading, toggleBookmark, } = usePostBookmark(post, post?.cellId); // ✅ Move ALL hook calls to the top, before any conditional logic const postPending = usePending(post?.id); const postVotePending = usePendingVote(post?.id); const [newComment, setNewComment] = useState(''); if (!postId) return
Invalid post ID
; // ✅ Loading state handled by hook if (comments.isLoading) { return (

Loading Post...

); } if (!post) { return (

Post not found

The post you're looking for doesn't exist or has been removed.

); } // ✅ All data comes pre-computed from hooks const { cell } = post; const visibleComments = comments.comments; // Already filtered by hook const handleCreateComment = async (e: React.FormEvent) => { e.preventDefault(); if (!newComment.trim()) return; // ✅ All validation handled in hook const result = await createComment(postId, newComment); if (result) { setNewComment(''); } }; const handleVotePost = async (isUpvote: boolean) => { // ✅ Permission checking handled in hook await votePost(post.id, isUpvote); }; const handleBookmark = async (e?: React.MouseEvent) => { if (e) { e.preventDefault(); e.stopPropagation(); } await toggleBookmark(); }; // ✅ Get vote status from hooks const postVoteType = userVotes.getPostVoteType(post.id); const isPostUpvoted = postVoteType === 'upvote'; const isPostDownvoted = postVoteType === 'downvote'; const handleModerateComment = async (commentId: string) => { const reason = window.prompt('Enter a reason for moderation (optional):') || undefined; if (!cell) return; // ✅ All validation handled in hook await moderateComment(cell.id, commentId, 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 moderateUser(cell.id, userAddress, reason); }; return (
{post.voteScore} {postVotePending.isPending && ( syncing… )}
r/{cell?.name || 'unknown'} Posted by u/ {formatDistanceToNow(new Date(post.timestamp), { addSuffix: true, })} {post.relevanceScore !== undefined && ( <> )} {postPending.isPending && ( <> syncing… )}

{post.title}

{post.content}

{/* Comment Form */} {canComment && (

Add a comment