Line at a glance + My line: every level shows who made their $20+ buy and how many qualifying buyers of their own they have (Clinton); 60s member cache for line views
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,7 @@ FACTS:
|
||||
- PROMO TOOLKIT BY BADGE + AI COPY ENGINE (2026-09-14): Promo tools > AI Copy Engine shows the ladder: Free (links, posts, swipes, banners, wall, objections, shorts, badge pages), Spark (payouts on: one-tap campaign templates aimed at the member's link, printable handout with their QR at /handout/<username>), Surge (first qualifying buyer; the AI Copy Engine is live: posts, DMs, follow-ups, objection replies, emails, story posts, team broadcasts in the member's name with their link, honesty rules built in; 20 free generations a month), Circuit (60 free; Video Maker renders every promo video and short with the member's own end card and QR, hosted for download; split tester compares join angles by views, joins, buyers), Nexus (150 free; Leader Ops: three-level team triage with stalled flags and one-click nudges, AI-drafted team broadcasts, credit grants from the leader's earned pool to anyone in their line, co-branded join page showing the sponsor's bio, and the member's own partner code: welcome credits funded from the leader's pool at each redemption, plus a partner kit page /partners?ref=<username>&promo=CODE). After the free allowance each generation costs 10 ad credits from the earned pool. Credits are advertising, never money.
|
||||
- LINKED POSITIONS AND BADGES (2026-09-14): qualifying buyers on a member's linked positions count toward their achievement badges (Spark/Surge/Circuit/Nexus) and light their chip on their sponsor's line, and a badge once earned never regresses. The contract still pays levels 2 and 3 to each position based on that position's OWN qualifying buyers, so the Qualifying buyers tile shows the main position's count with the linked positions' count underneath.
|
||||
- NO-PAYOUT POSITIONS (2026-09-14): admin can list member ids (Settings > No-payout positions) whose wallets must never be paid again (e.g. a compromised key). Those positions stay linked for history and counting only: the buy-from picker disables them, a purchase from them is refused, and invite links that would place a new member under them (or under their upline chain) route to the fallback sponsor instead. The contract itself cannot change a member's wallet.
|
||||
- LINE AT A GLANCE, ALL LEVELS (2026-09-14): a gold chip on any level means that person made their $20+ buy (a qualifying buyer for their own sponsor); a small number on the chip is how many qualifying buyers of their own they have. My line rows say the same in words. Leaders use it to see who is one buyer short and encourage them.
|
||||
- MY LINE SPONSOR LINE (2026-09-14): every row on My line, all three levels, shows who sponsored that person ("sponsored by @name", or "you" for directs).
|
||||
- MY LINE ACTIVITY DROP-DOWN (2026-09-14): every row on My line, all three levels, has an Activity button that opens that person's activity: last seen, joined, stage and next step, ads viewed today and claim streak, campaigns active, link views in 30 days and joins, directs and qualifying buyers, wallet/payouts, badges, and a Working or Quiet verdict. Read-only, phone friendly.
|
||||
- GETTING STARTED CARD (2026-09-14): the top of every new member's Overview shows four steps (username, link wallet, switch on payouts, first package) with the current one explained and one button that opens the right tab and highlights the control (Wallet tab > Link wallet; then Activate payouts). Encouragement only: it can be hidden, and members who only want to view ads and earn credits are never blocked. It disappears once all four are done.
|
||||
|
||||
+3
-3
@@ -430,11 +430,11 @@
|
||||
const levels = r.levels || [];
|
||||
const total = levels.reduce((n, L) => n + L.members.length, 0);
|
||||
if ($('treeSub')) $('treeSub').textContent = total ? total + ' across 3 levels' : 'share your link to grow';
|
||||
const chip = (m, q) => '<span class="lt-node' + (q ? ' qualified' : (m.email ? '' : ' deep')) + '" title="' + esc(m.name || 'member') + (q ? ' · qualified buyer' : '') + '">' + esc(String(m.name || 'M').replace(/^@/, '').slice(0, m.own ? 20 : 14)) + '</span>';
|
||||
const chip = (m, q) => '<span class="lt-node' + (q ? ' qualified' : (m.email ? '' : ' deep')) + '" title="' + esc(m.name || 'member') + (q ? ' · bought a $20+ package (counts for their sponsor)' : ' · no $20+ buy yet') + (m.buyers ? ' · ' + m.buyers + ' qualifying buyer' + (m.buyers === 1 ? '' : 's') + ' of their own' : '') + '">' + esc(String(m.name || 'M').replace(/^@/, '').slice(0, m.own ? 20 : 14)) + (m.buyers ? '<i class="lt-n" title="their own qualifying buyers">' + m.buyers + '</i>' : '') + '</span>';
|
||||
const rowFor = lvl => {
|
||||
const L = levels.find(x => x.level === lvl); const members = L ? L.members : [];
|
||||
// gold = the contract counted this member as one of your qualifying buyers (not "the first N chips")
|
||||
let html = members.map(m => chip(m, !!m.qualified)).join('');
|
||||
let html = members.map(m => chip(m, !!(m.qualified || m.bought))).join(''); // gold on every level = made their $20+ buy
|
||||
if (lvl <= 2 && members.length < (lvl === 1 ? 2 : 4)) html += '<span class="lt-node open">+ open</span>';
|
||||
if (!html) html = '<span class="lt-node open">+ open</span>';
|
||||
return '<div class="lt-row"><span class="lt-cap">L' + lvl + '</span><div class="lt-nodes">' + html + '</div></div>';
|
||||
@@ -1403,7 +1403,7 @@
|
||||
el.innerHTML = r.levels.map(L => !L.members.length ? '' :
|
||||
'<div class="lin-lvl"><div class="cap">Level ' + L.level + ' · ' + L.members.length + (L.level === 1 ? ' direct' : '') + '</div>'
|
||||
+ L.members.map(m => '<div class="lin-row' + (m.own ? ' own' : '') + '"><span class="nm">' + esc(m.name) + (m.own ? ' <span class="badge">yours</span>' : '') + '</span>'
|
||||
+ '<span class="em">' + (m.email ? esc(m.email) : (m.memberId ? '<span class="id">#' + m.memberId + '</span>' : '')) + (m.sponsor ? '<span class="sp">sponsored by ' + esc(m.sponsor) + '</span>' : '') + '</span>'
|
||||
+ '<span class="em">' + (m.email ? esc(m.email) : (m.memberId ? '<span class="id">#' + m.memberId + '</span>' : '')) + (m.sponsor ? '<span class="sp">sponsored by ' + esc(m.sponsor) + '</span>' : '') + (m.own ? '' : '<span class="sp">' + (m.bought ? '$20+ buy ✓' : 'no $20+ buy yet') + (m.buyers ? ' · ' + m.buyers + ' qualifying buyer' + (m.buyers === 1 ? '' : 's') + ' of their own' : '') + '</span>') + '</span>'
|
||||
+ '<span class="earn' + (m.earnedWei && m.earnedWei !== '0' ? ' on' : '') + '" title="POL this person has paid you so far">'
|
||||
+ (m.earnedWei && m.earnedWei !== '0' ? '+' + IAP.fmtPol(m.earnedWei) + ' POL' : '0.00 POL') + '</span>'
|
||||
+ '<span class="dt">' + new Date(m.joined).toLocaleDateString() + '</span>'
|
||||
|
||||
@@ -632,6 +632,7 @@ img{max-width:100%}
|
||||
.lt-node.deep{color:var(--muted)}
|
||||
.lt-node.qualified{border-color:#ffb238;color:#ffd15c;box-shadow:0 0 0 1px rgba(255,177,56,.35)}
|
||||
.lt-node.open{border-style:dashed;color:var(--muted);background:transparent}
|
||||
.lt-node .lt-n{display:inline-block;font-style:normal;font-size:10px;line-height:16px;min-width:16px;text-align:center;border-radius:8px;background:rgba(255,177,56,.18);color:#ffd15c;margin-left:6px;padding:0 4px;font-variant-numeric:tabular-nums}
|
||||
|
||||
.sh-report{display:block;text-align:center;margin-top:10px;color:rgba(255,255,255,.45);font-size:12px}
|
||||
.sh-report:hover{color:rgba(255,255,255,.8)}
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="icon" type="image/png" href="/logo-icon.png">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260914g">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260914h">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
<div class="card" id="lineTreeCard">
|
||||
<div class="card-head"><h3>Your line at a glance</h3><span class="sub" id="treeSub"></span></div>
|
||||
<div id="lineTree" class="line-tree"><p class="muted small">Loading your line…</p></div>
|
||||
<p class="small muted" id="treeLegend" style="margin:10px 0 0;line-height:2"><span class="lt-node qualified" style="font-size:11px;padding:2px 8px">name</span> qualifying buyer: bought a $20+ package, counts toward your levels · <span class="lt-node" style="font-size:11px;padding:2px 8px">name</span> joined, no $20+ buy yet · <span class="lt-node open" style="font-size:11px;padding:2px 8px">+ open</span> spot still open</p>
|
||||
<p class="small muted" id="treeLegend" style="margin:10px 0 0;line-height:2"><span class="lt-node qualified" style="font-size:11px;padding:2px 8px">name</span> bought a $20+ package: counts as a qualifying buyer for their sponsor (on level 1, that is you) · <span class="lt-node" style="font-size:11px;padding:2px 8px">name<i class="lt-n">2</i></span> their own qualifying buyers · <span class="lt-node" style="font-size:11px;padding:2px 8px">name</span> joined, no $20+ buy yet · <span class="lt-node open" style="font-size:11px;padding:2px 8px">+ open</span> spot still open</p>
|
||||
</div>
|
||||
<div class="card" id="badgeCard" hidden>
|
||||
<div class="card-head"><h3>Your achievements</h3><span class="sub">unlock as you build</span></div>
|
||||
@@ -984,7 +984,7 @@
|
||||
<script src="/assets/common.js?v=20260914a"></script>
|
||||
<script src="/assets/wallet.js?v=20260911a"></script>
|
||||
<script src="/assets/promo.js?v=20260911a"></script>
|
||||
<script src="/assets/my.js?v=20260914k"></script>
|
||||
<script src="/assets/my.js?v=20260914l"></script>
|
||||
<script src="/assets/chat.js?v=20260907l"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -136,6 +136,12 @@ const catalogCache = { at: 0, products: null };
|
||||
const wallUnlockedFor = bc => (bc >= 5 ? 3 : bc >= 2 ? 2 : 1);
|
||||
// every on-chain member id this session controls: the main wallet plus linked
|
||||
// positions (Qualified Start). Credits pool across them on the dashboard.
|
||||
const memberInfoCache = new Map(); // id -> { t, v } (60s): line views ask for every member's on-chain row
|
||||
async function cachedMember(id) {
|
||||
const c = memberInfoCache.get(id); if (c && Date.now() - c.t < 60000) return c.v;
|
||||
let v = null; try { v = await chain.member(id); } catch (e) {}
|
||||
memberInfoCache.set(id, { t: Date.now(), v }); return v;
|
||||
}
|
||||
async function myMemberIds(s) {
|
||||
const main = await auth.refreshMemberId(s);
|
||||
const ids = main ? [main] : [];
|
||||
@@ -1686,7 +1692,12 @@ const server = http.createServer(async (req, res) => {
|
||||
// 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);
|
||||
// Clinton's ask (2026-09-14): on levels 2 and 3 show who has made their $20+ buy (counted for their own
|
||||
// sponsor) and how many qualifying buyers of their own they have, so a leader can see who is one short
|
||||
const onchain = {};
|
||||
for (const m of levels.flatMap(L => L.members)) { let bought = false, buyers = 0; for (const id of idsOf(m)) { const mm = await cachedMember(id); if (mm) { if (mm.countedAsBuyer) bought = true; buyers += mm.buyerCount || 0; } } onchain[m.email] = { bought, buyers }; }
|
||||
const out = levels.map(L => ({ level: L.level, members: L.members.map(m => ({
|
||||
bought: onchain[m.email].bought, buyers: onchain[m.email].buyers,
|
||||
memberId: m.memberId || 0,
|
||||
sponsor: sponsorOf(m),
|
||||
name: m.username ? '@' + m.username : m.memberId ? 'member #' + m.memberId : 'member',
|
||||
|
||||
Reference in New Issue
Block a user