From 4a9e4c789ecb486cd76769c226ed841c26cbe921 Mon Sep 17 00:00:00 2001 From: martbost Date: Mon, 7 Sep 2026 08:19:40 -0500 Subject: [PATCH] Daily login bonus (streak) + solo-ad budget default - Daily login bonus: once/day, base 5 credits + gentle streak (+1/day, cap +5) for consecutive days; granted after sign-in, toast + cha-ching. New login_day/login_streak on earned_credits; /api/my/login-bonus. - Solo ad: selecting Solo now defaults Budget to 50 credits (5cr x 10 min deliveries) so entering 10 isn't rejected after submit. Co-Authored-By: Claude Opus 4.8 --- ads.js | Bin 61390 -> 62758 bytes db.js | 2 ++ public/assets/my.js | 11 +++++++++++ public/my.html | 2 +- server.js | 6 ++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/ads.js b/ads.js index 33d6cd08319076bbddcb4879108e6530c56333e5..f5522512a9cd7658b402e7fcecd02fafab3dd0b0 100644 GIT binary patch delta 995 zcmah|L2DC16b5^+1o5aIZ29UXyJnkgz=&p9gVG$7^b`~!mN3b_rqRu8*qsDo;-E)dgoH%I#9qMau!K-?P$-uajL{GxLP2eG*$`#dmcLq8 z)*G7cLB!?n_ny_?hqXmEViKd6V~SqPxS*g4*U4zr5;GaSxu$3$o(N1xeSB~lV}}W{ z^ry(9v1MZ$cW&Qu^&>yO-MkKjBoMMh?5J{lc;3_3Q{ zFN|1V*rlBvV)4PquH4C z1y~T&Ss>*R@!y%FQ93h^0V%x zaKc`a9tJ%y=P!q8$!eIDF|%B55Cx- { try { await q(sql); } catch (e) { if (!['ER_DUP_FIELDNAME', 'ER_DUP_KEYNAME'].includes(e.code)) throw e; } }; await alterSafe0('ALTER TABLE earned_credits ADD COLUMN milestones VARCHAR(255) NULL'); // comma-joined granted milestone keys + await alterSafe0('ALTER TABLE earned_credits ADD COLUMN login_day CHAR(10) NULL'); // last daily-login-bonus day + await alterSafe0('ALTER TABLE earned_credits ADD COLUMN login_streak INT NOT NULL DEFAULT 0'); // consecutive-day streak // additive columns (MySQL 8 has no IF NOT EXISTS for columns) const alterSafe = async sql => { try { await q(sql); } diff --git a/public/assets/my.js b/public/assets/my.js index 704f441..6ab9aac 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -285,6 +285,15 @@ chatSync(d); MYID = d.memberId || MYID; startLiveFeed(); + if (!window.__lbChecked) { // daily login bonus, once per session (server guards once/day) + window.__lbChecked = true; + api('/api/my/login-bonus').then(r => { + if (r && r.granted > 0) { + IAP.status('🎁 Daily login bonus: +' + r.granted + ' credits' + (r.streak > 1 ? ' · ' + r.streak + '-day streak' : '') + '!', 'ok'); + playSound('chaching'); setTimeout(loadDashboard, 900); + } + }).catch(() => {}); + } const wc = d.welcomeCredits || 0; $('dbCredits').textContent = ((d.credits || 0) + wc).toLocaleString(); $('dbCreditsSub').textContent = wc ? (d.credits || 0).toLocaleString() + ' purchased · ' + wc + ' welcome' : ''; @@ -592,6 +601,8 @@ // fixed-cost types derive their spend (featured = day slots, visits = flat pack), // so hide the free-form Budget field for them to avoid confusion $('cBudgetRow').hidden = (t === 'featured' || t === 'visits'); + // solo ads have a real floor (5cr x 10 deliveries): default to 50 so 10 isn't rejected + if (t === 'solo' && (!$('cBudget').value || Number($('cBudget').value) < 50)) $('cBudget').value = (lastRates && lastRates.soloCostPerRecipient ? lastRates.soloCostPerRecipient : 5) * 10; if (t === 'visits') visitHint(); $('cTitle').placeholder = t === 'solo' ? 'Subject line (max 80)' : 'Headline (max 60)'; soloHint(); diff --git a/public/my.html b/public/my.html index 1c3ae30..77cde4f 100644 --- a/public/my.html +++ b/public/my.html @@ -644,7 +644,7 @@ - + diff --git a/server.js b/server.js index cfee36b..5d1098b 100644 --- a/server.js +++ b/server.js @@ -715,6 +715,12 @@ const server = http.createServer(async (req, res) => { accounts.touchSeen(s.email).catch(() => {}); return json(res, 200, { ok: true, chatUnread: await messages.chatUnread(s.email) }); } + // daily login bonus (once/day, gentle streak) — granted after the login flow + if (p === '/api/my/login-bonus' && req.method === 'POST') { + const s = await auth.fromRequest(req); + if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' }); + return json(res, 200, await ads.grantLoginBonus(s.email)); + } // -- rehearsal test-POL faucet: top a connected wallet up to 10 test-POL // (anvil_setBalance, no key needed). Rehearsal-only, rate-limited. if (p === '/api/my/faucet' && req.method === 'POST') {