import { useEffect, useRef, useState } from 'react'; import { Bell, Globe2, Info, Star, Trash2 } from 'lucide-react'; import { toast } from './ui/sonner'; import { DirectTraceIcon } from './DirectTraceIcon'; import { isFavorite } from '../utils/favorites'; import { handleKeyboardActivate } from '../utils/a11y'; import { stripRegionScopePrefix } from '../utils/regionScope'; import { isPrefixOnlyContact } from '../utils/pubkey'; import { ContactAvatar } from './ContactAvatar'; import { ContactStatusInfo } from './ContactStatusInfo'; import type { Channel, Contact, Conversation, Favorite, 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; 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, onToggleNotifications, onToggleFavorite, onSetChannelFloodScopeOverride, onDeleteChannel, onDeleteContact, onOpenContactInfo, onOpenChannelInfo, }: ChatHeaderProps) { const [showKey, setShowKey] = useState(false); const [contactStatusInline, setContactStatusInline] = useState(true); const keyTextRef = useRef(null); useEffect(() => { setShowKey(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; const nextValue = window.prompt( 'Enter regional override flood scope for this room. This temporarily changes the radio flood scope before send and restores it after, which significantly slows room sends. Leave blank to clear.', activeFloodScopeLabel ?? '' ); if (nextValue === null) return; onSetChannelFloodScopeOverride(conversation.id, nextValue); }; 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' && ( )} {notificationsSupported && ( )} {conversation.type === 'channel' && onSetChannelFloodScopeOverride && ( )} {(conversation.type === 'channel' || conversation.type === 'contact') && ( )} {!(conversation.type === 'channel' && conversation.name === 'Public') && ( )}
); }