Add "Your Organization vs. the Network" live stat to admin
New chain.getOrgShare(rootId) + /api/admin/org-share endpoint compute an org's share of the whole contract by both member count and total POL paid, from in-memory state. Admin card (default root 21, saved to config.orgRootId, auto-loads) shows the two headline percentages, a bar, and the member/POL breakdown. Verified: #21 org = 51.1% of members, 49.5% of POL. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,8 @@ function render(){
|
||||
const em=state.email||{};document.getElementById('emailStatus').innerHTML=em.configured?'<span style="color:var(--ok)">● Payment emails are ON</span> — key is set':'<span style="color:var(--muted)">○ Payment emails are OFF</span> — no SendGrid key';
|
||||
const incEl=document.getElementById('incomeIds');if(incEl&&!incEl.value)incEl.value=state.config.ownerIds||'21,24,25';
|
||||
if(!incomeAutoLoaded&&incEl&&incEl.value){incomeAutoLoaded=true;loadIncome();}
|
||||
const orgEl=document.getElementById('orgRoot');if(orgEl&&!orgEl.value)orgEl.value=state.config.orgRootId||'21';
|
||||
if(!orgShareAutoLoaded&&orgEl&&orgEl.value){orgShareAutoLoaded=true;loadOrgShare();}
|
||||
const efi=document.getElementById('emailFromInput');if(efi&&!efi.value)efi.value=state.config.emailFrom||em.from||'';
|
||||
const f=document.getElementById('configForm'),c=state.config;for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','teamRootId','teamAlertEmail','ownerAlertEmail'])if(f.elements[k])f.elements[k].value=c[k]??'';f.elements.showSponsorName.checked=!!c.showSponsorName;f.elements.showQueueProgress.checked=!!c.showQueueProgress;
|
||||
}
|
||||
@@ -50,6 +52,29 @@ rows.addEventListener('change',async e=>{const sel=e.target.closest('select[data
|
||||
document.getElementById('sgKeyForm').addEventListener('submit',async e=>{e.preventDefault();const inp=e.currentTarget.elements.key,key=inp.value.trim();if(!key){showToast('Paste a key first');return}try{const d=await api('/api/admin/sendgrid-key',{method:'POST',body:JSON.stringify({key})});state.email={...(state.email||{}),configured:d.configured};inp.value='';render();showToast('Payment emails enabled')}catch(x){showToast(x.message)}});
|
||||
document.getElementById('sgKeyClear').addEventListener('click',async()=>{if(!confirm('Disable "you\'ve been paid" emails?'))return;try{const d=await api('/api/admin/sendgrid-key',{method:'POST',body:JSON.stringify({key:''})});state.email={...(state.email||{}),configured:d.configured};render();showToast('Payment emails disabled')}catch(x){showToast(x.message)}});
|
||||
document.getElementById('emailFromSave').addEventListener('click',async()=>{const v=document.getElementById('emailFromInput').value.trim();try{const d=await api('/api/admin/config',{method:'PATCH',body:JSON.stringify({emailFrom:v})});state.config=d.config;showToast('From address saved')}catch(x){showToast(x.message)}});
|
||||
let orgShareAutoLoaded=false;
|
||||
async function loadOrgShare(){
|
||||
const root=(document.getElementById('orgRoot').value||'').trim();
|
||||
const out=document.getElementById('orgShare');
|
||||
if(!root||!/^\d+$/.test(root)){out.innerHTML='<div class="empty">Enter a numeric top ID (e.g. 21).</div>';return}
|
||||
out.innerHTML='<div class="empty">Reading the blockchain…</div>';
|
||||
try{
|
||||
try{const c=await api('/api/admin/config',{method:'PATCH',body:JSON.stringify({orgRootId:root})});state.config=c.config;}catch(_){}
|
||||
const d=await api('/api/admin/org-share?root='+encodeURIComponent(root));
|
||||
if(!d.ready){out.innerHTML='<div class="empty">Snapshot not ready yet — try again in a minute.</div>';return}
|
||||
if(!d.found){out.innerHTML=`<div class="empty" style="color:var(--danger)">ID ${esc(root)} isn't in the indexed matrix yet.</div>`;return}
|
||||
const fmt=n=>Number(n).toLocaleString(undefined,{maximumFractionDigits:0});
|
||||
out.innerHTML=
|
||||
`<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:center;margin-bottom:14px">`+
|
||||
`<div style="text-align:center"><div style="font-size:44px;font-weight:900;color:var(--gold);line-height:1">${d.memberPct}%</div><div class="micro" style="margin-top:2px">of all members</div></div>`+
|
||||
`<div style="text-align:center"><div style="font-size:44px;font-weight:900;color:var(--ok);line-height:1">${d.polPct}%</div><div class="micro" style="margin-top:2px">of all POL paid</div></div>`+
|
||||
`</div>`+
|
||||
`<div class="progress-line" style="height:16px;margin:0 0 6px"><span style="width:${Math.min(100,d.memberPct)}%"></span></div>`+
|
||||
`<p style="color:var(--muted);margin:0 0 14px;font-size:14px">Your organization rooted at <strong class="mt-id">#${d.rootId}</strong> is <strong style="color:var(--text)">${d.orgMembers} of ${d.totalMembers}</strong> members in the whole contract (${d.orgBelow} below you + your position), and has earned <strong style="color:var(--text)">${fmt(d.orgPol)} of ${fmt(d.totalPol)} POL</strong> ever paid.</p>`+
|
||||
`<div class="facts" style="grid-template-columns:repeat(4,1fr)"><div class="fact"><small>Your org members</small><strong>${d.orgMembers}</strong></div><div class="fact"><small>Whole network</small><strong>${d.totalMembers}</strong></div><div class="fact"><small>Your org earned</small><strong>${fmt(d.orgPol)} POL</strong></div><div class="fact"><small>Network paid</small><strong>${fmt(d.totalPol)} POL</strong></div></div>`;
|
||||
}catch(x){out.innerHTML=`<div class="empty" style="color:var(--danger)">${esc(x.message)}</div>`}
|
||||
}
|
||||
document.getElementById('orgShareForm').addEventListener('submit',e=>{e.preventDefault();loadOrgShare()});
|
||||
let incomeAutoLoaded=false;
|
||||
async function loadIncome(){
|
||||
const ids=(document.getElementById('incomeIds').value||'').trim();
|
||||
|
||||
Reference in New Issue
Block a user