refactor(converter): shared page layout + reusable conversion logic/UI
Pages: - add WalletLayout route (WalletHeader + main + Footer via <Outlet/>), wrap converter/swap/bridge/transactions; thin pages, drop duplicated shell CSS - extract SwapBridgeTabs shared between swap/bridge pages Converter reuse (FSD layers, no widget->widget imports): - move commission tiers to entities/commission (+ CommissionTable ui) - shared calc hook features/payment/model/useCurrencyConversion; useConverterSection becomes thin wrapper; HomePage Converter reuses it - move ConvertField/DirectionSwapButton to shared/ui; delete dead useConverter Tooling: - add eslint.config.js (ESLint 9 flat config); fix no-explicit-any in WalletPage Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
102
src/features/payment/model/useCurrencyConversion.ts
Normal file
102
src/features/payment/model/useCurrencyConversion.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { useState } from 'react'
|
||||
import { useDebounce } from '@shared/lib/hooks/useDebounce'
|
||||
import { progressPercent } from '@entities/commission'
|
||||
import { GAS_PRICE, MIN_RUB_AMOUNT } from '@shared/config/constants'
|
||||
import type { ConvertFieldData } from '@shared/ui'
|
||||
import { usePaymentConfig } from '../hooks/usePaymentConfig'
|
||||
import { usePaymentQuote } from '../hooks/usePaymentQuote'
|
||||
import { usePaymentQuoteByRub } from '../hooks/usePaymentQuoteByRub'
|
||||
|
||||
const TOO_LARGE_ERROR = 'Сумма слишком большая и превышает 600 000 ₽'
|
||||
|
||||
const sanitize = (raw: string) => raw.replace(/[^0-9.]/g, '')
|
||||
|
||||
interface Options {
|
||||
/** Значение курса USDT/RUB до загрузки конфига (пилюля). */
|
||||
rateFallback?: number
|
||||
}
|
||||
|
||||
export function useCurrencyConversion({ rateFallback = 0 }: Options = {}) {
|
||||
const [direction, setDirection] = useState<'usdt_to_rub' | 'rub_to_usdt'>('usdt_to_rub')
|
||||
const [usdtInput, setUsdtInput] = useState('1000')
|
||||
const [rubInput, setRubInput] = useState(String(MIN_RUB_AMOUNT))
|
||||
|
||||
const { data: config } = usePaymentConfig()
|
||||
const configUsdtRate = Number(config?.usdt_exchange_rate) || rateFallback
|
||||
const gasPriceRub = Number(config?.gas_fee) || GAS_PRICE
|
||||
|
||||
const isUsdtToRub = direction === 'usdt_to_rub'
|
||||
|
||||
const numUsdt = Number.parseFloat(usdtInput) || 0
|
||||
const debouncedUsdt = useDebounce(numUsdt, 400)
|
||||
const { data: quoteUsdtToRub, isError: quoteError } = usePaymentQuote(isUsdtToRub ? debouncedUsdt : 0)
|
||||
|
||||
const numRubInput = Number.parseFloat(rubInput) || 0
|
||||
const debouncedRub = useDebounce(numRubInput, 400)
|
||||
const { data: quoteRubToUsdt, isError: quoteRubError } = usePaymentQuoteByRub(!isUsdtToRub ? debouncedRub : 0)
|
||||
|
||||
const rubBelowMin = !isUsdtToRub && numRubInput > 0 && numRubInput < MIN_RUB_AMOUNT
|
||||
|
||||
const rubTotal = quoteUsdtToRub?.total_price ?? ''
|
||||
const rubTotalNum = Number(rubTotal) || 0
|
||||
const usdtFromRub = quoteRubToUsdt?.usdt_amount ?? ''
|
||||
const usdtFromRubNum = Number(usdtFromRub) || 0
|
||||
|
||||
const commission = isUsdtToRub
|
||||
? Number(quoteUsdtToRub?.service_fee) || 0
|
||||
: Number(quoteRubToUsdt?.service_fee) || 0
|
||||
|
||||
const displayRubAmount = isUsdtToRub ? rubTotalNum : numRubInput
|
||||
const effectiveRate = isUsdtToRub
|
||||
? (numUsdt > 0 ? rubTotalNum / numUsdt : 0)
|
||||
: (usdtFromRubNum > 0 ? numRubInput / usdtFromRubNum : 0)
|
||||
|
||||
function onSwap() {
|
||||
setDirection(d => (d === 'usdt_to_rub' ? 'rub_to_usdt' : 'usdt_to_rub'))
|
||||
}
|
||||
|
||||
const convert: ConvertFieldData = isUsdtToRub
|
||||
? {
|
||||
value: usdtInput,
|
||||
currency: 'USDT',
|
||||
onChange: (raw) => setUsdtInput(sanitize(raw)),
|
||||
error: quoteError ? TOO_LARGE_ERROR : undefined,
|
||||
}
|
||||
: {
|
||||
value: rubInput,
|
||||
currency: 'RUB',
|
||||
onChange: (raw) => setRubInput(sanitize(raw)),
|
||||
error: rubBelowMin
|
||||
? `Минимальная сумма: ${MIN_RUB_AMOUNT.toLocaleString('ru-RU')} ₽`
|
||||
: quoteRubError
|
||||
? TOO_LARGE_ERROR
|
||||
: undefined,
|
||||
}
|
||||
|
||||
const pay: ConvertFieldData = isUsdtToRub
|
||||
? { value: rubTotal, currency: 'RUB' }
|
||||
: { value: usdtFromRub, currency: 'USDT' }
|
||||
|
||||
return {
|
||||
isUsdtToRub,
|
||||
gasPriceRub,
|
||||
configUsdtRate,
|
||||
convert,
|
||||
pay,
|
||||
onSwap,
|
||||
commission: {
|
||||
amount: displayRubAmount,
|
||||
progress: progressPercent(displayRubAmount),
|
||||
commission,
|
||||
effectiveRate,
|
||||
},
|
||||
// сырые значения для создания ордера и валидации в обёртках
|
||||
numUsdt,
|
||||
usdtFromRubNum,
|
||||
rubTotal,
|
||||
rubTotalNum,
|
||||
numRubInput,
|
||||
usdtFromRub,
|
||||
rubBelowMin,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user