diff --git a/ui/src/components/profiles/editor/index.tsx b/ui/src/components/profiles/editor/index.tsx index 01fc8b5b..da5f98c2 100644 --- a/ui/src/components/profiles/editor/index.tsx +++ b/ui/src/components/profiles/editor/index.tsx @@ -4,7 +4,7 @@ */ /* eslint-disable react-refresh/only-export-components */ -import { useState, useMemo, useCallback } from 'react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@/components/ui/button'; import { ConfirmDialog } from '@/components/shared/confirm-dialog'; @@ -16,7 +16,7 @@ import { FriendlyUISection } from './friendly-ui-section'; import { RawEditorSection } from './raw-editor-section'; import type { ProfileEditorProps, Settings, SettingsResponse } from './types'; -export function ProfileEditor({ profileName, onDelete }: ProfileEditorProps) { +export function ProfileEditor({ profileName, onDelete, onHasChangesUpdate }: ProfileEditorProps) { const [localEdits, setLocalEdits] = useState>({}); const [conflictDialog, setConflictDialog] = useState(false); const [rawJsonEdits, setRawJsonEdits] = useState(null); @@ -100,6 +100,11 @@ export function ProfileEditor({ profileName, onDelete }: ProfileEditorProps) { return Object.keys(localEdits).length > 0; }, [rawJsonEdits, localEdits, settings]); + // Notify parent of hasChanges state + useEffect(() => { + onHasChangesUpdate?.(computedHasChanges); + }, [computedHasChanges, onHasChangesUpdate]); + // Save mutation const saveMutation = useMutation({ mutationFn: async () => { diff --git a/ui/src/components/profiles/editor/types.ts b/ui/src/components/profiles/editor/types.ts index d7e36a13..950ae645 100644 --- a/ui/src/components/profiles/editor/types.ts +++ b/ui/src/components/profiles/editor/types.ts @@ -16,4 +16,5 @@ export interface SettingsResponse { export interface ProfileEditorProps { profileName: string; onDelete?: () => void; + onHasChangesUpdate?: (hasChanges: boolean) => void; } diff --git a/ui/src/pages/api.tsx b/ui/src/pages/api.tsx index 53671c79..dce6f8c7 100644 --- a/ui/src/pages/api.tsx +++ b/ui/src/pages/api.tsx @@ -37,6 +37,8 @@ export function ApiPage() { const [isCreateDialogOpen, setCreateDialogOpen] = useState(false); const [createMode, setCreateMode] = useState<'normal' | 'openrouter'>('normal'); const [deleteConfirm, setDeleteConfirm] = useState(null); + const [editorHasChanges, setEditorHasChanges] = useState(false); + const [pendingSwitch, setPendingSwitch] = useState(null); // Prefetch OpenRouter models when page loads (lazy - won't block render) useOpenRouterModels(); @@ -71,7 +73,21 @@ export function ApiPage() { // Handle create success const handleCreateSuccess = (name: string) => { setCreateDialogOpen(false); - setSelectedProfile(name); + // Use the same unsaved changes check as profile selection + if (editorHasChanges && selectedProfile !== null) { + setPendingSwitch(name); + } else { + setSelectedProfile(name); + } + }; + + // Handle profile selection with unsaved changes check + const handleProfileSelect = (name: string) => { + if (editorHasChanges && selectedProfile !== name) { + setPendingSwitch(name); + } else { + setSelectedProfile(name); + } }; return ( @@ -168,9 +184,7 @@ export function ApiPage() { key={profile.name} profile={profile} isSelected={selectedProfile === profile.name} - onSelect={() => { - setSelectedProfile(profile.name); - }} + onSelect={() => handleProfileSelect(profile.name)} onDelete={() => setDeleteConfirm(profile.name)} /> ))} @@ -208,6 +222,7 @@ export function ApiPage() { setDeleteConfirm(selectedProfileData.name)} + onHasChangesUpdate={setEditorHasChanges} /> ) : ( deleteConfirm && handleDelete(deleteConfirm)} onCancel={() => setDeleteConfirm(null)} /> + + {/* Unsaved Changes Confirmation */} + { + setSelectedProfile(pendingSwitch); + setPendingSwitch(null); + }} + onCancel={() => setPendingSwitch(null)} + /> ); }