Profile build-out (avatar + bio) + shareable bio page with QR
- Profile pane: avatar upload + bio, with a link to your public page - Wall becomes a bio page: avatar, bio, scannable join QR, line ladder, join CTA - qrcode npm dep; /api/qr renders SVG QR server-side (CSP-clean img) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
@@ -35,6 +35,7 @@ function newCode(taken) {
|
|||||||
const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null,
|
const pub = a => a ? { email: a.email, sponsorRef: a.sponsorRef || '', code: a.code || null,
|
||||||
username: a.username || null, memberId: a.memberId || 0,
|
username: a.username || null, memberId: a.memberId || 0,
|
||||||
lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null,
|
lineBannerUrl: a.lineBannerUrl || null, lineTargetUrl: a.lineTargetUrl || null,
|
||||||
|
avatarUrl: a.avatarUrl || null, bio: a.bio || null,
|
||||||
address: a.address || null, created: a.created } : null;
|
address: a.address || null, created: a.created } : null;
|
||||||
const USER_RE = /^[a-zA-Z0-9_]{3,20}$/;
|
const USER_RE = /^[a-zA-Z0-9_]{3,20}$/;
|
||||||
const normUser = u => String(u || '').trim().toLowerCase();
|
const normUser = u => String(u || '').trim().toLowerCase();
|
||||||
@@ -107,6 +108,14 @@ const J = {
|
|||||||
this.save();
|
this.save();
|
||||||
return { ok: true, account: pub(acct) };
|
return { ok: true, account: pub(acct) };
|
||||||
},
|
},
|
||||||
|
async setProfile(e, avatarUrl, bio) {
|
||||||
|
const acct = this.db.byEmail[e];
|
||||||
|
if (!acct) return { error: 'No such account.' };
|
||||||
|
if (avatarUrl !== undefined) acct.avatarUrl = avatarUrl || null;
|
||||||
|
if (bio !== undefined) acct.bio = bio || null;
|
||||||
|
this.save();
|
||||||
|
return { ok: true, account: pub(acct) };
|
||||||
|
},
|
||||||
async byMemberId(id) {
|
async byMemberId(id) {
|
||||||
const a = Object.values(this.db.byEmail).find(x => x.memberId === Number(id));
|
const a = Object.values(this.db.byEmail).find(x => x.memberId === Number(id));
|
||||||
return a ? pub(a) : null;
|
return a ? pub(a) : null;
|
||||||
@@ -146,6 +155,7 @@ const J = {
|
|||||||
const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code,
|
const rowPub = r => r ? pub({ email: r.email, sponsorRef: r.sponsor_ref, code: r.code,
|
||||||
username: r.username, memberId: r.member_id || 0,
|
username: r.username, memberId: r.member_id || 0,
|
||||||
lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url,
|
lineBannerUrl: r.line_banner_url, lineTargetUrl: r.line_target_url,
|
||||||
|
avatarUrl: r.avatar_url, bio: r.bio,
|
||||||
address: r.address, created: Number(r.created) }) : null;
|
address: r.address, created: Number(r.created) }) : null;
|
||||||
const D = {
|
const D = {
|
||||||
async signup(e, password, ref) {
|
async signup(e, password, ref) {
|
||||||
@@ -195,6 +205,11 @@ const D = {
|
|||||||
await db.q('UPDATE accounts SET line_banner_url=?, line_target_url=? WHERE email=?', [bannerUrl || null, targetUrl || null, e]);
|
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) };
|
return { ok: true, account: await this.byEmail(e) };
|
||||||
},
|
},
|
||||||
|
async setProfile(e, avatarUrl, bio) {
|
||||||
|
if (avatarUrl !== undefined) await db.q('UPDATE accounts SET avatar_url=? WHERE email=?', [avatarUrl || null, e]);
|
||||||
|
if (bio !== undefined) await db.q('UPDATE accounts SET bio=? WHERE email=?', [bio || null, e]);
|
||||||
|
return { ok: true, account: await this.byEmail(e) };
|
||||||
|
},
|
||||||
async byMemberId(id) {
|
async byMemberId(id) {
|
||||||
const r = await db.q('SELECT * FROM accounts WHERE member_id=?', [Number(id)]);
|
const r = await db.q('SELECT * FROM accounts WHERE member_id=?', [Number(id)]);
|
||||||
return rowPub(r[0]);
|
return rowPub(r[0]);
|
||||||
@@ -295,4 +310,5 @@ async function count() { return impl().count(); }
|
|||||||
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername,
|
module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername,
|
||||||
setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count,
|
setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count,
|
||||||
setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t),
|
setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t),
|
||||||
|
setProfile: (e, a, bio) => impl().setProfile(String(e || '').toLowerCase(), a, bio),
|
||||||
byMemberId: id => impl().byMemberId(id) };
|
byMemberId: id => impl().byMemberId(id) };
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ async function bootstrap() {
|
|||||||
await alterSafe('ALTER TABLE accounts ADD UNIQUE KEY uq_username (username)');
|
await alterSafe('ALTER TABLE accounts ADD UNIQUE KEY uq_username (username)');
|
||||||
await alterSafe('ALTER TABLE accounts ADD COLUMN member_id INT NULL');
|
await alterSafe('ALTER TABLE accounts ADD COLUMN member_id INT NULL');
|
||||||
await alterSafe('ALTER TABLE accounts ADD KEY idx_member (member_id)');
|
await alterSafe('ALTER TABLE accounts ADD KEY idx_member (member_id)');
|
||||||
|
await alterSafe('ALTER TABLE accounts ADD COLUMN avatar_url VARCHAR(500) NULL');
|
||||||
|
await alterSafe('ALTER TABLE accounts ADD COLUMN bio VARCHAR(600) NULL');
|
||||||
await alterSafe('ALTER TABLE accounts ADD COLUMN line_banner_url VARCHAR(500) NULL');
|
await alterSafe('ALTER TABLE accounts ADD COLUMN line_banner_url VARCHAR(500) NULL');
|
||||||
await alterSafe('ALTER TABLE accounts ADD COLUMN line_target_url VARCHAR(500) NULL');
|
await alterSafe('ALTER TABLE accounts ADD COLUMN line_target_url VARCHAR(500) NULL');
|
||||||
await q(`CREATE TABLE IF NOT EXISTS daily_views (
|
await q(`CREATE TABLE IF NOT EXISTS daily_views (
|
||||||
|
|||||||
Generated
+456
@@ -0,0 +1,456 @@
|
|||||||
|
{
|
||||||
|
"name": "instantadpay-site",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "instantadpay-site",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"mysql2": "^3.11.0",
|
||||||
|
"qrcode": "^1.5.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "26.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz",
|
||||||
|
"integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~8.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/aws-ssl-profiles": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/decamelize": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dijkstrajs": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/find-up": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"locate-path": "^5.0.0",
|
||||||
|
"path-exists": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/generate-function": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-property": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iconv-lite": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-property": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/locate-path": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-locate": "^4.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/long": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/lru.min": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=1.30.0",
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wellwelwel"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mysql2": {
|
||||||
|
"version": "3.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.3.tgz",
|
||||||
|
"integrity": "sha512-OKfWHkMAg9v06neq8FmSyhbxPQKABN9PAW5G9/bDTXzJBO5xXtkKL0V27vju7HQWk9UD4Od5BZsvCtBTB1CPEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"aws-ssl-profiles": "^1.1.2",
|
||||||
|
"generate-function": "^2.3.1",
|
||||||
|
"iconv-lite": "^0.7.3",
|
||||||
|
"long": "^5.3.2",
|
||||||
|
"lru.min": "^1.1.4",
|
||||||
|
"named-placeholders": "^1.1.6",
|
||||||
|
"sql-escaper": "^1.5.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": ">= 8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/named-placeholders": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lru.min": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-limit": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-try": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-locate": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-limit": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-try": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-exists": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/qrcode": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dijkstrajs": "^1.0.1",
|
||||||
|
"pngjs": "^5.0.0",
|
||||||
|
"yargs": "^15.3.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"qrcode": "bin/qrcode"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-main-filename": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/safer-buffer": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/set-blocking": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/sql-escaper": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0",
|
||||||
|
"deno": ">=2.0.0",
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/which-module": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "15.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||||
|
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^6.0.0",
|
||||||
|
"decamelize": "^1.2.0",
|
||||||
|
"find-up": "^4.1.0",
|
||||||
|
"get-caller-file": "^2.0.1",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"require-main-filename": "^2.0.0",
|
||||||
|
"set-blocking": "^2.0.0",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"which-module": "^2.0.0",
|
||||||
|
"y18n": "^4.0.0",
|
||||||
|
"yargs-parser": "^18.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "18.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||||
|
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"camelcase": "^5.0.0",
|
||||||
|
"decamelize": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -6,5 +6,5 @@
|
|||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {"start": "node server.js", "dev": "node --watch server.js"},
|
"scripts": {"start": "node server.js", "dev": "node --watch server.js"},
|
||||||
"engines": {"node": ">=20"},
|
"engines": {"node": ">=20"},
|
||||||
"dependencies": {"mysql2": "^3.11.0"}
|
"dependencies": {"mysql2": "^3.11.0", "qrcode": "^1.5.4"}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-1
@@ -1057,8 +1057,45 @@
|
|||||||
: 'Not set yet. Until you set one, your tour slot is skipped.';
|
: 'Not set yet. Until you set one, your tour slot is skipped.';
|
||||||
}
|
}
|
||||||
async function loadLineBanner() {
|
async function loadLineBanner() {
|
||||||
try { fillLineBanner(await (await fetch('/api/me')).json()); } catch (e) {}
|
try {
|
||||||
|
const a = await (await fetch('/api/me')).json();
|
||||||
|
fillLineBanner(a);
|
||||||
|
fillProfileDetails(a);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
function fillProfileDetails(a) {
|
||||||
|
if (!a) return;
|
||||||
|
if (a.bio) $('pfBio').value = a.bio;
|
||||||
|
if (a.avatarUrl) { const p = $('pfAvatarPrev'); p.src = a.avatarUrl; p.hidden = false; }
|
||||||
|
if (a.username) {
|
||||||
|
const link = location.origin + '/wall/' + a.username;
|
||||||
|
$('pfBioLink').textContent = link;
|
||||||
|
$('pfViewBio').hidden = false;
|
||||||
|
$('pfViewBio').href = '/wall/' + a.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pfAvatar; // pending avatar url
|
||||||
|
$('pfAvatarBtn').addEventListener('click', () => $('pfAvatarFile').click());
|
||||||
|
$('pfAvatarFile').addEventListener('change', async () => {
|
||||||
|
const f = $('pfAvatarFile').files[0];
|
||||||
|
if (!f) return;
|
||||||
|
$('pfAvatarInfo').textContent = 'Uploading…';
|
||||||
|
try {
|
||||||
|
const r = await (await fetch('/api/my/upload', { method: 'POST', headers: { 'Content-Type': f.type }, body: f })).json();
|
||||||
|
if (r.error) { $('pfAvatarInfo').textContent = r.error; $('pfAvatarFile').value = ''; return; }
|
||||||
|
pfAvatar = r.url;
|
||||||
|
$('pfAvatarInfo').textContent = 'Uploaded — save to apply.';
|
||||||
|
const p = $('pfAvatarPrev'); p.src = r.url; p.hidden = false;
|
||||||
|
} catch (e) { $('pfAvatarInfo').textContent = 'Upload failed.'; }
|
||||||
|
$('pfAvatarFile').value = '';
|
||||||
|
});
|
||||||
|
$('pfDetailsSave').addEventListener('click', busy2($('pfDetailsSave'), async () => {
|
||||||
|
const body = { bio: $('pfBio').value };
|
||||||
|
if (pfAvatar) body.avatarUrl = pfAvatar;
|
||||||
|
const r = await api('/api/my/profile-details', body);
|
||||||
|
IAP.status('Profile saved.', 'ok');
|
||||||
|
if (r.account) fillProfileDetails(r.account);
|
||||||
|
}));
|
||||||
$('lbUploadBtn').addEventListener('click', () => $('lbFile').click());
|
$('lbUploadBtn').addEventListener('click', () => $('lbFile').click());
|
||||||
$('lbFile').addEventListener('change', async () => {
|
$('lbFile').addEventListener('change', async () => {
|
||||||
const f = $('lbFile').files[0];
|
const f = $('lbFile').files[0];
|
||||||
|
|||||||
@@ -349,6 +349,12 @@ textarea{resize:vertical;font:inherit}
|
|||||||
/* ── welcome tour (gauntlet): framed line sites + countdown ── */
|
/* ── welcome tour (gauntlet): framed line sites + countdown ── */
|
||||||
.lgate-frame{flex:1;border:0;width:100%;background:#fff;min-height:0}
|
.lgate-frame{flex:1;border:0;width:100%;background:#fff;min-height:0}
|
||||||
.ggmeta{padding:8px 16px;background:var(--panel-solid);border-bottom:1px solid var(--line)}
|
.ggmeta{padding:8px 16px;background:var(--panel-solid);border-bottom:1px solid var(--line)}
|
||||||
|
/* ── bio header (wall/profile page) ── */
|
||||||
|
.bio-head{display:flex;gap:22px;align-items:center;flex-wrap:wrap;padding:8px 0 4px}
|
||||||
|
.bio-avatar{width:96px;height:96px;border-radius:50%;object-fit:cover;border:2px solid var(--mint);flex:0 0 auto}
|
||||||
|
.bio-meta{flex:1;min-width:220px}
|
||||||
|
.bio-qr{flex:0 0 auto;background:var(--panel);border:1px solid var(--line-strong);border-radius:14px;padding:10px}
|
||||||
|
.bio-qr img{display:block;border-radius:8px;background:#eef7f3}
|
||||||
/* ── wall page ── */
|
/* ── wall page ── */
|
||||||
.wall-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;text-align:center}
|
.wall-card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:16px;text-align:center}
|
||||||
.wall-card img{max-width:100%;border-radius:10px;border:1px solid var(--line-strong)}
|
.wall-card img{max-width:100%;border-radius:10px;border:1px solid var(--line-strong)}
|
||||||
|
|||||||
@@ -12,7 +12,12 @@
|
|||||||
IAP.$('wallJoin').href = '/my';
|
IAP.$('wallJoin').href = '/my';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
title.innerHTML = 'The <em>' + String(w.name).replace(/[&<>]/g, '') + '</em> line';
|
const safeName = String(w.name).replace(/[&<>]/g, '');
|
||||||
|
title.innerHTML = safeName;
|
||||||
|
IAP.$('bioHead').hidden = false;
|
||||||
|
if (w.avatarUrl) { const av = IAP.$('bioAvatar'); av.src = w.avatarUrl; av.hidden = false; }
|
||||||
|
IAP.$('bioText').textContent = w.bio || 'Building a team on InstantAdPay — join through this page and you\'re in my line.';
|
||||||
|
if (w.qrUrl) IAP.$('bioQr').src = w.qrUrl;
|
||||||
IAP.$('wallCtaHead').textContent = 'Join ' + w.name + '’s line';
|
IAP.$('wallCtaHead').textContent = 'Join ' + w.name + '’s line';
|
||||||
IAP.$('wallJoin').href = w.joinUrl;
|
IAP.$('wallJoin').href = w.joinUrl;
|
||||||
const grid = IAP.$('wallGrid');
|
const grid = IAP.$('wallGrid');
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<title>The contract | InstantAdPay</title>
|
<title>The contract | InstantAdPay</title>
|
||||||
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
<meta name="description" content="Plain-language review of the InstantAdPay settlement contract: what it does, what nobody can change, what the operator can and cannot touch, and how to verify all of it yourself.">
|
||||||
<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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -129,8 +129,8 @@
|
|||||||
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
<div class="small">Advertising services with a performance referral program. Not an investment product; no income guarantees. Crypto transactions are irreversible. Never spend what you cannot afford.</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/contract.js?v=20260906a"></script>
|
<script src="/assets/contract.js?v=20260906b"></script>
|
||||||
<script src="/assets/chat.js?v=20260906a"></script>
|
<script src="/assets/chat.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+5
-5
@@ -5,7 +5,7 @@
|
|||||||
<title>InstantAdPay: advertise and earn, locked in code</title>
|
<title>InstantAdPay: advertise and earn, locked in code</title>
|
||||||
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
<meta name="description" content="Real ad packages with instant on-chain settlement. Every purchase pays the sponsor line in the same transaction, verifiable by anyone on the live ledger.">
|
||||||
<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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -410,9 +410,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/wallet.js?v=20260906a"></script>
|
<script src="/assets/wallet.js?v=20260906b"></script>
|
||||||
<script src="/assets/home.js?v=20260906a"></script>
|
<script src="/assets/home.js?v=20260906b"></script>
|
||||||
<script src="/assets/chat.js?v=20260906a"></script>
|
<script src="/assets/chat.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+4
-4
@@ -5,7 +5,7 @@
|
|||||||
<title>Live ledger | InstantAdPay</title>
|
<title>Live ledger | InstantAdPay</title>
|
||||||
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
<meta name="description" content="Every purchase, payout, and pass-up on InstantAdPay, streamed straight from the blockchain with a verify link on every line.">
|
||||||
<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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
@@ -25,8 +25,8 @@
|
|||||||
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
<div>InstantAdPay · <a href="/">how it works</a> · <a id="contractLink" href="#" target="_blank" rel="noopener">contract source ↗</a></div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/ledger.js?v=20260906a"></script>
|
<script src="/assets/ledger.js?v=20260906b"></script>
|
||||||
<script src="/assets/chat.js?v=20260906a"></script>
|
<script src="/assets/chat.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+17
-5
@@ -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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body class="bo-body">
|
<body class="bo-body">
|
||||||
|
|
||||||
@@ -486,6 +486,18 @@
|
|||||||
<button class="btn" id="pfSaveBtn">Save username</button>
|
<button class="btn" id="pfSaveBtn">Save username</button>
|
||||||
<p class="muted small" id="pfCurrent" style="margin-top:10px"></p>
|
<p class="muted small" id="pfCurrent" style="margin-top:10px"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Avatar & bio</h3>
|
||||||
|
<p class="muted small">Your face and your story, shown on your public profile page
|
||||||
|
(<span class="mono" id="pfBioLink">set a username to get your link</span>).</p>
|
||||||
|
<p><img id="pfAvatarPrev" alt="avatar" style="width:84px;height:84px;border-radius:50%;object-fit:cover;border:2px solid var(--line-strong)" hidden></p>
|
||||||
|
<p><input type="file" id="pfAvatarFile" accept="image/png,image/jpeg,image/webp,image/gif" hidden>
|
||||||
|
<button class="btn small sec" id="pfAvatarBtn" type="button">Upload avatar</button>
|
||||||
|
<span class="small muted" id="pfAvatarInfo"></span></p>
|
||||||
|
<p><textarea id="pfBio" rows="3" maxlength="600" placeholder="A short bio for your profile page…" style="width:100%"></textarea></p>
|
||||||
|
<p><button class="btn" id="pfDetailsSave">Save profile</button>
|
||||||
|
<a class="btn small sec" id="pfViewBio" target="_blank" rel="noopener" hidden>View my page</a></p>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Your line banner</h3>
|
<h3>Your line banner</h3>
|
||||||
<p class="muted small">Set a destination link (and a banner image for your wall), and every new
|
<p class="muted small">Set a destination link (and a banner image for your wall), and every new
|
||||||
@@ -533,9 +545,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/wallet.js?v=20260906a"></script>
|
<script src="/assets/wallet.js?v=20260906b"></script>
|
||||||
<script src="/assets/my.js?v=20260906a"></script>
|
<script src="/assets/my.js?v=20260906b"></script>
|
||||||
<script src="/assets/chat.js?v=20260906a"></script>
|
<script src="/assets/chat.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+3
-3
@@ -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>Transaction | InstantAdPay</title>
|
<title>Transaction | 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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="nav"></div>
|
<div id="nav"></div>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
|
<p><a href="/ledger">← Back to the live ledger</a> · <a href="/contract">Read the contract review</a></p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/tx.js?v=20260906a"></script>
|
<script src="/assets/tx.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<title>Viewing ad — InstantAdPay</title>
|
<title>Viewing ad — InstantAdPay</title>
|
||||||
<link rel="stylesheet" href="/assets/site.css?v=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
<style>
|
<style>
|
||||||
html,body{height:100%;margin:0;overflow:hidden}
|
html,body{height:100%;margin:0;overflow:hidden}
|
||||||
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
|
.vw{display:flex;flex-direction:column;height:100vh;height:100dvh;background:var(--bg,#04110c);color:var(--ink,#e8fff7)}
|
||||||
@@ -43,6 +43,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<iframe class="vframe" id="vFrame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups" referrerpolicy="no-referrer" title="Advertiser site"></iframe>
|
<iframe class="vframe" id="vFrame" sandbox="allow-scripts allow-same-origin allow-forms allow-popups" referrerpolicy="no-referrer" title="Advertiser site"></iframe>
|
||||||
</div>
|
</div>
|
||||||
<script src="/assets/view.js?v=20260906a"></script>
|
<script src="/assets/view.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+14
-6
@@ -4,15 +4,23 @@
|
|||||||
<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>Banner wall | InstantAdPay</title>
|
<title>Banner wall | 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=20260906a">
|
<link rel="stylesheet" href="/assets/site.css?v=20260906b">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="nav"></div>
|
<div id="nav"></div>
|
||||||
<section>
|
<section>
|
||||||
<div class="wrap" style="max-width:920px">
|
<div class="wrap" style="max-width:920px">
|
||||||
<div class="sectionhead" style="text-align:left">
|
<div class="bio-head" id="bioHead" hidden>
|
||||||
<p class="eyebrow">Member banner wall</p>
|
<img id="bioAvatar" class="bio-avatar" alt="" hidden>
|
||||||
<h2 id="wallTitle">Loading the wall…</h2>
|
<div class="bio-meta">
|
||||||
|
<p class="eyebrow">Member profile</p>
|
||||||
|
<h2 id="wallTitle" style="margin:.1em 0">Loading…</h2>
|
||||||
|
<p id="bioText" class="lead" style="font-size:16px"></p>
|
||||||
|
</div>
|
||||||
|
<div class="bio-qr"><img id="bioQr" alt="Join QR code" width="150" height="150"><div class="small muted" style="text-align:center">scan to join</div></div>
|
||||||
|
</div>
|
||||||
|
<div class="sectionhead" style="text-align:left;margin-top:10px">
|
||||||
|
<h3 style="margin:0">The line, three levels deep</h3>
|
||||||
<p>Three positions, three levels — the exact levels the contract pays. Every banner here belongs
|
<p>Three positions, three levels — the exact levels the contract pays. Every banner here belongs
|
||||||
to a real member of this line, and every payment between them settles on-chain, instantly.</p>
|
to a real member of this line, and every payment between them settles on-chain, instantly.</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<script src="/assets/common.js?v=20260906a"></script>
|
<script src="/assets/common.js?v=20260906b"></script>
|
||||||
<script src="/assets/wall.js?v=20260906a"></script>
|
<script src="/assets/wall.js?v=20260906b"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const accounts = require('./accounts');
|
|||||||
const ads = require('./ads');
|
const ads = require('./ads');
|
||||||
const mailer = require('./mailer');
|
const mailer = require('./mailer');
|
||||||
const messages = require('./messages');
|
const messages = require('./messages');
|
||||||
|
let QR = null; try { QR = require('qrcode'); } catch (e) { /* optional */ }
|
||||||
const chatbot = require('./chatbot');
|
const chatbot = require('./chatbot');
|
||||||
|
|
||||||
const PORT = Number(process.env.PORT || 3000);
|
const PORT = Number(process.env.PORT || 3000);
|
||||||
@@ -651,6 +652,29 @@ const server = http.createServer(async (req, res) => {
|
|||||||
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||||
return json(res, 200, await messages.markRead(s.email, m[1]));
|
return json(res, 200, await messages.markRead(s.email, m[1]));
|
||||||
}
|
}
|
||||||
|
// -- QR code (SVG) for any InstantAdPay URL — used on bio/wall pages
|
||||||
|
if (p === '/api/qr' && req.method === 'GET') {
|
||||||
|
const data = String(u.searchParams.get('d') || '').slice(0, 300);
|
||||||
|
if (!QR || !data) { res.writeHead(404, baseHeaders()); return res.end(); }
|
||||||
|
try {
|
||||||
|
const svg = await QR.toString(data, { type: 'svg', margin: 1, width: 240,
|
||||||
|
color: { dark: '#0b1512', light: '#eef7f3' } });
|
||||||
|
res.writeHead(200, baseHeaders({ 'Content-Type': 'image/svg+xml', 'Cache-Control': 'public, max-age=3600' }));
|
||||||
|
return res.end(svg);
|
||||||
|
} catch (e) { res.writeHead(500, baseHeaders()); return res.end(); }
|
||||||
|
}
|
||||||
|
// -- profile: avatar + bio
|
||||||
|
if (p === '/api/my/profile-details' && req.method === 'POST') {
|
||||||
|
const s = await auth.fromRequest(req);
|
||||||
|
if (!s || !s.email) return json(res, 401, { error: 'Sign in first.' });
|
||||||
|
const b = await readBody(req);
|
||||||
|
const avatar = b.avatarUrl === undefined ? undefined : String(b.avatarUrl || '').trim();
|
||||||
|
if (avatar && !/^(\/uploads\/[a-z0-9]{24}\.(png|jpg|webp|gif)|https:\/\/[^\s]+)$/i.test(avatar))
|
||||||
|
return json(res, 400, { error: 'Avatar must be an uploaded image or an https image URL.' });
|
||||||
|
const bio = b.bio === undefined ? undefined : String(b.bio || '').trim().slice(0, 600);
|
||||||
|
const r = await accounts.setProfile(s.email, avatar, bio);
|
||||||
|
return json(res, r.error ? 400 : 200, r);
|
||||||
|
}
|
||||||
// -- line banner: the member's viral slot on welcome tours + their wall
|
// -- line banner: the member's viral slot on welcome tours + their wall
|
||||||
if (p === '/api/my/linebanner' && req.method === 'POST') {
|
if (p === '/api/my/linebanner' && req.method === 'POST') {
|
||||||
const s = await auth.fromRequest(req);
|
const s = await auth.fromRequest(req);
|
||||||
@@ -700,8 +724,10 @@ const server = http.createServer(async (req, res) => {
|
|||||||
const ladder = [a, ...await uplineSlides(a.email, 2)].slice(0, 3)
|
const ladder = [a, ...await uplineSlides(a.email, 2)].slice(0, 3)
|
||||||
.map(x => ({ name: x.username ? '@' + x.username : x.memberId ? 'member #' + x.memberId : 'a member',
|
.map(x => ({ name: x.username ? '@' + x.username : x.memberId ? 'member #' + x.memberId : 'a member',
|
||||||
bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl || null }));
|
bannerUrl: x.lineBannerUrl || null, targetUrl: x.lineTargetUrl || null }));
|
||||||
|
const joinPath = '/join/' + (a.username || a.code);
|
||||||
return json(res, 200, { name: a.username ? '@' + a.username : 'member #' + (a.memberId || 0),
|
return json(res, 200, { name: a.username ? '@' + a.username : 'member #' + (a.memberId || 0),
|
||||||
joinUrl: '/join/' + (a.username || a.code), ladder });
|
avatarUrl: a.avatarUrl || null, bio: a.bio || null,
|
||||||
|
joinUrl: joinPath, qrUrl: '/api/qr?d=' + encodeURIComponent('https://instantadpay.com' + joinPath), ladder });
|
||||||
}
|
}
|
||||||
// -- watch-to-earn video ads: serve one, then reward a server-clock-verified watch
|
// -- watch-to-earn video ads: serve one, then reward a server-clock-verified watch
|
||||||
if (p === '/api/my/videos' && req.method === 'GET') {
|
if (p === '/api/my/videos' && req.method === 'GET') {
|
||||||
|
|||||||
Reference in New Issue
Block a user