// screens-dashboard.jsx — staff-only ops cockpit. No user surfaces. // Sections an admin can see. A code-submitter (can_submit_codes && !is_admin) // sees only the subset in SUBMITTER_SECTIONS. const ADMIN_NAV = [ ["users", "users", "Users"], ["broadcast", "code", "Send Code"], ["admin-claims", "bolt", "Claim History"], ["promos", "credit", "Promos"], ["revenue", "chart", "Revenue"], ["health", "shield", "Health"], ["logs", "code", "Logs"], ]; const SUBMITTER_SECTIONS = new Set(["broadcast", "logs"]); const visibleNav = (user) => user?.is_admin ? ADMIN_NAV : user?.can_submit_codes ? ADMIN_NAV.filter(([id]) => SUBMITTER_SECTIONS.has(id)) : []; const Sidebar = ({ active, setActive, user }) => { const nav = visibleNav(user); return ( ); }; const NoAccess = () => (
access not assigned

This dashboard is staff-only.

Day-to-day claiming lives in the Telegram bot. Open it to manage your pass, check balances, and pull recent codes.

window.open("https://t.me/CCUclaimerbot", "_blank")}> Open @CCUclaimerbot
); const Dashboard = ({ user, plan, goto, addToast }) => { const nav = visibleNav(user); const [active, setActive] = React.useState(() => nav[0]?.[0] || null); // If user/role hydrates after first mount, snap to the first allowed section. React.useEffect(() => { const allowed = visibleNav(user).map(([id]) => id); if (allowed.length && !allowed.includes(active)) setActive(allowed[0]); }, [user]); if (!nav.length) return ; return (
); }; const AdminPanel = ({ active, user }) => { if (active === "users") return ; if (active === "broadcast") return ; if (active === "admin-claims") return ; if (active === "promos") return ; if (active === "revenue") return ; if (active === "logs") return ; return ; }; const AdminLogs = () => { const [entries, setEntries] = React.useState([]); const [level, setLevel] = React.useState("INFO"); const [search, setSearch] = React.useState(""); const [paused, setPaused] = React.useState(false); const [headSeq, setHeadSeq] = React.useState(0); const [bufferSize, setBufferSize] = React.useState(0); const tailRef = React.useRef(null); const lastSeqRef = React.useRef(0); const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}` }); const fetchLogs = React.useCallback(async (mode) => { // mode: "full" = reset + fetch latest 200; "tail" = incremental since last seq const params = new URLSearchParams(); if (mode === "tail" && lastSeqRef.current) { params.set("since_seq", String(lastSeqRef.current)); } else { params.set("limit", "300"); } if (level) params.set("level", level); if (search.trim()) params.set("search", search.trim()); try { const r = await fetch(`/api/admin/logs?${params.toString()}`, { headers: authHeader() }); if (!r.ok) return; const d = await r.json(); setHeadSeq(d.head_seq || 0); setBufferSize(d.buffer_size || 0); if (!d.entries?.length) return; lastSeqRef.current = d.entries[d.entries.length - 1].seq; if (mode === "tail") { setEntries(prev => { const merged = [...prev, ...d.entries]; return merged.length > 800 ? merged.slice(-800) : merged; }); } else { setEntries(d.entries); } } catch {} }, [level, search]); // Initial + filter-change full reload React.useEffect(() => { lastSeqRef.current = 0; setEntries([]); fetchLogs("full"); }, [level, search]); // Tail poll (every 2s) unless paused React.useEffect(() => { if (paused) return; const t = setInterval(() => fetchLogs("tail"), 2000); return () => clearInterval(t); }, [paused, fetchLogs]); // Auto-scroll to bottom on new entries (unless user scrolled up) React.useEffect(() => { const el = tailRef.current; if (!el) return; const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80; if (nearBottom) el.scrollTop = el.scrollHeight; }, [entries]); const levelColor = (lvl) => ({ DEBUG: "#6c7280", INFO: "#a3a3a3", WARNING: "#f5a623", ERROR: "#ff5050", CRITICAL: "#ff0066", }[lvl] || "#a3a3a3"); const fmtTs = (ts) => { const d = new Date(ts * 1000); return d.toTimeString().slice(0, 8) + "." + String(d.getMilliseconds()).padStart(3, "0"); }; return (
admin · live logs

Service log tail

setSearch(e.target.value)} style={{ flex: 1, minWidth: 180, background: "#181818", color: "#eee", border: "1px solid #333", borderRadius: 6, padding: "6px 10px" }} /> {entries.length} shown · head seq {headSeq} · buffer {bufferSize}
{entries.length === 0 ?

No log entries yet. The buffer fills as the service writes logs.

: entries.map(e => (
{fmtTs(e.ts)} {e.level} {e.logger} {e.message}
))}
); }; const AdminBroadcast = () => { const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}`, "content-type": "application/json" }); const PLATFORMS = [ { id: "both_stakes", label: "Both Stakes", desc: "Fire .com AND .us dispatch simultaneously — same code, both engines", color: "var(--accent)" }, { id: "stake_com", label: "Stake.com", desc: "API claim for all active Stake.com subscribers", color: "var(--accent)" }, { id: "stake_us", label: "Stake.us", desc: "API claim for all active Stake.us subscribers", color: "var(--accent)" }, { id: "shuffle", label: "Shuffle", desc: "Push to all connected userscript clients", color: "var(--ok)" }, { id: "thrill", label: "Thrill", desc: "Push to all connected userscript clients", color: "var(--ok)" }, ]; const [platform, setPlatform] = React.useState("both_stakes"); const [code, setCode] = React.useState(""); const [sending, setSending] = React.useState(false); const [result, setResult] = React.useState(null); // { ok, message } const [history, setHistory] = React.useState([]); // last few sends const postBroadcast = (plat) => fetch("/api/admin/broadcast", { method: "POST", headers: authHeader(), body: JSON.stringify({ code: code.trim(), platform: plat }), }).then(r => r.json().then(d => ({ ok: !!d.ok, error: d.error, platform: plat }))); const send = async () => { if (!code.trim()) return; setSending(true); setResult(null); try { const targets = platform === "both_stakes" ? ["stake_com", "stake_us"] : [platform]; const results = await Promise.all(targets.map(postBroadcast)); const allOk = results.every(r => r.ok); const plat = PLATFORMS.find(p => p.id === platform); const entry = { code: code.trim(), platform, label: plat.label, ok: allOk, time: new Date().toLocaleTimeString(), // When both-stakes, surface a per-target breakdown in the history line breakdown: targets.length > 1 ? results.map(r => `${r.platform === "stake_com" ? ".com" : ".us"}${r.ok ? "✓" : "✕"}`).join(" ") : null, }; setHistory(h => [entry, ...h].slice(0, 10)); if (allOk) { setResult({ ok: true, message: targets.length > 1 ? `Dispatched to Stake.com AND Stake.us — ${results.length} broadcasts fired` : `Dispatched — all ${plat.label} users notified` }); setCode(""); } else { const failed = results.filter(r => !r.ok); setResult({ ok: false, message: failed.map(r => `${r.platform}: ${r.error || "error"}`).join(" · "), }); } } catch (_) { setResult({ ok: false, message: "Network error" }); } setSending(false); }; const plat = PLATFORMS.find(p => p.id === platform); return (
admin · send code

Manual broadcast

Stake codes trigger API claims instantly. Shuffle/Thrill codes push to connected userscript clients.

{/* Platform picker — Both Stakes spans full width as the headline option, single-platform cards sit underneath in a 2-col grid. */}
Platform
{(() => { const cardStyle = (p) => ({ padding: "12px 14px", borderRadius: "var(--radius-sm)", cursor: "pointer", border: `2px solid ${platform === p.id ? p.color : "var(--border)"}`, background: platform === p.id ? "var(--surf-3)" : "var(--surf-2)", transition: "border-color .15s", }); const cardInner = (p) => ( <>
{p.label}
{p.desc}
); const both = PLATFORMS.find(p => p.id === "both_stakes"); const rest = PLATFORMS.filter(p => p.id !== "both_stakes"); return (
setPlatform(both.id)} style={cardStyle(both)}> {cardInner(both)}
{rest.map(p => (
setPlatform(p.id)} style={cardStyle(p)}> {cardInner(p)}
))}
); })()}
{/* Code input */}
Code
{ setCode(e.target.value); setResult(null); }} onKeyDown={e => e.key === "Enter" && !sending && send()} placeholder={`e.g. BOOST100`} style={{ flex: 1, fontFamily: "var(--font-mono)", letterSpacing: ".06em", fontSize: 15 }} /> {sending ? "Sending…" : `Send → ${plat.label}`}
{result && (
{result.ok ? "✓" : "✕"} {result.message}
)}
{/* Recent sends */} {history.length > 0 && (
Recent sends
{history.map((h, i) => (
{h.ok ? "✓" : "✕"} {h.code} {h.label} {h.breakdown && {h.breakdown}}
{h.time}
))}
)}
); }; const AdminUsers = () => { const [users, setUsers] = React.useState([]); const [loading, setLoading] = React.useState(true); const [search, setSearch] = React.useState(""); const [busy, setBusy] = React.useState({}); const [revealed, setRevealed] = React.useState({}); const [granting, setGranting] = React.useState({}); // userId → { plan, days } | null const [grantOk, setGrantOk] = React.useState({}); const [slots, setSlots] = React.useState({}); // userId → slot[] | "loading" const [slotBusy, setSlotBusy] = React.useState({}); // slotId → true const [details, setDetails] = React.useState({}); // userId → {stats} | "loading" const [flagBusy, setFlagBusy] = React.useState({}); // `${userId}:${flag}` → true const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}`, "content-type": "application/json" }); const load = () => { fetch("/api/admin/users", { headers: authHeader() }) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.users) setUsers(d.users); setLoading(false); }) .catch(() => setLoading(false)); }; React.useEffect(load, []); // Toggle the per-user slots panel; fetch slots on first open const toggleSlots = (userId) => { if (slots[userId] !== undefined) { setSlots(s => { const n = { ...s }; delete n[userId]; return n; }); return; } setSlots(s => ({ ...s, [userId]: "loading" })); fetch(`/api/admin/users/${userId}/slots`, { headers: authHeader() }) .then(r => r.ok ? r.json() : null) .then(d => setSlots(s => ({ ...s, [userId]: d?.slots || [] }))) .catch(() => setSlots(s => ({ ...s, [userId]: [] }))); }; // Toggle the per-user details panel — lazy-loads the stats endpoint and // caches the user record so flag toggles can rerender without a full reload. const toggleDetails = (userId) => { if (details[userId] !== undefined) { setDetails(s => { const n = { ...s }; delete n[userId]; return n; }); return; } setDetails(s => ({ ...s, [userId]: "loading" })); fetch(`/api/admin/users/${userId}/stats`, { headers: authHeader() }) .then(r => r.ok ? r.json() : null) .then(d => setDetails(s => ({ ...s, [userId]: d || { error: true } }))) .catch(() => setDetails(s => ({ ...s, [userId]: { error: true } }))); }; // Flip a per-user flag (or set granted_slots). Optimistic update on the // visible user list so the pill switches immediately; reverts if the POST // fails. Mirrors the bot's /setccu /setadmin / etc commands. const setFlag = async (userId, key, value) => { const ub = `${userId}:${key}`; setFlagBusy(b => ({ ...b, [ub]: true })); const prev = users.find(u => u.id === userId); setUsers(us => us.map(u => u.id === userId ? { ...u, [key]: value } : u)); try { const r = await fetch(`/api/admin/users/${userId}/flags`, { method: "POST", headers: authHeader(), body: JSON.stringify({ [key]: value }), }); const d = await r.json(); if (!d.ok) throw new Error(d.error || "flag update failed"); } catch (e) { // Revert on failure if (prev) setUsers(us => us.map(u => u.id === userId ? prev : u)); } setFlagBusy(b => ({ ...b, [ub]: false })); }; const setSlotActive = async (userId, slotId, active) => { setSlotBusy(b => ({ ...b, [slotId]: true })); try { const r = await fetch(`/api/admin/slots/${slotId}/active`, { method: "POST", headers: authHeader(), body: JSON.stringify({ active }), }); const d = await r.json(); if (d.ok) { setSlots(s => ({ ...s, [userId]: (s[userId] || []).map(sl => sl.id === slotId ? { ...sl, active } : sl), })); } } catch (_) {} setSlotBusy(b => ({ ...b, [slotId]: false })); }; const act = async (userId, action) => { setBusy(b => ({ ...b, [userId]: action })); const url = `/api/admin/users/${userId}/${action === "suspend" ? "suspend" : action + "-extension"}`; try { const r = await fetch(url, { method: "POST", headers: authHeader(), body: JSON.stringify({}) }); const d = await r.json(); if (action === "approve" && d.token) { setRevealed(rv => ({ ...rv, [userId]: d.token })); } load(); } catch (_) {} setBusy(b => ({ ...b, [userId]: null })); }; const grant = async (userId) => { const g = granting[userId] || { kind: "browser", days: 30 }; setBusy(b => ({ ...b, [userId]: "grant" })); try { const r = await fetch(`/api/admin/users/${userId}/grant`, { method: "POST", headers: authHeader(), body: JSON.stringify({ kind: g.kind, days: parseInt(g.days) || 30 }), }); const d = await r.json(); if (d.ok) { setGrantOk(o => ({ ...o, [userId]: true })); load(); } } catch (_) {} setBusy(b => ({ ...b, [userId]: null })); setTimeout(() => setGrantOk(o => ({ ...o, [userId]: false })), 3000); }; const filtered = users.filter(u => { if (!search) return true; const q = search.toLowerCase(); return (u.username || "").includes(q) || (u.email || "").includes(q); }); // What the admin can grant. Browser/API correspond to the paid time-pass // flow (extends tampermonkey_expires_at / plan_expires_at); Free clears. const PLAN_OPTS = [ { value: "browser", label: "Browser pass (days)" }, { value: "api", label: "API pass (days, comps browser)" }, { value: "free", label: "Free (clear)" }, ]; return (
admin · users

{loading ? "…" : `${users.length} users`}

Refresh
setSearch(e.target.value)} style={{ flex: 1 }} />
{loading ?

Loading…

: (
{filtered.map((u) => { const hasExt = false; // extension not yet available const isBusy = busy[u.id]; const token = revealed[u.id]; const g = granting[u.id] || { kind: "browser", days: "30" }; return (
{u.username || "—"} {u.email || "telegram"}
{u.plan_display || u.plan} {u.plan_expires_at && exp {new Date(u.plan_expires_at).toLocaleDateString()}} {u.is_admin && admin}
{/* Grant free subscription */}
setGranting(gr => ({ ...gr, [u.id]: { ...g, days: e.target.value } }))} placeholder="days" style={{ width: 60, fontSize: 12, padding: "4px 8px" }} /> grant(u.id)}> {isBusy === "grant" ? "…" : grantOk[u.id] ? "✓ Granted!" : "Grant free sub"} act(u.id, "suspend")} style={{ color: "var(--warn)" }}> {isBusy === "suspend" ? "…" : "Suspend"} toggleSlots(u.id)}> {slots[u.id] !== undefined ? "Hide slots" : "API slots"} toggleDetails(u.id)}> {details[u.id] !== undefined ? "Hide details" : "Details"}
{/* Operator-visible per-user record — flag toggles + claim stats */} {details[u.id] !== undefined && ( )} {/* Per-slot enable/disable — for wager-requirement enforcement */} {slots[u.id] !== undefined && (
{slots[u.id] === "loading" ? ( Loading slots… ) : slots[u.id].length === 0 ? ( No API slots configured. ) : (
{slots[u.id].map(sl => (
{sl.active ? "active" : "disabled"} {PLAT_LABEL[sl.platform] || sl.platform} slot {sl.slot_index} · {sl.currency} {sl.api_key_masked} setSlotActive(u.id, sl.id, !sl.active)} style={{ color: sl.active ? "var(--err)" : "var(--ok)" }}> {slotBusy[sl.id] ? "…" : sl.active ? "Disable" : "Enable"}
))}
)}
)} {/* Extension controls — only for ext-capable plans */} {hasExt && (
act(u.id, "approve")}> {isBusy === "approve" ? "…" : <> Approve ext token} act(u.id, "revoke")} style={{ color: "var(--err)", borderColor: "var(--err)" }}> {isBusy === "revoke" ? "…" : "Revoke token"}
)} {token && (
Extension token — copy and share with {u.username}. Not shown again. navigator.clipboard.writeText(token)}>
{token}
)}
); })}
)}
); }; const PLAT_LABEL = { stake_com: "Stake.com", stake_us: "Stake.us", shuffle: "Shuffle", thrill: "Thrill" }; // ── Admin user detail (flags + lifetime/7d metrics) ───────────────────── // Mounted inline under an AdminUsers row when the operator clicks "Details". // Shows the full operator-visible record from the rollout checklist: flags, // access windows, slot grants, lifetime and rolling-7d claim totals broken // out by platform / category / currency. const FLAG_DEFS = [ ["is_admin", "Admin"], ["can_submit_codes", "Can submit codes"], ["is_legacy", "Legacy"], ["beta_tester", "Beta tester"], ["ccu_member", "CCU member"], ["ccu_vip", "CCU VIP"], ["ccu_good_standing", "Good standing"], ["disabled", "Disabled"], ["reloads_paused", "Reloads paused"], ]; const CATEGORY_LABEL = { bonus: "bonus", reload: "reloads", daily_dollar: "daily $" }; const FlagToggle = ({ on, label, busy, onClick }) => ( ); // Re-shape the stats array into a {platform: {category: [{currency, amount, count}]}} map // for compact table rendering. const groupStats = (rows) => { const out = {}; for (const r of rows || []) { out[r.platform] = out[r.platform] || {}; out[r.platform][r.category] = out[r.platform][r.category] || []; out[r.platform][r.category].push(r); } return out; }; const StatsTable = ({ rows, header }) => { const grouped = groupStats(rows); const platforms = Object.keys(grouped).sort(); if (!platforms.length) { return (
{header}
No claimed events yet.
); } return (
{header}
{platforms.map(plat => (
{PLAT_LABEL[plat] || plat} {Object.entries(grouped[plat]).map(([cat, entries]) => (
{CATEGORY_LABEL[cat] || cat}:{" "} {entries.map(e => `${e.amount % 1 === 0 ? e.amount : e.amount.toFixed(4).replace(/0+$/, "").replace(/\.$/, "")} ${e.currency} (${e.count})`).join(", ")}
))}
))}
); }; const UserDetail = ({ user, stats, flagBusy, setFlag }) => { const tmExp = user.tampermonkey_expires_at ? new Date(user.tampermonkey_expires_at) : null; const tmLeft = tmExp ? Math.max(0, Math.round((tmExp - Date.now()) / 86400000)) : null; const planExp = user.plan_expires_at ? new Date(user.plan_expires_at) : null; const planLeft = planExp ? Math.max(0, Math.round((planExp - Date.now()) / 86400000)) : null; return (
{/* Identity */}
{user.telegram_username || user.username || "—"} tg: {user.telegram_id || "—"} · email: {user.email || "—"}
{/* Access */}
Browser pass: {tmLeft != null ? `${tmLeft}d left` : "—"} Plan: {user.plan} {planLeft != null ? `(${planLeft}d)` : ""} Slots: {user.granted_slots ?? 1} granted
{/* Flag toggles */}
{FLAG_DEFS.map(([key, label]) => ( setFlag(user.id, key, !user[key])} /> ))}
{/* Stats */} {stats === "loading" ? ( Loading stats… ) : stats?.error ? ( Failed to load stats. ) : (
)}
Thrill is "coming soon" — its rows are excluded from totals above.
); }; const AdminClaims = () => { const [claims, setClaims] = React.useState([]); const [loading, setLoading] = React.useState(true); const [platform, setPlatform] = React.useState(""); const [search, setSearch] = React.useState(""); const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}` }); const load = (plat) => { setLoading(true); const qs = plat ? `?platform=${plat}` : ""; fetch(`/api/admin/claims${qs}`, { headers: authHeader() }) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.claims) setClaims(d.claims); setLoading(false); }) .catch(() => setLoading(false)); }; React.useEffect(() => load(platform), [platform]); const ok = claims.filter(c => c.status === "claimed").length; const total = claims.length; const rate = total ? Math.round(ok / total * 100) : 0; const filtered = claims.filter(c => { if (!search) return true; const q = search.toLowerCase(); return (c.code || "").includes(q) || (c.username || "").includes(q) || (c.platform || "").includes(q); }); return (
admin · claim history

{loading ? "…" : `${ok} / ${total} claimed · ${rate}%`}

{["", "stake_com", "stake_us", "shuffle", "thrill"].map(p => ( setPlatform(p)}> {p ? PLAT_LABEL[p] : "All"} ))} load(platform)}> Refresh
setSearch(e.target.value)} style={{ width: "100%" }} />
{loading ?

Loading…

: (
{["Status", "Code", "User", "Platform", "Value", "Speed", "Time"].map(h => ( ))} {filtered.map(c => { const ok = c.status === "claimed"; const ts = c.time ? new Date(c.time).toLocaleString([], { month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" }) : "—"; const speed = c.elapsed_ms ? `${c.elapsed_ms}ms` : "—"; const val = c.value && c.currency ? `${c.value} ${c.currency}` : (c.message && !ok ? c.message : "—"); return ( ); })} {!filtered.length && ( )}
{h}
{ok ? "claimed" : "failed"} {c.code} {c.username} {PLAT_LABEL[c.platform] || c.platform} {val} {speed} {ts}
No claims found.
)}
); }; const AdminPromos = () => { const [promos, setPromos] = React.useState([]); const [loading, setLoading] = React.useState(true); const [form, setForm] = React.useState({ code: "", promo_type: "free_days", value: "7", max_uses: "", note: "" }); const [saving, setSaving] = React.useState(false); const [err, setErr] = React.useState(""); const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}`, "content-type": "application/json" }); const load = () => { setLoading(true); fetch("/api/admin/promos", { headers: authHeader() }) .then(r => r.ok ? r.json() : null) .then(d => { if (d?.promos) setPromos(d.promos); setLoading(false); }) .catch(() => setLoading(false)); }; React.useEffect(load, []); const create = async () => { setErr(""); setSaving(true); try { const r = await fetch("/api/admin/promos", { method: "POST", headers: authHeader(), body: JSON.stringify({ code: form.code.toUpperCase(), promo_type: form.promo_type, value: parseInt(form.value) || 0, max_uses: form.max_uses ? parseInt(form.max_uses) : null, note: form.note, }), }); const d = await r.json(); if (d.error) { setErr(d.error); } else { setForm({ code: "", promo_type: "free_days", value: "7", max_uses: "", note: "" }); load(); } } catch (_) { setErr("Network error"); } setSaving(false); }; const deactivate = async (id) => { await fetch(`/api/admin/promos/${id}`, { method: "DELETE", headers: authHeader() }); load(); }; const PRESET_TYPES = [ { type: "free_days", value: 7, label: "7 days free" }, { type: "discount_pct", value: 50, label: "50% off" }, ]; return (
admin · promos

Promo Codes

Create promo
{PRESET_TYPES.map(p => ( setForm(f => ({ ...f, promo_type: p.type, value: String(p.value) }))}> {p.label} ))}
Code
setForm(f => ({ ...f, code: e.target.value.toUpperCase() }))} placeholder="e.g. WELCOME7" style={{ textTransform: "uppercase", letterSpacing: ".06em" }} />
Type
{form.promo_type === "free_days" ? "Days" : "% off"}
setForm(f => ({ ...f, value: e.target.value }))} type="number" min="1" />
Max uses
setForm(f => ({ ...f, max_uses: e.target.value }))} placeholder="∞" type="number" min="1" />
Note (admin only)
setForm(f => ({ ...f, note: e.target.value }))} placeholder="optional memo" />
{saving ? "…" : "Create"}
{err &&

{err}

}
{loading ?

Loading…

: ( {["Code", "Type", "Value", "Used", "Max", "Note", ""].map(h => ( ))} {promos.map(p => ( ))} {!promos.length && ( )}
{h}
{p.code} {p.promo_type === "free_days" ? "free days" : "% off"} {p.promo_type === "free_days" ? `${p.value}d` : `${p.value}%`} {p.used_count} {p.max_uses ?? "∞"} {p.note || "—"} {p.active && ( deactivate(p.id)} style={{ color: "var(--err)", borderColor: "var(--err)" }}> Deactivate )} {!p.active && inactive}
No promos yet.
)}
); }; // Display-only label map for the admin revenue / claims views. The bot // stores time-pass SKUs in cc_subscriptions.plan; rolling them up under // "Browser pass" / "API pass" / "Legacy" matches the customer-facing model. // We don't show House Cut / Prime Cut / Grand Feast names anywhere visible. const PLAN_LABELS = { free: "Free", api: "API pass", // Time-pass SKUs from payments.py tampermonkey_24h: "Browser pass · 24h", tampermonkey_7d: "Browser pass · 7d", tampermonkey_30d: "Browser pass · 30d", api_slot_7d: "API pass · 7d", api_slot_30d: "API pass · 30d", // Server-granted (no payment) browser_pass_grant: "Browser pass · grant", api_pass_grant: "API pass · grant", free_reset: "Free (reset)", // Legacy tier IDs — bucketed so revenue rows still tally but don't surface // House Cut / Prime Cut / Grand Feast names anywhere visible. house_cut: "Legacy plan", prime_cut: "Legacy plan", grand_feast: "Legacy plan", stake_com: "Legacy plan", stake_us: "Legacy plan", bundle: "Legacy plan", shuffle: "Legacy plan", wild_card: "Legacy plan", stake_runner: "Legacy plan", god_mode: "Legacy plan", }; // Used by AdminRevenue to attribute MRR to a plan. Time-pass SKUs are // one-shots, not monthly, so they show actual sticker price. Legacy tiers // were per-slot monthly. const PLAN_PRICES = { tampermonkey_24h: 5, tampermonkey_7d: 15, tampermonkey_30d: 30, api_slot_7d: 20, api_slot_30d: 60, house_cut: 10, prime_cut: 15, grand_feast: 30, }; const AdminRevenue = () => { const [rev, setRev] = React.useState(null); const [loading, setLoading] = React.useState(true); const token = localStorage.getItem("cc_token"); React.useEffect(() => { fetch("/api/admin/revenue", { headers: { Authorization: `Bearer ${token}` } }) .then(r => r.ok ? r.json() : null) .then(d => { if (d) setRev(d); setLoading(false); }) .catch(() => setLoading(false)); }, []); if (loading) return

Loading…

; if (!rev) return

Failed to load revenue data.

; const byPlan = rev.by_plan || {}; return (
admin · revenue

${rev.mrr.toFixed(2)} MRR

{rev.active_subs} active subscribers · ${rev.arpu} ARPU

Subscribers by plan
{Object.entries(byPlan).map(([plan, count]) => { const price = PLAN_PRICES[plan] || 0; const label = PLAN_LABELS[plan] || plan; const planMrr = count * price; return (
{label} 0 ? "ok" : ""} dot={count > 0}>{count} users ${planMrr}/mo
); })}
); }; const AdminHealth = () => { const [stats, setStats] = React.useState(null); const [services, setServices] = React.useState(null); const [loading, setLoading] = React.useState(true); const authHeader = () => ({ Authorization: `Bearer ${localStorage.getItem("cc_token")}` }); const load = () => { setLoading(true); Promise.all([ fetch("/api/admin/stats", { headers: authHeader() }).then(r => r.ok ? r.json() : null), fetch("/api/admin/services", { headers: authHeader() }).then(r => r.ok ? r.json() : null), ]).then(([s, sv]) => { if (s) setStats(s); if (sv) setServices(sv); setLoading(false); }).catch(() => setLoading(false)); }; React.useEffect(() => { load(); }, []); if (loading) return

Loading…

; if (!stats) return

Not authorised or failed to load.

; // turnstile pool — shape: { backend, solver_enabled, stake_com:{tokens,max}, // stake_us:{tokens,max}, solves:{ok,failed,last_ok_at,last_error,last_error_at} } const ts = stats.turnstile || {}; const comPool = ts.stake_com?.tokens ?? 0; const comMax = ts.stake_com?.max ?? 0; const usPool = ts.stake_us?.tokens ?? 0; const usMax = ts.stake_us?.max ?? 0; // Solver is "erroring" only if its most recent failure is newer than its // most recent success — a stale error after recovery shouldn't alarm. const solverErroring = !!ts.solves?.last_error && (ts.solves.last_error_at || 0) > (ts.solves.last_ok_at || 0); // services — shape: { summary:{running,dead,cancelled}, cache_age_s, reload_users, daily_users, tasks:[…] } const sum = services?.summary || { running: 0, dead: 0, cancelled: 0 }; const tasks = services?.tasks || []; const fmtAgo = (epoch) => { if (!epoch) return "never"; const s = Math.max(0, Math.round(Date.now() / 1000 - epoch)); if (s < 60) return `${s}s ago`; if (s < 3600) return `${Math.round(s / 60)}m ago`; return `${Math.round(s / 3600)}h ago`; }; return (
admin · service health

Service health

claimer · live 0 ? "ok" : ""} dot={stats.sse_live > 0}>{stats.sse_live} SSE 0 ? "err" : "ok"} dot> {sum.dead > 0 ? `${sum.dead} task(s) dead` : `${sum.running} task(s) live`} {solverErroring ? "solver erroring" : "solver ok"} Refresh
Claims by platform
{["stake_com", "stake_us", "shuffle", "thrill"].map(p => { const row = (stats.by_platform || {})[p] || { claimed: 0, failed: 0 }; const total = row.claimed + row.failed; const rate = total ? Math.round(row.claimed / total * 100) : 0; return ( ); })}
Turnstile pool
solver backend {ts.backend || "—"}
solver enabled {ts.solver_enabled ? "yes" : "no"}
solves (ok / failed) {(ts.solves?.ok ?? 0)} / {(ts.solves?.failed ?? 0)}
last successful solve {fmtAgo(ts.solves?.last_ok_at)}
{ts.solves?.last_error && (
last solver error {ts.solves.last_error}
)}
user cache age {services?.cache_age_s != null ? `${services.cache_age_s}s` : "—"}
Plans breakdown
{Object.entries(stats.by_plan || {}).map(([plan, count]) => (
{PLAN_LABELS[plan] || plan} {count}
))}
{/* Claim engines — reload + daily-dollar background tasks */}
Claim engines · reload + daily-dollar
{sum.running} running {sum.dead > 0 && {sum.dead} dead} {services?.reload_users ?? 0} reload · {services?.daily_users ?? 0} daily
{tasks.length === 0 ? (

No background tasks running — no users have reload or daily-dollar enabled.

) : (
{["Engine", "Platform", "User", "State", "Last claim"].map(h => ( ))} {tasks.map((t, i) => ( ))}
{h}
{t.engine === "daily_dollar" ? "Daily dollar" : "Reload"} {PLAT_LABEL[t.platform] || t.platform} {(t.user_id || "").slice(0, 8)} {t.state} {fmtAgo(t.last_claim)}
)}
); }; Object.assign(window, { Dashboard });