Ad reporting + featured rotates one link + clear campaign form on submit
- Ad reporting (auto-approved ads need a safety valve): new reports.js store (dual-mode) + ad_reports table + POST /api/report-ad; a "⚠ report" control on served ad slots opens a reason prompt and notifies the admin by email (siteConfig.adminEmail / ADMIN_EMAIL). - Featured links now show ONE link at a time and cycle (true rotation), instead of listing all links at once. - Campaign form now clears EVERY field on submit (target/creative/title/etc.). A leftover target URL was carrying into the next campaign — the same reason a video short was saved with the wrong (massifly) CTA target. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+18
-7
@@ -28,16 +28,24 @@
|
||||
return 'Fully qualified. Every level pays you, and you catch the pass-ups that under-qualified positions below you let slip. Keep sharing and keep your campaigns running.';
|
||||
}
|
||||
// featured rotation strip on the overview
|
||||
let featItems = [], featIdx = 0, featTimer = null;
|
||||
function renderOneFeatured() {
|
||||
if (!featItems.length) return;
|
||||
const i = featItems[featIdx % featItems.length];
|
||||
$('featStrip').innerHTML = '<a href="' + i.url + '" target="_blank" rel="noopener nofollow"><span>' + esc(i.title) + '</span>'
|
||||
+ (i.by ? '<span class="by">' + esc(i.by) + '</span>' : '') + '</a>';
|
||||
}
|
||||
async function loadFeatured() {
|
||||
try {
|
||||
const r = await (await fetch('/api/featured')).json();
|
||||
const card = $('featuredCard'); if (!card) return;
|
||||
if (!r.items || !r.items.length) { card.hidden = true; return; }
|
||||
card.hidden = false;
|
||||
featItems = r.items; featIdx = Math.floor(Math.random() * featItems.length);
|
||||
$('featSub').textContent = r.items.length + ' link' + (r.items.length === 1 ? '' : 's') + ' in rotation';
|
||||
$('featStrip').innerHTML = r.items.map(i =>
|
||||
'<a href="' + i.url + '" target="_blank" rel="noopener nofollow"><span>' + esc(i.title) + '</span>'
|
||||
+ (i.by ? '<span class="by">' + esc(i.by) + '</span>' : '') + '</a>').join('');
|
||||
renderOneFeatured(); // show ONE at a time (true rotation), cycle if more than one
|
||||
clearInterval(featTimer);
|
||||
if (featItems.length > 1) featTimer = setInterval(() => { featIdx++; renderOneFeatured(); }, 6000);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
@@ -665,11 +673,14 @@
|
||||
ctaLabel: isVideo ? $('cVideoCta').value : $('cCtaLabel').value,
|
||||
budget: isFeat ? (Number($('cFeatDays').value) * (lastRates.featuredPerDay || 40)) : Number($('cBudget').value) });
|
||||
IAP.status('Campaign is live. It starts serving right away.', 'ok');
|
||||
$('cName').value = ''; $('cBudget').value = '';
|
||||
$('cSoloEd').innerHTML = ''; $('cSoloRaw').value = ''; $('cCtaLabel').value = '';
|
||||
$('cVideoUrl').value = ''; $('cVideoTitle').value = ''; $('cVideoCta').value = '';
|
||||
// clear EVERY field so no target/creative carries into the next campaign
|
||||
['cName', 'cBudget', 'cTarget', 'cImage', 'cTitle', 'cBody', 'cCtaLabel',
|
||||
'cVideoUrl', 'cVideoTitle', 'cVideoCta', 'cVisitTitle', 'cVisitCount', 'cFeatTitle']
|
||||
.forEach(id => { if ($(id)) $(id).value = ''; });
|
||||
$('cSoloEd').innerHTML = ''; if ($('cSoloRaw')) $('cSoloRaw').value = '';
|
||||
$('cVideoInfo').textContent = ''; $('cVideoPrev').hidden = true; $('cVideoPrev').innerHTML = '';
|
||||
$('edMediaInfo').textContent = '';
|
||||
if ($('edMediaInfo')) $('edMediaInfo').textContent = '';
|
||||
cVidDims = null;
|
||||
await loadCampaigns();
|
||||
}));
|
||||
// defers the busy() lookup to click time (busy is declared below)
|
||||
|
||||
Reference in New Issue
Block a user