Members can change their username and email: in-place profile card replaces the separate alert-email opt-in for signed-in owners

The first-time gate cannot be dismissed; editing later can. The 'Email me my alerts' card becomes
'Your member profile' for a signed-in owner, showing the username and confirmed email with Change
buttons, so the verified address is the one source of truth for both messaging and payout alerts.
Visitors and non-owners keep the original opt-in form unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-16 17:28:26 -05:00
parent 1ea8f8df48
commit 2c5fff20f5
3 changed files with 52 additions and 5 deletions
+2 -2
View File
@@ -21,7 +21,7 @@
<div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div> <div class="table-card" style="margin-bottom:18px;border-color:rgba(123,224,161,.4)"><h2 style="margin:0 0 4px">Your pipeline</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Money forming below you. Each generation in your leg pays your position at exactly one level — when a member's level catches up to their depth, their <em>next</em> upgrade comes to you.</p><div id="dPipeline"></div></div>
<div id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div> <div id="dCoachCard" class="table-card" style="margin-bottom:18px;display:none;border-color:rgba(240,197,109,.35)"><h2 style="margin:0 0 4px">Coach your team</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">The fastest way to grow your own income is helping the people below you take their next step. Here's who in <em>your</em> team could use a nudge today — updated live from the blockchain.</p><div id="dCoach"></div></div>
<div class="table-card" style="margin-bottom:18px;border-color:rgba(120,160,255,.35)"><h2 style="margin:0 0 4px">Messages <span id="msgBell" class="hidden" style="font-size:14px;background:var(--gold);color:#132;border-radius:20px;padding:2px 10px;vertical-align:middle;font-weight:800">🔔 0</span></h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Wallet-verified messaging along your matrix lines — your upline and your team can reach you here, and you can reach them. No email needed; your wallet is your identity. <span class="micro">Messages are member-to-member; the team admin can review them for abuse.</span></p><div id="dMsg"><div class="empty">Loading…</div></div></div> <div class="table-card" style="margin-bottom:18px;border-color:rgba(120,160,255,.35)"><h2 style="margin:0 0 4px">Messages <span id="msgBell" class="hidden" style="font-size:14px;background:var(--gold);color:#132;border-radius:20px;padding:2px 10px;vertical-align:middle;font-weight:800">🔔 0</span></h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Wallet-verified messaging along your matrix lines — your upline and your team can reach you here, and you can reach them. No email needed; your wallet is your identity. <span class="micro">Messages are member-to-member; the team admin can review them for abuse.</span></p><div id="dMsg"><div class="empty">Loading…</div></div></div>
<div class="table-card" style="margin-bottom:18px;border-color:rgba(78,214,203,.35)"><h2 style="margin:0 0 4px">Email me my alerts</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Get an email the moment this position is <strong>paid</strong>, and when it <strong>needs an upgrade</strong> to catch incoming pay — so you never miss one. Opt in with your email; unsubscribe anytime.</p><div id="dAlerts"></div></div> <div class="table-card" style="margin-bottom:18px;border-color:rgba(78,214,203,.35)"><h2 style="margin:0 0 4px" id="dAlertsHead">Email me my alerts</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px" id="dAlertsIntro">Get an email the moment this position is <strong>paid</strong>, and when it <strong>needs an upgrade</strong> to catch incoming pay — so you never miss one. Opt in with your email; unsubscribe anytime.</p><div id="dAlerts"></div></div>
<div class="table-card" style="margin-bottom:18px;border-color:rgba(240,197,109,.45)"><h2 style="margin:0 0 4px">Share this position</h2><div id="dShare"></div><a class="btn btn-primary" href="/tools" style="display:block;text-align:center;margin-top:14px;padding:14px 18px;font-size:16px;font-weight:800">🎬 Get Your Promo Tools — posts, swipes, videos &amp; banners →</a><a class="btn btn-secondary" href="/fast-start" style="display:block;text-align:center;margin-top:8px">🖨️ Print your 48-Hour Fast Start sheet</a><a class="btn btn-secondary" href="/weekly-rhythm" style="display:block;text-align:center;margin-top:8px">🔁 Print your Weekly Rhythm sheet</a><a class="btn btn-secondary" href="/generation-pay" style="display:block;text-align:center;margin-top:8px">📊 Print your Generation Pay chart</a><a class="btn btn-secondary" href="/flyers" style="display:block;text-align:center;margin-top:8px">🪧 Print handouts with your QR code</a><p class="micro" style="text-align:center;margin:8px 0 0">Ready-made content to share with your invite link — no writing needed.</p></div> <div class="table-card" style="margin-bottom:18px;border-color:rgba(240,197,109,.45)"><h2 style="margin:0 0 4px">Share this position</h2><div id="dShare"></div><a class="btn btn-primary" href="/tools" style="display:block;text-align:center;margin-top:14px;padding:14px 18px;font-size:16px;font-weight:800">🎬 Get Your Promo Tools — posts, swipes, videos &amp; banners →</a><a class="btn btn-secondary" href="/fast-start" style="display:block;text-align:center;margin-top:8px">🖨️ Print your 48-Hour Fast Start sheet</a><a class="btn btn-secondary" href="/weekly-rhythm" style="display:block;text-align:center;margin-top:8px">🔁 Print your Weekly Rhythm sheet</a><a class="btn btn-secondary" href="/generation-pay" style="display:block;text-align:center;margin-top:8px">📊 Print your Generation Pay chart</a><a class="btn btn-secondary" href="/flyers" style="display:block;text-align:center;margin-top:8px">🪧 Print handouts with your QR code</a><p class="micro" style="text-align:center;margin:8px 0 0">Ready-made content to share with your invite link — no writing needed.</p></div>
<div class="table-card" style="margin-bottom:18px;border-color:var(--gold)"><h2 style="margin:0 0 4px">Just joined under this position?</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Welcome to the team! Enter the <strong>new member ID</strong> the RM Circle dApp gave you — we'll verify it on the blockchain and let the team know you're in.</p><form id="dJoinForm" style="display:grid;gap:8px;max-width:480px"><input name="newId" class="input" inputmode="numeric" pattern="[0-9]{1,10}" maxlength="10" placeholder="Your NEW RM Circle ID (numbers only)" required><input name="memberName" class="input" maxlength="60" placeholder="Your name or Telegram @handle" required><button class="btn btn-primary">Submit My ID →</button></form><div id="dJoinMsg" style="margin-top:10px;font-size:14px"></div></div> <div class="table-card" style="margin-bottom:18px;border-color:var(--gold)"><h2 style="margin:0 0 4px">Just joined under this position?</h2><p style="color:var(--muted);font-size:13px;margin:0 0 12px">Welcome to the team! Enter the <strong>new member ID</strong> the RM Circle dApp gave you — we'll verify it on the blockchain and let the team know you're in.</p><form id="dJoinForm" style="display:grid;gap:8px;max-width:480px"><input name="newId" class="input" inputmode="numeric" pattern="[0-9]{1,10}" maxlength="10" placeholder="Your NEW RM Circle ID (numbers only)" required><input name="memberName" class="input" maxlength="60" placeholder="Your name or Telegram @handle" required><button class="btn btn-primary">Submit My ID →</button></form><div id="dJoinMsg" style="margin-top:10px;font-size:14px"></div></div>
<div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Payments received</h2><p style="color:var(--muted);font-size:13px;margin:0 0 10px">Every payment your position has received, straight from the smart contract.</p><div id="dIncome"></div></div> <div class="table-card" style="margin-bottom:18px"><h2 style="margin:0 0 4px">Payments received</h2><p style="color:var(--muted);font-size:13px;margin:0 0 10px">Every payment your position has received, straight from the smart contract.</p><div id="dIncome"></div></div>
@@ -31,5 +31,5 @@
</section> </section>
</main> </main>
<footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon and are historical facts, not a promise of future results. Participation involves cryptocurrency and smart-contract risk. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Home</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> <footer class="wrap disclaimer">All figures are read live from the RM Circle smart contract on Polygon and are historical facts, not a promise of future results. Participation involves cryptocurrency and smart-contract risk. Never use funds you cannot afford to lose.<div class="footer-links"><a href="/">Home</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="/qrlib.js"></script><script src="/rmc-wallet.js"></script><script src="/inapp-browser.js"></script><script src="/profile-gate.js?v=20260916c"></script><script src="/my.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="/wallet-notice.js" defer></script> <script src="/announce.js?v=20260916a"></script> <script src="/track.js"></script><script src="/qrlib.js"></script><script src="/rmc-wallet.js"></script><script src="/inapp-browser.js"></script><script src="/profile-gate.js?v=20260916d"></script><script src="/my.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="/wallet-notice.js" defer></script> <script src="/announce.js?v=20260916a"></script>
</body></html> </body></html>
+22
View File
@@ -622,6 +622,28 @@
async function renderAlerts(d){ async function renderAlerts(d){
const el=document.getElementById('dAlerts'); const el=document.getElementById('dAlerts');
if(!el)return; if(!el)return;
// A signed-in owner manages ONE identity here: the verified profile email is
// the alert address, and both it and the username are editable in place.
// Everyone else keeps the original opt-in form, so nothing existing breaks.
try{
const pr=window.RMCProfile?await window.RMCProfile.status():null;
if(pr&&pr.signedIn!==false&&Number(pr.id)===Number(d.id)&&pr.profile&&pr.profile.complete){
const hd=document.getElementById('dAlertsHead'),intro=document.getElementById('dAlertsIntro');
if(hd)hd.textContent='Your member profile';
if(intro)intro.innerHTML='How your team leader reaches you, and where your payout alerts go. Your email is never shown to other members and never sold.';
el.innerHTML=`<div style="display:flex;flex-wrap:wrap;gap:10px 22px;align-items:center;margin:0 0 12px">`+
`<div><div class="micro" style="color:var(--muted)">Username</div><div style="font-weight:700">@${esc(pr.profile.username)}</div></div>`+
`<div><div class="micro" style="color:var(--muted)">Email <span style="color:var(--ok)">✓ confirmed</span></div><div style="font-weight:700">${esc(pr.profile.email)}</div></div>`+
`</div><button id="pgEditUser" class="btn btn-secondary btn-sm">Change username</button> `+
`<button id="pgEditMail" class="btn btn-secondary btn-sm" style="margin-left:6px">Change email</button>`+
`<p class="micro" style="margin:10px 0 0;color:var(--muted)">Payout alerts go to this address. Changing it here changes both.</p>`;
const again=()=>renderAlerts(d);
const bu=document.getElementById('pgEditUser'),bm=document.getElementById('pgEditMail');
if(bu)bu.addEventListener('click',async()=>{await window.RMCProfile.edit('username');again();});
if(bm)bm.addEventListener('click',async()=>{await window.RMCProfile.edit('email');again();});
return;
}
}catch(e){}
let st={subscribed:false}; let st={subscribed:false};
try{st=await (await fetch('/api/public/alert-status?id='+d.id)).json();}catch(e){} try{st=await (await fetch('/api/public/alert-status?id='+d.id)).json();}catch(e){}
function subscribedView(email){ function subscribedView(email){
+28 -3
View File
@@ -9,7 +9,7 @@
// Usage: await window.RMCProfile.require(); // resolves once the profile is complete // Usage: await window.RMCProfile.require(); // resolves once the profile is complete
(function () { (function () {
'use strict'; 'use strict';
var back = null, resolveDone = null, state = null; var back = null, resolveDone = null, state = null, editable = false;
var GOLD = '#d4af37', TEAL = '#4ed6cb'; var GOLD = '#d4af37', TEAL = '#4ed6cb';
function el(tag, css, html) { function el(tag, css, html) {
@@ -35,13 +35,24 @@
'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7);' + 'background:#0a1119;border:1px solid rgba(212,175,55,.55);box-shadow:0 24px 70px rgba(0,0,0,.7);' +
'font-family:system-ui,Segoe UI,Arial,sans-serif;color:#e8eef6;padding:22px 22px 20px;'); 'font-family:system-ui,Segoe UI,Arial,sans-serif;color:#e8eef6;padding:22px 22px 20px;');
card.id = 'pgCard'; card.id = 'pgCard';
if (editable) { // editing an existing profile can be abandoned; the first-time gate can not
var x = el('button', 'position:absolute;top:10px;right:12px;z-index:2;width:34px;height:34px;border-radius:50%;border:none;' +
'cursor:pointer;background:rgba(255,255,255,.08);color:#fff;font-size:20px;line-height:1;font-weight:700;', '&times;');
x.setAttribute('aria-label', 'Close');
x.addEventListener('click', close);
card.appendChild(x);
back.addEventListener('click', function (e) { if (e.target === back) close(); });
}
back.appendChild(card); back.appendChild(card);
document.body.appendChild(back); document.body.appendChild(back);
return card; return card;
} }
function head(card, step) { function head(card, step) {
var keep = card.querySelector('button[aria-label="Close"]');
card.innerHTML = ''; card.innerHTML = '';
if (keep) card.appendChild(keep);
if (editable) return card;
card.appendChild(el('div', 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:' + GOLD + ';margin-bottom:8px;', card.appendChild(el('div', 'text-align:center;letter-spacing:2px;text-transform:uppercase;font-size:11px;font-weight:700;color:' + GOLD + ';margin-bottom:8px;',
'Position #' + esc(state.id) + ' · step ' + step + ' of 2')); 'Position #' + esc(state.id) + ' · step ' + step + ' of 2'));
return card; return card;
@@ -79,6 +90,7 @@
try { try {
var r = await api('/api/public/profile/username', { username: i.value }); var r = await api('/api/public/profile/username', { username: i.value });
state.profile = r.profile; state.profile = r.profile;
if (editable) { close(); return; }
next(); next();
} catch (e) { } catch (e) {
err.textContent = e.message; err.style.display = 'block'; b.disabled = false; b.textContent = 'Save and continue'; err.textContent = e.message; err.style.display = 'block'; b.disabled = false; b.textContent = 'Save and continue';
@@ -130,6 +142,7 @@
try { try {
var r = await api('/api/public/profile/email-verify', { code: ci.value }); var r = await api('/api/public/profile/email-verify', { code: ci.value });
state.profile = r.profile; state.profile = r.profile;
if (editable) { close(); return; }
done(); done();
} catch (e) { } catch (e) {
err.textContent = e.message; err.style.display = 'block'; cb.disabled = false; cb.textContent = 'Confirm and finish'; err.textContent = e.message; err.style.display = 'block'; cb.disabled = false; cb.textContent = 'Confirm and finish';
@@ -154,7 +167,7 @@
function close() { function close() {
if (back && back.parentNode) back.parentNode.removeChild(back); if (back && back.parentNode) back.parentNode.removeChild(back);
back = null; back = null;
if (resolveDone) { var r = resolveDone; resolveDone = null; r(state && state.profile); } if (resolveDone) { var r = resolveDone; resolveDone = null; r((state && state.profile) || null); }
} }
function next() { function next() {
if (!state.profile || !state.profile.username) return stepUsername(); if (!state.profile || !state.profile.username) return stepUsername();
@@ -179,6 +192,18 @@
return new Promise(function (res) { resolveDone = res; next(); }); return new Promise(function (res) { resolveDone = res; next(); });
} }
async function status() { try { return await api('/api/public/profile'); } catch (e) { return null; } } async function status() { try { return await api('/api/public/profile'); } catch (e) { return null; } }
// Change an existing username or email. Dismissable, unlike the first-time gate.
// Resolves with the profile when saved, or null if the member closed it.
async function edit(kind) {
if (back) return null;
try { state = await api('/api/public/profile'); } catch (e) { return null; }
if (!state || state.signedIn === false) return null;
editable = true;
shell();
var p = new Promise(function (res) { resolveDone = res; });
if (kind === 'email') stepEmail(); else stepUsername();
var out = await p; editable = false; return out;
}
window.RMCProfile = { require: require_, status: status }; window.RMCProfile = { require: require_, status: status, edit: edit };
})(); })();