Viral welcome tour + line banner + wall, milestone stepper, rich solo editor, CTA-gated reads
- Welcome tour (3 levels x 10s) unlocks welcome credits; line banner in Profile; public /wall/<username> - 'Your next move' redesigned as a milestone stepper - Solo composer: BV-style rich editor (H2/H3, inline image+video, undo/redo, raw text) - Solo read reward now requires clicking through to the advertiser, not just dwelling - Sanitizer: inline media whitelist + script/style stripped whole Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+27
-2
@@ -34,6 +34,7 @@ function newCode(taken) {
|
||||
}
|
||||
const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null,
|
||||
username: a.username || null, memberId: a.memberId || 0,
|
||||
lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null,
|
||||
address: a.address || null, created: a.created } : null;
|
||||
const USER_RE = /^[a-zA-Z0-9_]{3,20}$/;
|
||||
const normUser = u => String(u || '').trim().toLowerCase();
|
||||
@@ -98,6 +99,18 @@ const J = {
|
||||
this.save();
|
||||
return { ok: true, account: pub(acct) };
|
||||
},
|
||||
async setLineBanner(e, bannerUrl, targetUrl) {
|
||||
const acct = this.db.byEmail[e];
|
||||
if (!acct) return { error: 'No such account.' };
|
||||
acct.lineBannerUrl = bannerUrl || null;
|
||||
acct.lineTargetUrl = targetUrl || null;
|
||||
this.save();
|
||||
return { ok: true, account: pub(acct) };
|
||||
},
|
||||
async byMemberId(id) {
|
||||
const a = Object.values(this.db.byEmail).find(x => x.memberId === Number(id));
|
||||
return a ? pub(a) : null;
|
||||
},
|
||||
async setMemberId(e, id) {
|
||||
const acct = this.db.byEmail[e];
|
||||
if (acct && acct.memberId !== id) { acct.memberId = id; this.save(); }
|
||||
@@ -131,7 +144,9 @@ const J = {
|
||||
|
||||
// ---- MySQL mode ----
|
||||
const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code,
|
||||
username: r.username, memberId: r.member_id || 0, address: r.address, created: Number(r.created) }) : null;
|
||||
username: r.username, memberId: r.member_id || 0,
|
||||
lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url,
|
||||
address: r.address, created: Number(r.created) }) : null;
|
||||
const D = {
|
||||
async signup(e, password, ref) {
|
||||
const code = newCode();
|
||||
@@ -176,6 +191,14 @@ const D = {
|
||||
}
|
||||
return { ok: true, account: await this.byEmail(e) };
|
||||
},
|
||||
async setLineBanner(e, bannerUrl, targetUrl) {
|
||||
await db.q('UPDATE accounts SET line_banner_url=?, line_target_url=? WHERE email=?', [bannerUrl || null, targetUrl || null, e]);
|
||||
return { ok: true, account: await this.byEmail(e) };
|
||||
},
|
||||
async byMemberId(id) {
|
||||
const r = await db.q('SELECT * FROM accounts WHERE member_id=?', [Number(id)]);
|
||||
return rowPub(r[0]);
|
||||
},
|
||||
async setMemberId(e, id) { await db.q('UPDATE accounts SET member_id=? WHERE email=? AND (member_id IS NULL OR member_id<>?)', [id, e, id]); },
|
||||
async namesForMembers(ids) {
|
||||
if (!ids.length) return {};
|
||||
@@ -246,4 +269,6 @@ async function linkWallet(email, address) {
|
||||
async function count() { return impl().count(); }
|
||||
|
||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername,
|
||||
setUsername, setMemberId, namesForMembers, listByReferrer, linkWallet, count };
|
||||
setUsername, setMemberId, namesForMembers, listByReferrer, linkWallet, count,
|
||||
setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t),
|
||||
byMemberId: id => impl().byMemberId(id) };
|
||||
|
||||
Reference in New Issue
Block a user