remove admin
This commit is contained in:
161
dist/assets/index-BwMrNKcv.js
vendored
161
dist/assets/index-BwMrNKcv.js
vendored
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-D__qMdwn.js
vendored
Normal file
161
dist/assets/index-D__qMdwn.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@@ -5,8 +5,8 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BwMrNKcv.js"></script>
|
<script type="module" crossorigin src="/assets/index-D__qMdwn.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-BGYsKcOB.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-CfMiYyAp.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -79,3 +79,48 @@ export function getMyPurchaseRequests(
|
|||||||
): Promise<B2bPurchaseRequestListResponse> {
|
): Promise<B2bPurchaseRequestListResponse> {
|
||||||
return doB2bRequest(`/purchase-requests?limit=${limit}&offset=${offset}`, {}, true)
|
return doB2bRequest(`/purchase-requests?limit=${limit}&offset=${offset}`, {}, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Organizations (legal-entity wallets) ---
|
||||||
|
// Note: organizations endpoints live under /v1, unlike /purchase-requests.
|
||||||
|
|
||||||
|
export interface OrgWallet {
|
||||||
|
id: string
|
||||||
|
chain: string
|
||||||
|
address: string
|
||||||
|
derivation_path: string
|
||||||
|
created_at: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgAmountRaw {
|
||||||
|
raw: string
|
||||||
|
formatted: string
|
||||||
|
decimals: number
|
||||||
|
usd_price: string
|
||||||
|
usd_value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgWalletBalance {
|
||||||
|
id: string
|
||||||
|
chain: string
|
||||||
|
address: string
|
||||||
|
derivation_path: string
|
||||||
|
native_symbol: string
|
||||||
|
native: OrgAmountRaw
|
||||||
|
tokens: Record<string, OrgAmountRaw>
|
||||||
|
total_usd: string
|
||||||
|
error: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OrgBalancesResponse {
|
||||||
|
items: OrgWalletBalance[]
|
||||||
|
total_usd: string
|
||||||
|
has_errors: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrganizationWallets(): Promise<OrgWallet[]> {
|
||||||
|
return doB2bRequest('/v1/organizations/wallets', {}, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getOrganizationBalances(): Promise<OrgBalancesResponse> {
|
||||||
|
return doB2bRequest('/v1/organizations/wallets/balances', {}, true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
export { useCreatePurchaseRequest } from './hooks/useCreatePurchaseRequest'
|
export { useCreatePurchaseRequest } from './hooks/useCreatePurchaseRequest'
|
||||||
export { useMyPurchaseRequests } from './hooks/useMyPurchaseRequests'
|
export { useMyPurchaseRequests } from './hooks/useMyPurchaseRequests'
|
||||||
|
export { getOrganizationWallets, getOrganizationBalances } from './api/b2bApi'
|
||||||
export type {
|
export type {
|
||||||
CreatePurchaseRequestBody,
|
CreatePurchaseRequestBody,
|
||||||
B2bPurchaseRequest,
|
B2bPurchaseRequest,
|
||||||
B2bPurchaseRequestListResponse,
|
B2bPurchaseRequestListResponse,
|
||||||
|
OrgWallet,
|
||||||
|
OrgAmountRaw,
|
||||||
|
OrgWalletBalance,
|
||||||
|
OrgBalancesResponse,
|
||||||
} from './api/b2bApi'
|
} from './api/b2bApi'
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import { getCsrfToken } from '@shared/api/csrf'
|
import { getCsrfToken } from '@shared/api/csrf'
|
||||||
import { tokenStore, refreshAccessToken } from '@shared/api/tokenStore'
|
import { tokenStore, refreshAccessToken } from '@shared/api/tokenStore'
|
||||||
|
import {
|
||||||
|
getOrganizationWallets,
|
||||||
|
getOrganizationBalances,
|
||||||
|
type OrgAmountRaw,
|
||||||
|
type OrgWalletBalance,
|
||||||
|
} from '@features/b2b'
|
||||||
|
|
||||||
const WALLET_API_URL = 'https://app.cryptowallet.elcsa.ru'
|
const WALLET_API_URL = 'https://app.cryptowallet.elcsa.ru'
|
||||||
|
|
||||||
@@ -148,6 +154,94 @@ export async function getPortfolio(): Promise<PortfolioData> {
|
|||||||
return res.data
|
return res.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Legal-entity (b2b organizations) adapters ---
|
||||||
|
// The b2b balances endpoint returns the same information as the cryptowallet
|
||||||
|
// portfolio, but with snake_case keys, string-encoded numbers and an items[]
|
||||||
|
// array instead of a perChain map. These adapters normalise it into the
|
||||||
|
// existing PortfolioData / WalletAddress shapes so the wallet UI is reused as-is.
|
||||||
|
|
||||||
|
const NATIVE_SYMBOL_TO_CHAIN: Record<string, Chain> = {
|
||||||
|
ETH: 'ETH',
|
||||||
|
BNB: 'BSC',
|
||||||
|
BTC: 'BTC',
|
||||||
|
TRX: 'TRX',
|
||||||
|
SOL: 'SOL',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Normalise a b2b chain/native_symbol into our Chain enum, or null if unknown. */
|
||||||
|
function normalizeChain(chain: string, nativeSymbol?: string): Chain | null {
|
||||||
|
const upper = chain?.toUpperCase()
|
||||||
|
if ((CHAINS as string[]).includes(upper)) return upper as Chain
|
||||||
|
const bySymbol = nativeSymbol ? NATIVE_SYMBOL_TO_CHAIN[nativeSymbol.toUpperCase()] : undefined
|
||||||
|
return bySymbol ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNum(value: string | number | null | undefined): number {
|
||||||
|
const n = typeof value === 'number' ? value : parseFloat(value ?? '')
|
||||||
|
return Number.isFinite(n) ? n : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFormattedAmount(a: OrgAmountRaw): FormattedAmount {
|
||||||
|
return {
|
||||||
|
raw: a.raw,
|
||||||
|
formatted: a.formatted,
|
||||||
|
decimals: a.decimals,
|
||||||
|
usdPrice: parseNum(a.usd_price),
|
||||||
|
usdValue: parseNum(a.usd_value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toPortfolioChain(item: OrgWalletBalance, chain: Chain): PortfolioChain {
|
||||||
|
const tokens: Record<string, FormattedAmount> = {}
|
||||||
|
for (const [symbol, amount] of Object.entries(item.tokens ?? {})) {
|
||||||
|
tokens[symbol] = toFormattedAmount(amount)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
chain,
|
||||||
|
address: item.address,
|
||||||
|
native: toFormattedAmount(item.native),
|
||||||
|
tokens,
|
||||||
|
totalUsd: parseNum(item.total_usd),
|
||||||
|
stale: false,
|
||||||
|
lastUpdated: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrgPortfolio(): Promise<PortfolioData> {
|
||||||
|
const res = await getOrganizationBalances()
|
||||||
|
const perChain = {} as Record<Chain, PortfolioChain>
|
||||||
|
for (const item of res.items ?? []) {
|
||||||
|
const chain = normalizeChain(item.chain, item.native_symbol)
|
||||||
|
if (!chain) continue
|
||||||
|
perChain[chain] = toPortfolioChain(item, chain)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
totalUsd: parseNum(res.total_usd),
|
||||||
|
hasErrors: !!res.has_errors,
|
||||||
|
perChain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMPTY_AMOUNT: FormattedAmount = { raw: '0', formatted: '0', decimals: 0, usdPrice: 0, usdValue: 0 }
|
||||||
|
|
||||||
|
export async function getOrgWalletBalance(chain: Chain): Promise<WalletBalanceData> {
|
||||||
|
const portfolio = await getOrgPortfolio()
|
||||||
|
const c = portfolio.perChain[chain]
|
||||||
|
if (!c) return { chain, address: '', native: EMPTY_AMOUNT, tokens: {} }
|
||||||
|
return { chain, address: c.address, native: c.native, tokens: c.tokens }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getOrgWalletAddresses(): Promise<WalletAddress[]> {
|
||||||
|
const wallets = await getOrganizationWallets()
|
||||||
|
const result: WalletAddress[] = []
|
||||||
|
for (const w of wallets ?? []) {
|
||||||
|
const chain = normalizeChain(w.chain)
|
||||||
|
if (!chain) continue
|
||||||
|
result.push({ chain, address: w.address, derivationPath: w.derivation_path })
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export interface TokenInfo {
|
export interface TokenInfo {
|
||||||
chain: string
|
chain: string
|
||||||
symbol: string
|
symbol: string
|
||||||
|
|||||||
@@ -1,10 +1,19 @@
|
|||||||
import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
|
import { useQuery, useQueries, useMutation } from '@tanstack/react-query'
|
||||||
import { getWalletBalance, getPrices, sendWallet, getWalletAddresses, getPortfolio, getTokensList, getRelayQuote, executeRelaySwap, signRawEvmTx, signSolTx, getTrxSwapQuote, executeTrxSwap, getJumperTokens, getJumperQuote, executeBridge, createWallet, revealMnemonic, CHAINS, type Chain, type SendWalletPayload, type RelayQuotePayload, type RelaySwapStep, type TrxSwapQuotePayload, type JumperQuotePayload, type BridgeExecutePayload } from '../api/walletApi'
|
import { useMe } from '@features/auth'
|
||||||
|
import { getWalletBalance, getPrices, sendWallet, getWalletAddresses, getPortfolio, getOrgPortfolio, getOrgWalletBalance, getOrgWalletAddresses, getTokensList, getRelayQuote, executeRelaySwap, signRawEvmTx, signSolTx, getTrxSwapQuote, executeTrxSwap, getJumperTokens, getJumperQuote, executeBridge, createWallet, revealMnemonic, CHAINS, type Chain, type SendWalletPayload, type RelayQuotePayload, type RelaySwapStep, type TrxSwapQuotePayload, type JumperQuotePayload, type BridgeExecutePayload } from '../api/walletApi'
|
||||||
|
|
||||||
|
/** Legal-entity accounts read wallet data from b2b instead of cryptowallet. */
|
||||||
|
function useIsLegalAccount() {
|
||||||
|
const { data: me } = useMe()
|
||||||
|
return { isLegal: me?.account_type === 'legal_entity', ready: !!me }
|
||||||
|
}
|
||||||
|
|
||||||
export function useWalletBalance(chain: Chain) {
|
export function useWalletBalance(chain: Chain) {
|
||||||
|
const { isLegal, ready } = useIsLegalAccount()
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['wallet', 'balance', chain],
|
queryKey: ['wallet', 'balance', chain, isLegal ? 'org' : 'self'],
|
||||||
queryFn: () => getWalletBalance(chain),
|
queryFn: isLegal ? () => getOrgWalletBalance(chain) : () => getWalletBalance(chain),
|
||||||
|
enabled: isLegal ? ready : true,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -35,17 +44,21 @@ export function useSendWallet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useWalletAddresses() {
|
export function useWalletAddresses() {
|
||||||
|
const { isLegal, ready } = useIsLegalAccount()
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['wallet', 'addresses'],
|
queryKey: ['wallet', 'addresses', isLegal ? 'org' : 'self'],
|
||||||
queryFn: getWalletAddresses,
|
queryFn: isLegal ? getOrgWalletAddresses : getWalletAddresses,
|
||||||
|
enabled: isLegal ? ready : true,
|
||||||
staleTime: 10 * 60 * 1000,
|
staleTime: 10 * 60 * 1000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePortfolio() {
|
export function usePortfolio() {
|
||||||
|
const { isLegal, ready } = useIsLegalAccount()
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['wallet', 'portfolio'],
|
queryKey: ['wallet', 'portfolio', isLegal ? 'org' : 'self'],
|
||||||
queryFn: getPortfolio,
|
queryFn: isLegal ? getOrgPortfolio : getPortfolio,
|
||||||
|
enabled: isLegal ? ready : true,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function useLoginForm() {
|
|||||||
mutationFn: loginComplete,
|
mutationFn: loginComplete,
|
||||||
onSuccess: async ({ access_token }) => {
|
onSuccess: async ({ access_token }) => {
|
||||||
clearCsrfCache()
|
clearCsrfCache()
|
||||||
|
queryClient.clear()
|
||||||
if (access_token) tokenStore.set(access_token)
|
if (access_token) tokenStore.set(access_token)
|
||||||
await queryClient.invalidateQueries({ queryKey: AUTH_QUERY_KEY })
|
await queryClient.invalidateQueries({ queryKey: AUTH_QUERY_KEY })
|
||||||
navigate(ROUTES.PROFILE)
|
navigate(ROUTES.PROFILE)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function useRegisterForm() {
|
|||||||
mutationFn: registrationComplete,
|
mutationFn: registrationComplete,
|
||||||
onSuccess: async ({ access_token }) => {
|
onSuccess: async ({ access_token }) => {
|
||||||
clearCsrfCache()
|
clearCsrfCache()
|
||||||
|
queryClient.clear()
|
||||||
if (access_token) tokenStore.set(access_token)
|
if (access_token) tokenStore.set(access_token)
|
||||||
await queryClient.invalidateQueries({ queryKey: AUTH_QUERY_KEY })
|
await queryClient.invalidateQueries({ queryKey: AUTH_QUERY_KEY })
|
||||||
navigate(ROUTES.WALLET)
|
navigate(ROUTES.WALLET)
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export function WalletHeader() {
|
|||||||
mutationFn: logout,
|
mutationFn: logout,
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
tokenStore.clear()
|
tokenStore.clear()
|
||||||
|
queryClient.clear()
|
||||||
queryClient.setQueryData(AUTH_QUERY_KEY, null)
|
queryClient.setQueryData(AUTH_QUERY_KEY, null)
|
||||||
navigate(ROUTES.HOME)
|
navigate(ROUTES.HOME)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user