Unlisted Friend Circle demo video + config-driven recruiting-feed CTA

friend-circle-290a3815db.mp4: Marty-only share URL, referenced from no page.
recruitMsg + its Get Started button now read config.recruitCtaUrl (new PATCH
key, default home page) so notification CTAs can point at a proof-first
landing like /my/21. Tweet CTA was already config-driven (value PATCHed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-24 08:38:10 -05:00
parent 86ed87510b
commit 76a9b449c9
2 changed files with 6 additions and 4 deletions
Binary file not shown.
+6 -4
View File
@@ -139,9 +139,11 @@ function sendTelegram(text, topicId, replyMarkup) {
.catch(e=>console.error('telegram error', e.message));
}
// Recruiting-framed version of a team event for the new-members topic: social
// proof + a call to action to the home page, no internal/contact detail.
// proof + a call to action, no internal/contact detail. CTA destination is
// config.recruitCtaUrl (e.g. Marty's live dashboard as proof-first landing);
// defaults to the home page.
function recruitMsg(evt) {
const home = 'rmcircle.team', tags = '#RMCircle #Polygon #Crypto';
const home = String(getConfig().recruitCtaUrl || 'rmcircle.team').replace(/^https?:\/\//, ''), tags = '#RMCircle #Polygon #Crypto';
if (evt.type === 'payout') {
const line = evt.kind === 'upline'
? `Member #${evt.toId} just earned ${evt.pol.toFixed(2)} POL${evt.gen ? ` — a Gen ${evt.gen} upgrade pass-up` : ''}, paid automatically as their team grew beneath them.`
@@ -818,7 +820,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','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','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.'});
@@ -966,7 +968,7 @@ chain.startIndexer(evt=>{
sendTelegram(text);
// recruiting-framed copy of the SAME event to the new-members topic
// (social proof, CTA -> home). Fires only when a recruit topic is set.
if(c.telegramRecruitTopicId){ const rm=recruitMsg(evt); if(rm) sendTelegram(rm, c.telegramRecruitTopicId, {inline_keyboard:[[{text:'🚀 Get Started — rmcircle.team',url:'https://rmcircle.team/?utm_source=tg'}]]}); }
if(c.telegramRecruitTopicId){ const rm=recruitMsg(evt); if(rm){ const cta='https://'+String(c.recruitCtaUrl||'rmcircle.team').replace(/^https?:\/\//,''); sendTelegram(rm, c.telegramRecruitTopicId, {inline_keyboard:[[{text:'🚀 Get Started — rmcircle.team',url:cta+(cta.includes('?')?'&':'?')+'utm_source=tg'}]]}); } }
// admin email alert — same team-gated events, so deep-leg action still surfaces
if(c.teamAlertEmail){
const subj=evt.type==='registered'?`RM Circle team build: #${evt.id} registered under #${evt.referrerId}`