import type { ProfileBrief } from '@chat-app/shared/friends'; import { useEffect } from 'react'; import { createPortal } from 'react-dom'; import { usePeerPresence } from '../lib/usePeerPresence'; import { Avatar } from './Avatar'; interface Props { userId: string; profile: ProfileBrief | null; x: number; y: number; onClose: () => void; onStartDm?: (userId: string) => void; } const PRESENCE_DOT: Record = { online: 'bg-emerald-500', idle: 'bg-amber-400', dnd: 'bg-rose-500', invisible: 'bg-neutral-500', offline: 'bg-neutral-400 dark:bg-neutral-600', }; const CARD_W = 280; const CARD_H = 180; // Hover/click card surfaced from avatars around the app. Shows display name, // @handle, presence state + status message, plus a DM-start button when // the clicked profile isn't the caller. export function UserProfilePopover({ userId, profile, x, y, onClose, onStartDm, }: Props) { // Subscribe to the same presence feed that the conversation header uses, // so status updates flow in live. const presence = usePeerPresence(userId); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; const onDown = (e: MouseEvent) => { const t = e.target as HTMLElement | null; if (t?.closest('[data-user-popover]')) return; if (t?.closest('[data-user-popover-trigger]')) return; onClose(); }; window.addEventListener('keydown', onKey); window.addEventListener('mousedown', onDown); return () => { window.removeEventListener('keydown', onKey); window.removeEventListener('mousedown', onDown); }; }, [onClose]); const left = Math.min(Math.max(8, x), window.innerWidth - CARD_W - 8); const top = Math.min(Math.max(8, y), window.innerHeight - CARD_H - 8); const displayName = profile?.displayName ?? '?'; const username = profile?.username ?? ''; const state = presence?.state ?? 'offline'; const showPresence = state !== 'invisible'; const statusMessage = presence?.statusMessage?.trim() ?? ''; return createPortal(
{showPresence && (

{displayName}

{username && (

@{username}

)}
{statusMessage && state !== 'offline' && (

{statusMessage}

)} {onStartDm && ( )}
, document.body, ); }