openvibers/openvibe.deals Commits

For stores: how a shop lists its own deals (DEALS-LP) (#212)

OP OpenVibers committed 10 days ago · a590b87327 · 3 files, +63 −2

/stores explains OpenVibe.Sources' merchant opt-in (claim the domain, prove it by DNS TXT or meta tag, name the feed
or sitemap), links to it, and says what Deals never does (referral codes, guessed prices, listing after withdrawal,
paid ranking). The shops index points shops to it.

Changes

ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex 4ea0152..9632bb8 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -471,6 +471,12 @@ function createPages(ctx) {
}
router.get('/at', wrap(async (req, res) => send(req, res, 200, await sellersIndexPage(), { cacheable: true })));
++ // How a shop lists its own deals (DEALS-LP): the claim is Sources' merchant opt-in.
++ router.get('/stores', (req, res) => send(req, res, 200, {
++ title: 'List your store\'s deals', decision: pageDecision('/stores'), canonical: seo.canonicalUrl(config.baseUrl, '/stores'),
++ description: 'Run a shop? Claim your domain on OpenVibe.Sources, prove it is yours, name your feed or sitemap, and your own offers appear on OpenVibe.Deals. No fee, no referral codes.',
++ body: views.storesPage({ merchantUrl: `${config.sourcesPublicUrl || 'https://sources.openvibe.network'}/merchant` }),
++ }, { cacheable: true }));
async function sellerPage(seller, page = 1) {
const want = String(seller || '').toLowerCase();
ddiff --git a/server/render/views.js b/server/render/views.js
iindex b7760ba..9113764 100644
---- a/server/render/views.js
++++ b/server/render/views.js
@@@ -444,10 +444,33 @@ ${raw(pager ? paginationHtml(pager) : '')}`;
function sellersPage({ sellers }) {
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Shops' }]))}
<h1>Deals by shop</h1>
--<p class="lede">Every shop with a deal here, as the deal sources name it, the most deals first.</p>
++<p class="lede">Every shop with a deal here, as the deal sources name it, the most deals first. Run a shop? <a href="/stores">List your store's deals</a>.</p>
${sellers.length ? html`<ul class="seller-list">${sellers.map((x) => html`<li><a href="/at/${x.slug}">${x.seller}</a> <span class="meta">${x.count} deal${x.count === 1 ? '' : 's'}</span></li>`)}</ul>` : html`<p class="empty">No shops yet.</p>`}`;
}
++/**
++ * /stores: how a shop lists its own deals here (open item 140, DEALS-LP). The claim lives on OpenVibe.Sources'
++ * merchant opt-in (a domain, a proof, a feed or sitemap); its items reach Deals through the ordinary Sources import.
++ */
++function storesPage({ merchantUrl }) {
++ return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Shops', url: '/at' }, { name: 'For stores' }]))}
++<h1>List your store's deals</h1>
++<p class="lede">If you run a shop, your own product pages can be a deal source here. No fee, no ads, and no change to your prices: Deals shows what your pages state.</p>
++<ol class="steps">
++<li><b>Claim your domain</b> on OpenVibe.Sources' merchant page, signed in with your OpenVibe account.</li>
++<li><b>Prove it is yours</b> with a DNS TXT record (<code>_openvibe-sources.&lt;your domain&gt;</code>) or a meta tag on your home page. Sources checks again every few minutes, or when you press "Check now".</li>
++<li><b>Name your product feed or sitemap.</b> Sources reads it politely: robots.txt on every request, spaced requests, nothing your robots.txt forbids.</li>
++<li><b>Your offers appear here</b> from the structured data on your product pages (schema.org Offer: price, currency, availability, and a list price when your page states one). A person reviews each new offer before search engines see it.</li>
++</ol>
++<p><a class="button" href="${merchantUrl}">Claim your store on OpenVibe.Sources</a></p>
++<h2>What we never do</h2>
++<ul class="plain">
++<li>Change your links to add a referral code, or guess a price your page does not state.</li>
++<li>Keep listing you after you withdraw: withdrawing on Sources stops the reading, and offers that have no source left are taken down.</li>
++<li>Rank you by payment: the order on Deals is the deals' own, by heat, by ending and by when they were seen.</li>
++</ul>`;
++}
++
/** /c/:category: active deals in one category, as their sources name it. */
function categoryPage({ category, views: list, pager, urls, total }) {
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Categories', url: '/c' }, { name: category }]))}
@@@ -470,4 +493,4 @@ function message({ heading, text, action }) {
return html`<h1>${heading}</h1><p>${text}</p>${action ? html`<p><a class="button" href="${action.href}">${action.label}</a></p>` : ''}`;
}
--module.exports = { offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, sellerPage, sellersPage, categoryPage, categoriesPage, message, priceText, when, ago };
++module.exports = { storesPage, offerList, tabs, offerPage, productPage, submitForm, watchesPage, searchPage, modPage, storePage, sellerPage, sellersPage, categoryPage, categoriesPage, message, priceText, when, ago };
ddiff --git a/test/stores-page.test.js b/test/stores-page.test.js
nnew file mode 100644
iindex 0000000..45de2f3
---- /dev/null
++++ b/test/stores-page.test.js
@@@ -0,0 +1,32 @@
++'use strict';
++/**
++ * /stores (open item 140, DEALS-LP): how a shop lists its own deals here. It explains Sources' merchant opt-in
++ * (claim, proof, feed), links to it, says what Deals never does, is reachable from the shops index, and is indexable.
++ * node test/stores-page.test.js
++ */
++const assert = require('assert');
++const { boot, check, done, robotsOf } = require('./helpers/boot');
++
++(async () => {
++ const t = await boot();
++
++ await check('/stores explains the opt-in and links to Sources\' merchant page', async () => {
++ const r = await t.get('/stores');
++ assert.strictEqual(r.status, 200, r.text);
++ assert.match(r.text, /<h1>List your store&#39;s deals<\/h1>|<h1>List your store's deals<\/h1>/);
++ assert.match(r.text, /href="https:\/\/sources\.openvibe\.network\/merchant"/);
++ assert.match(r.text, /_openvibe-sources\.&lt;your domain&gt;/, 'the DNS proof is named');
++ assert.match(r.text, /robots\.txt/);
++ assert.match(r.text, /What we never do/);
++ assert.ok(!/noindex/.test(robotsOf(r.text) || ''), 'search engines may read it');
++ });
++
++ await check('the shops index points shops to it', async () => {
++ const r = await t.get('/at');
++ assert.strictEqual(r.status, 200);
++ assert.match(r.text, /href="\/stores"/);
++ });
++
++ await t.close();
++ done();
++})().catch((e) => { console.error(e); process.exit(1); });