fix(web): integrations panel review fixes

- Trim whitespace before checking the typed-confirm value (paste-with-spaces
  was silently keeping the button disabled).
- Wrap onConfirmDisconnect in try/catch and surface disconnect_failed inline
  in the modal — operators on flaky networks now see why nothing happened.
- Invalidate quality-profile + root-folder caches alongside config on both
  save and disconnect, so a base_url change refetches the lists immediately.
This commit is contained in:
2026-04-29 23:11:32 -04:00
parent c281c8b5dd
commit b1b50187b3
+34 -13
View File
@@ -66,7 +66,13 @@
default_root_folder_path: rootPath default_root_folder_path: rootPath
}; };
await putLidarrConfig(cfg); await putLidarrConfig(cfg);
await client.invalidateQueries({ queryKey: qk.lidarrConfig() }); // Invalidate config + profile/folder lists. A new base_url means a
// different server, so the cached lists must refetch.
await Promise.all([
client.invalidateQueries({ queryKey: qk.lidarrConfig() }),
client.invalidateQueries({ queryKey: qk.lidarrQualityProfiles() }),
client.invalidateQueries({ queryKey: qk.lidarrRootFolders() })
]);
apiKeyInput = ''; apiKeyInput = '';
} catch (e) { } catch (e) {
saveError = (e as { code?: string }).code ?? 'save_failed'; saveError = (e as { code?: string }).code ?? 'save_failed';
@@ -89,28 +95,40 @@
} }
// Disconnect typed-confirm modal. Requires the literal "DISCONNECT" string // Disconnect typed-confirm modal. Requires the literal "DISCONNECT" string
// to avoid muscle-memory clearing of an integration the operator depends on. // (whitespace-trimmed) to avoid muscle-memory clearing of an integration the
// operator depends on.
let modalOpen = $state(false); let modalOpen = $state(false);
let disconnectInput = $state(''); let disconnectInput = $state('');
const canDisconnect = $derived(disconnectInput === 'DISCONNECT'); let disconnectError = $state<string | null>(null);
const canDisconnect = $derived(disconnectInput.trim() === 'DISCONNECT');
async function onConfirmDisconnect() { async function onConfirmDisconnect() {
if (!canDisconnect) return; if (!canDisconnect) return;
await putLidarrConfig({ disconnectError = null;
enabled: false, try {
base_url: '', await putLidarrConfig({
api_key: '', enabled: false,
default_quality_profile_id: 0, base_url: '',
default_root_folder_path: '' api_key: '',
}); default_quality_profile_id: 0,
await client.invalidateQueries({ queryKey: qk.lidarrConfig() }); default_root_folder_path: ''
modalOpen = false; });
disconnectInput = ''; await Promise.all([
client.invalidateQueries({ queryKey: qk.lidarrConfig() }),
client.invalidateQueries({ queryKey: qk.lidarrQualityProfiles() }),
client.invalidateQueries({ queryKey: qk.lidarrRootFolders() })
]);
modalOpen = false;
disconnectInput = '';
} catch (e) {
disconnectError = (e as { code?: string }).code ?? 'disconnect_failed';
}
} }
function cancelDisconnect() { function cancelDisconnect() {
modalOpen = false; modalOpen = false;
disconnectInput = ''; disconnectInput = '';
disconnectError = null;
} }
</script> </script>
@@ -289,6 +307,9 @@
aria-label="Type DISCONNECT to confirm" aria-label="Type DISCONNECT to confirm"
class="mt-3 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent" class="mt-3 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
/> />
{#if disconnectError}
<p class="mt-2 text-sm text-error">Disconnect failed — {disconnectError}</p>
{/if}
<div class="mt-5 flex justify-end gap-2"> <div class="mt-5 flex justify-end gap-2">
<button <button
type="button" type="button"