Badges count qualifying buyers across linked positions (never regress); line chips light on any of a member's positions; buyers tile notes linked-position buyers

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-14 10:44:36 -05:00
parent 1fad5bdab2
commit 0a19e77e7f
4 changed files with 17 additions and 7 deletions
+13 -4
View File
@@ -1099,7 +1099,12 @@ const server = http.createServer(async (req, res) => {
try {
const mm = await chain.member(memberId);
out.buyerCount = mm.buyerCount;
const bal = await ads.balances((await myMemberIds(s)).ids, s.email);
const mine = await myMemberIds(s);
// linked positions are the member's own: their qualifying buyers count toward the account (Jim, 2026-09-14)
out.buyerCountMain = mm.buyerCount; out.buyerCountPositions = 0;
for (const id of mine.ids) if (id && id !== memberId) { try { out.buyerCountPositions += (await chain.member(id)).buyerCount || 0; } catch (e) {} }
out.buyerCountAll = out.buyerCountMain + out.buyerCountPositions; // badges count every position; levels the contract pays #main on depend on buyerCount alone
const bal = await ads.balances(mine.ids, s.email);
out.credits = bal.total; out.creditedCredits = bal.credited; out.earnedCredits = bal.earned; out.inCampaigns = bal.inCampaigns; out.availableCredits = bal.available;
} catch (e) { out.chainReadError = true; }
let earned = 0n, n = 0;
@@ -1114,12 +1119,13 @@ const server = http.createServer(async (req, res) => {
// achievement milestones (same ladder as the Overview stepper) + one-time credit
// bonuses — computed AFTER buyerCount is read from chain above (else always 0)
{
const bc = out.buyerCount || 0;
const bc = out.buyerCountAll != null ? out.buyerCountAll : (out.buyerCount || 0);
const reached = [];
if (out.memberId) reached.push('payouts');
if (bc >= 1) reached.push('firstBuyer');
if (bc >= 2) reached.push('level2');
if (bc >= 5) reached.push('level3');
try { for (const k of await ads.milestonesOf(out.email)) if (!reached.includes(k)) reached.push(k); } catch (e) {} // a badge once earned stays earned
out.milestonesReached = reached;
if (out.email && reached.length) out.milestonesGranted = await ads.grantMilestones(out.email, reached);
}
@@ -1677,6 +1683,9 @@ const server = http.createServer(async (req, res) => {
const label = a => a.username ? '@' + a.username : (a.memberId ? 'member #' + a.memberId : 'member');
for (const a of [meAcct, ...levels.flatMap(L => L.members)].filter(Boolean)) { const n = label(a); if (a.username) byTok[String(a.username).toLowerCase()] = n; if (a.code) byTok[String(a.code).toLowerCase()] = n; if (a.memberId) byTok[String(a.memberId)] = n; }
const sponsorOf = m => { const t = String(m.sponsorRef || '').toLowerCase(); if (!t) return null; if (meAcct && (t === String(meAcct.username || '').toLowerCase() || t === String(meAcct.code || '').toLowerCase() || t === String(meAcct.memberId || ''))) return 'you'; return byTok[t] || ('@' + t); };
// a member's linked positions are theirs too: a qualifying buy from one of them lights their chip
const posIds = {}; for (const m of levels.flatMap(L => L.members)) { try { posIds[m.email] = (await accounts.positions(m.email)).map(p => p.memberId).filter(Boolean); } catch (e) { posIds[m.email] = []; } }
const idsOf = m => [m.memberId, ...(posIds[m.email] || [])].filter(Boolean);
const out = levels.map(L => ({ level: L.level, members: L.members.map(m => ({
memberId: m.memberId || 0,
sponsor: sponsorOf(m),
@@ -1684,8 +1693,8 @@ const server = http.createServer(async (req, res) => {
ref: m.code || null, // opens the activity drop-down (Clinton's ask, 2026-09-14): any of the three levels
email: L.level === 1 ? m.email : null, // directs only
joined: m.created,
qualified: !!(m.memberId && qualified.has(m.memberId)),
earnedWei: (m.memberId && earnedBy[m.memberId]) || '0' })) }));
qualified: idsOf(m).some(id => qualified.has(id)),
earnedWei: idsOf(m).reduce((n, id) => n + BigInt(earnedBy[id] || '0'), 0n).toString() })) }));
// the member's own linked positions sit on level 1 too, labelled as theirs
if (own.length) {
if (!out.find(L => L.level === 1)) out.unshift({ level: 1, members: [] });