diff --git a/public/assets/my.js b/public/assets/my.js index 7b38a37..704f441 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -504,7 +504,7 @@ + r.campaigns.map(c => '' + c.name + '' + '' + c.type + (c.type === 'banner' && c.width ? ' ' + c.width + '×' + c.height + '' : '') + '' + '' + c.imps.toLocaleString() - + (c.impsNas ? ' +' + c.impsNas.toLocaleString() + ' net' : '') + + (c.impsNas ? ' +' + c.impsNas.toLocaleString() + ' network' : '') + '' + c.clicks + '' + '' + c.spent + '' + c.budget + '' + '' + (c.status === 'out' ? 'budget spent' : c.status) + '' diff --git a/public/my.html b/public/my.html index 258c44a..1c3ae30 100644 --- a/public/my.html +++ b/public/my.html @@ -644,7 +644,7 @@ - + diff --git a/server.js b/server.js index 9dede4b..cfee36b 100644 --- a/server.js +++ b/server.js @@ -121,7 +121,7 @@ async function frameCheck(url) { } async function boot() { await db.init({ dataDir: DATA_DIR }); // no-op without DATABASE_URL (JSON mode) - chain.init({ onEvent: ev => attachNames([ev]).then(a => pushFeed(a[0])).catch(() => pushFeed(ev)) }); + chain.init({ onEvent: ev => { attachNames([ev]).then(a => pushFeed(a[0])).catch(() => pushFeed(ev)); emailOnEvent(ev).catch(() => {}); } }); auth.init({ dataDir: DATA_DIR, chain, isProd: IS_PROD, site: 'instantadpay.com' }); accounts.init({ dataDir: DATA_DIR }); ads.init({ dataDir: DATA_DIR, chain }); @@ -245,6 +245,35 @@ async function nudgeReferrer(ref) { + 'https://instantadpay.com/my\n\nInstantAdPay').catch(e => console.error('nudge failed', e.message)); } catch (e) {} } +// welcome email on a new account: onboarding steps + who their sponsor is +async function sendWelcome(email, ref) { + try { + if (!mailer.hasKey()) return; + const spon = await accounts.sponsorOf(email); + const who = spon ? (spon.username ? '@' + spon.username : 'member #' + (spon.memberId || 0)) : ''; + const sponsorLine = who ? ('You joined through ' + who + ', your sponsor. They are there to help you get started, and you can message them anytime from your dashboard.\n\n') : ''; + mailer.send(email, 'Welcome to InstantAdPay', + 'Your free InstantAdPay account is ready.\n\n' + sponsorLine + + 'Getting started:\n' + + '1. Pick your username and fill out your profile.\n' + + '2. Grab your invite link and start sharing to build your line.\n' + + '3. Explore the ad packages when you are ready. Every payout settles on-chain, straight to your wallet.\n\n' + + 'Sign in anytime: https://instantadpay.com/my\n\nInstantAdPay').catch(() => {}); + } catch (e) {} +} +// on-chain event emails: a payout received, or a payout that passed you by +const weiToPol = w => { try { return (Number(BigInt(w) / (10n ** 14n)) / 10000).toString(); } catch (e) { return '?'; } }; +async function emailOnEvent(ev) { + if (!mailer.hasKey() || !ev) return; + const notify = async (memberId, subject, body) => { + if (!memberId) return; + const a = await accounts.byMemberId(memberId); + if (a && a.email) mailer.send(a.email, subject, body + '\n\nSee it on the live ledger: https://instantadpay.com/ledger\n\nInstantAdPay').catch(() => {}); + }; + if (ev.type === 'TierPaid') await notify(ev.recipientId, 'You just got paid on InstantAdPay', 'A level-' + ev.tier + ' payout of ' + weiToPol(ev.amountWei) + ' POL just landed in your wallet.'); + else if (ev.type === 'AwardPaid') await notify(ev.toId, 'You just got paid on InstantAdPay', weiToPol(ev.amountWei) + ' POL just landed in your wallet.'); + else if (ev.type === 'PassedUp') await notify(ev.skippedId, 'A payout passed you by on InstantAdPay', 'A level-' + ev.tier + ' payout passed you by because you were not qualified yet. Get qualified so you catch the next one.'); +} // ---- live feed (SSE) ---- const feedClients = new Set(); @@ -389,7 +418,7 @@ const server = http.createServer(async (req, res) => { const ref = parseCookies(req)['iap.sponsor'] || ''; const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged if (r.error) return json(res, 400, r); - if (r.created) nudgeReferrer(ref).catch(() => {}); + if (r.created) { nudgeReferrer(ref).catch(() => {}); sendWelcome(e, ref).catch(() => {}); } let memberId = 0; if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} } const token = await auth.mintSession({ email: r.account.email, address: r.account.address, memberId });