38451-vm/includes/terminal_layout.php
2026-02-18 06:21:48 +00:00

1055 lines
61 KiB
PHP

<?php
function renderTerminal($activeTab = 'spot') {
global $lang, $user;
$currentSymbol = $_GET['symbol'] ?? 'BTC';
$currentSymbol = strtoupper($currentSymbol);
$usdt_balance = 0;
if ($user) {
$stmt = db()->prepare("SELECT available FROM user_balances WHERE user_id = ? AND symbol = 'USDT'");
$stmt->execute([$user['id']]);
$bal = $stmt->fetch();
$usdt_balance = $bal['available'] ?? 0;
}
$full_coins = [
['symbol' => 'BTC', 'name' => 'Bitcoin', 'price' => '64,234.50', 'change' => '+2.45%'],
['symbol' => 'ETH', 'name' => 'Ethereum', 'price' => '3,456.20', 'change' => '+1.12%'],
['symbol' => 'USDT', 'name' => 'Tether', 'price' => '1.00', 'change' => '+0.01%'],
['symbol' => 'BNB', 'name' => 'BNB', 'price' => '598.40', 'change' => '-0.56%'],
['symbol' => 'SOL', 'name' => 'Solana', 'price' => '145.20', 'change' => '+5.67%'],
['symbol' => 'XRP', 'name' => 'Ripple', 'price' => '0.62', 'change' => '-1.23%'],
['symbol' => 'ADA', 'name' => 'Cardano', 'price' => '0.58', 'change' => '+0.89%'],
['symbol' => 'DOGE', 'name' => 'Dogecoin', 'price' => '0.16', 'change' => '+12.4%'],
['symbol' => 'DOT', 'name' => 'Polkadot', 'price' => '8.45', 'change' => '-2.11%'],
['symbol' => 'MATIC', 'name' => 'Polygon', 'price' => '0.92', 'change' => '+1.56%'],
['symbol' => 'LINK', 'name' => 'Chainlink', 'price' => '18.40', 'change' => '+3.22%'],
['symbol' => 'AVAX', 'name' => 'Avalanche', 'price' => '45.20', 'change' => '+4.12%'],
['symbol' => 'SHIB', 'name' => 'Shiba Inu', 'price' => '0.000027', 'change' => '-3.45%'],
['symbol' => 'TRX', 'name' => 'TRON', 'price' => '0.12', 'change' => '+0.56%'],
['symbol' => 'BCH', 'name' => 'Bitcoin Cash', 'price' => '456.20', 'change' => '+2.12%'],
['symbol' => 'LTC', 'name' => 'Litecoin', 'price' => '84.50', 'change' => '+1.45%'],
['symbol' => 'UNI', 'name' => 'Uniswap', 'price' => '7.20', 'change' => '-2.12%'],
];
?>
<link rel="stylesheet" href="/assets/css/terminal.css?v=<?= time() ?>">
<div class="terminal-sidebar-overlay" id="sidebar-overlay" onclick="toggleMobileSidebar()"></div>
<div class="terminal-container">
<!-- Mobile Top Switcher - Hidden on desktop to avoid duplicate fonts/text -->
<div class="mobile-type-switcher d-md-none">
<a href="/binary.php?symbol=<?= $currentSymbol ?>" class="<?= $activeTab === 'binary' ? 'active' : '' ?>"><?= __('sec_contract') ?></a>
<a href="/trade.php?symbol=<?= $currentSymbol ?>" class="<?= $activeTab === 'spot' ? 'active' : '' ?>"><?= __('spot') ?></a>
<a href="/contract.php?symbol=<?= $currentSymbol ?>" class="<?= $activeTab === 'contract' ? 'active' : '' ?>"><?= __('contract') ?></a>
</div>
<div class="terminal-main">
<!-- Left Sidebar -->
<div class="terminal-sidebar" id="terminal-sidebar">
<!-- Top Nav Tabs - Redesigned for beauty and clarity -->
<div class="terminal-top-nav sidebar-tabs">
<a href="/trade.php?symbol=<?= $currentSymbol ?>" class="terminal-tab <?= $activeTab === 'spot' ? 'active' : '' ?>">
<i class="bi bi-currency-exchange"></i>
<span><?= __('trade_spot') ?></span>
</a>
<a href="/contract.php?symbol=<?= $currentSymbol ?>" class="terminal-tab <?= $activeTab === 'contract' ? 'active' : '' ?>">
<i class="bi bi-layers-fill"></i>
<span><?= __('trade_contract') ?></span>
</a>
<a href="/binary.php?symbol=<?= $currentSymbol ?>" class="terminal-tab <?= $activeTab === 'binary' ? 'active' : '' ?>">
<i class="bi bi-lightning-charge-fill"></i>
<span><?= __('trade_binary') ?></span>
</a>
</div>
<div class="sidebar-search">
<input type="text" id="coin-search" placeholder="<?= __('search_coin') ?? 'Search Coin' ?>">
</div>
<div class="coin-list-container">
<div class="coin-list" id="coin-list">
<?php foreach ($full_coins as $coin): ?>
<div class="coin-row" data-symbol="<?= $coin['symbol'] ?>" onclick="location.href='?symbol=<?= $coin['symbol'] ?>'">
<div class="d-flex align-items-center">
<div class="bg-light p-1 rounded-circle me-2" style="width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">
<img src="<?php echo getCoinIcon($coin['symbol']); ?>" onerror="this.src='/assets/images/coin-placeholder.png'" alt="<?= $coin['symbol'] ?>" style="width: 18px; height: 18px; margin: 0;">
</div>
<div>
<div class="symbol fw-bold text-white" style="font-size: 14px;"><?= $coin['symbol'] ?></div>
<div class="change <?= strpos($coin['change'], '+') !== false ? 'text-success' : 'text-danger' ?>" style="font-size: 11px;"><?= $coin['change'] ?></div>
</div>
</div>
<div class="price fw-bold text-white" style="font-size: 14px;"><?= $coin['price'] ?></div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<!-- Middle Content: Chart & Trading -->
<div class="terminal-content">
<div class="terminal-header d-flex justify-content-between align-items-center px-3 py-2 border-bottom border-secondary">
<div class="d-flex align-items-center gap-3">
<div class="d-flex align-items-center" onclick="toggleMobileSidebar()" style="cursor: pointer;">
<i class="bi bi-list d-md-none fs-3 text-white me-2"></i>
<div class="fs-4 fw-bold text-white"><?= $currentSymbol ?>/USDT</div>
</div>
<div class="price-jump fs-4 fw-bold text-success">--</div>
</div>
<div class="d-flex gap-4 header-stats d-none d-md-flex">
<div class="header-stat">
<label class="d-block small text-muted"><?= __('change_24h') ?></label>
<span class="text-success fw-bold">--</span>
</div>
<div class="header-stat">
<label class="d-block small text-muted"><?= __('high') ?></label>
<span class="text-white fw-bold">--</span>
</div>
<div class="header-stat">
<label class="d-block small text-muted"><?= __('low') ?></label>
<span class="text-white fw-bold">--</span>
</div>
<div class="header-stat">
<label class="d-block small text-muted"><?= __('vol_24h') ?></label>
<span class="text-white fw-bold">--</span>
</div>
</div>
</div>
<div class="terminal-chart" style="height: 500px; background: #0b0e11;">
<!-- TradingView Widget BEGIN -->
<div class="tradingview-widget-container" style="height:100%;width:100%">
<div id="tradingview_chart" style="height:100%;width:100%"></div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script type="text/javascript">
new TradingView.widget(
{
"autosize": true,
"symbol": "BINANCE:<?= $currentSymbol ?>USDT",
"interval": "1",
"timezone": "Etc/UTC",
"theme": "dark",
"style": "1",
"locale": "en",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"hide_side_toolbar": false,
"allow_symbol_change": true,
"container_id": "tradingview_chart"
}
);
</script>
</div>
<!-- TradingView Widget END -->
</div>
<div class="trading-panels">
<?php if ($activeTab === 'binary'): ?>
<div class="binary-order-panel w-100">
<div class="d-flex justify-content-between align-items-end mb-2">
<div class="section-title text-white"><?= __('cycle_settlement') ?? 'Cycle / Settlement' ?></div>
<div class="small text-white"><?= __('available_balance') ?>: <span class="text-white fw-bold balance-highlight" id="user-usdt-balance"><?= number_format($usdt_balance, 2) ?></span> USDT</div>
</div>
<div class="cycle-grid">
<button class="cycle-btn active" onclick="selectCycle(this, 60, 8, 100, 4999)">
<span class="cycle-time">60<?= __('unit_seconds') ?></span>
<span class="cycle-profit">8%</span>
</button>
<button class="cycle-btn" onclick="selectCycle(this, 90, 12, 5000, 29999)">
<span class="cycle-time">90<?= __('unit_seconds') ?></span>
<span class="cycle-profit">12%</span>
</button>
<button class="cycle-btn" onclick="selectCycle(this, 120, 15, 30000, 99999)">
<span class="cycle-time">120<?= __('unit_seconds') ?></span>
<span class="cycle-profit">15%</span>
</button>
<button class="cycle-btn" onclick="selectCycle(this, 180, 20, 100000, 299999)">
<span class="cycle-time">180<?= __('unit_seconds') ?></span>
<span class="cycle-profit">20%</span>
</button>
<button class="cycle-btn" onclick="selectCycle(this, 300, 30, 300000, 999999999)">
<span class="cycle-time">300<?= __('unit_seconds') ?></span>
<span class="cycle-profit">30%</span>
</button>
</div>
<div class="section-title text-white mb-2"><?= __('purchase_amount') ?? 'Purchase Amount' ?> (USDT)</div>
<div class="amount-input-wrapper mb-3">
<input type="number" id="binary-amount" class="form-control" placeholder="100-4999" oninput="calculateProfit()">
</div>
<div class="quick-amounts d-flex gap-2 mb-3">
<?php foreach([100, 500, 1000, 5000, 10000, 50000] as $amt): ?>
<button class="btn btn-dark btn-sm flex-fill py-2" onclick="setBinaryAmount(<?= $amt ?>)"><?= $amt ?></button>
<?php endforeach; ?>
</div>
<div class="d-flex justify-content-between mb-3 small align-items-center bg-black bg-opacity-25 p-2 rounded">
<div class="text-white"><?= __('expected_profit') ?? 'Expected Profit' ?></div>
<div class="text-success fw-bold fs-6 profit-highlight" id="profit-display">0.00 USDT</div>
</div>
<div class="row g-2">
<div class="col-6">
<button onclick="placeOrder('up')" class="btn btn-success btn-buy-sell w-100 fw-bold d-flex flex-column align-items-center justify-content-center">
<div class="fs-5"><i class="bi bi-graph-up-arrow me-1"></i><?= __('buy_up') ?></div>
<div class="small opacity-75 fw-normal profit-rate-hint"><?= __('profit') ?> 8%</div>
</button>
</div>
<div class="col-6">
<button onclick="placeOrder('down')" class="btn btn-danger btn-buy-sell w-100 fw-bold d-flex flex-column align-items-center justify-content-center">
<div class="fs-5"><i class="bi bi-graph-down-arrow me-1"></i><?= __('buy_down') ?></div>
<div class="small opacity-75 fw-normal profit-rate-hint"><?= __('profit') ?> 8%</div>
</button>
</div>
</div>
</div>
<script>
let currentProfitRate = 8;
let currentSeconds = 60;
let minAmount = 100;
let maxAmount = 4999;
function selectCycle(btn, seconds, profit, min, max) {
document.querySelectorAll('.cycle-btn').forEach(el => el.classList.remove('active'));
btn.classList.add('active');
currentProfitRate = profit;
currentSeconds = seconds;
minAmount = min;
maxAmount = max;
const hint = max >= 999999999 ? min + '+' : min + '-' + max;
document.getElementById('binary-amount').placeholder = hint;
document.querySelectorAll('.profit-rate-hint').forEach(el => {
el.innerText = '<?= __("profit") ?> ' + profit + '%';
});
calculateProfit();
}
function setBinaryAmount(amt) {
document.getElementById('binary-amount').value = amt;
calculateProfit();
}
function calculateProfit() {
const amount = parseFloat(document.getElementById('binary-amount').value) || 0;
const profit = (amount * currentProfitRate / 100).toFixed(2);
document.getElementById('profit-display').innerText = profit + ' USDT';
}
function placeOrder(direction) {
const amount = parseFloat(document.getElementById('binary-amount').value);
const balance = parseFloat(document.getElementById('user-usdt-balance').innerText.replace(',', ''));
if (!amount || amount <= 0) {
showErrorModal('<?= __("enter_amount") ?>');
return;
}
if (amount < minAmount || amount > maxAmount) {
let msg = '<?= __("amount_limit_error") ?>';
msg = msg.replace('%min%', minAmount).replace('%max%', maxAmount);
showErrorModal(msg);
return;
}
if (amount > balance) {
showErrorModal('<?= __("insufficient_balance") ?? "Insufficient balance" ?>');
return;
}
const openPrice = parseFloat(document.querySelector('.price-jump').innerText.replace(/,/g, ''));
const formData = new FormData();
formData.append('action', 'place_order');
formData.append('symbol', '<?= $currentSymbol ?>');
formData.append('amount', amount);
formData.append('duration', currentSeconds);
formData.append('direction', direction);
formData.append('entry_price', openPrice);
formData.append('profit_rate', currentProfitRate);
fetch('/api/binary.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
const order = {
id: data.order_id,
time: new Date().toISOString().replace('T', ' ').substr(0, 19),
pair: '<?= $currentSymbol ?>/USDT',
type: 'Binary',
side: direction === 'up' ? '<?= __("buy_up") ?>' : '<?= __("buy_down") ?>',
side_type: direction,
price: openPrice,
amount: amount.toFixed(2),
total: '---',
status: 'Executing',
secondsLeft: currentSeconds,
totalSeconds: currentSeconds,
profitRate: currentProfitRate
};
historyData.open.unshift(order);
showHistoryTab('open');
showOrderPopup(order);
// Update balance display
document.getElementById('user-usdt-balance').innerText = (balance - amount).toLocaleString('en-US', {minimumFractionDigits: 2});
// Start timer
const timer = setInterval(() => {
order.secondsLeft--;
updateOrderPopup(order);
if (showHistoryTab.currentTab === 'open') showHistoryTab('open');
if (order.secondsLeft <= 0) {
clearInterval(timer);
settleOrderBackend(order);
}
}, 1000);
} else {
showErrorModal(data.error || 'Order failed');
}
});
}
function settleOrderBackend(order) {
const closePrice = parseFloat(document.querySelector('.price-jump').innerText.replace(/,/g, ''));
const formData = new FormData();
formData.append('action', 'settle_order');
formData.append('order_id', order.id);
formData.append('close_price', closePrice);
fetch('/api/binary.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
order.status = data.result === 'won' ? 'Profit' : 'Loss';
const amount = parseFloat(order.amount);
const profit = amount * order.profitRate / 100;
order.total = data.result === 'won' ? (amount + profit).toFixed(2) : '0.00';
order.close_price = closePrice;
historyData.open = historyData.open.filter(o => o.id !== order.id);
historyData.settlement.unshift(order);
showHistoryTab(showHistoryTab.currentTab);
if (data.result === 'won') {
const balance = parseFloat(document.getElementById('user-usdt-balance').innerText.replace(',', ''));
document.getElementById('user-usdt-balance').innerText = (balance + amount + profit).toLocaleString('en-US', {minimumFractionDigits: 2});
}
showOrderResult(data.result, data.pnl);
}
});
}
function showOrderResult(result, pnl) {
const popup = document.querySelector('.order-popup');
const details = document.querySelector('.popup-details');
const countdown = document.querySelector('.countdown-circle');
const footer = document.querySelector('.popup-footer');
countdown.style.display = 'none';
details.style.display = 'none';
footer.style.display = 'none';
const resultDiv = document.createElement('div');
resultDiv.className = 'order-result-display animate__animated animate__zoomIn';
resultDiv.innerHTML = `
<div class="result-icon mb-3">
<i class="bi bi-${result === 'won' ? 'check-circle-fill text-success' : 'x-circle-fill text-danger'}" style="font-size: 60px;"></i>
</div>
<h3 class="fw-bold text-white mb-2">${result === 'won' ? '<?= __("profit") ?>' : '<?= __("loss") ?>'}</h3>
<div class="fs-4 fw-bold ${result === 'won' ? 'text-success' : 'text-danger'} mb-4">
${result === 'won' ? '+' : ''}${parseFloat(pnl).toFixed(2)} USDT
</div>
<button class="btn btn-primary w-100 rounded-pill py-2 fw-bold" onclick="hideOrderPopup()"><?= __("confirm") ?></button>
`;
popup.appendChild(resultDiv);
}
function showErrorModal(msg) {
const modal = document.getElementById('error-modal-overlay');
document.getElementById('error-modal-msg').innerText = msg;
modal.style.display = 'flex';
setTimeout(() => modal.classList.add('show'), 10);
}
function closeErrorModal() {
const modal = document.getElementById('error-modal-overlay');
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 300);
}
function showMsg(msg, type = 'info') {
const alertBox = document.createElement('div');
alertBox.className = `custom-alert ${type}`;
alertBox.innerHTML = `
<div class="alert-content">
<i class="bi bi-${type === 'error' ? 'exclamation-circle-fill' : (type === 'warning' ? 'exclamation-triangle-fill' : 'info-circle-fill')}"></i>
<span>${msg}</span>
</div>
`;
document.body.appendChild(alertBox);
setTimeout(() => alertBox.classList.add('show'), 10);
setTimeout(() => {
alertBox.classList.remove('show');
setTimeout(() => alertBox.remove(), 300);
}, 3000);
}
function showOrderPopup(order) {
const popup = document.getElementById('order-popup-overlay');
// Reset popup content
document.querySelector('.countdown-circle').style.display = 'block';
document.querySelector('.popup-details').style.display = 'block';
document.querySelector('.popup-footer').style.display = 'block';
const oldResult = document.querySelector('.order-result-display');
if (oldResult) oldResult.remove();
const sideColor = order.side.includes('Up') || order.side.includes('涨') ? '#26a69a' : '#ef5350';
document.getElementById('popup-price').innerText = order.price;
document.getElementById('popup-cycle').innerText = order.totalSeconds + '<?= __('unit_seconds') ?>';
document.getElementById('popup-direction').innerText = order.side;
document.getElementById('popup-direction').style.color = sideColor;
document.getElementById('popup-quantity').innerText = order.amount + ' USDT';
document.getElementById('popup-open-price').innerText = order.price;
popup.style.display = 'flex';
updateOrderPopup(order);
}
function updateOrderPopup(order) {
const timeText = document.getElementById('popup-time-text');
const progress = document.getElementById('popup-progress');
const currentPrice = document.getElementById('popup-price');
timeText.innerText = order.secondsLeft + '<?= __('unit_seconds') ?>';
// Update current price in popup
currentPrice.innerText = document.querySelector('.price-jump').innerText;
const radius = 54; // Match SVG radius
const circumference = 2 * Math.PI * radius;
const offset = circumference - (order.secondsLeft / order.totalSeconds) * circumference;
progress.style.strokeDasharray = `${circumference} ${circumference}`;
progress.style.strokeDashoffset = offset;
}
function hideOrderPopup() {
document.getElementById('order-popup-overlay').style.display = 'none';
}
</script>
<?php else: ?>
<div class="order-form-container w-100">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="order-form-tabs m-0 d-flex gap-2">
<button class="active btn btn-sm btn-outline-primary py-1 px-3 fw-bold" style="font-size: 12px; border-radius: 6px;"><?= __('limit') ?? 'Limit' ?></button>
<button class="btn btn-sm btn-outline-secondary py-1 px-3 fw-bold" style="font-size: 12px; border-radius: 6px;"><?= __('market') ?? 'Market' ?></button>
</div>
<div class="small text-muted fw-medium"><?= __('assets') ?>: <span class="text-white fw-bold"><span id="spot-usdt-balance"><?= number_format($usdt_balance, 2) ?></span> USDT</span></div>
</div>
<?php if ($activeTab === 'contract'): ?>
<div class="mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('leverage') ?? 'Leverage' ?></label>
<select id="contract-leverage" class="form-select form-select-sm bg-dark border-secondary text-white fw-bold">
<?php foreach([10, 20, 50, 100, 150, 200] as $lev): ?>
<option value="<?= $lev ?>"><?= $lev ?>x</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="row g-3">
<div class="col-6">
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= ($activeTab === 'contract' ? (__('buy_long') ?? 'Buy/Long') : (__('buy_price') ?? 'Buy Price')) ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" id="spot-buy-price" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold">USDT</span>
</div>
</div>
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('amount') ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" id="spot-buy-amount" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold"><?= $currentSymbol ?></span>
</div>
</div>
<div class="d-flex gap-1 mb-3">
<?php foreach(['25%','50%','75%','100%'] as $p): ?>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;" onclick="setSpotPercent('buy', <?= intval($p) ?>)"><?= $p ?></button>
<?php endforeach; ?>
</div>
<button onclick="<?= ($activeTab === 'contract' ? "placeContractOrder('long')" : "placeSpotOrder('buy')") ?>" class="btn btn-success w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #0ecb81, #26a69a);"><?= ($activeTab === 'contract' ? (__('long') ?? 'Long') : (__('buy'))) ?> <?= $currentSymbol ?></button>
</div>
<div class="col-6">
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= ($activeTab === 'contract' ? (__('sell_short') ?? 'Sell/Short') : (__('sell_price') ?? 'Sell Price')) ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" id="spot-sell-price" value="64234.50" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold">USDT</span>
</div>
</div>
<div class="input-group-custom mb-3">
<label class="small text-muted mb-1 d-block fw-bold"><?= __('amount') ?></label>
<div class="input-wrapper bg-black p-2 rounded border border-secondary d-flex justify-content-between align-items-center" style="background: #0b0e11 !important; height: 45px;">
<input type="number" id="spot-sell-amount" placeholder="0.00" class="bg-transparent border-0 text-white w-75 fw-bold" style="outline: none; font-size: 14px;">
<span class="suffix text-muted small fw-bold"><?= $currentSymbol ?></span>
</div>
</div>
<div class="d-flex gap-1 mb-3">
<?php foreach(['25%','50%','75%','100%'] as $p): ?>
<button class="btn btn-dark btn-sm py-1 flex-fill fw-bold" style="font-size: 10px; background: #2b3139;" onclick="setSpotPercent('sell', <?= intval($p) ?>)"><?= $p ?></button>
<?php endforeach; ?>
</div>
<button onclick="<?= ($activeTab === 'contract' ? "placeContractOrder('short')" : "placeSpotOrder('sell')") ?>" class="btn btn-danger w-100 py-3 fw-bold rounded-3 shadow-sm border-0" style="background: linear-gradient(135deg, #f6465d, #ef5350);"><?= ($activeTab === 'contract' ? (__('short') ?? 'Short') : (__('sell'))) ?> <?= $currentSymbol ?></button>
</div>
</div>
</div>
<?php endif; ?>
</div>
<script>
function setSpotPercent(side, percent) {
const balance = side === 'buy' ? <?= $usdt_balance ?> : 0; // Simplified
// In a real app we'd need the coin balance for sell
if (side === 'buy') {
const price = parseFloat(document.getElementById('spot-buy-price').value);
if (price > 0) {
document.getElementById('spot-buy-amount').value = (balance * percent / 100 / price).toFixed(6);
}
}
}
function placeSpotOrder(side) {
const price = parseFloat(document.getElementById('spot-' + side + '-price').value);
const amount = parseFloat(document.getElementById('spot-' + side + '-amount').value);
if (!amount || amount <= 0) {
showMsg('<?= __("enter_amount") ?>', 'error');
return;
}
const formData = new FormData();
formData.append('symbol', '<?= $currentSymbol ?>');
formData.append('side', side);
formData.append('price', price);
formData.append('amount', amount);
formData.append('type', 'limit');
fetch('/api/spot.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Order placed successfully', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showMsg(data.error, 'error');
}
});
}
function placeContractOrder(direction) {
const price = parseFloat(document.getElementById('spot-' + (direction === 'long' ? 'buy' : 'sell') + '-price').value);
const amount = parseFloat(document.getElementById('spot-' + (direction === 'long' ? 'buy' : 'sell') + '-amount').value);
const leverage = parseInt(document.getElementById('contract-leverage').value);
if (!amount || amount <= 0) {
showMsg('<?= __("enter_amount") ?>', 'error');
return;
}
const formData = new FormData();
formData.append('action', 'place_order');
formData.append('symbol', '<?= $currentSymbol ?>');
formData.append('direction', direction);
formData.append('leverage', leverage);
formData.append('amount', amount);
formData.append('entry_price', price);
formData.append('type', 'market');
fetch('/api/contract.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Contract order opened', 'success');
setTimeout(() => location.reload(), 1000);
} else {
showMsg(data.error, 'error');
}
});
}
// Search functionality
document.getElementById('coin-search').addEventListener('input', function(e) {
const term = e.target.value.toUpperCase();
document.querySelectorAll('.coin-row').forEach(row => {
const symbol = row.getAttribute('data-symbol') || '';
row.style.display = symbol.includes(term) ? 'flex' : 'none';
});
});
let tickerWs, depthWs;
const currentBase = '<?= $currentSymbol ?>';
const currentPair = currentBase.toLowerCase() + 'usdt';
function formatPrice(p) {
if (p < 0.0001) return p.toFixed(8);
if (p < 1) return p.toFixed(6);
if (p < 100) return p.toFixed(4);
return p.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
}
function formatAmount(a) {
if (a >= 1000) return a.toLocaleString('en-US', {maximumFractionDigits: 2});
if (a >= 1) return a.toFixed(4);
return a.toFixed(6);
}
function updateOrderBookUI(data) {
const asks = data.asks || data.a || []; // Asks (sells)
const bids = data.bids || data.b || []; // Bids (buys)
const askContainer = document.getElementById('ob-asks');
const bidContainer = document.getElementById('ob-bids');
if (!askContainer || !bidContainer) return;
// Calculate total volume for relative bars (better visualization)
const sortedAsks = asks.slice(0, 20).reverse();
const sortedBids = bids.slice(0, 20);
// Get max volume in the current view to scale the bars correctly
const viewAsks = sortedAsks.map(a => parseFloat(a[1]));
const viewBids = sortedBids.map(b => parseFloat(b[1]));
const maxQty = Math.max(...viewAsks, ...viewBids, 0.01);
const askRows = askContainer.querySelectorAll('.ob-row');
askRows.forEach((row, i) => {
const item = sortedAsks[i];
if (item) {
const price = parseFloat(item[0]);
const amount = parseFloat(item[1]);
row.querySelector('.price').innerText = formatPrice(price);
row.querySelector('.amount').innerText = formatAmount(amount);
row.querySelector('.ob-row-bg').style.width = (amount / maxQty * 100) + '%';
row.style.opacity = '1';
} else {
row.querySelector('.price').innerText = '---';
row.querySelector('.amount').innerText = '---';
row.querySelector('.ob-row-bg').style.width = '0%';
}
});
const bidRows = bidContainer.querySelectorAll('.ob-row');
bidRows.forEach((row, i) => {
const item = sortedBids[i];
if (item) {
const price = parseFloat(item[0]);
const amount = parseFloat(item[1]);
row.querySelector('.price').innerText = formatPrice(price);
row.querySelector('.amount').innerText = formatAmount(amount);
row.querySelector('.ob-row-bg').style.width = (amount / maxQty * 100) + '%';
row.style.opacity = '1';
} else {
row.querySelector('.price').innerText = '---';
row.querySelector('.amount').innerText = '---';
row.querySelector('.ob-row-bg').style.width = '0%';
}
});
}
// Add fallback data simulation for Order Book only if WebSocket is not active
function simulateOrderBook() {
if (window.depthActive) return;
const midPrice = parseFloat(document.querySelector('.price-jump')?.innerText.replace(/,/g, '')) || 64000;
const data = {
a: Array.from({length: 20}, (_, i) => [(midPrice + (i + 1) * (midPrice * 0.0001)).toString(), (Math.random() * 2).toString()]),
b: Array.from({length: 20}, (_, i) => [(midPrice - (i + 1) * (midPrice * 0.0001)).toString(), (Math.random() * 2).toString()])
};
updateOrderBookUI(data);
}
setInterval(simulateOrderBook, 2000);
async function populateAllCoins() {
try {
const response = await fetch('https://api.binance.com/api/v3/ticker/24hr');
const data = await response.json();
const list = document.getElementById('coin-list');
const existingSymbols = Array.from(document.querySelectorAll('.coin-row')).map(r => r.getAttribute('data-symbol'));
data.forEach(item => {
if (item.symbol.endsWith('USDT')) {
const symbol = item.symbol.replace('USDT', '');
if (!existingSymbols.includes(symbol)) {
const price = parseFloat(item.lastPrice);
const change = parseFloat(item.priceChangePercent);
const row = document.createElement('div');
row.className = 'coin-row';
row.setAttribute('data-symbol', symbol);
row.onclick = () => {
location.href = '?symbol=' + symbol;
if(window.innerWidth <= 768) toggleMobileSidebar();
};
row.innerHTML = `
<div class="d-flex align-items-center">
<div class="bg-light p-1 rounded-circle me-2" style="width: 24px; height: 24px; display: flex; align-items: center; justify-content: center;">
<img src="${getCoinIconJs(symbol)}" onerror="this.src='/assets/images/coin-placeholder.png'" alt="${symbol}" style="width: 18px; height: 18px; margin: 0;">
</div>
<div>
<div class="symbol fw-bold text-white">${symbol}</div>
<div class="change ${change >= 0 ? 'text-success' : 'text-danger'}" style="font-size: 10px;">${(change >= 0 ? '+' : '') + change.toFixed(2)}%</div>
</div>
</div>
<div class="price fw-bold text-white">${formatPrice(price)}</div>
`;
list.appendChild(row);
}
}
});
} catch (e) {
console.error("Failed to populate coins", e);
}
}
function initTradingWS() {
// Symbol specific ticker for header and last price
tickerWs = new WebSocket(`wss://stream.binance.com:9443/ws/${currentPair}@ticker`);
tickerWs.onmessage = (e) => {
const data = JSON.parse(e.data);
const price = parseFloat(data.c);
const change = parseFloat(data.P);
const high = parseFloat(data.h);
const low = parseFloat(data.l);
const vol = parseFloat(data.v);
const priceEl = document.querySelector('.price-jump');
if (priceEl) {
const oldPrice = parseFloat(priceEl.innerText.replace(/,/g, ''));
priceEl.innerText = formatPrice(price);
priceEl.style.color = price >= oldPrice ? '#0ecb81' : '#f6465d';
// Update mid price in OB
const obPrice = document.getElementById('last-price-ob');
if (obPrice) {
obPrice.innerText = formatPrice(price);
obPrice.className = 'val ' + (price >= oldPrice ? 'text-success' : 'text-danger');
}
const obUsd = document.getElementById('last-price-usd');
if (obUsd) obUsd.innerText = '≈ $' + formatPrice(price);
}
const stats = document.querySelectorAll('.header-stat span');
if (stats[0]) {
stats[0].innerText = (change >= 0 ? '+' : '') + change.toFixed(2) + '%';
stats[0].className = (change >= 0 ? 'text-success' : 'text-danger') + ' fw-bold';
}
if (stats[1]) stats[1].innerText = formatPrice(high);
if (stats[2]) stats[2].innerText = formatPrice(low);
if (stats[3]) stats[3].innerText = parseFloat(vol).toLocaleString('en-US', {maximumFractionDigits: 2});
};
// Depth for Order Book
depthWs = new WebSocket(`wss://stream.binance.com:9443/ws/${currentPair}@depth20@1000ms`);
depthWs.onmessage = (e) => {
window.depthActive = true;
const data = JSON.parse(e.data);
updateOrderBookUI(data);
};
// Global ticker for sidebar
const allTickerWs = new WebSocket('wss://stream.binance.com:9443/ws/!ticker@arr');
allTickerWs.onmessage = (e) => {
const data = JSON.parse(e.data);
const sidebarRows = document.querySelectorAll('.coin-row');
const tickerMap = {};
data.forEach(item => {
if (item.s.endsWith('USDT')) {
tickerMap[item.s.replace('USDT', '')] = item;
}
});
sidebarRows.forEach(row => {
const sym = row.getAttribute('data-symbol');
if (tickerMap[sym]) {
const t = tickerMap[sym];
const price = parseFloat(t.c);
const change = parseFloat(t.P);
row.querySelector('.price').innerText = formatPrice(price);
const changeEl = row.querySelector('.change');
changeEl.innerText = (change >= 0 ? '+' : '') + change.toFixed(2) + '%';
changeEl.className = 'change ' + (change >= 0 ? 'text-success' : 'text-danger');
}
});
};
}
populateAllCoins().then(() => {
initTradingWS();
loadHistory();
});
</script>
<div class="order-history p-0">
<div class="d-flex gap-0 border-bottom border-secondary bg-dark history-tabs">
<h6 class="px-4 py-3 mb-0 active text-white bg-black" onclick="showHistoryTab('open')" id="tab-open" style="cursor: pointer; font-size: 13px; font-weight: bold; border-bottom: 2px solid var(--term-primary) !important;"><?= __('open_orders') ?></h6>
<h6 class="px-4 py-3 mb-0 text-white opacity-75" onclick="showHistoryTab('settlement')" id="tab-settlement" style="cursor: pointer; font-size: 13px;"><?= __('settlement_history') ?></h6>
</div>
<div class="table-responsive">
<table class="table table-dark table-sm mb-0" id="history-table" style="font-size: 12px;">
<thead>
<tr class="text-muted" style="background: rgba(255,255,255,0.02);">
<th class="ps-3 py-2"><?= __('time') ?></th>
<th class="py-2"><?= __('trading_pair') ?? 'Pair' ?></th>
<th class="py-2"><?= __('type') ?></th>
<th class="py-2"><?= __('direction') ?></th>
<th class="py-2"><?= __('price') ?></th>
<th class="py-2"><?= __('quantity') ?></th>
<th class="py-2"><?= __('total') ?? 'Total' ?></th>
<th class="py-2"><?= __('status') ?></th>
<th class="pe-3 py-2 text-end"><?= __('operation') ?></th>
</tr>
</thead>
<tbody id="history-body">
<!-- Data injected by JS -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Right Sidebar: Order Book -->
<div class="terminal-right-sidebar">
<div class="order-book">
<div class="ob-header">
<span><?= __('price') ?>(USDT)</span>
<span><?= __('quantity') ?>(<?= $currentSymbol ?>)</span>
</div>
<div class="ob-list asks" id="ob-asks">
<?php for($i=0;$i<20;$i++): ?>
<div class="ob-row">
<span class="price">---</span>
<span class="amount">---</span>
<div class="ob-row-bg" style="width: 0%"></div>
</div>
<?php endfor; ?>
</div>
<div class="ob-mid-price">
<span class="val text-success" id="last-price-ob">---</span>
<span class="small text-muted" id="last-price-usd">---</span>
</div>
<div class="ob-list bids" id="ob-bids">
<?php for($i=0;$i<20;$i++): ?>
<div class="ob-row">
<span class="price">---</span>
<span class="amount">---</span>
<div class="ob-row-bg" style="width: 0%"></div>
</div>
<?php endfor; ?>
</div>
</div>
</div>
</div>
</div>
<script>
function toggleMobileSidebar() {
document.getElementById('terminal-sidebar').classList.toggle('active');
document.getElementById('sidebar-overlay').classList.toggle('active');
}
const historyData = {
open: [],
settlement: []
};
async function loadHistory() {
try {
const resp = await fetch('/api/finance.php?action=get_orders&symbol=<?= $currentSymbol ?>&tab=<?= $activeTab ?>');
const data = await resp.json();
if (data.success) {
historyData.open = data.open;
historyData.settlement = data.settlement;
showHistoryTab(showHistoryTab.currentTab || 'open');
}
} catch (e) {
console.error("Failed to load history", e);
}
}
function showHistoryTab(tab) {
showHistoryTab.currentTab = tab;
// Update tabs UI
document.querySelectorAll('.history-tabs h6').forEach(el => {
el.classList.remove('active', 'text-white', 'bg-black', 'opacity-100');
el.classList.add('text-white', 'opacity-75');
el.style.borderBottom = 'none';
});
const activeTab = document.getElementById('tab-' + tab);
if (activeTab) {
activeTab.classList.add('active', 'text-white', 'bg-black', 'opacity-100');
activeTab.classList.remove('opacity-75');
activeTab.style.borderBottom = '2px solid var(--term-primary)';
}
// Update table content
const body = document.getElementById('history-body');
body.innerHTML = '';
if (!historyData[tab] || historyData[tab].length === 0) {
body.innerHTML = '<tr><td colspan="9" class="text-center py-5 text-muted"><?= __("no_records_found") ?? "No records found" ?></td></tr>';
return;
}
historyData[tab].forEach(row => {
const tr = document.createElement('tr');
const isProfit = row.status === 'Profit';
const isLoss = row.status === 'Loss';
const isExecuting = row.status === 'Executing';
const statusClass = isProfit ? 'text-success' : (isLoss ? 'text-danger' : 'text-info');
const statusBg = isProfit ? 'bg-success' : (isLoss ? 'bg-danger' : 'bg-info');
let displayStatus = row.status;
if (isExecuting) displayStatus = '<?= __("executing") ?> (' + row.secondsLeft + '<?= __('unit_seconds') ?>)';
if (isProfit) displayStatus = '<?= __("profit") ?>';
if (isLoss) displayStatus = '<?= __("loss") ?? "Loss" ?>';
// Format total/profit for settled orders
let totalDisplay = row.total;
if (tab === 'settlement' && (isProfit || isLoss)) {
const pl = parseFloat(row.pnl || 0).toFixed(2);
const total = parseFloat(row.total || 0).toFixed(2);
const plClass = pl >= 0 ? 'text-success' : 'text-danger';
totalDisplay = `<div class="${plClass} fw-bold">${total}</div><div class="small opacity-75">${pl >= 0 ? '+' : ''}${pl}</div>`;
}
const isUp = row.side_type === 'up' || row.side.includes('Up') || row.side.includes('涨') || row.side === 'Buy' || row.side === 'Long';
let operationHtml = `<button class="btn btn-sm btn-dark px-2 py-0" style="font-size: 10px;"><?= __('details') ?></button>`;
if (tab === 'open' && row.type === 'Contract') {
operationHtml = `<button onclick="closeContractOrder(${row.id})" class="btn btn-sm btn-danger px-2 py-0" style="font-size: 10px;"><?= __('close') ?? 'Close' ?></button>`;
}
tr.innerHTML = `
<td class="ps-3 py-3 text-muted" style="font-size: 11px;">${row.time}</td>
<td class="py-3 fw-bold text-white">${row.pair}</td>
<td class="py-3"><span class="small bg-secondary bg-opacity-25 px-1 rounded">${row.type}</span></td>
<td class="py-3 ${isUp ? 'text-success' : 'text-danger'}">
<i class="bi bi-graph-${isUp ? 'up' : 'down'} me-1"></i>${row.side}
</td>
<td class="py-3">${row.price}</td>
<td class="py-3">${row.amount}</td>
<td class="py-3">${totalDisplay}</td>
<td class="py-3"><span class="badge ${statusBg} bg-opacity-10 ${statusClass} rounded-pill px-2" style="font-size: 10px;">${displayStatus}</span></td>
<td class="pe-3 py-3 text-end">${operationHtml}</td>
`;
body.appendChild(tr);
});
}
function closeContractOrder(id) {
if (!confirm('Confirm close position?')) return;
const closePrice = parseFloat(document.querySelector('.price-jump').innerText.replace(/,/g, ''));
const formData = new FormData();
formData.append('action', 'close_order');
formData.append('order_id', id);
formData.append('close_price', closePrice);
fetch('/api/contract.php', {
method: 'POST',
body: formData
})
.then(r => r.json())
.then(data => {
if (data.success) {
showMsg('Position closed', 'success');
loadHistory();
} else {
showMsg(data.error, 'error');
}
});
}
// Initialize with open orders
showHistoryTab('open');
</script>
<!-- Order Countdown Popup Modal -->
<div class="order-popup-overlay" id="order-popup-overlay">
<div class="order-popup">
<h5><?= __('order_in_progress') ?></h5>
<div class="countdown-circle">
<svg width="120" height="120">
<circle class="bg" cx="60" cy="60" r="54"></circle>
<circle class="progress" id="popup-progress" cx="60" cy="60" r="54"></circle>
</svg>
<div class="time-text" id="popup-time-text">60<?= __('unit_seconds') ?></div>
</div>
<div class="popup-details">
<div class="popup-row">
<span class="label"><?= __('current_price') ?></span>
<span class="value" id="popup-price">---</span>
</div>
<div class="popup-row">
<span class="label"><?= __('cycle') ?></span>
<span class="value" id="popup-cycle">60<?= __('unit_seconds') ?></span>
</div>
<div class="popup-row">
<span class="label"><?= __('direction') ?></span>
<span class="value" id="popup-direction">---</span>
</div>
<div class="popup-row">
<span class="label"><?= __('quantity') ?></span>
<span class="value" id="popup-quantity">---</span>
</div>
<div class="popup-row">
<span class="label"><?= __('opening_price') ?></span>
<span class="value" id="popup-open-price">---</span>
</div>
</div>
<div class="popup-footer">
<?= __('final_price_settlement') ?>
</div>
</div>
</div>
<!-- Error Modal Popup -->
<div class="error-modal-overlay" id="error-modal-overlay" onclick="closeErrorModal()">
<div class="error-modal" onclick="event.stopPropagation()">
<div class="error-modal-icon">
<i class="bi bi-exclamation-octagon-fill"></i>
</div>
<h4><?= __('warning') ?></h4>
<p id="error-modal-msg">Error message goes here</p>
<button class="btn btn-primary rounded-pill px-5 py-2 fw-bold" onclick="closeErrorModal()"><?= __('confirm') ?></button>
</div>
</div>
<?php
}
?>