diff --git a/frontend/src/views/settings/SettingsView.vue b/frontend/src/views/settings/SettingsView.vue index 48946c7..3aac2f3 100644 --- a/frontend/src/views/settings/SettingsView.vue +++ b/frontend/src/views/settings/SettingsView.vue @@ -242,10 +242,13 @@ - + {{ syncResultMessage }} + + {{ errorMessage }} + @@ -341,6 +344,10 @@ const deleteLoading = ref(false) const showSyncResult = ref(false) const syncResultMessage = ref('') +// Error display +const showError = ref(false) +const errorMessage = ref('') + // Sync logs const syncLogs = ref([]) const logsLoading = ref(false) @@ -420,6 +427,13 @@ async function startPlaidLink() { try { const { data: linkData } = await bankSyncApi.createLinkToken() + if (!linkData.linkToken) { + errorMessage.value = 'Plaid returned an empty link token. Check your Plaid credentials in appsettings.json.' + showError.value = true + plaidLoading.value = false + return + } + // Dynamically load Plaid Link script await loadPlaidScript() @@ -431,8 +445,9 @@ async function startPlaidLink() { connections.value.unshift(conn) syncIntervals[conn.id] = conn.syncIntervalMinutes openMappingDialog(conn) - } catch (err) { - console.error('Failed to complete Plaid link:', err) + } catch (err: any) { + errorMessage.value = err.response?.data?.error || 'Failed to complete Plaid link' + showError.value = true } }, onExit: () => { @@ -440,8 +455,9 @@ async function startPlaidLink() { } }) plaid.open() - } catch (err) { - console.error('Failed to create link token:', err) + } catch (err: any) { + errorMessage.value = err.response?.data?.error || 'Failed to create Plaid link token. Check your Plaid credentials.' + showError.value = true plaidLoading.value = false } } @@ -469,8 +485,9 @@ async function connectSimpleFin() { showSimpleFinDialog.value = false simpleFinToken.value = '' openMappingDialog(conn) - } catch (err) { - console.error('Failed to connect SimpleFIN:', err) + } catch (err: any) { + errorMessage.value = err.response?.data?.error || 'Failed to connect via SimpleFIN. Check your setup token.' + showError.value = true } finally { simpleFinLoading.value = false } @@ -540,8 +557,9 @@ async function syncNow(conn: SyncConnection) { syncResultMessage.value = `Synced ${conn.institutionName}: ${data.transactionsImported} imported, ${data.transactionsSkipped} skipped` showSyncResult.value = true await loadConnections() - } catch (err) { - console.error('Sync failed:', err) + } catch (err: any) { + errorMessage.value = err.response?.data?.error || 'Sync failed' + showError.value = true } finally { syncingConnections[conn.id] = false } diff --git a/src/Purrse.Plugins.BankSync/PlaidSyncProvider.cs b/src/Purrse.Plugins.BankSync/PlaidSyncProvider.cs index c3e941d..15895f4 100644 --- a/src/Purrse.Plugins.BankSync/PlaidSyncProvider.cs +++ b/src/Purrse.Plugins.BankSync/PlaidSyncProvider.cs @@ -43,6 +43,10 @@ public class PlaidSyncProvider : IAccountSyncProvider public async Task CreateLinkTokenAsync(string userId) { + if (string.IsNullOrEmpty(_config.ClientId) || string.IsNullOrEmpty(_config.Secret)) + throw new InvalidOperationException( + "Plaid credentials are not configured. Set BankSync:Plaid:ClientId and BankSync:Plaid:Secret in appsettings.json."); + var response = await _client.LinkTokenCreateAsync(new LinkTokenCreateRequest { ClientId = _config.ClientId,