a90d5eee78
Normal people join with email + password (sponsor attribution via cookie at
signup); the wallet only appears when buying or activating payouts, and gets
linked to the account then. Wallet-only sign-in remains for crypto-native
users. Sessions carry {email, address, memberId}.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
5.2 KiB
JavaScript
121 lines
5.2 KiB
JavaScript
// My account: email-first join/login, wallet link at purchase time,
|
|
// free payout activation, invite link, on-chain activity.
|
|
(async function () {
|
|
await IAP.renderNav('my');
|
|
const $ = IAP.$;
|
|
|
|
async function api(path, body) {
|
|
const r = await (await fetch(path, { method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) })).json();
|
|
if (r.error) throw new Error(r.error);
|
|
return r;
|
|
}
|
|
|
|
async function render() {
|
|
const me = await IAP.refreshNavWallet();
|
|
const signedIn = me && me.signedIn;
|
|
$('authArea').hidden = !!signedIn;
|
|
$('memberArea').hidden = !signedIn;
|
|
if (!signedIn) return;
|
|
|
|
const who = [];
|
|
if (me.email) who.push(me.email);
|
|
if (me.address) who.push('wallet <span class="mono">' + me.address.slice(0, 8) + '…' + me.address.slice(-6) + '</span>');
|
|
else who.push('no wallet linked yet');
|
|
if (me.memberId) who.push('on-chain <b>member #' + me.memberId + '</b>'
|
|
+ (me.onchainSponsorId ? ', sponsored by #' + me.onchainSponsorId : ''));
|
|
else if (me.sponsorId) who.push('invited by member #' + me.sponsorId);
|
|
$('posLine').innerHTML = who.join('<br>');
|
|
|
|
$('creditLine').textContent = (me.credits || 0).toLocaleString();
|
|
$('linkCard').hidden = !!me.address;
|
|
$('activateCard').hidden = !(me.address && !me.memberId);
|
|
$('activityArea').hidden = !me.memberId;
|
|
|
|
if (me.memberId) {
|
|
const bc = me.buyerCount || 0;
|
|
$('qualLine').innerHTML = '<b>' + bc + '</b> qualifying buyer(s) referred<br>'
|
|
+ (bc >= 5 ? '<span class="badge">Level 3 unlocked: full three-level earnings</span>'
|
|
: bc >= 2 ? '<span class="badge">Level 2 unlocked</span> · ' + (5 - bc) + ' more for level 3'
|
|
: (2 - bc) + ' more buyer(s) of $20+ unlock level 2');
|
|
$('inviteLine').textContent = location.origin + '/join/' + me.memberId;
|
|
$('copyInvite').hidden = false;
|
|
loadActivity();
|
|
} else {
|
|
$('qualLine').textContent = 'Level 1 pays the moment you are on-chain. Referrals who buy packages of $20 or more unlock levels 2 and 3.';
|
|
$('inviteLine').textContent = me.address
|
|
? 'Activate payouts above and your invite link appears here.'
|
|
: 'Link a wallet and switch on payouts to get your invite link.';
|
|
$('copyInvite').hidden = true;
|
|
}
|
|
}
|
|
|
|
async function loadActivity() {
|
|
try {
|
|
const c = await IAP.getConfig();
|
|
const a = await (await fetch('/api/my/activity')).json();
|
|
const fill = (id, evs, empty) => {
|
|
const el = $(id);
|
|
el.innerHTML = '';
|
|
if (!evs || !evs.length) { el.innerHTML = '<div class="row muted">' + empty + '</div>'; return; }
|
|
for (const ev of evs) el.appendChild(IAP.feedRow(ev, c));
|
|
};
|
|
fill('earnFeed', a.earnings, 'No payouts yet. They appear here the moment one lands.');
|
|
fill('refFeed', a.referrals, 'No referral activity yet. Share your invite link.');
|
|
fill('buyFeed', a.purchases, 'No purchases from your wallet yet.');
|
|
} catch (e) {}
|
|
}
|
|
|
|
const busy = (btn, fn) => async () => {
|
|
try { btn.disabled = true; await fn(); }
|
|
catch (e) { IAP.status((e && e.message) || String(e), 'bad'); }
|
|
finally { btn.disabled = false; }
|
|
};
|
|
|
|
$('signupBtn').addEventListener('click', busy($('signupBtn'), async () => {
|
|
await api('/api/signup', { email: $('suEmail').value, password: $('suPass').value });
|
|
IAP.status('Welcome aboard. You are in.', 'ok');
|
|
await render();
|
|
}));
|
|
$('loginBtn').addEventListener('click', busy($('loginBtn'), async () => {
|
|
await api('/api/login', { email: $('liEmail').value, password: $('liPass').value });
|
|
IAP.status('Logged in.', 'ok');
|
|
await render();
|
|
}));
|
|
$('walletSigninLink').addEventListener('click', async e => {
|
|
e.preventDefault();
|
|
try {
|
|
IAP.status('Check your wallet for the free sign-in signature…');
|
|
await IAPWallet.signIn();
|
|
IAP.status('Signed in with your wallet.', 'ok');
|
|
await render();
|
|
} catch (err) { IAP.status((err && err.message) || String(err), 'bad'); }
|
|
});
|
|
$('linkBtn').addEventListener('click', busy($('linkBtn'), async () => {
|
|
IAP.status('Check your wallet for the free link signature…');
|
|
await IAPWallet.signIn(); // server binds the wallet to the signed-in email account
|
|
IAP.status('Wallet linked. Earnings pay there from now on.', 'ok');
|
|
await render();
|
|
}));
|
|
$('activateBtn').addEventListener('click', busy($('activateBtn'), async () => {
|
|
const me = await (await fetch('/api/me')).json();
|
|
IAP.status('Confirm the free activation in your wallet…');
|
|
const r = await IAPWallet.activate(me.sponsorId || 0);
|
|
if (r.receipt.status !== '0x1') throw new Error('Transaction reverted. Check the explorer.');
|
|
IAP.status('Payouts are on. Your invite link is live.', 'ok');
|
|
await render();
|
|
}));
|
|
$('copyInvite').addEventListener('click', async () => {
|
|
try { await navigator.clipboard.writeText($('inviteLine').textContent); IAP.status('Link copied.', 'ok'); }
|
|
catch (e) { IAP.status('Copy failed. Select and copy the link text.', 'bad'); }
|
|
});
|
|
$('logoutLink').addEventListener('click', async e => {
|
|
e.preventDefault();
|
|
await fetch('/api/auth/logout', { method: 'POST' });
|
|
IAP.status('Logged out.', 'ok');
|
|
await render();
|
|
});
|
|
|
|
render();
|
|
})();
|