feat: add quiet reading with rare confirmations

Part of the quiet-user-feedback change; docs and specs land in their own repositories.
This commit is contained in:
2026-10-06 07:01:58 +03:00
parent c096fc70ef
commit 5b30bb802a
77 changed files with 1352 additions and 1259 deletions
@@ -1,45 +1,54 @@
import {
type BrokerageAccountCard,
brokerageAccountCards,
useBrokerageAccounts,
} from "@/entities/brokerage-account";
import { useScreenSync } from "@/features/tbank-connection";
import { apiErrorCode } from "@/shared/api";
import { ApiErrorCode, apiErrorCode, isApiErrorCode } from "@/shared/api";
import { useInitialAvailability } from "@/shared/lib";
function useAccountsPageViewModel() {
type AccountsPageViewModel
= | { accounts: BrokerageAccountCard[]; state: "loaded" }
| { code?: string; state: "error" }
| { state: "empty" }
| { state: "loading" };
function isAccountsAccessFailure(code?: string) {
return isApiErrorCode(code, ApiErrorCode.InvalidTbankToken)
|| isApiErrorCode(code, ApiErrorCode.TbankNotConnected);
}
function useAccountsPageViewModel(): AccountsPageViewModel {
const accounts = useBrokerageAccounts();
const sync = useScreenSync({ screen: "accounts" }, [accounts]);
useScreenSync({ screen: "accounts" }, [accounts]);
if (
!accounts.data
|| (accounts.meta?.availability === "partial" && accounts.data.length === 0)
) {
if (!accounts.isError && accounts.meta?.sync.state !== "failed")
return { state: "loading" as const };
const code = apiErrorCode(accounts.error);
const isAccessFailure = isAccountsAccessFailure(code);
const hasResult = !!accounts.data
&& !(accounts.meta?.availability === "partial" && accounts.data.length === 0);
const availability = useInitialAvailability({
hasContent: hasResult,
identity: "accounts",
isError: accounts.isError || accounts.meta?.sync.state === "failed",
isFetching: accounts.isFetching,
});
if (isAccessFailure) return { code, state: "error" as const };
if (!hasResult || !accounts.data)
return {
code: apiErrorCode(accounts.error),
retry: sync.retry,
state: "error" as const,
};
}
const refresh = {
lastSuccessfulAt: accounts.meta?.lastSuccessfulAt,
refreshError: accounts.isError || sync.transportError || sync.sourceError,
refreshing: accounts.isFetching || sync.refreshing,
retry: sync.retry,
sourceError: sync.sourceError,
};
state: availability === "unavailable" ? "error" : "loading",
} as const;
if (accounts.data.length === 0)
return {
...refresh,
state: "empty" as const,
};
return {
accounts: brokerageAccountCards(accounts.data, accounts.meta?.dependencies),
...refresh,
state: "loaded" as const,
};
}