add redirects

This commit is contained in:
2026-05-27 20:33:33 +03:00
parent acd30dbb13
commit 2e6ed487fd
11 changed files with 309 additions and 166 deletions

1
dist/assets/index-BrS4UPZI.css vendored Normal file

File diff suppressed because one or more lines are too long

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-woqmDgbc.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-H9RMF-3p.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DianSEHM.css">
<script type="module" crossorigin src="/assets/index-woqmDgbc.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-BrS4UPZI.css">
</head>
<body>
<div id="root"></div>

10
package-lock.json generated
View File

@@ -11,6 +11,7 @@
"@reduxjs/toolkit": "^2.5.1",
"@tanstack/react-query": "^5.100.9",
"axios": "^1.7.9",
"qrcode.react": "^4.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-easy-crop": "^5.5.7",
@@ -3223,6 +3224,15 @@
"node": ">=6"
}
},
"node_modules/qrcode.react": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.2.0.tgz",
"integrity": "sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==",
"license": "ISC",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react": {
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz",

View File

@@ -13,6 +13,7 @@
"@reduxjs/toolkit": "^2.5.1",
"@tanstack/react-query": "^5.100.9",
"axios": "^1.7.9",
"qrcode.react": "^4.2.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-easy-crop": "^5.5.7",

View File

@@ -1,11 +1,14 @@
import { useState, useEffect } from 'react'
import { PrimaryButton } from '@shared/ui'
import { useNavigate } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import { Notification, PrimaryButton } from '@shared/ui'
import {
useWalletBalance, useWalletAddresses, useTokensList,
useRelayQuote, useExecuteRelaySwap, useSignSwap,
useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap,
type Chain, type RelaySwapResponse, type TrxSwapQuoteData,
} from '@features/wallet'
import { ROUTES } from '@shared/config/routes'
import { useDebounce } from '@shared/lib/hooks/useDebounce'
import { TOKENS_LIST, buildTokensFromBalance, useSwapForm } from '../../swap-form/model/useSwapForm'
import { SwapCard } from '../../swap-form/ui/SwapCard'
@@ -26,6 +29,9 @@ function nativeAddr(chain: string) {
}
export function BridgeForm() {
const navigate = useNavigate()
const queryClient = useQueryClient()
const {
fromAmount, fromUsd,
fromToken, toToken,
@@ -37,6 +43,7 @@ export function BridgeForm() {
const [toNetwork, setToNetwork] = useState('BSC')
const [modalData, setModalData] = useState<RelaySwapResponse | null>(null)
const [trxModalQuote, setTrxModalQuote] = useState<TrxSwapQuoteData | null>(null)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const isTrxNetwork = fromNetwork === 'TRX'
@@ -84,7 +91,7 @@ export function BridgeForm() {
const { data: quoteData } = useRelayQuote(quotePayload)
const { mutate: executeSwap, isPending: isSwapping } = useExecuteRelaySwap()
const { mutate: signSwap } = useSignSwap()
const { mutate: signSwap, isPending: isSigning } = useSignSwap()
const trxQuotePayload = isTrxNetwork && parsedAmount > 0
? { from: fromToken.symbol, to: toToken.symbol, amountHuman: debouncedAmount }
@@ -92,7 +99,9 @@ export function BridgeForm() {
const { data: trxQuoteData } = useTrxSwapQuote(trxQuotePayload)
const { mutate: fetchTrxQuote, isPending: isFetchingTrxQuote } = useFetchTrxQuote()
const { mutate: executeTrxSwap } = useExecuteTrxSwap()
const { mutate: executeTrxSwap, isPending: isExecutingTrxSwap } = useExecuteTrxSwap()
const isProcessing = isSigning || isExecutingTrxSwap
const displayToAmount = isTrxNetwork
? (trxQuoteData?.expectedOutFormatted ?? '0')
@@ -162,7 +171,23 @@ export function BridgeForm() {
onClose={() => setModalData(null)}
onConfirm={() => {
const txData = modalData.steps[0]?.items[0]?.data
if (txData) signSwap({ chain: fromNetwork as Chain, txData })
if (txData) {
setErrorMessage(null)
signSwap(
{ chain: fromNetwork as Chain, txData },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', fromNetwork] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', toNetwork] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'portfolio'] })
navigate(ROUTES.WALLET)
},
onError: (err) => {
setErrorMessage(err instanceof Error ? err.message : 'Не удалось подписать транзакцию')
},
},
)
}
setModalData(null)
}}
/>
@@ -176,11 +201,38 @@ export function BridgeForm() {
amountHuman={fromAmount}
onClose={() => setTrxModalQuote(null)}
onConfirm={() => {
executeTrxSwap(trxModalQuote.quoteId)
setErrorMessage(null)
executeTrxSwap(trxModalQuote.quoteId, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', 'TRX'] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', toNetwork] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'portfolio'] })
navigate(ROUTES.WALLET)
},
onError: (err) => {
setErrorMessage(err instanceof Error ? err.message : 'Не удалось выполнить бридж')
},
})
setTrxModalQuote(null)
}}
/>
)}
{isProcessing && (
<Notification
status="warning"
message="Обработка транзакции..."
onClose={() => {}}
/>
)}
{errorMessage && (
<Notification
status="error"
message={errorMessage}
onClose={() => setErrorMessage(null)}
/>
)}
</div>
)
}

View File

@@ -59,6 +59,15 @@
gap: 12px;
}
.qrWrap {
align-self: center;
padding: 12px;
background: #fff;
border-radius: 12px;
line-height: 0;
margin-top: 8px;
}
.label {
font-size: 13px;
color: var(--text-secondary, rgba(255,255,255,0.5));

View File

@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { QRCodeSVG } from 'qrcode.react'
import { useWalletAddresses, type Chain } from '@features/wallet'
import styles from './ReceiveModal.module.css'
@@ -87,6 +88,18 @@ export function ReceiveModal({ open, onClose, chain }: ReceiveModalProps) {
{copied ? 'Скопировано!' : 'Копировать'}
</button>
</div>
{address && (
<div className={styles.qrWrap}>
<QRCodeSVG
value={address}
size={196}
level="M"
bgColor="#ffffff"
fgColor="#000000"
marginSize={2}
/>
</div>
)}
</>
)}
</div>

View File

@@ -1,11 +1,14 @@
import { useState, useEffect } from 'react'
import { PrimaryButton } from '@shared/ui'
import { useNavigate } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import { Notification, PrimaryButton } from '@shared/ui'
import {
useWalletBalance, useWalletAddresses, useTokensList,
useRelayQuote, useExecuteRelaySwap, useSignSwap,
useTrxSwapQuote, useFetchTrxQuote, useExecuteTrxSwap,
type Chain, type RelaySwapResponse, type TrxSwapQuoteData,
} from '@features/wallet'
import { ROUTES } from '@shared/config/routes'
import { useDebounce } from '@shared/lib/hooks/useDebounce'
import { TOKENS_LIST, buildTokensFromBalance, useSwapForm } from '../model/useSwapForm'
import { SwapCard } from './SwapCard'
@@ -26,6 +29,9 @@ function nativeAddr(chain: string) {
}
export function SwapForm() {
const navigate = useNavigate()
const queryClient = useQueryClient()
const {
fromAmount, fromUsd,
fromToken, toToken,
@@ -36,6 +42,7 @@ export function SwapForm() {
const [fromNetwork, setFromNetwork] = useState('ETH')
const [modalData, setModalData] = useState<RelaySwapResponse | null>(null)
const [trxModalQuote, setTrxModalQuote] = useState<TrxSwapQuoteData | null>(null)
const [errorMessage, setErrorMessage] = useState<string | null>(null)
const isTrxNetwork = fromNetwork === 'TRX'
@@ -75,7 +82,7 @@ export function SwapForm() {
const { data: quoteData } = useRelayQuote(quotePayload)
const { mutate: executeSwap, isPending: isSwapping } = useExecuteRelaySwap()
const { mutate: signSwap } = useSignSwap()
const { mutate: signSwap, isPending: isSigning } = useSignSwap()
// ── TRX ─────────────────────────────────────────────────────────────────
const trxQuotePayload = isTrxNetwork && parsedAmount > 0
@@ -84,7 +91,9 @@ export function SwapForm() {
const { data: trxQuoteData } = useTrxSwapQuote(trxQuotePayload)
const { mutate: fetchTrxQuote, isPending: isFetchingTrxQuote } = useFetchTrxQuote()
const { mutate: executeTrxSwap } = useExecuteTrxSwap()
const { mutate: executeTrxSwap, isPending: isExecutingTrxSwap } = useExecuteTrxSwap()
const isProcessing = isSigning || isExecutingTrxSwap
// ── Display values ───────────────────────────────────────────────────────
const displayToAmount = isTrxNetwork
@@ -153,7 +162,22 @@ export function SwapForm() {
onClose={() => setModalData(null)}
onConfirm={() => {
const txData = modalData.steps[0]?.items[0]?.data
if (txData) signSwap({ chain: fromNetwork as Chain, txData })
if (txData) {
setErrorMessage(null)
signSwap(
{ chain: fromNetwork as Chain, txData },
{
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', fromNetwork] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'portfolio'] })
navigate(ROUTES.WALLET)
},
onError: (err) => {
setErrorMessage(err instanceof Error ? err.message : 'Не удалось подписать транзакцию')
},
},
)
}
setModalData(null)
}}
/>
@@ -167,11 +191,37 @@ export function SwapForm() {
amountHuman={fromAmount}
onClose={() => setTrxModalQuote(null)}
onConfirm={() => {
executeTrxSwap(trxModalQuote.quoteId)
setErrorMessage(null)
executeTrxSwap(trxModalQuote.quoteId, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['wallet', 'balance', 'TRX'] })
queryClient.invalidateQueries({ queryKey: ['wallet', 'portfolio'] })
navigate(ROUTES.WALLET)
},
onError: (err) => {
setErrorMessage(err instanceof Error ? err.message : 'Не удалось выполнить свап')
},
})
setTrxModalQuote(null)
}}
/>
)}
{isProcessing && (
<Notification
status="warning"
message="Обработка транзакции..."
onClose={() => {}}
/>
)}
{errorMessage && (
<Notification
status="error"
message={errorMessage}
onClose={() => setErrorMessage(null)}
/>
)}
</div>
)
}