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:
+39
-2
@@ -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' : ' ') + '</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() {
|
||||
|
||||
Reference in New Issue
Block a user