Files
rm-circle-team-router/public/admin.html
T
martbost e3f6b00f95 Make event pop-up admin-toggleable; drop it from join pages
The Weekly Huddle overlay now reads its content from /api/announce (config-
driven) instead of a baked-in block, and Admin -> Settings -> Event pop-up
exposes a toggle plus editable fields (eyebrow, flyer, join URL, date, times,
auto-hide, event id). Defaults are seeded into config on boot so the panel
reflects real state. Removed the include from join.html so it never interrupts
the join flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-08 05:28:03 -05:00

6 lines
18 KiB
HTML

<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,nofollow"><title>RM Circle Team Build Admin</title><link rel="icon" type="image/png" href="/favicon.png"><link rel="stylesheet" href="/styles.css"></head><body class="admin-bg">
<section id="loginView" class="login-panel"><div class="brand" style="margin-bottom:22px"><img class="brand-mark" src="/logo.jpg" alt="The RM Circle" width="42" height="42"><span>RM Circle<small>Sponsor Router Admin</small></span></div><h1>Team Admin</h1><p>Manage the current sponsor, qualification queue, and onboarding settings.</p><form id="loginForm"><div class="field"><label for="password">Admin password</label><input id="password" class="input" type="password" autocomplete="current-password" required></div><button class="btn btn-primary" style="width:100%">Sign in</button><div id="loginError" class="micro" style="color:var(--danger)"></div></form></section>
<section id="adminView" class="admin hidden"><div class="admin-top"><div><div class="eyebrow">Sponsor router</div><h1>RM Circle Team Build Admin</h1></div><div class="nav-actions"><a class="btn btn-secondary" href="/start" target="_blank">View Live Page ↗</a><button id="logoutBtn" class="btn btn-secondary">Log out</button></div></div>
<nav class="tabs" id="adminTabs" role="tablist"><button class="tab on" type="button" role="tab" data-tab="team">Team</button><button class="tab" type="button" role="tab" data-tab="traffic">Traffic</button><button class="tab" type="button" role="tab" data-tab="members">Members</button><button class="tab" type="button" role="tab" data-tab="money">My Money</button><button class="tab" type="button" role="tab" data-tab="settings">Settings</button></nav><div class="tabpane on" data-pane="team"><div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px"><div><h2 style="margin:0">Sponsor Queue</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Mark a sponsor qualified to automatically activate the next waiting position.</p></div></div><div class="table-wrap"><table class="table"><thead><tr><th>Order</th><th>Sponsor</th><th>Parent</th><th>Directs</th><th>Level</th><th>Status</th><th>Clicks</th><th>Actions</th></tr></thead><tbody id="sponsorRows"></tbody></table></div></div><details class="table-card" id="addSponsorCard" open><summary style="cursor:pointer;list-style:none;display:flex;justify-content:space-between;align-items:center;gap:12px"><span><h2 style="margin:0;display:inline">Add Sponsor</h2><span style="color:var(--muted);font-size:13px;display:block;margin-top:4px">Put a new position into the qualification queue.</span></span><span class="btn btn-secondary btn-sm" style="flex:none;pointer-events:none">Toggle</span></summary><form id="addSponsorForm" style="margin-top:14px"><div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end"><label style="font-size:12px">ID<br><input name="id" class="input" required style="width:96px"></label><label style="font-size:12px">Name<br><input name="name" class="input" required style="width:170px"></label><label style="font-size:12px">Parent ID<br><input name="parentId" class="input" style="width:110px"></label><label style="font-size:12px">Level<br><select name="level" class="select" style="min-width:145px"><option>Scintilla</option><option>Ascensus</option><option>Fabrica</option><option>Culmen</option><option>Apex</option><option>Fastigium</option><option>Vertex</option><option>Corona</option></select></label><label style="font-size:12px;flex:1;min-width:210px">Contact email (optional)<br><input name="email" class="input" type="email" placeholder="member@example.com"></label><label style="font-size:12px;flex:1;min-width:170px">Notes<br><input name="notes" class="input"></label><button class="btn btn-teal">Add to Queue</button></div></form></details><div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Coaching Radar</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Live triage of your whole org: who to nudge, what to tell them, and how much POL is on the line. Computed fresh from the chain index on every refresh.</p></div><button id="coachRefresh" class="btn btn-secondary btn-sm">Refresh</button></div><div id="coachOut"><div class="empty">Loading…</div></div></div><details class="table-card" id="grantCard"><summary style="cursor:pointer;list-style:none;display:flex;justify-content:space-between;align-items:center;gap:12px"><span><h2 style="margin:0;display:inline">Award Suite Capacity</h2><span style="color:var(--muted);font-size:13px;display:block;margin-top:4px">Give any position extra Suite allowance as a team bonus — issued by the team, not taken from anyone.</span></span><span class="btn btn-secondary btn-sm" style="flex:none;pointer-events:none">Open</span></summary><div style="margin-top:14px"><div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end"><label style="font-size:12px">Position<br><input id="agTo" class="input" type="number" min="1" placeholder="46" style="width:110px"></label><label style="font-size:12px">What<br><select id="agTool" class="select" style="min-width:180px"></select></label><label style="font-size:12px">How much<br><input id="agN" class="input" type="number" min="1" step="500" value="5000" style="width:130px"></label><label style="font-size:12px;flex:1;min-width:180px">Reason (shown in the log)<br><input id="agNote" class="input" type="text" maxlength="80" placeholder="Launch bonus"></label><button id="agGo" class="btn btn-primary">Award it</button></div><div id="agMsg" style="margin-top:10px;font-size:14px"></div><div style="margin-top:14px"><div style="color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Bonuses awarded this month</div><div class="table-wrap" style="max-height:240px"><table class="table" style="min-width:520px"><thead><tr><th>To</th><th>What</th><th>Amount</th><th>Reason</th><th>When</th></tr></thead><tbody id="agLog"></tbody></table></div></div></div></details><div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Member Messages</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Wallet-verified member-to-member messages, newest first. Admin can review for abuse — members are told this in the UI.</p></div><button id="msgRefresh" class="btn btn-secondary btn-sm">Refresh</button></div><div class="table-wrap"><table class="table"><thead><tr><th>When</th><th>From</th><th>To</th><th>Message</th><th>Read by</th></tr></thead><tbody id="msgRows"><tr><td colspan="5" class="empty">Loading…</td></tr></tbody></table></div></div></div><div class="tabpane" data-pane="traffic"><div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">Traffic &amp; Conversions</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">First-touch source per visitor session (referring domain or utm_source). Funnel: bridge page → start page → join click.</p></div><div id="funnelStats" class="funnel"></div><div class="table-wrap"><table class="table"><thead><tr><th>Source</th><th>Bridge views</th><th>Start views</th><th>Training views</th><th>Invite views</th><th>Join-now views</th><th>Join clicks</th><th>Confirmed</th><th>Start → Join</th></tr></thead><tbody id="trafficRows"></tbody></table></div></div><div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Your Organization vs. the Network</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">How your team — rooted at your top ID — stacks up against the entire RM Circle smart contract. Live on-chain.</p></div><form id="orgShareForm" style="display:flex;gap:8px"><input id="orgRoot" class="input" style="max-width:110px" placeholder="21" inputmode="numeric"><button class="btn btn-secondary btn-sm">Refresh</button></form></div><div id="orgShare"><div class="empty">Reading the blockchain…</div></div></div></div><div class="tabpane" data-pane="members"><div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">Member ID Submissions</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">New members who confirmed their purchase on the start page. Each one was posted to your Hermes Telegram chat — add them to the rotation.</p></div><div class="table-wrap"><table class="table"><thead><tr><th>When</th><th>Name / Handle</th><th>New ID</th><th>Joined under</th><th>Source</th><th>On-chain</th></tr></thead><tbody id="submissionRows"></tbody></table></div></div><div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">On-Chain Member Lookup</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Enter an RM Circle ID to read its registration, lineage, and every payment it has received — live from the smart contract.</p></div><form id="lookupForm" style="display:flex;gap:10px;margin-bottom:14px"><input id="lookupId" class="input" style="max-width:220px" placeholder="Member ID e.g. 46" inputmode="numeric"><button class="btn btn-teal">Look Up</button></form><div id="lookupResult"></div></div><div class="table-card"><div style="display:flex;justify-content:space-between;gap:12px;align-items:center;margin-bottom:12px;flex-wrap:wrap"><div><h2 style="margin:0">Matrix View</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">The entire on-chain matrix — who landed where, with tier, level, directs, and earnings per position. Click a position to drill down.</p></div><div style="display:flex;gap:8px"><button id="treeLoadBtn" class="btn btn-secondary btn-sm">Load Matrix</button><button id="treeToggleBtn" class="btn btn-secondary btn-sm hidden">List view</button></div></div><div id="matrixNav" class="hidden" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:12px"></div><div id="matrixTree"></div></div></div><div class="tabpane" data-pane="money"><div class="table-card"><div style="margin-bottom:12px"><h2 style="margin:0">My Positions — Income</h2><p style="color:var(--muted);margin:4px 0 0;font-size:13px">Every payment received by your own positions, live from the contract. Comma-separated IDs — saved for next time.</p></div><form id="incomeForm" style="display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap"><input id="incomeIds" class="input" style="max-width:260px" placeholder="21,24,25" inputmode="numeric"><button class="btn btn-teal">Load</button></form><div id="incomeAlert"></div><div id="incomeRouting" style="margin-bottom:14px"></div><div id="incomeSummary" class="facts" style="grid-template-columns:repeat(4,1fr);margin-bottom:12px"></div><div id="incomeResult"></div></div></div><div class="tabpane" data-pane="settings"><div class="pane-split"><div class="stack"><div class="table-card"><h2 style="margin-top:0">AI Chat</h2><p style="color:var(--muted);font-size:13px;margin:4px 0 12px">Paste an OpenRouter API key to switch the help chat from canned answers to AI (<span id="aiModel"></span>). Clear it to switch back.</p><div id="aiStatus" class="micro" style="margin-bottom:10px"></div><form id="aiKeyForm"><div class="field"><label>OpenRouter API key</label><input name="key" class="input" type="password" placeholder="sk-or-v1-…" autocomplete="off"></div><button class="btn btn-teal" style="width:100%">Save Key</button><button type="button" id="aiKeyClear" class="btn btn-secondary" style="width:100%;margin-top:8px">Clear Key (use canned answers)</button></form></div><div class="table-card"><h2 style="margin-top:0">Payment Emails (SendGrid)</h2><p style="color:var(--muted);font-size:13px;margin:4px 0 12px">When a payout hits a member whose sponsor record has a contact email, they get a "you've been paid" email automatically. Paste your SendGrid API key (Branded Voice Coolify app → Environment Variables → SENDGRID_API_KEY).</p><div id="emailStatus" class="micro" style="margin-bottom:10px"></div><form id="sgKeyForm"><div class="field"><label>SendGrid API key</label><input name="key" class="input" type="password" placeholder="SG.…" autocomplete="off"></div><button class="btn btn-teal" style="width:100%">Save Key</button><button type="button" id="sgKeyClear" class="btn btn-secondary" style="width:100%;margin-top:8px">Clear Key (disable emails)</button></form><div class="field" style="margin-top:12px"><label>From address (marketingwithmarty.com or mybrandedvoice.com — DKIM)</label><input id="emailFromInput" class="input" placeholder="The RM Circle Team &lt;no-reply@marketingwithmarty.com&gt;"><button type="button" id="emailFromSave" class="btn btn-secondary" style="width:100%;margin-top:8px">Save From Address</button></div></div></div><div class="table-card"><h2 style="margin-top:0">Public Page Settings</h2><form id="configForm"><div class="field"><label>Site name</label><input name="siteName" class="input"></div><div class="field"><label>Program name</label><input name="programName" class="input"></div><div class="field"><label>Bridge headline</label><input name="bridgeHeadline" class="input"></div><div class="field"><label>Bridge subheadline</label><textarea name="bridgeSubheadline" class="input" rows="3"></textarea></div><div class="field"><label>Premium entry (POL)</label><input name="premiumEntryPol" class="input" type="number"></div><div class="field"><label>RM dApp referral base URL</label><input name="dappReferralBaseUrl" class="input" placeholder="https://app.thermcircle.com?ref="></div><div class="field"><label>BeMob postback URL (paid traffic)</label><input name="bemobPostbackUrl" class="input" placeholder="https://xxxxx.bemobtrcks.com/postback"></div><div class="field"><label>Telegram bot token (Hermes notifications)</label><input name="telegramBotToken" class="input" type="password" autocomplete="off" placeholder="123456:ABC…"></div><div class="field"><label>Telegram chat ID (group or user)</label><input name="telegramChatId" class="input" placeholder="-1001234567890"></div><div class="field"><label>Telegram topic ID (optional, for forum groups)</label><input name="telegramTopicId" class="input" placeholder="55"></div><div class="field"><label>Always-direct position IDs</label><input name="directDefaultIds" class="input" inputmode="numeric" placeholder="139 — comma-separated; these positions skip moving-link rotation by default"><span class="micro" style="display:block;margin-top:4px;color:var(--muted)">Their /join/&lt;id&gt; links place new joins <strong>directly under them</strong> instead of rotating to the next member needing directs — so they keep the entry reward. Add <code>?direct=0</code> to a link to force rotation for that one share.</span></div><div class="field"><label>Team root ID (on-chain team-build alerts)</label><input name="teamRootId" class="input" inputmode="numeric" placeholder="21 — alerts fire for any activity at or below this member ID"></div><div class="field"><label>Team alert email (admin copy of every team-build alert)</label><input name="teamAlertEmail" class="input" type="email" placeholder="you@example.com — emailed for any activity below the team root"></div><div class="field"><label>Upgrade alert email (for your own positions)</label><input name="ownerAlertEmail" class="input" type="email" placeholder="you@example.com — emailed when a position in 'My Positions' needs upgrading to catch incoming pay"></div><div class="field"><label>Telegram/support URL (optional)</label><input name="telegramUrl" class="input"></div><div class="field"><label>Support message</label><textarea name="supportLabel" class="input" rows="3"></textarea></div><label style="text-transform:none;letter-spacing:0;margin:10px 0"><input type="checkbox" name="showSponsorName"> Show sponsor name publicly</label><label style="text-transform:none;letter-spacing:0;margin:10px 0"><input type="checkbox" name="showQueueProgress"> Show number waiting in queue</label><hr style="border:none;border-top:1px solid rgba(255,255,255,.09);margin:18px 0 6px"><h3 style="margin:4px 0 2px;font-size:15px">Event pop-up</h3><p style="color:var(--muted);font-size:12px;margin:0 0 10px">A dismissible overlay shown site-wide (never on join pages). Turn it off after the event, or edit it for the next huddle. Bump the <strong>Event ID</strong> to re-show it to everyone who already dismissed it.</p><label style="text-transform:none;letter-spacing:0;margin:6px 0;display:block"><input type="checkbox" name="announceEnabled"> Show the event pop-up</label><div class="field"><label>Eyebrow</label><input name="announceEyebrow" class="input"></div><div class="field"><label>Flyer image URL</label><input name="announceImg" class="input" placeholder="/huddle-flyer.jpg"></div><div class="field"><label>Join button URL</label><input name="announceMeetUrl" class="input" placeholder="https://meet.google.com/xxx-xxxx-xxx"></div><div class="field"><label>Date label</label><input name="announceDateLabel" class="input" placeholder="Tuesday, September 8"></div><div class="field"><label>Times — one per line as <code>TIME | REGION</code></label><textarea name="announceTimes" class="input" rows="4" placeholder="7:00 PM CST | USA / Canada"></textarea></div><div class="field"><label>Auto-hide after (UTC, ISO)</label><input name="announceExpiresUTC" class="input" placeholder="2026-09-09T02:30:00Z"></div><div class="field"><label>Event ID</label><input name="announceId" class="input" placeholder="huddle-2026-09-08"></div><button class="btn btn-primary" style="width:100%;margin-top:8px">Save Settings</button></form></div></div></div></section>
<div id="toast" class="toast"></div><script src="/admin.js"></script><script src="/admin-grant.js"></script><script src="/admin-tabs.js"></script></body></html>