From 68f05075ca09d42ab688dc577ca1ab5137f3578b Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Fri, 13 Mar 2026 18:11:18 -0700 Subject: [PATCH] Modal-ify the room region override --- .../ChannelFloodScopeOverrideModal.tsx | 105 ++++++++++++++++++ frontend/src/components/ChatHeader.tsx | 19 +++- .../src/test/chatHeaderKeyVisibility.test.tsx | 9 +- 3 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/ChannelFloodScopeOverrideModal.tsx diff --git a/frontend/src/components/ChannelFloodScopeOverrideModal.tsx b/frontend/src/components/ChannelFloodScopeOverrideModal.tsx new file mode 100644 index 0000000..be8b097 --- /dev/null +++ b/frontend/src/components/ChannelFloodScopeOverrideModal.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from 'react'; + +import { stripRegionScopePrefix } from '../utils/regionScope'; +import { Button } from './ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from './ui/dialog'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; + +interface ChannelFloodScopeOverrideModalProps { + open: boolean; + onClose: () => void; + roomName: string; + currentOverride: string | null; + onSetOverride: (value: string) => void; +} + +export function ChannelFloodScopeOverrideModal({ + open, + onClose, + roomName, + currentOverride, + onSetOverride, +}: ChannelFloodScopeOverrideModalProps) { + const [region, setRegion] = useState(''); + + useEffect(() => { + if (!open) { + return; + } + setRegion(stripRegionScopePrefix(currentOverride)); + }, [currentOverride, open]); + + const trimmedRegion = region.trim(); + + return ( + !isOpen && onClose()}> + + + Regional Override + + Room-level regional routing temporarily changes the radio flood scope before send and + restores it after. This can noticeably slow room sends. + + + +
+
+
{roomName}
+
+ Current regional override:{' '} + {currentOverride ? stripRegionScopePrefix(currentOverride) : 'none'} +
+
+ +
+ + setRegion(event.target.value)} + placeholder="Esperance" + autoFocus + /> +
+
+ + +
+ + +
+
+
+
+ ); +} diff --git a/frontend/src/components/ChatHeader.tsx b/frontend/src/components/ChatHeader.tsx index 1d5b247..8cf857e 100644 --- a/frontend/src/components/ChatHeader.tsx +++ b/frontend/src/components/ChatHeader.tsx @@ -3,6 +3,7 @@ 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 { stripRegionScopePrefix } from '../utils/regionScope'; @@ -60,11 +61,13 @@ export function ChatHeader({ 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 = @@ -100,12 +103,7 @@ export function ChatHeader({ 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); + setChannelOverrideOpen(true); }; const handleOpenConversationInfo = () => { @@ -408,6 +406,15 @@ export function ChatHeader({ onDiscover={onPathDiscovery} /> )} + {conversation.type === 'channel' && onSetChannelFloodScopeOverride && ( + setChannelOverrideOpen(false)} + roomName={conversation.name} + currentOverride={activeFloodScopeDisplay} + onSetOverride={(value) => onSetChannelFloodScopeOverride(conversation.id, value)} + /> + )} ); } diff --git a/frontend/src/test/chatHeaderKeyVisibility.test.tsx b/frontend/src/test/chatHeaderKeyVisibility.test.tsx index 43b9769..29af2f8 100644 --- a/frontend/src/test/chatHeaderKeyVisibility.test.tsx +++ b/frontend/src/test/chatHeaderKeyVisibility.test.tsx @@ -252,12 +252,11 @@ describe('ChatHeader key visibility', () => { expect(screen.getByText(/clearing the forced route afterward is enough/i)).toBeInTheDocument(); }); - it('prompts for regional override when globe button is clicked', () => { + it('opens the regional override modal and applies the entered region', async () => { const key = 'CD'.repeat(16); const channel = makeChannel(key, '#flightless', true); const conversation: Conversation = { type: 'channel', id: key, name: '#flightless' }; const onSetChannelFloodScopeOverride = vi.fn(); - const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue('Esperance'); render( { fireEvent.click(screen.getByTitle('Set regional override')); - expect(promptSpy).toHaveBeenCalled(); + expect(await screen.findByRole('dialog')).toBeInTheDocument(); + fireEvent.change(screen.getByLabelText('Region'), { target: { value: 'Esperance' } }); + fireEvent.click(screen.getByRole('button', { name: 'Use Esperance region for #flightless' })); + expect(onSetChannelFloodScopeOverride).toHaveBeenCalledWith(key, 'Esperance'); - promptSpy.mockRestore(); }); });