On-dashboard upgrades: qualified members upgrade their level right from /my/<id> - ownership enforced by matching the connected wallet to the position's account, exact BigInt cost read live via getAllCosts, MoonPay assist when underfunded, wallet-app deep links on mobile
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -49,7 +49,7 @@
|
||||
{k:['tier','standard','premium tier','standard tier','premium vs standard','which tier','half','smaller payment','less than expected','why is my payment'],
|
||||
a:()=>`There are two tiers. <strong>Premium</strong> is what our whole team builds at (${pol()} POL entry) — full payments. <strong>Standard</strong> costs about half and pays/earns half at every level. So if a payment ever comes in smaller than expected, it usually came from a Standard-tier position below you. Your tier is <strong>set when you join and can't be changed later</strong> (upgrading advances your level, not your tier), so always join <strong>Premium</strong> and make sure the people you bring on do too. Full breakdown: <a href="/how-pay-works">how-pay-works</a>.`},
|
||||
{k:['dashboard','my dashboard','my page','my position','my team','check my','see my','pipeline','my stats','alerts','notify me','email me','get notified','message','messages','contact my sponsor','contact my upline','contact my downline','reach my team'],
|
||||
a:()=>`Your <strong>Member Dashboard</strong> is at <a href="/my">rmcircle.team/my</a> — enter your ID to see your position, your team (with a depth summary showing members per generation), your payments, your pipeline (money forming below you), any spillover under you, and a <strong>Coach Your Team</strong> panel that tells you exactly who in your leg needs a nudge and what to say. You can also turn on <strong>email alerts</strong> there, and use <strong>Messages</strong> — wallet-verified messaging with your upline and your team (sign in once with your wallet; no email needed; only people on your matrix lines can message you). To share, use your personal invite page: <strong>rmcircle.team/join/<your ID></strong>.`},
|
||||
a:()=>`Your <strong>Member Dashboard</strong> is at <a href="/my">rmcircle.team/my</a> — enter your ID to see your position, your team (with a depth summary showing members per generation), your payments, your pipeline (money forming below you), any spillover under you, and a <strong>Coach Your Team</strong> panel that tells you exactly who in your leg needs a nudge and what to say. When you're qualified, you can also <strong>upgrade your level right from your dashboard</strong> — connect the wallet that owns your position and confirm; the exact cost is read live from the contract. You can also turn on <strong>email alerts</strong> there, and use <strong>Messages</strong> — wallet-verified messaging with your upline and your team (sign in once with your wallet; no email needed; only people on your matrix lines can message you). To share, use your personal invite page: <strong>rmcircle.team/join/<your ID></strong>.`},
|
||||
{k:['level','levels','upgrade','scintilla','ascensus','fabrica','culmen','apex','fastigium','vertex','corona','8 levels'],
|
||||
a:()=>`There are 8 Premium levels: <strong>Scintilla, Ascensus, Fabrica, Culmen, Apex, Fastigium, Vertex, Corona</strong>. Everyone starts at Scintilla. Upgrade as quickly as practical — ideally using earned POL — because the first two payments at each level are designed to help fund your next upgrade. Stay aware of your active downline's levels so you don't fall behind.`},
|
||||
{k:['30 positions','goal','milestone','matrix','how many people','team size'],
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
</section>
|
||||
<section id="tabDash" class="mp-panel">
|
||||
<div id="dFacts" class="facts" style="grid-template-columns:repeat(3,1fr);margin:18px 0"></div>
|
||||
<div id="dNextStep"></div>
|
||||
<div id="dNextStep"></div><div id="dUpgrade"></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><div id="dGens" style="margin-top:14px"></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 id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div>
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
?`<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>';
|
||||
renderNextStep(d);
|
||||
renderUpgradeCTA(d);
|
||||
renderGens(d);
|
||||
renderPipeline(d);
|
||||
renderCoach(d);
|
||||
@@ -159,6 +160,75 @@
|
||||
}).join('');
|
||||
el.innerHTML=`<div style="border-top:1px solid var(--line);padding-top:12px"><small style="text-transform:uppercase;letter-spacing:.08em;color:var(--muted);font-size:11px">Team depth — members per generation</small><div style="margin-top:8px">${rows}</div><p class="micro" style="margin:8px 0 0">Full generations duplicate: each one can hold twice the last. A generation pays this position at exactly one level — stay qualified and at that level to catch it.</p></div>`;
|
||||
}
|
||||
// On-page upgrade: connect the wallet that OWNS this position, read the
|
||||
// exact next-level cost from the contract (BigInt — never float-derived),
|
||||
// send upgrade(). Wrong value would simply revert; ownership is enforced by
|
||||
// matching the connected account to the position's on-chain account.
|
||||
const UPG={CONTRACT:'0x33bdaeefd6d17d80ae53816c916dfb26c4fb2daf',POLYGON:'0x89',SEL_UPGRADE:'0xd55ec697',SEL_GETCOSTS:'0x735f87b9'};
|
||||
let upEth=null;
|
||||
function renderUpgradeCTA(d){
|
||||
const el=document.getElementById('dUpgrade');if(!el)return;
|
||||
const ns=d.nextStep||{};
|
||||
if(ns.kind!=='upgrade'||d.directCount<2){el.innerHTML='';return;}
|
||||
const name=LEVELS[ns.nextLevel-1]||('Level '+ns.nextLevel);
|
||||
el.innerHTML=`<div class="table-card" style="margin:0 0 18px;border-color:rgba(240,197,109,.45)">
|
||||
<h2 style="margin:0 0 4px">⬆️ Upgrade to ${esc(name)} — right from this page</h2>
|
||||
<p style="color:var(--muted);font-size:13px;margin:0 0 12px">Exact cost <strong>${fmt(ns.cost)} POL</strong>, read live from the contract. Connect the wallet that owns position #${d.id}, confirm once, done. ${ns.funded?'<strong style="color:var(--ok)">Your wallet already covers it ✓</strong>':'Your wallet does not cover it yet — you can time it to your next catches, or top up below.'}</p>
|
||||
<button id="upgGo" class="btn btn-primary">Connect & upgrade to ${esc(name)} →</button>
|
||||
${ns.funded?'':`<button id="upgFund" class="btn btn-secondary" style="margin-left:8px">💳 Buy POL with card</button>`}
|
||||
<div id="upgHelp" style="display:none;margin-top:10px" class="micro">📱 On a phone with no wallet in this browser? Open this page inside your wallet app: <a id="upgMM" href="#">MetaMask</a> · <a id="upgTW" href="#">Trust Wallet</a></div>
|
||||
<div id="upgLog" class="micro" style="margin-top:10px"></div>
|
||||
<p class="micro" style="margin:10px 0 0">Upgrades are optional and self-paced — never use funds you can't afford to lose. If anything is off, the contract simply rejects it and you keep your POL (minus tiny gas).</p>
|
||||
</div>`;
|
||||
const go=document.getElementById('upgGo');if(go)go.addEventListener('click',function(){doUpgrade(d,ns);});
|
||||
const fu=document.getElementById('upgFund');if(fu)fu.addEventListener('click',async function(){
|
||||
try{const need=Math.max(62,Math.ceil(ns.cost- (0))+5);const r=await(await fetch('/api/public/moonpay-url?address='+encodeURIComponent(d.account||'')+'&pol='+need)).json();if(r&&r.url)window.open(r.url,'_blank','noopener');}catch(e){}
|
||||
});
|
||||
}
|
||||
function upgLog(msg,bad){const l=document.getElementById('upgLog');if(l){l.innerHTML=msg;l.style.color=bad?'var(--danger)':'var(--muted)';}}
|
||||
async function doUpgrade(d,ns){
|
||||
try{
|
||||
upgLog('Looking for your wallet…');
|
||||
upEth=await window.RMCWallet.pick();
|
||||
if(!upEth){
|
||||
const h=document.getElementById('upgHelp');if(h){h.style.display='block';
|
||||
const here=location.host+location.pathname+location.search;
|
||||
document.getElementById('upgMM').href='https://metamask.app.link/dapp/'+here;
|
||||
document.getElementById('upgTW').href='https://link.trustwallet.com/open_url?coin_id=966&url='+encodeURIComponent(location.href);}
|
||||
upgLog('No wallet in this browser — use the wallet-app links above, or install MetaMask on desktop.',true);return;
|
||||
}
|
||||
const accs=await upEth.request({method:'eth_requestAccounts'});const account=(accs[0]||'').toLowerCase();
|
||||
if(!d.account||account!==String(d.account).toLowerCase()){
|
||||
upgLog('This wallet ('+account.slice(0,6)+'…'+account.slice(-4)+') does not own position #'+d.id+' — connect the wallet that registered it ('+String(d.account||'').slice(0,6)+'…'+String(d.account||'').slice(-4)+').',true);return;
|
||||
}
|
||||
// network
|
||||
const cid=await upEth.request({method:'eth_chainId'});
|
||||
if(cid!==UPG.POLYGON){
|
||||
upgLog('Switching to Polygon…');
|
||||
try{await upEth.request({method:'wallet_switchEthereumChain',params:[{chainId:UPG.POLYGON}]});}
|
||||
catch(e){if(e&&e.code===4902){await upEth.request({method:'wallet_addEthereumChain',params:[{chainId:UPG.POLYGON,chainName:'Polygon Mainnet',nativeCurrency:{name:'POL',symbol:'POL',decimals:18},rpcUrls:['https://polygon-rpc.com'],blockExplorerUrls:['https://polygonscan.com']}]});}else{throw e;}}
|
||||
}
|
||||
// exact cost from the contract (BigInt)
|
||||
upgLog('Reading the exact upgrade cost from the contract…');
|
||||
const r=await upEth.request({method:'eth_call',params:[{to:UPG.CONTRACT,data:UPG.SEL_GETCOSTS},'latest']});
|
||||
const words=[];for(let i=2;i<r.length;i+=64)words.push(BigInt('0x'+r.slice(i,i+64)));
|
||||
const arr=(d.tier===2)?words.slice(24,32):words.slice(16,24);
|
||||
const val=arr[(d.level||1)-1];
|
||||
if(!val||val<=0n){upgLog('Could not read the upgrade cost — try again in a minute.',true);return;}
|
||||
upgLog('Upgrading to '+esc(LEVELS[ns.nextLevel-1]||'')+' for '+(Number(val)/1e18).toFixed(4)+' POL — confirm in your wallet…');
|
||||
const tx=await upEth.request({method:'eth_sendTransaction',params:[{from:account,to:UPG.CONTRACT,value:'0x'+val.toString(16),data:UPG.SEL_UPGRADE}]});
|
||||
upgLog('Submitted — waiting for confirmation… <a href="https://polygonscan.com/tx/'+tx+'" target="_blank" rel="noopener">view tx</a>');
|
||||
for(let i=0;i<60;i++){
|
||||
try{const rc=await upEth.request({method:'eth_getTransactionReceipt',params:[tx]});
|
||||
if(rc){if(rc.status==='0x0'){upgLog('⚠️ The upgrade reverted — your POL was returned (minus gas). Wait a moment and try again.',true);return;}
|
||||
upgLog('🎉 <strong>Upgraded!</strong> Refreshing your dashboard…');setTimeout(function(){location.reload();},2500);return;}
|
||||
}catch(e){}
|
||||
await new Promise(function(s){setTimeout(s,3000);});
|
||||
}
|
||||
upgLog('Still pending — it will land shortly; refresh in a minute.');
|
||||
}catch(e){upgLog('Upgrade failed / rejected: '+esc(e.message||String(e)),true);}
|
||||
}
|
||||
|
||||
// Wallet-verified messaging: sign-in = one free personal_sign; identity is
|
||||
// the wallet that owns a position; permissions follow the matrix lines.
|
||||
function renderMessages(d){
|
||||
|
||||
Reference in New Issue
Block a user