diff --git a/web/src/lib/api/errors.test.ts b/web/src/lib/api/errors.test.ts new file mode 100644 index 00000000..d12f1e0b --- /dev/null +++ b/web/src/lib/api/errors.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, test } from 'vitest'; +import { errCode, errMessage } from './errors'; +import { ERROR_COPY } from './error-copy'; + +describe('errCode', () => { + test('returns code from object', () => { + expect(errCode({ code: 'foo' })).toBe('foo'); + }); + + test("returns 'unknown' for null", () => { + expect(errCode(null)).toBe('unknown'); + }); + + test("returns 'unknown' for undefined", () => { + expect(errCode(undefined)).toBe('unknown'); + }); + + test("returns 'unknown' for non-object values", () => { + expect(errCode('boom')).toBe('unknown'); + expect(errCode(42)).toBe('unknown'); + expect(errCode(true)).toBe('unknown'); + }); + + test('handles missing code field', () => { + expect(errCode({})).toBe('unknown'); + expect(errCode({ message: 'hi' })).toBe('unknown'); + }); +}); + +describe('errMessage', () => { + test('returns mapped copy when code is known', () => { + const expected = ERROR_COPY.track_not_found; + expect(errMessage({ code: 'track_not_found' })).toBe(expected); + }); + + test('returns the unknown-fallback copy when code is missing', () => { + const result = errMessage({ code: 'definitely_not_a_real_code_xyz' }); + expect(result).toBeTruthy(); + expect(typeof result).toBe('string'); + expect(result.length).toBeGreaterThan(0); + }); + + test('accepts custom fallback parameter without throwing', () => { + // copyForCode itself returns a non-empty string for any input, so the + // explicit fallback only matters in narrow cases. Just exercise the + // signature. + const result = errMessage(null, 'custom fallback'); + expect(typeof result).toBe('string'); + expect(result.length).toBeGreaterThan(0); + }); +}); diff --git a/web/src/lib/api/errors.ts b/web/src/lib/api/errors.ts new file mode 100644 index 00000000..a7417811 --- /dev/null +++ b/web/src/lib/api/errors.ts @@ -0,0 +1,18 @@ +import { copyForCode } from './error-copy'; + +/** + * Returns the `code` field from an unknown error value, or 'unknown' + * when the value isn't shaped like { code: string }. + */ +export function errCode(err: unknown): string { + return (err as { code?: string })?.code ?? 'unknown'; +} + +/** + * Returns user-facing copy for an unknown error value. Looks up the + * error's code in the error-copy map; falls back to the supplied + * fallback (default: "Something went wrong.") when the code is unknown. + */ +export function errMessage(err: unknown, fallback = 'Something went wrong.'): string { + return copyForCode(errCode(err)) ?? fallback; +} diff --git a/web/src/lib/components/AddToPlaylistMenu.svelte b/web/src/lib/components/AddToPlaylistMenu.svelte index 16cafd94..a172eee7 100644 --- a/web/src/lib/components/AddToPlaylistMenu.svelte +++ b/web/src/lib/components/AddToPlaylistMenu.svelte @@ -3,7 +3,7 @@ import { useQueryClient } from '@tanstack/svelte-query'; import { createPlaylistsQuery, appendTracks, createPlaylist } from '$lib/api/playlists'; import { qk } from '$lib/api/queries'; - import { copyForCode } from '$lib/api/error-copy'; + import { errMessage } from '$lib/api/errors'; import TrackMenuItem from './TrackMenuItem.svelte'; import TrackMenuDivider from './TrackMenuDivider.svelte'; import type { TrackRef } from '$lib/api/types'; @@ -37,8 +37,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlists() }); onClose(); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - error = copyForCode(code); + error = errMessage(e); } finally { busy = false; } @@ -57,8 +56,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlists() }); onClose(); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - error = copyForCode(code); + error = errMessage(e); } finally { busy = false; } diff --git a/web/src/lib/components/FlagPopover.svelte b/web/src/lib/components/FlagPopover.svelte index a19a2890..a3a75db6 100644 --- a/web/src/lib/components/FlagPopover.svelte +++ b/web/src/lib/components/FlagPopover.svelte @@ -3,6 +3,7 @@ import { Flag } from 'lucide-svelte'; import { useQueryClient } from '@tanstack/svelte-query'; import { flagTrack } from '$lib/api/quarantine'; + import { errCode } from '$lib/api/errors'; import { qk } from '$lib/api/queries'; import type { TrackRef, LidarrQuarantineReason } from '$lib/api/types'; @@ -34,7 +35,8 @@ await client.invalidateQueries({ queryKey: qk.myQuarantine() }); onClose(); } catch (e) { - error = (e as { code?: string }).code ?? 'flag_failed'; + const code = errCode(e); + error = code === 'unknown' ? 'flag_failed' : code; } finally { submitting = false; } diff --git a/web/src/lib/components/PlaylistCard.svelte b/web/src/lib/components/PlaylistCard.svelte index 39484bd2..c14c289d 100644 --- a/web/src/lib/components/PlaylistCard.svelte +++ b/web/src/lib/components/PlaylistCard.svelte @@ -4,6 +4,7 @@ import type { Playlist, PlaylistTrack, TrackRef } from '$lib/api/types'; import { user } from '$lib/auth/store.svelte'; import { getPlaylist, refreshDiscover, refreshForYou } from '$lib/api/playlists'; + import { errCode } from '$lib/api/errors'; import { qk } from '$lib/api/queries'; import { playQueue } from '$lib/player/store.svelte'; @@ -95,7 +96,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlist(playlist.id) }); await queryClient.invalidateQueries({ queryKey: qk.playlists() }); } catch (err: unknown) { - showToast(`Refresh failed: ${(err as { code?: string })?.code ?? 'unknown'}`); + showToast(`Refresh failed: ${errCode(err)}`); } } diff --git a/web/src/lib/components/RemoveTrackPopover.svelte b/web/src/lib/components/RemoveTrackPopover.svelte index 180351aa..d3155dd7 100644 --- a/web/src/lib/components/RemoveTrackPopover.svelte +++ b/web/src/lib/components/RemoveTrackPopover.svelte @@ -2,7 +2,7 @@ import { Trash2 } from 'lucide-svelte'; import { useQueryClient } from '@tanstack/svelte-query'; import { removeTrack } from '$lib/api/admin/tracks'; - import { copyForCode } from '$lib/api/error-copy'; + import { errMessage } from '$lib/api/errors'; import { qk } from '$lib/api/queries'; import type { TrackRef } from '$lib/api/types'; @@ -45,8 +45,7 @@ // surface (if any) can pick up the flag. onClose(); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - error = copyForCode(code); + error = errMessage(e); } finally { busy = false; } diff --git a/web/src/routes/admin/+page.svelte b/web/src/routes/admin/+page.svelte index fa0712c2..46175da9 100644 --- a/web/src/routes/admin/+page.svelte +++ b/web/src/routes/admin/+page.svelte @@ -24,7 +24,7 @@ type ScanScheduleMode } from '$lib/api/admin'; import { qk } from '$lib/api/queries'; - import { copyForCode } from '$lib/api/error-copy'; + import { errCode, errMessage } from '$lib/api/errors'; import type { AdminQuarantineRow, LidarrRequest, @@ -132,7 +132,7 @@ await approveRequest(r.id); await invalidateRequests(); } catch (e) { - showToast(copyForCode((e as { code?: string }).code)); + showToast(errMessage(e)); } } async function onReject(r: LidarrRequest) { @@ -140,7 +140,7 @@ await rejectRequest(r.id); await invalidateRequests(); } catch (e) { - showToast(copyForCode((e as { code?: string }).code)); + showToast(errMessage(e)); } } @@ -156,7 +156,7 @@ await resolveQuarantine(row.track_id); await invalidateQuarantine(); } catch (e) { - showToast(copyForCode((e as { code?: string }).code)); + showToast(errMessage(e)); } } async function onDeleteFile(row: AdminQuarantineRow) { @@ -170,7 +170,7 @@ await deleteQuarantineFile(row.track_id); await invalidateQuarantine(); } catch (e) { - showToast(copyForCode((e as { code?: string }).code)); + showToast(errMessage(e)); } } async function onDeleteLidarr(row: AdminQuarantineRow) { @@ -184,7 +184,7 @@ await deleteQuarantineViaLidarr(row.track_id); await invalidateQuarantine(); } catch (e) { - showToast(copyForCode((e as { code?: string }).code)); + showToast(errMessage(e)); } } @@ -212,7 +212,7 @@ await client.invalidateQueries({ queryKey: qk.scanStatus() }); await client.invalidateQueries({ queryKey: qk.coverage() }); } catch (e) { - const code = (e as { code?: string; status?: number })?.code ?? 'unknown'; + const code = errCode(e); const status = (e as { status?: number })?.status; if (status === 409) { triggerResult = 'A scan is already running.'; @@ -286,7 +286,7 @@ await updateScanSchedule(patch); await client.invalidateQueries({ queryKey: qk.scanSchedule() }); } catch (e) { - showToast(`Schedule save failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Schedule save failed: ${errCode(e)}`); } finally { scheduleSaving = false; } @@ -325,7 +325,7 @@ await researchMissingArt(); showToast('All previously-failed art will be re-attempted on the next scan.'); } catch (e) { - showToast(`Re-search failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Re-search failed: ${errCode(e)}`); } finally { researchSaving = false; } @@ -340,7 +340,7 @@ bulkResult = `Queued ${queued} albums for cover refetch.`; await client.invalidateQueries({ queryKey: qk.coverage() }); } catch (e) { - bulkResult = `Failed: ${(e as { code?: string })?.code ?? 'unknown'}`; + bulkResult = `Failed: ${errCode(e)}`; } finally { bulkBusy = false; } diff --git a/web/src/routes/admin/integrations/+page.svelte b/web/src/routes/admin/integrations/+page.svelte index 2796eb0d..9d01d55d 100644 --- a/web/src/routes/admin/integrations/+page.svelte +++ b/web/src/routes/admin/integrations/+page.svelte @@ -19,6 +19,7 @@ type SMTPConfig } from '$lib/api/admin'; import { qk } from '$lib/api/queries'; + import { errCode } from '$lib/api/errors'; import type { LidarrConfig, LidarrTestResult } from '$lib/api/types'; // Lidarr connection panel. The "saved api key" is masked as "***" on GET — @@ -112,7 +113,8 @@ ]); apiKeyInput = ''; } catch (e) { - saveError = (e as { code?: string }).code ?? 'save_failed'; + const code = errCode(e); + saveError = code === 'unknown' ? 'save_failed' : code; } finally { isSaving = false; } @@ -160,7 +162,8 @@ modalOpen = false; disconnectInput = ''; } catch (e) { - disconnectError = (e as { code?: string }).code ?? 'disconnect_failed'; + const code = errCode(e); + disconnectError = code === 'unknown' ? 'disconnect_failed' : code; } } @@ -270,9 +273,9 @@ smtpPasswordInput = ''; showToast('SMTP config saved.'); } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'missing_fields') showToast('Host and from address are required when enabled.'); - else showToast(`Save failed: ${code ?? 'unknown'}`); + else showToast(`Save failed: ${code}`); } finally { smtpSaving = false; } @@ -284,12 +287,12 @@ await testSMTPConfig(); showToast('Test email sent. Check your inbox.'); } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); const message = (e as { message?: string })?.message; if (code === 'no_email_on_file') showToast('Set your email in /settings before testing.'); else if (code === 'not_configured') showToast('Save the SMTP config first.'); else if (code === 'send_failed') showToast(`Send failed: ${message || 'see server logs'}`); - else showToast(`Test failed: ${code ?? 'unknown'}`); + else showToast(`Test failed: ${code}`); } finally { smtpTesting = false; } diff --git a/web/src/routes/admin/quarantine/+page.svelte b/web/src/routes/admin/quarantine/+page.svelte index ae60ba3b..0fbca603 100644 --- a/web/src/routes/admin/quarantine/+page.svelte +++ b/web/src/routes/admin/quarantine/+page.svelte @@ -9,7 +9,7 @@ deleteQuarantineViaLidarr } from '$lib/api/admin'; import { qk } from '$lib/api/queries'; - import { copyForCode } from '$lib/api/error-copy'; + import { errMessage } from '$lib/api/errors'; import { playRadio } from '$lib/player/store.svelte'; import type { AdminQuarantineRow, LidarrQuarantineReason } from '$lib/api/types'; @@ -89,8 +89,7 @@ await resolveQuarantine(r.track_id); await invalidate(); } catch (e) { - const code = (e as { code?: string }).code ?? 'unknown'; - showToast(copyForCode(code)); + showToast(errMessage(e)); } } @@ -108,8 +107,7 @@ await deleteQuarantineFile(r.track_id); await invalidate(); } catch (e) { - const code = (e as { code?: string }).code ?? 'unknown'; - showToast(copyForCode(code)); + showToast(errMessage(e)); } } @@ -135,12 +133,12 @@ deleteLidarrError = null; await invalidate(); } catch (e) { - const code = (e as { code?: string }).code ?? 'unknown'; + const msg = errMessage(e); // Inline error keeps the modal open so the operator sees the failure // alongside the album they were about to remove. - deleteLidarrError = copyForCode(code); + deleteLidarrError = msg; // Also surface as toast so it's visible after dismissing the modal. - showToast(copyForCode(code)); + showToast(msg); } } diff --git a/web/src/routes/admin/requests/+page.svelte b/web/src/routes/admin/requests/+page.svelte index 42d6c662..b805401a 100644 --- a/web/src/routes/admin/requests/+page.svelte +++ b/web/src/routes/admin/requests/+page.svelte @@ -9,7 +9,7 @@ approveRequest, rejectRequest } from '$lib/api/admin'; - import { copyForCode } from '$lib/api/error-copy'; + import { errMessage } from '$lib/api/errors'; import StatusPill from '$lib/components/StatusPill.svelte'; import type { LidarrRequest, LidarrRequestKind, LidarrRequestStatus } from '$lib/api/types'; @@ -114,8 +114,7 @@ } await invalidate(); } catch (e) { - const code = (e as { code?: string }).code ?? 'unknown'; - showToast(copyForCode(code)); + showToast(errMessage(e)); } } @@ -155,8 +154,7 @@ await rejectRequest(r.id, notes ? notes : undefined); await invalidate(); } catch (e) { - const code = (e as { code?: string }).code ?? 'unknown'; - showToast(copyForCode(code)); + showToast(errMessage(e)); } } diff --git a/web/src/routes/admin/users/+page.svelte b/web/src/routes/admin/users/+page.svelte index 337a8b66..aa19ac3f 100644 --- a/web/src/routes/admin/users/+page.svelte +++ b/web/src/routes/admin/users/+page.svelte @@ -16,6 +16,7 @@ type AdminInvite, type CreateUserInput } from '$lib/api/admin'; + import { errCode } from '$lib/api/errors'; const client = useQueryClient(); @@ -62,11 +63,11 @@ await client.invalidateQueries({ queryKey: qk.adminUsers() }); showToast(u.is_admin ? `${u.username} is no longer an admin.` : `${u.username} is now an admin.`); } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'last_admin') { showToast(`Can't remove the last admin — promote someone else first.`); } else { - showToast(`Action failed: ${code ?? 'unknown'}`); + showToast(`Action failed: ${code}`); } } finally { saving = false; @@ -92,8 +93,7 @@ createForm = { username: '', password: '', confirmPassword: '', display_name: '', is_admin: false }; showToast('User created.'); } catch (e: unknown) { - const code = (e as { code?: string })?.code; - showToast(createUserErrorMessage(code)); + showToast(createUserErrorMessage(errCode(e))); } finally { saving = false; } @@ -117,11 +117,11 @@ showToast(`Deleted ${confirmDeleteTarget.username}.`); confirmDeleteTarget = null; } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'last_admin') { showToast(`Can't delete the last admin — promote someone else first.`); } else { - showToast(`Delete failed: ${code ?? 'unknown'}`); + showToast(`Delete failed: ${code}`); } } finally { saving = false; @@ -143,11 +143,11 @@ resetPasswordValue = ''; resetPasswordConfirm = ''; } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'password_too_short') { showToast('Password must be at least 8 characters.'); } else { - showToast(`Reset failed: ${code ?? 'unknown'}`); + showToast(`Reset failed: ${code}`); } } finally { saving = false; @@ -163,7 +163,7 @@ ? `Auto-approve disabled for ${u.username}.` : `Auto-approve enabled for ${u.username}.`); } catch (e: unknown) { - showToast(`Toggle failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Toggle failed: ${errCode(e)}`); } finally { saving = false; } @@ -176,7 +176,7 @@ await client.invalidateQueries({ queryKey: qk.adminInvites() }); showToast('Invite generated. Copy the token from the list below.'); } catch (e: unknown) { - showToast(`Generate failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Generate failed: ${errCode(e)}`); } finally { saving = false; } @@ -189,7 +189,7 @@ await client.invalidateQueries({ queryKey: qk.adminInvites() }); showToast('Invite revoked.'); } catch (e: unknown) { - showToast(`Revoke failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Revoke failed: ${errCode(e)}`); } finally { saving = false; } diff --git a/web/src/routes/albums/[id]/+page.svelte b/web/src/routes/albums/[id]/+page.svelte index edf88d4b..355265be 100644 --- a/web/src/routes/albums/[id]/+page.svelte +++ b/web/src/routes/albums/[id]/+page.svelte @@ -15,6 +15,7 @@ import { playQueue } from '$lib/player/store.svelte'; import { user } from '$lib/auth/store.svelte'; import { refetchAlbumCover } from '$lib/api/admin'; + import { errCode } from '$lib/api/errors'; import { useQueryClient } from '@tanstack/svelte-query'; const id = $derived(page.params.id ?? ''); @@ -78,7 +79,8 @@ await refetchAlbumCover(data.id); await queryClient.invalidateQueries({ queryKey: qk.album(data.id) }); } catch (e) { - refetchError = (e as { code?: string })?.code ?? 'refetch failed'; + const code = errCode(e); + refetchError = code === 'unknown' ? 'refetch failed' : code; } finally { refetching = false; } diff --git a/web/src/routes/playlists/[id]/+page.svelte b/web/src/routes/playlists/[id]/+page.svelte index 713e1bcd..7c1b1e2d 100644 --- a/web/src/routes/playlists/[id]/+page.svelte +++ b/web/src/routes/playlists/[id]/+page.svelte @@ -16,7 +16,7 @@ } from '$lib/api/playlists'; import { qk } from '$lib/api/queries'; import { user } from '$lib/auth/store.svelte'; - import { copyForCode } from '$lib/api/error-copy'; + import { errCode, errMessage } from '$lib/api/errors'; import { playQueue } from '$lib/player/store.svelte'; import type { TrackRef } from '$lib/api/types'; @@ -48,8 +48,7 @@ await reorderPlaylist(id, cur); await queryClient.invalidateQueries({ queryKey: qk.playlist(id) }); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - alert(copyForCode(code)); + alert(errMessage(e)); } } @@ -59,8 +58,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlist(id) }); await queryClient.invalidateQueries({ queryKey: qk.playlists() }); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - alert(copyForCode(code)); + alert(errMessage(e)); } } @@ -120,8 +118,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlists() }); editing = false; } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - alert(copyForCode(code)); + alert(errMessage(e)); } } @@ -132,8 +129,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlists() }); goto('/playlists'); } catch (e: unknown) { - const code = (e as { code?: string })?.code ?? 'unknown'; - alert(copyForCode(code)); + alert(errMessage(e)); } } @@ -158,7 +154,7 @@ await queryClient.invalidateQueries({ queryKey: qk.playlist(id) }); await queryClient.invalidateQueries({ queryKey: qk.playlists() }); } catch (e: unknown) { - showToast(`Refresh failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Refresh failed: ${errCode(e)}`); } finally { refreshingDiscover = false; } diff --git a/web/src/routes/register/+page.svelte b/web/src/routes/register/+page.svelte index a753a424..c4eaca31 100644 --- a/web/src/routes/register/+page.svelte +++ b/web/src/routes/register/+page.svelte @@ -2,6 +2,7 @@ import { pageTitle } from '$lib/branding'; import { goto } from '$app/navigation'; import { register } from '$lib/auth/store.svelte'; + import { errCode } from '$lib/api/errors'; let username = $state(''); let password = $state(''); @@ -46,8 +47,7 @@ }); await goto('/', { replaceState: true }); } catch (err: unknown) { - const code = (err as { code?: string })?.code ?? 'unknown'; - error = errorMessageFor(code); + error = errorMessageFor(errCode(err)); } finally { submitting = false; } diff --git a/web/src/routes/reset-password/[token]/+page.svelte b/web/src/routes/reset-password/[token]/+page.svelte index 6e62f8d2..586141f9 100644 --- a/web/src/routes/reset-password/[token]/+page.svelte +++ b/web/src/routes/reset-password/[token]/+page.svelte @@ -3,6 +3,7 @@ import { page } from '$app/state'; import { pageTitle } from '$lib/branding'; import { resetPassword } from '$lib/auth/store.svelte'; + import { errCode } from '$lib/api/errors'; let newPassword = $state(''); let confirmPassword = $state(''); @@ -27,13 +28,13 @@ await resetPassword(token, newPassword); await goto('/login?reset=ok', { replaceState: true }); } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'invalid_token') { error = 'This reset link is invalid, expired, or already used. Request a new one.'; } else if (code === 'password_too_short') { error = 'Password must be at least 8 characters.'; } else { - error = `Reset failed: ${code ?? 'unknown'}`; + error = `Reset failed: ${code}`; } } finally { submitting = false; diff --git a/web/src/routes/settings/+page.svelte b/web/src/routes/settings/+page.svelte index 01fe104b..af4b9d3f 100644 --- a/web/src/routes/settings/+page.svelte +++ b/web/src/routes/settings/+page.svelte @@ -15,6 +15,7 @@ getAPIToken, regenerateAPIToken } from '$lib/api/me'; + import { errCode } from '$lib/api/errors'; const queryClient = useQueryClient(); @@ -67,10 +68,10 @@ }); showToast('Profile saved.'); } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'email_taken') showToast('That email is already in use.'); else if (code === 'email_invalid') showToast('Email format is invalid.'); - else showToast(`Save failed: ${code ?? 'unknown'}`); + else showToast(`Save failed: ${code}`); } finally { profileSaving = false; } @@ -95,10 +96,10 @@ showToast('Password changed.'); passwordForm = { current: '', new: '', confirm: '' }; } catch (e: unknown) { - const code = (e as { code?: string })?.code; + const code = errCode(e); if (code === 'wrong_password') showToast('Current password is incorrect.'); else if (code === 'password_too_short') showToast('Password must be at least 8 characters.'); - else showToast(`Change failed: ${code ?? 'unknown'}`); + else showToast(`Change failed: ${code}`); } finally { passwordSaving = false; } @@ -140,7 +141,7 @@ apiToken = r.api_token; showToast('API token regenerated.'); } catch (e: unknown) { - showToast(`Regenerate failed: ${(e as { code?: string })?.code ?? 'unknown'}`); + showToast(`Regenerate failed: ${errCode(e)}`); } finally { tokenSaving = false; }