fix commission preview

This commit is contained in:
2026-06-24 00:03:18 +03:00
parent b9b2d9fcf3
commit 036c4a36ef
17 changed files with 246 additions and 410 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

161
dist/assets/index-DFLADCGY.js vendored Normal file

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
<script type="module" crossorigin src="/assets/index-BVKNc3Mv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-pOqaElwW.css">
<script type="module" crossorigin src="/assets/index-DFLADCGY.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BP0dDjYZ.css">
</head>
<body>
<div id="root"></div>

View File

@@ -7,7 +7,6 @@ import { ProfilePage } from '@pages/profile'
import { LoginPage } from '@pages/login'
import { RegisterPage } from '@pages/register'
import { RegisterTestPage } from '@pages/register-test'
import { ConverterTestPage } from '@pages/converter-test'
import { ConverterPage } from '@pages/converter'
import { SeedPhrasePage } from '@pages/seed-phrase'
import { KycPage } from '@pages/kyc'
@@ -38,7 +37,6 @@ export function RouterProvider() {
<Route path={ROUTES.SOGLASIE_PERSONALNYH_DANNYH} element={<SoglasiePage />} />
<Route path={ROUTES.REESTR_PD_RKN} element={<ReestryPage />} />
<Route path={ROUTES.REGISTER_TEST} element={<RegisterTestPage />} />
<Route path={ROUTES.CONVERTER_TEST} element={<ConverterTestPage />} />
<Route element={<GuestRoute />}>
<Route path={ROUTES.LOGIN} element={<LoginPage />} />

View File

@@ -246,7 +246,12 @@ export interface OpCostLeg {
}
export interface OpCost {
appCommission?: { inToken?: number; inNative?: number; usd?: number | null }
appCommission?: {
ratePercent?: number
inToken?: { symbol: string; amount: string }
inNative?: { symbol: string; amount: string }
usd?: number | null
}
balance?: { token?: OpCostLeg; gas?: OpCostLeg }
/** Хватает ли средств на сумму + газ. */
sufficient: boolean

View File

@@ -272,6 +272,11 @@ export interface RelayQuoteResponse {
amountUsd: string
}
}
appCommission?: {
ratePercent: number
inToken: { symbol: string; amount: string }
usd: number
}
}
export async function getTokensList(): Promise<TokenInfo[]> {

View File

@@ -1 +0,0 @@
export { ConverterTestPage } from './ui/ConverterTestPage'

View File

@@ -1,133 +0,0 @@
.page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1rem;
}
.wrap {
position: relative;
overflow: hidden;
width: 100%;
max-width: 900px;
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 24px;
padding: 40px;
}
.header {
margin-bottom: 40px;
}
.title {
font-size: clamp(32px, 4vw, 48px);
font-weight: 700;
}
.subtitle {
font-size: 15px;
line-height: 1.6;
color: var(--text-secondary);
margin-top: 12px;
max-width: 560px;
}
.body {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
}
.formCol {
display: flex;
flex-direction: column;
gap: 20px;
}
.hint {
font-size: 12px;
color: var(--highlight);
margin-top: -12px;
}
/* Панель условий / комиссии */
.infoCol {
display: flex;
flex-direction: column;
}
.infoTitle {
font-size: 16px;
font-weight: 600;
letter-spacing: 1px;
margin-bottom: 24px;
}
.infoRow {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 18px;
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
border: 1px solid var(--glass-border);
margin-bottom: 12px;
}
.infoRow[data-accent] {
border-color: var(--grad-center);
background: rgba(91, 61, 184, 0.12);
}
.infoLabel {
font-size: 13px;
color: var(--text-secondary);
}
.infoValue {
font-family: var(--font-mono);
font-size: 16px;
font-weight: 600;
}
.note {
font-size: 12px;
line-height: 1.6;
color: var(--text-secondary);
margin-top: 12px;
}
.submitBtn {
width: 100%;
margin-top: 40px;
padding: 18px;
border-radius: 12px;
background: var(--grad-center);
color: var(--text-primary);
font-size: 16px;
font-weight: 600;
letter-spacing: 1px;
transition: opacity 0.2s;
}
.submitBtn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
@media (max-width: 768px) {
.wrap {
padding: 28px 20px;
}
.body {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.header {
margin-bottom: 1.5rem;
}
}

View File

@@ -1,105 +0,0 @@
import { useState } from 'react'
import { FormField } from '@shared/ui'
import styles from './ConverterTestPage.module.css'
const MIN_ORDER = 500_000
const APPROX_RATE = 0.03 // примерная комиссия 3% для крупных заявок
const ru = (n: number) => n.toLocaleString('ru-RU', { maximumFractionDigits: 0 })
export function ConverterTestPage() {
const [amount, setAmount] = useState('')
const [name, setName] = useState('')
const [contact, setContact] = useState('')
const numAmount = Number(amount.replace(/\D/g, '')) || 0
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
const commission = numAmount * APPROX_RATE
const handleAmountChange = (value: string) => {
const digits = value.replace(/\D/g, '')
setAmount(digits ? ru(Number(digits)) : '')
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
// Тестовая страница — заявка никуда не отправляется.
}
return (
<div className={styles.page}>
<form className={styles.wrap} onSubmit={handleSubmit}>
<div className={styles.header}>
<h1 className={styles.title}>Оставить заявку</h1>
<p className={styles.subtitle}>
Конвертация крупных объёмов по индивидуальному курсу. Оставьте заявку
менеджер свяжется с вами, подтвердит актуальный курс и сопроводит сделку.
</p>
</div>
<div className={styles.body}>
<div className={styles.formCol}>
<FormField
label="Объём заявки, ₽"
type="text"
value={amount}
onChange={handleAmountChange}
placeholder="от 500 000"
/>
{belowMin && (
<p className={styles.hint}>
Минимальный объём заявки {ru(MIN_ORDER)}
</p>
)}
<FormField
label="Как к вам обращаться"
type="text"
value={name}
onChange={setName}
placeholder="Имя"
/>
<FormField
label="Email или телефон для связи"
type="text"
value={contact}
onChange={setContact}
placeholder="example@mail.ru / +7 900 000-00-00"
/>
</div>
<div className={styles.infoCol}>
<div className={styles.infoTitle}>УСЛОВИЯ</div>
<div className={styles.infoRow}>
<span className={styles.infoLabel}>Минимальный объём</span>
<span className={styles.infoValue}>{ru(MIN_ORDER)} </span>
</div>
<div className={styles.infoRow}>
<span className={styles.infoLabel}>Примерная комиссия</span>
<span className={styles.infoValue}>{(APPROX_RATE * 100).toFixed(0)} %</span>
</div>
<div className={styles.infoRow} data-accent>
<span className={styles.infoLabel}>Комиссия с объёма</span>
<span className={styles.infoValue}>
{numAmount > 0 ? `${ru(commission)}` : '—'}
</span>
</div>
<p className={styles.note}>
Итоговая комиссия рассчитывается индивидуально и зависит от объёма,
валюты и направления сделки.
</p>
</div>
</div>
<button type="submit" className={styles.submitBtn} disabled={belowMin}>
Оставить заявку
</button>
</form>
</div>
)
}

View File

@@ -18,6 +18,7 @@ import {
} from '../../swap-form/model/useSwapForm'
import { SwapCard } from '../../swap-form/ui/SwapCard'
import { NetworkSelect } from './NetworkSelect'
import { BridgeInfoPanel } from './BridgeInfoPanel'
import { BridgeConfirmModal } from './BridgeConfirmModal'
import styles from './BridgeForm.module.css'
@@ -152,6 +153,8 @@ export function BridgeForm() {
const { data: relayQuote } = useRelayQuote(relayQuotePayload)
const serviceFee = relayQuote?.appCommission?.usd?.toString()
const displayToAmount = relayQuote
? relayQuote.details.currencyOut.amountFormatted
: quote
@@ -242,6 +245,8 @@ export function BridgeForm() {
hideNetworkSelect
/>
<BridgeInfoPanel serviceFee={serviceFee} />
<PrimaryButton label={isPending ? 'Загрузка...' : 'Подтвердить бридж'} onClick={handleConfirm} disabled={!quotePayload || isPending} />
{quote && (

View File

@@ -0,0 +1,34 @@
.panel {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 20px;
padding: 4px 24px;
margin-bottom: 24px;
margin-top: 24px;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
}
.row:last-child {
border-bottom: none;
}
.label {
font-size: 12px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 1.2px;
font-weight: 700;
}
.value {
font-size: 14px;
color: var(--text-primary);
font-weight: 500;
}

View File

@@ -0,0 +1,22 @@
import styles from './BridgeInfoPanel.module.css'
interface Props {
serviceFee?: string
}
export function BridgeInfoPanel({ serviceFee }: Props) {
const rows = [
{ label: 'СЕРВИСНЫЙ СБОР', value: serviceFee ? `$${serviceFee}` : '—' },
]
return (
<div className={styles.panel}>
{rows.map(({ label, value }) => (
<div key={label} className={styles.row}>
<span className={styles.label}>{label}</span>
<span className={styles.value}>{value}</span>
</div>
))}
</div>
)
}

View File

@@ -39,7 +39,9 @@ function buildRows(
const isEth = meta.symbol === 'ETH'
const aprLabel = `Годовая доходность (${meta.aprLabel})`
const fee = quote ? `${truncateDecimals(quote.feeHuman, 8)} ${meta.symbol}` : DASH
// Сервисный сбор (0.7%) показываем в долларах из op-cost (appCommission.usd), а не в крипте.
const feeUsd = opCost?.appCommission?.usd
const fee = feeUsd != null ? usd(feeUsd) : DASH
const apr = quote?.aprPercent != null ? `${quote.aprPercent}%` : DASH
const resultLabel = isEth ? 'Вы получите' : 'Делегируется валидатору'

View File

@@ -109,6 +109,8 @@ export function SwapForm() {
? trxQuoteData?.fees.network.amountUsd?.toString()
: quoteData?.fees.gas.amountUsd
const serviceFee = quoteData?.appCommission?.usd?.toString()
const isButtonDisabled = isTrxNetwork
? parsedAmount <= 0 || isFetchingTrxQuote
: !quotePayload || isSwapping
@@ -153,7 +155,7 @@ export function SwapForm() {
onTokenChange={setToToken}
/>
<SwapInfoPanel gasFee={gasFee} />
<SwapInfoPanel gasFee={gasFee} serviceFee={serviceFee} />
<PrimaryButton
label={isSwapping || isFetchingTrxQuote ? 'Загрузка...' : undefined}

View File

@@ -2,13 +2,15 @@ import styles from './SwapInfoPanel.module.css'
interface Props {
gasFee?: string
serviceFee?: string
}
export function SwapInfoPanel({ gasFee }: Props) {
export function SwapInfoPanel({ gasFee, serviceFee }: Props) {
const rows = [
{ label: 'ПРОВАЙДЕР', value: 'ЛУЧШИЙ', link: false },
{ label: 'СКОЛЬЖЕНИЕ', value: 'АВТО (0.5%)', link: true },
{ label: 'СЕТЕВОЙ СБОР', value: gasFee ? `$${gasFee}` : '—', link: false },
{ label: 'СЕРВИСНЫЙ СБОР', value: serviceFee ? `$${serviceFee}` : '—', link: false },
]
return (

File diff suppressed because one or more lines are too long