Traffic Desk: stop-and-refund, plus never let a banner dead-end
Three fixes from live testing of the Traffic Desk: - /p/<id> with no page built now 302s to /join/<id> instead of returning raw JSON 404. Any /p/ link already on a banner, flyer, or in a DM must always land somewhere useful. - The personal-page ad destination is only offered (client) and only accepted (server) once a page actually exists; otherwise the member is pointed at the Page Builder. - Members can stop a running banner and get the unserved impressions back in their monthly balance. Counters are read before deactivation, since deactivating zeroes `remaining` and would look fully served. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,7 @@
|
||||
|
||||
<div class="tf-live" id="tfLiveWrap" style="display:none">
|
||||
<h2 style="font-size:20px;margin:0 0 8px">Your running banners</h2>
|
||||
<div class="table-wrap"><table><thead><tr><th>Size</th><th>Sends to</th><th>Bought</th><th>Served</th><th>Left</th><th>Clicks</th><th>Status</th></tr></thead><tbody id="tfLive"></tbody></table></div>
|
||||
<div class="table-wrap"><table><thead><tr><th>Size</th><th>Sends to</th><th>Bought</th><th>Served</th><th>Left</th><th>Clicks</th><th>Status</th><th></th></tr></thead><tbody id="tfLive"></tbody></table></div>
|
||||
</div>
|
||||
|
||||
<p class="tf-note"><strong style="color:var(--text)">What this is, honestly:</strong> display advertising is <strong style="color:var(--text)">awareness volume</strong> — it puts your link in front of people browsing the network. It is not a lead list, and click rates on display inventory are low by nature. The conversations you have with people you know are still what actually builds a team; this runs quietly in the background while you do that. <br><br>All banners use approved team creative, so every ad on the network stays on-brand. Independent team resource · No income is guaranteed · Cryptocurrency involves risk.</p>
|
||||
|
||||
+59
-8
@@ -46,11 +46,21 @@
|
||||
function renderTargets() {
|
||||
var host = $('tfTargets');
|
||||
host.innerHTML = '';
|
||||
[['join', 'My invite page'], ['page', 'My personal page (/p/' + state.id + ')']].forEach(function (t) {
|
||||
host.appendChild(chip(t[1], state.target === t[0], function () {
|
||||
state.target = t[0]; renderTargets();
|
||||
host.appendChild(chip('My invite page', state.target === 'join', function () {
|
||||
state.target = 'join'; renderTargets();
|
||||
}));
|
||||
if (state.hasPage) {
|
||||
host.appendChild(chip('My personal page', state.target === 'page', function () {
|
||||
state.target = 'page'; renderTargets();
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
// Don't let anyone point an ad at a page that doesn't exist yet.
|
||||
var d = document.createElement('span');
|
||||
d.className = 'tf-hint';
|
||||
d.style.cssText = 'margin:0;align-self:center';
|
||||
d.innerHTML = 'Want to send traffic to your own page instead? <a href="/suite/page" style="color:var(--gold);font-weight:700">Build it first →</a>';
|
||||
host.appendChild(d);
|
||||
}
|
||||
}
|
||||
|
||||
function gate(msg) {
|
||||
@@ -70,13 +80,26 @@
|
||||
var l = byId[c.adId] || {};
|
||||
var tr = document.createElement('tr');
|
||||
var served = l.served != null ? l.served : 0;
|
||||
var statusCell = c.stopped
|
||||
? 'stopped <span style="opacity:.7">(' + Number(c.refunded || 0).toLocaleString() + ' returned)</span>'
|
||||
: (l.live ? '<span style="color:var(--teal)">running</span>' : 'finished');
|
||||
tr.innerHTML = '<td><b>' + c.size + '</b></td>' +
|
||||
'<td>' + (c.target.indexOf('/p/') !== -1 ? 'personal page' : 'invite page') + '</td>' +
|
||||
'<td>' + Number(c.impressions).toLocaleString() + '</td>' +
|
||||
'<td><b>' + Number(served).toLocaleString() + '</b></td>' +
|
||||
'<td>' + (l.remaining != null ? Number(l.remaining).toLocaleString() : '—') + '</td>' +
|
||||
'<td>' + Number(c.bought != null ? c.bought : c.impressions).toLocaleString() + '</td>' +
|
||||
'<td><b>' + Number(c.stopped ? (c.served || 0) : served).toLocaleString() + '</b></td>' +
|
||||
'<td>' + (c.stopped ? '—' : (l.remaining != null ? Number(l.remaining).toLocaleString() : '—')) + '</td>' +
|
||||
'<td>' + (l.hits != null ? l.hits : '—') + '</td>' +
|
||||
'<td>' + (l.live ? '<span style="color:var(--teal)">running</span>' : 'finished') + '</td>';
|
||||
'<td>' + statusCell + '</td>';
|
||||
var act = document.createElement('td');
|
||||
if (!c.stopped && l.live) {
|
||||
var btn = document.createElement('button');
|
||||
btn.className = 'btn btn-secondary btn-sm';
|
||||
btn.textContent = 'Stop';
|
||||
btn.title = 'Stop this banner and return the unserved impressions to your balance';
|
||||
btn.addEventListener('click', function () { stopCampaign(c.adId, btn); });
|
||||
act.appendChild(btn);
|
||||
}
|
||||
tr.appendChild(act);
|
||||
tb.appendChild(tr);
|
||||
});
|
||||
$('tfLiveWrap').style.display = 'block';
|
||||
@@ -87,6 +110,8 @@
|
||||
state.creatives = st.creatives || {};
|
||||
state.id = d.id;
|
||||
state.remaining = st.remaining;
|
||||
if (typeof d.hasPage === 'boolean') state.hasPage = d.hasPage;
|
||||
if (!state.hasPage && state.target === 'page') state.target = 'join';
|
||||
if (!state.size) state.size = (st.sizes || [])[0] || null;
|
||||
if (!state.creative) state.creative = (state.creatives[state.size] || [])[0] || null;
|
||||
$('tfBal').style.display = 'flex';
|
||||
@@ -104,6 +129,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function stopCampaign(adId, btn) {
|
||||
if (btn) { btn.disabled = true; btn.textContent = 'Stopping…'; }
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-traffic-stop', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ad_id: adId })
|
||||
});
|
||||
var d = await r.json();
|
||||
if (!r.ok) {
|
||||
$('tfErr').textContent = d.error || 'Could not stop that banner.';
|
||||
$('tfErr').style.display = 'block';
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Stop'; }
|
||||
return;
|
||||
}
|
||||
$('tfOk').innerHTML = '✅ <b>Banner stopped.</b> ' +
|
||||
Number(d.stopped.refunded).toLocaleString() + ' unserved impressions went back into your balance' +
|
||||
(d.stopped.served ? ' — it had served ' + Number(d.stopped.served).toLocaleString() + '.' : '.');
|
||||
$('tfOk').style.display = 'block';
|
||||
boot();
|
||||
} catch (e) {
|
||||
$('tfErr').textContent = 'Connection hiccup — try again.';
|
||||
$('tfErr').style.display = 'block';
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Stop'; }
|
||||
}
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-traffic');
|
||||
|
||||
Reference in New Issue
Block a user