Credit activity ledger: every earn and spend logged with a reason, listed under Campaigns
Hugh earned a 10-credit sign-in bonus, then bought a 280-credit extension; earned credits spend first, so Earned read 0 and the bonus looked lost. There was no record a member could check. Now every real credit movement writes a credit_log row (JSON store in file mode): welcome, sign-in bonus with streak, daily claim, inbox reads, video watches, verified visits, milestone/leaderboard bonuses, partner codes, admin/team grants, AI Copy Engine charges + refunds, flat buys (featured run, extension, visit pack, more visits), login-ad days, and metered delivery rolled up per campaign per day; on-chain purchased-credit spends are marked. GET /api/my/credits/activity; dashboard shows the last 40 under Campaigns (also when there are no campaigns) with a link from Earn credits. Chatbot canned answer + AI prompt updated; my.js tag bumped. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+18
-1
@@ -779,6 +779,21 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// credit activity ledger under the campaign table (2026-09-16): every earn and spend with its reason
|
||||
async function loadCreditActivity(el) {
|
||||
try {
|
||||
const r = await (await fetch('/api/my/credits/activity')).json();
|
||||
if (!r.items || !r.items.length) return;
|
||||
const box = document.createElement('div'); box.id = 'creditActivity'; box.style.marginTop = '16px';
|
||||
const when = ts => new Date(ts).toLocaleString([], { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
|
||||
box.innerHTML = '<h4 style="margin:0 0 4px">Credit activity</h4>'
|
||||
+ '<p class="muted small" style="margin:0 0 8px">Every credit that came in or went out, newest first. Earned credits are spent before purchased ones, so an earn followed by a buy shows as two lines here. Views a campaign delivers roll up into one line per day.</p>'
|
||||
+ '<div class="tablewrap"><table class="camp-table act-table"><tbody>'
|
||||
+ r.items.map(i => '<tr><td class="muted small" style="white-space:nowrap">' + when(i.ts) + '</td><td class="num" style="white-space:nowrap"><b style="color:' + (i.delta > 0 ? '#3ecf7a' : 'inherit') + '">' + (i.delta > 0 ? '+' : '') + i.delta.toLocaleString() + '</b></td><td>' + esc(i.note) + '</td></tr>').join('')
|
||||
+ '</tbody></table></div>';
|
||||
el.appendChild(box);
|
||||
} catch (e) {}
|
||||
}
|
||||
async function loadCampaigns() {
|
||||
try {
|
||||
const r = await (await fetch('/api/my/campaigns')).json();
|
||||
@@ -808,7 +823,7 @@
|
||||
+ (r.rates.soloCostPerRecipient || 5) + 'cr/delivery';
|
||||
const el = $('campList');
|
||||
el.innerHTML = '';
|
||||
if (!r.campaigns.length) { el.innerHTML = '<p class="muted small">No campaigns yet. Launch your first below.</p>'; return; }
|
||||
if (!r.campaigns.length) { el.innerHTML = '<p class="muted small">No campaigns yet. Launch your first below.</p>'; loadCreditActivity(el); return; }
|
||||
const tbl = document.createElement('div');
|
||||
tbl.className = 'tablewrap';
|
||||
tbl.innerHTML = '<table class="camp-table"><thead><tr><th>Name</th><th>Type</th><th class="num">Views here</th><th class="num" title="Impressions delivered by Network Ad Space across the wider network (banner and text ads only)">Network views</th><th class="num">Clicks</th>'
|
||||
@@ -832,6 +847,7 @@
|
||||
const note = document.createElement('p'); note.className = 'muted small';
|
||||
note.textContent = 'Credits you put into a campaign are set aside the moment you start it, so your Available number drops right away and the campaign spends from its own budget. Featured links and verified-visit packs are paid in full up front; a featured run is sold by the day, so use Extend run to keep it going. Whatever a campaign has not spent comes back to Available when it ends.';
|
||||
el.appendChild(note);
|
||||
loadCreditActivity(el);
|
||||
el.querySelectorAll('button[data-extend]').forEach(b => b.addEventListener('click', async () => {
|
||||
const rr = r.rates || {}; const durs = rr.featuredDurations || [1, 2, 7]; const per = rr.featuredPerDay || 40;
|
||||
const d = await IAP.ask({ title: 'Extend featured run', text: 'How many more days? Choose ' + durs.join(', ') + '. ' + per + ' credits a day, charged now. The days are added after the current run, or start today if it has ended.', type: 'number', placeholder: 'e.g. ' + durs[durs.length - 1], ok: 'Extend' });
|
||||
@@ -2100,6 +2116,7 @@
|
||||
if (st.error) return null;
|
||||
$('earnProgress').textContent = 'today: ' + st.views + ' / ' + st.target + ' ads viewed';
|
||||
$('earnBalance').textContent = (st.earnedAvailable != null ? st.earnedAvailable : st.earned) + ' earned credits available' + (st.reserved ? ' · ' + st.reserved + ' in live campaigns' : '');
|
||||
if ($('earnActLink')) $('earnActLink').onclick = e => { e.preventDefault(); jumpTo('campaigns', 'creditActivity'); };
|
||||
const done = st.views >= st.target;
|
||||
$('earnClaimBtn').hidden = !(done && !st.claimed);
|
||||
if (st.claimed) {
|
||||
|
||||
+3
-2
@@ -597,7 +597,8 @@
|
||||
view counts. Finish today's set, claim your credits, and spend them on banner and text
|
||||
campaigns just like purchased ones.</p>
|
||||
<p><span class="badge" id="earnProgress">…</span>
|
||||
<span class="badge" id="earnBalance">…</span></p>
|
||||
<span class="badge" id="earnBalance">…</span>
|
||||
<a href="#" id="earnActLink" class="small" title="every credit earned or spent, with the reason">Credit activity</a></p>
|
||||
<div class="card" style="background:rgba(4,8,7,.5)" id="earnStage">
|
||||
<div id="earnAdBox" class="small muted" style="min-height:80px;display:grid;place-items:center">
|
||||
Press the button to open the first ad in a new tab.</div>
|
||||
@@ -1021,7 +1022,7 @@
|
||||
<script src="/assets/common.js?v=20260914a"></script>
|
||||
<script src="/assets/wallet.js?v=20260911a"></script>
|
||||
<script src="/assets/promo.js?v=20260911a"></script>
|
||||
<script src="/assets/my.js?v=20260916b"></script>
|
||||
<script src="/assets/my.js?v=20260916c"></script>
|
||||
<script src="/assets/chat.js?v=20260907l"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user