refactor: enforce frontend view model boundaries
Frontend verify / commits (pull_request) Successful in 28s
Frontend verify / security (pull_request) Successful in 10s
Frontend verify / verify (pull_request) Successful in 8m19s

This commit is contained in:
2026-09-28 18:48:23 +03:00
parent 5b69003516
commit ca0ad4f824
287 changed files with 8558 additions and 4538 deletions
+40 -7
View File
@@ -21,68 +21,93 @@ import { formatDate, instrumentLogoUrl } from "@/shared/lib";
const periods: StockPeriod[] = ["month", "halfYear", "year", "all"];
const filters = ["all", "trades", "dividends", "other"] as const;
type StockEventFilter = (typeof filters)[number];
const categories: Record<StockEventFilter, OperationCategory[]> = {
all: [],
dividends: ["income"],
other: ["tax", "fee", "transfer", "other"],
trades: ["trade"],
};
type StockPageVm = ReturnType<typeof useStockPageVm>;
function useStockPageVm() {
const { stockId = "" } = useParams();
const stock = useStock(stockId);
const uid = stock.data?.instrumentUid ?? "";
const [parameters, setParameters] = useSearchParams();
const period
= periods.find(value => value === parameters.get("period")) ?? "month";
const filter
= filters.find(value => value === parameters.get("events")) ?? "all";
const quote = useStockQuote(uid);
const candles = useStockCandles(uid, period);
const position = useStockPosition(uid);
const company = useStockCompany(uid);
const dividends = useStockDividends(uid);
const history = useInstrumentOperations(uid, filter, /* eslint-disable-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name. */ categories[filter]);
const history = useInstrumentOperations(
uid,
filter,
// eslint-disable-next-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name.
categories[filter],
);
const { operations } = history;
const sync = useScreenSync(
{
accountId: "all",
candlePeriod: period,
categories: /* eslint-disable-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name. */ categories[filter],
categories:
// eslint-disable-next-line security/detect-object-injection -- This lookup uses a fixed typed key set, not an arbitrary external property name.
categories[filter],
instrumentUid: uid,
limit: 20,
screen: "stock",
},
[stock, quote, candles, position, company, dividends, operations],
);
const setParameter = (key: string, value: string) => {
const next = new URLSearchParams(parameters);
next.set(key, value);
setParameters(next, { replace: true });
};
const chartData
= candles.data?.candles.map(candle => ({
...candle,
date: formatDate(candle.time),
price: Number(candle.close),
})) ?? [];
const average = position.data?.averagePurchasePrice;
const averageLine
= average && average.currency === candles.data?.currency
? Number(average.value)
: /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null;
: null; // eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
const companyData = company.data;
const search = parameters.size > 0 ? `?${parameters.toString()}` : "";
return {
averageLine:
averageLine !== null && Number.isFinite(averageLine) ? averageLine : /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null,
averageLine !== null && Number.isFinite(averageLine)
? averageLine
: null, // eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
candles: candles.data,
candlesState: section(candles, sync.retry),
canonicalTo:
stock.data?.slug && stock.data.slug !== stockId
? `/stocks/${encodeURIComponent(stock.data.slug)}${search}`
: /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null,
: null, // eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
chartData,
code: apiErrorCode(stock.error),
company: companyData,
@@ -92,8 +117,16 @@ function useStockPageVm() {
eventRows:
operations.data?.items.map(item => ({
...item,
logoName: item.logoName ?? stock.data?.logoName ?? /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null,
ticker: item.ticker ?? stock.data?.ticker ?? /* eslint-disable-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model. */ null,
logoName:
item.logoName
?? stock.data?.logoName
// eslint-disable-next-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
?? null,
ticker:
item.ticker
?? stock.data?.ticker
// eslint-disable-next-line unicorn/no-null -- Preserve the nullable domain value consumed by this view model.
?? null,
})) ?? [],
filter,
hasIncompleteCandle: