From 398a4fe23785336993f4a154e13517973e9fba14 Mon Sep 17 00:00:00 2001 From: Alex Rouse Date: Tue, 17 Jun 2025 19:06:06 -0400 Subject: [PATCH] feat(ui): Add confirmation dialog for unsaved changes in extension modal (#2971) Co-authored-by: Alex Rouse --- .../extensions/modal/EnvVarsSection.tsx | 8 + .../extensions/modal/ExtensionModal.tsx | 185 ++++++++++++------ 2 files changed, 138 insertions(+), 55 deletions(-) diff --git a/ui/desktop/src/components/settings/extensions/modal/EnvVarsSection.tsx b/ui/desktop/src/components/settings/extensions/modal/EnvVarsSection.tsx index 065acaa8..e0cdda1c 100644 --- a/ui/desktop/src/components/settings/extensions/modal/EnvVarsSection.tsx +++ b/ui/desktop/src/components/settings/extensions/modal/EnvVarsSection.tsx @@ -10,6 +10,7 @@ interface EnvVarsSectionProps { onRemove: (index: number) => void; onChange: (index: number, field: 'key' | 'value', value: string) => void; submitAttempted: boolean; + onPendingInputChange?: (hasPendingInput: boolean) => void; } export default function EnvVarsSection({ @@ -18,6 +19,7 @@ export default function EnvVarsSection({ onRemove, onChange, submitAttempted, + onPendingInputChange, }: EnvVarsSectionProps) { const [newKey, setNewKey] = React.useState(''); const [newValue, setNewValue] = React.useState(''); @@ -27,6 +29,12 @@ export default function EnvVarsSection({ value: false, }); + // Notify parent when pending input changes + React.useEffect(() => { + const hasPendingInput = newKey.trim() !== '' || newValue.trim() !== ''; + onPendingInputChange?.(hasPendingInput); + }, [newKey, newValue, onPendingInputChange]); + const handleAdd = () => { const keyEmpty = !newKey.trim(); const valueEmpty = !newValue.trim(); diff --git a/ui/desktop/src/components/settings/extensions/modal/ExtensionModal.tsx b/ui/desktop/src/components/settings/extensions/modal/ExtensionModal.tsx index 3326219f..0cfb0dd4 100644 --- a/ui/desktop/src/components/settings/extensions/modal/ExtensionModal.tsx +++ b/ui/desktop/src/components/settings/extensions/modal/ExtensionModal.tsx @@ -8,6 +8,7 @@ import { PlusIcon, Edit, Trash2, AlertTriangle } from 'lucide-react'; import ExtensionInfoFields from './ExtensionInfoFields'; import ExtensionTimeoutField from './ExtensionTimeoutField'; import { upsertConfig } from '../../../../api/sdk.gen'; +import { ConfirmationModal } from '../../../ui/ConfirmationModal'; interface ExtensionModalProps { title: string; @@ -31,6 +32,65 @@ export default function ExtensionModal({ const [formData, setFormData] = useState(initialData); const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false); const [submitAttempted, setSubmitAttempted] = useState(false); + const [showCloseConfirmation, setShowCloseConfirmation] = useState(false); + const [hasPendingEnvVars, setHasPendingEnvVars] = useState(false); + + // Function to check if form has been modified + const hasFormChanges = (): boolean => { + // Check if command/endpoint has changed + const commandChanged = + (formData.type === 'stdio' && formData.cmd !== initialData.cmd) || + (formData.type === 'sse' && formData.endpoint !== initialData.endpoint); + + // Check if any environment variables have been modified + const envVarsChanged = formData.envVars.some((envVar) => envVar.isEdited === true); + + // Check if new env vars have been added + const envVarsAdded = formData.envVars.length > initialData.envVars.length; + + // Check if env vars have been removed + const envVarsRemoved = formData.envVars.length < initialData.envVars.length; + + // Check if any environment variable fields have text entered (even if not marked as edited) + const envVarsHaveText = formData.envVars.some( + (envVar) => + (envVar.key.trim() !== '' || envVar.value.trim() !== '') && + // Don't count placeholder values for existing secrets + envVar.value !== '••••••••' + ); + + // Check if there are pending environment variables being typed + const hasPendingInput = hasPendingEnvVars; + + return ( + commandChanged || + envVarsChanged || + envVarsAdded || + envVarsRemoved || + envVarsHaveText || + hasPendingInput + ); + }; + + // Handle backdrop close with confirmation if needed + const handleBackdropClose = () => { + if (hasFormChanges()) { + setShowCloseConfirmation(true); + } else { + onClose(); + } + }; + + // Handle confirmed close + const handleConfirmClose = () => { + setShowCloseConfirmation(false); + onClose(); + }; + + // Handle cancel close confirmation + const handleCancelClose = () => { + setShowCloseConfirmation(false); + }; const handleAddEnvVar = (key: string, value: string) => { setFormData({ @@ -208,7 +268,7 @@ export default function ExtensionModal({ {submitLabel}