Sponsor Chat: two-way member<->sponsor messaging (presence, availability, mute)

Evolves the one-way broadcast into real support threads, keeping broadcast alongside.
- messages.js: kind='chat' rides sponsor_messages; sendChat/thread/threadList/
  markChatRead/chatUnread; broadcast inbox + login modal scoped to kind='broadcast'
- accounts.js: presence (last_seen), chat availability toggle, per-member mutes;
  sponsorOf() + isDownlineOf() resolvers
- server.js: /api/my/chat/{send,thread,threads,available,mute} + /api/my/ping
  heartbeat; dashboard emits chatUnread/chatAvailable/sponsor; auth = direct
  sponsor up, any downline down, or an existing thread; email only when offline
  and not mid-chat
- my.html/my.js/site.css: slide-in chat drawer (threads list + live thread,
  4s poll), presence dot, Message buttons on direct rows, Message-my-sponsor
  quick action, availability switch in Profile
- db.js: additive migrations (kind, pair index, chat_available, chat_mutes)
- chatbot.js: canned answer + AI fact for Sponsor Chat

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 05:24:23 -05:00
parent 23136f4ba0
commit ade0a13064
8 changed files with 475 additions and 18 deletions
+40 -7
View File
@@ -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=20260906m">
<link rel="stylesheet" href="/assets/site.css?v=20260907a">
</head>
<body class="bo-body">
@@ -207,6 +207,7 @@
<button type="button" data-goto="earn">👁 View today's ads</button>
<button type="button" data-goto="campaigns">📣 Launch a campaign</button>
<button type="button" id="qaCopyInvite">🔗 Copy my invite link</button>
<button type="button" id="qaMsgSponsor" hidden>💬 Message my sponsor</button>
</div>
</div>
<div class="card"><h3>Earning levels</h3>
@@ -239,7 +240,7 @@
<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>
direct referrals only; deeper levels show username and ID. Message your direct referrals right here.</p>
<div id="lineageWrap"><p class="muted small">Loading…</p></div>
</div>
@@ -562,10 +563,18 @@
<p><button class="btn" id="lbSaveBtn">Save line banner</button></p>
<p class="muted small" id="lbCurrent"></p>
</div>
<div class="card">
<h3>Sponsor chat</h3>
<p class="muted small">Your line can message you one-to-one for help, and you can message anyone
below you. When you are online you chat live; when you are away, messages wait in your inbox and
we email you. Turn this off and you stop taking live chats — people can still leave you a note,
you just reply on your own time.</p>
<label class="switch"><input type="checkbox" id="chatAvailToggle" checked>
<span>Available to chat with my line</span></label>
</div>
<div class="card">
<h3>Account details</h3>
<p class="muted small" id="pfDetails">…</p>
<p class="muted small">More profile fields (avatar, bio, links) are on the roadmap.</p>
</div>
</div>
@@ -595,9 +604,33 @@
</div>
</div>
<script src="/assets/common.js?v=20260906m"></script>
<script src="/assets/wallet.js?v=20260906m"></script>
<script src="/assets/my.js?v=20260906m"></script>
<script src="/assets/chat.js?v=20260906m"></script>
<!-- ── sponsor chat: slide-in drawer (threads list + 1:1 conversation) ── -->
<div id="chatDrawer" class="chat-drawer" hidden>
<div class="chat-head">
<button id="chatBack" class="chat-icon" type="button" hidden aria-label="Back to messages">‹</button>
<span id="chatDot" class="pres-dot" hidden></span>
<div class="chat-who"><b id="chatTitle">Messages</b><span id="chatStatus" class="small muted"></span></div>
<button id="chatMute" class="btn sec small" type="button" hidden>Mute</button>
<button id="chatClose" class="chat-icon" type="button" aria-label="Close">×</button>
</div>
<div id="chatThreads" class="chat-threads"><p class="muted small" style="padding:14px">Loading…</p></div>
<div id="chatConvo" class="chat-convo" hidden>
<div id="chatMsgs" class="chat-msgs"></div>
<div id="chatBanner" class="chat-banner" hidden></div>
<div class="chat-compose">
<textarea id="chatInput" rows="1" placeholder="Write a message…" maxlength="2000"></textarea>
<button id="chatSend" class="btn" type="button">Send</button>
</div>
</div>
</div>
<button id="chatFab" class="chat-fab" type="button" hidden aria-label="Messages">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H9l-4 4V6a1 1 0 0 1 1-1z"/></svg>
<span class="chat-fab-badge" id="chatFabBadge" hidden></span>
</button>
<script src="/assets/common.js?v=20260907a"></script>
<script src="/assets/wallet.js?v=20260907a"></script>
<script src="/assets/my.js?v=20260907a"></script>
<script src="/assets/chat.js?v=20260907a"></script>
</body>
</html>