Split submissions into rotation vs leg paths — decided by the chain
Two leg-build members submitted the /start form thinking it was required, polluting rotation bookkeeping. Rather than two forms, the server now classifies every submission by on-chain truth: referrer == active rotation sponsor -> "rotation" (Telegram says +1 direct, add to queue); anyone else -> "leg" (correct sponsor credited, "no rotation action needed"). The member dashboard gains its own "Just joined under this position?" form so leg builders have a proper landing spot; /start feedback explains each path to the submitter; admin submissions table shows a rotation/leg chip. Verified against live data: ID 65 claiming sponsor 36 correctly classified leg under #62. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+2
-1
@@ -21,7 +21,8 @@ function renderAnalytics(){
|
||||
(tot.postback?`<div class="fact"><small>BeMob postbacks</small><strong>${tot.postback}</strong></div>`:'');
|
||||
const subs=state.submissions||[];
|
||||
document.getElementById('submissionRows').innerHTML=subs.map(s=>{
|
||||
const oc=s.onchain?(s.onchain.registered?`<span style="color:var(--ok)">✓ ${esc(s.onchain.tier||'')} · ref #${esc(String(s.onchain.referrerId??'?'))}</span>`:'<span style="color:var(--danger)">✗ not found</span>'):'—';
|
||||
const path=s.path==='rotation'?'<span class="status status-active">rotation</span> ':s.path==='leg'?'<span class="status status-qualified">leg</span> ':'';
|
||||
const oc=s.onchain?(s.onchain.registered?`${path}<span style="color:var(--ok)">✓ ${esc(s.onchain.tier||'')} · ref #${esc(String(s.onchain.referrerId??'?'))}</span>`:'<span style="color:var(--danger)">✗ not found</span>'):'—';
|
||||
return `<tr><td>${esc((s.ts||'').replace('T',' ').slice(0,16))}</td><td>${esc(s.memberName||'—')}</td><td><strong>${esc(s.newId)}</strong></td><td>ID ${esc(s.sponsorId)}</td><td>${esc(s.source)}</td><td>${oc}</td></tr>`;
|
||||
}).join('')||'<tr><td colspan="6" class="empty">No submissions yet.</td></tr>';
|
||||
document.getElementById('trafficRows').innerHTML=list.map(r=>`<tr><td><strong>${esc(r.name)}</strong></td><td>${r.bridge}</td><td>${r.start}</td><td>${r.training}</td><td>${r.click}</td><td>${pct(r.click,r.start)}</td></tr>`).join('')||'<tr><td colspan="6" class="empty">No traffic recorded yet.</td></tr>';
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<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"><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>
|
||||
<div class="table-card"><h2 style="margin:0 0 4px">Your lineage</h2><p id="dLineage" style="color:var(--muted);font-size:14px;line-height:1.8;margin:8px 0 0"></p></div>
|
||||
<div class="notice" style="margin-top:18px"><strong>Team reminder:</strong> once your two directs are placed, retire your link and help your two get their two — always send new members through the <a href="/start" style="color:var(--gold)">current team sponsor page</a>.</div>
|
||||
|
||||
@@ -136,6 +136,27 @@
|
||||
document.getElementById('idError').textContent=x.message;
|
||||
}
|
||||
}
|
||||
document.getElementById('dJoinForm').addEventListener('submit',async e=>{
|
||||
e.preventDefault();
|
||||
const form=e.currentTarget,msg=document.getElementById('dJoinMsg');
|
||||
const newId=form.elements.newId.value.trim(),memberName=form.elements.memberName.value.trim();
|
||||
if(!/^\d{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.';return}
|
||||
msg.style.color='var(--muted)';msg.textContent='Verifying on the blockchain…';
|
||||
try{
|
||||
const pageId=(location.pathname.match(/^\/my\/(\d+)$/)||[])[1]||'?';
|
||||
const r=await fetch('/api/public/submit-id',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({newId,memberName,sponsorId:pageId,source:'dashboard-'+pageId,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 own dashboard →</a>`;
|
||||
if(d.duplicate)msg.innerHTML=`✓ ID <strong>${newId}</strong> was already submitted — you're on the list.${dash}`;
|
||||
else if(d.onchain&&d.onchain.registered)msg.innerHTML=`✓ Verified on the blockchain! ID <strong>${newId}</strong> is registered under sponsor <strong>#${d.onchain.referrerId}</strong> (${d.onchain.tier}). The team has been notified — welcome aboard.${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 — submitted. The team will verify your placement.${dash}`;
|
||||
}catch(x){msg.style.color='var(--danger)';msg.textContent=x.message}
|
||||
});
|
||||
document.getElementById('idForm').addEventListener('submit',e=>{
|
||||
e.preventDefault();
|
||||
const v=document.getElementById('memberId').value.trim();
|
||||
|
||||
+6
-4
@@ -40,10 +40,12 @@ document.getElementById('idSubmitForm').addEventListener('submit',async e=>{
|
||||
if(!r.ok)throw new Error(d.error||'Submission failed');
|
||||
form.classList.add('hidden');
|
||||
msg.style.color='var(--ok)';
|
||||
let chainNote='';
|
||||
if(d.onchain&&d.onchain.registered)chainNote=`<br><span style="color:var(--teal)">✓ Verified on the blockchain: ${d.onchain.tier} tier, ${d.onchain.level} level, referred by ID ${d.onchain.referrerId}.</span>`;
|
||||
else if(d.onchain&&d.onchain.registered===false)chainNote=`<br><span style="color:var(--danger)">⚠ We couldn't find this ID on the smart contract yet — double-check the number. The team will verify it manually.</span>`;
|
||||
msg.innerHTML=d.duplicate?`✓ ID <strong>${newId}</strong> was already submitted — you're on the list.`:`✓ Got it! ID <strong>${newId}</strong> is submitted under sponsor ID <strong>${sponsorId}</strong>. The team has been notified and you'll be added to the rotation.${chainNote}`;
|
||||
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()=>{
|
||||
|
||||
@@ -135,17 +135,29 @@ async function handleSubmitId(req, res) {
|
||||
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 6000))
|
||||
]);
|
||||
} catch (e) { onchain = null; }
|
||||
subs.push({ newId, memberName, sponsorId, source: source||'(direct)', clickid, ts: new Date().toISOString(),
|
||||
// the chain decides the path: rotation join (referrer = active rotation sponsor)
|
||||
// vs leg join (someone's personal team build) — regardless of which page they used
|
||||
const active = activeSponsor(getSponsors());
|
||||
let joinPath = 'unknown';
|
||||
if (onchain && onchain.registered) joinPath = (active && String(onchain.referrerId) === String(active.id)) ? 'rotation' : 'leg';
|
||||
else if (onchain && !onchain.registered) joinPath = 'notfound';
|
||||
subs.push({ newId, memberName, sponsorId, source: source||'(direct)', clickid, ts: new Date().toISOString(), path: joinPath,
|
||||
onchain: onchain ? { registered: onchain.registered, tier: onchain.tierName, level: onchain.levelName, referrerId: onchain.referrerId, uplineId: onchain.uplineId } : undefined });
|
||||
writeJson(SUBMISSIONS_FILE, subs.slice(-1000));
|
||||
recordEvent('purchase', source);
|
||||
firePostback(clickid, `purchase-${clickid}`, source);
|
||||
const chainLine = onchain === null ? '⏳ On-chain check unavailable — verify manually in admin.'
|
||||
: onchain.registered
|
||||
? `✅ VERIFIED ON-CHAIN: ${onchain.tierName} tier, level ${onchain.levelName}, referred by ID ${onchain.referrerId}${String(onchain.referrerId)!==sponsorId?` ⚠ (submitted sponsor was ${sponsorId})`:''}`
|
||||
: `❌ NOT FOUND ON-CHAIN — ID ${newId} has no registration on the contract yet.`;
|
||||
sendTelegram(`🔔 RM Circle: NEW MEMBER CONFIRMED\nName: ${memberName}\nNew ID: ${newId}\nJoined under sponsor: ${sponsorId}\nSource: ${source||'(direct)'}\n${chainLine}\n→ Add ${memberName} (ID ${newId}) to the rotation queue.`);
|
||||
return json(res, 200, { ok: true, onchain: onchain ? { registered: onchain.registered, tier: onchain.tierName, level: onchain.levelName, referrerId: onchain.referrerId } : null });
|
||||
let msg;
|
||||
if (joinPath === 'rotation') {
|
||||
msg = `🔔 RM Circle: ROTATION JOIN CONFIRMED ✅\nName: ${memberName}\nNew ID: ${newId} (${onchain.tierName}, verified on-chain)\nJoined under rotation sponsor: #${onchain.referrerId}\nSource: ${source||'(direct)'}\n→ +1 direct for ID ${active.id}; add ${memberName} (ID ${newId}) to the rotation queue.`;
|
||||
} else if (joinPath === 'leg') {
|
||||
msg = `🌱 RM Circle: TEAM-BUILD JOIN (not rotation)\nName: ${memberName}\nNew ID: ${newId} (${onchain.tierName}, verified on-chain)\nActual sponsor on-chain: #${onchain.referrerId}${sponsorId!=='?'&&String(onchain.referrerId)!==sponsorId?` (form said ${sponsorId})`:''}\nSource: ${source||'(direct)'}\n→ Leg growth under #${onchain.referrerId} — no rotation action needed. Add them to the rotation queue only if they want the team effort.`;
|
||||
} else if (joinPath === 'notfound') {
|
||||
msg = `🔔 RM Circle: ID SUBMITTED — ❌ NOT FOUND ON-CHAIN\nName: ${memberName}\nNew ID: ${newId}\nClaimed sponsor: ${sponsorId}\nSource: ${source||'(direct)'}\n→ ID has no registration on the contract — double-check with them before any queue action.`;
|
||||
} else {
|
||||
msg = `🔔 RM Circle: NEW MEMBER SUBMITTED\nName: ${memberName}\nNew ID: ${newId}\nClaimed sponsor: ${sponsorId}\nSource: ${source||'(direct)'}\n⏳ On-chain check unavailable — verify manually in admin (member lookup).`;
|
||||
}
|
||||
sendTelegram(msg);
|
||||
return json(res, 200, { ok: true, path: joinPath, onchain: onchain ? { registered: onchain.registered, tier: onchain.tierName, level: onchain.levelName, referrerId: onchain.referrerId } : null });
|
||||
}
|
||||
async function handleChat(req, res) {
|
||||
const ip = String(req.headers['x-forwarded-for']||req.socket.remoteAddress||'').split(',')[0].trim();
|
||||
|
||||
Reference in New Issue
Block a user