Files
martbost 7c8a43a055 Delete the rotation queue; sponsors.json stays as the member directory
Marty (2026-09-19): delete it if it no longer serves a purpose. The public link has run on the
chain walk since the mode flip and the queue's active head never received a join. Gone:
active/waiting/qualified statuses, normalizeStatuses, activeSponsor, publicSponsorPayload, the
increment/activate/qualify/reset actions, the click counter, the auto-count/auto-advance/
level-sync block, the legacy queue branches in current-sponsor and the submit-id classifier,
the chatbot's "placements waiting" facts, and the queue columns and buttons in admin. What stays
is a member directory (name, parent, level, notes, contact email) because the payout alerts and
the feed's name map read it. The chain walk is the only company rotation now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 07:26:25 -05:00

117 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let currentSponsor=null;
// Who invited this visitor, if anyone. A prospect who arrived on /join/<id> and then tapped
// a nav link used to land here and be offered the COMPANY rotation position instead — the
// inviter lost the referral just because the prospect clicked "Training" first. The invite
// is remembered in the rmc_ref cookie (set when the invite page was served) and can also be
// passed explicitly as ?ref=<id>.
function invitedBy(){
try{
const q=new URLSearchParams(location.search).get('ref');
if(q&&/^\d{1,15}$/.test(q))return q;
const m=document.cookie.match(/(?:^|;\s*)rmc_ref=(\d{1,15})/);
if(m)return m[1];
}catch(e){}
return null;
}
function placementChoice(){
try{
const q=new URLSearchParams(location.search).get('direct');
if(q==='1'||q==='0')return q;
const m=document.cookie.match(/(?:^|;\s*)rmc_direct=([01])/);
if(m)return m[1];
}catch(e){}
return null;
}
async function load(){
try{
const ref=invitedBy();
// an explicit ?direct=1/0 from the invite link wins over the position's default, and it
// has to be in scope for the join button further down — not just the display block
const dq=placementChoice();
const [cr,sr]=await Promise.all([
fetch('/api/public/config'),
fetch(ref?('/api/public/member?id='+encodeURIComponent(ref)):'/api/public/current-sponsor')
]);
const c=await cr.json(); const s=await sr.json();
if(!sr.ok)throw new Error(s.error||'No sponsor assigned');
if(ref&&s.registered){
// Route exactly the way that member's own invite page would: their moving link sends
// the join to the next position in their leg, unless their position is set to take
// direct placements. Same rules, so the two pages can never disagree.
const goDirect=dq==='1'||(dq!=='0'&&(s.directDefault||!s.joinTarget));
const t=goDirect?{id:s.id,referralUrl:s.referralUrl,directCount:s.directCount}:s.joinTarget;
currentSponsor={id:t.id,name:null,directs:(t.directCount!==undefined?t.directCount:0),goal:2,referralUrl:t.referralUrl};
// say whose team this is, so it never reads as an anonymous "current placement"
const badge=document.querySelector('.live-badge');
if(badge){
const dot=badge.querySelector('.dot');
badge.textContent=' '+(String(t.id)===String(ref)?('Invited by Member #'+ref):('Member #'+ref+"'s team"));
if(dot)badge.insertBefore(dot,badge.firstChild);
}
const jb=document.getElementById('joinButton');
if(jb)jb.textContent=(String(t.id)===String(ref)?('Join Member #'+ref+"'s Team →"):('Join '+'#'+ref+"'s Team →"));
const note=document.getElementById('queueText');
if(note&&String(t.id)!==String(ref)){
note.textContent='Member #'+ref+' is already qualified, so your entry fills the next open spot in their team (#'+t.id+').';
}
}else{
currentSponsor=s.sponsor;
}
if(c.siteName){document.title=`Get Started | ${c.siteName}`;const bn=document.getElementById('brandName');if(bn)bn.textContent=c.siteName}
document.getElementById('sponsorId').textContent=`ID ${currentSponsor.id}`;
document.getElementById('sponsorIdInline').textContent=currentSponsor.id;
document.getElementById('sponsorName').textContent=currentSponsor.name||'Current RM Circle placement';
document.getElementById('progressText').textContent=`${currentSponsor.directs} / 2`;
document.getElementById('progressBar').style.width=`${Math.min(100,(currentSponsor.directs/2)*100)}%`;
document.getElementById('entryPol').textContent=c.premiumEntryPol;
document.getElementById('entryPol2').textContent=c.premiumEntryPol;
if(c.polUsd>0){
var usd=Math.round(c.premiumEntryPol*c.polUsd);
['entryUsd','entryUsd2'].forEach(function(id){var el=document.getElementById(id);if(el)el.textContent=' (~$'+usd+' today — POL’s price moves)';});
}
// carry the inviter through to enrolment — /join-now already knows how to place a
// ?ref properly (direct placement or next open spot in their leg). Without this the
// button dropped the referral on the floor and enrolled under the company rotation.
document.getElementById('joinButton').href='/join-now'+(ref?('?ref='+encodeURIComponent(ref)+(dq?('&direct='+dq):'')):'');
{const dl=document.getElementById('dappJoinLink');if(dl&&currentSponsor.referralUrl){dl.href=currentSponsor.referralUrl;dl.classList.remove('hidden');}}
// the company-queue line only means anything on the company-rotation path
// only on the company-rotation path — the invited path already wrote its own line here
if(!ref)document.getElementById('queueText').textContent='The company rotation offers the next open position in the tree; it moves the moment that spot fills.';
document.getElementById('supportBox').textContent=c.supportLabel||'Contact your team sponsor if you need help before joining.';
if(c.telegramUrl){const w=document.getElementById('supportLinkWrap'),a=document.getElementById('supportLink');a.href=c.telegramUrl;w.classList.remove('hidden')}
}catch(e){
document.getElementById('sponsorName').textContent=e.message;
document.getElementById('joinButton').classList.add('hidden');
document.getElementById('copyButton').classList.add('hidden');
}
}
document.getElementById('joinButton').addEventListener('click',()=>{try{if(currentSponsor)sessionStorage.setItem('ctb.joinedSponsor',currentSponsor.id)}catch(e){};fetch('/api/public/join-click',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({source:window.ctbGetSource?window.ctbGetSource():'(direct)',clickid:window.ctbGetClickId?window.ctbGetClickId():''})}).catch(()=>{})});
document.getElementById('idSubmitForm').addEventListener('submit',async e=>{
e.preventDefault();
const form=e.currentTarget,input=form.elements.newId,msg=document.getElementById('idSubmitMsg');
const newId=input.value.trim();
const memberName=form.elements.memberName.value.trim();
if(!/^[0-9]{1,10}$/.test(newId)){msg.style.color='var(--danger)';msg.textContent='Numbers only — the ID shown by the RM Circle dApp.';return}
if(!memberName){msg.style.color='var(--danger)';msg.textContent='Add your name or Telegram handle so the team can reach you.';return}
let joinedSponsor='';try{joinedSponsor=sessionStorage.getItem('ctb.joinedSponsor')||''}catch(e){}
const sponsorId=joinedSponsor||(currentSponsor?currentSponsor.id:'?');
msg.style.color='var(--muted)';msg.textContent='Submitting…';
try{
const r=await fetch('/api/public/submit-id',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({newId,memberName,sponsorId,source:window.ctbGetSource?window.ctbGetSource():'(direct)',clickid:window.ctbGetClickId?window.ctbGetClickId():''})});
const d=await r.json();
if(!r.ok)throw new Error(d.error||'Submission failed');
form.classList.add('hidden');
msg.style.color='var(--ok)';
const dash=`<br><a href="/my/${newId}" style="color:var(--gold)">Open your Member Dashboard →</a>`;
if(d.duplicate)msg.innerHTML=`✓ ID <strong>${newId}</strong> was already submitted — you're on the list.${dash}`;
else if(d.path==='rotation')msg.innerHTML=`✓ Verified on the blockchain! ID <strong>${newId}</strong> joined through the team rotation under sponsor <strong>#${d.onchain.referrerId}</strong> (${d.onchain.tier}). The team has been notified and you're in line for the rotation that gets <strong>your</strong> 2.${dash}`;
else if(d.path==='leg')msg.innerHTML=`✓ Verified on the blockchain! ID <strong>${newId}</strong> is registered under sponsor <strong>#${d.onchain.referrerId}</strong>'s team build (${d.onchain.tier}) — your placement is recorded and the team has been notified. Heads up: the sponsor shown on this page is the site's team rotation, a separate group effort — your own team hub is your dashboard.${dash}`;
else if(d.path==='notfound'){msg.style.color='var(--danger)';msg.innerHTML=`⚠ ID <strong>${newId}</strong> isn't on the smart contract yet — double-check the number from the RM Circle dApp. Your submission is saved and the team will verify it manually.`;}
else msg.innerHTML=`✓ Got it! ID <strong>${newId}</strong> is submitted. The team has been notified and will verify your placement.${dash}`;
}catch(x){msg.style.color='var(--danger)';msg.textContent=x.message}
});
document.getElementById('copyButton').addEventListener('click',async()=>{
if(!currentSponsor)return; await navigator.clipboard.writeText(currentSponsor.id); const b=document.getElementById('copyButton'); const old=b.textContent;b.textContent='Copied ✓';setTimeout(()=>b.textContent=old,1400)
});
load();