From 4e9d9e6048a6d5d441e3612da5a541665407511f Mon Sep 17 00:00:00 2001 From: MOHAN Date: Wed, 26 Aug 2026 20:41:23 +0530 Subject: [PATCH] Route bank-connection page through the queued auth client /app/connect fired 3 authenticated requests concurrently on mount (link token, accounts, Google status) via raw fetch, bypassing both the new request queue and any refresh-on-401 handling entirely. Same nonce-race class of bug as the dashboard, plus no recovery path at all when a call failed. Converted every call on this page to apiFetch. --- app/app/connect/page.tsx | 81 ++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/app/app/connect/page.tsx b/app/app/connect/page.tsx index 55b203e..5c9a31c 100644 --- a/app/app/connect/page.tsx +++ b/app/app/connect/page.tsx @@ -3,6 +3,7 @@ import { AppShell } from "../../../components/app-shell"; import { useCallback, useEffect, useState } from "react"; import { usePlaidLink } from "react-plaid-link"; +import { apiFetch } from "@/lib/api"; type Account = { viewRef: string; @@ -75,9 +76,11 @@ export default function ConnectPage() { const createLinkToken = useCallback(async () => { setStatus("Requesting Plaid link token..."); try { - const res = await fetch("/api/plaid/link-token", { method: "POST" }); - const payload = await res.json(); - if (!res.ok || payload.error) { + const payload = await apiFetch<{ linkToken?: string; link_token?: string }>( + "/api/plaid/link-token", + { method: "POST" } + ); + if (payload.error) { setStatus(payload.error?.message ?? "Unable to create link token."); return; } @@ -94,19 +97,16 @@ export default function ConnectPage() { }, []); const loadAccounts = useCallback(async () => { - const res = await fetch("/api/accounts"); - if (!res.ok) { - return; - } - const payload = await res.json(); - setAccounts(payload.data?.accounts ?? payload.data ?? []); + const payload = await apiFetch<{ accounts?: Account[] } | Account[]>("/api/accounts"); + if (payload.error) return; + const data = payload.data as { accounts?: Account[] } | Account[] | undefined; + setAccounts((Array.isArray(data) ? data : data?.accounts) ?? []); }, []); const loadGoogleStatus = useCallback(async () => { - const res = await fetch("/api/google/status"); - if (!res.ok) return; - const payload = await res.json(); - setGoogleStatus(payload.data ?? payload); + const payload = await apiFetch("/api/google/status"); + if (payload.error) return; + setGoogleStatus(payload.data ?? null); }, []); const loadTellerScript = useCallback(() => { @@ -149,13 +149,11 @@ export default function ConnectPage() { if (linkMode === "update" && updateAccountRef) { setStatus("Finishing bank reconnection..."); try { - const res = await fetch("/api/plaid/repair-complete", { + const payload = await apiFetch("/api/plaid/repair-complete", { method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify({ accountId: updateAccountRef }) }); - const payload = await res.json(); - if (!res.ok || payload.error) { + if (payload.error) { setStatus(payload.error?.message ?? "Unable to finish bank reconnection."); return; } @@ -178,13 +176,11 @@ export default function ConnectPage() { setStatus("Exchanging public token..."); try { - const res = await fetch("/api/plaid/exchange", { + const payload = await apiFetch("/api/plaid/exchange", { method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify({ publicToken }) }); - const payload = await res.json(); - if (!res.ok || payload.error) { + if (payload.error) { setStatus(payload.error?.message ?? "Unable to exchange token."); return; } @@ -221,12 +217,10 @@ export default function ConnectPage() { accountType: manualType, mask: manualAccount.slice(-4) }; - fetch("/api/accounts/manual", { + apiFetch("/api/accounts/manual", { method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }) - .then((res) => res.json()) .then((data) => { if (data?.error) { setStatus(data.error?.message ?? "Unable to save manual account."); @@ -247,13 +241,14 @@ export default function ConnectPage() { const startUpdateMode = async (accountRef: string) => { setStatus("Requesting Plaid update-mode link token..."); try { - const res = await fetch("/api/plaid/update-link-token", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ accountId: accountRef }) - }); - const payload = await res.json(); - if (!res.ok || payload.error) { + const payload = await apiFetch<{ linkToken?: string; link_token?: string }>( + "/api/plaid/update-link-token", + { + method: "POST", + body: JSON.stringify({ accountId: accountRef }) + } + ); + if (payload.error) { setStatus(payload.error?.message ?? "Unable to create update-mode link token."); return; } @@ -283,9 +278,10 @@ export default function ConnectPage() { return; } - const configRes = await fetch("/api/teller/config"); - const configPayload = await configRes.json(); - if (!configRes.ok || configPayload.error) { + const configPayload = await apiFetch<{ applicationId: string; environment?: string; products: string[] }>( + "/api/teller/config" + ); + if (configPayload.error) { setStatus(configPayload.error?.message ?? "Teller is not configured."); return; } @@ -296,13 +292,11 @@ export default function ConnectPage() { products: configPayload.data.products, onSuccess: async (enrollment) => { setStatus("Importing Teller accounts..."); - const res = await fetch("/api/teller/enrollment", { + const payload = await apiFetch<{ accountCount?: number }>("/api/teller/enrollment", { method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify(enrollment) }); - const payload = await res.json(); - if (!res.ok || payload.error) { + if (payload.error) { setStatus(payload.error?.message ?? "Unable to import Teller accounts."); return; } @@ -317,9 +311,8 @@ export default function ConnectPage() { const syncTeller = async () => { setStatus("Syncing Teller transactions..."); - const res = await fetch("/api/teller/sync", { method: "POST" }); - const payload = await res.json(); - if (!res.ok || payload.error) { + const payload = await apiFetch<{ created?: number }>("/api/teller/sync", { method: "POST" }); + if (payload.error) { setStatus(payload.error?.message ?? "Unable to sync Teller transactions."); return; } @@ -329,13 +322,11 @@ export default function ConnectPage() { const enableSheetsMirror = async () => { setStatus("Enabling Sheets-first mirror mode..."); - const res = await fetch("/api/google/data-system-mode", { + const payload = await apiFetch("/api/google/data-system-mode", { method: "POST", - headers: { "Content-Type": "application/json" }, body: JSON.stringify({ mode: "google_sheets_mirror" }) }); - const payload = await res.json(); - if (!res.ok || payload.error) { + if (payload.error) { setStatus(payload.error?.message ?? "Unable to enable Sheets-first mirror mode."); return; }