14.05.2026 rip

This commit is contained in:
2026-05-14 23:23:20 +03:00
parent 26a7315ea1
commit 0d114e12c2
6 changed files with 271 additions and 8 deletions

View File

@@ -0,0 +1,138 @@
.overlay {
position: fixed;
inset: 0;
background: rgba(10, 11, 46, 0.75);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.card {
background: var(--bg-mid);
border: 1px solid var(--glass-border);
border-radius: 24px;
padding: 32px;
width: 100%;
max-width: 420px;
display: flex;
flex-direction: column;
gap: 24px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.title {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
}
.closeBtn {
background: none;
border: none;
color: var(--text-secondary);
font-size: 22px;
cursor: pointer;
line-height: 1;
padding: 0;
font-family: var(--font-sans);
}
.closeBtn:hover {
color: var(--text-primary);
}
.flow {
display: flex;
flex-direction: column;
gap: 8px;
}
.token {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 14px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 4px;
}
.tokenLabel {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-secondary);
font-weight: 700;
}
.tokenAmount {
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
}
.tokenUsd {
font-size: 13px;
color: var(--text-secondary);
}
.arrow {
text-align: center;
color: var(--text-secondary);
font-size: 18px;
line-height: 1;
}
.details {
display: flex;
flex-direction: column;
gap: 10px;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
}
.rowLabel {
font-size: 13px;
color: var(--text-secondary);
}
.rowValue {
font-size: 13px;
color: var(--text-primary);
font-weight: 600;
}
.impact {
color: var(--error);
}
.confirmBtn {
width: 100%;
height: 56px;
background: linear-gradient(135deg, var(--grad-edge), var(--grad-center));
border: none;
border-radius: 14px;
color: var(--text-primary);
font-size: 17px;
font-weight: 700;
cursor: pointer;
font-family: var(--font-sans);
letter-spacing: 0.3px;
transition: filter 0.25s, box-shadow 0.25s;
}
.confirmBtn:hover {
filter: brightness(1.15);
box-shadow: 0 0 24px rgba(91, 61, 184, 0.5);
}

View File

@@ -0,0 +1,81 @@
import styles from './SwapConfirmModal.module.css'
interface SwapData {
details: {
currencyIn: { amountFormatted: string; amountUsd: string; currency: { symbol: string } }
currencyOut: { amountFormatted: string; amountUsd: string; currency: { symbol: string } }
totalImpact: { percent: string }
rate: string
}
fees: {
gas: { amountUsd: string }
}
}
interface Props {
data: SwapData
onConfirm: () => void
onClose: () => void
}
export function SwapConfirmModal({ data, onConfirm, onClose }: Props) {
const { details, fees } = data
const { currencyIn, currencyOut, totalImpact, rate } = details
const impactPercent = parseFloat(totalImpact.percent)
const rateFormatted = parseFloat(rate).toFixed(4)
return (
<div className={styles.overlay} onClick={onClose}>
<div className={styles.card} onClick={e => e.stopPropagation()}>
<div className={styles.header}>
<span className={styles.title}>Подтвердить своп</span>
<button className={styles.closeBtn} onClick={onClose}>×</button>
</div>
<div className={styles.flow}>
<div className={styles.token}>
<span className={styles.tokenLabel}>Отдаёте</span>
<span className={styles.tokenAmount}>
{currencyIn.amountFormatted} {currencyIn.currency.symbol}
</span>
<span className={styles.tokenUsd}> ${currencyIn.amountUsd}</span>
</div>
<div className={styles.arrow}></div>
<div className={styles.token}>
<span className={styles.tokenLabel}>Получаете</span>
<span className={styles.tokenAmount}>
{currencyOut.amountFormatted} {currencyOut.currency.symbol}
</span>
<span className={styles.tokenUsd}> ${currencyOut.amountUsd}</span>
</div>
</div>
<div className={styles.details}>
<div className={styles.row}>
<span className={styles.rowLabel}>Курс</span>
<span className={styles.rowValue}>
1 {currencyIn.currency.symbol} = {rateFormatted} {currencyOut.currency.symbol}
</span>
</div>
<div className={styles.row}>
<span className={styles.rowLabel}>Комиссия сети</span>
<span className={styles.rowValue}>${fees.gas.amountUsd}</span>
</div>
<div className={styles.row}>
<span className={styles.rowLabel}>Влияние на цену</span>
<span className={`${styles.rowValue} ${impactPercent < 0 ? styles.impact : ''}`}>
{totalImpact.percent}%
</span>
</div>
</div>
<button className={styles.confirmBtn} onClick={onConfirm}>
Подтвердить
</button>
</div>
</div>
)
}

View File

@@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'
import { PrimaryButton } from '@shared/ui'
import { useWalletBalance, useWalletAddresses, useTokensList, useRelayQuote, useExecuteRelaySwap, type Chain } from '@features/wallet'
import { useWalletBalance, useWalletAddresses, useTokensList, useRelayQuote, useExecuteRelaySwap, useSignSwap, type Chain, type RelaySwapResponse } from '@features/wallet'
import { useDebounce } from '@shared/lib/hooks/useDebounce'
import { TOKENS_LIST, buildTokensFromBalance, useSwapForm } from '../model/useSwapForm'
import { SwapCard } from './SwapCard'
import { SwapDirectionButton } from './SwapDirectionButton'
import { SwapInfoPanel } from './SwapInfoPanel'
import { SwapConfirmModal } from './SwapConfirmModal'
import styles from './SwapForm.module.css'
@@ -27,6 +28,7 @@ export function SwapForm() {
} = useSwapForm()
const [fromNetwork, setFromNetwork] = useState('ETH')
const [modalData, setModalData] = useState<RelaySwapResponse | null>(null)
const { data: walletData } = useWalletBalance(fromNetwork as Chain)
const tokenOptions = walletData ? buildTokensFromBalance(walletData) : TOKENS_LIST
@@ -62,6 +64,7 @@ export function SwapForm() {
const { data: quoteData } = useRelayQuote(quotePayload)
const { mutate: executeSwap, isPending: isSwapping } = useExecuteRelaySwap()
const { mutate: signSwap } = useSignSwap()
const displayToAmount = quoteData?.details.currencyOut.amountFormatted ?? '0'
const displayToUsd = quoteData?.details.currencyOut.amountUsd
@@ -69,7 +72,9 @@ export function SwapForm() {
function handleSwap() {
if (!quotePayload) return
executeSwap(quotePayload)
executeSwap(quotePayload, {
onSuccess: (data) => setModalData(data),
})
}
return (
@@ -101,6 +106,18 @@ export function SwapForm() {
<SwapInfoPanel gasFee={gasFee} />
<PrimaryButton onClick={handleSwap} disabled={!quotePayload || isSwapping} />
{modalData && (
<SwapConfirmModal
data={modalData}
onClose={() => setModalData(null)}
onConfirm={() => {
const txData = modalData.steps[0]?.items[0]?.data
if (txData) signSwap({ chain: fromNetwork as Chain, txData })
setModalData(null)
}}
/>
)}
</div>
)
}