refactor: refactor foundation

This commit is contained in:
2026-08-14 09:43:19 +03:00
parent 2e32d226d3
commit da585b157a
54 changed files with 4822 additions and 535 deletions
@@ -1,6 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "../../../shared/api";
import type { paths } from "../../../shared/api/generated/schema";
import { apiQueryClient } from "@/shared/api";
import type { paths } from "@/shared/api/generated/schema";
export const brokerageAccountsQueryKey = ["brokerage-accounts"] as const;
@@ -13,15 +13,6 @@ export function useBrokerageAccounts(accessToken: string | null) {
enabled: Boolean(accessToken),
staleTime: 60_000,
retry: false,
queryFn: async () => {
const { data, error } = await apiClient.GET(
"/api/v1/brokerage-accounts",
{
headers: { Authorization: `Bearer ${accessToken}` },
},
);
if (error) throw error;
return data as BrokerageAccount[];
},
...apiQueryClient.queryOptions("get", "/api/v1/brokerage-accounts"),
});
}
@@ -2,26 +2,13 @@ import { MantineProvider } from "@mantine/core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { renderHook, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
const { get } = vi.hoisted(() => ({ get: vi.fn() }));
vi.mock("../../../shared/api", () => ({ apiClient: { GET: get } }));
import { portfolioQueryPolicy, usePortfolio } from "./usePortfolio";
import { usePortfolio } from "./usePortfolio";
describe("usePortfolio", () => {
it("uses the account endpoint with a 30 second cache and no retry", async () => {
const client = new QueryClient();
get.mockResolvedValueOnce({
data: {
lastSuccessfulAt: "2026-08-13T10:00:00Z",
isStale: false,
totalsByCurrency: [],
positions: [],
cashBalances: [],
},
});
const wrapper = ({ children }: { children: ReactNode }) => (
<MantineProvider>
<QueryClientProvider client={client}>{children}</QueryClientProvider>
@@ -33,13 +20,6 @@ describe("usePortfolio", () => {
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(get).toHaveBeenCalledWith(
"/api/v1/brokerage-accounts/{accountId}/portfolio",
{
params: { path: { accountId: "account-1" } },
headers: { Authorization: "Bearer token" },
},
);
expect(portfolioQueryPolicy).toEqual({ staleTime: 30_000, retry: false });
expect(result.current.data).toMatchObject({ positions: [] });
});
});
+9 -15
View File
@@ -1,11 +1,11 @@
import { useQuery } from "@tanstack/react-query";
import { apiClient } from "../../../shared/api";
import type { paths } from "../../../shared/api/generated/schema";
import { apiQueryClient } from "@/shared/api";
import type { paths } from "@/shared/api/generated/schema";
export const portfolioQueryKey = (accountId: string) =>
const portfolioQueryKey = (accountId: string) =>
["portfolio", accountId] as const;
export const portfolioQueryPolicy = {
const portfolioQueryPolicy = {
staleTime: 30_000,
retry: false,
} as const;
@@ -21,16 +21,10 @@ export function usePortfolio(
queryKey: portfolioQueryKey(accountId ?? ""),
enabled: Boolean(accountId && accessToken),
...portfolioQueryPolicy,
queryFn: async () => {
const { data, error } = await apiClient.GET(
"/api/v1/brokerage-accounts/{accountId}/portfolio",
{
params: { path: { accountId: accountId ?? "" } },
headers: { Authorization: `Bearer ${accessToken}` },
},
);
if (error) throw error;
return data as Portfolio;
},
...apiQueryClient.queryOptions(
"get",
"/api/v1/brokerage-accounts/{accountId}/portfolio",
{ params: { path: { accountId: accountId ?? "" } } },
),
});
}
+1 -6
View File
@@ -1,11 +1,6 @@
export type { Portfolio } from "./api/usePortfolio";
export { usePortfolio } from "./api/usePortfolio";
export {
portfolioQueryKey,
portfolioQueryPolicy,
usePortfolio,
} from "./api/usePortfolio";
export {
formatDecimal,
formatMoney,
formatSnapshotTime,
portfolioErrorCode,
+4 -2
View File
@@ -1,9 +1,11 @@
import { describe, expect, it } from "vitest";
import { formatDecimal, formatMoney } from "./display";
import { formatMoney } from "./display";
describe("portfolio display helpers", () => {
it("formats decimal strings without financial arithmetic", () => {
expect(formatDecimal("-1234567.89")).toBe("-1 234 567,89");
expect(formatMoney({ value: "-1234567.89", currency: "RUB" })).toBe(
"-1 234 567,89 ₽",
);
expect(formatMoney({ value: "10", currency: "RUB" })).toBe("10 ₽");
expect(formatMoney({ value: "0.5", currency: "USD" })).toBe("0,5 USD");
expect(formatMoney(null)).toBe("—");
+1 -1
View File
@@ -2,7 +2,7 @@ import type { Portfolio } from "../api/usePortfolio";
type Money = Portfolio["totalsByCurrency"][number];
export function formatDecimal(value: string): string {
function formatDecimal(value: string): string {
const [integer, fraction] = value.split(".");
const sign = integer.startsWith("-") ? "-" : "";
const grouped = (sign ? integer.slice(1) : integer).replace(