Fix frontend issues found in Phase 4 code review
Some checks failed
Validate / Hassfest (push) Has been cancelled
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:
@@ -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;
|
||||
|
||||
|
||||
@@ -32,8 +32,9 @@ export async function loadUser() {
|
||||
} catch {
|
||||
user = null;
|
||||
clearTokens();
|
||||
}
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function login(username: string, password: string) {
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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: '' };
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user