import { useEffect, 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 { cn } from '../lib/utils'; import { ContactAvatar } from './ContactAvatar'; import { ContactStatusInfo } from './ContactStatusInfo'; import type { Channel, Contact, Conversation, Favorite, PathDiscoveryResponse, RadioConfig, } from '../types'; import { CONTACT_TYPE_ROOM } 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 [pathDiscoveryOpen, setPathDiscoveryOpen] = useState(false); const [channelOverrideOpen, setChannelOverrideOpen] = useState(false); 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 activeContactIsRoomServer = activeContact?.type === CONTACT_TYPE_ROOM; 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); } }; 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' ? 'Channel 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 === 'channel' && activeFloodScopeDisplay && ( )}
{conversation.type === 'contact' && activeContact && (
)}
{conversation.type === 'contact' && !activeContactIsRoomServer && ( )} {conversation.type === 'contact' && !activeContactIsRoomServer && ( )} {notificationsSupported && !activeContactIsRoomServer && ( )} {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)} /> )}
); }