Dashboard: let members see what a nudge actually says before and after sending
A member sent a nudge to #102, then went looking for what had gone out. The only feedback was a small "Delivered to their dashboard" note, so she checked her clipboard - which still held the unrelated "Copy plain-English summary" text describing her OWN position. She reasonably concluded the nudge had sent her position instead of the recipient's. It had not: the message log shows #34 -> #102 carrying #102's own numbers. The feature was right and the feedback was wrong, which is its own kind of bug - a member who cannot verify what they sent will assume the worst. Every nudge now has a Preview button that names the recipient and shows the exact text before sending, and on success the panel shows what was sent and to whom rather than a bare "Delivered". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
import pkg from 'file:///D:/Projects/MarketingAgent/qa-tester/node_modules/playwright/index.js';
|
||||||
|
const { chromium } = pkg;
|
||||||
|
const b = await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||||
|
const page = await b.contexts()[0].newPage();
|
||||||
|
await page.setViewportSize({ width: 1500, height: 1000 });
|
||||||
|
await page.goto('https://www.adrevsplit.com/aradmin/admin.php?b=4411', { waitUntil:'domcontentloaded', timeout:45000 });
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
const r = await page.evaluate(() => {
|
||||||
|
const rows = [...document.querySelectorAll('table tr')].map(tr =>
|
||||||
|
[...tr.querySelectorAll('td,th')].map(td => td.innerText.replace(/\s+/g,' ').trim()));
|
||||||
|
const links = [...document.querySelectorAll('a[href*="rmcircle"], a[href*="join"]')].map(a=>a.getAttribute('href'));
|
||||||
|
return { rows: rows.filter(r2=>r2.length>3).slice(0,14), links: [...new Set(links)] };
|
||||||
|
});
|
||||||
|
console.log('=== approved login ads ===');
|
||||||
|
r.rows.forEach(x => console.log(' ' + x.join(' | ').slice(0,150)));
|
||||||
|
console.log('\n=== destination links found ===');
|
||||||
|
r.links.forEach(l => console.log(' ' + l));
|
||||||
|
await page.close(); await b.close();
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import pkg from 'file:///D:/Projects/MarketingAgent/qa-tester/node_modules/playwright/index.js';
|
||||||
|
const { chromium } = pkg;
|
||||||
|
const b = await chromium.connectOverCDP('http://127.0.0.1:9222');
|
||||||
|
const page = await b.contexts()[0].newPage();
|
||||||
|
await page.setViewportSize({ width: 1340, height: 1000 });
|
||||||
|
await page.goto('https://adrevsplit.com/login.php', { waitUntil:'domcontentloaded', timeout:45000 });
|
||||||
|
await page.waitForTimeout(3000);
|
||||||
|
console.log(JSON.stringify(await page.evaluate(() => {
|
||||||
|
const col = document.querySelector('.col-lg-10, .custom-col-10');
|
||||||
|
const out = { colTop: col ? Math.round(col.getBoundingClientRect().top) : null, children: [] };
|
||||||
|
if (col) for (const c of col.children) {
|
||||||
|
if (c.getBoundingClientRect().height === 0) continue;
|
||||||
|
const r = c.getBoundingClientRect(); const cs = getComputedStyle(c);
|
||||||
|
out.children.push({
|
||||||
|
tag: c.tagName, cls: (c.className||'').toString().slice(0,30),
|
||||||
|
top: Math.round(r.top), h: Math.round(r.height),
|
||||||
|
mt: cs.marginTop, mb: cs.marginBottom,
|
||||||
|
text: (c.innerText||'').replace(/\s+/g,' ').trim().slice(0,40)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const ul = document.querySelector('.flexy-menu');
|
||||||
|
out.menuTop = ul ? Math.round(ul.getBoundingClientRect().top) : null;
|
||||||
|
const sec = document.querySelector('section.container');
|
||||||
|
out.sectionTop = sec ? Math.round(sec.getBoundingClientRect().top) : null;
|
||||||
|
out.sectionPadTop = sec ? getComputedStyle(sec).paddingTop : null;
|
||||||
|
return out;
|
||||||
|
}, null), null, 1));
|
||||||
|
await b.close();
|
||||||
+26
-5
@@ -399,7 +399,18 @@
|
|||||||
// One-tap nudges: pre-written message per coach recommendation, sent over
|
// One-tap nudges: pre-written message per coach recommendation, sent over
|
||||||
// the wallet-verified Messages rails (and bridged to Telegram if linked).
|
// the wallet-verified Messages rails (and bridged to Telegram if linked).
|
||||||
const nudgeTexts={};
|
const nudgeTexts={};
|
||||||
function nudgeBtn(key){return `<br><button class="btn btn-teal btn-sm coach-nudge" data-nkey="${key}" style="margin-top:7px">📨 Send this nudge</button><span class="micro coach-nudge-note" style="display:none;margin-left:8px"></span>`}
|
// Nudges used to send silently with only a "Delivered" note, so nobody could
|
||||||
|
// see WHAT went out or to whom. A member went hunting in her clipboard, found
|
||||||
|
// the unrelated "copy my summary" text still sitting there, and reasonably
|
||||||
|
// concluded the nudge had sent her own position instead of the recipient's.
|
||||||
|
// Preview before, and the exact sent text after, removes the guesswork.
|
||||||
|
function nudgeBtn(key){return `<div class="coach-nudge-wrap" style="margin-top:7px">`+
|
||||||
|
`<button class="btn btn-teal btn-sm coach-nudge" data-nkey="${key}">📨 Send this nudge</button>`+
|
||||||
|
`<button class="btn btn-secondary btn-sm coach-nudge-preview" data-nkey="${key}" style="margin-left:6px">👁 Preview</button>`+
|
||||||
|
`<span class="micro coach-nudge-note" style="display:none;margin-left:8px"></span>`+
|
||||||
|
`<div class="coach-nudge-prev" style="display:none;margin-top:8px;background:rgba(78,214,203,.06);`+
|
||||||
|
`border:1px solid rgba(78,214,203,.28);border-radius:10px;padding:10px 12px;font-size:12.5px;`+
|
||||||
|
`line-height:1.6;color:var(--muted);white-space:pre-wrap"></div></div>`}
|
||||||
function regNudge(id,kind,text){const key=id+':'+kind;nudgeTexts[key]={to:id,text};return nudgeBtn(key)}
|
function regNudge(id,kind,text){const key=id+':'+kind;nudgeTexts[key]={to:id,text};return nudgeBtn(key)}
|
||||||
function renderCoach(d){
|
function renderCoach(d){
|
||||||
const card=document.getElementById('dCoachCard'),el=document.getElementById('dCoach');
|
const card=document.getElementById('dCoachCard'),el=document.getElementById('dCoach');
|
||||||
@@ -418,10 +429,20 @@
|
|||||||
if(!el.__nudgeBound){
|
if(!el.__nudgeBound){
|
||||||
el.__nudgeBound=true;
|
el.__nudgeBound=true;
|
||||||
el.addEventListener('click',async ev=>{
|
el.addEventListener('click',async ev=>{
|
||||||
|
const pv=ev.target.closest&&ev.target.closest('button.coach-nudge-preview');
|
||||||
const btn=ev.target.closest&&ev.target.closest('button.coach-nudge');
|
const btn=ev.target.closest&&ev.target.closest('button.coach-nudge');
|
||||||
if(!btn)return;
|
if(!pv&&!btn)return;
|
||||||
const rec=nudgeTexts[btn.dataset.nkey];if(!rec)return;
|
const rec=nudgeTexts[(pv||btn).dataset.nkey];if(!rec)return;
|
||||||
const note=btn.nextElementSibling;
|
const wrap=(pv||btn).closest('.coach-nudge-wrap');
|
||||||
|
const note=wrap&&wrap.querySelector('.coach-nudge-note');
|
||||||
|
const prev=wrap&&wrap.querySelector('.coach-nudge-prev');
|
||||||
|
const showPrev=(label)=>{if(!prev)return;prev.textContent=label+'\n\n'+rec.text;prev.style.display='';};
|
||||||
|
// Preview: show exactly what will be sent, and to whom, before sending.
|
||||||
|
if(pv){
|
||||||
|
if(prev&&prev.style.display!=='none'){prev.style.display='none';pv.textContent='\u{1F441} Preview';}
|
||||||
|
else{showPrev('This will be sent to #'+rec.to+':');pv.textContent='\u{1F441} Hide preview';}
|
||||||
|
return;
|
||||||
|
}
|
||||||
const say=(t,color)=>{if(note){note.style.display='';note.style.color=color||'var(--muted)';note.textContent=t;}};
|
const say=(t,color)=>{if(note){note.style.display='';note.style.color=color||'var(--muted)';note.textContent=t;}};
|
||||||
btn.disabled=true;const old=btn.textContent;btn.textContent='Sending…';
|
btn.disabled=true;const old=btn.textContent;btn.textContent='Sending…';
|
||||||
try{
|
try{
|
||||||
@@ -434,7 +455,7 @@
|
|||||||
}
|
}
|
||||||
const r=await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({toId:rec.to,body:rec.text})});
|
const r=await fetch('/api/public/msg-send',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({toId:rec.to,body:rec.text})});
|
||||||
const dd=await r.json().catch(()=>({}));
|
const dd=await r.json().catch(()=>({}));
|
||||||
if(r.ok&&!dd.error){btn.textContent='Sent ✓';say('Delivered to their dashboard — and straight to their Telegram if they’ve linked it.');}
|
if(r.ok&&!dd.error){btn.textContent='Sent ✓';showPrev('✅ Sent to #'+rec.to+' — this is exactly what they received:');const pb=wrap&&wrap.querySelector('.coach-nudge-preview');if(pb)pb.style.display='none';say('Delivered to #'+rec.to+'’s dashboard — and straight to their Telegram if they’ve linked it.');}
|
||||||
else{btn.textContent=old;btn.disabled=false;say(dd.error||'Could not send — try again.','var(--danger)');}
|
else{btn.textContent=old;btn.disabled=false;say(dd.error||'Could not send — try again.','var(--danger)');}
|
||||||
}catch(e){btn.textContent=old;btn.disabled=false;say('Network hiccup — try again.','var(--danger)');}
|
}catch(e){btn.textContent=old;btn.disabled=false;say('Network hiccup — try again.','var(--danger)');}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user