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;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<title>The contract | InstantAdPay</title>
|
||||
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
@@ -129,8 +129,8 @@
|
||||
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/contract.js?v=20260905y"></script>
|
||||
<script src="/assets/chat.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/contract.js?v=20260905z"></script>
|
||||
<script src="/assets/chat.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+5
-5
@@ -5,7 +5,7 @@
|
||||
<title>InstantAdPay: advertise and earn, locked in code</title>
|
||||
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -410,9 +410,9 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/wallet.js?v=20260905y"></script>
|
||||
<script src="/assets/home.js?v=20260905y"></script>
|
||||
<script src="/assets/chat.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/wallet.js?v=20260905z"></script>
|
||||
<script src="/assets/home.js?v=20260905z"></script>
|
||||
<script src="/assets/chat.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@
|
||||
<title>Live ledger | InstantAdPay</title>
|
||||
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
@@ -25,8 +25,8 @@
|
||||
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/ledger.js?v=20260905y"></script>
|
||||
<script src="/assets/chat.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/ledger.js?v=20260905z"></script>
|
||||
<script src="/assets/chat.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+50
-5
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Member area | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body class="bo-body">
|
||||
|
||||
@@ -82,6 +82,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── sponsor message modal: unmissable on sign-in ── -->
|
||||
<div id="msgModal" class="modal-back" hidden>
|
||||
<div class="modal-card">
|
||||
<p class="eyebrow" id="mmFrom">A message from your sponsor</p>
|
||||
<h3 id="mmSubject" style="margin:.2em 0 .6em"></h3>
|
||||
<div id="mmBody" class="promo-block ib-rich" style="max-height:50vh;overflow:auto"></div>
|
||||
<p style="margin-top:16px;text-align:right"><button class="btn" id="mmAck" type="button">Got it</button></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── signed-in: back-office shell ──────────────────────── -->
|
||||
<div id="memberArea" class="bo" hidden>
|
||||
<aside class="bo-side" id="boSide">
|
||||
@@ -217,6 +227,41 @@
|
||||
first package of $20 or more, they count toward your qualification.</p>
|
||||
<div id="rosterWrap"><p class="muted small" id="rosterEmpty">Nobody yet. Your link is ready above; share it and this list starts filling.</p></div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Your downline, three levels deep</h3>
|
||||
<p class="muted small">Everyone below you across levels 1–3. You see email addresses for your
|
||||
direct referrals only; deeper levels show username and ID. You can message any of them below.</p>
|
||||
<div id="lineageWrap"><p class="muted small">Loading…</p></div>
|
||||
</div>
|
||||
|
||||
<div class="card" id="broadcastCard">
|
||||
<h3>Message your team</h3>
|
||||
<p class="muted small">Send one broadcast a day to your downline. It lands in their on-site inbox
|
||||
and their email. Reaches the people who joined under you — they can't opt out of their sponsor,
|
||||
so make it count.</p>
|
||||
<p><select id="bcScope" style="width:100%">
|
||||
<option value="direct">My direct referrals only (level 1)</option>
|
||||
<option value="all">My whole downline (levels 1–3)</option>
|
||||
</select></p>
|
||||
<p><input id="bcSubject" maxlength="160" placeholder="Subject" style="width:100%"></p>
|
||||
<div class="ed-bar" aria-label="Formatting">
|
||||
<button type="button" data-bc="bold" title="Bold"><b>B</b></button>
|
||||
<button type="button" data-bc="italic" title="Italic"><i>I</i></button>
|
||||
<button type="button" data-bc="underline" title="Underline"><u>U</u></button>
|
||||
<button type="button" data-bcblock="h3" title="Heading">H</button>
|
||||
<button type="button" data-bc="insertUnorderedList" title="Bullet list">• List</button>
|
||||
<button type="button" id="bcLinkBtn" title="Insert link">🔗 Link</button>
|
||||
</div>
|
||||
<div id="bcEd" class="ed-body" contenteditable="true" data-ph="Write to your team…"></div>
|
||||
<p style="margin-top:12px"><button class="btn" id="bcSendBtn" type="button">Send broadcast</button>
|
||||
<span class="small muted" id="bcHint"></span></p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="upMsgCard" hidden>
|
||||
<h3>Messages from your upline</h3>
|
||||
<div id="upList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-buy" hidden>
|
||||
@@ -484,9 +529,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/wallet.js?v=20260905y"></script>
|
||||
<script src="/assets/my.js?v=20260905y"></script>
|
||||
<script src="/assets/chat.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/wallet.js?v=20260905z"></script>
|
||||
<script src="/assets/my.js?v=20260905z"></script>
|
||||
<script src="/assets/chat.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Transaction | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
@@ -33,7 +33,7 @@
|
||||
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
|
||||
</div>
|
||||
</section>
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/tx.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/tx.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Viewing ad — InstantAdPay</title>
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
<style>
|
||||
html,body{height:100%;margin:0;overflow:hidden}
|
||||
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
|
||||
@@ -43,6 +43,6 @@
|
||||
</div>
|
||||
<iframe class="vframe" id="vFrame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups" referrerpolicy="no-referrer" title="Advertiser site"></iframe>
|
||||
</div>
|
||||
<script src="/assets/view.js?v=20260905y"></script>
|
||||
<script src="/assets/view.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Banner wall | InstantAdPay</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905y">
|
||||
<link rel="stylesheet" href="/assets/site.css?v=20260905z">
|
||||
</head>
|
||||
<body>
|
||||
<div id="nav"></div>
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script src="/assets/common.js?v=20260905y"></script>
|
||||
<script src="/assets/wall.js?v=20260905y"></script>
|
||||
<script src="/assets/common.js?v=20260905z"></script>
|
||||
<script src="/assets/wall.js?v=20260905z"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user