Autosave: 20260220-053733
This commit is contained in:
parent
79fe775dc4
commit
3955cd1acf
@ -3,8 +3,8 @@ require_once __DIR__ . '/../db/config.php';
|
||||
require_once __DIR__ . '/../includes/lang.php';
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
|
||||
// Force admin to be Chinese
|
||||
$lang = 'zh';
|
||||
// Use site default language
|
||||
$lang = $_SESSION['lang'] ?? 'en';
|
||||
|
||||
// Admin check
|
||||
$admin = null;
|
||||
|
||||
@ -21,31 +21,31 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
$error = '请输入账号和密码';
|
||||
} else {
|
||||
$stmt = db()->prepare("SELECT * FROM admins WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$admin = $stmt->fetch();
|
||||
|
||||
if ($admin && password_verify($password, $admin['password_hash'])) {
|
||||
$_SESSION['admin_id'] = $admin['id'];
|
||||
$_SESSION['admin_username'] = $admin['username'];
|
||||
$_SESSION['admin_role'] = $admin['role'];
|
||||
header('Location: /admin/index.php');
|
||||
exit;
|
||||
if (empty($username) || empty($password)) {
|
||||
$error = __("fill_full_info");
|
||||
} else {
|
||||
$error = '管理员账号或密码错误';
|
||||
$stmt = db()->prepare("SELECT * FROM admins WHERE username = ?");
|
||||
$stmt->execute([$username]);
|
||||
$admin = $stmt->fetch();
|
||||
|
||||
if ($admin && password_verify($password, $admin["password_hash"])) {
|
||||
$_SESSION["admin_id"] = $admin["id"];
|
||||
$_SESSION["admin_username"] = $admin["username"];
|
||||
$_SESSION["admin_role"] = $admin["role"];
|
||||
header("Location: /admin/index.php");
|
||||
exit;
|
||||
} else {
|
||||
$error = __("invalid_account_pwd");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="<?= $lang ?>">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>管理员登录 - <?= $site_name ?></title>
|
||||
<title><?= __("login") ?> - <?= $site_name ?></title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<?php if ($site_favicon): ?>
|
||||
@ -90,7 +90,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
<?php else: ?>
|
||||
<i class="bi bi-shield-lock-fill me-2"></i>
|
||||
<?php endif; ?>
|
||||
<div>后台管理系统</div>
|
||||
<div><?= __("admin_panel") ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($error): ?>
|
||||
@ -101,14 +101,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
<form method="POST">
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted">管理员账号</label>
|
||||
<label class="form-label small text-muted"><?= __("account") ?></label>
|
||||
<input type="text" name="username" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label small text-muted">登录密码</label>
|
||||
<label class="form-label small text-muted"><?= __("password") ?></label>
|
||||
<input type="password" name="password" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100 rounded-pill">进入后台</button>
|
||||
<button type="submit" class="btn btn-primary w-100 rounded-pill"><?= __("login") ?></button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
126
api/swap.php
Normal file
126
api/swap.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
require_once __DIR__ . "/../db/config.php";
|
||||
require_once __DIR__ . "/../includes/lang.php";
|
||||
session_start();
|
||||
|
||||
header("Content-Type: application/json");
|
||||
|
||||
$db = db();
|
||||
$user_id = $_SESSION["user_id"] ?? null;
|
||||
|
||||
if (!$user_id) {
|
||||
echo json_encode(["success" => false, "error" => __("unauthorized")]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$from_coin = $_POST["from_coin"] ?? "";
|
||||
$to_coin = $_POST["to_coin"] ?? "";
|
||||
$amount = (float)($_POST["amount"] ?? 0);
|
||||
|
||||
if ($amount <= 0 || !$from_coin || !$to_coin || $from_coin === $to_coin) {
|
||||
echo json_encode(["success" => false, "error" => __("invalid_amount")]);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Robust price fetching using OKX as primary and CoinCap as fallback
|
||||
*/
|
||||
function getPrice($symbol) {
|
||||
if ($symbol === "USDT") return 1.0;
|
||||
|
||||
// Primary: OKX
|
||||
$pair = strtoupper($symbol) . "-USDT";
|
||||
$url = "https://www.okx.com/api/v5/market/ticker?instId=" . $pair;
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0"); // OKX might require UA
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response) {
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data["data"][0]["last"])) {
|
||||
return (float)$data["data"][0]["last"];
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: CoinCap
|
||||
$slugMap = [
|
||||
"BTC" => "bitcoin", "ETH" => "ethereum", "BNB" => "binance-coin",
|
||||
"SOL" => "solana", "XRP" => "ripple", "ADA" => "cardano",
|
||||
"DOGE" => "dogecoin", "DOT" => "polkadot", "MATIC" => "polygon",
|
||||
"AVAX" => "avalanche", "LINK" => "chainlink", "SHIB" => "shiba-inu",
|
||||
"TRX" => "tron", "BCH" => "bitcoin-cash", "LTC" => "litecoin",
|
||||
"UNI" => "uniswap"
|
||||
];
|
||||
$slug = $slugMap[strtoupper($symbol)] ?? strtolower($symbol);
|
||||
$url = "https://api.coincap.io/v2/assets/" . $slug;
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($response) {
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data["data"]["priceUsd"])) {
|
||||
return (float)$data["data"]["priceUsd"];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$from_price = getPrice($from_coin);
|
||||
$to_price = getPrice($to_coin);
|
||||
|
||||
if ($from_price === null || $to_price === null) {
|
||||
echo json_encode(["success" => false, "error" => "Failed to fetch exchange rate (Provider issue)"]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$rate = $from_price / $to_price;
|
||||
$target_amount = $amount * $rate;
|
||||
|
||||
$db->beginTransaction();
|
||||
try {
|
||||
// Check balance of from_coin
|
||||
$stmt = $db->prepare("SELECT available FROM user_balances WHERE user_id = ? AND symbol = ?");
|
||||
$stmt->execute([$user_id, $from_coin]);
|
||||
$from_balance = $stmt->fetchColumn() ?: 0;
|
||||
|
||||
if ($from_balance < $amount) {
|
||||
throw new Exception(__("insufficient_balance"));
|
||||
}
|
||||
|
||||
// Deduct from_coin
|
||||
$db->prepare("UPDATE user_balances SET available = available - ? WHERE user_id = ? AND symbol = ?")
|
||||
->execute([$amount, $user_id, $from_coin]);
|
||||
|
||||
// Add to_coin
|
||||
$stmt = $db->prepare("SELECT id FROM user_balances WHERE user_id = ? AND symbol = ?");
|
||||
$stmt->execute([$user_id, $to_coin]);
|
||||
if (!$stmt->fetch()) {
|
||||
$db->prepare("INSERT INTO user_balances (user_id, symbol, available) VALUES (?, ?, 0)")
|
||||
->execute([$user_id, $to_coin]);
|
||||
}
|
||||
$db->prepare("UPDATE user_balances SET available = available + ? WHERE user_id = ? AND symbol = ?")
|
||||
->execute([$target_amount, $user_id, $to_coin]);
|
||||
|
||||
// Record exchange
|
||||
$stmt = $db->prepare("INSERT INTO exchange_records (user_id, from_symbol, to_symbol, from_amount, to_amount, rate) VALUES (?, ?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$user_id, $from_coin, $to_coin, $amount, $target_amount, $rate]);
|
||||
|
||||
// Record transaction
|
||||
$db->prepare("INSERT INTO transactions (user_id, type, amount, symbol, status) VALUES (?, 'swap', ?, ?, 'completed')")
|
||||
->execute([$user_id, $amount, $from_coin]);
|
||||
|
||||
$db->commit();
|
||||
echo json_encode(["success" => true, "target_amount" => $target_amount]);
|
||||
} catch (Exception $e) {
|
||||
$db->rollBack();
|
||||
echo json_encode(["success" => false, "error" => $e->getMessage()]);
|
||||
}
|
||||
@ -69,10 +69,14 @@ include __DIR__ . '/../includes/header.php';
|
||||
<input type="password" name="password" class="form-control bg-black border-secondary text-white py-3 px-4 rounded-4" style="background: #0b0e11 !important; border-color: #2b3139 !important;" placeholder="<?= __('password') ?>" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold rounded-pill mb-4 shadow-primary"><?= __('login') ?></button>
|
||||
<div class="d-flex gap-3 mb-4">
|
||||
<button type="submit" class="btn btn-primary flex-grow-1 py-3 fw-bold rounded-pill shadow-primary"><?= __('login') ?></button>
|
||||
<a href="/auth/register.php" class="btn btn-outline-secondary flex-grow-1 py-3 fw-bold rounded-pill d-flex align-items-center justify-content-center" style="border-color: #2b3139; color: #fff;"><?= __('register') ?></a>
|
||||
</div>
|
||||
|
||||
<div class="text-center small text-muted">
|
||||
<?= __('no_account') ?> <a href="/auth/register.php" class="text-primary fw-bold text-decoration-none"><?= __('register') ?></a>
|
||||
<div class="text-center p-3 rounded-4 bg-black bg-opacity-25 border border-secondary border-opacity-25">
|
||||
<span class="small text-muted"><?= __('no_account') ?></span>
|
||||
<a href="/auth/register.php" class="ms-1 text-primary fw-bold text-decoration-none small"><?= __('register_now') ?></a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -197,19 +197,59 @@ if (isset($_SESSION['user_id'])) {
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
padding: 10px 20px;
|
||||
border-radius: 6px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-login:hover {
|
||||
background: #222;
|
||||
background: rgba(255,255,255,0.05);
|
||||
}
|
||||
.btn-register {
|
||||
background: var(--primary);
|
||||
color: #fff !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 98, 255, 0.2);
|
||||
}
|
||||
.btn-register:hover {
|
||||
background: #0056e0;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(0, 98, 255, 0.3);
|
||||
}
|
||||
@media (max-width: 576px) {
|
||||
.header-right {
|
||||
gap: 8px;
|
||||
}
|
||||
.auth-btns {
|
||||
background: rgba(255,255,255,0.08);
|
||||
padding: 3px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
.auth-btns a {
|
||||
padding: 7px 14px;
|
||||
font-size: 13px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.btn-login {
|
||||
background: transparent;
|
||||
color: rgba(255,255,255,0.8) !important;
|
||||
}
|
||||
.btn-login:active {
|
||||
background: rgba(255,255,255,0.1);
|
||||
}
|
||||
.btn-register {
|
||||
background: var(--primary);
|
||||
color: #fff !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 98, 255, 0.3);
|
||||
}
|
||||
.lang-switcher span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* User Dropdown */
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
|
||||
$lang = $_SESSION['lang'] ?? 'zh';
|
||||
$lang = $_SESSION['lang'] ?? 'en';
|
||||
if (isset($_GET['lang'])) {
|
||||
$lang = $_GET['lang'] === 'zh' ? 'zh' : 'en';
|
||||
$_SESSION['lang'] = $lang;
|
||||
@ -63,6 +63,8 @@ $translations = [
|
||||
'confirm' => '确 认',
|
||||
'buy' => '买',
|
||||
'sell' => '卖',
|
||||
'confirm_swap' => '确认兑换',
|
||||
'cancel' => '取消',
|
||||
'approx' => '约等于',
|
||||
'cookie_policy_title' => '隐私政策',
|
||||
'cookie_policy_content' => '<h3>1. 什么是隐私数据?</h3><p>隐私数据是您访问网站时存储在您的计算机或移动设备上的信息。它们广泛用于使网站运行或更高效地运行。</p>',
|
||||
@ -576,6 +578,8 @@ $translations = [
|
||||
'confirm' => 'Confirm',
|
||||
'buy' => 'Buy',
|
||||
'sell' => 'Sell',
|
||||
'confirm_swap' => 'Confirm Swap',
|
||||
'cancel' => 'Cancel',
|
||||
'approx' => 'Approx.',
|
||||
'cookie_policy_title' => 'Cookie Policy',
|
||||
'cookie_policy_content' => '<h3>1. What are Cookies?</h3><p>Cookies are small text files stored on your device when you visit a website. They are used to make websites work efficiently.</p>',
|
||||
|
||||
322
swap.php
322
swap.php
@ -1,13 +1,14 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/lang.php';
|
||||
require_once __DIR__ . '/includes/header.php';
|
||||
require_once __DIR__ . "/includes/lang.php";
|
||||
require_once __DIR__ . "/includes/header.php";
|
||||
|
||||
$usdt_balance = 0;
|
||||
$balances = [];
|
||||
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;
|
||||
$stmt = db()->prepare("SELECT symbol, available FROM user_balances WHERE user_id = ?");
|
||||
$stmt->execute([$user["id"]]);
|
||||
while ($row = $stmt->fetch()) {
|
||||
$balances[$row["symbol"]] = (float)$row["available"];
|
||||
}
|
||||
}
|
||||
?>
|
||||
<div class="container py-4 py-md-5 d-flex justify-content-center">
|
||||
@ -15,28 +16,28 @@ if ($user) {
|
||||
<div class="card-body p-3 p-md-4">
|
||||
<h3 class="fw-bold mb-4 text-white d-flex align-items-center gap-2">
|
||||
<i class="bi bi-lightning-charge-fill text-primary"></i>
|
||||
<?= __('swap') ?>
|
||||
<?= __("swap") ?>
|
||||
</h3>
|
||||
|
||||
<!-- From -->
|
||||
<div class="p-3 p-md-4 mb-2 rounded-4" style="background: #161a1e; border: 1px solid #2b3139;">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="text-white opacity-50 small fw-bold text-uppercase" style="letter-spacing: 1px;"><?= __('from') ?></span>
|
||||
<span class="text-white opacity-50 small"><?= __('balance') ?>: <span class="text-white fw-bold"><?= number_format($usdt_balance, 2) ?></span></span>
|
||||
<span class="text-white opacity-50 small fw-bold text-uppercase" style="letter-spacing: 1px;"><?= __("from") ?></span>
|
||||
<span class="text-white opacity-50 small"><?= __("balance") ?>: <span class="text-white fw-bold" id="from-balance-val">0.00</span></span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="number" id="from-amount" class="form-control bg-transparent border-0 text-white p-0 shadow-none w-50 fw-bold" placeholder="0.00" style="color: #fff !important; font-size: clamp(24px, 5vw, 32px) !important;">
|
||||
<div class="ms-auto dropdown">
|
||||
<div class="d-flex align-items-center bg-dark p-2 rounded-pill px-2 px-md-3 border border-secondary cursor-pointer shadow-sm dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<img src="<?php echo getCoinIcon('USDT'); ?>" width="24" height="24" class="me-2 rounded-circle" id="from-coin-img" alt="USDT" onerror="handleIconError(this, 'USDT')">
|
||||
<span class="fw-bold text-white small" id="from-coin-symbol" data-symbol="USDT"><?= __('USDT') ?></span>
|
||||
<img src="<?php echo getCoinIcon("USDT"); ?>" width="24" height="24" class="me-2 rounded-circle" id="from-coin-img" alt="USDT">
|
||||
<span class="fw-bold text-white small" id="from-coin-symbol" data-symbol="USDT">USDT</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-dark bg-dark border-secondary rounded-4 shadow-lg p-2" style="max-height: 300px; overflow-y: auto;">
|
||||
<?php
|
||||
$swap_coins = ['BTC', 'ETH', 'USDT', 'BNB', 'SOL', 'XRP', 'ADA', 'DOGE', 'DOT', 'MATIC', 'LINK', 'SHIB'];
|
||||
$swap_coins = ["BTC", "ETH", "USDT", "BNB", "SOL", "XRP", "ADA", "DOGE", "DOT", "MATIC", "LINK", "SHIB"];
|
||||
foreach($swap_coins as $sc): ?>
|
||||
<li><a class="dropdown-item rounded-3 py-2 d-flex align-items-center gap-2" href="#" onclick="selectCoin('from', '<?= $sc ?>', '<?= __($sc) ?>', '<?= getCoinIcon($sc) ?>')">
|
||||
<img src="<?= getCoinIcon($sc) ?>" width="20" height="20" alt="<?= $sc ?>" onerror="handleIconError(this, '<?= $sc ?>')"> <?= __($sc) ?>
|
||||
<li><a class="dropdown-item rounded-3 py-2 d-flex align-items-center gap-2" href="#" onclick="selectCoin('from', '<?= $sc ?>', '<?= $sc ?>', '<?= getCoinIcon($sc) ?>')">
|
||||
<img src="<?= getCoinIcon($sc) ?>" width="20" height="20" alt="<?= $sc ?>"> <?= $sc ?>
|
||||
</a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
@ -54,20 +55,20 @@ if ($user) {
|
||||
<!-- To -->
|
||||
<div class="p-3 p-md-4 mt-n1 mb-4 rounded-4" style="background: #161a1e; border: 1px solid #2b3139;">
|
||||
<div class="d-flex justify-content-between mb-3">
|
||||
<span class="text-white opacity-50 small fw-bold text-uppercase" style="letter-spacing: 1px;"><?= __('to') ?></span>
|
||||
<span class="text-white opacity-50 small"><?= __('balance') ?>: <span class="text-white fw-bold">0.00</span></span>
|
||||
<span class="text-white opacity-50 small fw-bold text-uppercase" style="letter-spacing: 1px;"><?= __("to") ?></span>
|
||||
<span class="text-white opacity-50 small"><?= __("balance") ?>: <span class="text-white fw-bold" id="to-balance-val">0.00</span></span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center">
|
||||
<input type="number" id="to-amount" class="form-control bg-transparent border-0 text-white p-0 shadow-none w-50 fw-bold" placeholder="0.00" readonly style="color: #fff !important; opacity: 1; font-size: clamp(24px, 5vw, 32px) !important;">
|
||||
<div class="ms-auto dropdown">
|
||||
<div class="d-flex align-items-center bg-dark p-2 rounded-pill px-2 px-md-3 border border-secondary cursor-pointer shadow-sm dropdown-toggle" data-bs-toggle="dropdown">
|
||||
<img src="<?php echo getCoinIcon('BTC'); ?>" width="24" height="24" class="me-2 rounded-circle" id="to-coin-img" alt="BTC" onerror="handleIconError(this, 'BTC')">
|
||||
<span class="fw-bold text-white small" id="to-coin-symbol" data-symbol="BTC"><?= __('BTC') ?></span>
|
||||
<img src="<?php echo getCoinIcon("BTC"); ?>" width="24" height="24" class="me-2 rounded-circle" id="to-coin-img" alt="BTC">
|
||||
<span class="fw-bold text-white small" id="to-coin-symbol" data-symbol="BTC">BTC</span>
|
||||
</div>
|
||||
<ul class="dropdown-menu dropdown-menu-dark bg-dark border-secondary rounded-4 shadow-lg p-2" style="max-height: 300px; overflow-y: auto;">
|
||||
<?php foreach($swap_coins as $sc): ?>
|
||||
<li><a class="dropdown-item rounded-3 py-2 d-flex align-items-center gap-2" href="#" onclick="selectCoin('to', '<?= $sc ?>', '<?= __($sc) ?>', '<?= getCoinIcon($sc) ?>')">
|
||||
<img src="<?= getCoinIcon($sc) ?>" width="20" height="20" alt="<?= $sc ?>" onerror="handleIconError(this, '<?= $sc ?>')"> <?= __($sc) ?>
|
||||
<li><a class="dropdown-item rounded-3 py-2 d-flex align-items-center gap-2" href="#" onclick="selectCoin('to', '<?= $sc ?>', '<?= $sc ?>', '<?= getCoinIcon($sc) ?>')">
|
||||
<img src="<?= getCoinIcon($sc) ?>" width="20" height="20" alt="<?= $sc ?>"> <?= $sc ?>
|
||||
</a></li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
@ -77,72 +78,254 @@ if ($user) {
|
||||
|
||||
<div class="mb-4 small px-3 py-3 rounded-4" style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05);">
|
||||
<div class="d-flex justify-content-between text-white opacity-50 mb-2">
|
||||
<span class="fw-medium"><?= __('rate') ?></span>
|
||||
<span class="fw-medium"><?= __("rate") ?></span>
|
||||
<span id="swap-rate" class="text-white fw-bold">--</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between text-white opacity-50 mb-2">
|
||||
<span class="fw-medium"><?= __('price_impact') ?></span>
|
||||
<span class="fw-medium"><?= __("price_impact") ?></span>
|
||||
<span class="text-success fw-bold">< 0.01%</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between text-white opacity-50">
|
||||
<span class="fw-medium"><?= __('slippage') ?></span>
|
||||
<span class="fw-medium"><?= __("slippage") ?></span>
|
||||
<span class="text-white fw-bold">0.5%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary w-100 py-3 rounded-pill fw-bold fs-5 shadow-primary transition-all active-scale" style="background: linear-gradient(135deg, #0062ff, #00d2ff); border: none;"><?= __('swap_now') ?></button>
|
||||
<button id="swap-btn" onclick="executeSwap()" class="btn btn-primary w-100 py-3 rounded-pill fw-bold fs-5 shadow-primary transition-all active-scale" style="background: linear-gradient(135deg, #0062ff, #00d2ff); border: none;"><?= __("swap_now") ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const fromInput = document.getElementById('from-amount');
|
||||
const toInput = document.getElementById('to-amount');
|
||||
const rateEl = document.getElementById('swap-rate');
|
||||
const approxLabel = '<?= __('approx') ?>';
|
||||
let rate = 1 / 64234.50;
|
||||
const fromInput = document.getElementById("from-amount");
|
||||
const toInput = document.getElementById("to-amount");
|
||||
const rateEl = document.getElementById("swap-rate");
|
||||
const swapBtn = document.getElementById("swap-btn");
|
||||
const approxLabel = "<?= __("approx") ?>";
|
||||
const userBalances = <?= json_encode($balances) ?>;
|
||||
|
||||
function selectCoin(type, symbol, displayName, icon) {
|
||||
document.getElementById(type + '-coin-symbol').innerText = displayName;
|
||||
document.getElementById(type + '-coin-symbol').dataset.symbol = symbol;
|
||||
document.getElementById(type + '-coin-img').src = icon;
|
||||
// Update rate mockly
|
||||
if (symbol === 'BTC') rate = 1 / 64234.50;
|
||||
else if (symbol === 'ETH') rate = 1 / 3456.20;
|
||||
else rate = 1;
|
||||
let fromPrice = 1;
|
||||
let toPrice = 1;
|
||||
|
||||
async function fetchPrice(symbol) {
|
||||
if (symbol === "USDT") return 1;
|
||||
|
||||
// Fallback chain for frontend price fetching
|
||||
try {
|
||||
// Try CoinCap first as it's CORS-friendly
|
||||
const resCC = await fetch(`https://api.coincap.io/v2/assets/${symbol.toLowerCase()}`);
|
||||
if (resCC.ok) {
|
||||
const dataCC = await resCC.json();
|
||||
if (dataCC.data && dataCC.data.priceUsd) return parseFloat(dataCC.data.priceUsd);
|
||||
}
|
||||
} catch (e) { console.log("CoinCap error, trying OKX..."); }
|
||||
|
||||
try {
|
||||
// Try OKX (might have CORS issues on some browsers)
|
||||
const response = await fetch(`https://www.okx.com/api/v5/market/ticker?instId=${symbol}-USDT`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.data && data.data[0]) return parseFloat(data.data[0].last);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Price fetch error:", e);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function updateRates() {
|
||||
const fromSymbol = document.getElementById("from-coin-symbol").dataset.symbol;
|
||||
const toSymbol = document.getElementById("to-coin-symbol").dataset.symbol;
|
||||
|
||||
fromPrice = await fetchPrice(fromSymbol);
|
||||
toPrice = await fetchPrice(toSymbol);
|
||||
|
||||
updateCalculation();
|
||||
}
|
||||
|
||||
function switchCoins() {
|
||||
const fromSymbol = document.getElementById('from-coin-symbol').dataset.symbol || 'USDT';
|
||||
const fromName = document.getElementById('from-coin-symbol').innerText;
|
||||
const fromIcon = document.getElementById('from-coin-img').src;
|
||||
const toSymbol = document.getElementById('to-coin-symbol').dataset.symbol || 'BTC';
|
||||
const toName = document.getElementById('to-coin-symbol').innerText;
|
||||
const toIcon = document.getElementById('to-coin-img').src;
|
||||
function selectCoin(type, symbol, displayName, icon) {
|
||||
document.getElementById(type + "-coin-symbol").innerText = displayName;
|
||||
document.getElementById(type + "-coin-symbol").dataset.symbol = symbol;
|
||||
document.getElementById(type + "-coin-img").src = icon;
|
||||
|
||||
selectCoin('from', toSymbol, toName, toIcon);
|
||||
selectCoin('to', fromSymbol, fromName, fromIcon);
|
||||
const bal = userBalances[symbol] || 0;
|
||||
document.getElementById(type + "-balance-val").innerText = bal.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 4});
|
||||
|
||||
updateRates();
|
||||
}
|
||||
|
||||
function switchCoins() {
|
||||
const fromSymbol = document.getElementById("from-coin-symbol").dataset.symbol;
|
||||
const fromName = document.getElementById("from-coin-symbol").innerText;
|
||||
const fromIcon = document.getElementById("from-coin-img").src;
|
||||
|
||||
const toSymbol = document.getElementById("to-coin-symbol").dataset.symbol;
|
||||
const toName = document.getElementById("to-coin-symbol").innerText;
|
||||
const toIcon = document.getElementById("to-coin-img").src;
|
||||
|
||||
selectCoin("from", toSymbol, toName, toIcon);
|
||||
selectCoin("to", fromSymbol, fromName, fromIcon);
|
||||
}
|
||||
|
||||
function updateCalculation() {
|
||||
const val = parseFloat(fromInput.value) || 0;
|
||||
const rate = fromPrice / toPrice;
|
||||
toInput.value = (val * rate).toFixed(8);
|
||||
const fromName = document.getElementById('from-coin-symbol').innerText;
|
||||
const toName = document.getElementById('to-coin-symbol').innerText;
|
||||
rateEl.innerText = `1 ${toName} ${approxLabel} ${(1/rate).toLocaleString()} ${fromName}`;
|
||||
|
||||
const fromName = document.getElementById("from-coin-symbol").innerText;
|
||||
const toName = document.getElementById("to-coin-symbol").innerText;
|
||||
|
||||
if (fromPrice && toPrice) {
|
||||
rateEl.innerText = `1 ${fromName} ${approxLabel} ${(rate).toFixed(8)} ${toName}`;
|
||||
} else {
|
||||
rateEl.innerText = "--";
|
||||
}
|
||||
}
|
||||
|
||||
fromInput.addEventListener('input', updateCalculation);
|
||||
async function executeSwap() {
|
||||
const amount = parseFloat(fromInput.value);
|
||||
const fromCoin = document.getElementById("from-coin-symbol").dataset.symbol;
|
||||
const toCoin = document.getElementById("to-coin-symbol").dataset.symbol;
|
||||
|
||||
if (!amount || amount <= 0) {
|
||||
Swal.fire({
|
||||
icon: 'warning',
|
||||
title: '<?= __("enter_amount") ?>',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (amount > (userBalances[fromCoin] || 0)) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: '<?= __("insufficient_balance") ?>',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Animate switch icon
|
||||
document.querySelector('.bg-primary.rounded-circle').addEventListener('click', function() {
|
||||
this.style.transform = this.style.transform === 'rotate(180deg)' ? 'rotate(0deg)' : 'rotate(180deg)';
|
||||
// Confirmation Modal
|
||||
const confirmResult = await Swal.fire({
|
||||
title: '<?= __("confirm_swap") ?>',
|
||||
html: `<div class="p-3 text-start">
|
||||
<div class="mb-2 d-flex justify-content-between"><span><?= __("from") ?>:</span> <span class="fw-bold text-primary">${amount} ${fromCoin}</span></div>
|
||||
<div class="mb-0 d-flex justify-content-between"><span><?= __("to") ?>:</span> <span class="fw-bold text-success">${toInput.value} ${toCoin}</span></div>
|
||||
</div>`,
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: '<?= __("confirm") ?>',
|
||||
cancelButtonText: '<?= __("cancel") ?>',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
cancelButtonColor: '#2b3139',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg',
|
||||
confirmButton: 'rounded-pill px-4',
|
||||
cancelButton: 'rounded-pill px-4'
|
||||
}
|
||||
});
|
||||
|
||||
if (!confirmResult.isConfirmed) return;
|
||||
|
||||
Swal.fire({
|
||||
title: 'Processing...',
|
||||
html: 'Please wait while we complete your exchange',
|
||||
allowOutsideClick: false,
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
didOpen: () => {
|
||||
Swal.showLoading();
|
||||
},
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
|
||||
swapBtn.disabled = true;
|
||||
const originalText = swapBtn.innerText;
|
||||
swapBtn.innerText = "Processing...";
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("from_coin", fromCoin);
|
||||
formData.append("to_coin", toCoin);
|
||||
formData.append("amount", amount);
|
||||
|
||||
try {
|
||||
const response = await fetch("api/swap.php", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
});
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
await Swal.fire({
|
||||
icon: 'success',
|
||||
title: '<?= __("success") ?>',
|
||||
text: 'Your exchange has been completed successfully!',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
location.reload();
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: '<?= __("failed") ?>',
|
||||
text: result.error || 'Unknown error occurred',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
swapBtn.disabled = false;
|
||||
swapBtn.innerText = originalText;
|
||||
}
|
||||
} catch (e) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: '<?= __("request_failed") ?>',
|
||||
background: '#161a1e',
|
||||
color: '#fff',
|
||||
confirmButtonColor: '#0062ff',
|
||||
customClass: {
|
||||
popup: 'rounded-4 border border-secondary shadow-lg'
|
||||
}
|
||||
});
|
||||
swapBtn.disabled = false;
|
||||
swapBtn.innerText = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
fromInput.addEventListener("input", updateCalculation);
|
||||
|
||||
// Initial setup
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
selectCoin("from", "USDT", "USDT", "<?php echo getCoinIcon("USDT"); ?>");
|
||||
selectCoin("to", "BTC", "BTC", "<?php echo getCoinIcon("BTC"); ?>");
|
||||
});
|
||||
|
||||
// Initialize symbols for JS
|
||||
document.getElementById('from-coin-symbol').dataset.symbol = 'USDT';
|
||||
document.getElementById('to-coin-symbol').dataset.symbol = 'BTC';
|
||||
// Animate switch icon
|
||||
document.querySelector(".bg-primary.rounded-circle").addEventListener("click", function() {
|
||||
this.style.transform = this.style.transform === "rotate(180deg)" ? "rotate(0deg)" : "rotate(180deg)";
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@ -150,7 +333,28 @@ document.getElementById('to-coin-symbol').dataset.symbol = 'BTC';
|
||||
box-shadow: 0 10px 25px rgba(0, 98, 255, 0.4);
|
||||
}
|
||||
.cursor-pointer { cursor: pointer; }
|
||||
#swap-rate { font-family: 'Roboto Mono', monospace; }
|
||||
#swap-rate { font-family: "Roboto Mono", monospace; }
|
||||
.active-scale:active { transform: scale(0.98); }
|
||||
|
||||
/* Beautiful SweetAlert2 Customization */
|
||||
.swal2-popup.rounded-4 {
|
||||
border-radius: 24px !important;
|
||||
padding: 2rem !important;
|
||||
background: #161a1e !important;
|
||||
}
|
||||
.swal2-title {
|
||||
font-size: 1.5rem !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
.swal2-confirm.rounded-pill, .swal2-cancel.rounded-pill {
|
||||
padding: 0.8rem 2rem !important;
|
||||
font-size: 0.9rem !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.swal2-icon {
|
||||
border-width: 2px !important;
|
||||
}
|
||||
</style>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<?php require_once __DIR__ . "/includes/footer.php"; ?>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user