My Dashboard nav shortcut + analytics whitelist fix for engaged/ctb-offer

nav-dash.js on all header pages: a top-right My Dashboard button when the
visitor is known (msg session from wallet sign-in or the Mini App bridge is
authoritative; else the last dashboard opened on the device). Prospects with
no identity never see it.
recordEvent was silently dropping the new engaged + ctb-offer-* events
(whitelist); now recorded per source, with admin funnel tiles for both.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-24 07:10:52 -05:00
parent b950e378d1
commit a0642516e1
11 changed files with 41 additions and 12 deletions
+5 -3
View File
@@ -8,8 +8,8 @@ function statusClass(s){return `status status-${s}`}
function pct(n,d){return d?`${Math.round((n/d)*100)}%`:'—'} function pct(n,d){return d?`${Math.round((n/d)*100)}%`:'—'}
function renderAnalytics(){ function renderAnalytics(){
const src=(state.analytics&&state.analytics.sources)||{}; const src=(state.analytics&&state.analytics.sources)||{};
const tot={bridge:0,start:0,click:0,training:0,postback:0,purchase:0,join:0,joinnow:0}; const tot={bridge:0,start:0,click:0,training:0,postback:0,purchase:0,join:0,joinnow:0,engaged:0,ctbCredited:0};
const list=Object.entries(src).map(([name,r])=>{tot.bridge+=r.bridge||0;tot.start+=r.start||0;tot.click+=r.click||0;tot.training+=r.training||0;tot.postback+=r.postback||0;tot.purchase+=r.purchase||0;tot.join+=r.join||0;tot.joinnow+=r.joinnow||0;return{name,bridge:r.bridge||0,start:r.start||0,click:r.click||0,training:r.training||0,join:r.join||0,joinnow:r.joinnow||0,purchase:r.purchase||0}}) const list=Object.entries(src).map(([name,r])=>{tot.bridge+=r.bridge||0;tot.start+=r.start||0;tot.click+=r.click||0;tot.training+=r.training||0;tot.postback+=r.postback||0;tot.purchase+=r.purchase||0;tot.join+=r.join||0;tot.joinnow+=r.joinnow||0;tot.engaged+=r.engaged||0;tot.ctbCredited+=r['ctb-offer-credited']||0;return{name,bridge:r.bridge||0,start:r.start||0,click:r.click||0,training:r.training||0,join:r.join||0,joinnow:r.joinnow||0,purchase:r.purchase||0}})
.sort((a,b)=>b.click-a.click||b.start-a.start||b.bridge-a.bridge); .sort((a,b)=>b.click-a.click||b.start-a.start||b.bridge-a.bridge);
document.getElementById('funnelStats').innerHTML= document.getElementById('funnelStats').innerHTML=
`<div class="fact"><small>Bridge views</small><strong>${tot.bridge}</strong></div>`+ `<div class="fact"><small>Bridge views</small><strong>${tot.bridge}</strong></div>`+
@@ -21,7 +21,9 @@ function renderAnalytics(){
(tot.joinnow?`<div class="fact"><small>Join-now views</small><strong>${tot.joinnow}</strong></div>`:'')+ (tot.joinnow?`<div class="fact"><small>Join-now views</small><strong>${tot.joinnow}</strong></div>`:'')+
(tot.purchase?`<div class="fact"><small>Confirmed joins</small><strong>${tot.purchase}</strong></div>`:'')+ (tot.purchase?`<div class="fact"><small>Confirmed joins</small><strong>${tot.purchase}</strong></div>`:'')+
(tot.joinnow&&tot.purchase?`<div class="fact"><small>Join-now → Confirmed</small><strong>${pct(tot.purchase,tot.joinnow)}</strong></div>`:'')+ (tot.joinnow&&tot.purchase?`<div class="fact"><small>Join-now → Confirmed</small><strong>${pct(tot.purchase,tot.joinnow)}</strong></div>`:'')+
(tot.postback?`<div class="fact"><small>BeMob postbacks</small><strong>${tot.postback}</strong></div>`:''); (tot.postback?`<div class="fact"><small>BeMob postbacks</small><strong>${tot.postback}</strong></div>`:'')+
(tot.engaged?`<div class="fact"><small>Engaged 30s+ visits</small><strong>${tot.engaged}</strong></div>`:'')+
(tot.ctbCredited?`<div class="fact"><small>CTB offer credits</small><strong>${tot.ctbCredited}</strong></div>`:'');
const subs=state.submissions||[]; const subs=state.submissions||[];
document.getElementById('submissionRows').innerHTML=subs.map(s=>{ document.getElementById('submissionRows').innerHTML=subs.map(s=>{
const path=s.path==='rotation'?'<span class="status status-active">rotation</span> ':s.path==='leg'?'<span class="status status-qualified">leg</span> ':''; const path=s.path==='rotation'?'<span class="status status-active">rotation</span> ':s.path==='leg'?'<span class="status status-qualified">leg</span> ':'';
+1 -1
View File
@@ -30,4 +30,4 @@
</div></section> </div></section>
</main> </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.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <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.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/training">Training</a><a href="/my">Member Dashboard</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html> <script src="/track.js"></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>
+1 -1
View File
@@ -25,4 +25,4 @@
</div></section> </div></section>
</main> </main>
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. Not an offer, solicitation, or guarantee of income. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. Not an offer, solicitation, or guarantee of income. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/contract">Contract Security</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html> <script src="/track.js"></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>
+1 -1
View File
@@ -74,4 +74,4 @@
</div></section> </div></section>
</main> </main>
<footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. No income is guaranteed. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/training">Training</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <footer class="wrap disclaimer">Independent, unaffiliated team resource. Informational and educational only. No income is guaranteed. Cryptocurrency participation carries risk of total loss.<div class="footer-links"><a href="/">Home</a><a href="/training">Training</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html> <script src="/track.js"></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>
+1 -1
View File
@@ -23,4 +23,4 @@
<section class="section"><div class="wrap"><div class="card" style="text-align:center;padding:34px"><div class="eyebrow">Ready to start?</div><h2 style="font-size:38px;margin:10px 0">See the current team placement.</h2><p style="max-width:680px;margin:0 auto 20px;color:var(--muted)">The onboarding page automatically shows the sponsor position the team is currently helping. Always use the sponsor shown there instead of an old screenshot or saved link.</p><a class="btn btn-primary" href="/start">Open Getting Started Instructions →</a></div></div></section> <section class="section"><div class="wrap"><div class="card" style="text-align:center;padding:34px"><div class="eyebrow">Ready to start?</div><h2 style="font-size:38px;margin:10px 0">See the current team placement.</h2><p style="max-width:680px;margin:0 auto 20px;color:var(--muted)">The onboarding page automatically shows the sponsor position the team is currently helping. Always use the sponsor shown there instead of an old screenshot or saved link.</p><a class="btn btn-primary" href="/start">Open Getting Started Instructions →</a></div></div></section>
</main> </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="/start">Getting Started</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <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="/start">Getting Started</a><a href="/my">Member Dashboard</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/bridge.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></body></html> <script src="/track.js"></script><script src="/bridge.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>
+1 -1
View File
@@ -29,4 +29,4 @@
<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></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></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></body></html> <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>
+27
View File
@@ -0,0 +1,27 @@
// "My Dashboard" shortcut in the top-right nav. Appears when we know who the
// visitor is: an authenticated msg session (web wallet sign-in OR the Telegram
// Mini App auth bridge) is authoritative; otherwise the last dashboard opened
// on this device (ctb.myId, set by my.js) or the tools-page ID. No identity →
// no button, so prospects never see it.
(function () {
'use strict';
if (/^\/my(\/|$)/.test(location.pathname)) return;
var nav = document.querySelector('.nav-actions');
if (!nav) return;
function add(id) {
if (!id || !/^\d{1,15}$/.test(String(id))) return;
if (document.getElementById('navMyDash')) return;
var a = document.createElement('a');
a.id = 'navMyDash';
a.className = 'btn btn-secondary';
a.href = '/my/' + id;
a.textContent = '📊 My Dashboard';
nav.insertBefore(a, nav.firstChild);
}
var stored = null;
try { stored = localStorage.getItem('ctb.myId') || localStorage.getItem('rmc.toolsId'); } catch (e) {}
fetch('/api/public/msg-me')
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (d) { add(d && d.id ? d.id : stored); })
.catch(function () { add(stored); });
})();
+1 -1
View File
@@ -16,4 +16,4 @@
<div class="callout warning" style="margin-top:12px"><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 id="supportBox" class="notice" style="margin-top:12px"></div><div id="supportLinkWrap" class="hidden" style="margin-top:10px"><a id="supportLink" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Open Team Support ↗</a></div></section></div> <div class="callout warning" style="margin-top:12px"><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 id="supportBox" class="notice" style="margin-top:12px"></div><div id="supportLinkWrap" class="hidden" style="margin-top:10px"><a id="supportLink" class="btn btn-secondary" target="_blank" rel="noopener noreferrer">Open Team Support ↗</a></div></section></div>
<figure class="roadmap-figure"><a href="/roadmap.webp" target="_blank" rel="noopener"><img src="/roadmap.webp" alt="RM Circle Premium Team Build Roadmap — core strategy, step-by-step guide, premium levels, and duplication formula" width="1149" height="1369" loading="lazy"></a><figcaption>This roadmap is the plan every member follows — tap to view full size.</figcaption></figure></div></main> <figure class="roadmap-figure"><a href="/roadmap.webp" target="_blank" rel="noopener"><img src="/roadmap.webp" alt="RM Circle Premium Team Build Roadmap — core strategy, step-by-step guide, premium levels, and duplication formula" width="1149" height="1369" loading="lazy"></a><figcaption>This roadmap is the plan every member follows — tap to view full size.</figcaption></figure></div></main>
<footer class="wrap disclaimer">This is an independent RM Circle Team Build onboarding resource, not an owner/principal page. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/my">Already joined? Open your Member Dashboard →</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <footer class="wrap disclaimer">This is an independent RM Circle Team Build onboarding resource, not an owner/principal page. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/my">Already joined? Open your Member Dashboard →</a><a href="/contract">Contract Security</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/start.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></body></html> <script src="/track.js"></script><script src="/start.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>
+1 -1
View File
@@ -477,4 +477,4 @@ Watch the short training, look at the live payment feed, then ask me anything.
</div></section> </div></section>
</main> </main>
<footer class="wrap disclaimer">Independent RM Circle Team Build team resource. Informational only — not an earnings guarantee or investment advice. Cryptocurrency participation carries risk of loss.<div class="footer-links"><a href="/">Strategy</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/disclaimer">Disclaimers</a></div></footer> <footer class="wrap disclaimer">Independent RM Circle Team Build team resource. Informational only — not an earnings guarantee or investment advice. Cryptocurrency participation carries risk of loss.<div class="footer-links"><a href="/">Strategy</a><a href="/training">Training</a><a href="/start">Getting Started</a><a href="/how-pay-works">How You Get Paid</a><a href="/disclaimer">Disclaimers</a></div></footer>
<script src="/tools.js"></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html> <script src="/tools.js"></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script><script src="/nav-dash.js" defer></script></body></html>
+1 -1
View File
@@ -86,4 +86,4 @@
</div></section> </div></section>
</main> </main>
<footer class="wrap disclaimer">This is an independent RM Circle Team Build training resource. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer> <footer class="wrap disclaimer">This is an independent RM Circle Team Build training resource. Always confirm transaction details in your wallet before signing. Never disclose your Secret Recovery Phrase.<div class="footer-links"><a href="/">Strategy</a><a href="/start">Getting Started</a><a href="/contract">Contract Security</a><a href="/admin">Team Admin</a><a href="/disclaimer">Disclaimers</a><a href="/how-pay-works">How You Get Paid</a><a href="/tools">Promo Tools</a></div></footer>
<script src="/track.js"></script><script src="/training.js"></script><script src="/chat.js" defer></script><script src="/translate.js" defer></script><script src="/tg-app.js" defer></script></body></html> <script src="/track.js"></script><script src="/training.js"></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>
+1 -1
View File
@@ -349,7 +349,7 @@ function getConfig() { return readJson(CONFIG_FILE); }
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) {
if (!['bridge','start','click','training','postback','purchase','join','joinnow'].includes(event)) return; if (!['bridge','start','click','training','postback','purchase','join','joinnow','engaged'].includes(event) && !String(event).startsWith('ctb-offer-')) return;
const s = String(source||'').toLowerCase().trim().replace(/[^a-z0-9.()\-_:/ ]/g,'').slice(0,80) || '(direct)'; const s = String(source||'').toLowerCase().trim().replace(/[^a-z0-9.()\-_:/ ]/g,'').slice(0,80) || '(direct)';
const a = getAnalytics(); if (!a.sources) a.sources = {}; const a = getAnalytics(); if (!a.sources) a.sources = {};
if (!a.sources[s]) { if (Object.keys(a.sources).length >= 500) return; a.sources[s] = { bridge:0, start:0, click:0 }; } if (!a.sources[s]) { if (Object.keys(a.sources).length >= 500) return; a.sources[s] = { bridge:0, start:0, click:0 }; }