Files
rm-circle-team-router/public/replays.js
T
martbost 79470e471c Members area: weekly webinar replay page at /replays
Wallet-gated like the Method lessons: the API returns titles/dates to
everyone (prospects see the shape of the product) and video URLs only with a
member session. List lives in the data volume (replays.json) so the weekly
add never needs a deploy; videos stream from the DO Spaces bucket (CSP
media-src added). Teaser on /training; chatbot + AI prompt updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-02 06:46:06 -05:00

72 lines
4.8 KiB
JavaScript

// Webinar replays — members-area page. The list comes from /api/public/replays:
// locked responses carry titles/dates only (prospects see the shape of the
// product), a member session gets the video URLs. Unlock is the same
// one-signature SIWE flow as the training page (msg-challenge/msg-verify via
// RMCWallet); Mini App visitors already carry a session and see no lock.
(function () {
'use strict';
function $(id) { return document.getElementById(id); }
function esc(s) { return String(s == null ? '' : s).replace(/[&<>'"]/g, function (c) { return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;' })[c]; }); }
function card(r, locked) {
var head = '<div style="padding:6px 6px 0"><div class="eyebrow" style="color:var(--teal)">' + esc(r.date || '') + (r.minutes ? ' · ' + esc(r.minutes) + ' min' : '') + '</div>' +
'<h2 style="margin:6px 0 6px">' + esc(r.title || 'Team webinar') + '</h2>' +
(r.blurb ? '<p style="color:var(--muted);margin:0 0 12px">' + esc(r.blurb) + '</p>' : '') + '</div>';
if (locked || !r.url) {
return '<div class="video-card rmc-lock" style="margin:0 0 26px">' + head +
'<div style="padding:8px 6px 10px">' +
'<p class="micro" style="color:var(--muted);margin:0 0 12px">🔒 Replays come with every team position. Sign in with the wallet that owns your position — one free signature, it can\'t move funds. Linked on Telegram? The ☰ Mini App in @RMCircleBot unlocks everything automatically.</p>' +
'<button class="btn btn-primary btn-sm rmc-unlock">🔓 I\'m a member — connect &amp; unlock</button> ' +
'<a class="btn btn-secondary btn-sm" href="/start">Not a member? See how to join</a>' +
'<div class="micro rmc-lock-err" style="color:#ff7b7b;margin-top:8px"></div></div></div>';
}
return '<div class="video-card" style="margin:0 0 26px">' + head +
'<video class="video-frame" style="display:block;width:100%;height:auto;aspect-ratio:16/9" controls preload="metadata"' + (r.poster ? ' poster="' + esc(r.poster) + '"' : '') + ' src="' + esc(r.url) + '"></video></div>';
}
var UNLOCK_LABEL = '🔓 I\'m a member — connect & unlock';
function wireUnlock() {
document.querySelectorAll('button.rmc-unlock').forEach(function (b) {
b.addEventListener('click', function (ev) {
var btn = ev.currentTarget, err = btn.closest('.rmc-lock').querySelector('.rmc-lock-err');
btn.disabled = true; btn.textContent = 'Connecting…';
(async function () {
try {
var eth = window.RMCWallet && await window.RMCWallet.pick();
if (!eth) { err.textContent = 'No wallet in this browser. On a phone, open this page inside your wallet app\'s browser — or use the Telegram Mini App.'; btn.disabled = false; btn.textContent = UNLOCK_LABEL; return; }
var accounts = await eth.request({ method: 'eth_requestAccounts' });
var account = accounts && accounts[0];
try { await window.RMCWallet.ensureChain(eth); } catch (ce) { err.textContent = 'Switch your wallet to the Polygon network, then tap unlock again.'; btn.disabled = false; btn.textContent = UNLOCK_LABEL; return; }
var ch = await (await fetch('/api/public/msg-challenge', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account }) })).json();
if (ch.error) throw new Error(ch.error);
var sig = await eth.request({ method: 'personal_sign', params: [ch.message, account] });
var vr = await (await fetch('/api/public/msg-verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address: account, signature: sig }) })).json();
if (vr.error) throw new Error(vr.error);
load();
} catch (e) { err.textContent = e.message || String(e); btn.disabled = false; btn.textContent = UNLOCK_LABEL; }
})();
});
});
}
function load() {
fetch('/api/public/replays').then(function (r) { return r.json(); }).then(function (d) {
var list = d.replays || [];
if (!list.length) {
$('replayList').innerHTML = '<div class="callout" style="text-align:center">The first replay is on its way — check back after this week\'s webinar.</div>';
return;
}
if (!d.locked) {
$('replayStatus').innerHTML = '<p class="micro" style="color:var(--ok);margin:0">✅ Members area unlocked — position #' + esc(d.id) + '</p>';
} else {
$('replayStatus').innerHTML = '';
}
$('replayList').innerHTML = list.map(function (r) { return card(r, d.locked); }).join('');
if (d.locked) wireUnlock();
}).catch(function () {
$('replayList').innerHTML = '<div class="callout warning">Could not load the replay list — refresh to try again.</div>';
});
}
load();
})();