Balances: one reservation rule (earned first) + purchased-grade credited pool
- balances(): live campaign budgets are set aside up front, earned pool first then purchased, so dashboard numbers never move per impression; new 'in campaigns' line - earned pool gains a purchase_grade portion for refunds/comps: shows under Purchased as 'credited to you' and funds login ads (daily login charge draws on it first) - viewing credits always spend before credited purchased money - dashboard/donut/spend banner/earn pane/positions show purchased, credited, earned, in campaigns - chatbot canned answer + AI prompt: the balance rule Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+20
-13
@@ -192,16 +192,19 @@
|
||||
try {
|
||||
const st = await (await fetch('/api/my/earn')).json();
|
||||
if (!st.error) {
|
||||
const purchased = d.credits || 0, earned = st.earned || 0, wc = Math.min(d.welcomeCredits || 0, earned);
|
||||
// one rule: a balance is what is NOT committed to a live campaign. Budgets are
|
||||
// set aside when a campaign starts and spend down inside Campaigns, so these
|
||||
// numbers only move when a campaign is created, topped up or paused
|
||||
const purchased = d.credits || 0, earned = d.earnedCredits != null ? d.earnedCredits : (st.earnedAvailable != null ? st.earnedAvailable : (st.earned || 0)), inC = d.inCampaigns || 0;
|
||||
$('dbCredits').textContent = (purchased + earned).toLocaleString();
|
||||
$('dbCreditsSub').textContent = purchased.toLocaleString() + ' purchased · ' + earned.toLocaleString() + ' earned';
|
||||
$('dbCreditsSub').textContent = purchased.toLocaleString() + ' purchased' + (d.creditedCredits ? ' (' + d.creditedCredits.toLocaleString() + ' credited to you)' : '') + ' · ' + earned.toLocaleString() + ' earned' + (inC ? ' · ' + inC.toLocaleString() + ' in campaigns' : '');
|
||||
donut($('chDonut'),
|
||||
[{ v: purchased, color: CH.mint }, { v: wc, color: CH.amber }, { v: earned - wc, color: CH.cyan }],
|
||||
{ big: (purchased + earned).toLocaleString(), small: 'spendable' });
|
||||
[{ v: purchased, color: CH.mint }, { v: earned, color: CH.cyan }, { v: inC, color: CH.amber }],
|
||||
{ big: (purchased + earned).toLocaleString(), small: 'available' });
|
||||
legend($('chDonutLegend'), [
|
||||
{ color: CH.mint, label: 'Purchased', v: purchased.toLocaleString() },
|
||||
{ color: CH.amber, label: 'Welcome', v: wc.toLocaleString() },
|
||||
{ color: CH.cyan, label: 'Earned and credited', v: Math.max(0, earned - wc).toLocaleString() }]);
|
||||
{ color: CH.mint, label: 'Purchased, available', v: purchased.toLocaleString() },
|
||||
{ color: CH.cyan, label: 'Earned, available', v: earned.toLocaleString() },
|
||||
{ color: CH.amber, label: 'In live campaigns', v: inC.toLocaleString() }]);
|
||||
const done = Math.min(st.views || 0, st.target || 5), left = Math.max(0, (st.target || 5) - done);
|
||||
donut($('chRing'), [{ v: done, color: st.claimed ? CH.mint : CH.cyan }, { v: left, color: 'none' }],
|
||||
{ big: done + '/' + (st.target || 5), small: st.claimed ? 'claimed' : 'ads viewed' });
|
||||
@@ -357,9 +360,9 @@
|
||||
}
|
||||
}).catch(() => {});
|
||||
}
|
||||
const wc = d.welcomeCredits || 0;
|
||||
$('dbCredits').textContent = ((d.credits || 0) + wc).toLocaleString();
|
||||
$('dbCreditsSub').textContent = wc ? (d.credits || 0).toLocaleString() + ' purchased · ' + wc + ' welcome' : '';
|
||||
const earnedAv = d.earnedCredits || 0, inCamp = d.inCampaigns || 0;
|
||||
$('dbCredits').textContent = ((d.credits || 0) + earnedAv).toLocaleString();
|
||||
$('dbCreditsSub').textContent = (d.credits || 0).toLocaleString() + ' purchased' + (d.creditedCredits ? ' (' + d.creditedCredits.toLocaleString() + ' credited to you)' : '') + ' · ' + earnedAv.toLocaleString() + ' earned' + (inCamp ? ' · ' + inCamp.toLocaleString() + ' in campaigns' : '');
|
||||
$('dbEarned').textContent = IAP.fmtPol(d.earnedWei || '0');
|
||||
$('dbBuyers').textContent = d.buyerCount || 0;
|
||||
$('dbTeam').textContent = (d.referrals || []).length;
|
||||
@@ -591,11 +594,15 @@
|
||||
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() + ')' : '');
|
||||
$('spendSub').textContent = '= $' + (r.availableCredits / 100).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' of ad delivery · ' + r.purchasedCredits.toLocaleString() + ' purchased' + (r.creditedCredits ? ' (' + r.creditedCredits.toLocaleString() + ' of it credited to you, spends on anything)' : '') + ' + ' + (r.earnedCredits || 0).toLocaleString() + ' earned' + (r.positionCount > 1 ? ' · pooled across ' + r.positionCount + ' positions (largest single position ' + (r.largestPosition || 0).toLocaleString() + ')' : '');
|
||||
if ($('spendNote')) $('spendNote').textContent = r.inCampaigns
|
||||
? 'Not counting ' + r.inCampaigns.toLocaleString() + ' credits already set aside for your live campaigns. That budget spends down inside each campaign below. This number only moves when you start, top up or pause a campaign.'
|
||||
: 'This is what is not committed to a campaign. When you start one, its budget moves out of here and spends down inside the campaign.';
|
||||
$('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)' : '')
|
||||
+ (r.inCampaigns ? ' · ' + r.inCampaigns.toLocaleString() + ' set aside in live campaigns' : '')
|
||||
+ ' credits · rates: 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 '
|
||||
@@ -1361,7 +1368,7 @@
|
||||
+ '<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' + (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>' : '');
|
||||
const html = mainRow + rows + (list.length ? '<p class="muted small" style="margin:8px 0 0">Pooled credits: <b>' + (r.totalCredits || 0).toLocaleString() + '</b>' + (r.credited ? ' plus <b>' + r.credited.toLocaleString() + '</b> credited to your account (spends from any position)' : '') + '. 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');
|
||||
@@ -1500,7 +1507,7 @@
|
||||
const st = await (await fetch('/api/my/earn')).json();
|
||||
if (st.error) return null;
|
||||
$('earnProgress').textContent = 'today: ' + st.views + ' / ' + st.target + ' ads viewed';
|
||||
$('earnBalance').textContent = st.earned + ' earned credits';
|
||||
$('earnBalance').textContent = (st.earnedAvailable != null ? st.earnedAvailable : st.earned) + ' earned credits available' + (st.reserved ? ' · ' + st.reserved + ' in live campaigns' : '');
|
||||
const done = st.views >= st.target;
|
||||
$('earnClaimBtn').hidden = !(done && !st.claimed);
|
||||
if (st.claimed) {
|
||||
|
||||
Reference in New Issue
Block a user