openvibers/openvibe.coupons Commits

Store page: product deals twenty at a time (#159)

OP OpenVibers committed 13 days ago · e4eac9ffcf · 3 files, +27 −2

Changes

ddiff --git a/server/http/public.js b/server/http/public.js
iindex f9c3da1..72d7cde 100644
---- a/server/http/public.js
++++ b/server/http/public.js
@@@ -293,6 +293,7 @@ function createPublicRoutes(ctx) {
m, domains, hints: await coupons.merchantHints(m.id), codes, deals, ended, reportsThisWeek, price,
now, windowDays: confidence.WINDOW_DAYS,
viewer: req.viewer, csrf: csrf(req), back: `/m/${m.slug}${tab === 'codes' ? '' : `?tab=${tab}`}`, flashMsg, tab, success,
++ dealsShown: Math.min(500, Math.max(0, Math.floor(Number(req.query.deals)) || 0)),
watching: await watches.isWatching(req.viewer.subject, m),
}),
}, { cacheable: !flashMsg });
ddiff --git a/server/render/store.js b/server/render/store.js
iindex 9cb2582..0e4791d 100644
---- a/server/render/store.js
++++ b/server/render/store.js
@@@ -210,7 +210,10 @@ ${facts}</section>` : h`<section class="ovk-section"><h2>Quick facts</h2><p clas
* The store page. codes: the shop's active codes (a code for the whole shop), deals: the codes for one product,
* ended: the codes that ended, reportsThisWeek: how many reports the shop's codes collected this week.
*/
--function storePage({ m, domains, hints, codes, deals, ended, reportsThisWeek, price, now, windowDays, viewer, csrf, watching, back, flashMsg, tab = 'codes', success = null }) {
++// Product deals show a page at a time (2026-10-31: Amazon's 200 made its store page 62 phone screens long).
++const DEALS_PAGE = 20;
++
++function storePage({ m, domains, hints, codes, deals, ended, reportsThisWeek, price, now, windowDays, viewer, csrf, watching, back, flashMsg, tab = 'codes', success = null, dealsShown = DEALS_PAGE }) {
const base = `/m/${m.slug}`;
const host = (domains[0] && domains[0].host) || (m.homepage_url ? hostOf(m.homepage_url) : m.name);
const all = [...codes, ...deals];
@@@ -239,12 +242,14 @@ function storePage({ m, domains, hints, codes, deals, ended, reportsThisWeek, pr
const flashMsgHtml = flashMsg ? h`<p class="notice" role="status">${flashMsg}</p>` : '';
++ const shown = Math.max(DEALS_PAGE, Math.min(deals.length, Math.floor(Number(dealsShown)) || DEALS_PAGE));
let body;
if (tab === 'about') {
body = h`${aboutTab({ m, domains, hints, codes: all, price, now, windowDays })}`;
} else if (tab === 'deals') {
body = h`<p class="ovk-note cpn-lede">Codes that take money off one product, as the source posted them.</p>
--${deals.length ? h`${deals.map((v) => couponCard(v, { viewer, csrf, back, link: true, shopUrl: m.homepage_url }))}` : h`<p class="empty">No product deals for ${m.name} right now.</p>`}
++${deals.length ? h`${deals.slice(0, shown).map((v) => couponCard(v, { viewer, csrf, back, link: true, shopUrl: m.homepage_url }))}
++${shown < deals.length ? h`<p class="cpn-more"><a class="ovk-btn ovk-btn--quiet" href="${base}?tab=deals&amp;deals=${String(Math.min(deals.length, shown + DEALS_PAGE))}">Show ${String(Math.min(DEALS_PAGE, deals.length - shown))} more <span class="cpn-muted">(${String(shown)} of ${String(deals.length)})</span></a></p>` : ''}` : h`<p class="empty">No product deals for ${m.name} right now.</p>`}
${merchantTrend(success, m.name)}`;
} else if (!codes.length) {
// A shop with no codes says so, and offers the watch that tells us when one appears.
ddiff --git a/test/store-page.test.js b/test/store-page.test.js
iindex 7ea8f78..a712537 100644
---- a/test/store-page.test.js
++++ b/test/store-page.test.js
@@@ -148,6 +148,25 @@ function bestCardCode(html) {
assert.doesNotMatch(about.text, /verified/i);
});
++ await check('product deals show twenty at a time, with a link to the next ones (Amazon had 200 on one page)', async () => {
++ const shop = await t.merchant({ name: 'Deals Shop', host: 'deals-shop.com' });
++ const people = Array.from({ length: 5 }, (_, i) => t.network.addUser('dealer-' + i));
++ let n = 0;
++ while (n < 25) {
++ await submitCode(t, people[n % 5], shop.slug, 'DEAL' + String(n).padStart(3, '0'), 'Widget ' + n + ' for $' + (10 + n));
++ n++;
++ }
++ const first = await t.get('/m/' + shop.slug + '?tab=deals');
++ assert.strictEqual(first.status, 200);
++ assert.strictEqual((first.text.match(/One product/g) || []).length, 20, 'the first page shows twenty');
++ assert.match(first.text, /href="\/m\/[^"]+\?tab=deals&amp;deals=25">Show 5 more/, 'and a link to the rest');
++ const all = await t.get('/m/' + shop.slug + '?tab=deals&deals=25');
++ assert.strictEqual((all.text.match(/One product/g) || []).length, 25, 'the link shows them all');
++ assert.doesNotMatch(all.text, /Show \d+ more/, 'with nothing left to show, no link');
++ const silly = await t.get('/m/' + shop.slug + '?tab=deals&deals=-4');
++ assert.strictEqual((silly.text.match(/One product/g) || []).length, 20, 'a nonsense count falls back to a page');
++ });
++
await t.close();
done();
})();