fix-spa-review-findings (ornith-35): fetch timeout+res.ok, AuthSection loop fix, me() timeout, enable2fa finally, TOTP mask toggle, snapshot backoff, dup key removed

Cross-review by qwopus35b pending. SPA lint 2/2 green.
This commit is contained in:
Investor Flow Build
2026-06-30 08:54:33 -04:00
parent ce26a0ca15
commit cc1213fb64
4 changed files with 140 additions and 28 deletions
-1
View File
@@ -42,7 +42,6 @@ export const UI_STRINGS = {
drawdownNote: "A default max-drawdown tolerance is set from your experience level (you can change it later).",
completeOnboardingButton: "Finish setup",
onboardingDone: "Setup complete — your starter watchlist is tracking.",
onboardingDone: "Setup complete — your starter watchlist is tracking.",
// Chart Lab (Slice 5c)
chartLabTitle: "Chart Lab — price, volume, and indicator overlays",
+37 -7
View File
@@ -45,22 +45,52 @@ export interface AuthUser {
onboarded?: boolean;
}
// 8-second timeout for all tRPC calls to prevent hung requests.
const TRPC_TIMEOUT_MS = 8_000;
async function parse<T>(res: Response): Promise<T> {
if (!res.ok) {
const body = await res.text();
throw new Error(`tRPC ${res.status}: ${body}`);
}
const json = await res.json();
if (json?.error) {
const msg = json.error.message ?? `tRPC error ${json.error.data?.code ?? ""}`;
throw new Error(typeof msg === "string" ? msg : "tRPC error");
throw new Error(`tRPC error: ${json.error.message ?? json.error.data?.code ?? "unknown"}`);
}
return json.result.data as T;
}
async function trpcQuery<T>(procedure: string, input?: unknown): Promise<T> {
const search = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
const res = await fetch(`/api/trpc/${procedure}${search}`, { headers: { "content-type": "application/json" }, credentials: "include" });
return parse<T>(res);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TRPC_TIMEOUT_MS);
try {
const search = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
const res = await fetch(`/api/trpc/${procedure}${search}`, {
headers: { "content-type": "application/json" },
credentials: "include",
signal: controller.signal,
});
return parse<T>(res);
} finally {
clearTimeout(timer);
}
}
async function trpcMutate<T>(procedure: string, input: unknown): Promise<T> {
const res = await fetch(`/api/trpc/${procedure}`, { method: "POST", headers: { "content-type": "application/json" }, credentials: "include", body: JSON.stringify(input) });
return parse<T>(res);
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TRPC_TIMEOUT_MS);
try {
const res = await fetch(`/api/trpc/${procedure}`, {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "include",
body: JSON.stringify(input),
signal: controller.signal,
});
return parse<T>(res);
} finally {
clearTimeout(timer);
}
}
export type Complexity = "beginner" | "intermediate" | "advanced";