Campaigns: gold available-to-spend banner + where-your-ads-run (incl. Network Ad Space); Wallet: live POL balances; Earn credits: done-for-today mark

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-10 07:36:24 -05:00
parent 4156b1f815
commit b824711ac5
17 changed files with 88 additions and 20 deletions
+34 -3
View File
@@ -582,6 +582,12 @@
if (r.bannerSizes && $('cSize') && !$('cSize').options.length)
$('cSize').innerHTML = r.bannerSizes.map(s => '<option value="' + s.id + '">' + s.label + '</option>').join('');
soloHint();
if ($('spendBanner')) {
$('spendBanner').hidden = false;
$('spendBig').textContent = r.availableCredits.toLocaleString();
$('spendSub').textContent = '= $' + (r.availableCredits / 100).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' of ad delivery' + (r.earnedCredits ? ' · ' + r.purchasedCredits.toLocaleString() + ' purchased + ' + r.earnedCredits.toLocaleString() + ' earned by viewing' : '') + (r.positionCount > 1 ? ' · pooled across ' + r.positionCount + ' positions (largest single position ' + (r.largestPosition || 0).toLocaleString() + ')' : '');
$('spendRates').innerHTML = [['Banner', r.rates.bannerCreditsPerBatch + ' cr / ' + r.rates.bannerBatch + ' views'], ['Text', r.rates.textCreditsPerBatch + ' cr / ' + r.rates.textBatch + ' views'], ['Login', r.rates.loginCreditsPerDay + ' cr / day'], ['Solo', r.rates.soloCostPerRecipient + ' cr / delivery'], ['Featured', (r.rates.featuredPerDay || 40) + ' cr / day'], ['Visit', (r.rates.visitCostPerVisit || 3) + ' cr / visit']].map(x => '<span><b>' + x[0] + '</b> ' + x[1] + '</span>').join('');
}
$('rateLine').textContent = 'Available to spend: ' + r.availableCredits.toLocaleString()
+ (r.earnedCredits ? ' (' + r.purchasedCredits.toLocaleString() + ' purchased + ' + r.earnedCredits + ' earned)' : '')
+ ' credits · rates: banner ' + r.rates.bannerCreditsPerBatch + 'cr/' + r.rates.bannerBatch
@@ -672,7 +678,17 @@
if (raw.hidden) { raw.value = ed.innerHTML; raw.hidden = false; ed.hidden = true; $('edRawBtn').textContent = 'Visual'; }
else { ed.innerHTML = raw.value; ed.hidden = false; raw.hidden = true; $('edRawBtn').textContent = 'Raw text'; }
});
const WHERE = { banner: 'Runs in the ad viewer, on the home page, the live ledger, every Overview, the sidebar tile, and out in the Network Ad Space rotation across the wider network.',
text: 'Runs in the ad viewer, the live ledger text slot, and out in the Network Ad Space rotation.',
login: 'Full screen for every member who signs in, ten seconds, once a day per member. Opens in a fresh tab, so any working page qualifies.',
solo: 'Delivered into member inboxes under Earn credits. Each read is timed and rewarded, so it gets opened.',
video: 'Plays in Watch videos and the Shorts feed under Earn credits. You pay only for completed watches.',
featured: 'Your headline and link in the Featured strip on every member Overview for the days you book.',
visits: 'A distinct member opens your site in a new tab, stays eight seconds and passes a check. Nobody counts twice.' };
const whereHint = () => { const el = $('cWhere'); if (el) el.textContent = WHERE[$('cType').value] || ''; };
whereHint();
$('cType').addEventListener('change', () => {
whereHint();
const t = $('cType').value;
$('cImageRow').hidden = t !== 'banner'; // only banners carry a creative; login frames its URL
$('cSizeRow').hidden = t !== 'banner';
@@ -1323,15 +1339,26 @@
const rows = list.map((p, i) => '<div class="lin-row"><span class="nm">Position ' + (i + 2) + ' <span class="mono">' + short(p.address) + '</span></span>'
+ '<span class="id">' + (p.memberId ? '#' + p.memberId : 'not on-chain yet') + '</span>'
+ '<span class="earn' + (p.counted ? ' on' : '') + '">' + (p.counted ? 'counts as a qualifying buyer' : p.memberId ? 'registered, buy $20+ to count' : 'buy a $20+ package to register it') + '</span>'
+ '<span class="dt">' + (p.credits || 0).toLocaleString() + ' credits</span>'
+ '<span class="dt">' + (p.credits || 0).toLocaleString() + ' credits' + (p.balanceWei != null ? ' · ' + (Number(BigInt(p.balanceWei) / 10n ** 14n) / 10000).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' POL' : '') + '</span>'
+ (!p.memberId ? '<button class="btn sec small" type="button" data-unlink="' + p.address + '">Unlink</button>' : '')
+ '</div>').join('');
const mainRow = r.main && r.main.address ? '<div class="lin-row"><span class="nm">Position 1 · main <span class="mono">' + short(r.main.address) + '</span></span>'
+ '<span class="id">' + (r.main.memberId ? '#' + r.main.memberId : 'payouts not on yet') + '</span>'
+ '<span class="earn on">' + (r.main.buyerCount || 0) + ' qualifying buyer(s)</span>'
+ '<span class="dt">' + (r.main.credits || 0).toLocaleString() + ' credits</span></div>' : '';
+ '<span class="dt">' + (r.main.credits || 0).toLocaleString() + ' credits' + (r.main.balanceWei != null ? ' · ' + (Number(BigInt(r.main.balanceWei) / 10n ** 14n) / 10000).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' POL' : '') + '</span></div>' : '';
const html = mainRow + rows + (list.length ? '<p class="muted small" style="margin:8px 0 0">Pooled credits: <b>' + (r.totalCredits || 0).toLocaleString() + '</b>. A campaign budget spends from one position at a time.</p>' : '');
if ($('qsList')) $('qsList').innerHTML = html;
// Wallet tab: live POL balance of the linked wallet, and which package it covers
const wb = $('walletBal');
if (wb && r.main && r.main.address && r.main.balanceWei != null) {
const polN = Number(BigInt(r.main.balanceWei) / 10n ** 14n) / 10000;
const usd = r.polUsd ? polN * r.polUsd : 0;
const pkgs = [5, 20, 50, 100, 250];
const covers = r.polUsd ? pkgs.filter(p => usd >= p * 1.06 + 0.05) : [];
wb.hidden = false;
wb.innerHTML = 'Wallet balance: <b class="mono">' + polN.toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' POL</b>' + (usd ? ' (about $' + usd.toLocaleString(undefined, { maximumFractionDigits: 0 }) + ')' : '')
+ (r.polUsd ? '<br><span class="muted">' + (covers.length ? 'Enough for the $' + covers[covers.length - 1] + ' package with gas to spare.' : 'Not enough for the $5 package yet. Buy POL with a card on the Buy packages tab, or send POL to this wallet.') + (covers.length && covers.length < pkgs.length ? ' Top up for the $' + pkgs[covers.length] + ' package.' : '') + '</span>' : '');
}
if ($('posList')) $('posList').innerHTML = html;
if ($('posCard')) $('posCard').hidden = !list.length;
// "Buy from" picker: main + every position
@@ -1461,7 +1488,11 @@
$('earnBalance').textContent = st.earned + ' earned credits';
const done = st.views >= st.target;
$('earnClaimBtn').hidden = !(done && !st.claimed);
if (st.claimed) $('earnHint').textContent = 'Claimed for today. Come back tomorrow, or put those credits to work in Campaigns.';
if (st.claimed) {
$('earnHint').textContent = 'Claimed for today. Come back tomorrow, or put those credits to work in Campaigns.';
const box = $('earnAdBox'); if (box && !box.querySelector('.done-big')) box.innerHTML = '<div class="done-big"><span class="tick">✓</span><b>Ads done for today</b><span class="muted small">Today\'s set is viewed and claimed. Fresh ads tomorrow.</span></div>';
if ($('earnStartBtn')) $('earnStartBtn').hidden = true;
} else if ($('earnStartBtn')) $('earnStartBtn').hidden = false;
else if (done) $('earnHint').textContent = 'Set complete. Claim your ' + st.claimCredits + ' credits.';
return st;
} catch (e) { return null; }