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.
This commit is contained in:
MOHAN 2026-08-26 20:41:23 +05:30
parent d6b25dd78d
commit 4e9d9e6048

View File

@ -3,6 +3,7 @@
import { AppShell } from "../../../components/app-shell"; import { AppShell } from "../../../components/app-shell";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { usePlaidLink } from "react-plaid-link"; import { usePlaidLink } from "react-plaid-link";
import { apiFetch } from "@/lib/api";
type Account = { type Account = {
viewRef: string; viewRef: string;
@ -75,9 +76,11 @@ export default function ConnectPage() {
const createLinkToken = useCallback(async () => { const createLinkToken = useCallback(async () => {
setStatus("Requesting Plaid link token..."); setStatus("Requesting Plaid link token...");
try { try {
const res = await fetch("/api/plaid/link-token", { method: "POST" }); const payload = await apiFetch<{ linkToken?: string; link_token?: string }>(
const payload = await res.json(); "/api/plaid/link-token",
if (!res.ok || payload.error) { { method: "POST" }
);
if (payload.error) {
setStatus(payload.error?.message ?? "Unable to create link token."); setStatus(payload.error?.message ?? "Unable to create link token.");
return; return;
} }
@ -94,19 +97,16 @@ export default function ConnectPage() {
}, []); }, []);
const loadAccounts = useCallback(async () => { const loadAccounts = useCallback(async () => {
const res = await fetch("/api/accounts"); const payload = await apiFetch<{ accounts?: Account[] } | Account[]>("/api/accounts");
if (!res.ok) { if (payload.error) return;
return; const data = payload.data as { accounts?: Account[] } | Account[] | undefined;
} setAccounts((Array.isArray(data) ? data : data?.accounts) ?? []);
const payload = await res.json();
setAccounts(payload.data?.accounts ?? payload.data ?? []);
}, []); }, []);
const loadGoogleStatus = useCallback(async () => { const loadGoogleStatus = useCallback(async () => {
const res = await fetch("/api/google/status"); const payload = await apiFetch<GoogleStatus>("/api/google/status");
if (!res.ok) return; if (payload.error) return;
const payload = await res.json(); setGoogleStatus(payload.data ?? null);
setGoogleStatus(payload.data ?? payload);
}, []); }, []);
const loadTellerScript = useCallback(() => { const loadTellerScript = useCallback(() => {
@ -149,13 +149,11 @@ export default function ConnectPage() {
if (linkMode === "update" && updateAccountRef) { if (linkMode === "update" && updateAccountRef) {
setStatus("Finishing bank reconnection..."); setStatus("Finishing bank reconnection...");
try { try {
const res = await fetch("/api/plaid/repair-complete", { const payload = await apiFetch("/api/plaid/repair-complete", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ accountId: updateAccountRef }) body: JSON.stringify({ accountId: updateAccountRef })
}); });
const payload = await res.json(); if (payload.error) {
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to finish bank reconnection."); setStatus(payload.error?.message ?? "Unable to finish bank reconnection.");
return; return;
} }
@ -178,13 +176,11 @@ export default function ConnectPage() {
setStatus("Exchanging public token..."); setStatus("Exchanging public token...");
try { try {
const res = await fetch("/api/plaid/exchange", { const payload = await apiFetch("/api/plaid/exchange", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ publicToken }) body: JSON.stringify({ publicToken })
}); });
const payload = await res.json(); if (payload.error) {
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to exchange token."); setStatus(payload.error?.message ?? "Unable to exchange token.");
return; return;
} }
@ -221,12 +217,10 @@ export default function ConnectPage() {
accountType: manualType, accountType: manualType,
mask: manualAccount.slice(-4) mask: manualAccount.slice(-4)
}; };
fetch("/api/accounts/manual", { apiFetch("/api/accounts/manual", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload) body: JSON.stringify(payload)
}) })
.then((res) => res.json())
.then((data) => { .then((data) => {
if (data?.error) { if (data?.error) {
setStatus(data.error?.message ?? "Unable to save manual account."); setStatus(data.error?.message ?? "Unable to save manual account.");
@ -247,13 +241,14 @@ export default function ConnectPage() {
const startUpdateMode = async (accountRef: string) => { const startUpdateMode = async (accountRef: string) => {
setStatus("Requesting Plaid update-mode link token..."); setStatus("Requesting Plaid update-mode link token...");
try { try {
const res = await fetch("/api/plaid/update-link-token", { const payload = await apiFetch<{ linkToken?: string; link_token?: string }>(
method: "POST", "/api/plaid/update-link-token",
headers: { "Content-Type": "application/json" }, {
body: JSON.stringify({ accountId: accountRef }) method: "POST",
}); body: JSON.stringify({ accountId: accountRef })
const payload = await res.json(); }
if (!res.ok || payload.error) { );
if (payload.error) {
setStatus(payload.error?.message ?? "Unable to create update-mode link token."); setStatus(payload.error?.message ?? "Unable to create update-mode link token.");
return; return;
} }
@ -283,9 +278,10 @@ export default function ConnectPage() {
return; return;
} }
const configRes = await fetch("/api/teller/config"); const configPayload = await apiFetch<{ applicationId: string; environment?: string; products: string[] }>(
const configPayload = await configRes.json(); "/api/teller/config"
if (!configRes.ok || configPayload.error) { );
if (configPayload.error) {
setStatus(configPayload.error?.message ?? "Teller is not configured."); setStatus(configPayload.error?.message ?? "Teller is not configured.");
return; return;
} }
@ -296,13 +292,11 @@ export default function ConnectPage() {
products: configPayload.data.products, products: configPayload.data.products,
onSuccess: async (enrollment) => { onSuccess: async (enrollment) => {
setStatus("Importing Teller accounts..."); setStatus("Importing Teller accounts...");
const res = await fetch("/api/teller/enrollment", { const payload = await apiFetch<{ accountCount?: number }>("/api/teller/enrollment", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(enrollment) body: JSON.stringify(enrollment)
}); });
const payload = await res.json(); if (payload.error) {
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to import Teller accounts."); setStatus(payload.error?.message ?? "Unable to import Teller accounts.");
return; return;
} }
@ -317,9 +311,8 @@ export default function ConnectPage() {
const syncTeller = async () => { const syncTeller = async () => {
setStatus("Syncing Teller transactions..."); setStatus("Syncing Teller transactions...");
const res = await fetch("/api/teller/sync", { method: "POST" }); const payload = await apiFetch<{ created?: number }>("/api/teller/sync", { method: "POST" });
const payload = await res.json(); if (payload.error) {
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to sync Teller transactions."); setStatus(payload.error?.message ?? "Unable to sync Teller transactions.");
return; return;
} }
@ -329,13 +322,11 @@ export default function ConnectPage() {
const enableSheetsMirror = async () => { const enableSheetsMirror = async () => {
setStatus("Enabling Sheets-first mirror mode..."); 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", method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mode: "google_sheets_mirror" }) body: JSON.stringify({ mode: "google_sheets_mirror" })
}); });
const payload = await res.json(); if (payload.error) {
if (!res.ok || payload.error) {
setStatus(payload.error?.message ?? "Unable to enable Sheets-first mirror mode."); setStatus(payload.error?.message ?? "Unable to enable Sheets-first mirror mode.");
return; return;
} }