79470e471c
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>
72 lines
4.8 KiB
JavaScript
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 ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' })[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 & 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();
|
|
})();
|