Compare commits

..

2 Commits

18 changed files with 75 additions and 21 deletions
+2 -2
View File
@@ -6,7 +6,7 @@
<title>Admin | InstantAdPay</title> <title>Admin | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style> <style>
.adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px} .adm-table th,.adm-table td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);font-size:13.5px}
.adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em} .adm-table th{color:var(--muted);font-size:11.5px;text-transform:uppercase;letter-spacing:.08em}
@@ -306,6 +306,6 @@
</div> </div>
<script src="/assets/common.js?v=20260910c"></script> <script src="/assets/common.js?v=20260910c"></script>
<script src="/assets/admin.js?v=20260910b"></script> <script src="/assets/admin.js?v=20260910c"></script>
</body> </body>
</html> </html>
+2 -2
View File
@@ -276,11 +276,11 @@
const q = ($('memFilter').value || '').trim().toLowerCase(); const q = ($('memFilter').value || '').trim().toLowerCase();
const list = allMembers.filter(a => !q || [a.email, a.username, a.memberId, a.sponsorRef, a.address, a.code].join(' ').toLowerCase().includes(q)); const list = allMembers.filter(a => !q || [a.email, a.username, a.memberId, a.sponsorRef, a.address, a.code].join(' ').toLowerCase().includes(q));
$('memSub').textContent = list.length + ' of ' + allMembers.length; $('memSub').textContent = list.length + ' of ' + allMembers.length;
$('memTable').innerHTML = '<tr><th>Email</th><th>Username</th><th>Member #</th><th>Wallet</th><th>Sponsor</th><th>Via</th><th>Code</th><th>Joined</th><th></th></tr>' $('memTable').innerHTML = '<tr><th>Email</th><th>Username</th><th>Member #</th><th>Wallet</th><th>Sponsor</th><th>Positions</th><th>Via</th><th>Code</th><th>Joined</th><th></th></tr>'
+ list.map(a => '<tr><td>' + esc(a.email) + '</td><td>' + (a.username ? '@' + esc(a.username) : '<span class="muted">none</span>') + '</td>' + list.map(a => '<tr><td>' + esc(a.email) + '</td><td>' + (a.username ? '@' + esc(a.username) : '<span class="muted">none</span>') + '</td>'
+ '<td>' + (a.memberId ? '#' + a.memberId : '<span class="muted">free</span>') + '</td>' + '<td>' + (a.memberId ? '#' + a.memberId : '<span class="muted">free</span>') + '</td>'
+ '<td class="mono small">' + (a.address ? esc(a.address.slice(0, 8) + '…' + a.address.slice(-6)) : '<span class="muted">none</span>') + '</td>' + '<td class="mono small">' + (a.address ? esc(a.address.slice(0, 8) + '…' + a.address.slice(-6)) : '<span class="muted">none</span>') + '</td>'
+ '<td>' + esc(a.sponsorRef || '') + '</td><td class="small muted">' + esc(a.joinedVia || '') + '</td><td class="mono small">' + esc(a.code || '') + '</td>' + '<td>' + esc(a.sponsorRef || '') + '</td><td class="small" title="linked Qualified Start positions' + (a.positionIds && a.positionIds.length ? ': #' + a.positionIds.join(', #') : '') + '">' + (a.positions ? a.positions : '<span class="muted">0</span>') + '</td><td class="small muted">' + esc(a.joinedVia || '') + '</td><td class="mono small">' + esc(a.code || '') + '</td>'
+ '<td class="small muted">' + when(a.created) + '</td>' + '<td class="small muted">' + when(a.created) + '</td>'
+ '<td class="act"><button class="btn small sec" data-spon="' + esc(a.email) + '" data-cur="' + esc(a.sponsorRef || '') + '">Sponsor</button></td></tr>').join(''); + '<td class="act"><button class="btn small sec" data-spon="' + esc(a.email) + '" data-cur="' + esc(a.sponsorRef || '') + '">Sponsor</button></td></tr>').join('');
} }
+36 -1
View File
@@ -81,6 +81,15 @@ window.IAPPromo = (function () {
return a; return a;
} }
// hook videos: one per angle; the matched link continues the same hook on the join page
const VID_BASE = 'https://coolify-saasytop.nyc3.digitaloceanspaces.com/promo/';
const VIDEOS = [
{ angle: 'instant', title: 'Paid before the page reloads', hook: 'What if your commission landed before the thank-you page finished loading?', caption: 'What if your commission landed before the thank-you page finished loading? On InstantAdPay a smart contract splits every ad package the second it sells, straight to wallets, on a public ledger. Free to join by email: {{LINK:instant}}' },
{ angle: 'adspend', title: 'You were buying traffic anyway', hook: 'Every ad dollar you have ever spent went one direction. Out.', caption: 'Every ad dollar you have ever spent went one direction. Out. Here the ad spend in your line pays you back in the same transaction. Seven formats, packages from $5, free to join: {{LINK:adspend}}' },
{ angle: 'free', title: 'Watch first, spend never', hook: 'You can run your first ad campaign here for exactly zero dollars.', caption: 'Run your first ad campaign for exactly zero dollars. Join free by email, earn credits by viewing ads, launch a real campaign, and watch real payouts land on a public ledger before you spend a cent: {{LINK:free}}' },
{ angle: 'ledger', title: 'No back office. No payday.', hook: 'Your last affiliate program paid you on the fifteenth. If it paid you.', caption: 'Your last affiliate program paid you on the fifteenth. If it paid you. Here the payroll is the blockchain: every payout is a public transaction, nothing is held, nobody can change the split. Open the ledger, then join free: {{LINK:ledger}}' },
{ angle: 'two', title: 'Two buyers open level two', hook: 'Two buyers. Then five. That is the whole ladder.', caption: 'Two buyers. Then five. That is the whole ladder. Level 1 pays from your first buyer, two qualifying buyers open level 2, five open level 3, all in the same transaction, straight to your wallet. Free to join: {{LINK:two}}' }
];
function fill(link, me) { function fill(link, me) {
const token = (me && (me.username || me.refCode || me.memberId)) || ''; const token = (me && (me.username || me.refCode || me.memberId)) || '';
// invite strip // invite strip
@@ -128,6 +137,32 @@ window.IAPPromo = (function () {
sw.appendChild(block(text, '<span class="pb-net">' + esc(s.tier) + '</span> <b>' + esc(s.subject) + '</b>')); sw.appendChild(block(text, '<span class="pb-net">' + esc(s.tier) + '</span> <b>' + esc(s.subject) + '</b>'));
} }
} }
// hook videos
const vw = $('promoVideos');
if (vw && vw.dataset.filled !== link) {
vw.dataset.filled = link; vw.innerHTML = '';
for (const v of VIDEOS) {
const mlink = angleLink(link, v.angle);
const card = document.createElement('div'); card.className = 'pv';
card.innerHTML = '<div class="pv-head"><span class="pb-net">' + esc(v.angle) + ' angle</span> <b>' + esc(v.title) + '</b><div class="muted small">' + esc(v.hook) + '</div></div>'
+ '<video src="' + VID_BASE + v.angle + '.mp4" poster="' + VID_BASE + v.angle + '.jpg" preload="none" controls playsinline style="width:100%;max-width:640px;border-radius:12px;background:#000;margin:10px 0"></video>'
+ '<p class="small"><span class="muted">Matched link:</span> <span class="mono" style="overflow-wrap:anywhere">' + esc(mlink) + '</span></p>';
const row = document.createElement('p'); row.className = 'pb-actions';
row.appendChild(copyBtn(mlink, 'Copy matched link'));
row.appendChild(linkBtn(VID_BASE + v.angle + '.mp4', 'Download 16:9'));
row.appendChild(linkBtn(VID_BASE + v.angle + '-portrait.mp4', 'Download 9:16'));
card.appendChild(row);
const cap = fillLink(v.caption, link);
const extras = [
linkBtn('https://twitter.com/intent/tweet?text=' + encodeURIComponent(cap), 'Post on X'),
linkBtn('https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent(mlink), 'Share on Facebook'),
linkBtn('https://t.me/share/url?url=' + encodeURIComponent(mlink) + '&text=' + encodeURIComponent(cap.replace(/https?:\/\/\S+$/, '').trim()), 'Telegram'),
linkBtn('https://wa.me/?text=' + encodeURIComponent(cap), 'WhatsApp')
];
card.appendChild(block(cap, '<span class="lab">Caption</span>', extras));
vw.appendChild(card);
}
}
// objections // objections
const ob = $('promoObjections'); const ob = $('promoObjections');
if (ob && ob.dataset.filled !== link) { if (ob && ob.dataset.filled !== link) {
@@ -146,5 +181,5 @@ window.IAPPromo = (function () {
} }
} }
return { fill, POSTS, TEXTS, SWIPES, OBJECTIONS }; return { fill, POSTS, TEXTS, SWIPES, OBJECTIONS, VIDEOS };
})(); })();
+2
View File
@@ -218,6 +218,8 @@ tr:last-child td{border-bottom:0}
.ad-strip a{color:var(--ink)} .ad-strip a{color:var(--ink)}
.ad-strip b{color:var(--mint)} .ad-strip b{color:var(--mint)}
.ad-foot{text-align:center} .ad-foot{text-align:center}
.pv{border:1px solid var(--line);border-radius:14px;padding:16px 18px;margin:0 0 16px;background:rgba(4,8,7,.35)}
.pv-head b{display:block;font-size:18px;margin:4px 0 2px}
.done-big{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 16px;text-align:center} .done-big{display:flex;flex-direction:column;align-items:center;gap:6px;padding:26px 16px;text-align:center}
.done-big .tick{width:84px;height:84px;border-radius:50%;display:grid;place-items:center;font-size:48px;font-weight:800;color:var(--mint-ink);background:var(--mint);box-shadow:0 0 0 10px rgba(67,232,195,.15),0 10px 30px rgba(67,232,195,.35)} .done-big .tick{width:84px;height:84px;border-radius:50%;display:grid;place-items:center;font-size:48px;font-weight:800;color:var(--mint-ink);background:var(--mint);box-shadow:0 0 0 10px rgba(67,232,195,.15),0 10px 30px rgba(67,232,195,.35)}
.done-big b{font-family:var(--disp);font-size:22px;margin-top:8px} .done-big b{font-family:var(--disp);font-size:22px;margin-top:8px}
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Disclaimer | InstantAdPay</title> <title>Disclaimer | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -20,7 +20,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
+1 -1
View File
@@ -6,7 +6,7 @@
<title>You're invited | InstantAdPay</title> <title>You're invited | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style> <style>
.jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0} .jn-nav{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:16px 0}
.jn-hero{padding:14px 0 4px;text-align:left} .jn-hero{padding:14px 0 4px;text-align:left}
+1 -1
View File
@@ -17,7 +17,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
+5 -3
View File
@@ -5,7 +5,7 @@
<title>Member area | InstantAdPay</title> <title>Member area | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -373,6 +373,7 @@
<span class="small muted" id="qsHint"></span></p> <span class="small muted" id="qsHint"></span></p>
<div id="qsList" class="small"></div> <div id="qsList" class="small"></div>
<p class="muted small" style="margin:12px 0 0">Your own money, your own wallets, a faster start. It is not an income promise: qualification only pays on future purchases in your line, and every purchase here is a real ad package you can spend.</p> <p class="muted small" style="margin:12px 0 0">Your own money, your own wallets, a faster start. It is not an income promise: qualification only pays on future purchases in your line, and every purchase here is a real ad package you can spend.</p>
<p class="small" style="border:1px solid var(--line-strong);border-radius:10px;padding:8px 12px;margin:10px 0 0"><b>Positions are for qualifying.</b> Up to five per account, each registered directly under your main member. Once you are qualified, buy from your main wallet: that keeps your own sponsor paid in full, the same way your people's purchases pay you.</p>
</div> </div>
<div class="card" id="qsCalcCard"> <div class="card" id="qsCalcCard">
<h3>Qualified Start calculator</h3> <h3>Qualified Start calculator</h3>
@@ -684,8 +685,9 @@
<div class="card promo-sec" id="promo-videos" hidden> <div class="card promo-sec" id="promo-videos" hidden>
<h3>Hook videos</h3> <h3>Hook videos</h3>
<p class="muted small">Short videos, one per angle, each with a matched invite link that lands on a <p class="muted small">Short videos, one per angle, each with a matched invite link that lands on a
page continuing the same hook. In production now. When they arrive, this tab holds the previews, page continuing the same hook. Post the video with the matched link, or download it and use it anywhere.
your matched links, and paste-ready captions.</p> Every caption below already carries your link.</p>
<div id="promoVideos"></div>
</div> </div>
<div class="card ad-foot" id="adSlotPane-promo" hidden></div> <div class="card ad-foot" id="adSlotPane-promo" hidden></div>
</div> </div>
+1 -1
View File
@@ -8,7 +8,7 @@
<meta name="theme-color" content="#043b2f"> <meta name="theme-color" content="#043b2f">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style> <style>
.pl-rule{border-left:4px solid #f2c94c;background:rgba(242,201,76,.08);padding:16px 20px;border-radius:0 12px 12px 0;margin:0 0 28px} .pl-rule{border-left:4px solid #f2c94c;background:rgba(242,201,76,.08);padding:16px 20px;border-radius:0 12px 12px 0;margin:0 0 28px}
.pl-rule b{font-family:var(--disp);font-size:17px;display:block;margin-bottom:6px} .pl-rule b{font-family:var(--disp);font-size:17px;display:block;margin-bottom:6px}
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Privacy Policy | InstantAdPay</title> <title>Privacy Policy | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Shorts | InstantAdPay</title> <title>Shorts | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style> <style>
html,body{height:100%;margin:0;overflow:hidden;background:#000} html,body{height:100%;margin:0;overflow:hidden;background:#000}
.sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)} .sh{position:fixed;inset:0;display:flex;flex-direction:column;background:#000;color:var(--ink,#e8fff7)}
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Terms of Service | InstantAdPay</title> <title>Terms of Service | InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<title>Transaction | InstantAdPay</title> <title>Transaction | InstantAdPay</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+1 -1
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Viewing ad — InstantAdPay</title> <title>Viewing ad — InstantAdPay</title>
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
<style> <style>
html,body{height:100%;margin:0;overflow:hidden} html,body{height:100%;margin:0;overflow:hidden}
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)} .vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
+1 -1
View File
@@ -6,7 +6,7 @@
<!--OG--> <!--OG-->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Sora:wght@600;700;800&display=swap">
<link rel="icon" type="image/png" href="/logo-icon.png"> <link rel="icon" type="image/png" href="/logo-icon.png">
<link rel="stylesheet" href="/assets/site.css?v=20260910g"> <link rel="stylesheet" href="/assets/site.css?v=20260910h">
</head> </head>
<body> <body>
<div id="nav"></div> <div id="nav"></div>
+16 -1
View File
@@ -717,6 +717,19 @@ const server = http.createServer(async (req, res) => {
// Qualified Start: a second (third…) wallet on the same account. It becomes // Qualified Start: a second (third…) wallet on the same account. It becomes
// its own on-chain member under this member's id when it buys; the session // its own on-chain member under this member's id when it buys; the session
// stays on the main wallet. // stays on the main wallet.
// policy (Marty, 2026-09-10): positions exist to qualify, so at most five per account, and a
// wallet that is already registered on-chain under anything other than this account's main
// member is refused (a position-under-position chain would recapture 70% of a self-buy)
const MAX_POS = 5;
const have = await accounts.positions(s.email);
if (!have.find(p => p.address === r.address.toLowerCase()) && have.length >= MAX_POS)
return json(res, 400, { error: 'You can link up to ' + MAX_POS + ' positions, which is everything Qualified Start needs. Once qualified, buy from your main wallet so your sponsor is paid in full.' });
if (memberId) {
const mainId = await auth.refreshMemberId(s);
let mm = null; try { mm = await chain.member(memberId); } catch (e) {}
if (mm && mainId && mm.sponsorId !== mainId)
return json(res, 400, { error: 'That wallet is already registered on-chain under ' + (mm.sponsorId ? 'member #' + mm.sponsorId : 'no sponsor') + ', not under your main member #' + mainId + '. Only positions registered directly under you can be linked.' });
}
const pr = await accounts.addPosition(s.email, r.address); const pr = await accounts.addPosition(s.email, r.address);
if (pr.error) return json(res, 400, pr); if (pr.error) return json(res, 400, pr);
if (memberId) await accounts.setPositionMember(r.address, memberId); if (memberId) await accounts.setPositionMember(r.address, memberId);
@@ -1641,7 +1654,9 @@ const server = http.createServer(async (req, res) => {
} }
if (p === '/api/admin/members' && req.method === 'GET') { if (p === '/api/admin/members' && req.method === 'GET') {
if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); if (!isAdmin(req)) return json(res, 401, { error: 'auth' });
return json(res, 200, { members: await accounts.listAll(500) }); const members = await accounts.listAll(500);
for (const m of members) { try { const ps = await accounts.positions(m.email); m.positions = ps.length; m.positionIds = ps.map(p => p.memberId).filter(Boolean); } catch (e) { m.positions = 0; } }
return json(res, 200, { members });
} }
if (p === '/api/admin/members' && req.method === 'PATCH') { if (p === '/api/admin/members' && req.method === 'PATCH') {
if (!isAdmin(req)) return json(res, 401, { error: 'auth' }); if (!isAdmin(req)) return json(res, 401, { error: 'auth' });