Holding tank: unsponsored free members, first-come adoption (own $20 buy required, 2 open, 7-day window, twice max), release to tank, admin view; PIF wallet-to-wallet POL gift with logging
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+56
-1
@@ -1011,7 +1011,54 @@
|
||||
}
|
||||
|
||||
// ── coaching: every direct's rung, stalled flag, one-click nudge ──
|
||||
// ── pay it forward: send POL from the sponsor's own wallet to a downline's linked address ──
|
||||
async function pif(email, name, address) {
|
||||
let suggest = 25;
|
||||
try { const { products } = await (await fetch('/api/catalog')).json(); const p20 = (products || []).find(p => p.priceCents === 2000); if (p20 && p20.costWei) suggest = Math.ceil(Number(p20.costWei) / 1e18) + 3; } catch (e) {}
|
||||
const amt = prompt('Send POL from your wallet to ' + name + ' (' + address.slice(0, 6) + '…' + address.slice(-4) + ') for their first package.\nSuggested: the $20 package plus fees. Amount in POL:', String(suggest));
|
||||
if (amt === null) return;
|
||||
const pol = Number(amt); if (!(pol > 0)) { IAP.status('Enter an amount in POL.', 'bad'); return; }
|
||||
try {
|
||||
IAP.status('Confirm the transfer in your wallet…', 'ok');
|
||||
const wei = (BigInt(Math.round(pol * 1e6)) * 10n ** 12n).toString();
|
||||
const hash = await IAPWallet.sendPol(address, wei);
|
||||
await api('/api/my/gift', { email, tx: hash, pol });
|
||||
IAP.status('Sent ' + pol + ' POL to ' + name + '. They have been told, with the proof link.', 'ok');
|
||||
playSound && playSound('chaching');
|
||||
} catch (e) { IAP.status('Transfer not sent: ' + ((e && e.message) || e), 'bad'); }
|
||||
}
|
||||
// ── holding tank: waiting members, adopt, my open adoptions ──
|
||||
const ago = ts => { if (!ts) return 'never'; const d = Math.floor((Date.now() - ts) / 86400000); return d === 0 ? 'today' : d === 1 ? 'yesterday' : d + ' days ago'; };
|
||||
async function loadTank() {
|
||||
const el = $('tankList'); if (!el) return;
|
||||
try {
|
||||
const r = await (await fetch('/api/my/tank')).json();
|
||||
if (r.error) { el.innerHTML = ''; return; }
|
||||
$('tankCap').textContent = r.cap; $('tankTtl').textContent = r.ttlDays;
|
||||
$('tankSub').textContent = r.waiting.length ? r.waiting.length + ' waiting' : 'nobody waiting right now';
|
||||
const why = $('tankWhy'); why.hidden = r.eligible; why.innerHTML = r.eligible ? '' : '<span class="badge amber">not yet</span> ' + esc(r.reason);
|
||||
$('tankMine').innerHTML = r.mine.length ? '<p class="small" style="margin:0 0 6px"><b>Your open adoptions</b></p>' + r.mine.map(m => '<div class="lin-row own"><span class="nm">' + esc(m.name) + '</span>'
|
||||
+ '<span class="em">' + (m.bought ? 'bought' : m.wallet ? 'wallet linked' : 'free, no wallet yet') + ' · last seen ' + ago(m.lastSeen) + '</span>'
|
||||
+ '<span class="dt">' + Math.max(0, Math.ceil((m.expires - Date.now()) / 86400000)) + ' days left</span>'
|
||||
+ '<button class="btn sec small" type="button" data-tchat="' + esc(m.email) + '" data-tname="' + esc(m.name) + '">Chat</button>'
|
||||
+ (m.address && !m.bought ? ' <button class="btn small" type="button" title="Pay it forward: send POL from your wallet to theirs for their first package" data-pif="' + esc(m.email) + '" data-pname="' + esc(m.name) + '" data-paddr="' + esc(m.address) + '">PIF</button>' : '') + '</div>').join('') : '';
|
||||
$('tankMine').querySelectorAll('[data-tchat]').forEach(b => b.addEventListener('click', () => openConvo(b.dataset.tchat, b.dataset.tname)));
|
||||
$('tankMine').querySelectorAll('[data-pif]').forEach(b => b.addEventListener('click', () => pif(b.dataset.pif, b.dataset.pname, b.dataset.paddr)));
|
||||
if (!r.waiting.length) { el.innerHTML = '<p class="muted small">The tank is empty. Anyone who joins from the public site without a sponsor lands here.</p>'; return; }
|
||||
el.innerHTML = r.waiting.map(w => '<div class="lin-row"><span class="nm">' + esc(w.name) + '</span>'
|
||||
+ '<span class="em">joined ' + ago(w.joined) + '</span>'
|
||||
+ '<span class="dt">last sign-in: <b>' + ago(w.lastSeen) + '</b></span>'
|
||||
+ (r.eligible ? '<button class="btn small" type="button" data-adopt="' + esc(w.username || w.email) + '" data-aname="' + esc(w.name) + '">Adopt</button>' : '') + '</div>').join('');
|
||||
el.querySelectorAll('[data-adopt]').forEach(b => b.addEventListener('click', async () => {
|
||||
const note = prompt('Your first message to ' + b.dataset.aname + ' (sent as a chat and an email):', 'Hi, I picked you up from the InstantAdPay holding tank so you have a sponsor who will actually help. Reply here and I will walk you through the first three steps.');
|
||||
if (note === null) return;
|
||||
try { const rr = await api('/api/my/tank/adopt', { who: b.dataset.adopt, note }); IAP.status('You are now the sponsor for ' + rr.name + '. Chat and email sent.', 'ok'); loadTank(); loadCoach(); }
|
||||
catch (e) { IAP.status(e.message, 'bad'); }
|
||||
}));
|
||||
} catch (e) {}
|
||||
}
|
||||
async function loadCoach() {
|
||||
loadTank();
|
||||
try {
|
||||
const r = await (await fetch('/api/my/coach')).json();
|
||||
const el = $('coachList'); if (!el || r.error) return;
|
||||
@@ -1022,11 +1069,19 @@
|
||||
+ '<span class="em">' + esc(x.label) + ' → ' + esc(x.next) + '</span>'
|
||||
+ '<span class="id">rung ' + x.rung + '/6</span>'
|
||||
+ '<span class="dt">' + (x.buyerCount ? x.buyerCount + ' buyer' + (x.buyerCount === 1 ? '' : 's') : '') + '</span>'
|
||||
+ '<button class="btn sec small" type="button" data-nudge="' + esc(x.email) + '" data-nname="' + esc(x.name) + '" data-say="' + esc(x.say) + '">Nudge</button></div>').join('');
|
||||
+ '<button class="btn sec small" type="button" data-nudge="' + esc(x.email) + '" data-nname="' + esc(x.name) + '" data-say="' + esc(x.say) + '">Nudge</button>'
|
||||
+ (x.free && x.address ? ' <button class="btn small" type="button" title="Pay it forward: send POL from your wallet to theirs for their first package" data-pif="' + esc(x.email) + '" data-pname="' + esc(x.name) + '" data-paddr="' + esc(x.address) + '">PIF</button>' : '')
|
||||
+ (x.free ? ' <button class="btn sec small" type="button" title="Pay it forward: give this free member to the holding tank so another member can coach them" data-release="' + esc(x.email) + '" data-rname="' + esc(x.name) + '">Release to tank</button>' : '') + '</div>').join('');
|
||||
el.querySelectorAll('[data-pif]').forEach(b => b.addEventListener('click', () => pif(b.dataset.pif, b.dataset.pname, b.dataset.paddr)));
|
||||
el.querySelectorAll('[data-nudge]').forEach(b => b.addEventListener('click', async () => {
|
||||
await openConvo(b.dataset.nudge, b.dataset.nname);
|
||||
const inp = $('chatInput'); if (inp) { inp.value = b.dataset.say.replace(/\{\{name\}\}/g, b.dataset.nname.replace(/^@/, '')); inp.focus(); }
|
||||
}));
|
||||
el.querySelectorAll('[data-release]').forEach(b => b.addEventListener('click', async () => {
|
||||
if (!confirm('Release ' + b.dataset.rname + ' to the holding tank? You stop being their sponsor and another member can adopt them.')) return;
|
||||
try { await api('/api/my/tank/release', { email: b.dataset.release }); IAP.status(b.dataset.rname + ' is in the holding tank.', 'ok'); loadCoach(); }
|
||||
catch (e) { IAP.status(e.message, 'bad'); }
|
||||
}));
|
||||
} catch (e) {}
|
||||
}
|
||||
// schedule chips on the campaign table (local time) + by-hour view bars
|
||||
|
||||
Reference in New Issue
Block a user