fix(frontend): save button always disabled on System Prompts page
Root cause: PromptEditor useEffect synced localPrompts back to match prompts after every keystroke, making isDirty() always false.
- Delegate disabled control to parent via hasChanges prop (no local sync)
- Derive currentPrompts synchronously to avoid empty-textarea flash
- Add key={selectedProfile} for clean remount on profile switch
- Update PromptEditor tests for new hasChanges prop
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
9f41a328e3
commit
0d3e8ce0ce
|
|
@ -1,9 +1,10 @@
|
|||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { RotateCcw, AlertTriangle } from 'lucide-react'
|
||||
|
||||
export interface PromptEditorProps {
|
||||
profileName: string
|
||||
prompts: Record<string, string>
|
||||
hasChanges: boolean
|
||||
isSaving: boolean
|
||||
onUpdate: (step: string, template: string) => void
|
||||
onSave: () => void
|
||||
|
|
@ -33,6 +34,7 @@ const findUnknownPlaceholders = (template: string, stepKey: string): string[] =>
|
|||
export const PromptEditor: React.FC<PromptEditorProps> = ({
|
||||
profileName,
|
||||
prompts,
|
||||
hasChanges,
|
||||
isSaving,
|
||||
onUpdate,
|
||||
onSave,
|
||||
|
|
@ -40,9 +42,9 @@ export const PromptEditor: React.FC<PromptEditorProps> = ({
|
|||
onResetAll,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [localPrompts, setLocalPrompts] = useState<Record<string, string>>({})
|
||||
const [localPrompts, setLocalPrompts] = useState<Record<string, string>>({ ...prompts })
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
setLocalPrompts({ ...prompts })
|
||||
}, [prompts])
|
||||
|
||||
|
|
@ -63,10 +65,6 @@ export const PromptEditor: React.FC<PromptEditorProps> = ({
|
|||
}
|
||||
}
|
||||
|
||||
const isDirty = useCallback(() => {
|
||||
return STEPS.some((step) => localPrompts[step.key] !== prompts[step.key])
|
||||
}, [localPrompts, prompts])
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-lg border border-gray-200 shadow-sm p-6 space-y-6">
|
||||
<div className="flex items-center gap-2 border-b border-gray-200 pb-3">
|
||||
|
|
@ -136,7 +134,7 @@ export const PromptEditor: React.FC<PromptEditorProps> = ({
|
|||
<button
|
||||
type="button"
|
||||
onClick={onSave}
|
||||
disabled={isSaving || !isDirty()}
|
||||
disabled={isSaving || !hasChanges}
|
||||
className="px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600 transition-all duration-200"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Changes'}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,9 @@ export const SystemPromptsPage: React.FC = () => {
|
|||
setEditPrompts(profileData.prompts)
|
||||
setHasChanges(false)
|
||||
}
|
||||
}, [profileData])
|
||||
}, [profileData]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
const currentPrompts = hasChanges ? editPrompts : (profileData?.prompts ?? {})
|
||||
|
||||
const handleSelectProfile = (name: string) => {
|
||||
if (selectedProfile === name) {
|
||||
|
|
@ -214,8 +216,10 @@ export const SystemPromptsPage: React.FC = () => {
|
|||
<>
|
||||
<PlaceholderDocs />
|
||||
<PromptEditor
|
||||
key={selectedProfile}
|
||||
profileName={selectedProfile}
|
||||
prompts={editPrompts}
|
||||
prompts={currentPrompts}
|
||||
hasChanges={hasChanges}
|
||||
isSaving={isSaving}
|
||||
onUpdate={handleUpdate}
|
||||
onSave={handleSave}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const mockPrompts: Record<string, string> = {
|
|||
const defaultProps = {
|
||||
profileName: 'A',
|
||||
prompts: mockPrompts,
|
||||
hasChanges: false,
|
||||
isSaving: false,
|
||||
onUpdate: vi.fn(),
|
||||
onSave: vi.fn(),
|
||||
|
|
@ -85,11 +86,7 @@ describe('PromptEditor', () => {
|
|||
})
|
||||
|
||||
it('calls onSave when "Save Changes" button is clicked', () => {
|
||||
render(<PromptEditor {...defaultProps} />)
|
||||
|
||||
// The Save button is disabled when not dirty, so we need to change something first
|
||||
const textareas = screen.getAllByRole('textbox')
|
||||
fireEvent.change(textareas[0], { target: { value: 'Modified decompose template' } })
|
||||
render(<PromptEditor {...defaultProps} hasChanges={true} />)
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: /save changes/i })
|
||||
expect(saveButton).not.toBeDisabled()
|
||||
|
|
|
|||
Loading…
Reference in New Issue