Traffic sources: referring domain on join-link visits and joins (per-member table), click sources per campaign (surface or outside host)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-10 10:12:13 -05:00
parent 6e187c4f41
commit ecdee89be1
6 changed files with 74 additions and 22 deletions
+8 -1
View File
@@ -611,7 +611,7 @@
+ '<td>' + c.type + (c.type === 'banner' && c.width ? ' <span class="muted small">' + c.width + '×' + c.height + '</span>' : '') + (c.dailyCap ? ' <span class="muted small" title="daily cap: ' + c.dailyCap + ' credits, ' + (c.daySpent || 0) + ' spent today">cap ' + c.dailyCap + '/day</span>' : '') + '</td>'
+ '<td class="num">' + c.imps.toLocaleString() + '</td>'
+ '<td class="num">' + (['banner', 'text'].includes(c.type) ? (c.impsNas || 0).toLocaleString() : '<span class="muted small" title="only banner and text ads syndicate to the network">n/a</span>') + '</td>'
+ '<td class="num">' + c.clicks + '</td>'
+ '<td class="num">' + c.clicks + (r.clickSources && r.clickSources[c.id] ? '<div class="muted small" style="white-space:nowrap" title="where the clicks happened">' + Object.entries(r.clickSources[c.id]).sort((a, b) => b[1] - a[1]).map(([k, v]) => esc(k) + ' ' + v).join(' · ') + (c.impsNas ? ' · network: see Network views' : '') + '</div>' : '') + '</td>'
+ '<td class="num">' + c.spent + '</td><td class="num">' + c.budget + '</td>'
+ '<td>' + (c.status === 'out' ? '<span class="badge amber">budget spent</span>' : c.status) + '</td>'
+ '<td>' + (c.status === 'active' ? '<button class="btn small sec" data-camp="' + c.id + '" data-act="pause">Pause</button>'
@@ -1018,6 +1018,13 @@
if (!rows.length) { t.innerHTML = '<tr><td class="muted small">No views yet. Share your link and the numbers start here.</td></tr>'; return; }
t.innerHTML = '<tr><th>Hook</th><th>Views (30d)</th><th>Views (all)</th><th>Joined</th><th>Qualifying buyers</th></tr>'
+ rows.map(a => '<tr><td>' + esc(a.angle === 'plain' ? 'plain link' : '?v=' + a.angle) + '</td><td class="mono">' + a.views30 + '</td><td class="mono">' + a.views + '</td><td class="mono">' + a.joins + '</td><td class="mono">' + a.buyers + '</td></tr>').join('');
const st = $('linkSrcTable');
if (st) {
const src = (r.sources || []).filter(x => x.views || x.joins);
st.innerHTML = src.length ? '<tr><th>Source</th><th>Views (30d)</th><th>Views (all)</th><th>Joined</th><th>Qualifying buyers</th></tr>'
+ src.map(x => '<tr><td>' + esc(x.source) + '</td><td class="mono">' + x.views30 + '</td><td class="mono">' + x.views + '</td><td class="mono">' + x.joins + '</td><td class="mono">' + x.buyers + '</td></tr>').join('')
: '<tr><td class="muted small">Sources appear as visits arrive.</td></tr>';
}
} catch (e) {}
}
// ── prospects: the member's own follow-up list ──
+3 -1
View File
@@ -281,6 +281,8 @@
<h3>Your links: what is working</h3>
<p class="muted small">Views, joins and qualifying buyers for each hook. Add <span class="mono">?v=instant</span>, <span class="mono">?v=adspend</span>, <span class="mono">?v=free</span>, <span class="mono">?v=ledger</span> or <span class="mono">?v=two</span> to the end of your link to pick the hook the page opens with.</p>
<div class="tablewrap"><table id="linkStatsTable" class="small"></table></div>
<p class="muted small" style="margin:14px 0 6px"><b>Where your visitors came from</b> (referring site of the first visit; "direct" is a typed or pasted link, a text message, or an email)</p>
<div class="tablewrap"><table id="linkSrcTable" class="small"></table></div>
</div>
<div class="card">
<h3>Your line</h3>
@@ -874,7 +876,7 @@
<script src="/assets/common.js?v=20260910c"></script>
<script src="/assets/wallet.js?v=20260910a"></script>
<script src="/assets/promo.js?v=20260909b"></script>
<script src="/assets/my.js?v=20260910k"></script>
<script src="/assets/my.js?v=20260910l"></script>
<script src="/assets/chat.js?v=20260907l"></script>
</body>
</html>