feat(investments): warn and offer retry after exhausted read retries (WealthLedger/frontend#55) (#136)
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 9s
Frontend verify / verify (push) Failing after 7m5s

This commit was merged in pull request #136.
This commit is contained in:
2026-11-03 09:49:08 +03:00
parent aaee9591d7
commit 8ce1e8e444
36 changed files with 1025 additions and 80 deletions
@@ -16,7 +16,7 @@ import {
import { useIncomeSummary } from "@/entities/income";
import { usePortfolio } from "@/entities/portfolio";
import { useScreenSync } from "@/features/tbank-connection";
import { apiErrorCode, isBrokerageAccessLoss } from "@/shared/api";
import { apiErrorCode, isBrokerageAccessLoss, readRetry } from "@/shared/api";
import { useInitialAvailability } from "@/shared/lib";
import {
@@ -45,6 +45,7 @@ type AccountPageContent = {
accountId?: string;
incomeSummary: AccountPageIncomeSummary;
partial?: boolean;
readRetry: AccountPageReadRetry;
recentEvents: AccountPageRecentEvents;
recentOperations: AccountPageRecentOperations;
source?: InvestmentResourceMeta;
@@ -68,10 +69,12 @@ type AccountPageLoadedViewModel = AccountPageContent & {
unrealizedReturns: Portfolio["totalsByCurrency"];
};
type AccountPageReadRetry = ReturnType<typeof readRetry>;
type AccountPageViewModel
= | AccountPageEmptyViewModel
| AccountPageLoadedViewModel
| { code?: string; state: "error" }
| { code?: string; readRetry: AccountPageReadRetry; state: "error" }
| { state: "loading" };
function useAccountPageViewModel(): AccountPageViewModel {
@@ -113,6 +116,8 @@ function useAccountPageViewModel(): AccountPageViewModel {
]);
const code = apiErrorCode(portfolio.error);
// The portfolio drives this screen; the retry repeats only its GET.
const retry = readRetry([portfolio]);
const isAccessFailure = isBrokerageAccessLoss(code);
const availability = useInitialAvailability({
@@ -122,12 +127,13 @@ function useAccountPageViewModel(): AccountPageViewModel {
isFetching: portfolio.isFetching,
});
if (isAccessFailure) return { code, state: "error" as const };
if (isAccessFailure)
return { code, readRetry: retry, state: "error" as const };
if (!portfolio.data)
return {
state: availability === "unavailable" ? "error" : "loading",
} as const;
return availability === "unavailable"
? { code, readRetry: retry, state: "error" as const }
: { state: "loading" as const };
const account = accounts.data?.find(item => item.id === accountId);
const recentOperations = toRecentOperations(accountId, operations);
@@ -144,6 +150,7 @@ function useAccountPageViewModel(): AccountPageViewModel {
account,
accountId,
incomeSummary,
readRetry: retry,
recentEvents,
recentOperations,
source: portfolio.meta,
@@ -181,6 +188,7 @@ function useAccountPageViewModel(): AccountPageViewModel {
partial: isPartial,
portfolio: portfolio.data,
positionSections: toAccountPagePositionSections(portfolio.data),
readRetry: retry,
recentEvents,
recentOperations,
slice: current.slice,