Fix frontend issues found in Phase 4 code review
Some checks failed
Validate / Hassfest (push) Has been cancelled

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) <noreply@anthropic.com>
This commit is contained in:
2026-03-19 13:51:39 +03:00
parent 87ce1bc5ec
commit 2b487707ce
7 changed files with 34 additions and 22 deletions

View File

@@ -24,6 +24,7 @@ export function isAuthenticated(): boolean {
}
async function refreshAccessToken(): Promise<boolean> {
if (typeof window === 'undefined') return false;
const refreshToken = localStorage.getItem('refresh_token');
if (!refreshToken) return false;

View File

@@ -32,9 +32,10 @@ export async function loadUser() {
} catch {
user = null;
clearTokens();
}
} finally {
loading = false;
}
}
export async function login(username: string, password: string) {
const data = await api<{ access_token: string; refresh_token: string }>('/auth/login', {

View File

@@ -28,8 +28,12 @@
});
</script>
{#if isAuthPage || auth.loading}
{#if isAuthPage}
{@render children()}
{:else if auth.loading}
<div class="min-h-screen flex items-center justify-center">
<p class="text-sm text-[var(--color-muted-foreground)]">Loading...</p>
</div>
{:else if auth.user}
<div class="flex h-screen">
<!-- Sidebar -->

View File

@@ -31,8 +31,10 @@
async function remove(id: number) {
if (!confirm('Delete this server?')) return;
try {
await api(`/servers/${id}`, { method: 'DELETE' });
await load();
} catch (err: any) { error = err.message; }
}
</script>

View File

@@ -20,10 +20,10 @@
async function create(e: SubmitEvent) {
e.preventDefault();
error = '';
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) : {} };
try {
await api('/targets', { method: 'POST', body: JSON.stringify({ type: formType, name: form.name, config }) });
showForm = false;
form = { name: '', bot_token: '', chat_id: '', url: '', headers: '' };

View File

@@ -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<number | null>(null);
let editing = $state<number | null>(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;
try {
await api(`/templates/${id}`, { method: 'DELETE' });
await load();
} catch (err: any) { error = err.message; }
}
</script>
@@ -94,8 +98,10 @@
<div class="flex-1">
<p class="font-medium">{template.name}</p>
<pre class="text-xs text-[var(--color-muted-foreground)] mt-1 whitespace-pre-wrap font-mono bg-[var(--color-muted)] rounded p-2">{template.body.slice(0, 200)}{template.body.length > 200 ? '...' : ''}</pre>
{#if preview && editing === null}
<!-- show preview inline if triggered -->
{#if preview && previewId === template.id && !showForm}
<div class="mt-2 p-2 bg-green-50 rounded text-sm">
<pre class="whitespace-pre-wrap">{preview}</pre>
</div>
{/if}
</div>
<div class="flex items-center gap-3 ml-4">
@@ -107,10 +113,4 @@
</Card>
{/each}
</div>
{#if preview && !showForm}
<Card class="mt-4">
<p class="text-sm font-medium mb-2">Preview</p>
<pre class="text-sm whitespace-pre-wrap bg-[var(--color-muted)] rounded p-3">{preview}</pre>
</Card>
{/if}
{/if}

View File

@@ -15,9 +15,11 @@
onMount(load);
async function load() {
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;
try {
await api(`/trackers/${id}`, { method: 'DELETE' });
await load();
} catch (err: any) { error = err.message; }
}
function toggleAlbum(albumId: string) {