a87a63d75d
Site copy regenerated through the bv-tester1 engine; em dashes scrubbed from all user-visible strings. /api/my/activity serves per-member earnings, referrals, and purchases from the chain index. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
89 lines
4.1 KiB
JavaScript
89 lines
4.1 KiB
JavaScript
// Shared page runtime: site config, nav, formatting. Zero dependencies.
|
||
window.IAP = (function () {
|
||
let config = null;
|
||
const $ = id => document.getElementById(id);
|
||
|
||
async function getConfig() {
|
||
if (!config) config = await (await fetch('/api/config')).json();
|
||
return config;
|
||
}
|
||
function fmtPol(wei) {
|
||
const s = BigInt(wei).toString().padStart(19, '0');
|
||
const whole = s.slice(0, -18) || '0';
|
||
const frac = s.slice(-18, -12).replace(/0+$/, '');
|
||
return whole + (frac ? '.' + frac : '');
|
||
}
|
||
const fmtUsd = cents => '$' + (cents / 100).toFixed(2);
|
||
|
||
function status(msg, cls) {
|
||
let el = $('status');
|
||
if (!el) { el = document.createElement('div'); el.id = 'status'; document.body.appendChild(el); }
|
||
el.textContent = msg; el.className = cls || ''; el.hidden = false;
|
||
clearTimeout(status._t);
|
||
if (cls === 'ok') status._t = setTimeout(() => { el.hidden = true; }, 6000);
|
||
}
|
||
|
||
async function renderNav(active) {
|
||
const c = await getConfig();
|
||
const nav = document.createElement('nav');
|
||
nav.innerHTML = '<div class="wrap">'
|
||
+ '<a class="logo" href="/">Instant<b>AdPay</b></a>'
|
||
+ '<span class="links">'
|
||
+ '<a href="/" data-p="home">How it works</a>'
|
||
+ '<a href="/#ladder" data-p="pricing">Ad packages</a>'
|
||
+ '<a href="/ledger" data-p="ledger">Live ledger</a>'
|
||
+ '<a href="/my" data-p="my">Members</a>'
|
||
+ '</span><span id="navWallet" class="muted">…</span></div>';
|
||
document.body.prepend(nav);
|
||
if (c.rehearsal) {
|
||
const b = document.createElement('div');
|
||
b.className = 'rehearsal';
|
||
b.innerHTML = '<b>Testnet rehearsal</b>: running on ' + c.chainName + '. Purchases use valueless test POL while we prove every payout in public.';
|
||
document.body.prepend(b);
|
||
}
|
||
const a = nav.querySelector('[data-p="' + active + '"]');
|
||
if (a) a.style.color = 'var(--ink)';
|
||
refreshNavWallet();
|
||
}
|
||
async function refreshNavWallet() {
|
||
try {
|
||
const me = await (await fetch('/api/me')).json();
|
||
const el = $('navWallet');
|
||
if (!el) return;
|
||
if (me.signedIn) {
|
||
el.innerHTML = (me.memberId ? '<span class="badge">member #' + me.memberId + '</span> ' : '')
|
||
+ '<span class="mono">' + me.address.slice(0, 6) + '…' + me.address.slice(-4) + '</span>';
|
||
} else {
|
||
el.innerHTML = '<a href="/my">Sign in</a>';
|
||
}
|
||
return me;
|
||
} catch (e) { return null; }
|
||
}
|
||
function describeEvent(ev, c) {
|
||
const pol = w => fmtPol(w) + ' POL';
|
||
switch (ev.type) {
|
||
case 'Purchase': return '🧾 member #' + ev.buyerId + ' bought package #' + ev.productId
|
||
+ ' (' + fmtUsd(ev.priceCents) + ') for ' + pol(ev.paidWei) + ' → +' + ev.creditAmount.toLocaleString() + ' credits';
|
||
case 'TierPaid': return '💸 level ' + ev.tier + ' payout → member #' + ev.recipientId + ': ' + pol(ev.amountWei)
|
||
+ (ev.hops ? ' (passed up ' + ev.hops + ')' : '');
|
||
case 'PassedUp': return '↷ level ' + ev.tier + ' passed over #' + ev.skippedId + ' (' + ev.reason + ')';
|
||
case 'AdminPaid': return '🏛 platform fee settled: ' + pol(ev.amountWei);
|
||
case 'BuyerCounted': return '⭐ member #' + ev.sponsorId + ' now has ' + ev.newCount + ' qualifying buyer(s)';
|
||
case 'MemberActivated': return '👤 member #' + ev.id + ' activated a payout wallet';
|
||
case 'AwardPaid': return '🎁 award: ' + pol(ev.amountWei) + ' → member #' + ev.toId;
|
||
case 'CreditsConsumed': return '📣 member #' + ev.memberId + ' ran ads: −' + ev.amount.toLocaleString() + ' credits';
|
||
case 'PriceCached': return '🔮 oracle price refreshed';
|
||
case 'FallbackPriceUsed': return '🔮 cached price bridged an oracle gap';
|
||
default: return '· ' + ev.type;
|
||
}
|
||
}
|
||
function feedRow(ev, c) {
|
||
const div = document.createElement('div');
|
||
div.className = 'row t-' + ev.type;
|
||
div.innerHTML = '<span>' + describeEvent(ev, c) + '</span>'
|
||
+ '<span class="tx"><a target="_blank" rel="noopener" href="' + c.explorer + '/tx/' + ev.tx + '">verify ↗</a></span>';
|
||
return div;
|
||
}
|
||
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, $ };
|
||
})();
|