From dda8c976b0a21df7680cefd70d81b1b3c961c833 Mon Sep 17 00:00:00 2001 From: martbost Date: Wed, 16 Sep 2026 10:23:00 -0500 Subject: [PATCH] No native dialogs: Shorts report prompt -> inline reason buttons; admin drip confirms -> IAP.confirmBox Same class of bug as the campaign #27 false report: the Shorts report was a prompt() pre-filled with 'inappropriate'. Members now tap a reason button on purpose or cancel. Script tags bumped. Co-Authored-By: Claude Fable 5.1 --- public/admin.html | 2 +- public/assets/admin.js | 4 ++-- public/assets/shorts.js | 18 ++++++++++++++---- public/shorts.html | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/public/admin.html b/public/admin.html index 39c59ed..ef95984 100644 --- a/public/admin.html +++ b/public/admin.html @@ -487,6 +487,6 @@ - + diff --git a/public/assets/admin.js b/public/assets/admin.js index 9b891bc..612ee18 100644 --- a/public/assets/admin.js +++ b/public/assets/admin.js @@ -704,7 +704,7 @@ const b = e.target.closest('[data-act]'); if (!b) return; const card = b.closest('.drip-step'), i = Number(card.dataset.i); dripSeq = readDrip(); - if (b.dataset.act === 'remove') { if (!confirm('Remove email ' + (i + 1) + '?')) return; dripSeq.splice(i, 1); drawDrip(); return; } + if (b.dataset.act === 'remove') { if (!await IAP.confirmBox('Remove email ' + (i + 1) + ' from the sequence?', { title: 'Remove step', ok: 'Remove', cancel: 'Keep' })) return; dripSeq.splice(i, 1); drawDrip(); return; } if (b.dataset.act === 'up' && i > 0) { [dripSeq[i - 1], dripSeq[i]] = [dripSeq[i], dripSeq[i - 1]]; drawDrip(); return; } if (b.dataset.act === 'down' && i < dripSeq.length - 1) { [dripSeq[i + 1], dripSeq[i]] = [dripSeq[i], dripSeq[i + 1]]; drawDrip(); return; } if (b.dataset.act === 'test') { @@ -730,7 +730,7 @@ const cards = document.querySelectorAll('#dripSteps .drip-step'); const c = cards[cards.length - 1]; if (c) { c.scrollIntoView({ behavior: 'smooth', block: 'center' }); c.querySelector('.ds-subject').focus(); } }); $('dripReset').addEventListener('click', busy($('dripReset'), async () => { - if (!confirm('Replace the saved sequence with the built-in defaults?')) return; + if (!await IAP.confirmBox('Replace the saved sequence with the built-in defaults?', { title: 'Reset sequence', ok: 'Replace', cancel: 'Cancel' })) return; const r = await api('/api/admin/drip', { reset: true }, 'PATCH'); dripSeq = r.sequence; drawDrip(); IAP.status('Defaults restored.', 'ok'); await loadSettings(); })); diff --git a/public/assets/shorts.js b/public/assets/shorts.js index 7743d27..9935daa 100644 --- a/public/assets/shorts.js +++ b/public/assets/shorts.js @@ -55,12 +55,22 @@ $('shNext').hidden = false; } $('shNext').addEventListener('click', load); + // 2026-09-16: was a native prompt() pre-filled with 'inappropriate' (one stray OK = a false report). + // Now an inline row of reason buttons: the member picks one on purpose, or cancels. $('shReport').addEventListener('click', e => { e.preventDefault(); if (!st.adId) return; - const reason = (prompt('Report this short: broken, inappropriate, spam, scam, or other', 'inappropriate') || '').trim().toLowerCase(); - if (!reason) return; - fetch('/api/report-ad', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaignId: st.adId, reason }) }) - .then(() => { $('shReport').textContent = '✓ reported — thanks'; }).catch(() => {}); + let row = document.getElementById('shReasons'); + if (row) { row.remove(); return; } + row = document.createElement('div'); row.id = 'shReasons'; + row.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;align-items:center'; + row.innerHTML = 'What is wrong?' + ['broken', 'inappropriate', 'spam', 'scam', 'other'].map(r => '').join('') + ''; + $('shReport').insertAdjacentElement('afterend', row); + row.addEventListener('click', ev => { + const b = ev.target.closest('button[data-reason]'); if (!b) return; + const reason = b.dataset.reason; row.remove(); if (!reason) return; + fetch('/api/report-ad', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ campaignId: st.adId, reason }) }) + .then(() => { $('shReport').textContent = '✓ reported — thanks'; }).catch(() => {}); + }); }); // presence enforcement: pause when the tab/window loses focus, resume on return document.addEventListener('visibilitychange', () => { diff --git a/public/shorts.html b/public/shorts.html index d3a76f4..f1b8dd4 100644 --- a/public/shorts.html +++ b/public/shorts.html @@ -45,6 +45,6 @@ - +