// 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 = '
';
document.body.prepend(nav);
if (c.rehearsal) {
const b = document.createElement('div');
b.className = 'rehearsal';
b.innerHTML = 'Testnet rehearsal: 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.className = 'active';
refreshNavWallet();
}
async function refreshNavWallet() {
try {
const me = await (await fetch('/api/me')).json();
const el = $('navWallet');
if (!el) return;
if (me.signedIn) {
// email-only members have no wallet address yet
const who = me.address
? '' + me.address.slice(0, 6) + 'โฆ' + me.address.slice(-4) + ''
: (me.email ? String(me.email).replace(/[&<>]/g, '') : 'signed in');
el.innerHTML = (me.memberId ? 'member #' + me.memberId + ' ' : '') + who;
} else {
el.innerHTML = 'Sign in';
}
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 = '' + describeEvent(ev, c) + ''
+ (c.explorer
? 'verify โ'
: '' + ev.tx.slice(0, 12) + 'โฆ');
return div;
}
// Render one served ad into #. Silent if no inventory.
async function adSlot(type, elId) {
try {
const { ad } = await (await fetch('/api/ads/slot?type=' + type)).json();
const el = $(elId);
if (!ad || !el) return;
if (ad.imageUrl) {
el.innerHTML = ''
+ '
'
+ 'member ad
';
} else {
el.innerHTML = '' + ad.title + ''
+ (ad.body ? ' ยท ' + ad.body : '') + ' member ad';
}
el.hidden = false;
} catch (e) {}
}
return { getConfig, fmtPol, fmtUsd, status, renderNav, refreshNavWallet, describeEvent, feedRow, adSlot, $ };
})();