fix docs
This commit is contained in:
File diff suppressed because one or more lines are too long
161
dist/assets/index-BwMrNKcv.js
vendored
Normal file
161
dist/assets/index-BwMrNKcv.js
vendored
Normal file
File diff suppressed because one or more lines are too long
161
dist/assets/index-D4qEPrTa.js
vendored
161
dist/assets/index-D4qEPrTa.js
vendored
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" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ЭКСА — Ваш мост в мир цифровых активов</title>
|
||||
<script type="module" crossorigin src="/assets/index-D4qEPrTa.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CneFMUxK.css">
|
||||
<script type="module" crossorigin src="/assets/index-BwMrNKcv.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BGYsKcOB.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
4689
src/b2bapi.json
4689
src/b2bapi.json
File diff suppressed because it is too large
Load Diff
81
src/features/b2b/api/b2bApi.ts
Normal file
81
src/features/b2b/api/b2bApi.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { getCsrfToken } from '@shared/api/csrf'
|
||||
import { refreshAccessToken } from '@shared/api/tokenStore'
|
||||
|
||||
const B2B_API_URL = 'https://app.b2b.elcsa.ru/api'
|
||||
|
||||
async function doB2bRequest<T>(
|
||||
path: string,
|
||||
options: RequestInit,
|
||||
allowRetry: boolean,
|
||||
): Promise<T> {
|
||||
const csrf = await getCsrfToken()
|
||||
|
||||
const res = await fetch(`${B2B_API_URL}${path}`, {
|
||||
...options,
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'X-CSRF-Token': csrf,
|
||||
...options.headers,
|
||||
},
|
||||
})
|
||||
|
||||
if (res.status === 401 && allowRetry) {
|
||||
try {
|
||||
await refreshAccessToken()
|
||||
return doB2bRequest<T>(path, options, false)
|
||||
} catch {
|
||||
throw new Error('Unauthorized')
|
||||
}
|
||||
}
|
||||
|
||||
const data = await res.json()
|
||||
if (!res.ok) throw data
|
||||
return data as T
|
||||
}
|
||||
|
||||
export interface CreatePurchaseRequestBody {
|
||||
usdt_amount: number | string
|
||||
comment?: string | null
|
||||
target_wallet_chain?: string | null
|
||||
target_wallet_address?: string | null
|
||||
}
|
||||
|
||||
export interface B2bPurchaseRequest {
|
||||
id: string
|
||||
organization_id: string
|
||||
status: string
|
||||
usdt_amount: string
|
||||
rub_amount: string | null
|
||||
exchange_rate: string | null
|
||||
service_fee_percent: string | null
|
||||
comment: string | null
|
||||
admin_comment: string | null
|
||||
target_wallet_chain: string | null
|
||||
target_wallet_address: string | null
|
||||
tx_hash: string | null
|
||||
created_at: string | null
|
||||
updated_at: string | null
|
||||
completed_at: string | null
|
||||
}
|
||||
|
||||
export interface B2bPurchaseRequestListResponse {
|
||||
items: B2bPurchaseRequest[]
|
||||
total: number
|
||||
}
|
||||
|
||||
export function createPurchaseRequest(
|
||||
body: CreatePurchaseRequestBody,
|
||||
): Promise<B2bPurchaseRequest> {
|
||||
return doB2bRequest('/purchase-requests', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
}, true)
|
||||
}
|
||||
|
||||
export function getMyPurchaseRequests(
|
||||
limit = 50,
|
||||
offset = 0,
|
||||
): Promise<B2bPurchaseRequestListResponse> {
|
||||
return doB2bRequest(`/purchase-requests?limit=${limit}&offset=${offset}`, {}, true)
|
||||
}
|
||||
13
src/features/b2b/hooks/useCreatePurchaseRequest.ts
Normal file
13
src/features/b2b/hooks/useCreatePurchaseRequest.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { createPurchaseRequest } from '../api/b2bApi'
|
||||
|
||||
export function useCreatePurchaseRequest() {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation({
|
||||
mutationFn: createPurchaseRequest,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['b2b', 'purchase-requests'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
10
src/features/b2b/hooks/useMyPurchaseRequests.ts
Normal file
10
src/features/b2b/hooks/useMyPurchaseRequests.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { getMyPurchaseRequests } from '../api/b2bApi'
|
||||
|
||||
export function useMyPurchaseRequests() {
|
||||
return useQuery({
|
||||
queryKey: ['b2b', 'purchase-requests'],
|
||||
queryFn: () => getMyPurchaseRequests(),
|
||||
staleTime: 30_000,
|
||||
})
|
||||
}
|
||||
7
src/features/b2b/index.ts
Normal file
7
src/features/b2b/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { useCreatePurchaseRequest } from './hooks/useCreatePurchaseRequest'
|
||||
export { useMyPurchaseRequests } from './hooks/useMyPurchaseRequests'
|
||||
export type {
|
||||
CreatePurchaseRequestBody,
|
||||
B2bPurchaseRequest,
|
||||
B2bPurchaseRequestListResponse,
|
||||
} from './api/b2bApi'
|
||||
293
src/openapi.json
293
src/openapi.json
@@ -581,6 +581,24 @@
|
||||
"default": 0,
|
||||
"title": "Offset"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Q"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -806,6 +824,153 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/organizations/search": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"organizations"
|
||||
],
|
||||
"summary": "Search Parties",
|
||||
"operationId": "search_parties_v1_organizations_search_get",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
"title": "Q"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"maximum": 200,
|
||||
"minimum": 1,
|
||||
"default": 50,
|
||||
"title": "Limit"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "offset",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"title": "Offset"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PartySearchListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "Conflict",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"429": {
|
||||
"description": "Too Many Requests",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/organizations/{organization_id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -4833,6 +4998,134 @@
|
||||
],
|
||||
"title": "OrganizationResponse"
|
||||
},
|
||||
"PartySearchListResponse": {
|
||||
"properties": {
|
||||
"items": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/PartySearchResponse"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "Items"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"title": "Total"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"items",
|
||||
"total"
|
||||
],
|
||||
"title": "PartySearchListResponse"
|
||||
},
|
||||
"PartySearchResponse": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"title": "Id"
|
||||
},
|
||||
"account_type": {
|
||||
"type": "string",
|
||||
"title": "Account Type"
|
||||
},
|
||||
"user_id": {
|
||||
"type": "string",
|
||||
"title": "User Id"
|
||||
},
|
||||
"email": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Email"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Name"
|
||||
},
|
||||
"inn": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Inn"
|
||||
},
|
||||
"phone": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Phone"
|
||||
},
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Status"
|
||||
},
|
||||
"kyc_verified": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Kyc Verified"
|
||||
},
|
||||
"created_at": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Created At"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"account_type",
|
||||
"user_id",
|
||||
"email",
|
||||
"name",
|
||||
"inn",
|
||||
"phone",
|
||||
"status",
|
||||
"kyc_verified",
|
||||
"created_at"
|
||||
],
|
||||
"title": "PartySearchResponse"
|
||||
},
|
||||
"PurchaseRequestListResponse": {
|
||||
"properties": {
|
||||
"items": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { FormField, Select } from '@shared/ui'
|
||||
import { useCreatePurchaseRequest } from '@features/b2b'
|
||||
import { FormField, Notification, Select } from '@shared/ui'
|
||||
import styles from './LegalConverterPage.module.css'
|
||||
|
||||
const MIN_ORDER = 500_000
|
||||
@@ -35,6 +36,9 @@ export function LegalConverterPage() {
|
||||
const [name, setName] = useState('')
|
||||
const [contact, setContact] = useState('')
|
||||
const [days, setDays] = useState<number>(TERM_OPTIONS[0].days)
|
||||
const [notice, setNotice] = useState<'success' | 'error' | null>(null)
|
||||
|
||||
const { mutate: submitRequest, isPending } = useCreatePurchaseRequest()
|
||||
|
||||
const numAmount = Number(amount.replace(/\D/g, '')) || 0
|
||||
const belowMin = numAmount > 0 && numAmount < MIN_ORDER
|
||||
@@ -50,7 +54,31 @@ export function LegalConverterPage() {
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// Бэкенд пока не подключён — заявка никуда не отправляется.
|
||||
if (numAmount === 0 || belowMin || isPending) return
|
||||
|
||||
// Отдельных полей под имя/контакт/срок/сумму в ₽ у API нет — всё уходит в comment.
|
||||
const comment = [
|
||||
name && `Имя: ${name}`,
|
||||
contact && `Контакт: ${contact}`,
|
||||
`Срок ожидания: ${dayLabel(days)}`,
|
||||
`Сумма: ${ru(numAmount)} ₽`,
|
||||
`Комиссия: ≈${ru(commission)} ₽`,
|
||||
`Итого: ≈${ru(total)} ₽`,
|
||||
].filter(Boolean).join('; ')
|
||||
|
||||
submitRequest(
|
||||
{ usdt_amount: numAmount, comment },
|
||||
{
|
||||
onSuccess: () => {
|
||||
setNotice('success')
|
||||
setAmount('')
|
||||
setName('')
|
||||
setContact('')
|
||||
setDays(TERM_OPTIONS[0].days)
|
||||
},
|
||||
onError: () => setNotice('error'),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -145,9 +173,21 @@ export function LegalConverterPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" className={styles.submitBtn} disabled={belowMin}>
|
||||
Оставить заявку
|
||||
<button
|
||||
type="submit"
|
||||
className={styles.submitBtn}
|
||||
disabled={belowMin || numAmount === 0 || isPending}
|
||||
>
|
||||
{isPending ? 'Отправляем...' : 'Оставить заявку'}
|
||||
</button>
|
||||
|
||||
{notice && (
|
||||
<Notification
|
||||
status={notice}
|
||||
message={notice === 'success' ? 'Заявка отправлена' : 'Не удалось отправить заявку'}
|
||||
onClose={() => setNotice(null)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,41 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin: 0 0 20px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 38px;
|
||||
padding: 0 20px;
|
||||
background: transparent;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 10px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
border-color: rgba(74, 109, 255, 0.4);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.tab[data-active] {
|
||||
background: rgba(74, 109, 255, 0.12);
|
||||
border-color: rgba(74, 109, 255, 0.5);
|
||||
color: var(--interactive, #4a6dff);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.inner {
|
||||
padding: 20px 16px 32px;
|
||||
|
||||
@@ -1,12 +1,45 @@
|
||||
import { useState } from 'react'
|
||||
import { useMe } from '@features/auth'
|
||||
import { TransactionsList } from '@widgets/transactions-list'
|
||||
import { PurchaseRequestsList } from '@widgets/purchase-requests-list'
|
||||
import styles from './TransactionsPage.module.css'
|
||||
|
||||
type Tab = 'transactions' | 'requests'
|
||||
|
||||
export function TransactionsPage() {
|
||||
const { data } = useMe()
|
||||
const [tab, setTab] = useState<Tab>('transactions')
|
||||
|
||||
const isLegal = !!data && data.account_type !== 'individual'
|
||||
const activeTab: Tab = isLegal ? tab : 'transactions'
|
||||
|
||||
return (
|
||||
<div className={styles.inner}>
|
||||
<div className={styles.glow} />
|
||||
<h1 className={styles.title}>Транзакции</h1>
|
||||
<TransactionsList />
|
||||
|
||||
{isLegal && (
|
||||
<div className={styles.tabs}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.tab}
|
||||
data-active={activeTab === 'transactions' || undefined}
|
||||
onClick={() => setTab('transactions')}
|
||||
>
|
||||
Транзакции
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.tab}
|
||||
data-active={activeTab === 'requests' || undefined}
|
||||
onClick={() => setTab('requests')}
|
||||
>
|
||||
Заявки
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'transactions' ? <TransactionsList /> : <PurchaseRequestsList />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
1
src/widgets/purchase-requests-list/index.ts
Normal file
1
src/widgets/purchase-requests-list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { PurchaseRequestsList } from './ui/PurchaseRequestsList'
|
||||
@@ -0,0 +1,65 @@
|
||||
.status {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.statusError {
|
||||
color: var(--error, #ff4466);
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.empty {
|
||||
color: var(--text-secondary);
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tableWrap {
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.table th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table td {
|
||||
padding: 14px 16px;
|
||||
color: var(--text-primary);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mono {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.statusBadge {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
border-radius: 8px;
|
||||
background: rgba(74, 109, 255, 0.12);
|
||||
color: var(--interactive, #4a6dff);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { useMyPurchaseRequests } from '@features/b2b'
|
||||
import styles from './PurchaseRequestsList.module.css'
|
||||
|
||||
function formatAmount(value: string | null, suffix: string): string {
|
||||
if (!value) return '—'
|
||||
return `${value} ${suffix}`
|
||||
}
|
||||
|
||||
function formatDate(value: string | null): string {
|
||||
if (!value) return '—'
|
||||
const d = new Date(value)
|
||||
if (Number.isNaN(d.getTime())) return '—'
|
||||
return d.toLocaleString('ru-RU')
|
||||
}
|
||||
|
||||
export function PurchaseRequestsList() {
|
||||
const { data, isLoading, isError } = useMyPurchaseRequests()
|
||||
|
||||
if (isLoading) return <p className={styles.status}>Загрузка...</p>
|
||||
if (isError) return <p className={styles.statusError}>Не удалось загрузить заявки. Попробуйте обновить страницу.</p>
|
||||
if (!data || data.items.length === 0) return <p className={styles.empty}>У вас пока нет заявок.</p>
|
||||
|
||||
return (
|
||||
<div className={styles.tableWrap}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>USDT</th>
|
||||
<th>Сумма ₽</th>
|
||||
<th>Курс</th>
|
||||
<th>Статус</th>
|
||||
<th>Создана</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data.items.map((req) => (
|
||||
<tr key={req.id}>
|
||||
<td className={styles.mono}>{formatAmount(req.usdt_amount, 'USDT')}</td>
|
||||
<td className={styles.mono}>{formatAmount(req.rub_amount, '₽')}</td>
|
||||
<td className={styles.mono}>{req.exchange_rate ?? '—'}</td>
|
||||
<td>
|
||||
<span className={styles.statusBadge}>{req.status}</span>
|
||||
</td>
|
||||
<td>{formatDate(req.created_at)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user