mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-13 08:15:17 +00:00
fix(ui): add unsaved changes confirmation when switching profiles
- Add onHasChangesUpdate callback to ProfileEditor - Track editor dirty state in ApiPage parent - Show confirmation dialog before discarding unsaved changes - Handle edge case for 'New' profile button Closes #163
This commit is contained in:
1 parent
bf65de55cf
commit
b790005c85
3 files changed
+41
-6
No files matched your search
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable react-refresh/only-export-components */
|
/* 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 { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { ConfirmDialog } from '@/components/shared/confirm-dialog';
|
import { ConfirmDialog } from '@/components/shared/confirm-dialog';
|
||||||
@@ -16,7 +16,7 @@ import { FriendlyUISection } from './friendly-ui-section';
|
|||||||
import { RawEditorSection } from './raw-editor-section';
|
import { RawEditorSection } from './raw-editor-section';
|
||||||
import type { ProfileEditorProps, Settings, SettingsResponse } from './types';
|
import type { ProfileEditorProps, Settings, SettingsResponse } from './types';
|
||||||
|
|
||||||
export function ProfileEditor({ profileName, onDelete }: ProfileEditorProps) {
|
export function ProfileEditor({ profileName, onDelete, onHasChangesUpdate }: ProfileEditorProps) {
|
||||||
const [localEdits, setLocalEdits] = useState<Record<string, string>>({});
|
const [localEdits, setLocalEdits] = useState<Record<string, string>>({});
|
||||||
const [conflictDialog, setConflictDialog] = useState(false);
|
const [conflictDialog, setConflictDialog] = useState(false);
|
||||||
const [rawJsonEdits, setRawJsonEdits] = useState<string | null>(null);
|
const [rawJsonEdits, setRawJsonEdits] = useState<string | null>(null);
|
||||||
@@ -100,6 +100,11 @@ export function ProfileEditor({ profileName, onDelete }: ProfileEditorProps) {
|
|||||||
return Object.keys(localEdits).length > 0;
|
return Object.keys(localEdits).length > 0;
|
||||||
}, [rawJsonEdits, localEdits, settings]);
|
}, [rawJsonEdits, localEdits, settings]);
|
||||||
|
|
||||||
|
// Notify parent of hasChanges state
|
||||||
|
useEffect(() => {
|
||||||
|
onHasChangesUpdate?.(computedHasChanges);
|
||||||
|
}, [computedHasChanges, onHasChangesUpdate]);
|
||||||
|
|
||||||
// Save mutation
|
// Save mutation
|
||||||
const saveMutation = useMutation({
|
const saveMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
|
|||||||
@@ -16,4 +16,5 @@ export interface SettingsResponse {
|
|||||||
export interface ProfileEditorProps {
|
export interface ProfileEditorProps {
|
||||||
profileName: string;
|
profileName: string;
|
||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
|
onHasChangesUpdate?: (hasChanges: boolean) => void;
|
||||||
}
|
}
|
||||||
+33
-4
@@ -37,6 +37,8 @@ export function ApiPage() {
|
|||||||
const [isCreateDialogOpen, setCreateDialogOpen] = useState(false);
|
const [isCreateDialogOpen, setCreateDialogOpen] = useState(false);
|
||||||
const [createMode, setCreateMode] = useState<'normal' | 'openrouter'>('normal');
|
const [createMode, setCreateMode] = useState<'normal' | 'openrouter'>('normal');
|
||||||
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
|
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
|
||||||
|
const [editorHasChanges, setEditorHasChanges] = useState(false);
|
||||||
|
const [pendingSwitch, setPendingSwitch] = useState<string | null>(null);
|
||||||
|
|
||||||
// Prefetch OpenRouter models when page loads (lazy - won't block render)
|
// Prefetch OpenRouter models when page loads (lazy - won't block render)
|
||||||
useOpenRouterModels();
|
useOpenRouterModels();
|
||||||
@@ -71,7 +73,21 @@ export function ApiPage() {
|
|||||||
// Handle create success
|
// Handle create success
|
||||||
const handleCreateSuccess = (name: string) => {
|
const handleCreateSuccess = (name: string) => {
|
||||||
setCreateDialogOpen(false);
|
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 (
|
return (
|
||||||
@@ -168,9 +184,7 @@ export function ApiPage() {
|
|||||||
key={profile.name}
|
key={profile.name}
|
||||||
profile={profile}
|
profile={profile}
|
||||||
isSelected={selectedProfile === profile.name}
|
isSelected={selectedProfile === profile.name}
|
||||||
onSelect={() => {
|
onSelect={() => handleProfileSelect(profile.name)}
|
||||||
setSelectedProfile(profile.name);
|
|
||||||
}}
|
|
||||||
onDelete={() => setDeleteConfirm(profile.name)}
|
onDelete={() => setDeleteConfirm(profile.name)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@@ -208,6 +222,7 @@ export function ApiPage() {
|
|||||||
<ProfileEditor
|
<ProfileEditor
|
||||||
profileName={selectedProfileData.name}
|
profileName={selectedProfileData.name}
|
||||||
onDelete={() => setDeleteConfirm(selectedProfileData.name)}
|
onDelete={() => setDeleteConfirm(selectedProfileData.name)}
|
||||||
|
onHasChangesUpdate={setEditorHasChanges}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<OpenRouterQuickStart
|
<OpenRouterQuickStart
|
||||||
@@ -242,6 +257,20 @@ export function ApiPage() {
|
|||||||
onConfirm={() => deleteConfirm && handleDelete(deleteConfirm)}
|
onConfirm={() => deleteConfirm && handleDelete(deleteConfirm)}
|
||||||
onCancel={() => setDeleteConfirm(null)}
|
onCancel={() => setDeleteConfirm(null)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Unsaved Changes Confirmation */}
|
||||||
|
<ConfirmDialog
|
||||||
|
open={!!pendingSwitch}
|
||||||
|
title="Unsaved Changes"
|
||||||
|
description={`You have unsaved changes in "${selectedProfile}". Discard and switch to "${pendingSwitch}"?`}
|
||||||
|
confirmText="Discard & Switch"
|
||||||
|
variant="destructive"
|
||||||
|
onConfirm={() => {
|
||||||
|
setSelectedProfile(pendingSwitch);
|
||||||
|
setPendingSwitch(null);
|
||||||
|
}}
|
||||||
|
onCancel={() => setPendingSwitch(null)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user