Feature/quick review scroll (#110)
* fix: allow scrolling in QuickReviewDialog when content overflows viewport The dialog had no max-height or overflow handling, so tall content (e.g. extern representation with warnings + journal preview) was clipped with no way to scroll. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add detailed error logging to Enable Banking integration Adds structured console logging with full context (status codes, response bodies, connection IDs, account UIDs, stack traces) to all Enable Banking operations: connect, callback, sync, disconnect, and the API client retry/error paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add detailed error logging to login and register flows Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: implement KPI settings management with preferences and dynamic KPI display --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4d4a9a40c9
commit
c1630068b3
@@ -53,6 +53,11 @@ export default function BankingSettingsPanel() {
|
||||
setConnectingBankName(bank.name)
|
||||
|
||||
try {
|
||||
console.log('[enable-banking] Initiating bank connection', {
|
||||
bankName: bank.name,
|
||||
bankCountry: bank.country,
|
||||
})
|
||||
|
||||
const response = await fetch('/api/extensions/ext/enable-banking/connect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -62,11 +67,26 @@ export default function BankingSettingsPanel() {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('[enable-banking] Connect request failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: data.error,
|
||||
bankName: bank.name,
|
||||
})
|
||||
throw new Error(data.error)
|
||||
}
|
||||
|
||||
console.log('[enable-banking] Redirecting to bank authorization', {
|
||||
connectionId: data.connection_id,
|
||||
hasAuthUrl: !!data.authorization_url,
|
||||
})
|
||||
window.location.href = data.authorization_url
|
||||
} catch (error) {
|
||||
console.error('[enable-banking] Connect flow failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
bankName: bank.name,
|
||||
})
|
||||
toast({
|
||||
title: 'Fel',
|
||||
description: error instanceof Error ? error.message : 'Kunde inte ansluta bank',
|
||||
@@ -82,6 +102,8 @@ export default function BankingSettingsPanel() {
|
||||
setSyncingConnectionId(connectionId)
|
||||
|
||||
try {
|
||||
console.log('[enable-banking] Starting sync', { connectionId })
|
||||
|
||||
const response = await fetch('/api/extensions/ext/enable-banking/sync', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -91,9 +113,21 @@ export default function BankingSettingsPanel() {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('[enable-banking] Sync request failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: data.error,
|
||||
connectionId,
|
||||
})
|
||||
throw new Error(data.error)
|
||||
}
|
||||
|
||||
console.log('[enable-banking] Sync completed', {
|
||||
connectionId,
|
||||
imported: data.imported,
|
||||
duplicates: data.duplicates,
|
||||
})
|
||||
|
||||
toast({
|
||||
title: 'Synkronisering klar',
|
||||
description: `${data.imported} nya transaktioner importerade`,
|
||||
@@ -102,6 +136,11 @@ export default function BankingSettingsPanel() {
|
||||
setShowCsvFallback(false)
|
||||
fetchConnections()
|
||||
} catch (error) {
|
||||
console.error('[enable-banking] Sync flow failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
connectionId,
|
||||
})
|
||||
toast({
|
||||
title: 'Fel',
|
||||
description: error instanceof Error ? error.message : 'Synkronisering misslyckades',
|
||||
@@ -123,6 +162,8 @@ export default function BankingSettingsPanel() {
|
||||
if (!ok) return
|
||||
|
||||
try {
|
||||
console.log('[enable-banking] Disconnecting bank', { connectionId })
|
||||
|
||||
const response = await fetch('/api/extensions/ext/enable-banking/disconnect', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -131,15 +172,27 @@ export default function BankingSettingsPanel() {
|
||||
|
||||
if (!response.ok) {
|
||||
const data = await response.json()
|
||||
console.error('[enable-banking] Disconnect request failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
error: data.error,
|
||||
connectionId,
|
||||
})
|
||||
throw new Error(data.error || 'Disconnect failed')
|
||||
}
|
||||
|
||||
console.log('[enable-banking] Bank disconnected', { connectionId })
|
||||
toast({
|
||||
title: 'Bank bortkopplad',
|
||||
description: 'Bankanslutningen och PSD2-samtycket har återkallats',
|
||||
})
|
||||
fetchConnections()
|
||||
} catch (error) {
|
||||
console.error('[enable-banking] Disconnect flow failed', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
connectionId,
|
||||
})
|
||||
toast({
|
||||
title: 'Fel',
|
||||
description: error instanceof Error ? error.message : 'Kunde inte koppla bort bank',
|
||||
|
||||
@@ -148,8 +148,14 @@ export const enableBankingExtension: Extension = {
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
log.error('Database error:', error)
|
||||
throw new Error('Failed to store connection')
|
||||
log.error('[enable-banking] Database error storing connection', {
|
||||
errorMessage: error.message,
|
||||
errorCode: error.code,
|
||||
errorDetails: error.details,
|
||||
user_id: user.id,
|
||||
bank: aspsp_name,
|
||||
})
|
||||
throw new Error(`Failed to store connection: ${error.message}`)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -157,7 +163,14 @@ export const enableBankingExtension: Extension = {
|
||||
authorization_url: url,
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Bank connection error:', error)
|
||||
log.error('[enable-banking] Connect handler error', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
name: error instanceof Error ? error.name : undefined,
|
||||
user_id: user.id,
|
||||
aspsp_name,
|
||||
aspsp_country,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Connection failed' },
|
||||
{ status: 500 }
|
||||
@@ -256,7 +269,15 @@ export const enableBankingExtension: Extension = {
|
||||
last_synced_at: syncedAt,
|
||||
})
|
||||
} catch (error) {
|
||||
log.error('Sync error:', error)
|
||||
log.error('[enable-banking] Sync handler error', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
name: error instanceof Error ? error.name : undefined,
|
||||
user_id: user.id,
|
||||
connection_id,
|
||||
connectionStatus: connection.status,
|
||||
bankName: connection.bank_name,
|
||||
})
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Sync failed' },
|
||||
{ status: 500 }
|
||||
@@ -298,8 +319,12 @@ export const enableBankingExtension: Extension = {
|
||||
try {
|
||||
await deleteSession(connection.session_id)
|
||||
} catch (error) {
|
||||
// Consent may already be expired — log and continue
|
||||
log.error('Failed to revoke PSD2 session (may be expired):', error)
|
||||
log.error('[enable-banking] Failed to revoke PSD2 session (may be expired)', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
sessionId: connection.session_id,
|
||||
connectionId: connection_id,
|
||||
connectionStatus: connection.status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -187,6 +187,10 @@ async function authenticatedFetchWithRetry(
|
||||
try {
|
||||
const response = await authenticatedFetch(endpoint, options)
|
||||
if (attempt < MAX_RETRIES && [429, 502, 503, 504].includes(response.status)) {
|
||||
console.warn(`[enable-banking] Retrying ${endpoint} (attempt ${attempt + 1}/${MAX_RETRIES})`, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * (attempt + 1)))
|
||||
continue
|
||||
}
|
||||
@@ -194,9 +198,16 @@ async function authenticatedFetchWithRetry(
|
||||
} catch (error: unknown) {
|
||||
const isAbort = error instanceof Error && error.name === 'AbortError'
|
||||
if (attempt < MAX_RETRIES && isAbort) {
|
||||
console.warn(`[enable-banking] Request timeout, retrying ${endpoint} (attempt ${attempt + 1}/${MAX_RETRIES})`)
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS * (attempt + 1)))
|
||||
continue
|
||||
}
|
||||
console.error(`[enable-banking] Request failed for ${endpoint}`, {
|
||||
attempt,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
name: error instanceof Error ? error.name : undefined,
|
||||
isTimeout: isAbort,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -220,9 +231,17 @@ export async function getASPSPs(country: string = 'SE'): Promise<ASPSP[]> {
|
||||
const response = await authenticatedFetchWithRetry(`/aspsps?${params.toString()}`)
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
console.error('Failed to fetch ASPSPs:', error)
|
||||
throw new Error('Failed to fetch banks')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] getASPSPs failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
country,
|
||||
psuType,
|
||||
sandbox: isSandbox,
|
||||
apiUrl: ENABLE_BANKING_API_URL,
|
||||
})
|
||||
throw new Error(`Failed to fetch banks (${response.status})`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
@@ -294,9 +313,19 @@ export async function startAuthorization(
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
console.error('Failed to start authorization:', error)
|
||||
throw new Error('Failed to start bank connection')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] startAuthorization failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
aspspName,
|
||||
aspspCountry,
|
||||
psuType,
|
||||
redirectUrl,
|
||||
apiUrl: ENABLE_BANKING_API_URL,
|
||||
requestBody: JSON.stringify(requestBody),
|
||||
})
|
||||
throw new Error(`Failed to start bank connection (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -314,9 +343,16 @@ export async function createSession(code: string): Promise<SessionResponse> {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text()
|
||||
console.error('Failed to create session:', error)
|
||||
throw new Error('Failed to create bank session')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] createSession failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
hasCode: !!code,
|
||||
codeLength: code?.length,
|
||||
apiUrl: ENABLE_BANKING_API_URL,
|
||||
})
|
||||
throw new Error(`Failed to create bank session (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -331,7 +367,14 @@ export async function getSession(sessionId: string): Promise<SessionResponse> {
|
||||
const response = await authenticatedFetchWithRetry(`/sessions/${sessionId}`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get session')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] getSession failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
sessionId,
|
||||
})
|
||||
throw new Error(`Failed to get session (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -348,7 +391,14 @@ export async function deleteSession(sessionId: string): Promise<void> {
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to revoke session')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] deleteSession failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
sessionId,
|
||||
})
|
||||
throw new Error(`Failed to revoke session (${response.status}): ${body}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +411,14 @@ export async function getAccountBalances(accountUid: string): Promise<Balance[]>
|
||||
const response = await authenticatedFetchWithRetry(`/accounts/${accountUid}/balances`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get account balances')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] getAccountBalances failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
accountUid,
|
||||
})
|
||||
throw new Error(`Failed to get account balances (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
const data: BalanceResponse = await response.json()
|
||||
@@ -417,7 +474,17 @@ export async function getAccountTransactions(
|
||||
const response = await authenticatedFetchWithRetry(endpoint)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get transactions')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] getAccountTransactions failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
accountUid,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
hasContinuationKey: !!continuationKey,
|
||||
})
|
||||
throw new Error(`Failed to get transactions (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
@@ -482,7 +549,18 @@ export async function getAllTransactionsWithRaw(
|
||||
const response = await authenticatedFetchWithRetry(endpoint)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to get transactions')
|
||||
const body = await response.text()
|
||||
console.error('[enable-banking] getAllTransactionsWithRaw failed', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
body,
|
||||
accountUid,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
page,
|
||||
hasContinuationKey: !!continuationKey,
|
||||
})
|
||||
throw new Error(`Failed to get transactions (${response.status}): ${body}`)
|
||||
}
|
||||
|
||||
const rawText = await response.text()
|
||||
|
||||
@@ -38,12 +38,27 @@ export async function syncAccountTransactions(
|
||||
toDate: string,
|
||||
ingest: IngestFn = defaultIngest
|
||||
): Promise<SyncResult> {
|
||||
console.log('[enable-banking] syncAccountTransactions starting', {
|
||||
connectionId,
|
||||
accountUid: account.uid,
|
||||
accountIban: account.iban,
|
||||
fromDate,
|
||||
toDate,
|
||||
})
|
||||
|
||||
const { transactions, rawPages } = await getAllTransactionsWithRaw(
|
||||
account.uid,
|
||||
fromDate,
|
||||
toDate,
|
||||
)
|
||||
|
||||
console.log('[enable-banking] Fetched transactions from API', {
|
||||
connectionId,
|
||||
accountUid: account.uid,
|
||||
transactionCount: transactions.length,
|
||||
rawPageCount: rawPages.length,
|
||||
})
|
||||
|
||||
const bankTransactions = transactions.map(tx => convertTransaction(tx, account.currency))
|
||||
|
||||
// Convert Enable Banking format to generic RawTransaction
|
||||
@@ -62,6 +77,14 @@ export async function syncAccountTransactions(
|
||||
|
||||
const ingestResult = await ingest(supabase, userId, rawTransactions)
|
||||
|
||||
console.log('[enable-banking] Ingest result', {
|
||||
connectionId,
|
||||
accountUid: account.uid,
|
||||
imported: ingestResult.imported,
|
||||
duplicates: ingestResult.duplicates,
|
||||
errors: ingestResult.errors,
|
||||
})
|
||||
|
||||
// Archive raw PSD2 API responses as räkenskapsinformation (BFL 7 kap)
|
||||
for (let i = 0; i < rawPages.length; i++) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user