diff --git a/public/admin-tabs.js b/public/admin-tabs.js new file mode 100644 index 0000000..5b5d545 --- /dev/null +++ b/public/admin-tabs.js @@ -0,0 +1,60 @@ +// Admin tab bar. Separate file because the site's CSP is script-src 'self'. +// +// The admin page had fourteen panels in a fixed 1.5fr/.75fr split — ten tall +// ones on the left, four short forms on the right. The right column ran out a +// quarter of the way down and you scrolled past dead space for the rest. +// Grouping them into tabs lets every panel use the full width. +(function () { + 'use strict'; + var KEY = 'rmc.admin.tab'; + + function panes() { return [].slice.call(document.querySelectorAll('.tabpane')); } + function tabs() { return [].slice.call(document.querySelectorAll('#adminTabs .tab')); } + + function show(key) { + var found = false; + panes().forEach(function (p) { + var on = p.getAttribute('data-pane') === key; + p.classList.toggle('on', on); + if (on) found = true; + }); + if (!found) return false; + tabs().forEach(function (t) { + var on = t.getAttribute('data-tab') === key; + t.classList.toggle('on', on); + t.setAttribute('aria-selected', on ? 'true' : 'false'); + }); + try { localStorage.setItem(KEY, key); } catch (e) {} + return true; + } + + document.addEventListener('DOMContentLoaded', function () { + var bar = document.getElementById('adminTabs'); + if (!bar) return; + + bar.addEventListener('click', function (e) { + var t = e.target.closest('.tab'); + if (t) show(t.getAttribute('data-tab')); + }); + + // Left/right arrows move between tabs, as a tablist should. + bar.addEventListener('keydown', function (e) { + if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; + var all = tabs(); + var i = all.indexOf(document.activeElement); + if (i < 0) return; + var next = all[(i + (e.key === 'ArrowRight' ? 1 : all.length - 1)) % all.length]; + next.focus(); + show(next.getAttribute('data-tab')); + e.preventDefault(); + }); + + // Come back to whichever tab was last open. + var saved = null; + try { saved = localStorage.getItem(KEY); } catch (e) {} + if (!saved || !show(saved)) { + var first = tabs()[0]; + if (first) show(first.getAttribute('data-tab')); + } + }); +})(); diff --git a/public/admin.html b/public/admin.html index 087ae7c..cc0bcea 100644 --- a/public/admin.html +++ b/public/admin.html @@ -1,18 +1,5 @@ RM Circle Team Build Admin
The RM CircleRM CircleSponsor Router Admin

Team Admin

Manage the current sponsor, qualification queue, and onboarding settings.

-
+

Sponsor Queue

Mark a sponsor qualified to automatically activate the next waiting position.

OrderSponsorParentDirectsLevelStatusClicksActions

Add Sponsor

Coaching Radar

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.

Loading…

Award Suite Capacity

Give any position extra Suite allowance as a team bonus — issued by the team, not taken from anyone.
Open
Bonuses awarded this month
ToWhatAmountReasonWhen

Member Messages

Wallet-verified member-to-member messages, newest first. Admin can review for abuse — members are told this in the UI.

WhenFromToMessageRead by
Loading…

Traffic & Conversions

First-touch source per visitor session (referring domain or utm_source). Funnel: bridge page → start page → join click.

SourceBridge viewsStart viewsTraining viewsInvite viewsJoin-now viewsJoin clicksConfirmedStart → Join

Your Organization vs. the Network

How your team — rooted at your top ID — stacks up against the entire RM Circle smart contract. Live on-chain.

Reading the blockchain…

Member ID Submissions

New members who confirmed their purchase on the start page. Each one was posted to your Hermes Telegram chat — add them to the rotation.

WhenName / HandleNew IDJoined underSourceOn-chain

On-Chain Member Lookup

Enter an RM Circle ID to read its registration, lineage, and every payment it has received — live from the smart contract.

Matrix View

The entire on-chain matrix — who landed where, with tier, level, directs, and earnings per position. Click a position to drill down.

My Positions — Income

Every payment received by your own positions, live from the contract. Comma-separated IDs — saved for next time.

AI Chat

Paste an OpenRouter API key to switch the help chat from canned answers to AI (). Clear it to switch back.

Payment Emails (SendGrid)

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).

Public Page Settings

+
diff --git a/public/styles.css b/public/styles.css index 63102cb..2838b22 100644 --- a/public/styles.css +++ b/public/styles.css @@ -248,3 +248,16 @@ a.brand{text-decoration:none;color:inherit} .pay-diagram{width:100%;min-width:640px;height:auto;display:block} img.brand-mark{object-fit:cover;font-size:0} + +/* Admin tabs — replaces the old fixed 1.5fr/.75fr admin-grid, which left the + right column empty for most of the page. */ +.tabs{display:flex;gap:6px;flex-wrap:wrap;border-bottom:1px solid #27445e;margin-bottom:18px} +.tab{appearance:none;background:transparent;border:0;border-bottom:2px solid transparent;color:#8da3b5;font:inherit;font-weight:800;font-size:14px;letter-spacing:.4px;padding:12px 17px;cursor:pointer;border-radius:10px 10px 0 0;transition:.15s ease} +.tab:hover{color:var(--text);background:rgba(255,255,255,.035)} +.tab.on{color:var(--gold);border-bottom-color:var(--gold);background:rgba(243,190,67,.07)} +.tab:focus-visible{outline:2px solid var(--teal);outline-offset:-2px} +.tabpane{display:none;gap:18px;grid-template-columns:1fr;align-content:start} +.tabpane.on{display:grid} +.pane-split{display:grid;grid-template-columns:1.6fr .9fr;gap:18px;align-items:start} +#grantCard summary::-webkit-details-marker{display:none} +@media(max-width:900px){.pane-split{grid-template-columns:1fr}.tab{padding:10px 12px;font-size:13px}}