Admin: member search + drilldown card (identity, chain, credits, line, purchases, payouts, campaigns) with username/sponsor/wallet/credits/delete actions; sortable + filterable admin tables; Earn tab zero-credit button wording

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-13 06:27:07 -05:00
parent 1faa43780e
commit 0c7f957db4
7 changed files with 330 additions and 11 deletions
+32
View File
@@ -201,6 +201,23 @@ const J = {
if (p.memberId) return { error: 'That position is already registered on-chain and cannot be unlinked.' };
delete this.db.positions[a]; this.save(); return { ok: true };
},
// admin only: point the account at a different main wallet (or none); the caller re-reads the member id
async adminSetAddress(e, a) {
const acct = this.db.byEmail[e]; if (!acct) return { error: 'No such account.' };
if (a) {
if (this.db.byAddress[a] && this.db.byAddress[a] !== e) return { error: 'That wallet is the main wallet of another account.' };
if (this.db.positions[a]) return { error: 'That wallet is a linked position on ' + (this.db.positions[a].email === e ? 'this' : 'another') + ' account.' };
}
if (acct.address) delete this.db.byAddress[acct.address];
acct.address = a || null; if (a) this.db.byAddress[a] = e; this.save(); return { ok: true, account: pub(acct) };
},
async positionByAddress(a) { const p = this.db.positions[a]; return p ? { address: a, email: p.email, memberId: p.memberId } : null; },
async removeAccount(e) {
const acct = this.db.byEmail[e]; if (!acct) return { error: 'No such account.' };
if (acct.address) delete this.db.byAddress[acct.address]; if (acct.code) delete this.db.byCode[acct.code];
for (const [a, p] of Object.entries(this.db.positions)) if (p.email === e) delete this.db.positions[a];
delete this.db.byEmail[e]; this.save(); return { ok: true };
},
};
// ---- MySQL mode ----
@@ -350,6 +367,18 @@ const D = {
if (r.member_id) return { error: 'That position is already registered on-chain and cannot be unlinked.' };
await db.q('DELETE FROM positions WHERE address=?', [a]); return { ok: true };
},
async adminSetAddress(e, a) {
if (a) {
const o = (await db.q('SELECT email FROM accounts WHERE address=?', [a]))[0]; if (o && o.email !== e) return { error: 'That wallet is the main wallet of another account.' };
const pos = (await db.q('SELECT email FROM positions WHERE address=?', [a]))[0]; if (pos) return { error: 'That wallet is a linked position on ' + (pos.email === e ? 'this' : 'another') + ' account.' };
}
await db.q('UPDATE accounts SET address=? WHERE email=?', [a || null, e]); return { ok: true, account: await this.byEmail(e) };
},
async positionByAddress(a) { const r = (await db.q('SELECT * FROM positions WHERE address=?', [a]))[0]; return r ? { address: r.address, email: r.email, memberId: r.member_id || 0 } : null; },
async removeAccount(e) {
await db.q('DELETE FROM positions WHERE email=?', [e]);
const r = await db.q('DELETE FROM accounts WHERE email=?', [e]); return r.affectedRows ? { ok: true } : { error: 'No such account.' };
},
};
const impl = () => db.enabled() ? D : J;
@@ -446,6 +475,9 @@ module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUs
setWallOffers: (e, j) => impl().setWallOffers(String(e || '').toLowerCase(), j),
setProfile: (e, a, bio, socials) => impl().setProfile(String(e || '').toLowerCase(), a, bio, socials),
touchSeen: e => impl().touchSeen(String(e || '').toLowerCase()),
adminSetAddress: (e, a) => impl().adminSetAddress(String(e || '').toLowerCase(), a ? normAddr(a) : null),
positionByAddress: a => impl().positionByAddress(normAddr(a)),
removeAccount: e => impl().removeAccount(String(e || '').toLowerCase()),
setChatAvailable: (e, v) => impl().setChatAvailable(String(e || '').toLowerCase(), v),
getMutes: e => impl().getMutes(String(e || '').toLowerCase()),
setMute: (o, t, m) => impl().setMute(String(o || '').toLowerCase(), String(t || '').toLowerCase(), m),