Files
frontend/src/widgets/pools/ui/LpDepositCard.tsx
2026-06-17 18:24:14 +03:00

211 lines
6.4 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 { PrimaryButton } from '@shared/ui'
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
import type { Pool, LpQuote } from '@features/pools'
import { formatFeeTier } from '../model/meta'
import styles from './LpDepositCard.module.css'
interface Props {
pool: Pool | null
quote: LpQuote | undefined
quoteFetching?: boolean
pending?: boolean
amount0: string
amount1: string
priceLower: string
priceUpper: string
slippage: string
useNativeEth: boolean
onAmount0Change: (v: string) => void
onAmount1Change: (v: string) => void
onPriceLowerChange: (v: string) => void
onPriceUpperChange: (v: string) => void
onSlippageChange: (v: string) => void
onUseNativeEthChange: (v: boolean) => void
onSubmit: () => void
}
const DASH = '—'
const decimalRe = /^(\d+\.?\d*|\.?\d*)$/
function AmountInput({
symbol,
value,
onChange,
}: {
symbol: string
value: string
onChange: (v: string) => void
}) {
return (
<div className={styles.amountRow}>
<input
className={styles.amountInput}
type="text"
inputMode="decimal"
value={value}
onChange={(e) => {
const v = e.target.value
if (decimalRe.test(v) || v === '') onChange(v)
}}
placeholder="0"
aria-label={`Сумма ${symbol}`}
/>
<div className={styles.token}>
<span>{symbol}</span>
</div>
</div>
)
}
function PriceInput({
label,
value,
onChange,
}: {
label: string
value: string
onChange: (v: string) => void
}) {
return (
<label className={styles.priceField}>
<span className={styles.priceLabel}>{label}</span>
<input
className={styles.priceInput}
type="text"
inputMode="decimal"
value={value}
onChange={(e) => {
const v = e.target.value
if (decimalRe.test(v) || v === '') onChange(v)
}}
placeholder="0"
/>
</label>
)
}
export function LpDepositCard({
pool,
quote,
quoteFetching,
pending,
amount0,
amount1,
priceLower,
priceUpper,
slippage,
useNativeEth,
onAmount0Change,
onAmount1Change,
onPriceLowerChange,
onPriceUpperChange,
onSlippageChange,
onUseNativeEthChange,
onSubmit,
}: Props) {
if (!pool) {
return (
<div className={styles.card}>
<div className={styles.placeholder}>
<p className={styles.placeholderTitle}>Выберите пул</p>
<p className={styles.placeholderText}>
Выберите пул в таблице выше, чтобы внести ликвидность.
</p>
</div>
</div>
)
}
const balance = quote?.balance
// sufficient ноги может быть null (не проверялось / покрыто нативным ETH) — это не «не хватает».
const insufficient = balance?.sufficient === false
const apr = quote?.aprPercent != null ? `${quote.aprPercent}%` : DASH
const feeUsd = quote?.appFeeTotalUsd != null ? `$${quote.appFeeTotalUsd.toFixed(2)}` : DASH
const amount0Out = quote ? truncateDecimals(quote.amount0DesiredHuman, 8) : DASH
const amount1Out = quote ? truncateDecimals(quote.amount1DesiredHuman, 8) : DASH
const shortLeg = balance && balance.token1.sufficient === false ? balance.token1 : balance?.token0
const canSubmit = !!quote && !insufficient && !pending
return (
<div className={styles.card}>
<div className={styles.top}>
<span className={styles.pair}>
{pool.symbol0}/{pool.symbol1}
</span>
<span className={styles.fee}>{formatFeeTier(pool.feeTier)}</span>
</div>
<span className={styles.tag}>Суммы взноса</span>
<div className={styles.amounts}>
<AmountInput symbol={pool.symbol0} value={amount0} onChange={onAmount0Change} />
<AmountInput symbol={pool.symbol1} value={amount1} onChange={onAmount1Change} />
</div>
<span className={styles.tag}>Диапазон цен ({pool.symbol1} за {pool.symbol0})</span>
<div className={styles.range}>
<PriceInput label="Мин." value={priceLower} onChange={onPriceLowerChange} />
<PriceInput label="Макс." value={priceUpper} onChange={onPriceUpperChange} />
</div>
<div className={styles.options}>
<label className={styles.slippage}>
<span className={styles.priceLabel}>Проскальзывание, %</span>
<input
className={styles.priceInput}
type="text"
inputMode="decimal"
value={slippage}
onChange={(e) => {
const v = e.target.value
if (decimalRe.test(v) || v === '') onSlippageChange(v)
}}
placeholder="1"
/>
</label>
<label className={styles.toggle}>
<input
type="checkbox"
checked={useNativeEth}
onChange={(e) => onUseNativeEthChange(e.target.checked)}
/>
<span>Использовать нативный ETH</span>
</label>
</div>
<div className={styles.quote} data-loading={quoteFetching ? 'true' : undefined}>
<div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Взнос {pool.symbol0}</span>
<span className={styles.quoteValue}>{amount0Out}</span>
</div>
<div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Взнос {pool.symbol1}</span>
<span className={styles.quoteValue}>{amount1Out}</span>
</div>
<div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Комиссия сервиса (0.7%)</span>
<span className={styles.quoteValue}>{feeUsd}</span>
</div>
<div className={styles.quoteRow}>
<span className={styles.quoteLabel}>Годовая доходность (APR)</span>
<span className={`${styles.quoteValue} ${styles.success}`}>{apr}</span>
</div>
</div>
{insufficient && (
<p className={styles.warning}>
Недостаточно средств{shortLeg ? `: не хватает ${shortLeg.symbol}` : ''}. Пополните баланс.
</p>
)}
<div className={styles.submit}>
<PrimaryButton
label={pending ? 'Отправка…' : 'Внести в пул'}
type="button"
onClick={onSubmit}
disabled={!canSubmit}
/>
</div>
</div>
)
}