mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-29 18:13:47 +00:00
formatPrice from utils/format.ts only swapped the currency symbol (220 ₽ -> ¥220) without converting the underlying amount, because it had no source of exchange rates and the landing/gift pages never called the useCurrency hook that knew how to fetch them. - Add a module-level rates cache in utils/format with setExchangeRates setter. formatPrice now converts kopeks/100 from RUB to the target currency via currencyApi.convertFromRub when rates are cached. - useCurrency pushes its loaded rates into that cache so any subsequent formatPrice call benefits, including subcomponents that cannot easily receive a prop. - Call useCurrency in QuickPurchase, GiftSubscription, and AdminLandingEditor — the only entry points whose subcomponents still use the synchronous formatPrice (everything else already routes through useCurrency.formatAmount). For RU locale behavior is unchanged. For EN/ZH/FA the amount is now divided by the per-currency rate and formatted via Intl.NumberFormat with 2 fraction digits (0 for IRR since amounts are large).
62 lines
2.6 KiB
TypeScript
62 lines
2.6 KiB
TypeScript
export const USER_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
|
|
export function formatUptime(seconds: number): string {
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
if (days > 0) return `${days}d ${hours}h`;
|
|
if (hours > 0) return `${hours}h ${minutes}m`;
|
|
return `${minutes}m`;
|
|
}
|
|
|
|
import i18next from 'i18next';
|
|
import { currencyApi, type ExchangeRates } from '../api/currency';
|
|
|
|
const LANG_CURRENCY_MAP: Record<
|
|
string,
|
|
{ currency: string; locale: string; symbol: string; key?: keyof ExchangeRates }
|
|
> = {
|
|
ru: { currency: 'RUB', locale: 'ru-RU', symbol: '₽' },
|
|
en: { currency: 'USD', locale: 'en-US', symbol: '$', key: 'USD' },
|
|
zh: { currency: 'CNY', locale: 'zh-CN', symbol: '¥', key: 'CNY' },
|
|
fa: { currency: 'IRR', locale: 'fa-IR', symbol: '﷼', key: 'IRR' },
|
|
};
|
|
|
|
const DEFAULT_CURRENCY = { currency: 'RUB', locale: 'ru-RU', symbol: '₽' };
|
|
|
|
// Глобальный кэш курсов. Заполняется один раз из useCurrency (см. setExchangeRates ниже)
|
|
// и используется здесь синхронно. Без него formatPrice падал в "просто замена символа",
|
|
// что давало пользователю на лендинге `¥220` вместо реально конвертированной суммы.
|
|
let cachedExchangeRates: ExchangeRates | null = null;
|
|
|
|
export function setExchangeRates(rates: ExchangeRates | null): void {
|
|
cachedExchangeRates = rates;
|
|
}
|
|
|
|
export function formatPrice(kopeks: number, lang?: string): string {
|
|
const resolvedLang = lang || i18next.language || 'ru';
|
|
const config = LANG_CURRENCY_MAP[resolvedLang] || DEFAULT_CURRENCY;
|
|
let amount = kopeks / 100;
|
|
|
|
// Конвертация по курсу для не-рублёвых локалей. Без rates fallback на сырую сумму
|
|
// (поведение до фикса), чтобы первый рендер до загрузки курсов не отдавал NaN.
|
|
if (config.key && cachedExchangeRates) {
|
|
amount = currencyApi.convertFromRub(amount, config.key, cachedExchangeRates);
|
|
}
|
|
|
|
// Для IRR суммы большие — без дробной части.
|
|
const maximumFractionDigits = config.currency === 'IRR' ? 0 : 2;
|
|
|
|
try {
|
|
return new Intl.NumberFormat(config.locale, {
|
|
style: 'currency',
|
|
currency: config.currency,
|
|
maximumFractionDigits,
|
|
}).format(amount);
|
|
} catch {
|
|
const rounded =
|
|
maximumFractionDigits === 0 ? Math.round(amount) : Math.round(amount * 100) / 100;
|
|
return `${rounded} ${config.symbol}`;
|
|
}
|
|
}
|