fix(ui): error-handle fire-and-forget mutations in settings panels

This commit is contained in:
Gabriel Brown
2026-07-11 16:59:16 -04:00
parent 8c579d060a
commit bca86196fb
5 changed files with 185 additions and 34 deletions
@@ -285,10 +285,18 @@ export const AiProviderProfilesPanel = () => {
<Select
value={defaultProfile?._id ?? ''}
onValueChange={async (value) => {
await setDefaultProfile({
profileId: value as Id<'aiProviderProfiles'>,
});
toast.success('Default AI provider updated.');
try {
await setDefaultProfile({
profileId: value as Id<'aiProviderProfiles'>,
});
toast.success('Default AI provider updated.');
} catch (error) {
toast.error(
error instanceof Error
? error.message
: 'Could not update default AI provider.',
);
}
}}
>
<SelectTrigger>
@@ -332,8 +340,16 @@ export const AiProviderProfilesPanel = () => {
size='icon'
aria-label='Remove provider'
onClick={async () => {
await removeProfile({ profileId: profile._id });
toast.success('AI provider removed.');
try {
await removeProfile({ profileId: profile._id });
toast.success('AI provider removed.');
} catch (error) {
toast.error(
error instanceof Error
? error.message
: 'Could not remove AI provider.',
);
}
}}
>
<Trash2 className='size-4' />
@@ -160,13 +160,17 @@ export const DotfilesManager = () => {
const saveSelected = async (next: string) => {
if (!selected) return;
await putFile({
path: selected.path,
content: next,
isExecutable: selected.isExecutable,
});
setSavedContent(next);
toast.success('Saved.');
try {
await putFile({
path: selected.path,
content: next,
isExecutable: selected.isExecutable,
});
setSavedContent(next);
toast.success('Saved.');
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Save failed.');
}
};
const importAll = async (incoming: UploadFile[]) => {
@@ -231,11 +235,15 @@ export const DotfilesManager = () => {
const deleteSelected = async () => {
if (!selected) return;
await removeFile({ fileId: selected._id as never });
setSelected(undefined);
setContent('');
setSavedContent('');
toast.success('Deleted.');
try {
await removeFile({ fileId: selected._id as never });
setSelected(undefined);
setContent('');
setSavedContent('');
toast.success('Deleted.');
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Delete failed.');
}
};
return (
@@ -345,8 +353,12 @@ export const DotfilesManager = () => {
<RepoPanel
settings={settings}
onSave={async (values) => {
await updateEnv(values);
toast.success('Saved.');
try {
await updateEnv(values);
toast.success('Saved.');
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Save failed.');
}
}}
/>
@@ -223,8 +223,16 @@ export const SpoonClonePanel = ({ spoon }: { spoon: Doc<'spoons'> }) => {
remoteName={remote.remoteName}
url={remote.url}
onRemove={async () => {
await removeRemote({ remoteId: remote._id });
toast.success('Remote removed.');
try {
await removeRemote({ remoteId: remote._id });
toast.success('Remote removed.');
} catch (error) {
toast.error(
error instanceof Error
? error.message
: 'Could not remove remote.',
);
}
}}
/>
))}
@@ -130,24 +130,35 @@ export const SpoonSecretsForm = ({ spoonId }: { spoonId: Id<'spoons'> }) => {
return;
}
setBulkSaving(true);
try {
for (const secret of parsedBulk.secrets) {
let imported = 0;
const failed: string[] = [];
for (const secret of parsedBulk.secrets) {
try {
await createSecret({
spoonId,
name: secret.name,
value: secret.value,
description: 'Imported from .env',
});
imported += 1;
} catch (error) {
console.error(error);
failed.push(secret.name);
}
setBulkText('');
setBulkDialogOpen(false);
toast.success(`${parsedBulk.secrets.length} secrets imported.`);
} catch (error) {
console.error(error);
toast.error('Could not import secrets.');
} finally {
setBulkSaving(false);
}
setBulkSaving(false);
if (imported > 0) {
toast.success(`${imported} secret${imported === 1 ? '' : 's'} imported.`);
}
if (failed.length) {
toast.error(
`Could not import ${failed.length} secret${failed.length === 1 ? '' : 's'}: ${failed.join(', ')}`,
);
return;
}
setBulkText('');
setBulkDialogOpen(false);
};
const readEnvFile = async (file?: File) => {
@@ -310,8 +321,16 @@ export const SpoonSecretsForm = ({ spoonId }: { spoonId: Id<'spoons'> }) => {
size='icon'
aria-label={`Remove ${secret.name}`}
onClick={async () => {
await removeSecret({ secretId: secret._id });
toast.success('Secret removed.');
try {
await removeSecret({ secretId: secret._id });
toast.success('Secret removed.');
} catch (error) {
toast.error(
error instanceof Error
? error.message
: 'Could not remove secret.',
);
}
}}
>
<Trash2 className='size-4' />