From 85eb5385e93cc82d662344cee8cbc93eb246ce04 Mon Sep 17 00:00:00 2001 From: Jona Schwarz Date: Fri, 10 Apr 2026 16:18:25 +0200 Subject: [PATCH] feat(project-settings): improve save button feedback states --- .../components/project-settings-form.tsx | 57 +++++++++++++------ .../features/projects/view/settings-panel.tsx | 4 +- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/renderer/features/projects/components/project-settings-form.tsx b/src/renderer/features/projects/components/project-settings-form.tsx index 4706cc1e6..194072ae7 100644 --- a/src/renderer/features/projects/components/project-settings-form.tsx +++ b/src/renderer/features/projects/components/project-settings-form.tsx @@ -1,5 +1,5 @@ -import { GitBranch } from 'lucide-react'; -import { useEffect, useMemo, useState } from 'react'; +import { Check, GitBranch, Loader2, Undo2 } from 'lucide-react'; +import { useMemo, useState } from 'react'; import type { Branch } from '@shared/git'; import type { ProjectSettings } from '@main/core/projects/settings/schema'; import { useBranches } from '@renderer/features/projects/repository/use-branches'; @@ -76,35 +76,49 @@ export interface ProjectSettingsFormProps { initial: ProjectSettings; onSuccess: () => void; save: (settings: ProjectSettings) => Promise; - isSaving: boolean; } +type SaveStatus = 'idle' | 'saving' | 'saved' | 'error'; + export function ProjectSettingsForm({ projectId, initial, onSuccess, save, - isSaving, }: ProjectSettingsFormProps) { const { branches } = useBranches(projectId); const { remotes } = useRemotes(projectId); const baseline = useMemo(() => settingsToForm(initial), [initial]); const [form, setForm] = useState(baseline); + const [savedForm, setSavedForm] = useState(baseline); + const [saveStatus, setSaveStatus] = useState('idle'); - useEffect(() => { - setForm(baseline); - }, [baseline]); - - const isDirty = JSON.stringify(form) !== JSON.stringify(baseline); + const formSnapshot = useMemo(() => JSON.stringify(form), [form]); + const savedSnapshot = useMemo(() => JSON.stringify(savedForm), [savedForm]); + const dirty = formSnapshot !== savedSnapshot; + const saving = saveStatus === 'saving'; + const saved = saveStatus === 'saved' && !dirty; + const saveDisabled = saving || !dirty; function update(key: K, value: FormState[K]) { - setForm((prev) => ({ ...prev, [key]: value })); + setForm((current) => ({ ...current, [key]: value })); + setSaveStatus((current) => (current === 'idle' ? current : 'idle')); } async function handleSave() { - await save(formToSettings(form)); - onSuccess(); + const formAtSubmit = form; + + setSaveStatus('saving'); + + try { + await save(formToSettings(formAtSubmit)); + setSavedForm(formAtSubmit); + setSaveStatus('saved'); + onSuccess(); + } catch { + setSaveStatus('error'); + } } return ( @@ -265,11 +279,22 @@ export function ProjectSettingsForm({
- - void handleSave()} disabled={!isDirty || isSaving}> - {isSaving ? 'Saving…' : 'Save'} + void handleSave()} disabled={saveDisabled}> + + {saving &&
diff --git a/src/renderer/features/projects/view/settings-panel.tsx b/src/renderer/features/projects/view/settings-panel.tsx index f164244ce..ac7731a29 100644 --- a/src/renderer/features/projects/view/settings-panel.tsx +++ b/src/renderer/features/projects/view/settings-panel.tsx @@ -7,7 +7,7 @@ export function SettingsPanel() { const { params: { projectId }, } = useParams('project'); - const { settings, isLoading, save, isSaving } = useProjectSettings(projectId); + const { settings, isLoading, save } = useProjectSettings(projectId); if (isLoading || !settings) { return ( @@ -19,11 +19,11 @@ export function SettingsPanel() { return ( {}} save={save} - isSaving={isSaving} /> ); }