Training center: new members-area pane for videos + materials
New "Training" nav pane lists curated lessons (inline video for mp4/webm, links for external videos and docs) from /api/training (admin-curated via data/training.json, with a sensible default). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+23
-2
@@ -285,6 +285,26 @@
|
||||
if (toast) { IAP.status(toast, kind); if (sound) playSound(sound); liveRefresh(); }
|
||||
}
|
||||
|
||||
// training center: videos + materials (admin-curated via data/training.json)
|
||||
async function loadTraining() {
|
||||
try {
|
||||
const r = await (await fetch('/api/training')).json();
|
||||
const el = $('trainingList'); if (!el) return;
|
||||
const items = r.items || [];
|
||||
if (!items.length) { el.innerHTML = '<p class="muted small">Training materials are being added. Check back soon.</p>'; return; }
|
||||
el.innerHTML = items.map(it => {
|
||||
const isVid = it.videoUrl && /\.(mp4|webm)(\?|$)/i.test(it.videoUrl);
|
||||
const media = isVid ? '<video src="' + esc(it.videoUrl) + '" controls playsinline style="width:100%;max-width:640px;border-radius:12px;background:#000"></video>' : '';
|
||||
const links = [];
|
||||
if (it.videoUrl && !isVid) links.push('<a class="btn small sec" href="' + esc(it.videoUrl) + '" target="_blank" rel="noopener">Watch</a>');
|
||||
if (it.docUrl) links.push('<a class="btn small sec" href="' + esc(it.docUrl) + '" target="_blank" rel="noopener">Open material</a>');
|
||||
return '<div class="card"><h3>' + esc(it.title || 'Lesson') + '</h3>'
|
||||
+ (it.desc ? '<p class="muted small">' + esc(it.desc) + '</p>' : '')
|
||||
+ (media ? '<p style="margin:10px 0">' + media + '</p>' : '')
|
||||
+ (links.length ? '<p>' + links.join(' ') + '</p>' : '') + '</div>';
|
||||
}).join('');
|
||||
} catch (e) {}
|
||||
}
|
||||
// visual line tree on the Overview: YOU + three levels, qualified directs in gold
|
||||
async function loadLineTree(buyerCount) {
|
||||
try {
|
||||
@@ -409,9 +429,9 @@
|
||||
}
|
||||
|
||||
// ── back-office menu: hash-routed panes ───────────────
|
||||
const PANES = ['overview', 'line', 'buy', 'campaigns', 'earn', 'earnings', 'promo', 'wallet', 'profile'];
|
||||
const PANES = ['overview', 'line', 'buy', 'campaigns', 'earn', 'earnings', 'promo', 'training', 'wallet', 'profile'];
|
||||
const TITLES = { overview: 'Overview', line: 'My line', buy: 'Buy packages', campaigns: 'Campaigns',
|
||||
earn: 'Earn credits', earnings: 'Earnings', promo: 'Promo tools',
|
||||
earn: 'Earn credits', earnings: 'Earnings', promo: 'Promo tools', training: 'Training',
|
||||
wallet: 'Wallet & account', profile: 'Profile' };
|
||||
function setPane(name) {
|
||||
if (!PANES.includes(name)) name = 'overview';
|
||||
@@ -426,6 +446,7 @@
|
||||
if (name === 'profile') loadLineBanner();
|
||||
if (name === 'line') { loadLineage(); loadUplineMessages(); }
|
||||
if (name === 'campaigns') ['cTarget', 'cImage', 'cVideoUrl'].forEach(id => { if ($(id)) $(id).value = ''; }); // no residual URL between visits
|
||||
if (name === 'training') loadTraining();
|
||||
document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer
|
||||
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
|
||||
}
|
||||
|
||||
+11
-1
@@ -126,6 +126,7 @@
|
||||
<button data-pane="earn" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v9M9 10c0-1.1 1.3-1.8 3-1.8s3 .7 3 1.8-1.3 1.6-3 1.8-3 .7-3 1.8 1.3 1.8 3 1.8 3-.7 3-1.8"/></svg>Earn credits<span class="pill" id="inboxBadge" hidden></span></button>
|
||||
<button data-pane="earnings" type="button"><svg viewBox="0 0 24 24"><path d="M4 17l5-5 4 3 7-8"/><path d="M14 7h6v6"/></svg>Earnings</button>
|
||||
<button data-pane="promo" type="button"><svg viewBox="0 0 24 24"><path d="M7 10s5-1 9-5v14c-4-4-9-5-9-5H5a2 2 0 0 1-2-2v0a2 2 0 0 1 2-2h2z"/><path d="M8 15l1 5h2l-1-5"/></svg>Promo tools</button>
|
||||
<button data-pane="training" type="button"><svg viewBox="0 0 24 24"><path d="M3 5l9-2 9 2v11l-9 2-9-2z"/><path d="M12 3v13"/><path d="M9 11l3 2 3-2"/></svg>Training</button>
|
||||
<button data-pane="wallet" type="button"><svg viewBox="0 0 24 24"><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/><circle cx="16.5" cy="14.5" r="1.4"/></svg>Wallet</button>
|
||||
<button data-pane="profile" type="button"><svg viewBox="0 0 24 24"><circle cx="12" cy="8.5" r="3.6"/><path d="M4.5 20c1.6-3.6 4.2-5.2 7.5-5.2s5.9 1.6 7.5 5.2"/></svg>Profile</button>
|
||||
</nav>
|
||||
@@ -615,6 +616,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-training" hidden>
|
||||
<div class="card">
|
||||
<h3>Training center</h3>
|
||||
<p class="muted small">Everything to help you get started and grow: walkthrough videos and materials.
|
||||
New lessons land here as they are added.</p>
|
||||
</div>
|
||||
<div id="trainingList"></div>
|
||||
</div>
|
||||
|
||||
<div class="pane" id="pane-wallet" hidden>
|
||||
<div class="card" id="faucetCard" hidden>
|
||||
<h3>Get test POL</h3>
|
||||
@@ -670,7 +680,7 @@
|
||||
</div>
|
||||
<script src="/assets/common.js?v=20260907s"></script>
|
||||
<script src="/assets/wallet.js?v=20260907q"></script>
|
||||
<script src="/assets/my.js?v=20260907t"></script>
|
||||
<script src="/assets/my.js?v=20260907u"></script>
|
||||
<script src="/assets/chat.js?v=20260907l"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -728,6 +728,17 @@ const server = http.createServer(async (req, res) => {
|
||||
accounts.touchSeen(s.email).catch(() => {});
|
||||
return json(res, 200, { ok: true, chatUnread: await messages.chatUnread(s.email) });
|
||||
}
|
||||
// training center content (admin-curated via data/training.json)
|
||||
if (p === '/api/training' && req.method === 'GET') {
|
||||
const s = await auth.fromRequest(req);
|
||||
if (!s) return json(res, 401, { error: 'Sign in first.' });
|
||||
let items = [];
|
||||
try { const j = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'training.json'), 'utf8')); if (Array.isArray(j)) items = j; } catch (e) {}
|
||||
if (!items.length) items = [{ title: 'Getting started with InstantAdPay',
|
||||
desc: 'How the platform works, how every payout splits on-chain to real wallets, and how to build your line.',
|
||||
docUrl: 'https://instantadpay.com/' }];
|
||||
return json(res, 200, { items });
|
||||
}
|
||||
// daily login bonus (once/day, gentle streak) — granted after the login flow
|
||||
if (p === '/api/my/login-bonus' && req.method === 'POST') {
|
||||
const s = await auth.fromRequest(req);
|
||||
|
||||
Reference in New Issue
Block a user