// Traffic Desk client — pick size, pick approved creative, pick destination, // launch. Balance and running campaigns come from the server. (function () { 'use strict'; var $ = function (id) { return document.getElementById(id); }; var state = { size: null, creative: null, target: 'join', creatives: {}, remaining: 0, id: null }; var busy = false; function chip(label, on, fn) { var b = document.createElement('button'); b.type = 'button'; b.className = 'tf-chip' + (on ? ' on' : ''); b.textContent = label; b.addEventListener('click', fn); return b; } function renderSizes(sizes) { var host = $('tfSizes'); host.innerHTML = ''; sizes.forEach(function (s) { host.appendChild(chip(s, s === state.size, function () { state.size = s; state.creative = (state.creatives[s] || [])[0] || null; renderSizes(sizes); renderCreatives(); })); }); } function renderCreatives() { var host = $('tfCreatives'); host.innerHTML = ''; (state.creatives[state.size] || []).forEach(function (f) { var d = document.createElement('div'); d.className = 'tf-cre' + (f === state.creative ? ' on' : ''); var img = document.createElement('img'); img.src = '/banners/' + f; img.alt = 'Team banner ' + state.size; img.loading = 'lazy'; d.appendChild(img); d.addEventListener('click', function () { state.creative = f; renderCreatives(); }); host.appendChild(d); }); } 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(); })); }); } function gate(msg) { $('tfGate').style.display = 'block'; $('tfGate').innerHTML = msg; $('tfCard').style.opacity = '.55'; $('tfGo').disabled = true; } function renderLive(campaigns, live) { if (!campaigns.length) { $('tfLiveWrap').style.display = 'none'; return; } var byId = {}; (live || []).forEach(function (l) { byId[l.ad_id] = l; }); var tb = $('tfLive'); tb.innerHTML = ''; campaigns.slice().reverse().forEach(function (c) { var l = byId[c.adId] || {}; var tr = document.createElement('tr'); var served = l.served != null ? l.served : 0; tr.innerHTML = '' + c.size + '' + '' + (c.target.indexOf('/p/') !== -1 ? 'personal page' : 'invite page') + '' + '' + Number(c.impressions).toLocaleString() + '' + '' + Number(served).toLocaleString() + '' + '' + (l.remaining != null ? Number(l.remaining).toLocaleString() : '—') + '' + '' + (l.hits != null ? l.hits : '—') + '' + '' + (l.live ? 'running' : 'finished') + ''; tb.appendChild(tr); }); $('tfLiveWrap').style.display = 'block'; } function applyStatus(d) { var st = d.status; state.creatives = st.creatives || {}; state.id = d.id; state.remaining = st.remaining; if (!state.size) state.size = (st.sizes || [])[0] || null; if (!state.creative) state.creative = (state.creatives[state.size] || [])[0] || null; $('tfBal').style.display = 'flex'; $('tfRemain').textContent = Number(st.remaining).toLocaleString(); $('tfLimit').textContent = Number(st.limit).toLocaleString(); $('tfNote').innerHTML = 'Level ' + d.level + ' includes ' + Number(st.limit).toLocaleString() + ' impressions a month on the network. Each upgrade raises it — Apex is where it jumps to 50,000.'; $('tfImp').max = st.remaining; $('tfImp').value = Math.min(Number($('tfImp').value) || st.remaining, st.remaining) || 100; renderSizes(st.sizes || []); renderCreatives(); renderTargets(); renderLive(st.campaigns || [], d.live || []); if (st.remaining <= 0) { $('tfGo').disabled = true; $('tfGo').textContent = 'Allowance used — refills on the 1st'; } } async function boot() { try { var r = await fetch('/api/public/suite-traffic'); if (r.status === 401) { gate('You’re not signed in yet. Open the Suite and connect the wallet that holds your position, then come back.'); return; } if (r.status === 403) { gate('The Traffic Desk isn’t open for this position yet. See your Suite.'); return; } if (!r.ok) return; var d = await r.json(); if (!d.configured) { gate('The ad network bridge is being finalized — this opens shortly.'); return; } applyStatus(d); } catch (e) {} } async function run() { if (busy) return; busy = true; $('tfErr').style.display = 'none'; $('tfOk').style.display = 'none'; $('tfGo').disabled = true; $('tfGo').innerHTML = 'Launching on the network…'; try { var r = await fetch('/api/public/suite-traffic', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ size: state.size, creative: state.creative, target: state.target, impressions: Number($('tfImp').value) || 0 }) }); var d = await r.json(); if (!r.ok) { $('tfErr').textContent = d.error || 'That didn’t go through — try again.'; $('tfErr').style.display = 'block'; } else { $('tfOk').innerHTML = '✅ Your banner is live on the network.
' + Number(d.campaign.impressions).toLocaleString() + ' impressions of ' + d.campaign.size + ' pointed at your ' + (d.campaign.target.indexOf('/p/') !== -1 ? 'personal page' : 'invite page') + '. It starts rotating immediately — check back here to watch it serve.'; $('tfOk').style.display = 'block'; applyStatus({ status: d.status, level: 0, id: state.id, live: [], configured: true }); // refresh from the server so the running-banners table picks it up setTimeout(boot, 800); } } catch (e) { $('tfErr').textContent = 'Connection hiccup — try again.'; $('tfErr').style.display = 'block'; } busy = false; if (state.remaining > 0) { $('tfGo').disabled = false; $('tfGo').textContent = '🚦 Launch my banner'; } } document.addEventListener('DOMContentLoaded', function () { boot(); $('tfGo').addEventListener('click', run); }); })();