remove admin

This commit is contained in:
2026-06-10 18:34:41 +03:00
parent 6c4f9a97a6
commit bf8a57359c
11 changed files with 331 additions and 171 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-D__qMdwn.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" /> <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>

View File

@@ -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)
}

View File

@@ -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'

View File

@@ -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

View File

@@ -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,
}) })
} }

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}, },