From 532cb18b685fb12e6cc7668c90d56725127554fb Mon Sep 17 00:00:00 2001 From: martbost Date: Fri, 14 Aug 2026 05:39:02 -0500 Subject: [PATCH] =?UTF-8?q?Split=20submissions=20into=20rotation=20vs=20le?= =?UTF-8?q?g=20paths=20=E2=80=94=20decided=20by=20the=20chain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- public/admin.js | 3 ++- public/my.html | 1 + public/my.js | 21 +++++++++++++++++++++ public/start.js | 10 ++++++---- server.js | 26 +++++++++++++++++++------- 5 files changed, 49 insertions(+), 12 deletions(-) diff --git a/public/admin.js b/public/admin.js index ccb24ea..42bf01a 100644 --- a/public/admin.js +++ b/public/admin.js @@ -21,7 +21,8 @@ function renderAnalytics(){ (tot.postback?`
BeMob postbacks${tot.postback}
`:''); const subs=state.submissions||[]; document.getElementById('submissionRows').innerHTML=subs.map(s=>{ - const oc=s.onchain?(s.onchain.registered?`✓ ${esc(s.onchain.tier||'')} · ref #${esc(String(s.onchain.referrerId??'?'))}`:'✗ not found'):'—'; + const path=s.path==='rotation'?'rotation ':s.path==='leg'?'leg ':''; + const oc=s.onchain?(s.onchain.registered?`${path}✓ ${esc(s.onchain.tier||'')} · ref #${esc(String(s.onchain.referrerId??'?'))}`:'✗ not found'):'—'; return `${esc((s.ts||'').replace('T',' ').slice(0,16))}${esc(s.memberName||'—')}${esc(s.newId)}ID ${esc(s.sponsorId)}${esc(s.source)}${oc}`; }).join('')||'No submissions yet.'; document.getElementById('trafficRows').innerHTML=list.map(r=>`${esc(r.name)}${r.bridge}${r.start}${r.training}${r.click}${pct(r.click,r.start)}`).join('')||'No traffic recorded yet.'; diff --git a/public/my.html b/public/my.html index 9005d90..6b2d497 100644 --- a/public/my.html +++ b/public/my.html @@ -8,6 +8,7 @@

Your team

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.

✓ qualified (2/2 directs) · P Premium · S Standard · ⬇ everyone below that position (all generations) and the POL they've earned · ↧ spillover = placed there by upline activity; only members who join with a position's own ID count toward its 2/2

Share this position

+

Just joined under this position?

Welcome to the team! Enter the new member ID the RM Circle dApp gave you — we'll verify it on the blockchain and let the team know you're in.

Payments received

Every payment your position has received, straight from the smart contract.

Your lineage

Team reminder: once your two directs are placed, retire your link and help your two get their two — always send new members through the current team sponsor page.
diff --git a/public/my.js b/public/my.js index 14a5126..99b547f 100644 --- a/public/my.js +++ b/public/my.js @@ -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=`
Open your own dashboard →`; + if(d.duplicate)msg.innerHTML=`✓ ID ${newId} was already submitted — you're on the list.${dash}`; + else if(d.onchain&&d.onchain.registered)msg.innerHTML=`✓ Verified on the blockchain! ID ${newId} is registered under sponsor #${d.onchain.referrerId} (${d.onchain.tier}). The team has been notified — welcome aboard.${dash}`; + else if(d.path==='notfound'){msg.style.color='var(--danger)';msg.innerHTML=`⚠ ID ${newId} 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(); diff --git a/public/start.js b/public/start.js index dc14d02..f5c651d 100644 --- a/public/start.js +++ b/public/start.js @@ -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=`
✓ Verified on the blockchain: ${d.onchain.tier} tier, ${d.onchain.level} level, referred by ID ${d.onchain.referrerId}.`; - else if(d.onchain&&d.onchain.registered===false)chainNote=`
⚠ We couldn't find this ID on the smart contract yet — double-check the number. The team will verify it manually.`; - msg.innerHTML=d.duplicate?`✓ ID ${newId} was already submitted — you're on the list.`:`✓ Got it! ID ${newId} is submitted under sponsor ID ${sponsorId}. The team has been notified and you'll be added to the rotation.${chainNote}`; + const dash=`
Open your Member Dashboard →`; + if(d.duplicate)msg.innerHTML=`✓ ID ${newId} was already submitted — you're on the list.${dash}`; + else if(d.path==='rotation')msg.innerHTML=`✓ Verified on the blockchain! ID ${newId} joined through the team rotation under sponsor #${d.onchain.referrerId} (${d.onchain.tier}). The team has been notified and you're in line for the rotation that gets your 2.${dash}`; + else if(d.path==='leg')msg.innerHTML=`✓ Verified on the blockchain! ID ${newId} is registered under sponsor #${d.onchain.referrerId}'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 ${newId} 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 ${newId} 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()=>{ diff --git a/server.js b/server.js index 7f4505c..fa76a73 100644 --- a/server.js +++ b/server.js @@ -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();