diff --git a/accounts.js b/accounts.js index d85c1ff..d2db2f6 100644 --- a/accounts.js +++ b/accounts.js @@ -438,7 +438,7 @@ async function getChatSettings(email) { return { available: a ? a.chatAvailable !== false : true, mutes: await impl().getMutes(String(email || '').toLowerCase()) }; } -module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername, listAll, setSponsorRef, +module.exports = { init, signup, login, ensure, byEmail, byAddress, byCode, byUsername, byMemberId: id => impl().byMemberId(Number(id) || 0), listAll, setSponsorRef, setUsername, setMemberId, namesForMembers, listByReferrer, downline, linkWallet, count, setLineBanner: (e, b, t) => impl().setLineBanner(String(e || '').toLowerCase(), b, t), setWallOffers: (e, j) => impl().setWallOffers(String(e || '').toLowerCase(), j), diff --git a/chatbot.js b/chatbot.js index 463565b..260592b 100644 --- a/chatbot.js +++ b/chatbot.js @@ -78,6 +78,7 @@ FACTS: - Live formats: display banners (per impression), text ads (per impression), full-screen LOGIN ADS (per day: right after a member signs in they land on a sponsor interstitial — they click "Open Ad", the advertiser's page opens in a NEW tab, a countdown runs on the interstitial, and at zero a "Go to dashboard" button appears. Just a CTA link is enough; an optional banner image can be the clickable creative. No framing requirement since it opens in its own tab), WATCH-TO-EARN VIDEO ADS (advertiser uploads an MP4/WebM or gives a direct https .mp4/.webm link and picks a required watch length — 10s/30s/60s — which sets the per-view price; viewers watch in an escape-proof player under Earn credits > Watch videos, the watch time is enforced on the server clock, and they earn credits per completed watch; you never see your own videos), and solo ads. Banner ads also require a size (standard IAB sizes like 728x90, 300x250). Coming: featured rotation with disclosed rotation size, verified-visit packs. - WALLETS + BUYING POL (Training > Wallets and buying POL, /wallets, members only): preferred MetaMask (recommended; extra accounts for Qualified Start), Phantom, SafePal, Coinbase Wallet; Trust works but blocks buys spending most of its POL (keep ~2x). MoonPay flow: connect wallet, Buy packages > "Buy POL with a card" opens MoonPay with POL on Polygon + the member's address prefilled; card/Apple Pay/Google Pay; first-time ID check; minimum order ~$30; buy package cost + 2-3 POL for fees; POL arrives in minutes; then buy. Exchanges: withdraw POL on the Polygon network. Never MATIC on Ethereum, never share the recovery phrase. - HOLDING TANK (Members > My line > Holding tank card): free members who joined with no sponsor wait there; a member who has switched on payouts AND bought their own $20+ package can Adopt one (first come, max 2 open adoptions, 7-day window; if the person never links a wallet or buys, they fall back into the tank; a person can be adopted twice at most). Adopting sets the sponsor, opens a chat and emails the member; their first purchase then binds to the adopter on-chain. Members can also "Release to tank" one of their own free referrals (pay it forward). Admin sees the tank under Members. +- DORMANT-LEAD RESCUE: a FREE referral (no wallet, no purchase) with no message from their sponsor for 10 days triggers a warning email + dashboard flag to the sponsor ("unreached, tank in N days"); at 14 days (warning at least 4 days old) the lead moves to the holding tank and the sponsor is told. Sponsor resets the clock with a chat, a Nudge, or the "Contacted them" button (for phone/text contact). Leads whose sponsor link resolves to nobody go to the tank after a day. Nothing on-chain moves; anyone bound by a purchase never moves. - PIF (pay it forward) button: on a free direct or an adopted member who has linked a wallet, the sponsor taps PIF, enters an amount (suggested: the $20 package plus fees), and their OWN wallet app opens with the member's address prefilled; the POL goes wallet to wallet. The site never touches the funds; it only logs the transaction and tells the recipient with a Polygonscan link. The gift is theirs; nothing forces a purchase. - FOUNDING WEEK / PRE-LAUNCH (Training > Founding week checklist, /launch, members only): eight items read live from the account: username, wallet linked, payouts on, level 2 qualified (2 buyers of $20+, or Qualified Start with 2 linked positions), the leader play = level 3 (5 qualifying buyers, up to 5 linked positions; then buy from the main wallet), line banner, links + play chosen (self-marked), first two placed. Reason: unqualified levels pass up, so leaders qualify BEFORE their teams' teams buy. Countdown shows when admin sets launchAt. Never call the site 'pre-launch' publicly: it is live and paying. - SCHEDULING (2026-09-11): any campaign except featured can take an optional start and end time (local time) in the New campaign form; solo ads label it "Send from" (inbox deliveries begin then). A scheduled campaign shows "scheduled" until it starts; at the end it shows "ended" and the unspent budget returns to Available. Banner/text scheduled campaigns join the partner network at their start time. There is NO dayparting (hours-of-day targeting) by design; the daily cap paces budgets. Each campaign row shows a small views-by-hour chart (on-site views, viewer's local time, last 7 days). diff --git a/db.js b/db.js index 42c0833..a42f1c4 100644 --- a/db.js +++ b/db.js @@ -146,6 +146,9 @@ async function bootstrap() { note VARCHAR(600) NULL, closed BIGINT NULL, INDEX (adoptee), INDEX (adopter), INDEX (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); // holding-tank adoptions + await q(`CREATE TABLE IF NOT EXISTS lead_marks ( + lead VARCHAR(190) PRIMARY KEY, sponsor VARCHAR(190) NULL, contacted_ts BIGINT NULL, warned_ts BIGINT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`); // dormant-lead rescue: manual "contacted" marks + warning sent await q(`CREATE TABLE IF NOT EXISTS prospects ( id INT AUTO_INCREMENT PRIMARY KEY, owner_email VARCHAR(190) NOT NULL, diff --git a/messages.js b/messages.js index 30fc1e3..67ebf04 100644 --- a/messages.js +++ b/messages.js @@ -77,6 +77,13 @@ async function sendChat(fromMember, fromEmail, toEmail, body) { return { id, sent: now }; } // messages between two members (both directions), id > afterId, oldest→newest +// last message from one member to another, any kind (0 if never) +async function lastFrom(fromEmail, toEmail) { + const f = String(fromEmail || '').toLowerCase(), t = String(toEmail || '').toLowerCase(); + if (db.enabled()) { const r = await db.q('SELECT MAX(sent) s FROM sponsor_messages WHERE from_email=? AND to_email=?', [f, t]); return Number((r[0] && r[0].s) || 0); } + if (!J.db) J.load(); + return J.db.items.filter(i => i.fromEmail === f && i.toEmail === t).reduce((m, i) => Math.max(m, i.sent || 0), 0); +} async function thread(aEmail, bEmail, afterId = 0, limit = 300) { const a = String(aEmail || '').toLowerCase(), b = String(bEmail || '').toLowerCase(); if (db.enabled()) { @@ -170,5 +177,5 @@ async function markRead(email, id) { return { ok: true }; } -module.exports = { init, lastBroadcastAt, deliver, inbox, unreadCount, newestUnread, markRead, +module.exports = { init, lastFrom, lastBroadcastAt, deliver, inbox, unreadCount, newestUnread, markRead, sendChat, thread, markChatRead, chatUnread, threadList }; diff --git a/public/assets/my.js b/public/assets/my.js index aaf507f..c825627 100644 --- a/public/assets/my.js +++ b/public/assets/my.js @@ -1065,13 +1065,19 @@ const d = r.directs || []; $('coachSummary').innerHTML = d.length ? '' + d.length + ' direct' + (d.length === 1 ? '' : 's') + ' · ' + r.stalled + ' quiet for 3+ days' + (r.stalled ? ' · start at the top' : '') : ''; if (!d.length) { el.innerHTML = '
No directs yet. When someone joins through your link they show up here with their next step.
'; return; } - el.innerHTML = d.map(x => 'Reach your free referrals. If there is no message from you to a free referral for 10 days, you get a warning; at 14 days they move to the holding tank so someone else can help them. A chat, a nudge, or tapping Contacted them resets the clock. Nothing on-chain ever moves.
Where each person you referred sits on the ladder, who has gone quiet, and what to say next. Nudge opens a chat with the message already written; edit it if you like, then send. Quiet members also get one automatic reminder email per step from InstantAdPay, so your nudge is the personal one.
Loading…