/** * @file components/members/MemberItem.tsx * @description Member item component rendering user details with popover toggle for profile views and direct actions. */ "use client"; import { useState, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; import type { User } from "@/db/schema"; import { clsx } from "clsx"; import { UserAvatar } from "../ui/UserAvatar"; import { UserProfilePopover } from "../ui/UserProfilePopover"; import { useActiveServer } from "@/lib/context/ServerContext"; import { useSidebarStore } from "@/lib/stores/useSidebarStore"; import { useUser } from "@/lib/context/UserContext"; /** Props for the MemberItem component. */ interface MemberItemProps { member: User; isOffline?: boolean; } /** Renders an individual member item with an interactive user profile popover. */ export function MemberItem({ member, isOffline = false }: MemberItemProps) { const router = useRouter(); const { setActiveServer } = useActiveServer(); const { closeMembers } = useSidebarStore(); const { currentUser, friendships } = useUser(); const currentUserId = currentUser.id; const [isProfileOpen, setIsProfileOpen] = useState(false); const containerRef = useRef(null); const triggerRef = useRef(null); // Close popover on click outside or ESC key press useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( containerRef.current && !containerRef.current.contains(event.target as Node) ) { setIsProfileOpen(false); } }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { setIsProfileOpen(false); } }; if (isProfileOpen) { document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", handleKeyDown); } return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; }, [isProfileOpen]); /** Determines the friendship status between the logged-in user and this member. */ const getFriendshipStatus = () => { if (!member.id || member.id === currentUserId || !friendships) { return null; } const friendship = friendships.find( (f) => (f.senderId === member.id && f.receiverId === currentUserId) || (f.receiverId === member.id && f.senderId === currentUserId), ); return friendship ? friendship.status : null; }; /** Initiates or navigates to a direct message conversation with the target member. */ const handleStartConversation = async (recipientId: string) => { try { const res = await fetch("/api/dm", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ recipientId }), }); if (res.ok) { const conversation = await res.json(); setActiveServer(null); closeMembers(); router.push(`/dm/${conversation.id}`); } } catch (error) { console.error("Failed to start conversation:", error); } }; /** Sends a friend request to the specified username. */ const handleAddFriend = async (username: string) => { try { const res = await fetch("/api/friends", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username }), }); if (res.ok) { router.refresh(); } } catch (error) { console.error("Error sending friend request:", error); } }; return (
{isProfileOpen && ( )}
); }