import React, { useState } from 'react'; import { Link, useParams, useNavigate } from 'react-router-dom'; import { usePost, usePostComments, useForumActions, usePermissions, useUserVotes, } from '@/hooks'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; import { ArrowLeft, ArrowUp, ArrowDown, Clock, MessageCircle, Send, Loader2, Shield, UserX, } from 'lucide-react'; import { formatDistanceToNow } from 'date-fns'; import { RelevanceIndicator } from './ui/relevance-indicator'; import { AuthorDisplay } from './ui/author-display'; import { usePending, usePendingVote } from '@/hooks/usePending'; import { Tooltip, TooltipContent, TooltipTrigger, } from '@/components/ui/tooltip'; // Extracted child component to respect Rules of Hooks const PendingBadge: React.FC<{ id: string }> = ({ id }) => { const { isPending } = usePending(id); if (!isPending) return null; return ( <> syncing… ); }; 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, { includeModerated: false }); const { createComment, votePost, voteComment, moderateComment, moderateUser, isCreatingComment, isVoting, } = useForumActions(); const { canVote, canComment, canModerate } = usePermissions(); const userVotes = useUserVotes(); // ✅ 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 handleVoteComment = async (commentId: string, isUpvote: boolean) => { // ✅ Permission checking handled in hook await voteComment(commentId, isUpvote); }; // ✅ Get vote status from hooks const postVoteType = userVotes.getPostVoteType(post.id); const isPostUpvoted = postVoteType === 'upvote'; const isPostDownvoted = postVoteType === 'downvote'; const getCommentVoteType = (commentId: string) => { return userVotes.getCommentVoteType(commentId); }; 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