diff --git a/accounts.js b/accounts.js index 3e88f33..4ceba60 100644 --- a/accounts.js +++ b/accounts.js @@ -82,6 +82,13 @@ const J = { async byEmail(e) { return pub(this.db.byEmail[e]); }, async byAddress(a) { const e = this.db.byAddress[a]; return e ? pub(this.db.byEmail[e]) : null; }, async byCode(c) { const e = this.db.byCode[c]; return e ? pub(this.db.byEmail[e]) : null; }, + async listByReferrer(refs) { + const set = new Set(refs.filter(Boolean).map(String)); + return Object.values(this.db.byEmail) + .filter(a => set.has(String(a.sponsorRef || ''))) + .sort((a, b) => b.created - a.created) + .slice(0, 200).map(pub); + }, async linkWallet(e, a) { const acct = this.db.byEmail[e]; if (!acct) return { error: 'No such account.' }; @@ -133,6 +140,13 @@ const D = { async byEmail(e) { const r = await db.q('SELECT * FROM accounts WHERE email=?', [e]); return rowPub(r[0]); }, async byAddress(a) { const r = await db.q('SELECT * FROM accounts WHERE address=?', [a]); return rowPub(r[0]); }, async byCode(c) { const r = await db.q('SELECT * FROM accounts WHERE code=?', [c]); return rowPub(r[0]); }, + async listByReferrer(refs) { + const clean = refs.filter(Boolean).map(String); + if (!clean.length) return []; + const rows = await db.q('SELECT * FROM accounts WHERE sponsor_ref IN (' + clean.map(() => '?').join(',') + + ') ORDER BY created DESC LIMIT 200', clean); + return rows.map(rowPub); + }, async linkWallet(e, a) { const cur = await this.byEmail(e); if (!cur) return { error: 'No such account.' }; @@ -166,6 +180,7 @@ async function ensure(email, sponsorRef) { async function byEmail(email) { return impl().byEmail(normEmail(email)); } async function byAddress(address) { return impl().byAddress(normAddr(address)); } async function byCode(code) { return impl().byCode(String(code || '').toLowerCase()); } +async function listByReferrer(refs) { return impl().listByReferrer(refs || []); } async function linkWallet(email, address) { const a = normAddr(address); if (!/^0x[0-9a-f]{40}$/.test(a)) return { error: 'Bad wallet address.' }; @@ -173,4 +188,4 @@ async function linkWallet(email, address) { } async function count() { return impl().count(); } -module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, linkWallet, count }; +module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, listByReferrer, linkWallet, count }; diff --git a/public/assets/my.js b/public/assets/my.js index c28f88e..c8563c7 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -11,12 +11,60 @@ return r; } + function nextMove(d) { + if (!d.address) return 'Grab your link below and start sharing today. Then link your wallet (one free signature) so every payment can lock to you before your people start buying.'; + if (!d.memberId) return 'Your wallet is linked. Switch on payouts above (one free transaction) and every purchase in your line pays you the moment it happens.'; + if (d.buyerCount === 0) return 'Payouts are on and your link is live. Your next milestone: your first buyer of $20 or more. Every direct pays you 50 percent from their very first package.'; + if (d.buyerCount === 1) return 'One qualifying buyer down, one to go. Your next $20+ buyer unlocks level 2: 20 percent of everything your people’s people buy.'; + if (d.buyerCount < 5) return 'Level 2 is open. ' + (5 - d.buyerCount) + ' more qualifying buyer(s) unlock level 3 and the full three-level flow.'; + return 'Fully qualified. Every level pays you, and you catch the pass-ups that under-qualified positions below you let slip. Keep sharing and keep your campaigns running.'; + } + async function loadDashboard() { + try { + const d = await (await fetch('/api/my/dashboard')).json(); + if (d.error) return; + $('dbCredits').textContent = (d.credits || 0).toLocaleString(); + $('dbEarned').textContent = IAP.fmtPol(d.earnedWei || '0'); + $('dbBuyers').textContent = d.buyerCount || 0; + $('dbTeam').textContent = (d.referrals || []).length; + $('nextMove').textContent = nextMove(d); + $('qualFill').style.width = Math.min(100, (d.buyerCount || 0) * 20) + '%'; + const wrap = $('rosterWrap'); + if ((d.referrals || []).length) { + $('rosterEmpty').hidden = true; + let t = wrap.querySelector('table'); + if (t) t.remove(); + t = document.createElement('table'); + t.className = 'roster'; + t.innerHTML = d.referrals.map(r => '