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 (
);
}
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 && (
)}
{!canComment && (
Connect wallet and verify Ordinal ownership to comment
)}
{/* Comments */}
Comments ({visibleComments.length})
{visibleComments.length === 0 ? (
No comments yet
{canComment
? 'Be the first to share your thoughts!'
: 'Connect your wallet to join the conversation.'}
) : (
visibleComments.map(comment => (
))
)}
);
};
export default PostDetail;