fix: prevent bank sync loading screen from persisting forever (#201)

* 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

* 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) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-04-08 20:44:40 +02:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 211033410c
commit 0baeea303f
+28 -7
View File
@@ -20,11 +20,16 @@ export default function BankingSettingsPage() {
const [isSyncing, setIsSyncing] = useState(false)
const [syncResult, setSyncResult] = useState<{ imported: number } | null>(null)
const successTimerRef = useRef<ReturnType<typeof setTimeout>>(null)
const syncInitiatedRef = useRef(false)
const abortControllerRef = useRef<AbortController | null>(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 (