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(); }
|
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
|
// visual line tree on the Overview: YOU + three levels, qualified directs in gold
|
||||||
async function loadLineTree(buyerCount) {
|
async function loadLineTree(buyerCount) {
|
||||||
try {
|
try {
|
||||||
@@ -409,9 +429,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── back-office menu: hash-routed panes ───────────────
|
// ── 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',
|
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' };
|
wallet: 'Wallet & account', profile: 'Profile' };
|
||||||
function setPane(name) {
|
function setPane(name) {
|
||||||
if (!PANES.includes(name)) name = 'overview';
|
if (!PANES.includes(name)) name = 'overview';
|
||||||
@@ -426,6 +446,7 @@
|
|||||||
if (name === 'profile') loadLineBanner();
|
if (name === 'profile') loadLineBanner();
|
||||||
if (name === 'line') { loadLineage(); loadUplineMessages(); }
|
if (name === 'line') { loadLineage(); loadUplineMessages(); }
|
||||||
if (name === 'campaigns') ['cTarget', 'cImage', 'cVideoUrl'].forEach(id => { if ($(id)) $(id).value = ''; }); // no residual URL between visits
|
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
|
document.getElementById('memberArea').classList.remove('side-open'); // close mobile drawer
|
||||||
if (location.hash !== '#' + name) history.replaceState(null, '', '#' + name);
|
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="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="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="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="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>
|
<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>
|
</nav>
|
||||||
@@ -615,6 +616,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</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="pane" id="pane-wallet" hidden>
|
||||||
<div class="card" id="faucetCard" hidden>
|
<div class="card" id="faucetCard" hidden>
|
||||||
<h3>Get test POL</h3>
|
<h3>Get test POL</h3>
|
||||||
@@ -670,7 +680,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260907s"></script>
|
<script src="/assets/common.js?v=20260907s"></script>
|
||||||
<script src="/assets/wallet.js?v=20260907q"></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>
|
<script src="/assets/chat.js?v=20260907l"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -728,6 +728,17 @@ const server = http.createServer(async (req, res) => {
|
|||||||
accounts.touchSeen(s.email).catch(() => {});
|
accounts.touchSeen(s.email).catch(() => {});
|
||||||
return json(res, 200, { ok: true, chatUnread: await messages.chatUnread(s.email) });
|
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
|
// daily login bonus (once/day, gentle streak) — granted after the login flow
|
||||||
if (p === '/api/my/login-bonus' && req.method === 'POST') {
|
if (p === '/api/my/login-bonus' && req.method === 'POST') {
|
||||||
const s = await auth.fromRequest(req);
|
const s = await auth.fromRequest(req);
|
||||||
|
|||||||
Reference in New Issue
Block a user