diff --git a/private/join-now.html b/private/join-now.html index 4b4a9f3..2b25ee9 100644 --- a/private/join-now.html +++ b/private/join-now.html @@ -71,4 +71,4 @@

No income is guaranteed. Cryptocurrency and smart-contract participation carry risk. Only use funds you can afford to lose.

- + diff --git a/public/my.html b/public/my.html index 3929d71..3395f88 100644 --- a/public/my.html +++ b/public/my.html @@ -31,4 +31,4 @@ - + diff --git a/public/training.html b/public/training.html index 032c5fd..ec803f1 100644 --- a/public/training.html +++ b/public/training.html @@ -87,4 +87,4 @@ - + diff --git a/public/wallet-notice.js b/public/wallet-notice.js new file mode 100644 index 0000000..a24d580 --- /dev/null +++ b/public/wallet-notice.js @@ -0,0 +1,22 @@ +// Temporary, admin-controlled heads-up shown right above any wallet-connect +// button while a scanner false-positive is being cleared. Driven by +// config.walletNotice (admin PATCH): non-empty string = show it, ""/unset = +// nothing renders. One config flip removes it everywhere, no deploy. +(function () { + 'use strict'; + fetch('/api/public/config').then(function (r) { return r.json(); }).then(function (c) { + var msg = (c && c.walletNotice || '').trim(); + if (!msg) return; + var btns = [].slice.call(document.querySelectorAll('button, a.btn')); + var targets = btns.filter(function (b) { return /connect|unlock/i.test(b.textContent || ''); }); + if (!targets.length) return; + targets.forEach(function (btn) { + if (btn.dataset.wnDone) return; btn.dataset.wnDone = '1'; + var n = document.createElement('div'); + n.className = 'callout'; + n.style.cssText = 'margin:0 0 12px;font-size:13px;line-height:1.5;border-color:rgba(243,190,67,.5)'; + n.innerHTML = 'ℹ️ ' + msg; // admin-authored, same trust level as other config-injected copy + btn.parentNode.insertBefore(n, btn); + }); + }).catch(function () {}); +})(); diff --git a/server.js b/server.js index 9886960..1f56c28 100644 --- a/server.js +++ b/server.js @@ -492,7 +492,7 @@ function staticFile(req,res,file,status=200){ async function handleApi(req,res,pathname){ if(req.method==='GET'&&pathname==='/health') return json(res,200,{ok:true}); if(req.method==='GET'&&pathname==='/api/public/config'){ - const c=getConfig();await getPolUsd().catch(()=>{});return json(res,200,{polUsd:polPrice.usd||0,siteName:c.siteName,programName:c.programName,bridgeHeadline:c.bridgeHeadline,bridgeSubheadline:c.bridgeSubheadline,premiumEntryPol:c.premiumEntryPol,telegramUrl:c.telegramUrl,supportLabel:c.supportLabel,showQueueProgress:c.showQueueProgress}); + const c=getConfig();await getPolUsd().catch(()=>{});return json(res,200,{polUsd:polPrice.usd||0,siteName:c.siteName,programName:c.programName,bridgeHeadline:c.bridgeHeadline,bridgeSubheadline:c.bridgeSubheadline,premiumEntryPol:c.premiumEntryPol,telegramUrl:c.telegramUrl,supportLabel:c.supportLabel,showQueueProgress:c.showQueueProgress,walletNotice:c.walletNotice||''}); } if(req.method==='GET'&&pathname==='/api/public/member'){ const ip=String(req.headers['x-forwarded-for']||req.socket.remoteAddress||'').split(',')[0].trim(); @@ -821,7 +821,7 @@ async function handleApi(req,res,pathname){ const maxOrder=sponsors.reduce((m,s)=>Math.max(m,s.sortOrder||0),0);sponsors.push({id:String(id).trim(),name:String(name).trim(),parentId:String(parentId||'').trim(),directs:0,level,status:sponsors.some(s=>s.status==='active')?'waiting':'active',sortOrder:maxOrder+10,clicks:0,notes:String(notes||'').trim(),email:String(email||'').trim().slice(0,120)});sponsors=normalizeStatuses(sponsors);saveSponsors(sponsors);return json(res,201,{sponsors}); } if(req.method==='PATCH'&&pathname==='/api/admin/config'){ - const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next}); + const b=await bodyJson(req),cur=getConfig(),next={...cur};for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','showSponsorName','showQueueProgress','bemobPostbackUrl','telegramBotToken','companionBotToken','miniAppShortName','telegramChatId','telegramTopicId','telegramRecruitTopicId','teamRootId','emailFrom','teamAlertEmail','ownerIds','ownerAlertEmail','orgRootId','ctbOfferPostbackUrl','ctbOfferSecret','recruitCtaUrl','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;next.updatedAt=new Date().toISOString();writeJson(CONFIG_FILE,next);return json(res,200,{config:next}); } const m=pathname.match(/^\/api\/admin\/sponsors\/([^/]+)(?:\/(increment|activate|qualify|reset|move))?$/); if(m){const id=decodeURIComponent(m[1]),action=m[2]||null;let sponsors=getSponsors(),idx=sponsors.findIndex(s=>s.id===id);if(idx<0)return json(res,404,{error:'Sponsor not found.'});