PHP WebShell
Текущая директория: /var/www/bitcardoApp/user/dashboard
Просмотр файла: index.php
<?php
//user/dashboard/index.php
include '../common/header.php';
include '../../models/dashboard/index.php';
include_once '../../models/crypto/create_address_helper.php';
include_once '../../models/crypto/create_btc_user_address.php';
include_once '../../models/crypto/create_sol_user_address.php';
include_once '../../models/crypto/create_tron_wallet.php';
include '../../models/dashboard/wallets.php';
?>
<style>
.bc-actionbar-wrap{ width:100%; }
.bc-actionbar{
display:flex;
flex-wrap:wrap;
gap:14px;
align-items:center;
}
/* Default (desktop/tablet): 4 on a row */
.bc-actionbtn{
min-width: 100px; /* keeps them stable on most desktops */
display:flex;
align-items:center;
justify-content:center;
gap:10px;
height:54px;
padding:0 18px;
border-radius:14px;
text-decoration:none;
color:#fff;
font-weight:800;
letter-spacing:.2px;
/* very little shadow */
box-shadow: 0 2px 6px rgba(0,0,0,.10);
border: 1px solid rgba(255,255,255,.22);
position:relative;
overflow:hidden;
user-select:none;
}
.bc-actionbtn:active{ transform: translateY(1px); }
.bc-actionbtn:hover{ color:#fff; filter: brightness(1.01); }
.bc-ico{
width:34px;
height:34px;
border-radius:10px;
display:grid;
place-items:center;
background: rgba(255,255,255,.16);
border: 1px solid rgba(255,255,255,.18);
backdrop-filter: blur(4px);
}
.bc-text{ font-size:16px; line-height:1; }
/* subtle sheen band */
.bc-actionbtn::after{
content:"";
position:absolute;
inset:-40% -20%;
background: linear-gradient(115deg,
rgba(255,255,255,0) 20%,
rgba(255,255,255,.10) 38%,
rgba(255,255,255,0) 55%);
transform: rotate(10deg);
pointer-events:none;
opacity:.55;
}
/* Mobile: 2 per row */
@media (max-width: 576px){
.bc-actionbar{ gap:10px; }
.bc-actionbtn{
flex: 0 0 calc(50% - 5px);
min-width: 0;
height:50px;
padding:0 12px;
border-radius:14px;
}
.bc-text{ font-size:14px; }
.bc-ico{ width:32px; height:32px; border-radius:10px; }
}
/* BUY: slightly more vibrant than #248ee2 */
/* BUY: main color #0473aa (less white sheen, more color presence) */
.bc-buy{
background:
radial-gradient(120px 80px at 18% 20%,
rgba(255,255,255,.18) 0%,
rgba(255,255,255,.07) 38%,
rgba(255,255,255,0) 72%),
linear-gradient(135deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 60%),
linear-gradient(135deg,
#0a8ac7 0%,
#0473aa 52%,
#035b88 100%);
}
/* SELL: main color #de1342 */
.bc-sell{
background:
radial-gradient(140px 90px at 78% 35%,
rgba(255,255,255,.16) 0%,
rgba(255,255,255,.06) 40%,
rgba(255,255,255,0) 74%),
linear-gradient(135deg, rgba(255,255,255,.06) 0%, rgba(255,255,255,0) 60%),
linear-gradient(135deg,
#ff2a5b 0%,
#de1342 52%,
#b80f36 100%);
}
/* BUY GIFTCARD: main color #f3a117 */
.bc-gc-buy{
background:
radial-gradient(160px 100px at 22% 45%,
rgba(255,255,255,.18) 0%,
rgba(255,255,255,.07) 40%,
rgba(255,255,255,0) 78%),
radial-gradient(220px 140px at 85% 25%,
rgba(255,255,255,.08) 0%,
rgba(255,255,255,0) 62%),
linear-gradient(135deg,
#ffbe3a 0%,
#f3a117 52%,
#cf8208 100%);
}
/* SELL GIFTCARD: main color #00a4e1 */
.bc-gc-sell{
background:
radial-gradient(170px 105px at 78% 22%,
rgba(255,255,255,.16) 0%,
rgba(255,255,255,.06) 42%,
rgba(255,255,255,0) 80%),
radial-gradient(240px 150px at 18% 85%,
rgba(255,255,255,.07) 0%,
rgba(255,255,255,0) 64%),
linear-gradient(135deg,
#27c6ff 0%,
#00a4e1 52%,
#007bb3 100%);
}
.owl-carousel .item a:hover .card{
box-shadow: 0 6px 18px rgba(0,0,0,.08);
transform: translateY(-1px);
transition: all .15s ease;
}
.owl-carousel .item a .card{
transition: all .15s ease;
}
/* Balance toggle button */
#toggleBalancesBtn{
display:inline;
width:40px;
height:32px;
}
.bc-wallet-actions{
background: linear-gradient(90deg, #0473aa 0%, #f3a117 90%);
border-radius: 16px;
padding: 12px 12px;
display: inline-flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
box-shadow: 0 2px 8px rgba(0,0,0,.08);
border: 1px solid rgba(255,255,255,.22);
}
</style>
<!-- Main Container -->
<div class="container mt-3">
<div class="row">
<?php include '../common/nav.php'; ?>
<main class="col-md-9 col-lg-10 px-md-5 mb-5">
<?php include '../common/page-header.php'; ?>
<?php if (!empty($_SESSION['flash_success'])): ?>
<div class="alert alert-success text-break">
<?= htmlspecialchars($_SESSION['flash_success']) ?>
</div>
<?php unset($_SESSION['flash_success']); ?>
<?php endif; ?>
<?php if (!empty($_SESSION['flash_error'])): ?>
<div class="alert alert-danger text-break">
<?= htmlspecialchars($_SESSION['flash_error']) ?>
</div>
<?php unset($_SESSION['flash_error']); ?>
<?php endif; ?>
<!-- WALLETS CARD-->
<section class="px-md-5">
<!-- Available balance header + right toggle -->
<div class="d-flex align-items-center justify-content-between">
<h6 class="mb-0"> Available Balance</h6>
</div>
<div class="py-3">
<div class="mb-2 ms-1">
<span class="fs-4 fw-bold" id="totalNgnText">
₦<?= number_format($totalNgn, 2); ?>
</span> |
<span class="fs-4 fw-bold" id="totalUsdText">
$<?= number_format($totalUsd, 2); ?>
</span>
<button type="button"
id="toggleBalancesBtn"
class="btn btn-light btn-sm rounded-5 border text-dark"
aria-pressed="false"
title="Hide balances">
<i class="bi bi-eye"></i>
</button>
</div>
<?php if (!empty($wallets)) : ?>
<div class="align-content-start">
<!-- Deposit Dropdown -->
<div class="btn-group me-1">
<button type="button" class="btn btn-primary btn-sm rounded-5 px-3 dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Deposit
</button>
<ul class="dropdown-menu">
<?php foreach ($wallets as $coin => $wallet): ?>
<li>
<a class="dropdown-item d-flex align-items-center" href="../wallets/single-wallet.php?ussgwt=<?php echo $wallet['wallet_id']; ?>&coin=<?php echo $wallet['coin']; ?>">
<span><?php echo htmlspecialchars($wallet['label'] ?: $coin); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<!-- Transfer Dropdown -->
<div class="btn-group me-1">
<button type="button" class="btn btn-outline-secondary btn-sm rounded-5 px-3 dropdown-toggle my-1" data-bs-toggle="dropdown" aria-expanded="false">
Withdraw
</button>
<ul class="dropdown-menu">
<?php foreach ($wallets as $coin => $wallet):
// Decide link based on wallet type
$link = ($wallet['type'] === 'fiat')
? "../fiat/send_fiat.php?wallet_id=" . urlencode($wallet['wallet_id'])
: "../crypto/send_crypto.php?coin=" . urlencode($wallet['coin']);
?>
<li>
<a class="dropdown-item d-flex align-items-center" href="<?php echo $link; ?>">
<span><?php echo htmlspecialchars($wallet['label'] ?: $wallet['coin']); ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php if (!empty($walletOptions)): ?>
<form method="post" class="d-inline">
<div class="btn-group">
<button type="button" class="btn btn-primary rounded-5 dropdown-toggle py-1 my-1" data-bs-toggle="dropdown" aria-expanded="false">
Create Wallet
</button>
<ul class="dropdown-menu">
<?php foreach ($walletOptions as $symbol => $opt): ?>
<li>
<button type="submit"
class="dropdown-item"
id="<?php echo htmlspecialchars($symbol); ?>"
name="<?php echo htmlspecialchars($opt['name']); ?>"
value="1">
<?php echo htmlspecialchars($opt['label']); ?>
</button>
</li>
<?php endforeach; ?>
</ul>
</div>
</form>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<!-- Owl Carousel -->
<div class="owl-carousel owl-theme">
<!-- Card -->
<?php foreach ($wallets as $wallet) {
$coin = strtoupper($wallet['coin']);
// Single wallet URL (same as your Deposit dropdown)
$walletUrl = "../wallets/single-wallet.php?ussgwt=" . urlencode($wallet['wallet_id'])
. "&coin=" . urlencode($wallet['coin']);
?>
<div class="item shadow-sm">
<a href="<?= htmlspecialchars($walletUrl) ?>"
class="text-decoration-none text-dark d-block"
style="outline: none;">
<div class="card rounded-1 bg-white">
<!-- Card Header with icon and coin text aligned to the left -->
<div class="card-header bg-white d-flex d-flex">
<img src="../../assets/icons/<?= htmlspecialchars($wallet['icon']) ?>" class="bg-white flag" />
<div class="ngn-text"><?= htmlspecialchars($wallet['coin']); ?></div>
</div>
<!-- Card Body -->
<div class="card-body">
<!-- Wallet Info -->
<div class="wallet-info">
<i class="fas fa-wallet"></i>
<span>
<?= '...' . htmlspecialchars(substr($wallet['wallet_add'], -10)); ?>
</span>
</div>
<!-- Primary Amount -->
<div class="amount text-end" style="line-height:1.2;" data-coin-primary="<?= htmlspecialchars($coin) ?>">
<?php if ($coin === 'NGN'): ?>
₦<?= number_format((float)$wallet['balance'], 2, '.', ','); ?>
<?php elseif (!empty($wallet['usd_equiv'])): ?>
$<?= number_format((float)$wallet['usd_equiv'], 2, '.', ','); ?>
<?php endif; ?>
</div>
<!-- Raw Amount -->
<div class="text-muted text-end" style="font-size:14px; margin-top:-5px;" data-coin-raw="<?= htmlspecialchars($coin) ?>">
<?php if ($coin === 'NGN'): ?>
₦<?= number_format((float)$wallet['balance'], 2, '.', ','); ?>
<?php else: ?>
<?= fmt_coin_amount($wallet['balance'], $wallet['coin']); ?>
<?php endif; ?>
</div>
</div>
</div>
</a>
</div>
<?php } ?>
</div>
<!-- Owl Carousel -->
</section>
<section class="pt-1 container">
<!-- Buy / Sell / Giftcards buttons (no Swap) -->
<div class="bc-actionbar-wrap mt-3 px-1 px-md-5">
<div class="row">
<div class="col-6 col-md-3 mb-3">
<a href="../crypto/buy.php" class="bc-actionbtn bc-buy" role="button">
<span class="bc-ico" aria-hidden="true">
<!-- up-right arrow -->
<svg viewBox="0 0 24 24" width="22" height="22" fill="none">
<path d="M7 17L17 7" stroke="white" stroke-width="2.6" stroke-linecap="round"/>
<path d="M10 7H17V14" stroke="white" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="bc-text">Buy Crypto</span>
</a>
</div>
<div class="col-6 col-md-3 mb-3">
<a href="../crypto/sell.php" class="bc-actionbtn bc-sell" role="button">
<span class="bc-ico" aria-hidden="true">
<!-- down arrow -->
<svg viewBox="0 0 24 24" width="22" height="22" fill="none">
<path d="M12 5V19" stroke="white" stroke-width="2.6" stroke-linecap="round"/>
<path d="M7.5 14.5L12 19l4.5-4.5" stroke="white" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="bc-text">Sell Crypto</span>
</a>
</div>
<div class="col-6 col-md-3 mb-3">
<a href="../giftcards/buy_giftcard.php" class="bc-actionbtn bc-gc-buy" role="button">
<span class="bc-ico" aria-hidden="true">
<!-- gift icon -->
<svg viewBox="0 0 24 24" width="22" height="22" fill="none">
<path d="M20 12v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8" stroke="white" stroke-width="2.4" stroke-linecap="round"/>
<path d="M2 8h20v4H2z" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
<path d="M12 8v14" stroke="white" stroke-width="2.4" stroke-linecap="round"/>
<path d="M12 8H8.5a2.5 2.5 0 1 1 0-5c2 0 3.2 2.2 3.5 5Z" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
<path d="M12 8h3.5a2.5 2.5 0 1 0 0-5c-2 0-3.2 2.2-3.5 5Z" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
</svg>
</span>
<span class="bc-text">Buy Giftcard</span>
</a>
</div>
<div class="col-6 col-md-3 mb-3">
<a href="../giftcards/submit_card.php" class="bc-actionbtn bc-gc-sell" role="button">
<span class="bc-ico" aria-hidden="true">
<!-- tag icon -->
<svg viewBox="0 0 24 24" width="22" height="22" fill="none">
<path d="M20 13l-7 7a2 2 0 0 1-2.83 0L3 12.83V3h9.83L20 10.17a2 2 0 0 1 0 2.83Z" stroke="white" stroke-width="2.4" stroke-linejoin="round"/>
<path d="M7.5 7.5h.01" stroke="white" stroke-width="4" stroke-linecap="round"/>
</svg>
</span>
<span class="bc-text">Sell Giftcard</span>
</a>
</div>
</div>
</div>
</section>
<!-- EXCHANGE -->
<section class="px-md-5 mt-1" id="">
<div class="bg-white currency-box p-4 shadow-none border-0 row">
<div class="col-md-6 d-none d-md-block">
<h5 class="mb-3">Market Watch</h5>
<ul class="nav nav-tabs" id="cryptoTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active text-dark" id="btc-tab" data-bs-toggle="tab" data-bs-target="#btc" type="button" role="tab" aria-controls="btc" aria-selected="true">BTC</button>
</li>
<!-- <li class="nav-item" role="presentation">
<button class="nav-link text-dark" id="eth-tab" data-bs-toggle="tab" data-bs-target="#eth" type="button" role="tab" aria-controls="eth" aria-selected="false">ETH</button>
</li> -->
<li class="nav-item" role="presentation">
<button class="nav-link text-dark" id="sol-tab" data-bs-toggle="tab" data-bs-target="#sol" type="button" role="tab" aria-controls="sol" aria-selected="false">SOL</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link text-dark" id="trx-tab" data-bs-toggle="tab" data-bs-target="#trx" type="button" role="tab" aria-controls="trx" aria-selected="false">TRX</button>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane fade show active" id="btc" role="tabpanel" aria-labelledby="btc-tab">
<div id="tradingview_btcusdt"></div>
</div>
<div class="tab-pane fade" id="eth" role="tabpanel" aria-labelledby="eth-tab">
<div id="tradingview_ethusdt"></div>
</div>
<div class="tab-pane fade" id="sol" role="tabpanel" aria-labelledby="sol-tab">
<div id="tradingview_solusdt"></div>
</div>
<div class="tab-pane fade" id="trx" role="tabpanel" aria-labelledby="trx-tab">
<div id="tradingview_trxusdt"></div>
</div>
</div>
</div>
<div id="convert" class="d-md-none" style="height: 50px"></div>
<div class="col-md-6" id="">
<h5 class="mb-3">Asset Swap</h5>
<div class="currency-box bg-white border shadow-sm">
<?php include '../crypto/swap.php'; ?>
</div>
</div>
</div>
<div>
<a class="" href="https://wallet.bitcardo.com/chat/sso/">
<img src="../../assets/images/crypto-banner.png" class="w-100" alt="">
</a>
</div>
<div class="d-flex align-items-center mb-4 justify-content-center mt-4">
<a href="../security/notifications.php" class="btn btn-outline-primary rounded-5 text-decoration-none">
<span class="me-2"><i class="bi bi-bell"></i></span>
<span class="fw-bold">Get exchange rate updates</span>
</a>
</div>
</section>
<!-- TRANSACTIONS -->
<section class="px-md-4 mt-5">
<div class="bg-white p-4 currency-box shadow-none border-0">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">Transactions</h5>
<a href="../data/transactions.php" class="text-decoration-none text-primary fw-semibold">See all</a>
</div>
<?php if (empty($recent_transactions)): ?>
<div class="text-center text-muted py-4">No recent transactions</div>
<?php else: ?>
<?php foreach ($recent_transactions as $t):
$coin = htmlspecialchars($t['coin']);
$amount_val = ($coin === 'NGN')
? number_format((float)$t['amount'], 2, '.', '')
: fmt_coin_amount($t['amount'], $coin);
$amount = $amount_val . ' ' . $coin;
$type = $t['type']; // send, receive, swap, deposit, withdrawal
$amountShow = $amount;
$amountClass = 'text-secondary';
$icon = '<i class="bi bi-question-circle"></i>';
$counterparty = '';
switch ($type) {
case 'send':
$amountShow = '-' . $amount;
$amountClass = 'text-danger';
$icon = '<i class="text-danger bi bi-box-arrow-in-up-right"></i>';
$counterparty = trim($t['receiver_first'] . ' ' . $t['receiver_last'])
?: truncate_left($t['receiver_address']);
break;
case 'receive':
$amountShow = '+' . $amount;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-box-arrow-in-down-right"></i>';
$counterparty = trim($t['sender_first'] . ' ' . $t['sender_last'])
?: truncate_left($t['sender_address']);
break;
case 'swap':
$amountClass = 'text-info';
$icon = '<i class="text-info bi bi-shuffle"></i>';
$address = $t['receiver_address'] ?: $t['sender_address'];
$counterparty = truncate_left($address, 15);
break;
case 'deposit':
$amountShow = '+' . $amount;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-box-arrow-in-down-right"></i>';
$counterparty = 'Deposit Wallet';
break;
case 'withdrawal':
$amountShow = '-' . $amount;
$amountClass = 'text-danger';
$icon = '<i class="text-danger bi bi-box-arrow-in-up-right"></i>';
$counterparty = 'Withdrawal Wallet';
break;
case 'buy':
$amountShow = '+' . $amount;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-bag-check"></i>';
$counterparty = 'Buy Crypto';
break;
case 'fee':
$isDebit = ((int)$t['sender_uid'] === (int)$user_id);
if ($isDebit) {
$amountShow = '-' . $amount;
$amountClass = 'text-danger';
$icon = '<i class="text-warning bi bi-receipt"></i>';
$counterparty = 'Fee';
} else {
$amountShow = '+' . $amount;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-receipt"></i>';
$counterparty = 'Fee Rebate';
}
break;
case 'giftcard_payout':
$amountShow = '+' . $amount;
$amountClass = 'text-success';
$icon = '<i class="text-success bi bi-gift"></i>';
$counterparty = 'Giftcard Payout';
break;
}
$typeText = match ($type) {
'giftcard_payout' => 'Giftcard Payout',
'buy' => 'Buy Crypto',
default => ucfirst($type),
};
$dateText = date('D, M j', strtotime($t['created_at']));
?>
<a href="../data/transaction_detail.php?id=<?= $t['trans_id'] ?>" class="text-decoration-none text-dark">
<div class="transaction-item d-flex justify-content-between align-items-center py-2 border-0 border-bottom">
<div class="d-flex align-items-start gap-3">
<div class="transaction-icon">
<?= $icon ?>
</div>
<div>
<div class="fw-semibold"><?= htmlspecialchars($counterparty) ?></div>
<small class="text-muted"><?= $typeText ?> · <?= $dateText ?></small>
</div>
</div>
<?php
$coinText = $coin;
$amountOnly = trim(str_ireplace($coinText, '', $amountShow)); // keeps + / - sign
?>
<div class="text-end">
<div class="amount-lg <?= $amountClass ?>"><?= htmlspecialchars($amountOnly) ?></div>
<div class="small text-muted lh-1"><?= htmlspecialchars($coinText) ?></div>
</div>
</div>
</a>
<?php endforeach; ?>
<?php endif; ?>
</div>
</section>
</main>
</div>
</div>
<?php include '../common/footer.php'; ?>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script type="text/javascript">
new TradingView.widget({
"container_id": "tradingview_btcusdt",
"symbol": "BINANCE:BTCUSDT",
"interval": "H",
"width": "100%",
"height": 330,
"theme": "light",
"style": "1",
"locale": "en",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"hide_top_toolbar": false,
"save_image": false,
"studies": [],
"withdateranges": true
});
new TradingView.widget({
"container_id": "tradingview_ethusdt",
"symbol": "BINANCE:ETHUSDT",
"interval": "H",
"width": "100%",
"height": 330,
"theme": "light",
"style": "1",
"locale": "en",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"hide_top_toolbar": false,
"save_image": false,
"studies": [],
"withdateranges": true
});
new TradingView.widget({
"container_id": "tradingview_solusdt",
"symbol": "BINANCE:SOLUSDT",
"interval": "H",
"width": "100%",
"height": 330,
"theme": "light",
"style": "1",
"locale": "en",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"hide_top_toolbar": false,
"save_image": false,
"studies": [],
"withdateranges": true
});
new TradingView.widget({
"container_id": "tradingview_trxusdt",
"symbol": "BINANCE:TRXUSDT",
"interval": "H",
"width": "100%",
"height": 330,
"theme": "light",
"style": "1",
"locale": "en",
"toolbar_bg": "#f1f3f6",
"enable_publishing": false,
"hide_top_toolbar": false,
"save_image": false,
"studies": [],
"withdateranges": true
});
</script>
<!-- Live wallet balance updates (no refresh) + chime on change (single poller only) -->
<script>
(function () {
// ---------------------------
// BALANCE HIDE/SHOW (persisted)
// ---------------------------
const BAL_MASK_KEY = "bc_hide_balances";
function isHidden() {
return localStorage.getItem(BAL_MASK_KEY) === "1";
}
function setHidden(val) {
localStorage.setItem(BAL_MASK_KEY, val ? "1" : "0");
}
function maskValue(real) {
if (!real) return "••••";
const s = String(real).trim();
const first = s.charAt(0);
if (first === "₦" || first === "$") return first + "••••";
return "••••";
}
function applyBalanceMask() {
const hidden = isHidden();
// totals
const tn = document.getElementById("totalNgnText");
const tu = document.getElementById("totalUsdText");
if (tn) {
if (!tn.dataset.real) tn.dataset.real = tn.textContent;
tn.textContent = hidden ? maskValue(tn.dataset.real) : tn.dataset.real;
}
if (tu) {
if (!tu.dataset.real) tu.dataset.real = tu.textContent;
tu.textContent = hidden ? maskValue(tu.dataset.real) : tu.dataset.real;
}
// wallet amounts (primary + raw)
document.querySelectorAll("[data-coin-primary], [data-coin-raw]").forEach((el) => {
if (!el.dataset.real) el.dataset.real = el.textContent;
el.textContent = hidden ? maskValue(el.dataset.real) : el.dataset.real;
});
// button UI
const btn = document.getElementById("toggleBalancesBtn");
if (btn) {
btn.setAttribute("aria-pressed", hidden ? "true" : "false");
btn.title = hidden ? "Show balances" : "Hide balances";
btn.innerHTML = hidden ? '<i class="bi bi-eye-slash"></i>' : '<i class="bi bi-eye"></i>';
}
}
// Wire button
document.addEventListener("click", function (e) {
const btn = e.target.closest("#toggleBalancesBtn");
if (!btn) return;
const hidden = !isHidden();
setHidden(hidden);
applyBalanceMask();
});
// Apply on initial render
document.addEventListener("DOMContentLoaded", function () {
applyBalanceMask();
});
// ---- AUDIO UNLOCK (required by browsers) ----
let audioCtx = null;
let audioUnlocked = false;
async function ensureAudioReady() {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
if (!AudioCtx) return false;
if (!audioCtx) audioCtx = new AudioCtx();
// Some browsers create it in "suspended" state until a gesture
if (audioCtx.state === 'suspended') {
try { await audioCtx.resume(); } catch (e) {}
}
return audioCtx.state === 'running';
}
async function unlockAudioOnce() {
if (audioUnlocked) return;
const ok = await ensureAudioReady();
if (!ok) return;
// Play a tiny inaudible tick to finalize unlock in stubborn browsers
try {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
gain.gain.value = 0.00001;
osc.connect(gain).connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.01);
} catch (e) {}
audioUnlocked = true;
document.removeEventListener('click', unlockAudioOnce);
document.removeEventListener('keydown', unlockAudioOnce);
document.removeEventListener('touchstart', unlockAudioOnce);
}
// Attach unlock listeners
document.addEventListener('click', unlockAudioOnce, { passive: true });
document.addEventListener('keydown', unlockAudioOnce);
document.addEventListener('touchstart', unlockAudioOnce, { passive: true });
function playChime() {
const a = document.getElementById("balanceSound");
if (!a) return;
a.currentTime = 0;
a.play().catch(() => {});
}
// ---- CHANGE DETECTION ----
let lastSnapshot = {
totalNgn: null,
totalUsd: null,
wallets: {} // coin => { primary, raw }
};
async function poll() {
try {
const res = await fetch("/user/dashboard/wallet_balances.php?ts=" + Date.now(), {
credentials: "include",
cache: "no-store"
});
if (!res.ok) return;
const data = await res.json();
if (!data.ok) return;
let changed = false;
// totals
const tnVal = data.totals?.ngn || '';
const tuVal = data.totals?.usd || '';
if (lastSnapshot.totalNgn !== null && lastSnapshot.totalNgn !== tnVal) changed = true;
if (lastSnapshot.totalUsd !== null && lastSnapshot.totalUsd !== tuVal) changed = true;
const tn = document.getElementById("totalNgnText");
const tu = document.getElementById("totalUsdText");
// store real values; UI rendering handled by applyBalanceMask()
if (tn) tn.dataset.real = tnVal;
if (tu) tu.dataset.real = tuVal;
lastSnapshot.totalNgn = tnVal;
lastSnapshot.totalUsd = tuVal;
// wallets
const wallets = data.wallets || {};
Object.keys(wallets).forEach((coin) => {
const w = wallets[coin] || {};
const primary = w.primary ?? '';
const raw = w.raw ?? '';
const prev = lastSnapshot.wallets[coin];
if (prev && (prev.primary !== primary || prev.raw !== raw)) changed = true;
const p = document.querySelector('[data-coin-primary="' + coin + '"]');
const r = document.querySelector('[data-coin-raw="' + coin + '"]');
if (p) p.dataset.real = primary;
if (r) r.dataset.real = raw;
lastSnapshot.wallets[coin] = { primary, raw };
});
// Apply mask after updating values
applyBalanceMask();
// sound on any increase/decrease (any change)
if (changed) playChime();
} catch (e) {
console.error("Wallet poll error", e);
}
}
poll();
setInterval(poll, 5000);
})();
</script>
<audio id="balanceSound" preload="auto">
<source src="https://wallet.bitcardo.com/assets/sounds/balance.mp3x" type="audio/mpeg">
</audio>
Выполнить команду
Для локальной разработки. Не используйте в интернете!