import { useEffect, useRef, useState } from 'react'; import { Bell, Globe2, Info, Route, Star, Trash2 } from 'lucide-react'; import { toast } from './ui/sonner'; import { DirectTraceIcon } from './DirectTraceIcon'; import { ContactPathDiscoveryModal } from './ContactPathDiscoveryModal'; import { ChannelFloodScopeOverrideModal } from './ChannelFloodScopeOverrideModal'; import { isFavorite } from '../utils/favorites'; import { handleKeyboardActivate } from '../utils/a11y'; import { isPublicChannelKey } from '../utils/publicChannel'; import { stripRegionScopePrefix } from '../utils/regionScope'; import { isPrefixOnlyContact } from '../utils/pubkey'; import { ContactAvatar } from './ContactAvatar'; import { ContactStatusInfo } from './ContactStatusInfo'; import type { Channel, Contact, Conversation, Favorite, PathDiscoveryResponse, RadioConfig, } from '../types'; interface ChatHeaderProps { conversation: Conversation; contacts: Contact[]; channels: Channel[]; config: RadioConfig | null; favorites: Favorite[]; notificationsSupported: boolean; notificationsEnabled: boolean; notificationsPermission: NotificationPermission | 'unsupported'; onTrace: () => void; onPathDiscovery: (publicKey: string) => Promise; onToggleNotifications: () => void; onToggleFavorite: (type: 'channel' | 'contact', id: string) => void; onSetChannelFloodScopeOverride?: (key: string, floodScopeOverride: string) => void; onDeleteChannel: (key: string) => void; onDeleteContact: (publicKey: string) => void; onOpenContactInfo?: (publicKey: string) => void; onOpenChannelInfo?: (channelKey: string) => void; } export function ChatHeader({ conversation, contacts, channels, config, favorites, notificationsSupported, notificationsEnabled, notificationsPermission, onTrace, onPathDiscovery, onToggleNotifications, onToggleFavorite, onSetChannelFloodScopeOverride, onDeleteChannel, onDeleteContact, onOpenContactInfo, onOpenChannelInfo, }: ChatHeaderProps) { const [showKey, setShowKey] = useState(false); const [contactStatusInline, setContactStatusInline] = useState(true); const [pathDiscoveryOpen, setPathDiscoveryOpen] = useState(false); const [channelOverrideOpen, setChannelOverrideOpen] = useState(false); const keyTextRef = useRef(null); useEffect(() => { setShowKey(false); setPathDiscoveryOpen(false); setChannelOverrideOpen(false); }, [conversation.id]); const activeChannel = conversation.type === 'channel' ? channels.find((channel) => channel.key === conversation.id) : undefined; const activeFloodScopeOverride = conversation.type === 'channel' ? (activeChannel?.flood_scope_override ?? null) : null; const activeFloodScopeLabel = activeFloodScopeOverride ? stripRegionScopePrefix(activeFloodScopeOverride) : null; const activeFloodScopeDisplay = activeFloodScopeOverride ? activeFloodScopeOverride : null; const isPrivateChannel = conversation.type === 'channel' && !activeChannel?.is_hashtag; const activeContact = conversation.type === 'contact' ? contacts.find((contact) => contact.public_key === conversation.id) : null; const activeContactIsPrefixOnly = activeContact ? isPrefixOnlyContact(activeContact.public_key) : false; const titleClickable = (conversation.type === 'contact' && onOpenContactInfo) || (conversation.type === 'channel' && onOpenChannelInfo); const favoriteTitle = conversation.type === 'contact' ? isFavorite(favorites, 'contact', conversation.id) ? 'Remove from favorites. Favorite contacts stay loaded on the radio for ACK support.' : 'Add to favorites. Favorite contacts stay loaded on the radio for ACK support.' : isFavorite(favorites, conversation.type as 'channel' | 'contact', conversation.id) ? 'Remove from favorites' : 'Add to favorites'; const handleEditFloodScopeOverride = () => { if (conversation.type !== 'channel' || !onSetChannelFloodScopeOverride) return; setChannelOverrideOpen(true); }; const handleOpenConversationInfo = () => { if (conversation.type === 'contact' && onOpenContactInfo) { onOpenContactInfo(conversation.id); return; } if (conversation.type === 'channel' && onOpenChannelInfo) { onOpenChannelInfo(conversation.id); } }; useEffect(() => { if (conversation.type !== 'contact') { setContactStatusInline(true); return; } const measure = () => { const keyElement = keyTextRef.current; if (!keyElement) return; const isTruncated = keyElement.scrollWidth > keyElement.clientWidth + 1; setContactStatusInline(!isTruncated); }; measure(); const onResize = () => { window.requestAnimationFrame(measure); }; window.addEventListener('resize', onResize); let observer: ResizeObserver | null = null; if (typeof ResizeObserver !== 'undefined') { observer = new ResizeObserver(() => { window.requestAnimationFrame(measure); }); if (keyTextRef.current?.parentElement) { observer.observe(keyTextRef.current.parentElement); } } return () => { window.removeEventListener('resize', onResize); observer?.disconnect(); }; }, [conversation.id, conversation.type, showKey]); return (
{conversation.type === 'contact' && onOpenContactInfo && ( )}

{titleClickable ? ( ) : ( {conversation.type === 'channel' && !conversation.name.startsWith('#') && activeChannel?.is_hashtag ? '#' : ''} {conversation.name} )}

{isPrivateChannel && !showKey ? ( ) : ( { e.stopPropagation(); navigator.clipboard.writeText(conversation.id); toast.success( conversation.type === 'channel' ? 'Room key copied!' : 'Contact key copied!' ); }} title="Click to copy" aria-label={ conversation.type === 'channel' ? 'Copy channel key' : 'Copy contact key' } > {conversation.type === 'channel' ? conversation.id.toLowerCase() : conversation.id} )}
{conversation.type === 'contact' && activeContact && contactStatusInline && ( )}
{conversation.type === 'contact' && activeContact && !contactStatusInline && ( )} {conversation.type === 'channel' && activeFloodScopeDisplay && ( )}
{conversation.type === 'contact' && ( )} {conversation.type === 'contact' && ( )} {notificationsSupported && ( )} {conversation.type === 'channel' && onSetChannelFloodScopeOverride && ( )} {(conversation.type === 'channel' || conversation.type === 'contact') && ( )} {!(conversation.type === 'channel' && isPublicChannelKey(conversation.id)) && ( )}
{conversation.type === 'contact' && activeContact && ( setPathDiscoveryOpen(false)} contact={activeContact} contacts={contacts} radioName={config?.name ?? null} onDiscover={onPathDiscovery} /> )} {conversation.type === 'channel' && onSetChannelFloodScopeOverride && ( setChannelOverrideOpen(false)} roomName={conversation.name} currentOverride={activeFloodScopeDisplay} onSetOverride={(value) => onSetChannelFloodScopeOverride(conversation.id, value)} /> )}
); }