From 2b487707ceb145ddedd4d5a7f171a9a4ffd36164 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 19 Mar 2026 13:51:39 +0300 Subject: [PATCH] Fix frontend issues found in Phase 4 code review Fixes 7 issues identified by code-reviewer agent: 1. (Critical) Move JSON.parse inside try/catch in targets page to handle malformed webhook headers input gracefully 2. (Low) Add window guard to refreshAccessToken for SSR safety 3. (Important) Show loading indicator instead of page content while auth state is being resolved (prevents flash of protected content) 4. (Important) Add try/catch to trackers load() Promise.all 5. (Important) Add error handling to remove() in servers, trackers, and templates pages 6. (Important) Track preview per-template with previewId, show inline instead of global bottom block 7. (Important) Use finally block for loading state in auth Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/lib/api.ts | 1 + frontend/src/lib/auth.svelte.ts | 3 ++- frontend/src/routes/+layout.svelte | 6 +++++- frontend/src/routes/servers/+page.svelte | 6 ++++-- frontend/src/routes/targets/+page.svelte | 6 +++--- frontend/src/routes/templates/+page.svelte | 20 ++++++++++---------- frontend/src/routes/trackers/+page.svelte | 14 +++++++++----- 7 files changed, 34 insertions(+), 22 deletions(-) diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index e8f2e24..96ce2fc 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -24,6 +24,7 @@ export function isAuthenticated(): boolean { } async function refreshAccessToken(): Promise { + if (typeof window === 'undefined') return false; const refreshToken = localStorage.getItem('refresh_token'); if (!refreshToken) return false; diff --git a/frontend/src/lib/auth.svelte.ts b/frontend/src/lib/auth.svelte.ts index 8bd190c..c2d8bf2 100644 --- a/frontend/src/lib/auth.svelte.ts +++ b/frontend/src/lib/auth.svelte.ts @@ -32,8 +32,9 @@ export async function loadUser() { } catch { user = null; clearTokens(); + } finally { + loading = false; } - loading = false; } export async function login(username: string, password: string) { diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index d6570b7..543cfd5 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -28,8 +28,12 @@ }); -{#if isAuthPage || auth.loading} +{#if isAuthPage} {@render children()} +{:else if auth.loading} +
+

Loading...

+
{:else if auth.user}
diff --git a/frontend/src/routes/servers/+page.svelte b/frontend/src/routes/servers/+page.svelte index a34392a..eeb9754 100644 --- a/frontend/src/routes/servers/+page.svelte +++ b/frontend/src/routes/servers/+page.svelte @@ -31,8 +31,10 @@ async function remove(id: number) { if (!confirm('Delete this server?')) return; - await api(`/servers/${id}`, { method: 'DELETE' }); - await load(); + try { + await api(`/servers/${id}`, { method: 'DELETE' }); + await load(); + } catch (err: any) { error = err.message; } } diff --git a/frontend/src/routes/targets/+page.svelte b/frontend/src/routes/targets/+page.svelte index 6f5c05b..3a615ee 100644 --- a/frontend/src/routes/targets/+page.svelte +++ b/frontend/src/routes/targets/+page.svelte @@ -20,10 +20,10 @@ async function create(e: SubmitEvent) { e.preventDefault(); error = ''; - const config = formType === 'telegram' - ? { bot_token: form.bot_token, chat_id: form.chat_id } - : { url: form.url, headers: form.headers ? JSON.parse(form.headers) : {} }; try { + const config = formType === 'telegram' + ? { bot_token: form.bot_token, chat_id: form.chat_id } + : { url: form.url, headers: form.headers ? JSON.parse(form.headers) : {} }; await api('/targets', { method: 'POST', body: JSON.stringify({ type: formType, name: form.name, config }) }); showForm = false; form = { name: '', bot_token: '', chat_id: '', url: '', headers: '' }; diff --git a/frontend/src/routes/templates/+page.svelte b/frontend/src/routes/templates/+page.svelte index 11acaf0..67383b9 100644 --- a/frontend/src/routes/templates/+page.svelte +++ b/frontend/src/routes/templates/+page.svelte @@ -8,6 +8,7 @@ let showForm = $state(false); let form = $state({ name: '', body: '{{ added_count }} new item(s) added to album "{{ album_name }}".' }); let preview = $state(''); + let previewId = $state(null); let editing = $state(null); let error = $state(''); @@ -33,6 +34,7 @@ } async function doPreview(id: number) { + previewId = id; try { const res = await api<{ rendered: string }>(`/templates/${id}/preview`, { method: 'POST' }); preview = res.rendered; @@ -48,8 +50,10 @@ async function remove(id: number) { if (!confirm('Delete this template?')) return; - await api(`/templates/${id}`, { method: 'DELETE' }); - await load(); + try { + await api(`/templates/${id}`, { method: 'DELETE' }); + await load(); + } catch (err: any) { error = err.message; } } @@ -94,8 +98,10 @@

{template.name}

{template.body.slice(0, 200)}{template.body.length > 200 ? '...' : ''}
- {#if preview && editing === null} - + {#if preview && previewId === template.id && !showForm} +
+
{preview}
+
{/if}
@@ -107,10 +113,4 @@ {/each}
- {#if preview && !showForm} - -

Preview

-
{preview}
-
- {/if} {/if} diff --git a/frontend/src/routes/trackers/+page.svelte b/frontend/src/routes/trackers/+page.svelte index fafb74c..e1a15c1 100644 --- a/frontend/src/routes/trackers/+page.svelte +++ b/frontend/src/routes/trackers/+page.svelte @@ -15,9 +15,11 @@ onMount(load); async function load() { - [trackers, servers, targets] = await Promise.all([ - api('/trackers'), api('/servers'), api('/targets') - ]); + try { + [trackers, servers, targets] = await Promise.all([ + api('/trackers'), api('/servers'), api('/targets') + ]); + } catch { /* handled by api redirect on 401 */ } } async function loadAlbums() { @@ -45,8 +47,10 @@ async function remove(id: number) { if (!confirm('Delete this tracker?')) return; - await api(`/trackers/${id}`, { method: 'DELETE' }); - await load(); + try { + await api(`/trackers/${id}`, { method: 'DELETE' }); + await load(); + } catch (err: any) { error = err.message; } } function toggleAlbum(albumId: string) {