ce49dbbe4d
- /join/<token>[?v=angle] now serves a capture page (email first, wallet later) with angle-matched hook copy, sponsor line, worked-example ledger, how-it-works, live package ladder, and per-angle og tags (og:url keeps ?v=). The sponsor cookie is set exactly as before; ?v= is remembered and stored on the account as joined_via (shown in admin Members). - drip.js: 4-step getting-started sequence (24h/48h/96h/168h) queued when a free account is created with the pre-checked opt-in; ticker every 10 min; signed /unsubscribe link in every email; MySQL + JSON storage. - Admin > Settings: edit the sequence as JSON, reset to defaults, send any step to the admin inbox; Overview shows follow-ups in flight. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
68 lines
4.9 KiB
JavaScript
68 lines
4.9 KiB
JavaScript
// Invite / lead-capture page: /join/<token>[?v=<angle>]
|
|
// Email first (code sign-in creates the account), wallet later inside the
|
|
// member area. The angle only changes the hook copy; the sponsor cookie was
|
|
// set by the server when this page was served.
|
|
(function () {
|
|
const $ = id => document.getElementById(id);
|
|
const ANGLES = {
|
|
instant: { eyebrow: 'Same-transaction payouts', h: 'Paid before the page <em>reloads.</em>', lead: 'What if your commission landed before the thank-you page finished loading? On InstantAdPay that is not a metaphor. A smart contract on Polygon splits every ad package the moment it sells.' },
|
|
adspend: { eyebrow: 'For marketers who buy traffic', h: 'You were buying <em>ads anyway.</em>', lead: 'Every ad dollar you ever spent went one direction: out. Here the ad spend in your line pays you, in the same transaction, on a public ledger. Seven formats, dwell-timed views, packages from $5.' },
|
|
free: { eyebrow: 'Costs nothing to try', h: 'Watch first. <em>Spend never.</em>', lead: 'Join free, view a few ads, earn credits, and run your first campaign for zero dollars. Upgrade only if you want more reach.' },
|
|
ledger: { eyebrow: 'No back office', h: 'No back office. <em>No payday.</em>', lead: 'Your last program paid you on the 15th, if it paid you. Here every payout is a public transaction on Polygon you can read yourself, and nothing is ever held.' },
|
|
two: { eyebrow: 'The referral side, exactly as coded', h: 'Two buyers open <em>level two.</em>', lead: 'Every direct buyer pays you 50 percent from their first package. Two qualifying buyers open level two, five open level three. Written as constants in a verified contract.' }
|
|
};
|
|
const v = new URLSearchParams(location.search).get('v');
|
|
const a = v && ANGLES[v];
|
|
if (a) { $('jnEyebrow').textContent = a.eyebrow; $('jnHead').innerHTML = a.h; $('jnLead').textContent = a.lead; }
|
|
|
|
async function api(path, body) {
|
|
const r = await (await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) })).json();
|
|
if (r.error) throw new Error(r.error);
|
|
return r;
|
|
}
|
|
const err = m => { const e = $('jnErr'); e.textContent = m || ''; e.hidden = !m; };
|
|
function busy(btn, fn) {
|
|
return async () => { if (btn.disabled) return; btn.disabled = true; err(''); try { await fn(); } catch (e) { err(e.message || 'Something went wrong.'); } finally { btn.disabled = false; } };
|
|
}
|
|
const send = busy($('jnSend'), async () => {
|
|
const r = await api('/api/auth/email/start', { email: $('jnEmail').value });
|
|
$('jnCodeRow').hidden = false; $('jnVerify').hidden = false; $('jnSend').hidden = true; $('jnResend').hidden = false;
|
|
if (r.devCode) $('jnCode').value = r.devCode;
|
|
IAP.status(r.sent ? 'Code sent. Check your inbox (and spam, the first time).' : 'Dev mode: code filled in.', 'ok');
|
|
$('jnCode').focus();
|
|
});
|
|
$('jnSend').addEventListener('click', send);
|
|
$('jnResend').addEventListener('click', busy($('jnResend'), async () => {
|
|
const r = await api('/api/auth/email/start', { email: $('jnEmail').value });
|
|
if (r.devCode) $('jnCode').value = r.devCode;
|
|
IAP.status('Fresh code sent.', 'ok');
|
|
}));
|
|
$('jnVerify').addEventListener('click', busy($('jnVerify'), async () => {
|
|
await api('/api/auth/email/verify', { email: $('jnEmail').value, code: $('jnCode').value, newsletter: !!$('jnNews').checked, followups: !!$('jnNews').checked });
|
|
IAP.status('You are in. Taking you to your dashboard…', 'ok');
|
|
location.href = '/my';
|
|
}));
|
|
$('jnEmail').addEventListener('keydown', e => { if (e.key === 'Enter') ($('jnVerify').hidden ? $('jnSend') : $('jnVerify')).click(); });
|
|
$('jnCode').addEventListener('keydown', e => { if (e.key === 'Enter') $('jnVerify').click(); });
|
|
|
|
// sponsor line
|
|
fetch('/api/sponsor').then(r => r.json()).then(sp => {
|
|
if (sp && sp.invited && sp.name) {
|
|
$('jnSponName').textContent = sp.name;
|
|
if (sp.avatarUrl) { $('jnSponImg').src = sp.avatarUrl; $('jnSponImg').hidden = false; }
|
|
$('jnSpon').hidden = false;
|
|
}
|
|
}).catch(() => {});
|
|
// package ladder (dollar constants + live POL quote)
|
|
const NAMES = { 1: 'Micro', 2: 'Activation', 3: 'Builder', 4: 'Growth', 5: 'Leader' };
|
|
fetch('/api/catalog').then(r => r.json()).then(c => {
|
|
const wrap = $('jnLadder'); if (!wrap || !c.products) return;
|
|
wrap.innerHTML = c.products.filter(p => p.active !== false).map(p =>
|
|
'<div class="jn-pk"><div class="n">' + (NAMES[p.id] || 'Package ' + p.id) + '</div><div class="p">' + IAP.fmtUsd(p.priceCents) + '</div><div class="c">' + Number(p.creditAmount).toLocaleString() + ' credits</div>'
|
|
+ (p.costWei ? '<div class="small muted">' + IAP.fmtPol(p.costWei) + ' POL now</div>' : '') + '</div>').join('');
|
|
}).catch(() => {});
|
|
fetch('/api/stats').then(r => r.json()).then(s => {
|
|
if (s && s.onchainMembers) $('jnStats').textContent = s.onchainMembers.toLocaleString() + (s.onchainMembers === 1 ? ' member' : ' members') + ' on-chain so far.';
|
|
}).catch(() => {});
|
|
})();
|