Add "Your pipeline" widget to member dashboards
Makes the depth-matched payment routing visible per member: walks their subtree and shows who is ONE upgrade away from paying them (member at depth D whose level equals D — their next upgrade routes to this position first), the exact amounts from the contract's cost tables (exposed via the member API), eligibility warnings (needs 2 directs / needs level X), a ready-now total, and whether it covers their own next upgrade. Members building toward their pay-you level are counted, with the takeaway line "helping your leg upgrade IS your income." Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -454,6 +454,7 @@ async function memberPublic(id) {
|
||||
out.income = inc.map(p => ({ fromId: p.fromId, pol: p.pol, ts: p.ts, desc: describeIncome(p.fromTier, p.level, p.pol) })).reverse().slice(0, 50);
|
||||
} catch (e) { out.income = []; }
|
||||
out.subtree = getSubtree(id, 99); // full leg — the dashboard drills down client-side
|
||||
if (costs) out.upgradeCosts = costs.up; // {1:[8 std], 2:[8 prem]} POL — for the pipeline widget
|
||||
// moving-link automation: the first position below (breadth-first, left→right)
|
||||
// that still needs directs is where a qualified member's effort goes next
|
||||
if (out.subtree) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:6px"><div><div class="eyebrow">Member position</div><h1 style="margin:6px 0 0">Position <span class="gold" id="dTitle">#—</span> <span id="dVerified" class="live-badge" style="vertical-align:middle;margin-left:8px"><span class="dot"></span> Verified on-chain</span> <span id="dQualified"></span></h1></div><button id="switchId" class="btn btn-secondary btn-sm">Look up another ID</button></div>
|
||||
<div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div>
|
||||
<div class="table-card" style="margin-bottom:18px"><div style="display:flex;justify-content:space-between;gap:12px;align-items:flex-start;flex-wrap:wrap"><div><h2 style="margin:0 0 4px">Your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 14px">Your position's matrix — the rollup line on each card counts everyone underneath, all the way down. Click a position to drill into that leg. Open slots are where the next placements land.</p></div><button id="dTreeToggle" class="btn btn-secondary btn-sm">List view</button></div><div id="dTreeNav" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="dTree"></div><p class="micro" style="margin:14px 0 0"><span class="mtp-qmark" style="position:static;display:inline-grid;vertical-align:middle">✓</span> qualified (2/2 directs) · <span class="mt-badge mt-prem">P</span> Premium · <span class="mt-badge">S</span> Standard · ⬇ everyone below that position (all generations) and the POL they've earned · <span style="color:var(--teal)">↧ spillover</span> = placed there by upline activity; only members who join with a position's own ID count toward its 2/2</p><div id="dSpillNote" class="hidden"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Share this position</h2><div id="dShare"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px;border-color:var(--gold)"><h2 style="margin:0 0 4px">Just joined under this position?</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Welcome to the team! Enter the <strong>new member ID</strong> the RM Circle dApp gave you — we'll verify it on the blockchain and let the team know you're in.</p><form id="dJoinForm" style="display:grid;gap:8px;max-width:480px"><input name="newId" class="input" inputmode="numeric" pattern="[0-9]{1,10}" maxlength="10" placeholder="Your NEW RM Circle ID (numbers only)" required><input name="memberName" class="input" maxlength="60" placeholder="Your name or Telegram @handle" required><button class="btn btn-primary">Submit My ID →</button></form><div id="dJoinMsg" style="margin-top:10px;font-size:14px"></div></div>
|
||||
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Payments received</h2><p style="color:var(--muted);font-size:13px;margin:0 0 10px">Every payment your position has received, straight from the smart contract.</p><div id="dIncome"></div></div>
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
document.getElementById('dIncome').innerHTML=inc.length
|
||||
?`<div class="table-wrap"><table class="table" style="min-width:520px"><thead><tr><th>When</th><th>From member</th><th>For</th><th>Amount</th></tr></thead><tbody>${inc.map(p=>`<tr><td>${date(p.ts)}</td><td>#${p.fromId}</td><td>${esc(p.desc)}</td><td><strong>${fmt(p.pol)} POL</strong></td></tr>`).join('')}</tbody></table></div>`
|
||||
:'<div class="empty">No payments yet — they appear here the moment they land on-chain.</div>';
|
||||
renderPipeline(d);
|
||||
renderShare(d);
|
||||
document.getElementById('dLineage').innerHTML=d.uplineChain&&d.uplineChain.length
|
||||
?`<strong style="color:var(--text)">#${d.id}</strong> → ${d.uplineChain.map(i=>'#'+i).join(' → ')} <span style="color:#8498aa">(root)</span>`
|
||||
@@ -110,6 +111,49 @@
|
||||
return q.createSvgTag({cellSize:4,margin:3,scalable:true});
|
||||
}catch(e){return ''}
|
||||
}
|
||||
const LEVELS=['Scintilla','Ascensus','Fabrica','Culmen','Apex','Fastigium','Vertex','Corona'];
|
||||
function renderPipeline(d){
|
||||
const el=document.getElementById('dPipeline');
|
||||
if(!el)return;
|
||||
if(!d.subtree||(!d.subtree.left&&!d.subtree.right)){el.innerHTML='<div class="empty">Your pipeline starts when your first team members are placed below you.</div>';return}
|
||||
if(!d.upgradeCosts){el.innerHTML='<div class="empty">Pipeline data is loading — check back in a minute.</div>';return}
|
||||
const up=d.upgradeCosts;
|
||||
// walk the subtree with depth: a member at depth D pays THIS position when
|
||||
// they buy the upgrade OUT of level D (cost index D-1) — first in line
|
||||
const ready=[],building=[],passedCount={n:0};
|
||||
(function walk(n,depth){
|
||||
if(!n||depth>16)return;
|
||||
const lvl=n.level||1,tier=n.tier===2?2:1;
|
||||
if(depth>=1){
|
||||
if(lvl===depth){
|
||||
const amt=(up[tier]||[])[depth-1];
|
||||
if(amt)ready.push({id:n.id,depth,tier,amt,toLevel:LEVELS[depth]||('Level '+(depth+1)),needLevel:depth});
|
||||
}else if(lvl<depth)building.push({id:n.id,depth,steps:depth-lvl});
|
||||
else passedCount.n++;
|
||||
}
|
||||
walk(n.left,depth+1);walk(n.right,depth+1);
|
||||
})(d.subtree,0);
|
||||
ready.sort((a,b)=>a.depth-b.depth||a.id-b.id);
|
||||
const eligible=r=>d.directCount>=2&&(d.level||1)>=r.needLevel;
|
||||
const readyTotal=ready.filter(eligible).reduce((s,r)=>s+r.amt,0);
|
||||
let html='';
|
||||
if(ready.length){
|
||||
html+=`<p style="margin:0 0 8px;font-weight:800;color:var(--ok)">Ready now — one upgrade from your wallet:</p>`;
|
||||
html+=ready.slice(0,10).map(r=>{
|
||||
const warn=!eligible(r)?(d.directCount<2?' <span style="color:var(--danger)">⚠ needs you qualified (2 directs)</span>':` <span style="color:var(--danger)">⚠ needs you at ${esc(LEVELS[r.needLevel-1])}+</span>`):'';
|
||||
return `<div class="pp-row" style="padding:10px 14px"><div class="pp-icon">⏳</div><div class="pp-body"><strong>#${r.id}</strong> <span class="pp-meta" style="display:inline">· gen ${r.depth} · their ${esc(r.toLevel)} upgrade pays you <strong style="color:var(--gold)">${fmt(r.amt)} POL</strong>${warn}</span></div></div>`;
|
||||
}).join('');
|
||||
if(ready.length>10)html+=`<p class="micro" style="margin:6px 0 0">…and ${ready.length-10} more.</p>`;
|
||||
if(readyTotal>0){
|
||||
const myNext=(up[d.tier===2?2:1]||[])[(d.level||1)-1];
|
||||
const fundNote=myNext?` Your next upgrade (${esc(LEVELS[d.level]||'max')}) costs ${fmt(myNext)} — ${readyTotal>=myNext?'the pipeline above covers it':'these payments put you '+fmt(readyTotal)+' toward it'}.`:'';
|
||||
html+=`<p style="margin:12px 0 0;color:var(--muted)"><strong style="color:var(--text)">${fmt(readyTotal)} POL</strong> is one upgrade away from your wallet.${fundNote}</p>`;
|
||||
}
|
||||
}else html+='<p style="margin:0 0 8px;color:var(--muted)">Nobody is at their pay-you milestone yet — the members below are still climbing toward it.</p>';
|
||||
if(building.length)html+=`<p class="micro" style="margin:10px 0 0">${building.length} more member${building.length===1?' is':'s are'} building toward their pay-you level${passedCount.n?`; ${passedCount.n} already passed theirs`:''}. Helping your leg upgrade IS your income.</p>`;
|
||||
else if(passedCount.n)html+=`<p class="micro" style="margin:10px 0 0">${passedCount.n} member${passedCount.n===1?' has':'s have'} already passed their pay-you milestone.</p>`;
|
||||
el.innerHTML=html;
|
||||
}
|
||||
function renderShare(d){
|
||||
const el=document.getElementById('dShare');
|
||||
if(!el)return;
|
||||
|
||||
Reference in New Issue
Block a user