Modal-ify the room region override

This commit is contained in:
Jack Kingsman
2026-03-13 18:11:18 -07:00
parent adfb8c930c
commit 68f05075ca
3 changed files with 123 additions and 10 deletions

View File

@@ -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 (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent className="sm:max-w-[520px]">
<DialogHeader>
<DialogTitle>Regional Override</DialogTitle>
<DialogDescription>
Room-level regional routing temporarily changes the radio flood scope before send and
restores it after. This can noticeably slow room sends.
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="rounded-md border border-border bg-muted/20 p-3 text-sm">
<div className="font-medium">{roomName}</div>
<div className="mt-1 text-muted-foreground">
Current regional override:{' '}
{currentOverride ? stripRegionScopePrefix(currentOverride) : 'none'}
</div>
</div>
<div className="space-y-2">
<Label htmlFor="channel-region-input">Region</Label>
<Input
id="channel-region-input"
value={region}
onChange={(event) => setRegion(event.target.value)}
placeholder="Esperance"
autoFocus
/>
</div>
</div>
<DialogFooter className="gap-2 sm:block sm:space-x-0">
<div className="space-y-2">
<Button
type="button"
className="w-full"
disabled={trimmedRegion.length === 0}
onClick={() => {
onSetOverride(trimmedRegion);
onClose();
}}
>
{trimmedRegion.length > 0
? `Use ${trimmedRegion} region for ${roomName}`
: `Use region for ${roomName}`}
</Button>
<Button
type="button"
variant="outline"
className="w-full"
onClick={() => {
onSetOverride('');
onClose();
}}
>
Do not use region routing for {roomName}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@@ -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<HTMLSpanElement | null>(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 && (
<ChannelFloodScopeOverrideModal
open={channelOverrideOpen}
onClose={() => setChannelOverrideOpen(false)}
roomName={conversation.name}
currentOverride={activeFloodScopeDisplay}
onSetOverride={(value) => onSetChannelFloodScopeOverride(conversation.id, value)}
/>
)}
</header>
);
}

View File

@@ -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(
<ChatHeader
@@ -270,8 +269,10 @@ describe('ChatHeader key visibility', () => {
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();
});
});