From 849a392d1454929eee2baf84cc61498f70a6e2cc Mon Sep 17 00:00:00 2001 From: Catherine Renelle <32781029+catrenelle@users.noreply.github.com> Date: Sun, 8 Feb 2026 16:29:16 -0500 Subject: [PATCH] Fix Plaid link token error handling and show user-visible errors Validate Plaid credentials are configured before calling the API, and validate the returned link token before passing it to Plaid Link JS. Show error snackbars for all bank sync failures instead of only logging to console. Co-Authored-By: Claude Opus 4.6 --- frontend/src/views/settings/SettingsView.vue | 36 ++++++++++++++----- .../PlaidSyncProvider.cs | 4 +++ 2 files changed, 31 insertions(+), 9 deletions(-) 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,