refactor: tighten types and reuse instrument UI
Frontend verify / commits (pull_request) Successful in 24s
Frontend verify / security (pull_request) Failing after 9s
Frontend verify / verify (pull_request) Skipped

This commit is contained in:
2026-09-29 06:15:31 +03:00
parent b404c1802a
commit 7c28a423dd
67 changed files with 380 additions and 347 deletions
+3 -3
View File
@@ -51,7 +51,7 @@ describe("useAuthenticatedQuery", () => {
await new Promise(resolve => setTimeout(resolve, 0));
expect(requests).not.toHaveBeenCalled();
expect(result.current.fetchStatus).toBe("idle");
expect(result.current?.fetchStatus).toBe("idle");
configureAuthClient({
accessToken: () => "token",
@@ -59,9 +59,9 @@ describe("useAuthenticatedQuery", () => {
refresh: () => Promise.resolve(true),
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
await waitFor(() => expect(result.current?.isSuccess).toBe(true));
expect(requests).toHaveBeenCalledOnce();
expect(result.current.data?.data).toEqual([
expect(result.current?.data?.data).toEqual([
{
id: "account-1",
name: "Основной",
+8 -6
View File
@@ -9,13 +9,15 @@ function isValidPeriod(from: null | string, to: null | string) {
);
}
const moscowFormatter = new Intl.DateTimeFormat("en-CA", {
day: "2-digit",
month: "2-digit",
timeZone: "Europe/Moscow",
year: "numeric",
});
function moscowDate(value: Date): string {
const parts = new Intl.DateTimeFormat("en-CA", {
day: "2-digit",
month: "2-digit",
timeZone: "Europe/Moscow",
year: "numeric",
}).formatToParts(value);
const parts = moscowFormatter.formatToParts(value);
const part = (type: string) =>
parts.find(item => item.type === type)?.value;
+20 -16
View File
@@ -7,17 +7,19 @@ type FormatMoneyOptions = {
type Money = { currency: string; value: string };
const dateFormatter = new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
timeZone: "UTC",
year: "numeric",
});
function formatDate(value: string): string {
return new Intl.DateTimeFormat("ru-RU", {
day: "2-digit",
month: "2-digit",
timeZone: "UTC",
year: "numeric",
}).format(new Date(value));
return dateFormatter.format(new Date(value));
}
function formatDecimal(value: string): string {
const [integer, fraction] = value.split(".", DECIMAL_PART_COUNT);
const [integer = "", fraction] = value.split(".", DECIMAL_PART_COUNT);
const sign = integer.startsWith("-") ? "-" : "";
const digits = sign ? integer.slice(1) : integer;
const groupSize = 3;
@@ -41,7 +43,7 @@ function formatMoney(
): string {
if (!money) return "—";
const [integerPart, fractionPart = ""] = money.value.split(
const [integerPart = "", fractionPart = ""] = money.value.split(
".",
DECIMAL_PART_COUNT,
);
@@ -59,20 +61,22 @@ function formatMoney(
return `${formatDecimal(decimal)} ${currency}`;
}
const snapshotFormatter = new Intl.DateTimeFormat("ru-RU", {
dateStyle: "short",
timeStyle: "short",
timeZone: "UTC",
});
function formatSnapshotTime(value: string): string {
return new Intl.DateTimeFormat("ru-RU", {
dateStyle: "short",
timeStyle: "short",
timeZone: "UTC",
}).format(new Date(value));
return snapshotFormatter.format(new Date(value));
}
const logoSize = 160;
const chartFormatter = new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 4 });
function formatChartValue(value: number) {
return new Intl.NumberFormat("ru-RU", { maximumFractionDigits: 4 }).format(
value,
);
return chartFormatter.format(value);
}
function instrumentLogoUrl(logoName?: null | string): null | string {
+2 -3
View File
@@ -61,14 +61,13 @@ const ColorsView: FC = () => {
))}
</SimpleGrid>
{Object.keys(designTokens.color.palettes).map(palette => (
{Object.entries(designTokens.color.palettes).map(([palette, colors]) => (
<Stack gap="xs" key={palette}>
<Title order={2}>{palette}</Title>
<SimpleGrid cols={{ base: 2, lg: 10, sm: 5 }}>
{
// eslint-disable-next-line security/detect-object-injection -- This is a positional index into the local array, not a user-supplied object key.
theme.colors[palette].map((value, shade) => (
colors.map((value, shade) => (
<Paper key={value} p="xs" withBorder>
<Box aria-hidden="true" bg={value} h={40} />
+28 -12
View File
@@ -1,14 +1,22 @@
import tokens from "@wealthledger/tokens/tokens.json";
import { describe, expect, it } from "vitest";
import { assert, describe, expect, it } from "vitest";
function luminance(color: string) {
const channels = [1, 3, 5]
.map(index => Number.parseInt(color.slice(index, index + 2), 16) / 255)
.map(value =>
value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4,
);
let result = 0;
return channels[0] * 0.2126 + channels[1] * 0.7152 + channels[2] * 0.0722;
for (const [index, weight] of [0.2126, 0.7152, 0.0722].entries()) {
const channel = Number.parseInt(
color.slice(1 + index * 2, 3 + index * 2), 16,
) / 255;
const linear = channel <= 0.04045
? channel / 12.92
: ((channel + 0.055) / 1.055) ** 2.4;
result += linear * weight;
}
return result;
}
describe("semantic text contrast", () => {
@@ -24,6 +32,8 @@ describe("semantic text contrast", () => {
const foreground
= palettes[role.color as keyof typeof palettes][role.shade];
assert.isDefined(foreground);
const surfaces = [
"#ffffff",
palettes.gray[0],
@@ -34,25 +44,31 @@ describe("semantic text contrast", () => {
...Object.entries(palettes)
.filter(([name]) => name !== "dark")
.map(
([, palette]) =>
`#${[1, 3, 5]
([, palette]) => {
const base = palette[0];
assert.isDefined(base);
return `#${[1, 3, 5]
.map(index =>
Math.round(
Number.parseInt(palette[0].slice(index, index + 2), 16)
Number.parseInt(base.slice(index, index + 2), 16)
* 0.45
+ 255 * 0.55,
)
.toString(16)
.padStart(2, "0"),
)
.join("")}`,
.join("")}`;
},
),
];
for (const background of surfaces)
for (const background of surfaces) {
assert.isDefined(background);
expect(
(luminance(background) + 0.05) / (luminance(foreground) + 0.05),
).toBeGreaterThanOrEqual(4.5);
}
},
);
});