From 0baeea303fc84b8c9e719439ea723079e27eabbd Mon Sep 17 00:00:00 2001 From: Jakob Wennberg <149234542+jakobwennberg@users.noreply.github.com> Date: Wed, 8 Apr 2026 20:44:40 +0200 Subject: [PATCH] fix: prevent bank sync loading screen from persisting forever (#201) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: add rounding tolerance to INK2 balance check and clarify warnings INK2/SRU rounds each ruta independently to whole kronor, so with 11+ rutor the accumulated rounding can produce a 1-2 kr difference that triggered a false "balance sheet not in balance" warning. Add a 2 kr tolerance. Also clarify the unclosed fiscal year warning to indicate that generation still works. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: also warn when equity/liabilities exist but assets are zero Address Greptile review feedback — the balance check guard should trigger when either side has a non-zero total, not only when assets > 0. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: prevent bank sync loading screen from persisting forever Replace isSyncing state guard with a ref to prevent the useEffect from re-firing when sync state changes (race condition with router.replace changing searchParams). Add AbortController with 2-minute timeout so the fetch can't hang indefinitely on slow syncs. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: skip toast and state updates on unmount-abort Add unmountedRef to distinguish timeout-abort from unmount-abort. When the user navigates away mid-sync, silently bail instead of showing the misleading "took too long" toast. Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- app/(dashboard)/settings/banking/page.tsx | 35 ++++++++++++++++++----- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/app/(dashboard)/settings/banking/page.tsx b/app/(dashboard)/settings/banking/page.tsx index 776941c6..b6134cdb 100644 --- a/app/(dashboard)/settings/banking/page.tsx +++ b/app/(dashboard)/settings/banking/page.tsx @@ -20,11 +20,16 @@ export default function BankingSettingsPage() { const [isSyncing, setIsSyncing] = useState(false) const [syncResult, setSyncResult] = useState<{ imported: number } | null>(null) const successTimerRef = useRef>(null) + const syncInitiatedRef = useRef(false) + const abortControllerRef = useRef(null) + const unmountedRef = useRef(false) const hasBankingExtension = ENABLED_EXTENSION_IDS.has('enable-banking') useEffect(() => { return () => { + unmountedRef.current = true if (successTimerRef.current) clearTimeout(successTimerRef.current) + if (abortControllerRef.current) abortControllerRef.current.abort() } }, []) @@ -32,19 +37,26 @@ export default function BankingSettingsPage() { const bankConnected = searchParams.get('bank_connected') const bankError = searchParams.get('bank_error') - if (bankConnected === 'true' && !isSyncing) { + if (bankConnected === 'true' && !syncInitiatedRef.current) { + syncInitiatedRef.current = true const connectionId = searchParams.get('connection_id') router.replace('/settings/banking') if (connectionId) { setIsSyncing(true) + const controller = new AbortController() + abortControllerRef.current = controller + const syncTimeout = setTimeout(() => controller.abort(), 120_000) + ;(async () => { try { const res = await fetch('/api/extensions/ext/enable-banking/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ connection_id: connectionId, days_back: 120 }), + signal: controller.signal, }) + clearTimeout(syncTimeout) const data = await res.json() if (res.ok) { setSyncResult({ imported: data.imported ?? 0 }) @@ -56,11 +68,20 @@ export default function BankingSettingsPage() { throw new Error(data.error || 'Sync failed') } } catch (err) { - toast({ - title: 'Synkronisering misslyckades', - description: err instanceof Error ? err.message : 'Kunde inte hämta transaktioner', - variant: 'destructive', - }) + clearTimeout(syncTimeout) + if (unmountedRef.current) return + if (controller.signal.aborted) { + toast({ + title: 'Synkronisering tog för lång tid', + description: 'Transaktionerna hämtas i bakgrunden. Ladda om sidan om en stund.', + }) + } else { + toast({ + title: 'Synkronisering misslyckades', + description: err instanceof Error ? err.message : 'Kunde inte hämta transaktioner', + variant: 'destructive', + }) + } setIsSyncing(false) } })() @@ -82,7 +103,7 @@ export default function BankingSettingsPage() { setBankConnectionError(errorMsg) router.replace('/settings/banking') } - }, [searchParams, router, toast, isSyncing]) + }, [searchParams, router, toast]) if (isSyncing) { return (