Make event pop-up admin-toggleable; drop it from join pages
The Weekly Huddle overlay now reads its content from /api/announce (config- driven) instead of a baked-in block, and Admin -> Settings -> Event pop-up exposes a toggle plus editable fields (eyebrow, flyer, join URL, date, times, auto-hide, event id). Defaults are seeded into config on boot so the panel reflects real state. Removed the include from join.html so it never interrupts the join flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -46,12 +46,12 @@ function render(){
|
|||||||
if(!grantAutoLoaded){grantAutoLoaded=true;if(window.agBoot)window.agBoot();}
|
if(!grantAutoLoaded){grantAutoLoaded=true;if(window.agBoot)window.agBoot();}
|
||||||
if(!msgsAutoLoaded){msgsAutoLoaded=true;loadAdminMsgs();}
|
if(!msgsAutoLoaded){msgsAutoLoaded=true;loadAdminMsgs();}
|
||||||
const efi=document.getElementById('emailFromInput');if(efi&&!efi.value)efi.value=state.config.emailFrom||em.from||'';
|
const efi=document.getElementById('emailFromInput');if(efi&&!efi.value)efi.value=state.config.emailFrom||em.from||'';
|
||||||
const f=document.getElementById('configForm'),c=state.config;for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','teamRootId','teamAlertEmail','ownerAlertEmail','directDefaultIds'])if(f.elements[k])f.elements[k].value=c[k]??'';f.elements.showSponsorName.checked=!!c.showSponsorName;f.elements.showQueueProgress.checked=!!c.showQueueProgress;
|
const f=document.getElementById('configForm'),c=state.config;for(const k of ['siteName','programName','bridgeHeadline','bridgeSubheadline','premiumEntryPol','dappReferralBaseUrl','telegramUrl','supportLabel','bemobPostbackUrl','telegramBotToken','telegramChatId','telegramTopicId','teamRootId','teamAlertEmail','ownerAlertEmail','directDefaultIds','announceEyebrow','announceImg','announceMeetUrl','announceDateLabel','announceTimes','announceExpiresUTC','announceId'])if(f.elements[k])f.elements[k].value=c[k]??'';f.elements.showSponsorName.checked=!!c.showSponsorName;f.elements.showQueueProgress.checked=!!c.showQueueProgress;if(f.elements.announceEnabled)f.elements.announceEnabled.checked=c.announceEnabled===undefined?true:!!c.announceEnabled;
|
||||||
}
|
}
|
||||||
document.getElementById('loginForm').addEventListener('submit',async e=>{e.preventDefault();const err=document.getElementById('loginError');err.textContent='';try{await api('/api/admin/login',{method:'POST',body:JSON.stringify({password:document.getElementById('password').value})});document.getElementById('password').value='';await loadState()}catch(x){err.textContent=x.message}});
|
document.getElementById('loginForm').addEventListener('submit',async e=>{e.preventDefault();const err=document.getElementById('loginError');err.textContent='';try{await api('/api/admin/login',{method:'POST',body:JSON.stringify({password:document.getElementById('password').value})});document.getElementById('password').value='';await loadState()}catch(x){err.textContent=x.message}});
|
||||||
document.getElementById('logoutBtn').addEventListener('click',async()=>{await api('/api/admin/logout',{method:'POST'});location.reload()});
|
document.getElementById('logoutBtn').addEventListener('click',async()=>{await api('/api/admin/logout',{method:'POST'});location.reload()});
|
||||||
document.getElementById('addSponsorForm').addEventListener('submit',async e=>{e.preventDefault();const form=e.currentTarget,obj=Object.fromEntries(new FormData(form));try{const d=await api('/api/admin/sponsors',{method:'POST',body:JSON.stringify(obj)});state.sponsors=d.sponsors;form.reset();render();showToast('Sponsor added')}catch(x){showToast(x.message)}});
|
document.getElementById('addSponsorForm').addEventListener('submit',async e=>{e.preventDefault();const form=e.currentTarget,obj=Object.fromEntries(new FormData(form));try{const d=await api('/api/admin/sponsors',{method:'POST',body:JSON.stringify(obj)});state.sponsors=d.sponsors;form.reset();render();showToast('Sponsor added')}catch(x){showToast(x.message)}});
|
||||||
document.getElementById('configForm').addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget,obj=Object.fromEntries(new FormData(f));obj.showSponsorName=f.elements.showSponsorName.checked;obj.showQueueProgress=f.elements.showQueueProgress.checked;obj.premiumEntryPol=Number(obj.premiumEntryPol);try{const d=await api('/api/admin/config',{method:'PATCH',body:JSON.stringify(obj)});state.config=d.config;render();showToast('Settings saved')}catch(x){showToast(x.message)}});
|
document.getElementById('configForm').addEventListener('submit',async e=>{e.preventDefault();const f=e.currentTarget,obj=Object.fromEntries(new FormData(f));obj.showSponsorName=f.elements.showSponsorName.checked;obj.showQueueProgress=f.elements.showQueueProgress.checked;obj.announceEnabled=!!(f.elements.announceEnabled&&f.elements.announceEnabled.checked);obj.premiumEntryPol=Number(obj.premiumEntryPol);try{const d=await api('/api/admin/config',{method:'PATCH',body:JSON.stringify(obj)});state.config=d.config;render();showToast('Settings saved')}catch(x){showToast(x.message)}});
|
||||||
rows.addEventListener('click',async e=>{const b=e.target.closest('button[data-action]');if(!b)return;const id=b.dataset.id,a=b.dataset.action;try{let d;if(a==='rename'){const cur=(state.sponsors.find(s=>s.id===id)||{}).name||'';const nm=prompt(`Name for sponsor ID ${id}:`,cur);if(nm===null||!nm.trim())return;d=await api(`/api/admin/sponsors/${id}`,{method:'PATCH',body:JSON.stringify({name:nm.trim()})});}else if(a==='email'){const cur=(state.sponsors.find(s=>s.id===id)||{}).email||'';const em=prompt(`Contact email for sponsor ID ${id} (leave empty to clear):`,cur);if(em===null)return;d=await api(`/api/admin/sponsors/${id}`,{method:'PATCH',body:JSON.stringify({email:em.trim()})});}else if(a==='delete'){if(!confirm(`Delete sponsor ID ${id}?`))return;d=await api(`/api/admin/sponsors/${id}`,{method:'DELETE'})}else if(a==='inc')d=await api(`/api/admin/sponsors/${id}/increment`,{method:'POST'});else if(a==='qualify')d=await api(`/api/admin/sponsors/${id}/qualify`,{method:'POST'});else if(a==='activate')d=await api(`/api/admin/sponsors/${id}/activate`,{method:'POST'});else if(a==='reset')d=await api(`/api/admin/sponsors/${id}/reset`,{method:'POST'});else if(a==='up'||a==='down')d=await api(`/api/admin/sponsors/${id}/move`,{method:'POST',body:JSON.stringify({direction:a})});if(d&&d.sponsors){state.sponsors=d.sponsors;render();showToast('Updated')}}catch(x){showToast(x.message)}});
|
rows.addEventListener('click',async e=>{const b=e.target.closest('button[data-action]');if(!b)return;const id=b.dataset.id,a=b.dataset.action;try{let d;if(a==='rename'){const cur=(state.sponsors.find(s=>s.id===id)||{}).name||'';const nm=prompt(`Name for sponsor ID ${id}:`,cur);if(nm===null||!nm.trim())return;d=await api(`/api/admin/sponsors/${id}`,{method:'PATCH',body:JSON.stringify({name:nm.trim()})});}else if(a==='email'){const cur=(state.sponsors.find(s=>s.id===id)||{}).email||'';const em=prompt(`Contact email for sponsor ID ${id} (leave empty to clear):`,cur);if(em===null)return;d=await api(`/api/admin/sponsors/${id}`,{method:'PATCH',body:JSON.stringify({email:em.trim()})});}else if(a==='delete'){if(!confirm(`Delete sponsor ID ${id}?`))return;d=await api(`/api/admin/sponsors/${id}`,{method:'DELETE'})}else if(a==='inc')d=await api(`/api/admin/sponsors/${id}/increment`,{method:'POST'});else if(a==='qualify')d=await api(`/api/admin/sponsors/${id}/qualify`,{method:'POST'});else if(a==='activate')d=await api(`/api/admin/sponsors/${id}/activate`,{method:'POST'});else if(a==='reset')d=await api(`/api/admin/sponsors/${id}/reset`,{method:'POST'});else if(a==='up'||a==='down')d=await api(`/api/admin/sponsors/${id}/move`,{method:'POST',body:JSON.stringify({direction:a})});if(d&&d.sponsors){state.sponsors=d.sponsors;render();showToast('Updated')}}catch(x){showToast(x.message)}});
|
||||||
document.getElementById('aiKeyForm').addEventListener('submit',async e=>{e.preventDefault();const inp=e.currentTarget.elements.key,key=inp.value.trim();if(!key){showToast('Paste a key first');return}try{const d=await api('/api/admin/openrouter-key',{method:'POST',body:JSON.stringify({key})});state.aiChat={...(state.aiChat||{}),configured:d.configured};inp.value='';render();showToast('AI chat enabled')}catch(x){showToast(x.message)}});
|
document.getElementById('aiKeyForm').addEventListener('submit',async e=>{e.preventDefault();const inp=e.currentTarget.elements.key,key=inp.value.trim();if(!key){showToast('Paste a key first');return}try{const d=await api('/api/admin/openrouter-key',{method:'POST',body:JSON.stringify({key})});state.aiChat={...(state.aiChat||{}),configured:d.configured};inp.value='';render();showToast('AI chat enabled')}catch(x){showToast(x.message)}});
|
||||||
document.getElementById('aiKeyClear').addEventListener('click',async()=>{if(!confirm('Turn off AI chat and go back to built-in answers?'))return;try{const d=await api('/api/admin/openrouter-key',{method:'POST',body:JSON.stringify({key:''})});state.aiChat={...(state.aiChat||{}),configured:d.configured};render();showToast('AI chat disabled')}catch(x){showToast(x.message)}});
|
document.getElementById('aiKeyClear').addEventListener('click',async()=>{if(!confirm('Turn off AI chat and go back to built-in answers?'))return;try{const d=await api('/api/admin/openrouter-key',{method:'POST',body:JSON.stringify({key:''})});state.aiChat={...(state.aiChat||{}),configured:d.configured};render();showToast('AI chat disabled')}catch(x){showToast(x.message)}});
|
||||||
|
|||||||
+118
-109
@@ -1,114 +1,123 @@
|
|||||||
/* announce.js — dismissible event pop-up overlay (RM Circle).
|
/* announce.js — dismissible event pop-up overlay (RM Circle).
|
||||||
Weekly one-off: edit the EV block below each week (or set enabled:false to hide).
|
Content is admin-controlled: fetched from /api/announce (edit it under
|
||||||
Self-gating: shows once per browser until dismissed, and never after `expiresUTC`. */
|
Admin → Settings → Event pop-up; no deploy needed). Self-gating: shows once
|
||||||
|
per browser until dismissed, and never after `expiresUTC`. */
|
||||||
(function () {
|
(function () {
|
||||||
var EV = {
|
function parseTimes(s) {
|
||||||
id: 'huddle-2026-09-08', // bump this when the event changes (resets dismissals)
|
return String(s || '').split(/\r?\n+/).map(function (l) { return l.trim(); }).filter(Boolean)
|
||||||
enabled: true,
|
.map(function (l) { var i = l.indexOf('|'); return i < 0 ? [l, ''] : [l.slice(0, i).trim(), l.slice(i + 1).trim()]; });
|
||||||
img: '/huddle-flyer.jpg',
|
|
||||||
meet: 'https://meet.google.com/gsw-yhqn-zrc',
|
|
||||||
eyebrow: 'Team RM Circle · Weekly Huddle',
|
|
||||||
dateLabel: 'Tuesday, September 8',
|
|
||||||
times: [
|
|
||||||
['🇺🇸', '7:00 PM CST', 'USA / Canada'],
|
|
||||||
['🇺🇸', '8:00 PM EST', ''],
|
|
||||||
['🇹🇹', '8:00 PM AST', 'Caribbean'],
|
|
||||||
['🇬🇧', '1:00 AM', 'UK']
|
|
||||||
],
|
|
||||||
expiresUTC: '2026-09-09T02:30:00Z' // ~9:30 PM CT Sept 8 (after the huddle ends)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!EV.enabled) return;
|
|
||||||
if (Date.now() > Date.parse(EV.expiresUTC)) return;
|
|
||||||
var KEY = 'rmc-announce-' + EV.id;
|
|
||||||
try { if (localStorage.getItem(KEY) === 'done') return; } catch (e) {}
|
|
||||||
function dismiss() { try { localStorage.setItem(KEY, 'done'); } catch (e) {} close(); }
|
|
||||||
|
|
||||||
var back, card;
|
|
||||||
function close() {
|
|
||||||
if (!back) return;
|
|
||||||
back.style.opacity = '0';
|
|
||||||
setTimeout(function () { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; }, 260);
|
|
||||||
document.removeEventListener('keydown', onKey);
|
|
||||||
}
|
|
||||||
function onKey(e) { if (e.key === 'Escape') dismiss(); }
|
|
||||||
|
|
||||||
function build() {
|
|
||||||
back = document.createElement('div');
|
|
||||||
back.setAttribute('role', 'dialog');
|
|
||||||
back.setAttribute('aria-label', 'Team RM Circle Weekly Huddle invitation');
|
|
||||||
back.style.cssText = 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;' +
|
|
||||||
'padding:20px;background:rgba(3,7,14,.82);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);' +
|
|
||||||
'opacity:0;transition:opacity .28s ease;overflow:auto;';
|
|
||||||
back.addEventListener('click', function (e) { if (e.target === back) dismiss(); });
|
|
||||||
|
|
||||||
card = document.createElement('div');
|
|
||||||
card.style.cssText = 'position:relative;width:100%;max-width:428px;max-height:94vh;overflow:auto;border-radius:20px;' +
|
|
||||||
'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7),0 0 0 1px rgba(212,175,55,.15);' +
|
|
||||||
'font-family:system-ui,Segoe UI,Arial,sans-serif;';
|
|
||||||
|
|
||||||
var x = document.createElement('button');
|
|
||||||
x.setAttribute('aria-label', 'Close');
|
|
||||||
x.innerHTML = '×';
|
|
||||||
x.style.cssText = 'position:absolute;top:10px;right:12px;z-index:2;width:38px;height:38px;border-radius:50%;border:none;cursor:pointer;' +
|
|
||||||
'background:rgba(6,10,16,.72);color:#fff;font-size:24px;line-height:1;font-weight:700;display:grid;place-items:center;' +
|
|
||||||
'box-shadow:0 2px 10px rgba(0,0,0,.5);';
|
|
||||||
x.addEventListener('click', dismiss);
|
|
||||||
|
|
||||||
var eyebrow = document.createElement('div');
|
|
||||||
eyebrow.textContent = EV.eyebrow;
|
|
||||||
eyebrow.style.cssText = 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;' +
|
|
||||||
'color:#d4af37;padding:14px 44px 10px;';
|
|
||||||
|
|
||||||
var img = document.createElement('img');
|
|
||||||
img.src = EV.img;
|
|
||||||
img.alt = 'You are invited — RM Circle online presentation, Tuesday 7:00 PM Central, via Google Meet';
|
|
||||||
img.style.cssText = 'display:block;width:100%;height:auto;';
|
|
||||||
|
|
||||||
var foot = document.createElement('div');
|
|
||||||
foot.style.cssText = 'padding:16px 20px 20px;';
|
|
||||||
|
|
||||||
var dateRow = document.createElement('div');
|
|
||||||
dateRow.innerHTML = '🗓 <b>' + EV.dateLabel + '</b>';
|
|
||||||
dateRow.style.cssText = 'text-align:center;color:#eef2f7;font-size:15px;margin-bottom:12px;';
|
|
||||||
|
|
||||||
var grid = document.createElement('div');
|
|
||||||
grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;';
|
|
||||||
EV.times.forEach(function (t) {
|
|
||||||
var cell = document.createElement('div');
|
|
||||||
cell.style.cssText = 'background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.22);border-radius:10px;padding:8px 10px;';
|
|
||||||
cell.innerHTML = '<div style="font-size:15px;font-weight:800;color:#f3c34a">' + t[1] + '</div>' +
|
|
||||||
(t[2] ? '<div style="font-size:11px;color:#9fb0c2;margin-top:1px">' + t[2] + '</div>' : '<div style="font-size:11px;color:#9fb0c2;margin-top:1px"> </div>');
|
|
||||||
grid.appendChild(cell);
|
|
||||||
});
|
|
||||||
|
|
||||||
var join = document.createElement('a');
|
|
||||||
join.href = EV.meet;
|
|
||||||
join.target = '_blank';
|
|
||||||
join.rel = 'noopener';
|
|
||||||
join.textContent = 'Join Google Meet →';
|
|
||||||
join.style.cssText = 'display:block;text-align:center;text-decoration:none;background:linear-gradient(180deg,#e9c249,#d4af37);' +
|
|
||||||
'color:#0a1119;font-size:18px;font-weight:800;padding:15px;border-radius:12px;box-shadow:0 8px 22px rgba(212,175,55,.35);';
|
|
||||||
join.addEventListener('click', function () { try { localStorage.setItem(KEY, 'done'); } catch (e) {} });
|
|
||||||
|
|
||||||
var link = document.createElement('div');
|
|
||||||
link.textContent = 'meet.google.com/gsw-yhqn-zrc';
|
|
||||||
link.style.cssText = 'text-align:center;color:#9fb0c2;font-size:13px;margin-top:10px;font-family:ui-monospace,Menlo,Consolas,monospace;';
|
|
||||||
|
|
||||||
var later = document.createElement('button');
|
|
||||||
later.textContent = 'Maybe later';
|
|
||||||
later.style.cssText = 'display:block;margin:12px auto 0;background:none;border:none;color:#7d8ea0;font-size:13px;cursor:pointer;text-decoration:underline;';
|
|
||||||
later.addEventListener('click', dismiss);
|
|
||||||
|
|
||||||
foot.appendChild(dateRow); foot.appendChild(grid); foot.appendChild(join); foot.appendChild(link); foot.appendChild(later);
|
|
||||||
card.appendChild(x); card.appendChild(eyebrow); card.appendChild(img); card.appendChild(foot);
|
|
||||||
back.appendChild(card);
|
|
||||||
document.body.appendChild(back);
|
|
||||||
document.addEventListener('keydown', onKey);
|
|
||||||
requestAnimationFrame(function () { back.style.opacity = '1'; });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function go() { setTimeout(build, 1100); }
|
function start(EV) {
|
||||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', go);
|
if (!EV || !EV.enabled || !EV.id || !EV.img) return;
|
||||||
else go();
|
if (EV.expiresUTC) { var exp = Date.parse(EV.expiresUTC); if (exp && Date.now() > exp) return; }
|
||||||
|
var KEY = 'rmc-announce-' + EV.id;
|
||||||
|
try { if (localStorage.getItem(KEY) === 'done') return; } catch (e) {}
|
||||||
|
EV.timesArr = parseTimes(EV.times);
|
||||||
|
|
||||||
|
var back, card;
|
||||||
|
function done() { try { localStorage.setItem(KEY, 'done'); } catch (e) {} }
|
||||||
|
function close() {
|
||||||
|
if (!back) return;
|
||||||
|
back.style.opacity = '0';
|
||||||
|
setTimeout(function () { if (back && back.parentNode) back.parentNode.removeChild(back); back = null; }, 260);
|
||||||
|
document.removeEventListener('keydown', onKey);
|
||||||
|
}
|
||||||
|
function dismiss() { done(); close(); }
|
||||||
|
function onKey(e) { if (e.key === 'Escape') dismiss(); }
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
back = document.createElement('div');
|
||||||
|
back.setAttribute('role', 'dialog');
|
||||||
|
back.setAttribute('aria-label', (EV.eyebrow || 'Event') + ' invitation');
|
||||||
|
back.style.cssText = 'position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;' +
|
||||||
|
'padding:20px;background:rgba(3,7,14,.82);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);' +
|
||||||
|
'opacity:0;transition:opacity .28s ease;overflow:auto;';
|
||||||
|
back.addEventListener('click', function (e) { if (e.target === back) dismiss(); });
|
||||||
|
|
||||||
|
card = document.createElement('div');
|
||||||
|
card.style.cssText = 'position:relative;width:100%;max-width:428px;max-height:94vh;overflow:auto;border-radius:20px;' +
|
||||||
|
'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7),0 0 0 1px rgba(212,175,55,.15);' +
|
||||||
|
'font-family:system-ui,Segoe UI,Arial,sans-serif;';
|
||||||
|
|
||||||
|
var x = document.createElement('button');
|
||||||
|
x.setAttribute('aria-label', 'Close');
|
||||||
|
x.innerHTML = '×';
|
||||||
|
x.style.cssText = 'position:absolute;top:10px;right:12px;z-index:2;width:38px;height:38px;border-radius:50%;border:none;cursor:pointer;' +
|
||||||
|
'background:rgba(6,10,16,.72);color:#fff;font-size:24px;line-height:1;font-weight:700;display:grid;place-items:center;box-shadow:0 2px 10px rgba(0,0,0,.5);';
|
||||||
|
x.addEventListener('click', dismiss);
|
||||||
|
|
||||||
|
var eyebrow = document.createElement('div');
|
||||||
|
eyebrow.textContent = EV.eyebrow || '';
|
||||||
|
eyebrow.style.cssText = 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:#d4af37;padding:14px 44px 10px;';
|
||||||
|
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.src = EV.img;
|
||||||
|
img.alt = EV.eyebrow || 'Event flyer';
|
||||||
|
img.style.cssText = 'display:block;width:100%;height:auto;';
|
||||||
|
|
||||||
|
var foot = document.createElement('div');
|
||||||
|
foot.style.cssText = 'padding:16px 20px 20px;';
|
||||||
|
|
||||||
|
if (EV.dateLabel) {
|
||||||
|
var dateRow = document.createElement('div');
|
||||||
|
dateRow.innerHTML = '🗓 <b></b>';
|
||||||
|
dateRow.querySelector('b').textContent = EV.dateLabel;
|
||||||
|
dateRow.style.cssText = 'text-align:center;color:#eef2f7;font-size:15px;margin-bottom:12px;';
|
||||||
|
foot.appendChild(dateRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EV.timesArr.length) {
|
||||||
|
var grid = document.createElement('div');
|
||||||
|
grid.style.cssText = 'display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px;';
|
||||||
|
EV.timesArr.forEach(function (t) {
|
||||||
|
var cell = document.createElement('div');
|
||||||
|
cell.style.cssText = 'background:rgba(212,175,55,.08);border:1px solid rgba(212,175,55,.22);border-radius:10px;padding:8px 10px;';
|
||||||
|
var time = document.createElement('div');
|
||||||
|
time.style.cssText = 'font-size:15px;font-weight:800;color:#f3c34a;'; time.textContent = t[0];
|
||||||
|
var region = document.createElement('div');
|
||||||
|
region.style.cssText = 'font-size:11px;color:#9fb0c2;margin-top:1px;'; region.textContent = t[1] || ' ';
|
||||||
|
cell.appendChild(time); cell.appendChild(region); grid.appendChild(cell);
|
||||||
|
});
|
||||||
|
foot.appendChild(grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
var join = document.createElement('a');
|
||||||
|
join.href = EV.meetUrl || '#'; join.target = '_blank'; join.rel = 'noopener';
|
||||||
|
join.textContent = 'Join Google Meet →';
|
||||||
|
join.style.cssText = 'display:block;text-align:center;text-decoration:none;background:linear-gradient(180deg,#e9c249,#d4af37);' +
|
||||||
|
'color:#0a1119;font-size:18px;font-weight:800;padding:15px;border-radius:12px;box-shadow:0 8px 22px rgba(212,175,55,.35);';
|
||||||
|
join.addEventListener('click', done);
|
||||||
|
foot.appendChild(join);
|
||||||
|
|
||||||
|
if (EV.meetUrl) {
|
||||||
|
var link = document.createElement('div');
|
||||||
|
link.textContent = EV.meetUrl.replace(/^https?:\/\//, '');
|
||||||
|
link.style.cssText = 'text-align:center;color:#9fb0c2;font-size:13px;margin-top:10px;font-family:ui-monospace,Menlo,Consolas,monospace;word-break:break-all;';
|
||||||
|
foot.appendChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
var later = document.createElement('button');
|
||||||
|
later.textContent = 'Maybe later';
|
||||||
|
later.style.cssText = 'display:block;margin:12px auto 0;background:none;border:none;color:#7d8ea0;font-size:13px;cursor:pointer;text-decoration:underline;';
|
||||||
|
later.addEventListener('click', dismiss);
|
||||||
|
foot.appendChild(later);
|
||||||
|
|
||||||
|
card.appendChild(x); card.appendChild(eyebrow); card.appendChild(img); card.appendChild(foot);
|
||||||
|
back.appendChild(card); document.body.appendChild(back);
|
||||||
|
document.addEventListener('keydown', onKey);
|
||||||
|
requestAnimationFrame(function () { back.style.opacity = '1'; });
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(build, 1100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchAndGo() {
|
||||||
|
fetch('/api/announce', { headers: { 'Accept': 'application/json' } })
|
||||||
|
.then(function (r) { return r.ok ? r.json() : null; })
|
||||||
|
.then(function (EV) { if (EV) start(EV); })
|
||||||
|
.catch(function () {});
|
||||||
|
}
|
||||||
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fetchAndGo);
|
||||||
|
else fetchAndGo();
|
||||||
})();
|
})();
|
||||||
|
|||||||
+1
-1
@@ -29,5 +29,5 @@
|
|||||||
<div class="callout warning" style="margin-top:12px;max-width:880px;margin-left:auto;margin-right:auto"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div></div></section>
|
<div class="callout warning" style="margin-top:12px;max-width:880px;margin-left:auto;margin-right:auto"><strong>Risk reminder:</strong> participation involves cryptocurrency and smart-contract risk. No income is guaranteed. Use only funds you can afford to lose.</div></div></section>
|
||||||
|
|
||||||
</main><footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a><a href="/privacy">Privacy</a><a href="/refunds">Refunds</a></div></footer>
|
</main><footer class="wrap disclaimer">This independent team page is educational and is not an earnings guarantee or investment advice. Cryptocurrency and smart-contract participation involve risk, including possible loss of funds. Never use funds you cannot afford to lose. Results depend on actual participation, qualification, upgrades, smart-contract behavior, and the market value of POL.<div class="footer-links"><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/tools">Promo Tools</a><a href="/privacy">Privacy</a><a href="/refunds">Refunds</a></div></footer>
|
||||||
<script src="/track.js"></script><script src="/join.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script> <script src="/announce.js"></script>
|
<script src="/track.js"></script><script src="/join.js"></script><script src="/payouts.js" defer></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -381,6 +381,25 @@ function writeJson(file, data) {
|
|||||||
function getSponsors() { return readJson(SPONSORS_FILE).sort((a,b)=>(a.sortOrder||0)-(b.sortOrder||0)); }
|
function getSponsors() { return readJson(SPONSORS_FILE).sort((a,b)=>(a.sortOrder||0)-(b.sortOrder||0)); }
|
||||||
function saveSponsors(s) { writeJson(SPONSORS_FILE, s); }
|
function saveSponsors(s) { writeJson(SPONSORS_FILE, s); }
|
||||||
function getConfig() { return readJson(CONFIG_FILE); }
|
function getConfig() { return readJson(CONFIG_FILE); }
|
||||||
|
// Event pop-up (announce.js) defaults — seeded into config once so the admin
|
||||||
|
// panel shows the real state and admins can toggle/edit it without a deploy.
|
||||||
|
const ANNOUNCE_DEFAULTS = {
|
||||||
|
announceEnabled: true,
|
||||||
|
announceId: 'huddle-2026-09-08',
|
||||||
|
announceImg: '/huddle-flyer.jpg',
|
||||||
|
announceMeetUrl: 'https://meet.google.com/gsw-yhqn-zrc',
|
||||||
|
announceEyebrow: 'Team RM Circle · Weekly Huddle',
|
||||||
|
announceDateLabel: 'Tuesday, September 8',
|
||||||
|
announceTimes: '7:00 PM CST | USA / Canada\n8:00 PM EST |\n8:00 PM AST | Caribbean\n1:00 AM | UK',
|
||||||
|
announceExpiresUTC: '2026-09-09T02:30:00Z'
|
||||||
|
};
|
||||||
|
function seedAnnounceDefaults() {
|
||||||
|
try { const c = readJson(CONFIG_FILE) || {}; let ch = false;
|
||||||
|
for (const k in ANNOUNCE_DEFAULTS) if (!(k in c)) { c[k] = ANNOUNCE_DEFAULTS[k]; ch = true; }
|
||||||
|
if (ch) writeJson(CONFIG_FILE, c);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
seedAnnounceDefaults();
|
||||||
function activeSponsor(sponsors) { return sponsors.find(s=>s.status==='active') || sponsors.find(s=>s.status==='waiting') || null; }
|
function activeSponsor(sponsors) { return sponsors.find(s=>s.status==='active') || sponsors.find(s=>s.status==='waiting') || null; }
|
||||||
function getAnalytics() { try { return readJson(ANALYTICS_FILE); } catch (e) { return { sources: {} }; } }
|
function getAnalytics() { try { return readJson(ANALYTICS_FILE); } catch (e) { return { sources: {} }; } }
|
||||||
function recordEvent(event, source) {
|
function recordEvent(event, source) {
|
||||||
@@ -528,6 +547,15 @@ async function handleApi(req,res,pathname){
|
|||||||
if(req.method==='GET'&&pathname==='/api/public/config'){
|
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,walletNotice:c.walletNotice||''});
|
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/announce'){
|
||||||
|
const c=getConfig();
|
||||||
|
return json(res,200,{
|
||||||
|
enabled: c.announceEnabled===undefined ? true : !!c.announceEnabled,
|
||||||
|
id: c.announceId||'', img: c.announceImg||'', meetUrl: c.announceMeetUrl||'',
|
||||||
|
eyebrow: c.announceEyebrow||'', dateLabel: c.announceDateLabel||'',
|
||||||
|
times: c.announceTimes||'', expiresUTC: c.announceExpiresUTC||''
|
||||||
|
});
|
||||||
|
}
|
||||||
if(req.method==='GET'&&pathname==='/api/public/member'){
|
if(req.method==='GET'&&pathname==='/api/public/member'){
|
||||||
const ip=String(req.headers['x-forwarded-for']||req.socket.remoteAddress||'').split(',')[0].trim();
|
const ip=String(req.headers['x-forwarded-for']||req.socket.remoteAddress||'').split(',')[0].trim();
|
||||||
if(memberLookupLimited(ip))return json(res,429,{error:'Too many lookups — give it a minute.'});
|
if(memberLookupLimited(ip))return json(res,429,{error:'Too many lookups — give it a minute.'});
|
||||||
@@ -1523,7 +1551,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});
|
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'){
|
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','walletNotice','tweetEnabled','tweetCtaUrl','tweetHashtags','blotatoTwitterId','dappFallbackPublic','moonpayPublicKey','moonpaySecretKey','publicRotationMode','publicRotationRootId','rotationExcludeIds','suiteAllowlist','suiteToolsInAlerts','suiteLevelOverride','directDefaultIds'])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','suiteAllowlist','suiteToolsInAlerts','suiteLevelOverride','directDefaultIds','announceEnabled','announceId','announceImg','announceMeetUrl','announceEyebrow','announceDateLabel','announceTimes','announceExpiresUTC'])if(Object.prototype.hasOwnProperty.call(b,k))next[k]=b[k];next.premiumEntryPol=Number(next.premiumEntryPol)||362;if('announceEnabled' in next)next.announceEnabled=!!next.announceEnabled;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))?$/);
|
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.'});
|
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.'});
|
||||||
|
|||||||
Reference in New Issue
Block a user