import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu'; import { requestConfirmation } from '@/features/dialogs/actions'; import { useCan } from '@/features/server/hooks'; import { setActiveThreadId } from '@/features/server/channels/actions'; import { useOwnUserId } from '@/features/server/users/hooks'; import { getTRPCClient } from '@/lib/trpc'; import { Permission } from '@pulse/shared'; import { Bell, BellOff, ClipboardCopy, Ellipsis, ExternalLink, Tags, Trash } from 'lucide-react'; import { memo, useCallback, useEffect, useState } from 'react'; import { toast } from 'sonner'; type TForumPostMenuProps = { threadId: number; threadName: string; creatorId?: number; currentTagIds: number[]; channelId: number; onEditTags: (threadId: number, currentTagIds: number[]) => void; onPostDeleted: () => void; }; const ForumPostMenu = memo( ({ threadId, threadName, creatorId, currentTagIds, channelId, onEditTags, onPostDeleted }: TForumPostMenuProps) => { const can = useCan(); const ownUserId = useOwnUserId(); const [following, setFollowing] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const isCreator = creatorId === ownUserId; const canEditTags = isCreator || can(Permission.MANAGE_CHANNELS); const canDelete = isCreator || can(Permission.MANAGE_CHANNELS); // Fetch follow status when menu opens useEffect(() => { if (!menuOpen) return; const trpc = getTRPCClient(); trpc.threads.getFollowStatus .query({ threadId }) .then((result) => setFollowing(result.following)) .catch(() => {}); }, [menuOpen, threadId]); const onOpenPost = useCallback(() => { setActiveThreadId(threadId); }, [threadId]); const onToggleFollow = useCallback(async () => { const trpc = getTRPCClient(); const newState = !following; try { await trpc.threads.followThread.mutate({ threadId, follow: newState }); setFollowing(newState); toast.success(newState ? 'Following post' : 'Unfollowed post'); } catch { toast.error('Failed to update follow status'); } }, [threadId, following]); const onCopyLink = useCallback(() => { navigator.clipboard.writeText(`${channelId}/${threadId}`); toast.success('Link copied'); }, [channelId, threadId]); const onDelete = useCallback(async () => { const choice = await requestConfirmation({ title: 'Delete Post', message: `Are you sure you want to delete "${threadName}"? This will permanently remove the post and all its replies.`, confirmLabel: 'Delete', cancelLabel: 'Cancel' }); if (!choice) return; const trpc = getTRPCClient(); try { await trpc.threads.deleteThread.mutate({ threadId }); toast.success('Post deleted'); onPostDeleted(); } catch { toast.error('Failed to delete post'); } }, [threadId, threadName, onPostDeleted]); return ( Open Post {following ? ( ) : ( )} {following ? 'Unfollow Post' : 'Follow Post'} {canEditTags && ( onEditTags(threadId, currentTagIds)}> Edit Tags )} Copy Link {canDelete && ( <> Delete Post )} ); } ); export { ForumPostMenu };