Files
instantadpay/public/assets/join.js
T

91 lines
8.4 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.',
points: ['A verified contract splits every package in the same transaction it sells: 50 percent to the sponsor, 20 and 10 up the line, 20 to the platform.', 'It lands in your own wallet in seconds. There is no balance to withdraw because nothing is ever held.', 'Every payment is public on Polygonscan, so you can check the claim before you spend a dollar.'],
cta: 'See a payout land in seconds', sub: 'Free account by email. No password, no wallet today. Your invite link is live the moment you are in.' },
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.',
points: ['Seven formats: banners, text ads, login ads, solo ads to member inboxes, video, featured links and verified visits.', 'Views are timed on the server, so a real person saw your ad. Banners and text also run across a partner ad network.', 'When anyone in your line buys ads, the contract pays you in that same transaction.'],
cta: 'Put your next ad dollar where it pays you back', sub: 'Free account by email. See every format and the live rates before you buy anything.' },
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.',
points: ['Join with just an email. No password, no wallet today.', 'View a few ads each day and earn credits you can spend on your own banner or text campaign.', 'Buy a package only if you want more reach. They start at $5, and every payout on them is public.'],
cta: 'Start for free. No card, no wallet.', sub: 'Type your email and we send a 6-digit code. That is the whole signup.' },
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.',
points: ['Every payout is a public transaction on Polygon. Click it, read it, verify it yourself.', 'The contract holds zero balance. It splits and sends in the same transaction, with no pause switch and no upgrade path.', 'No back office means nobody can delay, reverse or review your commission.'],
cta: 'Check the ledger yourself, then decide', sub: 'Free account by email. The public ledger and the verified contract are one click from your dashboard.' },
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.',
points: ['Every direct buyer pays you 50 percent from their very first package.', 'Two qualifying buyers open level two at 20 percent. Five open level three at 10 percent. Constants in a verified contract.', 'Until a level opens, its share climbs to the next qualified member above, so the plan rewards the people who build.'],
cta: 'Start your line. Two buyers is the target.', sub: 'Free account by email. Your invite link and the team-building plays are waiting inside.' }
};
const v = new URLSearchParams(location.search).get('v');
const a = v && ANGLES[v];
if (a) {
document.body.classList.add('squeeze'); // server sets it too; this covers cached HTML
$('jnEyebrow').textContent = '· ' + a.eyebrow; $('jnHead').innerHTML = a.h; $('jnLead').textContent = a.lead;
if (a.cta) { $('jnCapH').textContent = a.cta; $('jnCapSub').textContent = a.sub || ''; }
// the matching hook video + this angle's three points replace the worked example
const VID = 'https://coolify-saasytop.nyc3.digitaloceanspaces.com/promo/';
const vid = $('jnVideo'); vid.src = VID + v + '.mp4'; vid.poster = VID + v + '.jpg';
$('jnPoints').innerHTML = (a.points || []).map(t => '<li>' + t.replace(/[&<>]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c])) + '</li>').join('');
$('jnMock').hidden = true; $('jnAngle').hidden = false; $('jnPoints').hidden = false;
}
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;
// the last thing they see before the account is created: who they are joining under
if (sponsorName) { $('jnUnder').textContent = 'Joining under ' + sponsorName + '. Not who invited you? Open their invite link first, then come back for the code.'; $('jnUnder').hidden = false; }
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?welcome=1'; // dashboard runs the username step + welcome tour on arrival
}));
$('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 (the link opened most recently sets the sponsor; it locks at account creation)
let sponsorName = '';
fetch('/api/sponsor').then(r => r.json()).then(sp => {
if (sp && sp.invited && sp.name) {
sponsorName = 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(() => {});
})();