91 lines
3.3 KiB
TypeScript
91 lines
3.3 KiB
TypeScript
import { useState } from 'react'
|
||
import type { JumperQuote } from '@features/wallet'
|
||
import { fromBaseUnits } from '@shared/lib/utils/baseUnits'
|
||
import { truncateDecimals } from '@shared/lib/utils/truncateDecimals'
|
||
import styles from './BridgeConfirmModal.module.css'
|
||
|
||
interface Props {
|
||
quote: JumperQuote
|
||
fromAmountHuman: string
|
||
insufficientBalance?: boolean
|
||
isExecuting?: boolean
|
||
onConfirm: () => void
|
||
onClose: () => void
|
||
}
|
||
|
||
export function BridgeConfirmModal({ quote, fromAmountHuman, insufficientBalance, isExecuting, onConfirm, onClose }: Props) {
|
||
const { action, estimate, toolDetails } = quote
|
||
const toSymbol = action.toToken.symbol
|
||
const fromSymbol = action.fromToken.symbol
|
||
|
||
const [closing, setClosing] = useState(false)
|
||
|
||
// Trigger the exit animation; the real close fires on animation end so the ×
|
||
// and backdrop paths play the same closing transition.
|
||
function requestClose() {
|
||
setClosing(true)
|
||
}
|
||
|
||
function handleAnimationEnd() {
|
||
if (closing) onClose()
|
||
}
|
||
|
||
const toAmount = truncateDecimals(fromBaseUnits(estimate.toAmount, action.toToken.decimals), 8)
|
||
const toMin = truncateDecimals(fromBaseUnits(estimate.toAmountMin, action.toToken.decimals), 8)
|
||
|
||
const feesUsd = (estimate.feeCosts ?? [])
|
||
.reduce((sum, f) => sum + (parseFloat(f.amountUSD) || 0), 0)
|
||
.toFixed(2)
|
||
|
||
return (
|
||
<div className={`${styles.overlay} ${closing ? styles.closing : ''}`} onClick={requestClose}>
|
||
<div
|
||
className={`${styles.card} ${closing ? styles.closing : ''}`}
|
||
onClick={e => e.stopPropagation()}
|
||
onAnimationEnd={handleAnimationEnd}
|
||
>
|
||
<div className={styles.header}>
|
||
<span className={styles.title}>Подтвердить бридж</span>
|
||
<button className={styles.closeBtn} onClick={requestClose}>×</button>
|
||
</div>
|
||
|
||
<div className={styles.flow}>
|
||
<div className={styles.token}>
|
||
<span className={styles.tokenLabel}>Отдаёте</span>
|
||
<span className={styles.tokenAmount}>{fromAmountHuman} {fromSymbol}</span>
|
||
</div>
|
||
|
||
<div className={styles.arrow}>↓</div>
|
||
|
||
<div className={styles.token}>
|
||
<span className={styles.tokenLabel}>Получаете</span>
|
||
<span className={styles.tokenAmount}>{toAmount} {toSymbol}</span>
|
||
<span className={styles.minOut}>Минимум: {toMin} {toSymbol}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className={styles.details}>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowLabel}>Комиссия</span>
|
||
<span className={styles.rowValue}>≈${feesUsd}</span>
|
||
</div>
|
||
<div className={styles.row}>
|
||
<span className={styles.rowLabel}>Мост</span>
|
||
<span className={styles.rowValue}>{toolDetails.name}</span>
|
||
</div>
|
||
</div>
|
||
|
||
{insufficientBalance && (
|
||
<p className={styles.warning}>
|
||
Введённое количество превышает баланс кошелька — бридж будет отклонён.
|
||
</p>
|
||
)}
|
||
|
||
<button className={styles.confirmBtn} onClick={onConfirm} disabled={isExecuting}>
|
||
{isExecuting ? 'Обработка...' : 'Подтвердить бридж'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|