Files
bedolaga-cabinet/src/hooks/useCurrency.ts
Fringg 6ffd0ae824 fix(format): convert price by exchange rate on landings and gift page
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).
2026-05-13 04:56:15 +03:00

141 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useCallback, useEffect, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { currencyApi, type ExchangeRates } from '../api/currency';
import { setExchangeRates as setGlobalExchangeRates } from '../utils/format';
// Map language to currency
const LANGUAGE_CURRENCY_MAP: Record<string, keyof ExchangeRates | 'RUB'> = {
ru: 'RUB',
en: 'USD',
zh: 'CNY',
fa: 'IRR',
};
// Default rates for fallback
const DEFAULT_RATES: ExchangeRates = {
USD: 100,
CNY: 14,
IRR: 0.0024,
};
export function useCurrency() {
const { i18n, t } = useTranslation();
// Fetch exchange rates
const { data: exchangeRates = DEFAULT_RATES } = useQuery({
queryKey: ['exchange-rates'],
queryFn: currencyApi.getExchangeRates,
staleTime: 60 * 60 * 1000, // 1 hour
refetchOnWindowFocus: false,
retry: 2,
});
// Делимся курсом с синхронным formatPrice из utils/format.ts —
// у него нет своего источника rates, а на лендинге его дёргают subcomponents.
useEffect(() => {
setGlobalExchangeRates(exchangeRates);
}, [exchangeRates]);
// Get current language and currency
const currentLanguage = i18n.language;
const targetCurrency = LANGUAGE_CURRENCY_MAP[currentLanguage] || 'USD';
// Check if current language is Russian (no conversion needed)
const isRussian = currentLanguage === 'ru';
// Get currency symbol from translations
const currencySymbol = t('common.currency');
// Format amount with currency conversion
const formatAmount = useCallback(
(rubAmount: number, decimals: number = 2): string => {
if (isRussian) {
return rubAmount.toFixed(decimals);
}
// Convert to target currency
const convertedAmount = currencyApi.convertFromRub(
rubAmount,
targetCurrency as keyof ExchangeRates,
exchangeRates,
);
// For IRR (Iranian Toman), use no decimals as amounts are large
if (targetCurrency === 'IRR') {
return Math.round(convertedAmount).toLocaleString('fa-IR');
}
return convertedAmount.toFixed(decimals);
},
[isRussian, targetCurrency, exchangeRates],
);
// Format amount with currency symbol
const formatWithCurrency = useCallback(
(rubAmount: number, decimals: number = 2): string => {
return `${formatAmount(rubAmount, decimals)} ${currencySymbol}`;
},
[formatAmount, currencySymbol],
);
// Format amount with + sign (for earnings/bonuses)
const formatPositive = useCallback(
(rubAmount: number, decimals: number = 2): string => {
return `+${formatAmount(rubAmount, decimals)} ${currencySymbol}`;
},
[formatAmount, currencySymbol],
);
// Get raw converted amount (for calculations)
const convertAmount = useCallback(
(rubAmount: number): number => {
if (isRussian) {
return rubAmount;
}
return currencyApi.convertFromRub(
rubAmount,
targetCurrency as keyof ExchangeRates,
exchangeRates,
);
},
[isRussian, targetCurrency, exchangeRates],
);
// Convert from user's currency back to rubles
const convertToRub = useCallback(
(amount: number): number => {
if (isRussian) {
return amount;
}
return currencyApi.convertToRub(amount, targetCurrency as keyof ExchangeRates, exchangeRates);
},
[isRussian, targetCurrency, exchangeRates],
);
return useMemo(
() => ({
exchangeRates,
targetCurrency,
isRussian,
currencySymbol,
formatAmount,
formatWithCurrency,
formatPositive,
convertAmount,
convertToRub,
}),
[
exchangeRates,
targetCurrency,
isRussian,
currencySymbol,
formatAmount,
formatWithCurrency,
formatPositive,
convertAmount,
convertToRub,
],
);
}