Promo toolkit by badge + AI Copy Engine at Surge: posts, DMs, follow-ups, objection replies, emails, stories in the member's name; monthly free allowance by badge (20/60/150), then ad credits per generation; admin usage view
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+31
-1
@@ -1473,9 +1473,39 @@
|
||||
}
|
||||
document.querySelectorAll('.subtabs [data-earn]').forEach(b =>
|
||||
b.addEventListener('click', () => setEarnSub(b.dataset.earn)));
|
||||
// promo toolkit: badge tiers + the AI Copy Engine (Marty, 2026-09-14)
|
||||
let tkState = null;
|
||||
async function loadToolkit() {
|
||||
try {
|
||||
const r = await (await fetch('/api/my/toolkit')).json(); if (r.error) return; tkState = r;
|
||||
const esc = t => String(t == null ? '' : t).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
||||
const names = { free: 'Free', spark: 'Spark', surge: 'Surge', circuit: 'Circuit', nexus: 'Nexus' };
|
||||
$('tkTierLine').textContent = 'you are at ' + names[r.tier];
|
||||
$('tkTiers').innerHTML = r.tiers.map(t => '<div class="tk-tier' + (t.reached ? ' reached' : '') + (t.key === r.tier ? ' current' : '') + '"><b>' + esc(t.name) + (t.reached ? ' ✓' : '') + '</b><div class="need">' + (t.reached ? esc(t.blurb) : 'Unlocks when you ' + esc(t.needText)) + '</div><ul>' + t.items.map(i => '<li' + (i.live ? '' : ' class="soon"') + '>' + esc(i.t) + '</li>').join('') + '</ul>' + (t.reached ? '' : '<div class="lock" style="margin-top:8px">locked</div>') + '</div>').join('');
|
||||
$('tkLocked').hidden = r.unlocked; $('tkForm').hidden = !r.unlocked;
|
||||
if (!r.unlocked) { $('tkLocked').textContent = 'Unlocks at Surge: your first qualifying buyer of a $20 or larger package. Then it writes posts, DMs, follow-ups, objection replies, emails and story posts in your name, with your link, inside the honesty rules. ' + (r.tier === 'free' ? 'Step one is switching on payouts.' : 'You are one buyer away.'); $('tkUsage').textContent = 'locked'; return; }
|
||||
$('tkUsage').textContent = r.freeLeft + ' free this month · then ' + r.cost + ' credits each · you have ' + r.available.toLocaleString() + ' credits';
|
||||
$('tkCostLine').textContent = r.freeLeft > 0 ? 'This one is free (' + r.freeLeft + ' left this month).' : 'This one costs ' + r.cost + ' credits from your earned pool.';
|
||||
if (!$('tkKind').options.length) { $('tkKind').innerHTML = r.kinds.map(k => '<option value="' + k.key + '">' + esc(k.label) + '</option>').join(''); $('tkAngle').innerHTML = r.angles.map(a => '<option value="' + a + '">' + a + '</option>').join(''); }
|
||||
$('tkHistory').innerHTML = r.history.length ? '<p class="small muted" style="margin:0 0 4px">Recent</p>' + r.history.map(h => '<div class="tk-hist"><span class="muted">' + esc(h.kind) + ' · ' + new Date(h.ts).toLocaleString() + (h.charged ? ' · ' + h.charged + ' credits' : ' · free') + '</span><br>' + esc(h.text).replace(/\n/g, '<br>') + '</div>').join('') : '';
|
||||
} catch (e) {}
|
||||
}
|
||||
async function tkGenerate() {
|
||||
const btn = $('tkGo'); if (btn.disabled) return; btn.disabled = true; btn.textContent = 'Writing…';
|
||||
try {
|
||||
const r = await (await fetch('/api/my/toolkit/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ kind: $('tkKind').value, angle: $('tkAngle').value, brief: $('tkBrief').value }) })).json();
|
||||
if (r.error) { IAP.status(r.error, 'bad'); return; }
|
||||
$('tkText').value = r.text; $('tkOut').hidden = false;
|
||||
IAP.status(r.charged ? r.charged + ' credits used.' : 'Written. ' + r.freeLeft + ' free left this month.', 'ok');
|
||||
loadToolkit(); loadDashboard();
|
||||
} catch (e) { IAP.status('The engine did not answer. Try again.', 'bad'); }
|
||||
finally { btn.disabled = false; btn.textContent = 'Write it'; }
|
||||
}
|
||||
if ($('tkGo')) { $('tkGo').addEventListener('click', tkGenerate); $('tkAgain').addEventListener('click', tkGenerate); $('tkCopy').addEventListener('click', async () => { try { await navigator.clipboard.writeText($('tkText').value); IAP.status('Copied.', 'ok'); } catch (e) { $('tkText').select(); } }); }
|
||||
// promo tools: pill menu switches between posts / swipes / banners / wall / videos
|
||||
function setPromoSub(name) {
|
||||
const ids = ['posts', 'text', 'swipe', 'banners', 'wall', 'objections', 'videos'];
|
||||
const ids = ['toolkit', 'posts', 'text', 'swipe', 'banners', 'wall', 'objections', 'videos'];
|
||||
if (name === 'toolkit') loadToolkit();
|
||||
if (!ids.includes(name)) name = 'posts';
|
||||
ids.forEach(id => { const el = $('promo-' + id); if (el) el.hidden = id !== name; });
|
||||
document.querySelectorAll('.promo-pills [data-promo]').forEach(b => {
|
||||
|
||||
Reference in New Issue
Block a user