Sponsor to downline messaging: lineage view, daily broadcast (email+inbox), login modal
- Downline resolver (3 levels); email visible for directs only, username+ID deeper - Broadcast composer (rich editor) to directs or full downline, 1/day cap, sends email + on-site inbox - Upline messages inbox in My line; unmissable sign-in modal for unread, ack marks read - messages.js dual-mode store; sponsor_messages table; sanitizeRich reused Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -146,6 +146,7 @@
|
||||
const tc = $('dbTeamChip'), wk = (d.referrals || []).filter(r => Date.now() - new Date(r.joined) < 6048e5).length;
|
||||
if (tc && wk) { tc.hidden = false; tc.textContent = '+' + wk + ' this week'; }
|
||||
setInboxBadge(d.inboxUnread || 0);
|
||||
if (d.sponsorMsg) showSponsorModal(d.sponsorMsg);
|
||||
loadCharts(d);
|
||||
$('nextMove').textContent = nextMove(d);
|
||||
renderSteps(d);
|
||||
@@ -228,6 +229,7 @@
|
||||
if ($('boTitle')) $('boTitle').textContent = TITLES[name];
|
||||
if (name === 'earn') setEarnSub(earnSub); // refresh whichever sub-tab is active
|
||||
if (name === 'profile') loadLineBanner();
|
||||
if (name === 'line') { loadLineage(); loadUplineMessages(); }
|
||||
document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer
|
||||
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
|
||||
}
|
||||
@@ -537,6 +539,65 @@
|
||||
}
|
||||
$('vidStartBtn').addEventListener('click', () => loadVideoAd());
|
||||
|
||||
// ── unmissable sponsor-message modal on sign-in ──
|
||||
function showSponsorModal(msg) {
|
||||
if (!$('msgModal')) return;
|
||||
$('mmFrom').textContent = 'A message from ' + (msg.fromName || 'your sponsor');
|
||||
$('mmSubject').textContent = msg.subject || '';
|
||||
$('mmBody').innerHTML = msg.body || ''; // server-sanitized
|
||||
$('msgModal').hidden = false;
|
||||
$('mmAck').onclick = async () => {
|
||||
$('msgModal').hidden = true;
|
||||
try { await fetch('/api/my/messages/' + msg.id + '/read', { method: 'POST' }); } catch (e) {}
|
||||
};
|
||||
}
|
||||
|
||||
// ── downline lineage + sponsor broadcast + upline messages ──
|
||||
async function loadLineage() {
|
||||
try {
|
||||
const r = await (await fetch('/api/my/line')).json();
|
||||
const el = $('lineageWrap');
|
||||
if (r.error || !r.levels || !r.levels.every) return;
|
||||
if (!r.levels.length || !r.levels.some(L => L.members.length)) {
|
||||
el.innerHTML = '<p class="muted small">No one in your downline yet. Share your link and it fills in here.</p>';
|
||||
return;
|
||||
}
|
||||
el.innerHTML = r.levels.map(L => !L.members.length ? '' :
|
||||
'<div class="lin-lvl"><div class="cap">Level ' + L.level + ' · ' + L.members.length + (L.level === 1 ? ' direct' : '') + '</div>'
|
||||
+ L.members.map(m => '<div class="lin-row"><span class="nm">' + esc(m.name) + '</span>'
|
||||
+ (m.email ? '<span class="em">' + esc(m.email) + '</span>' : '<span class="id">#' + m.memberId + '</span>')
|
||||
+ '<span class="id" style="margin-left:auto">' + new Date(m.joined).toLocaleDateString() + '</span></div>').join('')
|
||||
+ '</div>').join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
async function loadUplineMessages() {
|
||||
try {
|
||||
const r = await (await fetch('/api/my/messages')).json();
|
||||
if (r.error) return;
|
||||
const card = $('upMsgCard'), list = $('upList');
|
||||
if (!r.items || !r.items.length) { card.hidden = true; return; }
|
||||
card.hidden = false;
|
||||
list.innerHTML = r.items.map(i => '<div class="promo-block" style="margin-bottom:10px">'
|
||||
+ '<div style="display:flex;justify-content:space-between;gap:10px"><b>' + esc(i.subject) + '</b>'
|
||||
+ '<span class="small muted">' + esc(i.fromName) + ' · ' + new Date(i.sent).toLocaleDateString() + '</span></div>'
|
||||
+ '<div class="ib-rich" style="margin-top:8px">' + (i.body || '') + '</div></div>').join('');
|
||||
// opening the pane marks them read
|
||||
for (const i of r.items) if (!i.read) fetch('/api/my/messages/' + i.id + '/read', { method: 'POST' }).catch(() => {});
|
||||
} catch (e) {}
|
||||
}
|
||||
// broadcast composer editor (its own small rich editor, server sanitizes)
|
||||
document.querySelectorAll('[data-bc]').forEach(b =>
|
||||
b.addEventListener('click', () => { $('bcEd').focus(); document.execCommand(b.dataset.bc, false, null); }));
|
||||
document.querySelectorAll('[data-bcblock]').forEach(b =>
|
||||
b.addEventListener('click', () => { $('bcEd').focus(); document.execCommand('formatBlock', false, b.dataset.bcblock); }));
|
||||
if ($('bcLinkBtn')) $('bcLinkBtn').addEventListener('click', () => { const u = prompt('Link URL (https://…)'); if (u) { $('bcEd').focus(); document.execCommand('createLink', false, u); } });
|
||||
if ($('bcSendBtn')) $('bcSendBtn').addEventListener('click', busy2($('bcSendBtn'), async () => {
|
||||
const r = await api('/api/my/broadcast', { scope: $('bcScope').value, subject: $('bcSubject').value, body: $('bcEd').innerHTML });
|
||||
IAP.status('Broadcast sent to ' + r.sent + ' member' + (r.sent === 1 ? '' : 's') + '.', 'ok');
|
||||
$('bcSubject').value = ''; $('bcEd').innerHTML = '';
|
||||
$('bcHint').textContent = 'Sent. You can send your next broadcast in 24 hours.';
|
||||
}));
|
||||
|
||||
// ── solo-ads inbox: list, read view, dwell-gated read reward ──
|
||||
let ibTimer = null;
|
||||
function setInboxBadge(n) {
|
||||
|
||||
@@ -341,6 +341,17 @@ textarea{resize:vertical;font:inherit}
|
||||
.wall-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;text-align:center}
|
||||
.wall-card img{max-width:100%;border-radius:10px;border:1px solid var(--line-strong)}
|
||||
.wall-pos{font-family:var(--mono);font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px}
|
||||
/* ── modal (sponsor message) ── */
|
||||
.modal-back{position:fixed;inset:0;z-index:100;background:rgba(2,6,5,.72);display:flex;align-items:center;justify-content:center;padding:20px}
|
||||
.modal-card{background:var(--panel-solid);border:1px solid var(--line-strong);border-radius:var(--radius);
|
||||
padding:24px;max-width:520px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,.5)}
|
||||
/* ── downline lineage list ── */
|
||||
.lin-lvl{margin:10px 0}
|
||||
.lin-lvl>.cap{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:6px}
|
||||
.lin-row{display:flex;gap:10px;align-items:baseline;padding:6px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}
|
||||
.lin-row .nm{font-weight:700;min-width:120px}
|
||||
.lin-row .em{font-size:12px;color:var(--mint);overflow-wrap:anywhere}
|
||||
.lin-row .id{font-family:var(--mono);font-size:11px;color:var(--muted)}
|
||||
/* ── solo composer: toolbar + contenteditable editor ── */
|
||||
.ed-bar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
|
||||
.ed-bar button{background:var(--panel);color:var(--ink);border:1px solid var(--line-strong);border-radius:8px;
|
||||
|
||||
Reference in New Issue
Block a user