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:
@@ -381,6 +381,25 @@ function writeJson(file, data) {
|
||||
function getSponsors() { return readJson(SPONSORS_FILE).sort((a,b)=>(a.sortOrder||0)-(b.sortOrder||0)); }
|
||||
function saveSponsors(s) { writeJson(SPONSORS_FILE, s); }
|
||||
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 getAnalytics() { try { return readJson(ANALYTICS_FILE); } catch (e) { return { sources: {} }; } }
|
||||
function recordEvent(event, source) {
|
||||
@@ -528,6 +547,15 @@ async function handleApi(req,res,pathname){
|
||||
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||''});
|
||||
}
|
||||
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'){
|
||||
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.'});
|
||||
@@ -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});
|
||||
}
|
||||
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))?$/);
|
||||
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