PNG IHDR x sBIT|d pHYs + tEXtSoftware www.inkscape.org< ,tEXtComment
document.addEventListener('DOMContentLoaded', function () {
// --- Helper: XSS Sanitization ---
// Prevents malicious scripts stored in the database from executing in the browser
function escapeHTML(str) {
if (str === null || str === undefined) return '';
return String(str).replace(/[&<>'"]/g, match => {
const escape = { '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' };
return escape[match];
});
}
// --- Helper: Get CSRF Token ---
// Retrieves the CSRF token from the meta tag to secure POST requests
function getCsrfToken() {
const tokenElement = document.querySelector('meta[name="csrf-token"]');
return tokenElement ? tokenElement.getAttribute('content') : '';
}
// --- Mobile Sidebar Logic (for all pages) ---
const hamburger = document.getElementById('hamburger-menu');
const sidebar = document.getElementById('sidebar');
const closeMenuBtn = document.getElementById('close-menu-btn');
if (hamburger && sidebar && closeMenuBtn) {
hamburger.addEventListener('click', (event) => {
event.stopPropagation();
sidebar.classList.add('open');
});
closeMenuBtn.addEventListener('click', () => {
sidebar.classList.remove('open');
});
document.addEventListener('click', (event) => {
if (sidebar.classList.contains('open') && !sidebar.contains(event.target) && !hamburger.contains(event.target)) {
sidebar.classList.remove('open');
}
});
}
// --- Accordion Logic (for specific pages) ---
const accordionHeaders = document.querySelectorAll('.accordion-header');
if (accordionHeaders.length > 0) {
accordionHeaders.forEach(header => {
header.addEventListener('click', () => {
const accordionItem = header.parentElement;
if (accordionItem) {
accordionItem.classList.toggle('active');
}
});
});
}
// --- Balance Selector Dropdown Logic ---
const balanceSelectorBtn = document.getElementById('balance-selector-btn');
const balanceDropdown = document.getElementById('balance-dropdown-menu');
if (balanceSelectorBtn && balanceDropdown) {
balanceSelectorBtn.addEventListener('click', (event) => {
event.stopPropagation();
balanceDropdown.classList.toggle('open');
});
document.addEventListener('click', (event) => {
if (
balanceDropdown.classList.contains('open') &&
!balanceDropdown.contains(event.target) &&
!balanceSelectorBtn.contains(event.target)
) {
balanceDropdown.classList.remove('open');
}
});
}
// --- Notifications Panel Logic ---
const openNotificationsBtn = document.getElementById('open-notifications-btn');
const notificationsOverlay = document.getElementById('notifications-overlay');
const notificationsBackdrop = document.getElementById('notifications-backdrop');
const closeNotificationsBtns = document.querySelectorAll('.js-close-notifications');
if (openNotificationsBtn && notificationsOverlay && notificationsBackdrop && closeNotificationsBtns.length > 0) {
const openPanel = () => notificationsOverlay.classList.add('open');
const closePanel = () => notificationsOverlay.classList.remove('open');
openNotificationsBtn.addEventListener('click', openPanel);
closeNotificationsBtns.forEach(btn => btn.addEventListener('click', closePanel));
notificationsBackdrop.addEventListener('click', closePanel);
}
// --- User Menu / Account Panel Logic ---
const userMenuBtn = document.getElementById('user-menu-btn');
const userAccountPanel = document.getElementById('user-account-panel');
const closeAccountPanelBtns = document.querySelectorAll('.js-close-account-panel');
if (userMenuBtn && userAccountPanel) {
userMenuBtn.addEventListener('click', (event) => {
event.stopPropagation();
if (balanceDropdown && balanceDropdown.classList.contains('open')) {
balanceDropdown.classList.remove('open');
}
if (document.getElementById('language-dropdown-menu')?.classList.contains('open')) {
document.getElementById('language-dropdown-menu').classList.remove('open');
}
userAccountPanel.classList.toggle('open');
});
closeAccountPanelBtns.forEach(btn => {
btn.addEventListener('click', () => {
userAccountPanel.classList.remove('open');
});
});
document.addEventListener('click', (event) => {
if (
userAccountPanel.classList.contains('open') &&
!userAccountPanel.contains(event.target) &&
!userMenuBtn.contains(event.target)
) {
userAccountPanel.classList.remove('open');
}
});
}
// --- TradingView Widget ---
if (document.getElementById('tradingview_f24de')) {
new TradingView.widget({
"autosize": true,
"symbol": "CRYPTOCAP:BTC",
"interval": "D",
"timezone": "Etc/UTC",
"theme": "dark",
"style": "1",
"locale": "en",
"enable_publishing": false,
"hide_top_toolbar": true,
"withdateranges": true,
"backgroundColor": "rgba(5, 8, 11, 1)",
"gridColor": "rgba(255, 255, 255, 0.045)",
"container_id": "tradingview_f24de"
});
}
// --- Language Dropdown & GTranslate UI Logic ---
const langSelectorBtn = document.getElementById('language-selector-btn');
const langDropdownMenu = document.getElementById('language-dropdown-menu');
const langSearchInput = document.getElementById('language-search-input');
const langListItems = document.querySelectorAll('.language-list-item');
if (langSelectorBtn && langDropdownMenu) {
langSelectorBtn.addEventListener('click', (event) => {
event.stopPropagation();
langDropdownMenu.classList.toggle('open');
});
document.addEventListener('click', (event) => {
if (langDropdownMenu.classList.contains('open') && !langDropdownMenu.contains(event.target) && !langSelectorBtn.contains(event.target)) {
langDropdownMenu.classList.remove('open');
}
});
if (langSearchInput) {
langSearchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
langListItems.forEach(item => {
const languageName = item.textContent.toLowerCase();
if (languageName.includes(searchTerm)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
}
if (langListItems.length > 0) {
langListItems.forEach(item => {
item.addEventListener('click', function() {
const currentActive = document.querySelector('.language-list-item.active');
if (currentActive) {
currentActive.classList.remove('active');
}
this.classList.add('active');
langDropdownMenu.classList.remove('open');
});
});
}
}
// --- Move Money Hover Dropdown Logic ---
const moveMoneyContainer = document.getElementById('move-money-container');
const moveMoneyDropdown = document.getElementById('move-money-dropdown-menu');
if (moveMoneyContainer && moveMoneyDropdown) {
moveMoneyContainer.addEventListener('mouseenter', () => {
moveMoneyDropdown.classList.add('open');
moveMoneyContainer.classList.add('open');
});
moveMoneyContainer.addEventListener('mouseleave', () => {
moveMoneyDropdown.classList.remove('open');
moveMoneyContainer.classList.remove('open');
});
}
// --- Overview Card View Toggle Logic ---
const overviewCard = document.getElementById('overview-card');
const assetsViewBtn = document.getElementById('toggle-assets-view-btn');
const overviewViewBtn = document.getElementById('toggle-overview-view-btn');
const assetsViewBtnDetails = document.getElementById('toggle-assets-view-btn-details');
const overviewViewBtnDetails = document.getElementById('toggle-overview-view-btn-details');
if (overviewCard && assetsViewBtn && overviewViewBtn && assetsViewBtnDetails && overviewViewBtnDetails) {
const showAssetsView = () => overviewCard.classList.remove('show-details');
const showDetailsView = () => overviewCard.classList.add('show-details');
assetsViewBtn.addEventListener('click', showAssetsView);
assetsViewBtnDetails.addEventListener('click', showAssetsView);
overviewViewBtn.addEventListener('click', showDetailsView);
overviewViewBtnDetails.addEventListener('click', showDetailsView);
}
// --- Fetch Live Asset Prices ---
async function fetchAssetPrices() {
const assetIds = ['bitcoin', 'ethereum', 'ripple', 'cardano'];
const idsString = assetIds.join(',');
const apiUrl = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${idsString}`;
const overviewAssetList = document.getElementById('overview-asset-list');
const mobileAssetList = document.querySelector('.mobile-tab-content .asset-list');
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const assets = await response.json();
let assetsHtml = '';
assets.forEach(asset => {
const price = asset.current_price;
const change = asset.price_change_percentage_24h;
const changeClass = change >= 0 ? 'positive' : 'negative';
const formattedPrice = price.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
// Sanitized injection
assetsHtml += `
<div class="asset-item">
<a href="trade.php?asset=${escapeHTML(asset.symbol)}">
<div class="asset-icon">
<img src="${escapeHTML(asset.image)}" alt="${escapeHTML(asset.name)} logo">
</div>
<div class="asset-info">
<div class="asset-name">${escapeHTML(asset.name)}</div>
<div class="asset-symbol">${escapeHTML(asset.symbol)}</div>
</div>
<div class="asset-price">
<div class="asset-value">${escapeHTML(formattedPrice)}</div>
<div class="asset-change ${changeClass}">${change.toFixed(2)}%</div>
</div>
</a>
</div>
`;
});
if (overviewAssetList) overviewAssetList.innerHTML = assetsHtml;
if (mobileAssetList) mobileAssetList.innerHTML = assetsHtml;
} catch (error) {
console.error("Could not fetch asset prices:", error);
if (overviewAssetList) overviewAssetList.innerHTML = '<p class="error-message">Could not load asset data.</p>';
if (mobileAssetList) mobileAssetList.innerHTML = '<p class="error-message">Could not load asset data.</p>';
}
}
fetchAssetPrices();
// --- ASSETS PAGE: Fetch and display all assets ---
const assetsTableContainer = document.getElementById('assets-table-container');
if (assetsTableContainer) {
let allFetchedAssets = [];
async function fetchAllAssets() {
const apiUrl = 'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false';
try {
const response = await fetch(apiUrl);
if (!response.ok) {
throw new Error(`API error! status: ${response.status}`);
}
allFetchedAssets = await response.json();
displayAssets(allFetchedAssets);
} catch (error) {
console.error("Could not fetch assets for the table:", error);
assetsTableContainer.innerHTML = '<p style="text-align: center; padding: 20px;">Could not load asset data. Please try again later.</p>';
}
}
function displayAssets(assetsToDisplay) {
let tableHtml = '<div class="assets-table">';
tableHtml += `
<div class="table-header">
<span>Asset</span>
<span>Type</span>
<span>Current price (USD)</span>
<span>In your wallet</span>
<span>Actions</span>
</div>
`;
if (assetsToDisplay.length === 0) {
assetsTableContainer.innerHTML = '<p style="text-align: center; padding: 20px;">No assets found.</p>';
return;
}
assetsToDisplay.forEach(asset => {
const price = asset.current_price;
const formattedPrice = price ? price.toLocaleString('en-US', { style: 'currency', currency: 'USD' }) : 'N/A';
const change24h = asset.price_change_percentage_24h || 0;
const changeClass = change24h >= 0 ? 'positive' : 'negative';
// Sanitized rendering
tableHtml += `
<div class="table-row">
<div>
<div class="asset-icon">
<img src="${escapeHTML(asset.image)}" alt="${escapeHTML(asset.name)} logo">
</div>
<div>
<div class="asset-name">${escapeHTML(asset.name)}</div>
<div class="asset-symbol">${escapeHTML(asset.symbol)}</div>
</div>
</div>
<div>Cryptocurrency</div>
<div class="asset-price">
<div>
<div class="asset-value">${escapeHTML(formattedPrice)}</div>
<div class="asset-change ${changeClass}">${change24h.toFixed(2)}%</div>
</div>
</div>
<div data-label="Wallet:">$0.00</div>
<div class="actions-cell">
<a href="#" class="btn-action-small">Deposit</a>
<a href="trade.php?asset=${escapeHTML(asset.symbol)}" class="btn-action-small">Trade</a>
</div>
</div>
`;
});
tableHtml += '</div>';
assetsTableContainer.innerHTML = tableHtml;
}
const searchInput = document.getElementById('asset-search-input');
if (searchInput) {
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
const filteredAssets = allFetchedAssets.filter(asset =>
asset.name.toLowerCase().includes(searchTerm) ||
asset.symbol.toLowerCase().includes(searchTerm)
);
displayAssets(filteredAssets);
});
}
fetchAllAssets();
}
// --- PAGE-SPECIFIC LOGIC (TRADING FORM) ---
const tradeForm = document.getElementById('trade-form');
const tradingViewContainerId = 'tradingview_f24de';
if (tradeForm && document.getElementById(tradingViewContainerId)) {
const marketAssets = [
// === CRYPTOCURRENCIES ===
{ group: 'Cryptocurrencies', name: 'Bitcoin / USD', value: 'BTCUSD', provider: 'BITSTAMP' },
{ group: 'Cryptocurrencies', name: 'Ethereum / USD', value: 'ETHUSD', provider: 'BITSTAMP' },
{ group: 'Cryptocurrencies', name: 'Solana / USD', value: 'SOLUSD', provider: 'COINBASE' },
{ group: 'Cryptocurrencies', name: 'Ripple / USD', value: 'XRPUSD', provider: 'BITSTAMP' },
// === FOREX PAIRS ===
{ group: 'Forex Pairs', name: 'EUR / USD', value: 'EURUSD', provider: 'FX' },
{ group: 'Forex Pairs', name: 'GBP / USD', value: 'GBPUSD', provider: 'FX' },
{ group: 'Forex Pairs', name: 'USD / JPY', value: 'USDJPY', provider: 'FX' },
// === INDICES & STOCKS ===
{ group: 'Indices & Stocks', name: 'S&P 500', value: 'SPX500', provider: 'TVC' },
{ group: 'Indices & Stocks', name: 'NASDAQ 100', value: 'NSX100', provider: 'TVC' },
{ group: 'Indices & Stocks', name: 'Apple Inc.', value: 'AAPL', provider: 'NASDAQ' },
// === METALS ===
{ group: 'Metals', name: 'Gold', value: 'XAUUSD', provider: 'OANDA' },
{ group: 'Metals', name: 'Silver', value: 'XAGUSD', provider: 'OANDA' },
// === COMMODITIES ===
{ group: 'Commodities', name: 'Crude Oil (WTI)', value: 'WTI', provider: 'TVC' },
{ group: 'Commodities', name: 'Natural Gas', value: 'NATURALGAS', provider: 'TVC' }
];
const marketSelect = document.getElementById('market-select');
const formInputs = tradeForm.querySelectorAll('input[required], select[required]');
const submitButton = document.getElementById('trade-submit-btn');
function createOrUpdateWidget(symbol, provider) {
const widgetContainer = document.getElementById(tradingViewContainerId);
if (!widgetContainer) return;
widgetContainer.innerHTML = '';
new TradingView.widget({
"autosize": true,
"symbol": `${provider}:${symbol}`,
"interval": "D",
"timezone": "Etc/UTC",
"theme": "dark",
"style": "1",
"locale": "en",
"enable_publishing": false,
"hide_top_toolbar": true,
"withdateranges": true,
"backgroundColor": "rgba(5, 8, 11, 1)",
"gridColor": "rgba(255, 255, 255, 0.045)",
"container_id": tradingViewContainerId
});
}
const groupedAssets = marketAssets.reduce((acc, asset) => {
(acc[asset.group] = acc[asset.group] || []).push(asset);
return acc;
}, {});
for (const groupName in groupedAssets) {
const optgroup = document.createElement('optgroup');
optgroup.label = groupName;
groupedAssets[groupName].forEach(asset => {
const option = document.createElement('option');
option.value = `${asset.provider}:${asset.value}`;
option.textContent = asset.name;
optgroup.appendChild(option);
});
marketSelect.appendChild(optgroup);
}
marketSelect.addEventListener('change', (event) => {
const [provider, symbol] = event.target.value.split(':');
createOrUpdateWidget(symbol, provider);
});
function checkFormValidity() {
let allValid = true;
formInputs.forEach(input => {
if (!input.value) {
allValid = false;
}
});
if (allValid) {
submitButton.removeAttribute('disabled');
} else {
submitButton.setAttribute('disabled', 'true');
}
}
formInputs.forEach(input => {
input.addEventListener('input', checkFormValidity);
input.addEventListener('change', checkFormValidity);
});
tradeForm.addEventListener('submit', (e) => {
e.preventDefault();
const submitButton = document.getElementById('trade-submit-btn');
const submitText = submitButton.querySelector('.submit-text');
submitButton.setAttribute('disabled', 'true');
submitButton.classList.add('loading');
submitText.textContent = 'Placing...';
const formData = new FormData(tradeForm);
formData.append('csrf_token', getCsrfToken()); // Added CSRF Protection
fetch('process_trade.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
Swal.fire({
icon: 'success',
title: 'Success!',
text: data.message,
confirmButtonColor: '#4CAF50'
});
tradeForm.reset();
checkFormValidity();
const balanceElement = document.getElementById('user-balance-display');
if (balanceElement && typeof data.new_balance !== 'undefined') {
const formattedBalance = parseFloat(data.new_balance).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
balanceElement.textContent = formattedBalance;
}
loadUserTrades();
} else {
Swal.fire({
icon: 'error',
title: 'Oops...',
text: escapeHTML(data.message), // Sanitized response message
confirmButtonColor: '#f44336'
});
}
})
.catch(error => {
console.error('Error:', error);
Swal.fire({
icon: 'error',
title: 'Connection Error',
text: 'An error occurred while connecting to the server. Please try again.'
});
})
.finally(() => {
submitButton.classList.remove('loading');
submitText.textContent = 'Place Trade';
checkFormValidity();
});
});
if (marketAssets.length > 0) {
const firstAsset = marketAssets[0];
createOrUpdateWidget(firstAsset.value, firstAsset.provider);
}
}
}); // End DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
loadUserTrades();
});
// Adjusted Polling: Increased to 15 seconds AND checks if tab is visible to prevent server overload
setInterval(() => {
if (navigator.onLine && document.visibilityState === 'visible') {
loadUserTrades();
}
}, 15000);
async function loadUserTrades() {
const openTradesBody = document.getElementById('open-trades-body');
const closedTradesBody = document.getElementById('closed-trades-body');
const openTradesCount = document.getElementById('open-trades-count');
const closedTradesCount = document.getElementById('closed-trades-count');
try {
const response = await fetch('fetch_trades.php');
const data = await response.json();
if (data.status !== 'success') {
console.error('Error loading trades:', data.message);
openTradesBody.innerHTML = '<div class="no-trades-message">Error loading trades.</div>';
return;
}
if (typeof data.current_balance !== 'undefined') {
const formattedBalance = parseFloat(data.current_balance).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const headerBalance = document.getElementById('user-balance-display');
const mainBalance = document.getElementById('main-balance-display');
const mobileBalance = document.getElementById('mobile-balance-display');
const detailsBalance = document.getElementById('details-balance-display');
if (headerBalance) headerBalance.textContent = formattedBalance;
if (mainBalance) mainBalance.textContent = formattedBalance;
if (mobileBalance) mobileBalance.textContent = formattedBalance;
if (detailsBalance) detailsBalance.textContent = formattedBalance;
}
openTradesBody.innerHTML = '';
openTradesCount.textContent = `(${escapeHTML(data.open_trades.length)})`;
if (data.open_trades.length === 0) {
openTradesBody.innerHTML = '<div class="no-trades-message">You have no open trades.</div>';
} else {
data.open_trades.forEach(trade => {
const tradeRowHtml = createTradeRow(trade, true);
openTradesBody.innerHTML += tradeRowHtml;
});
}
closedTradesBody.innerHTML = '';
closedTradesCount.textContent = `(${escapeHTML(data.closed_trades.length)})`;
if (data.closed_trades.length === 0) {
closedTradesBody.innerHTML = '<div class="no-trades-message">You have no closed trades.</div>';
} else {
data.closed_trades.forEach(trade => {
const tradeRowHtml = createTradeRow(trade, false);
closedTradesBody.innerHTML += tradeRowHtml;
});
}
startPnlAnimation();
} catch (error) {
console.error('Failed to fetch trades:', error);
openTradesBody.innerHTML = '<div class="no-trades-message">Could not connect to the server.</div>';
}
}
// XSS Mitigation included in string generation
function createTradeRow(trade, isOpen) {
const tradeDate = escapeHTML(new Date(trade.created_at).toLocaleDateString());
const tradeAmount = parseFloat(trade.trade_amount);
if (isNaN(tradeAmount)) {
console.error('Invalid trade_amount received from server:', trade.trade_amount, 'for trade ID:', trade.id);
return `<div class="trades-table-row error-row"><div data-label="Error">Error: Invalid data.</div></div>`;
}
const directionClass = trade.trade_type === 'BUY' ? 'positive' : 'negative';
let pnlHtml;
let actionHtml;
if (isOpen) {
// OPEN trades
pnlHtml = `<span class="pnl-animating" data-trade-id="${escapeHTML(trade.id)}" data-trade-amount="${tradeAmount.toFixed(2)}">0.00</span>`;
actionHtml = `<button class="btn-close-trade" data-trade-id="${escapeHTML(trade.id)}">Close</button>`;
} else {
// CLOSED trades
const finalPnl = parseFloat(trade.pnl || 0).toFixed(4);
const pnlClass = finalPnl > 0 ? 'positive' : (finalPnl < 0 ? 'negative' : 'neutral');
pnlHtml = `<span class="${pnlClass}">${escapeHTML(finalPnl)}</span>`;
actionHtml = `<span>${escapeHTML(trade.status)}</span>`;
}
return `
<div class="trades-table-row">
<div data-label="Date">${tradeDate}</div>
<div data-label="Trade">${escapeHTML(trade.commodity)}</div>
<div data-label="Direction" class="${directionClass}">${escapeHTML(trade.trade_type)}</div>
<div data-label="Amount">${tradeAmount.toFixed(2)}</div>
<div data-label="Profit" class="pnl-value">${pnlHtml}</div>
<div data-label="Action">${actionHtml}</div>
</div>
`;
}
function startPnlAnimation() {
const pnlElements = document.querySelectorAll('.pnl-animating');
// Ensure userSignalStrength is defined, fallback to 50 if missing globally
const signalStrengthValue = typeof userSignalStrength !== 'undefined' ? parseFloat(userSignalStrength) : 50;
pnlElements.forEach(el => {
const maxLoss = parseFloat(el.dataset.tradeAmount);
if (isNaN(maxLoss)) {
el.textContent = "Error";
console.error("Stopping animation because maxLoss is NaN.", el);
return;
}
let currentPnl = 0.00;
const updatePnl = () => {
const maxVolatilityPercentage = 0.20;
const signalMultiplier = signalStrengthValue / 100;
const maxTickChange = maxLoss * maxVolatilityPercentage * signalMultiplier;
// Visual math only!
const change = (Math.random() - 0.5) * 2 * maxTickChange;
currentPnl += change;
if (currentPnl < -maxLoss) {
currentPnl = -maxLoss;
}
el.textContent = currentPnl.toFixed(2);
const parentContainer = el.parentElement;
if (currentPnl >= 0) {
parentContainer.className = 'pnl-value positive';
} else {
parentContainer.className = 'pnl-value negative';
}
if (!navigator.onLine) {
return;
}
const randomDelay = Math.random() * (2500 - 400) + 400;
setTimeout(updatePnl, randomDelay);
};
updatePnl();
});
}
document.addEventListener('click', function(e) {
if (e.target.classList.contains('btn-close-trade')) {
const button = e.target;
const tradeId = button.dataset.tradeId;
// CRITICAL FIX: The user should only confirm intent. Server calculates actual price.
Swal.fire({
title: 'Close Trade?',
text: `You are about to close this trade at the current live market price. The server will calculate your final Profit/Loss.`,
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, close it!'
}).then((result) => {
if (result.isConfirmed) {
closeTrade(tradeId); // Note: finalPnl has been removed!
}
});
}
});
// CRITICAL FIX: Only send Trade ID. Removed forged Client PNL from payload.
async function closeTrade(tradeId) {
const formData = new FormData();
formData.append('trade_id', tradeId);
// Add CSRF token for security
const csrfToken = document.querySelector('meta[name="csrf-token"]');
if (csrfToken) {
formData.append('csrf_token', csrfToken.getAttribute('content'));
}
try {
const response = await fetch('close_trade.php', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.status === 'success') {
Swal.fire('Closed!', data.message, 'success');
loadUserTrades();
} else {
Swal.fire('Error!', escapeHTML(data.message), 'error');
}
} catch (error) {
Swal.fire('Connection Error', 'Could not connect to the server.', 'error');
}
}
window.addEventListener('offline', () => {
console.log('Network connection lost. PNL animations paused.');
Swal.fire({
toast: true,
position: 'top-end',
icon: 'warning',
title: 'Connection lost. Live data is paused.',
showConfirmButton: false,
timer: 5000
});
});
window.addEventListener('online', () => {
console.log('Network connection restored. Resuming live data...');
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: 'Connection restored. Resuming live data...',
showConfirmButton: false,
timer: 3000
});
loadUserTrades();
});
b IDATxytVսϓ22 A@IR:hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-E