/* MBEX — Home screen */ function getHomeRateMeta(dir, rates) { const buying = dir === "buy"; const rate = Number((buying ? rates.buy : rates.sell) || 0); const marketRate = Number( buying ? (rates.marketSell || rates.cbr || rates.buy || 0) : (rates.marketBuy || rates.cbr || rates.sell || 0), ); const enabled = buying ? rates.tradeAvailability?.buy_enabled !== false : rates.tradeAvailability?.sell_enabled !== false; const action = buying ? "Вы покупаете USDT" : "Вы продаёте USDT"; const toneClass = buying ? "rate-hero-buy" : "rate-hero-sell"; return { buying, rate, marketRate, enabled, action, toneClass }; } function formatRateTimestamp(value) { if (!value) { return "Обновляется"; } const parsed = Date.parse(value); if (!Number.isFinite(parsed)) { return "Обновляется"; } return `Актуально ${new Date(parsed).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit", }).replace(", ", " в ")}`; } function getRateRange(data, fallbackRate) { const values = (Array.isArray(data) ? data : []) .map((item) => Number(typeof item === "object" ? item.value : item)) .filter((item) => Number.isFinite(item) && item > 0); const fallback = Number(fallbackRate) || 0; return { min: values.length ? Math.min(...values) : fallback, max: values.length ? Math.max(...values) : fallback, }; } function formatRateTitle(value) { if (!value || !Number.isFinite(Date.parse(value))) { return "Курс обновляется"; } return `Обновлено ${new Date(Date.parse(value)).toLocaleString("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", })}`; } function RateHero({ dir, app }) { const [isRateInfoOpen, setIsRateInfoOpen] = React.useState(false); const rates = app.publicState.rates; const meta = getHomeRateMeta(dir, rates); const marketDiff = meta.marketRate > 0 && meta.rate > 0 ? meta.rate - meta.marketRate : 0; const marketDeltaPct = meta.marketRate > 0 && meta.rate > 0 ? (marketDiff / meta.marketRate) * 100 : 0; const chartData = meta.buying ? rates.buyPoints : rates.sellPoints; const updatedLabel = formatRateTimestamp(rates.updatedAt); const rateRange = getRateRange(chartData, meta.rate); const rateFixingInfo = meta.buying ? "Курс фиксируется после пересчёта средств." : "Курс фиксируется после поступления и проверки USDT на одноразовый кошелёк."; React.useEffect(() => { setIsRateInfoOpen(false); }, [dir]); return (
USDT / RUB
{window.MBEX.fmtRub2(meta.rate)} = 0 ? "is-up" : "is-down"}> {marketDeltaPct >= 0 ? "+" : ""}{marketDeltaPct.toFixed(1)}%
{updatedLabel}
Мин. {window.MBEX.fmtRub2(rateRange.min)} ₽
Макс. {window.MBEX.fmtRub2(rateRange.max)} ₽
{meta.action}

{rateFixingInfo}

{!meta.enabled ? (
{meta.buying ? "Покупка временно недоступна" : "Продажа временно недоступна"}
) : null}
); } function DirectionSwitcher({ dir, setDir, app }) { const rates = app.publicState.rates; return (
); } const CALCULATOR_RECEIVE_NOTICE = "Обратите внимание: сумма в калькуляторе является предварительной и не включает комиссию сервиса и комиссию сети TRC20. Итоговая сумма к получению с учетом всех комиссий указана ниже в расчете"; function CalculatorReceiveNotice({ onClose }) { React.useEffect(() => { const handleKeyDown = (event) => { if (event.key === "Escape") { onClose(); } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [onClose]); return ReactDOM.createPortal(
event.stopPropagation()} >
Предварительный расчёт

{CALCULATOR_RECEIVE_NOTICE}

, document.body, ); } function CalcCard({ dir, setDir, payInput, setPayInput, app, onCreate }) { const rates = app.publicState.rates; const profile = app.privateState.profile || {}; const referral = app.privateState.referral || {}; const [activeInput, setActiveInput] = React.useState("pay"); const [receiveInput, setReceiveInput] = React.useState(""); const [isReceiveNoticeOpen, setIsReceiveNoticeOpen] = React.useState(false); const meta = getHomeRateMeta(dir, rates); const buying = meta.buying; const rate = meta.rate; const networkFeeUsdt = Number(rates.commissionSettings?.network_fee_usdt || 0); const rawPayInput = normalizeCalcInput(payInput); const rawReceiveInput = normalizeCalcInput(receiveInput); const payInputNum = parseCalcInput(rawPayInput); const receiveInputNum = parseCalcInput(rawReceiveInput); const reversePayNum = activeInput === "receive" ? ( buying ? window.MBEX.calculateBuyRubFromGrossUsdt(receiveInputNum, rate) : window.MBEX.calculateSellUsdtFromPayoutRub(receiveInputNum, rate, profile) ) : 0; const rawPayNum = activeInput === "receive" ? reversePayNum : payInputNum; const payNum = buying ? window.MBEX.snapRubStepAmount(rawPayNum, 1000) : rawPayNum; const draftPayNum = roundCalcAmount(payNum, buying ? 0 : 2); const buyEstimate = buying ? window.MBEX.calculateBuyEstimate(payNum, rate, profile, referral, networkFeeUsdt) : null; const sellEstimate = !buying ? window.MBEX.calculateSellEstimate(payNum, rate, profile) : null; const receiveValue = activeInput === "receive" ? receiveInput : formatCalcInput( buying ? buyEstimate?.grossUsdt : sellEstimate?.payoutRub, buying ? 2 : 0, ); const payValue = activeInput === "receive" ? formatCalcInput(payNum, buying ? 0 : 2) : payInput; const minSell = Number(rates.sellLimits?.min_usdt || 65); const maxSell = Number(rates.sellLimits?.max_usdt || 100000); const buyEnabled = rates.tradeAvailability?.buy_enabled !== false; const sellEnabled = rates.tradeAvailability?.sell_enabled !== false; const currentEnabled = buying ? buyEnabled : sellEnabled; const sumOk = buying ? rawPayNum > 0 && payNum >= 1000 : payNum >= minSell && payNum <= maxSell; const canContinue = currentEnabled && sumOk && rawPayNum > 0; function onInput(event) { setActiveInput("pay"); setReceiveInput(""); setPayInput(event.target.value.replace(/[^\d.,]/g, "")); } function onReceiveInput(event) { setActiveInput("receive"); setReceiveInput(event.target.value.replace(/[^\d.,]/g, "")); } function snapPayInput() { if (!buying || activeInput !== "pay" || payInputNum <= 0) { return; } setPayInput(formatCalcInput(window.MBEX.snapRubStepAmount(payInputNum, 1000), 0)); } function onSwap() { if (activeInput === "receive") { setPayInput(formatCalcInput(payNum, buying ? 0 : 2)); setReceiveInput(""); setActiveInput("pay"); } setDir(buying ? "sell" : "buy"); } function disabledText() { if (buying && !buyEnabled) { return "Покупка USDT временно отключена в настройках Mini App."; } if (!buying && !sellEnabled) { return "Продажа USDT временно отключена в настройках Mini App."; } return ""; } return (
{!currentEnabled ? (
{disabledText()}
) : null}
Вы платите {buying ? "Мин. 1 000 ₽ · шаг 500 ₽" : `Мин. ${minSell} USDT`}
{buying ? "RUB" : "USDT"}
Вы получаете
{buying ? "USDT" : "RUB"}
1 USDT = {window.MBEX.fmtRub2(rate)} ₽
{buying ? ( <>
≈ Предварительно {window.MBEX.fmtUsdt(buyEstimate?.walletUsdt || 0)} USDT
Комиссия {window.MBEX.fmtUsdt(buyEstimate?.commissionUsdt || 0)} USDT / {window.MBEX.fmtPct(buyEstimate?.commissionPct || 0)}%
Комиссия сети TRC-20 {window.MBEX.fmtUsdt(buyEstimate?.networkFeeUsdt || 0)} USDT
) : ( <>
≈ Предварительно {window.MBEX.fmtRub(sellEstimate?.payoutRub || 0)} ₽
Комиссия {window.MBEX.fmtRub(sellEstimate?.commissionRub || 0)} ₽ / {window.MBEX.fmtPct(sellEstimate?.commissionPct || 0)}%
)}
{isReceiveNoticeOpen ? ( setIsReceiveNoticeOpen(false)} /> ) : null}
); } function normalizeCalcInput(value) { return String(value || "").replace(/\s/g, "").replace(",", "."); } function parseCalcInput(value) { const parsed = Number(normalizeCalcInput(value) || 0); return Number.isFinite(parsed) ? parsed : 0; } function formatCalcInput(value, maximumFractionDigits) { const num = Number(value || 0); if (!Number.isFinite(num) || num <= 0) { return ""; } return num.toLocaleString("ru-RU", { maximumFractionDigits, }); } function roundCalcAmount(value, fractionDigits) { const num = Number(value || 0); if (!Number.isFinite(num) || num <= 0) { return 0; } const factor = 10 ** fractionDigits; return Math.round(num * factor) / factor; } function ActiveOrderCard({ order, nowMs, onOpen }) { const statusMeta = window.MBEX.getStatusMeta(order, nowMs); const secondsLeft = window.MBEX.getOrderSecondsLeft(order, nowMs); const transfer = order.typeKey === "transfer"; const buying = order.typeKey === "buy"; const title = transfer ? "Перестановка RUB" : buying ? "Покупка USDT" : "Продажа USDT"; const route = transfer ? [order.fromCity, order.toCity].filter(Boolean).join(" → ") : ""; return (
{statusMeta.title} {secondsLeft > 0 ? window.MBEX.formatDuration(secondsLeft) : "00:00"}
{title}
{window.MBEX.orderDisplayCode(order)} · {route || statusMeta.subtitle}
{window.MBEX.orderDisplayAmount(order)}
{transfer ? "Маршрут" : (order.sumUsdt || order.depositAmountReceived) > 0 ? `${window.MBEX.fmtUsdt(order.sumUsdt || order.depositAmountReceived)} USDT` : "Ожидаем сумму"}
Открыть статус
); } function formatCbrDate(nowMs, offsetDays) { const baseMs = Number(nowMs) || Date.now(); return new Date(baseMs + offsetDays * 24 * 60 * 60 * 1000).toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", timeZone: "Europe/Moscow", }); } function CbrRateCard({ app }) { const rates = app.publicState.rates; const currentRate = Number(rates.cbr || 0); const previousRate = Number(rates.cbrYesterday || 0); const changePct = currentRate > 0 && previousRate > 0 ? ((currentRate - previousRate) / previousRate) * 100 : null; const changeTone = changePct > 0 ? "is-up" : changePct < 0 ? "is-down" : "is-flat"; return (
Курс ЦБ РФ
Вчера, {formatCbrDate(app.nowMs, -1)}
{previousRate > 0 ? `${window.MBEX.fmtRub2(previousRate)} ₽` : "—"}
Сегодня, {formatCbrDate(app.nowMs, 0)}
{currentRate > 0 ? `${window.MBEX.fmtRub2(currentRate)} ₽` : "—"} {changePct !== null ? ( {changePct > 0 ? : null} {changePct < 0 ? : null} {changePct > 0 ? "+" : ""}{changePct.toFixed(2)}% ) : null}
); } function ChartCard({ app }) { const rates = app.publicState.rates; const [timeframe, setTimeframe] = React.useState("MBEX"); const data = timeframe === "Рынок" ? rates.marketSeries : rates.buySeries; const current = timeframe === "Рынок" ? Number(rates.marketSell || 0) : Number(rates.buy || 0); const first = data.length ? data[0] : current; const last = data.length ? data[data.length - 1] : current; const change = first > 0 ? ((last - first) / first) * 100 : 0; const up = change >= 0; return (
Курс USDT / RUB
{window.MBEX.fmtRub2(current)} ₽ {up ? : } {Math.abs(change).toFixed(2)}%
{["MBEX", "Рынок"].map((key) => (
setTimeframe(key)} style={{ flex: 1, textAlign: "center", padding: "7px 0", borderRadius: 9, fontSize: 12.5, fontWeight: 700, cursor: "pointer", background: timeframe === key ? "var(--accent-soft)" : "transparent", color: timeframe === key ? "var(--accent)" : "var(--text-3)", }} > {key}
))}
Рынок Rapira {window.MBEX.fmtRub2(rates.marketSell || 0)} ₽
Курс ЦБ РФ {window.MBEX.fmtRub2(rates.cbr || 0)} ₽
); } function ReferralTeaser({ app, onClick }) { const referral = app.privateState.referral; const count = Number(referral?.refCount || 0); const balance = Number(referral?.refBalance || 0); const subtitle = count > 0 ? `${count} рефералов · ${window.MBEX.fmtUsdt(balance)} USDT доступно` : "Другу скидка на первую сделку, вам — бонус с комиссии"; return (
Реферальная программа
{subtitle}
); } function HomeQuickLinks({ app, nav }) { const items = [ { icon: , title: "Рефералка", sub: "Бонусы и ссылка", target: "referral", }, { icon: , title: "Офисы", sub: "Адреса и контакты", target: "offices", }, { icon: , title: "Поддержка", sub: "Связаться с нами", target: "support", }, { icon: , title: "AML / KYC", sub: "Правила проверки", target: "aml", }, ]; return (
{items.map((item) => (
nav(item.target)} > {item.icon} {item.title} {item.sub}
))}
); } function HomeScreen({ dir, setDir, payInput, setPayInput, app, onCreate, onOpenOrder, nav }) { const active = app.activeOrders[0] || null; return (
{!active ? : null} {app.authState.lastError ? (
{app.authState.lastError}
) : null} {active ? (
Активная заявка
onOpenOrder(active.id)} />
) : null} {active ? : null}
Сервисы
Курс и комиссия фиксируются в заявке по текущим правилам MBEX.
); } window.HomeScreen = HomeScreen;