Buy packages pane inside the member back office

Members buy directly from the dashboard: live-priced tiles in a dedicated
menu section, wallet flow with buy-time sponsor resolution, dashboard
refresh on settle. No more round-trip to the homepage. Assets v=20260905e.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-05 06:07:33 -05:00
parent cdeb59ed7f
commit a4c7028c0e
5 changed files with 67 additions and 20 deletions
+39 -2
View File
@@ -68,8 +68,8 @@
}
// ── back-office menu: hash-routed panes ───────────────
const PANES = ['overview', 'line', 'campaigns', 'earn', 'earnings', 'wallet'];
const TITLES = { overview: 'Overview', line: 'My line', campaigns: 'Campaigns',
const PANES = ['overview', 'line', 'buy', 'campaigns', 'earn', 'earnings', 'wallet'];
const TITLES = { overview: 'Overview', line: 'My line', buy: 'Buy packages', campaigns: 'Campaigns',
earn: 'Earn credits', earnings: 'Earnings', wallet: 'Wallet & account' };
function setPane(name) {
if (!PANES.includes(name)) name = 'overview';
@@ -202,6 +202,43 @@
// defers the busy() lookup to click time (busy is declared below)
function busy2(btn, fn) { return (...a) => busy(btn, fn)(...a); }
// ── in-dashboard package buying ──
const PKG = { 1: 'Micro', 2: 'Activation', 3: 'Builder', 4: 'Growth', 5: 'Leader' };
async function loadBuyTiles() {
try {
const { products } = await (await fetch('/api/catalog')).json();
const wrap = $('boTiles');
wrap.innerHTML = '';
for (const p of products) {
const bonus = p.creditAmount - p.priceCents;
const div = document.createElement('div');
div.className = 'tile' + (p.priceCents === 5000 ? ' hot' : '');
div.innerHTML = '<div class="name">' + (PKG[p.id] || 'Package ' + p.id) + '</div>'
+ '<div class="price">$' + Math.round(p.priceCents / 100) + '</div>'
+ '<div class="cr">' + p.creditAmount.toLocaleString() + ' credits</div>'
+ '<div class="bonus">' + (bonus > 0 ? '+' + bonus.toLocaleString() + ' bonus credits' : '&nbsp;') + '</div>'
+ '<div class="pol">' + (p.costWei ? IAP.fmtPol(p.costWei) + ' POL right now' : 'paused') + '</div>'
+ '<button class="btn small" data-id="' + p.id + '" data-cost="' + (p.costWei || '') + '"'
+ (p.costWei ? '' : ' disabled') + '>Buy</button>';
wrap.appendChild(div);
}
wrap.querySelectorAll('button[data-id]').forEach(b => b.addEventListener('click', async () => {
try {
b.disabled = true;
const spNow = await (await fetch('/api/sponsor')).json();
IAP.status('Confirm the purchase in your wallet…');
const r = await IAPWallet.buy(Number(b.dataset.id), spNow.sponsorId || 0, b.dataset.cost);
if (r.status !== '0x1' && r.receipt && r.receipt.status !== '0x1') throw new Error('Transaction reverted.');
IAP.status('Purchase settled on-chain. Credits are in your account.', 'ok');
await render();
loadBuyTiles();
} catch (e) { IAP.status('Purchase failed: ' + ((e && e.message) || e), 'bad'); }
finally { b.disabled = false; }
}));
} catch (e) {}
}
loadBuyTiles();
// ── earn-by-viewing: daily ad set with dwell, then claim ──
const earnState = { types: ['banner', 'text'], i: 0, timer: null };
async function earnRefresh() {
+4 -4
View File
@@ -5,7 +5,7 @@
<title>The contract | InstantAdPay</title>
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
<link rel="stylesheet" href="/assets/site.css?v=20260905e">
</head>
<body>
<div class="wrap">
@@ -129,8 +129,8 @@
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
</footer>
</div>
<script src="/assets/common.js?v=20260905d"></script>
<script src="/assets/contract.js?v=20260905d"></script>
<script src="/assets/chat.js?v=20260905d"></script>
<script src="/assets/common.js?v=20260905e"></script>
<script src="/assets/contract.js?v=20260905e"></script>
<script src="/assets/chat.js?v=20260905e"></script>
</body>
</html>
+5 -5
View File
@@ -5,7 +5,7 @@
<title>InstantAdPay: advertise and earn, locked in code</title>
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
<link rel="stylesheet" href="/assets/site.css?v=20260905e">
</head>
<body>
@@ -399,9 +399,9 @@
</div>
</section>
<script src="/assets/common.js?v=20260905d"></script>
<script src="/assets/wallet.js?v=20260905d"></script>
<script src="/assets/home.js?v=20260905d"></script>
<script src="/assets/chat.js?v=20260905d"></script>
<script src="/assets/common.js?v=20260905e"></script>
<script src="/assets/wallet.js?v=20260905e"></script>
<script src="/assets/home.js?v=20260905e"></script>
<script src="/assets/chat.js?v=20260905e"></script>
</body>
</html>
+4 -4
View File
@@ -5,7 +5,7 @@
<title>Live ledger | InstantAdPay</title>
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
<link rel="stylesheet" href="/assets/site.css?v=20260905e">
</head>
<body>
<div class="wrap">
@@ -25,8 +25,8 @@
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
</footer>
</div>
<script src="/assets/common.js?v=20260905d"></script>
<script src="/assets/ledger.js?v=20260905d"></script>
<script src="/assets/chat.js?v=20260905d"></script>
<script src="/assets/common.js?v=20260905e"></script>
<script src="/assets/ledger.js?v=20260905e"></script>
<script src="/assets/chat.js?v=20260905e"></script>
</body>
</html>
+15 -5
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260905d">
<link rel="stylesheet" href="/assets/site.css?v=20260905e">
</head>
<body class="bo-body">
@@ -54,6 +54,7 @@
<nav class="bo-menu" aria-label="Member menu">
<button data-pane="overview" class="on" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="8" height="8" rx="2"/><rect x="13" y="3" width="8" height="8" rx="2"/><rect x="3" y="13" width="8" height="8" rx="2"/><rect x="13" y="13" width="8" height="8" rx="2"/></svg>Overview</button>
<button data-pane="line" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="7" r="3.2"/><circle cx="5" cy="17" r="2.6"/><circle cx="19" cy="17" r="2.6"/><path d="M12 10v3M12 13l-5 2M12 13l5 2"/></svg>My line</button>
<button data-pane="buy" type="button"><svg viewBox="0 0 24 24"><circle cx="9" cy="20" r="1.6"/><circle cx="17" cy="20" r="1.6"/><path d="M3 4h2l2.4 11.2A2 2 0 0 0 9.4 17h8.2a2 2 0 0 0 2-1.6L21 8H6"/></svg>Buy packages</button>
<button data-pane="campaigns" type="button"><svg viewBox="0 0 24 24"><path d="M3 11l14-5v12L3 13v-2z"/><path d="M17 8a4 4 0 0 1 0 8M7 13v5a2 2 0 0 0 4 0v-3"/></svg>Campaigns</button>
<button data-pane="earn" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v9M9 10c0-1.1 1.3-1.8 3-1.8s3 .7 3 1.8-1.3 1.6-3 1.8-3 .7-3 1.8 1.3 1.8 3 1.8 3-.7 3-1.8"/></svg>Earn credits</button>
<button data-pane="earnings" type="button"><svg viewBox="0 0 24 24"><path d="M4 17l5-5 4 3 7-8"/><path d="M14 7h6v6"/></svg>Earnings</button>
@@ -128,6 +129,15 @@
</div>
</div>
<div class="pane" id="pane-buy" hidden>
<div class="card">
<h3>Buy an ad package</h3>
<p class="muted small">Priced in dollars, settled from your wallet at the live rate. Credits mint
to this account instantly, and every payout in your sponsor line lands the moment you confirm.</p>
<div class="tiles" id="boTiles"><div class="tile"><span class="muted small">Loading live prices…</span></div></div>
</div>
</div>
<div class="pane" id="pane-campaigns" hidden>
<div class="card" id="campGate" hidden>
<h3>Campaigns unlock with your first package</h3>
@@ -230,9 +240,9 @@
</div>
</div>
<script src="/assets/common.js?v=20260905d"></script>
<script src="/assets/wallet.js?v=20260905d"></script>
<script src="/assets/my.js?v=20260905d"></script>
<script src="/assets/chat.js?v=20260905d"></script>
<script src="/assets/common.js?v=20260905e"></script>
<script src="/assets/wallet.js?v=20260905e"></script>
<script src="/assets/my.js?v=20260905e"></script>
<script src="/assets/chat.js?v=20260905e"></script>
</body>
</html>