diff --git a/public/join-now.js b/public/join-now.js index 79c7a3a..eb7a454 100644 --- a/public/join-now.js +++ b/public/join-now.js @@ -55,6 +55,10 @@ fetch('/api/public/config').then(function(r){return r.json()}).then(function(c){ // personal invite: ?ref= -> place in that member's leg via the site's moving-link joinTarget var qp=new URLSearchParams(location.search); var ref=qp.get('ref'); + // No ?ref on the URL? Fall back to the invite cookie set when this visitor landed on + // someone's /join/ page. Without this a prospect who wandered through the nav + // enrolled under the company rotation and the inviter lost the referral. + if(!ref){ try{ var ck=document.cookie.match(/(?:^|;\s*)rmc_ref=(\d{1,15})/); if(ck)ref=ck[1]; }catch(e){} } // Direct link (?direct=1): opt out of rotation — register straight under // the inviter (spillover in their team), never the next-to-qualify member. if(ref&&/^\d{1,15}$/.test(ref)&&qp.get('direct')==='1'){ diff --git a/public/start.js b/public/start.js index 70e8b38..d4b53ab 100644 --- a/public/start.js +++ b/public/start.js @@ -1,10 +1,49 @@ let currentSponsor=null; +// Who invited this visitor, if anyone. A prospect who arrived on /join/ 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=. +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; +} async function load(){ try{ - const [cr,sr]=await Promise.all([fetch('/api/public/config'),fetch('/api/public/current-sponsor')]); + const ref=invitedBy(); + 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'); - currentSponsor=s.sponsor; + 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 t=(s.directDefault||!s.joinTarget)?{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; @@ -17,9 +56,15 @@ async function load(){ 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)';}); } - document.getElementById('joinButton').href='/join-now'; + // 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)):''); {const dl=document.getElementById('dappJoinLink');if(dl&¤tSponsor.referralUrl){dl.href=currentSponsor.referralUrl;dl.classList.remove('hidden');}} - document.getElementById('queueText').textContent=c.showQueueProgress?`${s.waitingCount} team placement${s.waitingCount===1?'':'s'} waiting behind the current sponsor.`:''; + // 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=(c.showQueueProgress&&s.waitingCount!==undefined) + ?`${s.waitingCount} team placement${s.waitingCount===1?'':'s'} waiting behind the current sponsor.`:''; 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){ diff --git a/server.js b/server.js index 8914c5c..c0d3659 100644 --- a/server.js +++ b/server.js @@ -1786,7 +1786,16 @@ function serveMemberPage(req,res,file,kind,id){ ].join(''); html=html.replace(/]*>/gi,''); html=html.replace(/(]*>)/i,`$1${og}`); - res.writeHead(200,securityHeaders({'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-cache'})); + const head={'Content-Type':'text/html; charset=utf-8','Cache-Control':'no-cache'}; + // Remember who invited this visitor. Terry hit this on 2026-09-18: a prospect opens + // /join/840, taps "Training" in the nav, and by the time they reach /start the site has + // forgotten him and offers the COMPANY rotation position instead — his referral, handed + // to a stranger. The invite is the referral, so it has to outlive the one page it landed + // on. 30 days, Lax so it survives a normal click-through from anywhere. + if(kind==='join'&&/^\d{1,15}$/.test(String(id))){ + head['Set-Cookie']='rmc_ref='+id+'; Path=/; Max-Age=2592000; SameSite=Lax'+(IS_PROD?'; Secure':''); + } + res.writeHead(200,securityHeaders(head)); res.end(html); } const server=http.createServer(async(req,res)=>{