2025-04-15 16:28:03 +05:30
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import { Link, useParams } from 'react-router-dom';
|
2025-09-03 15:56:00 +05:30
|
|
|
import {
|
|
|
|
|
useCell,
|
|
|
|
|
useCellPosts,
|
|
|
|
|
useForumActions,
|
|
|
|
|
usePermissions,
|
|
|
|
|
useUserVotes,
|
|
|
|
|
useAuth,
|
2025-09-12 16:03:12 +05:30
|
|
|
useForumData,
|
2025-09-03 15:56:00 +05:30
|
|
|
} from '@/hooks';
|
2025-04-15 16:28:03 +05:30
|
|
|
import { Button } from '@/components/ui/button';
|
2025-04-22 10:39:32 +05:30
|
|
|
import { Input } from '@/components/ui/input';
|
2025-04-15 16:28:03 +05:30
|
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
|
|
|
import { Skeleton } from '@/components/ui/skeleton';
|
2025-09-10 15:04:24 +05:30
|
|
|
import { LinkRenderer } from '@/components/ui/link-renderer';
|
2025-09-11 08:54:26 +02:00
|
|
|
import { ShareButton } from '@/components/ui/ShareButton';
|
2025-08-30 18:34:50 +05:30
|
|
|
import {
|
|
|
|
|
ArrowLeft,
|
|
|
|
|
MessageSquare,
|
|
|
|
|
MessageCircle,
|
|
|
|
|
ArrowUp,
|
|
|
|
|
ArrowDown,
|
|
|
|
|
RefreshCw,
|
2025-09-05 16:50:30 +05:30
|
|
|
Shield,
|
|
|
|
|
UserX,
|
2025-08-30 18:34:50 +05:30
|
|
|
} from 'lucide-react';
|
2025-04-15 16:28:03 +05:30
|
|
|
import { formatDistanceToNow } from 'date-fns';
|
2025-04-22 11:05:49 +05:30
|
|
|
import { CypherImage } from './ui/CypherImage';
|
2025-08-11 12:23:08 +05:30
|
|
|
import { AuthorDisplay } from './ui/author-display';
|
2025-09-05 16:50:30 +05:30
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from '@/components/ui/tooltip';
|
2025-04-15 16:28:03 +05:30
|
|
|
|
|
|
|
|
const PostList = () => {
|
|
|
|
|
const { cellId } = useParams<{ cellId: string }>();
|
2025-09-03 15:56:00 +05:30
|
|
|
|
|
|
|
|
// ✅ Use reactive hooks for data and actions
|
|
|
|
|
const cell = useCell(cellId);
|
|
|
|
|
const cellPosts = useCellPosts(cellId, { sortBy: 'relevance' });
|
2025-08-30 18:34:50 +05:30
|
|
|
const {
|
|
|
|
|
createPost,
|
2025-04-24 14:31:00 +05:30
|
|
|
votePost,
|
2025-06-06 16:45:14 +05:30
|
|
|
moderatePost,
|
2025-09-10 17:28:03 +05:30
|
|
|
unmoderatePost,
|
2025-08-11 12:23:08 +05:30
|
|
|
moderateUser,
|
2025-09-03 15:56:00 +05:30
|
|
|
refreshData,
|
|
|
|
|
isCreatingPost,
|
|
|
|
|
isVoting,
|
|
|
|
|
} = useForumActions();
|
|
|
|
|
const { canPost, canVote, canModerate } = usePermissions();
|
|
|
|
|
const userVotes = useUserVotes();
|
2025-09-15 14:24:52 +05:30
|
|
|
const { currentUser } = useAuth();
|
2025-09-12 16:03:12 +05:30
|
|
|
const { commentsByPost } = useForumData();
|
2025-09-03 15:56:00 +05:30
|
|
|
|
2025-04-22 10:39:32 +05:30
|
|
|
const [newPostTitle, setNewPostTitle] = useState('');
|
2025-04-15 16:28:03 +05:30
|
|
|
const [newPostContent, setNewPostContent] = useState('');
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
if (!cellId || cellPosts.isLoading) {
|
2025-04-15 16:28:03 +05:30
|
|
|
return (
|
|
|
|
|
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
|
|
|
<div className="mb-6">
|
2025-08-30 18:34:50 +05:30
|
|
|
<Link
|
|
|
|
|
to="/"
|
|
|
|
|
className="text-cyber-accent hover:underline flex items-center gap-1 text-sm"
|
|
|
|
|
>
|
2025-04-15 16:28:03 +05:30
|
|
|
<ArrowLeft className="w-4 h-4" /> Back to Cells
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
<Skeleton className="h-8 w-32 mb-6 bg-cyber-muted" />
|
|
|
|
|
<Skeleton className="h-6 w-64 mb-6 bg-cyber-muted" />
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
<div className="space-y-4">
|
|
|
|
|
{[...Array(3)].map((_, i) => (
|
|
|
|
|
<div key={i} className="border border-cyber-muted rounded-sm p-4">
|
|
|
|
|
<div className="mb-2">
|
|
|
|
|
<Skeleton className="h-6 w-full mb-2 bg-cyber-muted" />
|
|
|
|
|
<Skeleton className="h-6 w-3/4 mb-2 bg-cyber-muted" />
|
|
|
|
|
</div>
|
|
|
|
|
<Skeleton className="h-4 w-32 bg-cyber-muted" />
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
if (!cell) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="container mx-auto px-4 py-8 max-w-4xl">
|
|
|
|
|
<div className="mb-6">
|
2025-08-30 18:34:50 +05:30
|
|
|
<Link
|
|
|
|
|
to="/"
|
|
|
|
|
className="text-cyber-accent hover:underline flex items-center gap-1 text-sm"
|
|
|
|
|
>
|
2025-04-15 16:28:03 +05:30
|
|
|
<ArrowLeft className="w-4 h-4" /> Back to Cells
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="p-8 text-center">
|
|
|
|
|
<h1 className="text-2xl font-bold mb-4">Cell Not Found</h1>
|
2025-08-30 18:34:50 +05:30
|
|
|
<p className="text-cyber-neutral mb-6">
|
|
|
|
|
The cell you're looking for doesn't exist.
|
|
|
|
|
</p>
|
2025-04-15 16:28:03 +05:30
|
|
|
<Button asChild>
|
|
|
|
|
<Link to="/">Return to Cells</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
const handleCreatePost = async (e: React.FormEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (!newPostContent.trim()) return;
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
// ✅ All validation handled in hook
|
|
|
|
|
const post = await createPost(cellId, newPostTitle, newPostContent);
|
|
|
|
|
if (post) {
|
|
|
|
|
setNewPostTitle('');
|
|
|
|
|
setNewPostContent('');
|
2025-04-15 16:28:03 +05:30
|
|
|
}
|
|
|
|
|
};
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-11 12:10:51 +05:30
|
|
|
// Handle keyboard shortcuts
|
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
2025-09-15 15:00:07 +05:30
|
|
|
// 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) {
|
2025-09-11 12:10:51 +05:30
|
|
|
e.preventDefault();
|
|
|
|
|
if (!isCreatingPost && newPostContent.trim() && newPostTitle.trim()) {
|
|
|
|
|
handleCreatePost(e as React.FormEvent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-04-24 14:31:00 +05:30
|
|
|
const handleVotePost = async (postId: string, isUpvote: boolean) => {
|
2025-09-03 15:56:00 +05:30
|
|
|
// ✅ Permission checking handled in hook
|
2025-04-24 14:31:00 +05:30
|
|
|
await votePost(postId, isUpvote);
|
|
|
|
|
};
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
const getPostVoteType = (postId: string) => {
|
|
|
|
|
return userVotes.getPostVoteType(postId);
|
2025-04-24 14:31:00 +05:30
|
|
|
};
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
// ✅ Posts already filtered by hook based on user permissions
|
|
|
|
|
const visiblePosts = cellPosts.posts;
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-06-06 16:42:00 +05:30
|
|
|
const handleModerate = async (postId: string) => {
|
2025-08-30 18:34:50 +05:30
|
|
|
const reason =
|
|
|
|
|
window.prompt('Enter a reason for moderation (optional):') || undefined;
|
2025-06-06 16:42:00 +05:30
|
|
|
if (!cell) return;
|
2025-09-03 15:56:00 +05:30
|
|
|
// ✅ All validation handled in hook
|
|
|
|
|
await moderatePost(cell.id, postId, reason);
|
2025-06-06 16:42:00 +05:30
|
|
|
};
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-10 17:28:03 +05:30
|
|
|
const handleUnmoderate = async (postId: string) => {
|
|
|
|
|
const reason =
|
|
|
|
|
window.prompt('Optional note for unmoderation?') || undefined;
|
|
|
|
|
if (!cell) return;
|
|
|
|
|
await unmoderatePost(cell.id, postId, reason);
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-06 16:45:14 +05:30
|
|
|
const handleModerateUser = async (userAddress: string) => {
|
2025-08-30 18:34:50 +05:30
|
|
|
const reason =
|
|
|
|
|
window.prompt('Reason for moderating this user? (optional)') || undefined;
|
2025-06-06 16:45:14 +05:30
|
|
|
if (!cell) return;
|
2025-09-03 15:56:00 +05:30
|
|
|
// ✅ All validation handled in hook
|
|
|
|
|
await moderateUser(cell.id, userAddress, reason);
|
2025-06-06 16:45:14 +05:30
|
|
|
};
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
return (
|
2025-09-05 20:26:29 +05:30
|
|
|
<div className="page-main">
|
|
|
|
|
<div className="content-spacing">
|
2025-08-30 18:34:50 +05:30
|
|
|
<Link
|
|
|
|
|
to="/"
|
|
|
|
|
className="text-cyber-accent hover:underline flex items-center gap-1 text-sm"
|
|
|
|
|
>
|
2025-04-15 16:28:03 +05:30
|
|
|
<ArrowLeft className="w-4 h-4" /> Back to Cells
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-05 20:26:29 +05:30
|
|
|
<div className="flex gap-4 items-start content-spacing">
|
2025-08-30 18:34:50 +05:30
|
|
|
<CypherImage
|
|
|
|
|
src={cell.icon}
|
|
|
|
|
alt={cell.name}
|
2025-04-15 16:28:03 +05:30
|
|
|
className="w-12 h-12 object-cover rounded-sm border border-cyber-muted"
|
2025-04-22 11:05:49 +05:30
|
|
|
generateUniqueFallback={true}
|
2025-04-15 16:28:03 +05:30
|
|
|
/>
|
2025-04-22 10:39:32 +05:30
|
|
|
<div className="flex-1">
|
|
|
|
|
<div className="flex items-center justify-between">
|
2025-09-05 20:26:29 +05:30
|
|
|
<h1 className="page-title text-glow">{cell.name}</h1>
|
2025-08-30 18:34:50 +05:30
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={refreshData}
|
2025-09-03 15:56:00 +05:30
|
|
|
disabled={cellPosts.isLoading}
|
2025-04-22 10:39:32 +05:30
|
|
|
title="Refresh data"
|
|
|
|
|
>
|
2025-08-30 18:34:50 +05:30
|
|
|
<RefreshCw
|
2025-09-03 15:56:00 +05:30
|
|
|
className={`w-4 h-4 ${cellPosts.isLoading ? 'animate-spin' : ''}`}
|
2025-08-30 18:34:50 +05:30
|
|
|
/>
|
2025-04-22 10:39:32 +05:30
|
|
|
</Button>
|
|
|
|
|
</div>
|
2025-09-05 20:26:29 +05:30
|
|
|
<p className="page-subtitle">{cell.description}</p>
|
2025-04-15 16:28:03 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
{canPost && (
|
2025-09-05 20:26:29 +05:30
|
|
|
<div className="section-spacing">
|
2025-09-11 12:10:51 +05:30
|
|
|
<form onSubmit={handleCreatePost} onKeyDown={handleKeyDown}>
|
2025-04-15 16:28:03 +05:30
|
|
|
<h2 className="text-sm font-bold mb-2 flex items-center gap-1">
|
|
|
|
|
<MessageSquare className="w-4 h-4" />
|
|
|
|
|
New Thread
|
|
|
|
|
</h2>
|
2025-04-22 10:39:32 +05:30
|
|
|
<div className="mb-3">
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Thread title"
|
|
|
|
|
value={newPostTitle}
|
2025-08-30 18:34:50 +05:30
|
|
|
onChange={e => setNewPostTitle(e.target.value)}
|
2025-04-22 10:39:32 +05:30
|
|
|
className="mb-3 bg-cyber-muted/50 border-cyber-muted"
|
2025-09-03 15:56:00 +05:30
|
|
|
disabled={isCreatingPost}
|
2025-04-22 10:39:32 +05:30
|
|
|
/>
|
|
|
|
|
<Textarea
|
|
|
|
|
placeholder="What's on your mind?"
|
|
|
|
|
value={newPostContent}
|
2025-08-30 18:34:50 +05:30
|
|
|
onChange={e => setNewPostContent(e.target.value)}
|
2025-04-22 10:39:32 +05:30
|
|
|
className="bg-cyber-muted/50 border-cyber-muted resize-none"
|
2025-09-03 15:56:00 +05:30
|
|
|
disabled={isCreatingPost}
|
2025-04-22 10:39:32 +05:30
|
|
|
/>
|
|
|
|
|
</div>
|
2025-09-15 15:00:07 +05:30
|
|
|
<div className="flex items-center justify-between text-xs text-muted-foreground mt-1 mb-2">
|
|
|
|
|
<span>Press Enter for newline • Ctrl+Enter or Shift+Enter to post</span>
|
|
|
|
|
<span />
|
|
|
|
|
</div>
|
2025-04-15 16:28:03 +05:30
|
|
|
<div className="flex justify-end">
|
2025-08-30 18:34:50 +05:30
|
|
|
<Button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={
|
2025-09-03 15:56:00 +05:30
|
|
|
isCreatingPost ||
|
2025-08-30 18:34:50 +05:30
|
|
|
!newPostContent.trim() ||
|
|
|
|
|
!newPostTitle.trim()
|
|
|
|
|
}
|
2025-04-15 16:28:03 +05:30
|
|
|
>
|
2025-09-03 15:56:00 +05:30
|
|
|
{isCreatingPost ? 'Posting...' : 'Post Thread'}
|
2025-04-15 16:28:03 +05:30
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-08-30 18:34:50 +05:30
|
|
|
|
|
|
|
|
|
2025-09-03 15:56:00 +05:30
|
|
|
{!canPost && !currentUser && (
|
2025-09-05 20:26:29 +05:30
|
|
|
<div className="section-spacing content-card-sm text-center">
|
2025-08-30 18:34:50 +05:30
|
|
|
<p className="text-sm mb-3">
|
|
|
|
|
Connect wallet and verify Ordinal ownership to post
|
|
|
|
|
</p>
|
2025-04-24 14:31:00 +05:30
|
|
|
<Button asChild size="sm">
|
|
|
|
|
<Link to="/">Connect Wallet</Link>
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-15 16:28:03 +05:30
|
|
|
<div className="space-y-4">
|
2025-09-03 15:56:00 +05:30
|
|
|
{visiblePosts.length === 0 ? (
|
2025-09-05 20:26:29 +05:30
|
|
|
<div className="empty-state">
|
|
|
|
|
<MessageCircle className="empty-state-icon text-cyber-neutral opacity-50" />
|
|
|
|
|
<h2 className="empty-state-title">No Threads Yet</h2>
|
|
|
|
|
<p className="empty-state-description">
|
2025-09-03 15:56:00 +05:30
|
|
|
{canPost
|
2025-08-30 18:34:50 +05:30
|
|
|
? 'Be the first to post in this cell!'
|
|
|
|
|
: 'Connect your wallet and verify Ordinal ownership to start a thread.'}
|
2025-04-15 16:28:03 +05:30
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2025-06-06 16:42:00 +05:30
|
|
|
visiblePosts.map(post => (
|
2025-09-08 13:01:36 +05:30
|
|
|
<div key={post.id} className="thread-card">
|
2025-04-24 14:31:00 +05:30
|
|
|
<div className="flex gap-4">
|
|
|
|
|
<div className="flex flex-col items-center">
|
2025-08-30 18:34:50 +05:30
|
|
|
<button
|
2025-09-03 15:56:00 +05:30
|
|
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'upvote' ? 'text-cyber-accent' : ''}`}
|
2025-04-24 14:31:00 +05:30
|
|
|
onClick={() => handleVotePost(post.id, true)}
|
2025-09-03 15:56:00 +05:30
|
|
|
disabled={!canVote || isVoting}
|
2025-08-30 18:34:50 +05:30
|
|
|
title={
|
2025-09-03 15:56:00 +05:30
|
|
|
canVote ? 'Upvote' : 'Connect wallet and verify to vote'
|
2025-08-30 18:34:50 +05:30
|
|
|
}
|
2025-04-24 14:31:00 +05:30
|
|
|
>
|
|
|
|
|
<ArrowUp className="w-4 h-4" />
|
|
|
|
|
</button>
|
2025-08-30 18:34:50 +05:30
|
|
|
<span className="text-sm py-1">
|
|
|
|
|
{post.upvotes.length - post.downvotes.length}
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
2025-09-03 15:56:00 +05:30
|
|
|
className={`p-1 rounded-sm hover:bg-cyber-muted/50 ${getPostVoteType(post.id) === 'downvote' ? 'text-cyber-accent' : ''}`}
|
2025-04-24 14:31:00 +05:30
|
|
|
onClick={() => handleVotePost(post.id, false)}
|
2025-09-03 15:56:00 +05:30
|
|
|
disabled={!canVote || isVoting}
|
2025-08-30 18:34:50 +05:30
|
|
|
title={
|
2025-09-03 15:56:00 +05:30
|
|
|
canVote ? 'Downvote' : 'Connect wallet and verify to vote'
|
2025-08-30 18:34:50 +05:30
|
|
|
}
|
2025-04-24 14:31:00 +05:30
|
|
|
>
|
|
|
|
|
<ArrowDown className="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-08-30 18:34:50 +05:30
|
|
|
|
2025-04-24 14:31:00 +05:30
|
|
|
<div className="flex-1">
|
|
|
|
|
<Link to={`/post/${post.id}`} className="block">
|
2025-08-30 18:34:50 +05:30
|
|
|
<h2 className="text-lg font-bold hover:text-cyber-accent">
|
|
|
|
|
{post.title}
|
|
|
|
|
</h2>
|
2025-09-10 15:04:24 +05:30
|
|
|
<p className="line-clamp-2 text-sm mb-3">
|
|
|
|
|
<LinkRenderer text={post.content} />
|
|
|
|
|
</p>
|
2025-04-24 14:31:00 +05:30
|
|
|
<div className="flex items-center gap-4 text-xs text-cyber-neutral">
|
2025-08-30 18:34:50 +05:30
|
|
|
<span>
|
|
|
|
|
{formatDistanceToNow(post.timestamp, {
|
|
|
|
|
addSuffix: true,
|
|
|
|
|
})}
|
|
|
|
|
</span>
|
2025-08-11 12:23:08 +05:30
|
|
|
<span>by </span>
|
2025-08-30 18:34:50 +05:30
|
|
|
<AuthorDisplay
|
2025-09-03 15:01:57 +05:30
|
|
|
address={post.author}
|
2025-08-30 18:34:50 +05:30
|
|
|
className="text-xs"
|
|
|
|
|
showBadge={false}
|
|
|
|
|
/>
|
2025-09-11 08:54:26 +02:00
|
|
|
<span>•</span>
|
|
|
|
|
<span>
|
|
|
|
|
<MessageSquare className="inline w-3 h-3 mr-1" />
|
2025-09-12 16:03:12 +05:30
|
|
|
{commentsByPost[post.id]?.length || 0} comments
|
2025-09-11 08:54:26 +02:00
|
|
|
</span>
|
|
|
|
|
<ShareButton
|
|
|
|
|
url={`${window.location.origin}/post/${post.id}`}
|
|
|
|
|
title={post.title}
|
|
|
|
|
description={post.content}
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="text-cyber-neutral"
|
|
|
|
|
showText={false}
|
|
|
|
|
/>
|
2025-04-24 14:31:00 +05:30
|
|
|
</div>
|
|
|
|
|
</Link>
|
2025-09-03 15:56:00 +05:30
|
|
|
{canModerate(cell.id) && !post.moderated && (
|
2025-09-05 16:50:30 +05:30
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
size="icon"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="h-6 w-6 text-cyber-neutral hover:text-orange-500"
|
|
|
|
|
onClick={() => handleModerate(post.id)}
|
|
|
|
|
>
|
|
|
|
|
<Shield className="h-3 w-3" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Moderate post</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
2025-06-06 16:42:00 +05:30
|
|
|
)}
|
2025-09-05 16:50:30 +05:30
|
|
|
{canModerate(cell.id) && post.author !== cell.author && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
size="icon"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="h-6 w-6 text-cyber-neutral hover:text-red-500"
|
|
|
|
|
onClick={() => handleModerateUser(post.author)}
|
|
|
|
|
>
|
|
|
|
|
<UserX className="h-3 w-3" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Moderate user</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
2025-06-06 16:45:14 +05:30
|
|
|
)}
|
2025-09-10 17:28:03 +05:30
|
|
|
{canModerate(cell.id) && post.moderated && (
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
className="h-6 px-2 text-cyber-neutral hover:text-green-500"
|
|
|
|
|
onClick={() => handleUnmoderate(post.id)}
|
|
|
|
|
>
|
|
|
|
|
Unmoderate
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>
|
|
|
|
|
<p>Unmoderate post</p>
|
|
|
|
|
</TooltipContent>
|
|
|
|
|
</Tooltip>
|
2025-06-06 16:42:00 +05:30
|
|
|
)}
|
2025-04-15 16:28:03 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-04-24 14:31:00 +05:30
|
|
|
</div>
|
2025-04-15 16:28:03 +05:30
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default PostList;
|