"use client"; import { useState, useRef, useEffect } from "react"; import { createPortal } from "react-dom"; import { Mail, Phone, Building, ExternalLink, Copy, Send, UserPlus } from "lucide-react"; import { Avatar } from "@/components/ui/avatar"; import { cn } from "@/lib/utils"; import { useContactStore, getContactDisplayName } from "@/stores/contact-store"; import { toast } from "@/stores/toast-store"; import type { ContactCard } from "@/lib/jmap/types"; interface RecipientPopoverProps { name?: string; email: string; /** Display label override (e.g. "me") */ displayLabel?: string; /** Called when user clicks "View contact" - receives the contact and email */ onViewContact?: (contact: ContactCard | null, email: string) => void; className?: string; } export function RecipientPopover({ name, email, displayLabel, onViewContact, className }: RecipientPopoverProps) { const [isOpen, setIsOpen] = useState(false); const [position, setPosition] = useState<{ top: number; left: number } | null>(null); const triggerRef = useRef(null); const popoverRef = useRef(null); const contacts = useContactStore((s) => s.contacts); // Find matching contact by email const contact = contacts.find((c) => { if (!c.emails) return false; return Object.values(c.emails).some( (e) => e.address.toLowerCase() === email.toLowerCase() ); }); const contactName = contact ? getContactDisplayName(contact) : name; const emails = contact?.emails ? Object.values(contact.emails) : []; const phones = contact?.phones ? Object.values(contact.phones) : []; const orgs = contact?.organizations ? Object.values(contact.organizations) : []; const handleToggle = () => { if (isOpen) { handleClose(); return; } if (!triggerRef.current) return; const rect = triggerRef.current.getBoundingClientRect(); const popoverWidth = 300; const popoverHeight = 250; let top = rect.bottom + 4; let left = rect.left; // Keep within viewport if (left + popoverWidth > window.innerWidth - 8) { left = window.innerWidth - popoverWidth - 8; } if (left < 8) left = 8; if (top + popoverHeight > window.innerHeight - 8) { top = rect.top - popoverHeight - 4; } setPosition({ top, left }); setIsOpen(true); }; const handleClose = () => { setIsOpen(false); setPosition(null); }; // Close on outside click useEffect(() => { if (!isOpen) return; const handler = (e: MouseEvent) => { if ( popoverRef.current && !popoverRef.current.contains(e.target as Node) && triggerRef.current && !triggerRef.current.contains(e.target as Node) ) { handleClose(); } }; document.addEventListener("mousedown", handler); return () => document.removeEventListener("mousedown", handler); }, [isOpen]); // Close on Escape useEffect(() => { if (!isOpen) return; const handler = (e: KeyboardEvent) => { if (e.key === "Escape") handleClose(); }; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); }, [isOpen]); // Close on scroll so the popover does not float while content moves. useEffect(() => { if (!isOpen) return; const handler = () => handleClose(); document.addEventListener("scroll", handler, true); window.addEventListener("resize", handler); return () => { document.removeEventListener("scroll", handler, true); window.removeEventListener("resize", handler); }; }, [isOpen]); const handleViewContact = () => { if (onViewContact) { onViewContact(contact ?? null, email); } handleClose(); }; const handleCopyEmail = async (addr: string) => { try { await navigator.clipboard.writeText(addr); toast.success("Copied!"); } catch { toast.error("Failed to copy"); } }; return ( <> {isOpen && position && createPortal(
{/* Header with avatar and name */}
{contactName || email}
{contactName && contactName !== email && (
{email}
)} {orgs.length > 0 && orgs[0].name && (
{orgs[0].name}
)}
{/* Contact details */}
{/* Show additional emails if contact has them */} {emails.length > 1 && (
{emails.slice(1).map((e, i) => (
{e.address}
))}
)} {/* Phone numbers */} {phones.length > 0 && (
{phones.map((p, i) => ( ))}
)}
{/* Actions */}
Email {onViewContact && ( )}
, document.body )} ); }