Email notifications (welcome + payout + passed-up) and net->network label
- Welcome email on new account: onboarding steps + who their sponsor is. - On-chain event emails: a payout received (TierPaid/AwardPaid) and a payout that passed you by unqualified (PassedUp), wired into the chain event stream. - Campaign stats: "+N net" relabeled to "+N network" (clearer than "net"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -504,7 +504,7 @@
|
|||||||
+ r.campaigns.map(c => '<tr><td><b>' + c.name + '</b></td>'
|
+ r.campaigns.map(c => '<tr><td><b>' + c.name + '</b></td>'
|
||||||
+ '<td>' + c.type + (c.type === 'banner' && c.width ? ' <span class="muted small">' + c.width + '×' + c.height + '</span>' : '') + '</td>'
|
+ '<td>' + c.type + (c.type === 'banner' && c.width ? ' <span class="muted small">' + c.width + '×' + c.height + '</span>' : '') + '</td>'
|
||||||
+ '<td class="num">' + c.imps.toLocaleString()
|
+ '<td class="num">' + c.imps.toLocaleString()
|
||||||
+ (c.impsNas ? ' <span class="muted small" title="views across the network">+' + c.impsNas.toLocaleString() + ' net</span>' : '')
|
+ (c.impsNas ? ' <span class="muted small" title="views across the network">+' + c.impsNas.toLocaleString() + ' network</span>' : '')
|
||||||
+ '</td><td class="num">' + c.clicks + '</td>'
|
+ '</td><td class="num">' + c.clicks + '</td>'
|
||||||
+ '<td class="num">' + c.spent + '</td><td class="num">' + c.budget + '</td>'
|
+ '<td class="num">' + c.spent + '</td><td class="num">' + c.budget + '</td>'
|
||||||
+ '<td>' + (c.status === 'out' ? '<span class="badge amber">budget spent</span>' : c.status) + '</td>'
|
+ '<td>' + (c.status === 'out' ? '<span class="badge amber">budget spent</span>' : c.status) + '</td>'
|
||||||
|
|||||||
+1
-1
@@ -644,7 +644,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260907n"></script>
|
<script src="/assets/common.js?v=20260907n"></script>
|
||||||
<script src="/assets/wallet.js?v=20260907f"></script>
|
<script src="/assets/wallet.js?v=20260907f"></script>
|
||||||
<script src="/assets/my.js?v=20260907n"></script>
|
<script src="/assets/my.js?v=20260907o"></script>
|
||||||
<script src="/assets/chat.js?v=20260907l"></script>
|
<script src="/assets/chat.js?v=20260907l"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ async function frameCheck(url) {
|
|||||||
}
|
}
|
||||||
async function boot() {
|
async function boot() {
|
||||||
await db.init({ dataDir: DATA_DIR }); // no-op without DATABASE_URL (JSON mode)
|
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' });
|
auth.init({ dataDir: DATA_DIR, chain, isProd: IS_PROD, site: 'instantadpay.com' });
|
||||||
accounts.init({ dataDir: DATA_DIR });
|
accounts.init({ dataDir: DATA_DIR });
|
||||||
ads.init({ dataDir: DATA_DIR, chain });
|
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));
|
+ 'https://instantadpay.com/my\n\nInstantAdPay').catch(e => console.error('nudge failed', e.message));
|
||||||
} catch (e) {}
|
} 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) ----
|
// ---- live feed (SSE) ----
|
||||||
const feedClients = new Set();
|
const feedClients = new Set();
|
||||||
@@ -389,7 +418,7 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const ref = parseCookies(req)['iap.sponsor'] || '';
|
const ref = parseCookies(req)['iap.sponsor'] || '';
|
||||||
const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged
|
const r = await accounts.ensure(e, ref); // first touch wins; existing accounts unchanged
|
||||||
if (r.error) return json(res, 400, r);
|
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;
|
let memberId = 0;
|
||||||
if (r.account.address) { try { memberId = await chain.memberIdByAccount(r.account.address); } catch (err) {} }
|
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 });
|
const token = await auth.mintSession({ email: r.account.email, address: r.account.address, memberId });
|
||||||
|
|||||||
Reference in New Issue
Block a user