- Change file mode from 644 to 755 for all project files - Add chain_id parameter to get_balance RPC endpoint with default "ait-devnet" - Rename Miner.extra_meta_data to extra_metadata for consistency
1251 lines
59 KiB
HTML
Executable File
1251 lines
59 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en" data-theme="dark">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AITBC Trade Exchange - Buy AITBC with Bitcoin</title>
|
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
|
<meta http-equiv="Pragma" content="no-cache">
|
|
<meta http-equiv="Expires" content="0">
|
|
<link rel="stylesheet" href="/assets/css/aitbc.css?v=20241229-1305">
|
|
<link rel="stylesheet" href="/assets/css/site-header.css">
|
|
<script src="/assets/js/axios.min.js?v=20260215-2015"></script>
|
|
<script src="/assets/js/lucide.js?v=20260215-2015"></script>
|
|
<style>
|
|
.gradient-bg {
|
|
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); /* Changed to blue to match global theme */
|
|
}
|
|
.card-hover {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.card-hover:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
}
|
|
.pulse-animation {
|
|
animation: pulse 2s infinite;
|
|
}
|
|
@keyframes pulse {
|
|
0% { opacity: 1; }
|
|
50% { opacity: 0.5; }
|
|
100% { opacity: 1; }
|
|
}
|
|
/* Fix navigation button styling */
|
|
.nav-button {
|
|
background: transparent !important;
|
|
color: white !important;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
font-weight: 500;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.nav-button:hover {
|
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
color: white !important;
|
|
}
|
|
.nav-button:focus {
|
|
outline: none;
|
|
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.3);
|
|
}
|
|
</style>
|
|
<link rel="preload" href="/assets/css/font-awesome.min.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
|
<noscript><link rel="stylesheet" href="/assets/css/font-awesome.min.css"></noscript>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" crossorigin="anonymous" media="print" onload="this.media='all'; this.onload=null;">
|
|
</head>
|
|
<body class="bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
|
|
<div data-global-header></div>
|
|
|
|
<!-- Price Ticker -->
|
|
<section class="bg-white dark:bg-gray-800 border-b dark:border-gray-700">
|
|
<div class="container mx-auto px-4 py-4">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-6">
|
|
<div class="flex items-center space-x-2">
|
|
<span class="text-gray-600 dark:text-gray-400">AITBC/BTC:</span>
|
|
<span class="text-2xl font-bold text-green-600 dark:text-green-400" id="aitbcBtcPrice">0.00001</span>
|
|
<span class="text-sm text-green-500 dark:text-green-400">+5.2%</span>
|
|
</div>
|
|
<div class="flex items-center space-x-2">
|
|
<span class="text-gray-600 dark:text-gray-400">24h Volume:</span>
|
|
<span class="font-semibold text-gray-900 dark:text-white" id="dailyVolume">Loading...</span>
|
|
</div>
|
|
</div>
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
Last updated: <span id="lastUpdated">Just now</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Main Content -->
|
|
<main class="container mx-auto px-4 py-8">
|
|
<!-- Trade Section -->
|
|
<section id="tradeSection" class="section">
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Buy AITBC -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<h2 class="text-xl font-bold mb-6 flex items-center text-gray-900 dark:text-white">
|
|
<i data-lucide="arrow-down-left" class="w-5 h-5 mr-2 text-green-600 dark:text-green-400"></i>
|
|
Buy AITBC with Bitcoin
|
|
</h2>
|
|
|
|
<div id="tradeConnectPrompt" class="bg-blue-50 dark:bg-blue-900/30 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4">
|
|
<p class="text-sm text-blue-800 text-gray-100 dark:text-blue-100 mb-3">
|
|
<i data-lucide="wallet" class="w-4 h-4 inline mr-1"></i>
|
|
Connect your wallet to start trading
|
|
</p>
|
|
<button onclick="connectWallet()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">
|
|
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
|
|
</button>
|
|
</div>
|
|
|
|
<div id="tradeForm" class="hidden">
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 mb-4">
|
|
<p class="text-sm text-blue-800 dark:text-blue-200">
|
|
<i data-lucide="info" class="w-4 h-4 inline mr-1"></i>
|
|
Send Bitcoin to the generated address. Your AITBC will be credited after 1 confirmation.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Pay with Bitcoin</label>
|
|
<div class="relative">
|
|
<input type="number" id="btcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-12 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="0.001" step="0.00001">
|
|
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">BTC</span>
|
|
</div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Available: 0.12345 BTC</p>
|
|
</div>
|
|
|
|
<div class="flex justify-center">
|
|
<button onclick="swapCurrencies()" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-full hover:bg-gray-200 dark:hover:bg-gray-600 transition">
|
|
<i data-lucide="arrow-up-down" class="w-5 h-5 text-gray-700 dark:text-gray-300"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">You will receive</label>
|
|
<div class="relative">
|
|
<input type="number" id="aitbcAmount" class="w-full border dark:border-gray-600 rounded-lg px-4 py-3 pr-16 bg-white dark:bg-gray-700 text-gray-900 dark:text-white" placeholder="100" step="0.01">
|
|
<span class="absolute right-3 top-3 text-gray-500 dark:text-gray-400">AITBC</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
|
|
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
|
|
<span>Price</span>
|
|
<span>0.00001 BTC/AITBC</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm mb-2 text-gray-700 dark:text-gray-300">
|
|
<span>Fee (0.5%)</span>
|
|
<span>0.000005 BTC</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm font-semibold text-gray-900 dark:text-white">
|
|
<span>Total</span>
|
|
<span>0.001005 BTC</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button onclick="createPaymentRequest()" class="w-full bg-green-600 text-white py-3 rounded-lg hover:bg-green-700 transition font-semibold">
|
|
Create Payment Request
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Order Book -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
|
|
<h2 class="text-xl font-bold mb-6 flex items-center justify-between text-gray-900 dark:text-white">
|
|
<span class="flex items-center">
|
|
<i data-lucide="book-open" class="w-5 h-5 mr-2 text-blue-600 dark:text-blue-400"></i>
|
|
Order Book
|
|
</span>
|
|
<div class="flex space-x-2">
|
|
<button onclick="refreshOrderBook()" class="p-1 hover:bg-gray-100 dark:hover:bg-gray-700 rounded">
|
|
<i data-lucide="refresh-cw" class="w-4 h-4 text-gray-700 dark:text-gray-300"></i>
|
|
</button>
|
|
</div>
|
|
</h2>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<!-- Sell Orders -->
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-red-600 dark:text-red-400 mb-3">Sell Orders</h3>
|
|
<div class="space-y-1" id="sellOrders">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-red-600 dark:text-red-400">0.00001</span>
|
|
<span class="text-gray-900 dark:text-white">500</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-red-600 dark:text-red-400">0.000011</span>
|
|
<span class="text-gray-900 dark:text-white">300</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-red-600 dark:text-red-400">0.000012</span>
|
|
<span class="text-gray-900 dark:text-white">200</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Buy Orders -->
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-green-600 dark:text-green-400 mb-3">Buy Orders</h3>
|
|
<div class="space-y-1" id="buyOrders">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-green-600 dark:text-green-400">0.000009</span>
|
|
<span class="text-gray-900 dark:text-white">150</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-green-600 dark:text-green-400">0.000008</span>
|
|
<span class="text-gray-900 dark:text-white">200</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-green-600 dark:text-green-400">0.000007</span>
|
|
<span class="text-gray-900 dark:text-white">300</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Trades -->
|
|
<div class="mt-6 pt-6 border-t dark:border-gray-700">
|
|
<h3 class="text-sm font-semibold text-gray-600 dark:text-gray-400 mb-3">Recent Trades</h3>
|
|
<div class="space-y-1" id="recentTrades">
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-900 dark:text-white">0.000010</span>
|
|
<span class="text-gray-600 dark:text-gray-400">100</span>
|
|
<span class="text-green-600 dark:text-green-400">Buy</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-900 dark:text-white">0.000011</span>
|
|
<span class="text-red-600 dark:text-red-400">50</span>
|
|
<span class="text-gray-500 dark:text-gray-400">5 min ago</span>
|
|
</div>
|
|
<div class="flex justify-between text-sm">
|
|
<span class="text-gray-900 dark:text-white">0.00001</span>
|
|
<span class="text-green-600 dark:text-green-400">200</span>
|
|
<span class="text-gray-500 dark:text-gray-400">8 min ago</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GPU Marketplace Link -->
|
|
<div class="mt-8 bg-gradient-to-r from-purple-600 to-blue-600 rounded-lg p-8 text-white">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h2 class="text-2xl font-bold mb-2">Ready to Use Your AITBC?</h2>
|
|
<p class="mb-4">Purchase GPU compute time for AI workloads on our decentralized marketplace</p>
|
|
<button onclick="window.location.href='/marketplace/'" class="bg-white text-purple-600 px-6 py-3 rounded-lg hover:bg-purple-100 transition font-semibold">
|
|
Browse GPU Marketplace
|
|
</button>
|
|
</div>
|
|
<i data-lucide="cpu" class="w-24 h-24 opacity-50"></i>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Marketplace Section -->
|
|
<section id="marketplaceSection" class="section hidden">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold">Available GPU Compute</h2>
|
|
<button onclick="showSection('trade')" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">
|
|
<i data-lucide="arrow-left" class="w-4 h-4 inline mr-2"></i>Back to Trading
|
|
</button>
|
|
</div>
|
|
|
|
<div id="gpuList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
<!-- GPU cards will be inserted here -->
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Wallet Section -->
|
|
<section id="walletSection" class="section hidden">
|
|
<div class="max-w-4xl mx-auto">
|
|
<h2 class="text-2xl font-bold mb-6">Your Profile</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
|
<!-- User Profile Card -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
|
|
<h3 class="text-lg font-semibold mb-4 flex items-center">
|
|
<i data-lucide="user" class="w-5 h-5 mr-2 text-blue-500"></i>
|
|
User Profile
|
|
</h3>
|
|
|
|
<div id="notLoggedIn" class="space-y-4">
|
|
<p class="text-gray-600 dark:text-gray-400">Please connect your wallet to access your profile</p>
|
|
<button onclick="connectWallet()" id="connectWalletBtn" class="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition">
|
|
<i data-lucide="wallet" class="w-4 h-4 inline mr-2"></i>Connect Wallet
|
|
</button>
|
|
</div>
|
|
|
|
<div id="userProfile" class="hidden space-y-4">
|
|
<div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Username</p>
|
|
<p class="font-semibold text-gray-900 dark:text-white" id="userUsername">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">User ID</p>
|
|
<p class="font-mono text-xs text-gray-700 dark:text-gray-300" id="userId">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Member Since</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" id="userCreated">-</p>
|
|
</div>
|
|
<button onclick="logout()" class="w-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-4 py-2 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">
|
|
<i data-lucide="log-out" class="w-4 h-4 inline mr-2"></i>Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- AITBC Wallet -->
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
|
|
<h3 class="text-lg font-semibold mb-4 flex items-center">
|
|
<i data-lucide="coins" class="w-5 h-5 mr-2 text-purple-500"></i>
|
|
AITBC Wallet
|
|
</h3>
|
|
<div class="space-y-3">
|
|
<div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Address</p>
|
|
<p class="font-mono text-sm text-gray-700 dark:text-gray-300" id="aitbcAddress">Not connected</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Balance</p>
|
|
<p class="text-2xl font-bold text-gray-900 dark:text-white" id="aitbcBalance">0 AITBC</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transaction History -->
|
|
<div class="mt-12 bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
|
|
<h3 class="text-lg font-semibold mb-6 text-gray-900 dark:text-white">Transaction History</h3>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-sm">
|
|
<thead>
|
|
<tr class="border-b dark:border-gray-700">
|
|
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Time</th>
|
|
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Type</th>
|
|
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Amount</th>
|
|
<th class="text-left py-3 text-gray-700 dark:text-gray-300">Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="transactionHistory">
|
|
<tr class="border-b dark:border-gray-700">
|
|
<td class="py-3 text-gray-700 dark:text-gray-300">2025-12-28 10:30</td>
|
|
<td class="py-2">
|
|
<span class="text-green-600 dark:text-green-400">Buy AITBC</span>
|
|
</td>
|
|
<td class="py-3 text-gray-700 dark:text-gray-300">+100 AITBC</td>
|
|
<td class="py-2">
|
|
<span class="bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 px-2 py-1 rounded text-xs">Completed</span>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- QR Code Modal -->
|
|
<div id="qrModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-8 max-w-md w-full">
|
|
<h3 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">Send Bitcoin to Complete Purchase</h3>
|
|
|
|
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg mb-4">
|
|
<img id="paymentQR" src="" alt="Payment QR Code" class="mx-auto">
|
|
</div>
|
|
|
|
<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 mb-4">
|
|
<p class="text-sm text-yellow-800 dark:text-yellow-200">
|
|
<strong>Payment Address:</strong>
|
|
</p>
|
|
<p class="font-mono text-xs break-all text-gray-700 dark:text-gray-300" id="paymentAddress"></p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4 mb-4 text-sm">
|
|
<div>
|
|
<p class="text-gray-600 dark:text-gray-400">Amount to Send:</p>
|
|
<p class="font-semibold text-gray-900 dark:text-white" id="paymentAmount">0 BTC</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-gray-600 dark:text-gray-400">You'll Receive:</p>
|
|
<p class="font-semibold text-green-600 dark:text-green-400" id="receiveAmount">0 AITBC</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-center mb-4">
|
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-green-600" id="paymentSpinner"></div>
|
|
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">Waiting for payment...</span>
|
|
</div>
|
|
|
|
<div class="flex space-x-3">
|
|
<button onclick="closeQRModal()" class="flex-1 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 py-2 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition">
|
|
Cancel
|
|
</button>
|
|
<button onclick="checkPaymentStatus()" class="flex-1 bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">
|
|
Check Payment
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
console.log('Exchange script loaded and executing globally.');
|
|
console.log('Script execution initiated.');
|
|
// API Configuration
|
|
const API_BASE = window.location.origin + '/api';
|
|
const BLOCKCHAIN_API = window.location.origin + '/rpc';
|
|
const EXCHANGE_RATE = 0.00001; // 1 AITBC = 0.00001 BTC
|
|
|
|
let walletAddress = null;
|
|
let currentUser = null;
|
|
let sessionToken = null;
|
|
let aitbcBalance = 0;
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('DOMContentLoaded event fired, starting script execution.');
|
|
lucide.createIcons();
|
|
updatePrices();
|
|
loadGPUOffers();
|
|
|
|
// Auto-refresh prices every 30 seconds
|
|
setInterval(updatePrices, 30000);
|
|
|
|
// Input handlers
|
|
document.getElementById('btcAmount').addEventListener('input', updateAITBCAmount);
|
|
document.getElementById('aitbcAmount').addEventListener('input', updateBTCAmount);
|
|
|
|
// Initialize enhanced dark mode
|
|
// initializeTheme(); handled by global-header.js
|
|
|
|
// Initialize touch navigation
|
|
new ExchangeTouchNavigation();
|
|
});
|
|
|
|
// Enhanced Dark mode functionality with system preference detection
|
|
|
|
|
|
else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
|
|
// Save to localStorage for persistence
|
|
localStorage.setItem('exchangeTheme', theme);
|
|
|
|
// Update button display
|
|
|
|
|
|
// Send analytics event if available
|
|
if (window.analytics) {
|
|
window.analytics.track('exchange_theme_changed', { theme });
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 2. Check main site preference for consistency
|
|
const mainSiteTheme = localStorage.getItem('theme');
|
|
if (mainSiteTheme) {
|
|
return mainSiteTheme;
|
|
}
|
|
|
|
// 3. Check system preference
|
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
return 'dark';
|
|
}
|
|
|
|
// 4. Default to dark (AITBC brand preference)
|
|
return 'dark';
|
|
}
|
|
|
|
|
|
});
|
|
}
|
|
}
|
|
|
|
// Touch Navigation for Mobile
|
|
class ExchangeTouchNavigation {
|
|
constructor() {
|
|
this.touchStartX = 0;
|
|
this.touchStartY = 0;
|
|
this.touchEndX = 0;
|
|
this.touchEndY = 0;
|
|
this.minSwipeDistance = 50;
|
|
this.maxVerticalDistance = 100;
|
|
|
|
// Exchange sections for navigation
|
|
this.sections = ['trade', 'marketplace', 'wallet'];
|
|
this.currentSectionIndex = 0;
|
|
|
|
this.bindEvents();
|
|
this.setupMobileOptimizations();
|
|
this.updateCurrentSection();
|
|
}
|
|
|
|
bindEvents() {
|
|
document.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: false });
|
|
document.addEventListener('touchmove', this.handleTouchMove.bind(this), { passive: false });
|
|
document.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: false });
|
|
}
|
|
|
|
handleTouchStart(e) {
|
|
this.touchStartX = e.touches[0].clientX;
|
|
this.touchStartY = e.touches[0].clientY;
|
|
}
|
|
|
|
handleTouchMove(e) {
|
|
// Prevent scrolling when detecting horizontal swipes
|
|
const touchCurrentX = e.touches[0].clientX;
|
|
const touchCurrentY = e.touches[0].clientY;
|
|
const deltaX = Math.abs(touchCurrentX - this.touchStartX);
|
|
const deltaY = Math.abs(touchCurrentY - this.touchStartY);
|
|
|
|
// If horizontal movement is greater than vertical, prevent default scrolling
|
|
if (deltaX > deltaY && deltaX > 10) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
|
|
handleTouchEnd(e) {
|
|
this.touchEndX = e.changedTouches[0].clientX;
|
|
this.touchEndY = e.changedTouches[0].clientY;
|
|
|
|
const deltaX = this.touchEndX - this.touchStartX;
|
|
const deltaY = Math.abs(this.touchEndY - this.touchStartY);
|
|
|
|
// Only process swipe if vertical movement is minimal
|
|
if (deltaY < this.maxVerticalDistance && Math.abs(deltaX) > this.minSwipeDistance) {
|
|
if (deltaX > 0) {
|
|
this.swipeRight();
|
|
} else {
|
|
this.swipeLeft();
|
|
}
|
|
}
|
|
}
|
|
|
|
swipeLeft() {
|
|
// Navigate to next section
|
|
const nextIndex = Math.min(this.currentSectionIndex + 1, this.sections.length - 1);
|
|
this.navigateToSection(nextIndex);
|
|
}
|
|
|
|
swipeRight() {
|
|
// Navigate to previous section
|
|
const prevIndex = Math.max(this.currentSectionIndex - 1, 0);
|
|
this.navigateToSection(prevIndex);
|
|
}
|
|
|
|
navigateToSection(index) {
|
|
const section = this.sections[index];
|
|
showSection(section);
|
|
this.currentSectionIndex = index;
|
|
|
|
// Update URL hash without triggering scroll
|
|
history.replaceState(null, null, `#${section}`);
|
|
|
|
// Add visual feedback
|
|
this.showSwipeIndicator();
|
|
}
|
|
|
|
showSwipeIndicator() {
|
|
// Create a temporary visual indicator
|
|
const indicator = document.createElement('div');
|
|
indicator.textContent = '← Swipe to navigate →';
|
|
indicator.style.cssText = `
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(0,0,0,0.8);
|
|
color: white;
|
|
padding: 8px 16px;
|
|
border-radius: 20px;
|
|
font-size: 12px;
|
|
z-index: 1000;
|
|
pointer-events: none;
|
|
animation: fadeInOut 2s ease-in-out;
|
|
`;
|
|
|
|
// Add fade animation
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes fadeInOut {
|
|
0% { opacity: 0; }
|
|
20% { opacity: 1; }
|
|
80% { opacity: 1; }
|
|
100% { opacity: 0; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
document.body.appendChild(indicator);
|
|
setTimeout(() => {
|
|
document.body.removeChild(indicator);
|
|
document.head.removeChild(style);
|
|
}, 2000);
|
|
}
|
|
|
|
setupMobileOptimizations() {
|
|
this.setupTouchButtons();
|
|
this.setupMobileMenu();
|
|
this.setupScrollOptimizations();
|
|
}
|
|
|
|
setupTouchButtons() {
|
|
// Make buttons more touch-friendly
|
|
const buttons = document.querySelectorAll('button, input[type="submit"], .cta-button');
|
|
buttons.forEach(button => {
|
|
button.addEventListener('touchstart', () => {
|
|
button.style.transform = 'scale(0.98)';
|
|
}, { passive: true });
|
|
|
|
button.addEventListener('touchend', () => {
|
|
button.style.transform = '';
|
|
}, { passive: true });
|
|
});
|
|
}
|
|
|
|
setupMobileMenu() {
|
|
// On very small screens, create a hamburger menu for nav buttons
|
|
if (window.innerWidth < 640) {
|
|
this.createMobileNavMenu();
|
|
}
|
|
|
|
// Re-check on resize
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth < 640) {
|
|
this.createMobileNavMenu();
|
|
}
|
|
});
|
|
}
|
|
|
|
createMobileNavMenu() {
|
|
const nav = document.querySelector('nav');
|
|
if (!nav || nav.querySelector('.mobile-menu-toggle')) return;
|
|
|
|
// Create hamburger button
|
|
const menuToggle = document.createElement('button');
|
|
menuToggle.className = 'mobile-menu-toggle';
|
|
menuToggle.innerHTML = '☰';
|
|
menuToggle.setAttribute('aria-label', 'Toggle navigation menu');
|
|
menuToggle.style.cssText = `
|
|
display: none;
|
|
background: rgba(255,255,255,0.1);
|
|
border: none;
|
|
color: white;
|
|
font-size: 18px;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
margin-left: auto;
|
|
cursor: pointer;
|
|
`;
|
|
|
|
// Hide original nav buttons and show hamburger on mobile
|
|
const navButtons = nav.querySelectorAll('button:not(.mobile-menu-toggle)');
|
|
navButtons.forEach(btn => btn.style.display = 'none');
|
|
menuToggle.style.display = 'block';
|
|
|
|
// Toggle menu on click
|
|
menuToggle.addEventListener('click', () => {
|
|
const isOpen = menuToggle.textContent === '✕';
|
|
if (isOpen) {
|
|
navButtons.forEach(btn => btn.style.display = 'none');
|
|
menuToggle.innerHTML = '☰';
|
|
} else {
|
|
navButtons.forEach(btn => btn.style.display = 'inline-flex');
|
|
menuToggle.innerHTML = '✕';
|
|
}
|
|
});
|
|
|
|
nav.appendChild(menuToggle);
|
|
}
|
|
|
|
setupScrollOptimizations() {
|
|
// Improve momentum scrolling on iOS
|
|
if ('webkitOverflowScrolling' in document.body.style) {
|
|
document.body.style.webkitOverflowScrolling = 'touch';
|
|
}
|
|
|
|
// Add touch feedback for interactive elements
|
|
document.querySelectorAll('a, button, input, select').forEach(el => {
|
|
el.addEventListener('touchstart', () => {
|
|
el.style.opacity = '0.8';
|
|
}, { passive: true });
|
|
|
|
el.addEventListener('touchend', () => {
|
|
el.style.opacity = '';
|
|
}, { passive: true });
|
|
});
|
|
}
|
|
|
|
updateCurrentSection() {
|
|
// Update current section based on visible section
|
|
this.sections.forEach((sectionId, index) => {
|
|
const element = document.getElementById(sectionId + 'Section');
|
|
if (element && !element.classList.contains('hidden')) {
|
|
this.currentSectionIndex = index;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Connect Wallet
|
|
async function connectWallet() {
|
|
try {
|
|
// Real wallet mode - connect to extension
|
|
if (typeof window.aitbcWallet === 'undefined') {
|
|
showToast('AITBC Wallet extension not found. Please install it first.', 'error');
|
|
return;
|
|
}
|
|
|
|
// Request connection to wallet extension
|
|
const response = await window.aitbcWallet.connect();
|
|
|
|
if (response.success) {
|
|
walletAddress = response.address;
|
|
|
|
// Login or register user with real wallet
|
|
const loginResponse = await axios.post(`${API_BASE}/users/login`, {
|
|
wallet_address: walletAddress
|
|
});
|
|
|
|
const user = loginResponse.data;
|
|
currentUser = user;
|
|
sessionToken = user.session_token;
|
|
|
|
// Update UI
|
|
updateWalletUI(user);
|
|
showToast(`Connected to AITBC Wallet: ${walletAddress.substring(0, 20)}...`);
|
|
|
|
// Load real balance
|
|
await loadUserBalance();
|
|
} else {
|
|
showToast('Failed to connect to wallet: ' + response.error, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to connect wallet:', error);
|
|
console.error('Error details:', JSON.stringify(error, null, 2));
|
|
const errorMsg = error.message || error.error || error.toString() || 'Unknown error';
|
|
showToast('Failed to connect wallet: ' + errorMsg, 'error');
|
|
}
|
|
}
|
|
|
|
// Update Wallet UI (helper function)
|
|
function updateWalletUI(user) {
|
|
document.getElementById('aitbcAddress').textContent = walletAddress;
|
|
document.getElementById('userUsername').textContent = user.username;
|
|
document.getElementById('userId').textContent = user.user_id;
|
|
document.getElementById('userCreated').textContent = new Date(user.created_at).toLocaleDateString();
|
|
|
|
// Update navigation
|
|
document.getElementById('navConnectBtn').classList.add('hidden');
|
|
document.getElementById('navUserInfo').classList.remove('hidden');
|
|
document.getElementById('navUsername').textContent = user.username;
|
|
|
|
// Show trade form, hide connect prompt
|
|
document.getElementById('tradeConnectPrompt').classList.add('hidden');
|
|
document.getElementById('tradeForm').classList.remove('hidden');
|
|
|
|
// Show profile, hide login prompt
|
|
document.getElementById('notLoggedIn').classList.add('hidden');
|
|
document.getElementById('userProfile').classList.remove('hidden');
|
|
}
|
|
|
|
// Update Prices
|
|
function updatePrices() {
|
|
// Fetch real market data
|
|
fetchMarketData();
|
|
}
|
|
|
|
// Fetch real market data
|
|
async function fetchMarketData() {
|
|
try {
|
|
// Get market stats from API
|
|
const response = await axios.get(`${API_BASE}/marketplace/stats`);
|
|
const stats = response.data;
|
|
|
|
// Update price
|
|
if (stats.price) {
|
|
document.getElementById('aitbcBtcPrice').textContent = stats.price.toFixed(5);
|
|
}
|
|
|
|
// Update volume
|
|
if (stats.daily_volume > 0) {
|
|
document.getElementById('dailyVolume').textContent =
|
|
stats.daily_volume.toLocaleString() + ' AITBC';
|
|
} else {
|
|
document.getElementById('dailyVolume').textContent = '0 AITBC';
|
|
}
|
|
|
|
// Update last updated time
|
|
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
|
|
} catch (error) {
|
|
// Fallback if API is not available
|
|
const variation = (Math.random() - 0.5) * 0.000001;
|
|
const newPrice = EXCHANGE_RATE + variation;
|
|
document.getElementById('aitbcBtcPrice').textContent = newPrice.toFixed(5);
|
|
document.getElementById('dailyVolume').textContent = '0 AITBC';
|
|
document.getElementById('lastUpdated').textContent = new Date().toLocaleTimeString();
|
|
}
|
|
}
|
|
|
|
// Currency Conversion
|
|
function updateAITBCAmount() {
|
|
const btcAmount = parseFloat(document.getElementById('btcAmount').value) || 0;
|
|
const aitbcAmount = btcAmount / EXCHANGE_RATE;
|
|
document.getElementById('aitbcAmount').value = aitbcAmount.toFixed(2);
|
|
}
|
|
|
|
function updateBTCAmount() {
|
|
const aitbcAmount = parseFloat(document.getElementById('aitbcAmount').value) || 0;
|
|
const btcAmount = aitbcAmount * EXCHANGE_RATE;
|
|
document.getElementById('btcAmount').value = btcAmount.toFixed(5);
|
|
}
|
|
|
|
function swapCurrencies() {
|
|
const btcInput = document.getElementById('btcAmount');
|
|
const aitbcInput = document.getElementById('aitbcAmount');
|
|
|
|
const temp = btcInput.value;
|
|
btcInput.value = aitbcInput.value;
|
|
aitbcInput.value = temp;
|
|
|
|
updateAITBCAmount();
|
|
}
|
|
|
|
// Create Payment Request
|
|
async function createPaymentRequest() {
|
|
const btcAmount = parseFloat(document.getElementById('btcAmount').value) || 0;
|
|
const aitbcAmount = parseFloat(document.getElementById('aitbcAmount').value) || 0;
|
|
|
|
if (btcAmount <= 0 || aitbcAmount <= 0) {
|
|
showToast('Please enter a valid amount', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!currentUser || !sessionToken) {
|
|
showToast('Please connect your wallet first', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Create payment request
|
|
const response = await axios.post(`${API_BASE}/exchange/create-payment`, {
|
|
user_id: currentUser.user_id,
|
|
aitbc_amount: aitbcAmount,
|
|
btc_amount: btcAmount
|
|
}, {
|
|
headers: { 'X-Session-Token': sessionToken }
|
|
});
|
|
|
|
const payment = response.data;
|
|
showPaymentModal(payment);
|
|
|
|
// Start checking payment status
|
|
startPaymentMonitoring(payment.payment_id);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to create payment:', error);
|
|
showToast('Failed to create payment request', 'error');
|
|
}
|
|
}
|
|
|
|
// Show Payment Modal
|
|
function showPaymentModal(payment) {
|
|
// Update modal with payment details
|
|
document.getElementById('paymentAddress').textContent = payment.payment_address;
|
|
document.getElementById('paymentAmount').textContent = payment.btc_amount + ' BTC';
|
|
document.getElementById('receiveAmount').textContent = payment.aitbc_amount + ' AITBC';
|
|
|
|
// Generate QR code
|
|
const qrData = `bitcoin:${payment.payment_address}?amount=${payment.btc_amount}`;
|
|
document.getElementById('paymentQR').src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(qrData)}`;
|
|
|
|
// Store payment ID for checking
|
|
window.currentPaymentId = payment.payment_id;
|
|
|
|
// Show modal
|
|
document.getElementById('qrModal').classList.remove('hidden');
|
|
}
|
|
|
|
// Start Payment Monitoring
|
|
function startPaymentMonitoring(paymentId) {
|
|
const checkInterval = setInterval(async () => {
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/exchange/payment-status/${paymentId}`);
|
|
const payment = response.data;
|
|
|
|
if (payment.status === 'confirmed') {
|
|
clearInterval(checkInterval);
|
|
handlePaymentConfirmed(payment);
|
|
} else if (payment.status === 'expired') {
|
|
clearInterval(checkInterval);
|
|
showToast('Payment expired. Please try again.', 'error');
|
|
closeQRModal();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking payment:', error);
|
|
}
|
|
}, 10000); // Check every 10 seconds
|
|
}
|
|
|
|
// Check Payment Status
|
|
async function checkPaymentStatus() {
|
|
if (!window.currentPaymentId) return;
|
|
|
|
try {
|
|
const response = await axios.get(`${API_BASE}/exchange/payment-status/${window.currentPaymentId}`);
|
|
const payment = response.data;
|
|
|
|
if (payment.status === 'confirmed') {
|
|
handlePaymentConfirmed(payment);
|
|
} else {
|
|
showToast('Payment not yet detected. Please wait.', 'info');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error checking payment:', error);
|
|
showToast('Error checking payment status', 'error');
|
|
}
|
|
}
|
|
|
|
// Handle Payment Confirmed
|
|
function handlePaymentConfirmed(payment) {
|
|
closeQRModal();
|
|
showToast(`Payment confirmed! ${payment.aitbc_amount} AITBC credited to your wallet.`, 'success');
|
|
|
|
// Update wallet balance
|
|
updateWalletBalance();
|
|
|
|
// Add to transaction history
|
|
addTransaction('Buy AITBC', `+${payment.aitbc_amount} AITBC`, 'Completed');
|
|
|
|
// Clear form
|
|
document.getElementById('btcAmount').value = '';
|
|
document.getElementById('aitbcAmount').value = '';
|
|
}
|
|
|
|
// Close QR Modal
|
|
function closeQRModal() {
|
|
document.getElementById('qrModal').classList.add('hidden');
|
|
window.currentPaymentId = null;
|
|
}
|
|
|
|
// Mint AITBC (simulated)
|
|
async function mintAITBC(address, amount) {
|
|
try {
|
|
const response = await axios.post(`${BLOCKCHAIN_API}/admin/mintFaucet`, {
|
|
address: address,
|
|
amount: amount
|
|
});
|
|
console.log('Minted AITBC:', response.data);
|
|
} catch (error) {
|
|
console.error('Failed to mint AITBC:', error);
|
|
}
|
|
}
|
|
|
|
// Logout
|
|
async function logout() {
|
|
if (!sessionToken) return;
|
|
|
|
try {
|
|
await axios.post(`${API_BASE}/users/logout`, {}, {
|
|
headers: { 'X-Session-Token': sessionToken }
|
|
});
|
|
} catch (error) {
|
|
console.error('Logout error:', error);
|
|
}
|
|
|
|
// Clear local data
|
|
currentUser = null;
|
|
sessionToken = null;
|
|
walletAddress = null;
|
|
aitbcBalance = 0;
|
|
|
|
// Update UI
|
|
document.getElementById('notLoggedIn').classList.remove('hidden');
|
|
document.getElementById('userProfile').classList.add('hidden');
|
|
document.getElementById('aitbcAddress').textContent = 'Not connected';
|
|
document.getElementById('aitbcBalance').textContent = '0 AITBC';
|
|
|
|
// Update navigation
|
|
document.getElementById('navConnectBtn').classList.remove('hidden');
|
|
document.getElementById('navUserInfo').classList.add('hidden');
|
|
|
|
// Hide trade form, show connect prompt
|
|
document.getElementById('tradeConnectPrompt').classList.remove('hidden');
|
|
document.getElementById('tradeForm').classList.add('hidden');
|
|
|
|
showToast('Logged out successfully');
|
|
}
|
|
|
|
// Load User Balance
|
|
async function loadUserBalance() {
|
|
if (!currentUser || !sessionToken) return;
|
|
|
|
try {
|
|
const response = await axios.get(
|
|
`${API_BASE}/users/${currentUser.user_id}/balance`,
|
|
{ headers: { 'X-Session-Token': sessionToken } }
|
|
);
|
|
|
|
const balance = response.data;
|
|
aitbcBalance = balance.balance;
|
|
document.getElementById('aitbcBalance').textContent = aitbcBalance.toFixed(2);
|
|
} catch (error) {
|
|
console.error('Failed to load balance:', error);
|
|
// Set demo balance for testing
|
|
aitbcBalance = 1000;
|
|
document.getElementById('aitbcBalance').textContent = aitbcBalance.toFixed(2) + ' AITBC';
|
|
}
|
|
}
|
|
|
|
// Update Wallet Balance (legacy)
|
|
async function updateWalletBalance() {
|
|
if (!walletAddress) return;
|
|
|
|
try {
|
|
const response = await axios.get(`${BLOCKCHAIN_API}/getBalance/${walletAddress}`);
|
|
aitbcBalance = response.data.balance;
|
|
document.getElementById('aitbcBalance').textContent = aitbcBalance + ' AITBC';
|
|
} catch (error) {
|
|
// Demo balance
|
|
aitbcBalance = 1000;
|
|
document.getElementById('aitbcBalance').textContent = aitbcBalance + ' AITBC';
|
|
}
|
|
}
|
|
|
|
// Load GPU Offers
|
|
async function loadGPUOffers() {
|
|
console.log('Switch to real GPU offers initiated from:', API_BASE + '/miners/list');
|
|
try {
|
|
const response = await fetch(API_BASE + '/marketplace/offers');
|
|
console.log('API response status:', response.status, 'OK:', response.ok);
|
|
console.log('Response headers:', response.headers);
|
|
const responseText = await response.text();
|
|
console.log('Raw response text:', responseText);
|
|
if (!response.ok) throw new Error(`HTTP error: status ${response.status}, message: ${response.statusText}`);
|
|
const data = JSON.parse(responseText);
|
|
console.log('Parsed API data:', data);
|
|
if (data.gpus && data.gpus.length > 0) {
|
|
displayRealGPUOffers(data.gpus);
|
|
} else {
|
|
console.log('No GPU data from API, falling back to demo offers temporarily.');
|
|
displayDemoOffers();
|
|
}
|
|
} catch (error) {
|
|
console.log('API error details:', error.message, 'Stack:', error.stack);
|
|
console.log('Falling back to demo offers as a temporary measure.');
|
|
displayDemoOffers();
|
|
}
|
|
}
|
|
|
|
// Display GPU Offers
|
|
function displayGPUOffers(offers) {
|
|
const container = document.getElementById('gpuList');
|
|
|
|
if (offers.length === 0) {
|
|
container.innerHTML = '<div class="col-span-full text-center py-12 text-gray-500">No GPU offers available at the moment.</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = offers.map(offer => {
|
|
const attrs = offer.attributes || {};
|
|
return `
|
|
<div class="bg-white rounded-lg shadow-lg p-6 card-hover">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">${attrs.gpu_model || 'GPU'}</h3>
|
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">Available</span>
|
|
</div>
|
|
<div class="space-y-2 text-sm text-gray-600 mb-4">
|
|
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>Memory: ${attrs.gpu_memory_gb || 'N/A'} GB</p>
|
|
<p><i data-lucide="zap" class="w-4 h-4 inline mr-1"></i>CUDA: ${attrs.cuda_version || 'N/A'}</p>
|
|
<p><i data-lucide="cpu" class="w-4 h-4 inline mr-1"></i>Capacity: ${offer.capacity || 1} GPU(s)</p>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-2xl font-bold text-purple-600">${offer.price || '50'} AITBC/hr</span>
|
|
<button onclick="purchaseGPU('${offer.id}')" class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 transition">
|
|
Purchase
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Purchase GPU
|
|
async function purchaseGPU(offerId) {
|
|
if (!walletAddress) {
|
|
showToast('Please connect your wallet first', 'error');
|
|
return;
|
|
}
|
|
|
|
if (aitbcBalance < 100) {
|
|
showToast('Insufficient AITBC balance. Please purchase more tokens.', 'error');
|
|
showSection('trade');
|
|
return;
|
|
}
|
|
|
|
showToast('GPU time purchased successfully!');
|
|
addTransaction('GPU Purchase', '-100 AITBC', 'Completed');
|
|
updateWalletBalance();
|
|
}
|
|
|
|
// Refresh Order Book
|
|
function refreshOrderBook() {
|
|
// Simulate order book refresh
|
|
showToast('Order book refreshed');
|
|
}
|
|
|
|
// Transaction Management
|
|
function addTransaction(type, amount, status) {
|
|
const tbody = document.getElementById('transactionHistory');
|
|
const time = new Date().toLocaleString();
|
|
|
|
const row = document.createElement('tr');
|
|
row.className = 'border-b';
|
|
row.innerHTML = `
|
|
<td class="py-2">${time}</td>
|
|
<td class="py-2">
|
|
<span class="${amount.startsWith('+') ? 'text-green-600' : 'text-red-600'}">${type}</span>
|
|
</td>
|
|
<td class="py-2">${amount}</td>
|
|
<td class="py-2">
|
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-xs">${status}</span>
|
|
</td>
|
|
`;
|
|
|
|
tbody.insertBefore(row, tbody.firstChild);
|
|
}
|
|
|
|
// QR Modal
|
|
function showQRModal() {
|
|
document.getElementById('qrModal').classList.remove('hidden');
|
|
}
|
|
|
|
function closeQRModal() {
|
|
document.getElementById('qrModal').classList.add('hidden');
|
|
}
|
|
|
|
// Toast Notification
|
|
function showToast(message, type = 'success') {
|
|
const toast = document.createElement('div');
|
|
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg transform transition-transform duration-300 ${
|
|
type === 'error' ? 'bg-red-500' : 'bg-green-500'
|
|
} text-white`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
setTimeout(() => {
|
|
toast.style.transform = 'translateY(0)';
|
|
}, 100);
|
|
|
|
setTimeout(() => {
|
|
toast.style.transform = 'translateY(100%)';
|
|
setTimeout(() => toast.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
</script>
|
|
|
|
<!-- Admin Access (hidden) -->
|
|
<div style="position: fixed; bottom: 10px; left: 10px; opacity: 0.3; font-size: 12px;">
|
|
<a href="/Exchange/admin/" style="color: #666;">Admin</a>
|
|
</div>
|
|
|
|
<script>
|
|
// GPU Integration
|
|
function displayDemoOffers() {
|
|
console.log('Exchange script loaded, displayDemoOffers defined.');
|
|
const container = document.getElementById('gpuList');
|
|
if (!container) return;
|
|
container.innerHTML = `
|
|
<div class="bg-white rounded-lg shadow-lg p-6 card-hover">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">Demo RTX 4090</h3>
|
|
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm">Demo</span>
|
|
</div>
|
|
<div class="space-y-2 text-sm text-gray-600 mb-4">
|
|
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>Memory: 24 GB</p>
|
|
<p><i data-lucide="zap" class="w-4 h-4 inline mr-1"></i>CUDA: 12.4</p>
|
|
<p><i data-lucide="cpu" class="w-4 h-4 inline mr-1"></i>Concurrency: 4 jobs</p>
|
|
<p><i data-lucide="map-pin" class="w-4 h-4 inline mr-1"></i>Region: EU-West</p>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-2xl font-bold text-purple-600">42 AITBC/hr</span>
|
|
<button onclick="purchaseGPU('demo-rtx4090')" class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 transition">Purchase</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
if (window.lucide) {
|
|
window.lucide.createIcons();
|
|
}
|
|
}
|
|
|
|
async function loadRealGPUOffers() {
|
|
console.log('Switching to production: loadRealGPUOffers called with API_BASE:', API_BASE + '/miners/list');
|
|
try {
|
|
const response = await fetch(API_BASE + '/marketplace/offers');
|
|
console.log('Production API response status:', response.status);
|
|
if (!response.ok) throw new Error(`HTTP error: status ${response.status}`);
|
|
const data = await response.json();
|
|
console.log('Production API data:', data);
|
|
if (data.gpus && data.gpus.length > 0) {
|
|
displayRealGPUOffers(data.gpus);
|
|
} else {
|
|
throw new Error('No GPU data from production API');
|
|
}
|
|
} catch (error) {
|
|
console.log('Production API error:', error.message, 'Using demo offers as fallback.');
|
|
displayDemoOffers();
|
|
}
|
|
}
|
|
|
|
function displayRealGPUOffers(gpus) {
|
|
const container = document.getElementById('gpuList');
|
|
container.innerHTML = '';
|
|
|
|
gpus.forEach(gpu => {
|
|
const gpuCard = `
|
|
<div class="bg-white rounded-lg shadow-lg p-6 card-hover">
|
|
<div class="flex justify-between items-start mb-4">
|
|
<h3 class="text-lg font-semibold">${gpu.capabilities.gpu.model}</h3>
|
|
<span class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">Available</span>
|
|
</div>
|
|
<div class="space-y-2 text-sm text-gray-600 mb-4">
|
|
<p><i data-lucide="monitor" class="w-4 h-4 inline mr-1"></i>Memory: ${gpu.capabilities.gpu.memory_gb} GB</p>
|
|
<p><i data-lucide="zap" class="w-4 h-4 inline mr-1"></i>CUDA: ${gpu.capabilities.gpu.cuda_version}</p>
|
|
<p><i data-lucide="cpu" class="w-4 h-4 inline mr-1"></i>Concurrency: ${gpu.concurrency}</p>
|
|
<p><i data-lucide="map-pin" class="w-4 h-4 inline mr-1"></i>Region: ${gpu.region}</p>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-2xl font-bold text-purple-600">50 AITBC/hr</span>
|
|
<button onclick="purchaseGPU('${gpu.id}')" class="bg-purple-600 text-white px-4 py-2 rounded hover:bg-purple-700 transition">
|
|
Purchase
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
container.innerHTML += gpuCard;
|
|
});
|
|
|
|
lucide.createIcons();
|
|
}
|
|
|
|
// Override the loadGPUOffers function
|
|
const originalLoadGPUOffers = loadGPUOffers;
|
|
loadGPUOffers = loadRealGPUOffers;
|
|
</script>
|
|
<script>
|
|
console.log('Exchange script loaded, displayDemoOffers defined.');
|
|
</script>
|
|
<script src="/assets/js/global-header.js"></script>
|
|
</body>
|
|
</html>
|