Fix profile persistence + badge ribbon alignment; 24h sessions

- BUG: /api/me never returned avatarUrl/bio/socials/lineBanner fields, so the
  Profile pane repopulated empty on reload — data WAS saved, just not surfaced.
  Now returned so avatar, bio, and social links persist visibly.
- Achievement badge: member name drops the "@" and sits on a per-badge ribbonY
  (spark .728 / surge .779 / circuit .713 / nexus .709), gold with dark outline.
- Sessions now expire in 24h (was 30d) so members re-login daily and see the
  login ad each day.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-07 06:13:19 -05:00
parent a634f83a8e
commit 5b1a60f653
4 changed files with 18 additions and 14 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ let IS_PROD = false;
let SITE = 'instantadpay.com'; let SITE = 'instantadpay.com';
const CHALLENGE_TTL = 10 * 60 * 1000; const CHALLENGE_TTL = 10 * 60 * 1000;
const SESSION_TTL = 30 * 24 * 60 * 60 * 1000; // 30 days const SESSION_TTL = 24 * 60 * 60 * 1000; // 24h — forces a daily re-login so the login ad shows each day
const challenges = new Map(); // addressLower -> {message, exp} const challenges = new Map(); // addressLower -> {message, exp}
// ---- JSON session fallback ---- // ---- JSON session fallback ----
+7 -7
View File
@@ -41,11 +41,11 @@
} }
// achievement badges: the milestone ladder as unlockable medals + share-to-image // achievement badges: the milestone ladder as unlockable medals + share-to-image
const BADGES = [ const BADGES = [ // ribbonY = vertical center of each badge's name ribbon (art differs per tier)
{ key: 'payouts', label: 'Spark', sub: 'payouts on', img: '/badges/badge-spark.jpg' }, { key: 'payouts', label: 'Spark', sub: 'payouts on', img: '/badges/badge-spark.jpg', ribbonY: 0.728 },
{ key: 'firstBuyer', label: 'Surge', sub: 'first qualifying buyer', img: '/badges/badge-surge.jpg' }, { key: 'firstBuyer', label: 'Surge', sub: 'first qualifying buyer', img: '/badges/badge-surge.jpg', ribbonY: 0.779 },
{ key: 'level2', label: 'Circuit', sub: '2 qualifying buyers', img: '/badges/badge-circuit.jpg' }, { key: 'level2', label: 'Circuit', sub: '2 qualifying buyers', img: '/badges/badge-circuit.jpg', ribbonY: 0.713 },
{ key: 'level3', label: 'Nexus', sub: 'fully qualified', img: '/badges/badge-nexus.jpg' } { key: 'level3', label: 'Nexus', sub: 'fully qualified', img: '/badges/badge-nexus.jpg', ribbonY: 0.709 }
]; ];
let lastMilestones = []; let lastMilestones = [];
function renderBadges(d) { function renderBadges(d) {
@@ -74,7 +74,7 @@
// composite the ornate badge template with the member's @username on the ribbon // composite the ornate badge template with the member's @username on the ribbon
function shareBadge(key, d) { function shareBadge(key, d) {
const b = BADGES.find(x => x.key === key); if (!b) return; const b = BADGES.find(x => x.key === key); if (!b) return;
const who = d.username ? '@' + d.username : d.memberId ? 'member #' + d.memberId : ''; const who = d.username ? d.username : d.memberId ? 'member #' + d.memberId : '';
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
const c = document.createElement('canvas'); c.width = img.width; c.height = img.height; const c = document.createElement('canvas'); c.width = img.width; c.height = img.height;
@@ -84,7 +84,7 @@
x.textAlign = 'center'; x.textAlign = 'center';
x.textBaseline = 'middle'; x.textBaseline = 'middle';
x.font = 'bold ' + Math.round(c.width * 0.055) + 'px Sora, "Segoe UI", sans-serif'; x.font = 'bold ' + Math.round(c.width * 0.055) + 'px Sora, "Segoe UI", sans-serif';
const y = c.height * 0.82; const y = c.height * (b.ribbonY || 0.75);
x.lineJoin = 'round'; x.lineJoin = 'round';
x.lineWidth = Math.max(3, Math.round(c.width * 0.008)); x.lineWidth = Math.max(3, Math.round(c.width * 0.008));
x.strokeStyle = 'rgba(4,20,15,.9)'; x.strokeStyle = 'rgba(4,20,15,.9)';
+5 -5
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=20260907c"> <link rel="stylesheet" href="/assets/site.css?v=20260907d">
</head> </head>
<body class="bo-body"> <body class="bo-body">
@@ -627,9 +627,9 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/assets/common.js?v=20260907c"></script> <script src="/assets/common.js?v=20260907d"></script>
<script src="/assets/wallet.js?v=20260907c"></script> <script src="/assets/wallet.js?v=20260907d"></script>
<script src="/assets/my.js?v=20260907c"></script> <script src="/assets/my.js?v=20260907d"></script>
<script src="/assets/chat.js?v=20260907c"></script> <script src="/assets/chat.js?v=20260907d"></script>
</body> </body>
</html> </html>
+5 -1
View File
@@ -432,7 +432,11 @@ const server = http.createServer(async (req, res) => {
const out = { signedIn: true, email: s.email || (acct && acct.email) || null, const out = { signedIn: true, email: s.email || (acct && acct.email) || null,
address: s.address || (acct && acct.address) || null, memberId, address: s.address || (acct && acct.address) || null, memberId,
username: (acct && acct.username) || null, username: (acct && acct.username) || null,
refCode: (acct && acct.code) || null, sponsorId }; refCode: (acct && acct.code) || null, sponsorId,
// profile + line-banner fields so the Profile pane repopulates on reload (were being saved but not returned)
avatarUrl: (acct && acct.avatarUrl) || null, bio: (acct && acct.bio) || null,
socials: (acct && acct.socials) || null,
lineBannerUrl: (acct && acct.lineBannerUrl) || null, lineTargetUrl: (acct && acct.lineTargetUrl) || null };
if (memberId) { if (memberId) {
try { try {
const mm = await chain.member(memberId); const mm = await chain.member(memberId);