feat(web): заготовка веб-клиента на Vite + React 19 + TypeScript strict
- стек по §5.2: React 19, React Router, TanStack Query, Zustand, Tailwind CSS 4, i18next (ru/en), Vitest + Testing Library - дизайн-токены §3 docs/client-web.md (тёмная тема по умолчанию, светлая, accent) - типизированный API-клиент §8.1: единый формат ошибок, ApiError с кодом - App: экран состояния фундамента с readz/meta, локализация ошибок по code - тесты: клиент API, форматирование, рендер App (успех/ошибка кода/сеть) - eslint + prettier + tsc strict; initial JS 97.6 KB gzip
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { ApiError, apiErrorCode } from '@/api/client';
|
||||
|
||||
export function formatBytes(bytes: number, locale = 'ru'): string {
|
||||
if (!Number.isFinite(bytes) || bytes < 0) {
|
||||
return '—';
|
||||
}
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let value = bytes;
|
||||
let unit = 0;
|
||||
while (value >= 1024 && unit < units.length - 1) {
|
||||
value /= 1024;
|
||||
unit += 1;
|
||||
}
|
||||
const formatted = new Intl.NumberFormat(locale, {
|
||||
maximumFractionDigits: value < 10 && unit > 0 ? 1 : 0,
|
||||
}).format(value);
|
||||
return `${formatted} ${units[unit]}`;
|
||||
}
|
||||
|
||||
// instanceof is unreliable across module instances (aliases, lazy chunks),
|
||||
// so the error shape is checked structurally as well.
|
||||
function hasErrorCode(value: unknown): value is { code: string } {
|
||||
return (
|
||||
typeof value === 'object' &&
|
||||
value !== null &&
|
||||
typeof (value as { code?: unknown }).code === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function errorCode(error: unknown): string {
|
||||
// The code from the server error envelope wins: it survives serialization
|
||||
// boundaries where instanceof checks do not.
|
||||
const envelopeCode = apiErrorCode(error);
|
||||
if (envelopeCode !== null) {
|
||||
return envelopeCode;
|
||||
}
|
||||
if (error instanceof ApiError) {
|
||||
return error.code;
|
||||
}
|
||||
if (hasErrorCode(error)) {
|
||||
return error.code;
|
||||
}
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return 'network.aborted';
|
||||
}
|
||||
return 'network.unreachable';
|
||||
}
|
||||
Reference in New Issue
Block a user