Fix: buy links wallet first; achievement milestones use live buyerCount

- Buying now ensures the wallet is LINKED to the account before the purchase
  (one signature if not linked). Without it, a wallet only connected for the
  faucet bought successfully but credits resolved against member id 0 and never
  showed until a manual link. Matches the "buying links your wallet" promise.
- Dashboard computed achievement milestones from buyerCount BEFORE reading it
  from chain (always 0), so Surge/Circuit/Nexus never unlocked even when
  qualification advanced. Moved the milestone block after the chain read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 07:24:26 -05:00
parent d275822cd4
commit 1363bd7b0b
3 changed files with 24 additions and 15 deletions
+8
View File
@@ -1063,6 +1063,14 @@
wrap.querySelectorAll('button[data-id]').forEach(b => b.addEventListener('click', async () => { wrap.querySelectorAll('button[data-id]').forEach(b => b.addEventListener('click', async () => {
try { try {
b.disabled = true; b.disabled = true;
// the buyer's credits are read by the member id of their LINKED wallet.
// If they only connected a wallet (e.g. for the faucet) but never linked
// it, link it first (one signature) so the purchase's credits show up.
const meNow = await (await fetch('/api/me')).json();
if (!meNow.address) {
IAP.status('Link your wallet first — one quick signature…');
await IAPWallet.signIn();
}
const spNow = await (await fetch('/api/sponsor')).json(); const spNow = await (await fetch('/api/sponsor')).json();
IAP.status('Confirm the purchase in your wallet…'); IAP.status('Confirm the purchase in your wallet…');
const r = await IAPWallet.buy(Number(b.dataset.id), spNow.sponsorId || 0, b.dataset.cost); const r = await IAPWallet.buy(Number(b.dataset.id), spNow.sponsorId || 0, b.dataset.cost);
+4 -4
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<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="stylesheet" href="/assets/site.css?v=20260907h"> <link rel="stylesheet" href="/assets/site.css?v=20260907i">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -641,9 +641,9 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260907h"></script> <script src="/assets/common.js?v=20260907i"></script>
<script src="/assets/wallet.js?v=20260907f"></script> <script src="/assets/wallet.js?v=20260907f"></script>
<script src="/assets/my.js?v=20260907h"></script> <script src="/assets/my.js?v=20260907i"></script>
<script src="/assets/chat.js?v=20260907h"></script> <script src="/assets/chat.js?v=20260907i"></script>
</body> </body>
</html> </html>
+12 -11
View File
@@ -480,17 +480,6 @@ const server = http.createServer(async (req, res) => {
online: (Date.now() - (spon.lastSeen || 0)) < 60000, online: (Date.now() - (spon.lastSeen || 0)) < 60000,
available: spon.chatAvailable !== false }; available: spon.chatAvailable !== false };
} }
// achievement milestones (same ladder as the Overview stepper) + one-time credit bonuses
{
const bc = out.buyerCount || 0;
const reached = [];
if (out.memberId) reached.push('payouts');
if (bc >= 1) reached.push('firstBuyer');
if (bc >= 2) reached.push('level2');
if (bc >= 5) reached.push('level3');
out.milestonesReached = reached;
if (out.email && reached.length) out.milestonesGranted = await ads.grantMilestones(out.email, reached);
}
if (out.email) { // unmissable login modal when the upline sent a message if (out.email) { // unmissable login modal when the upline sent a message
const un = await messages.newestUnread(out.email); const un = await messages.newestUnread(out.email);
if (un) { if (un) {
@@ -514,6 +503,18 @@ const server = http.createServer(async (req, res) => {
out.earnedWei = earned.toString(); out.earnedWei = earned.toString();
out.earnCount = n; out.earnCount = n;
} }
// achievement milestones (same ladder as the Overview stepper) + one-time credit
// bonuses — computed AFTER buyerCount is read from chain above (else always 0)
{
const bc = out.buyerCount || 0;
const reached = [];
if (out.memberId) reached.push('payouts');
if (bc >= 1) reached.push('firstBuyer');
if (bc >= 2) reached.push('level2');
if (bc >= 5) reached.push('level3');
out.milestonesReached = reached;
if (out.email && reached.length) out.milestonesGranted = await ads.grantMilestones(out.email, reached);
}
// who joined through this member: their invite link uses username when set, // who joined through this member: their invite link uses username when set,
// else code, and the numeric id once on-chain — match all three // else code, and the numeric id once on-chain — match all three
const refs = []; const refs = [];