refactor(web): consolidate error-copy table to JSON source of truth

Three admin pages had divergent inline error-code → user-copy switch
tables (with wording drift, including a stale "Settings → Integrations"
that should have been Admin). Consolidate into web/src/lib/styles/error-copy.json,
expose via web/src/lib/api/error-copy.ts (ERROR_COPY + copyForCode),
and refactor the three pages to import the helper.

Fixes the three-way drift; the only user-visible behavior change is
the quarantine page now correctly says "Admin → Integrations".

Sets up the JSON for the M7 Flutter client (#356) to consume the same
table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 15:01:46 -04:00
parent ea22807a3f
commit c3be0f3e6e
6 changed files with 40 additions and 68 deletions
+6 -20
View File
@@ -12,6 +12,7 @@
deleteQuarantineViaLidarr
} from '$lib/api/admin';
import { qk } from '$lib/api/queries';
import { copyForCode } from '$lib/api/error-copy';
import type {
AdminQuarantineRow,
LidarrRequest,
@@ -60,21 +61,6 @@
toastTimer = setTimeout(() => { toast = null; }, 5000);
}
function errorCopy(code: string): string {
switch (code) {
case 'lidarr_unreachable': return "Lidarr is unreachable. Try again, or check Admin → Integrations.";
case 'lidarr_disabled': return 'Lidarr integration is not enabled.';
case 'lidarr_auth_failed': return 'Lidarr authentication failed.';
case 'lidarr_defaults_incomplete': return 'Lidarr defaults missing — set them in Admin → Integrations.';
case 'lidarr_server_error': return "Lidarr returned an error. Check Lidarr's logs.";
case 'lidarr_rejected': return 'Lidarr rejected the request. Check the server logs for details.';
case 'request_not_pending': return 'This request is no longer pending.';
case 'request_not_found': return 'That request no longer exists.';
case 'track_not_found': return 'That track no longer exists.';
default: return code || 'Action failed.';
}
}
// ---- Two-click destructive confirm for quarantine actions ----
// Map key is `${trackId}:${actionKey}`. First click sets a key; second
// click within 3s fires the action; otherwise the key clears.
@@ -134,7 +120,7 @@
await approveRequest(r.id);
await invalidateRequests();
} catch (e) {
showToast(errorCopy((e as { code?: string }).code ?? ''));
showToast(copyForCode((e as { code?: string }).code));
}
}
async function onReject(r: LidarrRequest) {
@@ -142,7 +128,7 @@
await rejectRequest(r.id);
await invalidateRequests();
} catch (e) {
showToast(errorCopy((e as { code?: string }).code ?? ''));
showToast(copyForCode((e as { code?: string }).code));
}
}
@@ -158,7 +144,7 @@
await resolveQuarantine(row.track_id);
await invalidateQuarantine();
} catch (e) {
showToast(errorCopy((e as { code?: string }).code ?? ''));
showToast(copyForCode((e as { code?: string }).code));
}
}
async function onDeleteFile(row: AdminQuarantineRow) {
@@ -172,7 +158,7 @@
await deleteQuarantineFile(row.track_id);
await invalidateQuarantine();
} catch (e) {
showToast(errorCopy((e as { code?: string }).code ?? ''));
showToast(copyForCode((e as { code?: string }).code));
}
}
async function onDeleteLidarr(row: AdminQuarantineRow) {
@@ -186,7 +172,7 @@
await deleteQuarantineViaLidarr(row.track_id);
await invalidateQuarantine();
} catch (e) {
showToast(errorCopy((e as { code?: string }).code ?? ''));
showToast(copyForCode((e as { code?: string }).code));
}
}
</script>