openvibers/openvibe.deals Commits
For stores: how a shop lists its own deals (DEALS-LP) (#212)
Changes
@@@ -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();@@@ -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.<your domain></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 };nnew file mode 100644@@@ -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'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\.<your domain>/, '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); });
@@ -471,6 +471,12 @@ function createPages(ctx) {
@@ -471,6 +471,12 @@ function createPages(ctx) {
}
}
router.get('/at', wrap(async (req, res) => send(req, res, 200, await sellersIndexPage(), { cacheable: true })));
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) {
async function sellerPage(seller, page = 1) {
const want = String(seller || '').toLowerCase();
const want = String(seller || '').toLowerCase();
@@ -444,10 +444,33 @@ ${raw(pager ? paginationHtml(pager) : '')}`;
@@ -444,10 +444,33 @@ ${raw(pager ? paginationHtml(pager) : '')}`;
function sellersPage({ sellers }) {
function sellersPage({ sellers }) {
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Shops' }]))}
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Shops' }]))}
<h1>Deals by shop</h1>
<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>`}`;
${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.<your domain></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. */
/** /c/:category: active deals in one category, as their sources name it. */
function categoryPage({ category, views: list, pager, urls, total }) {
function categoryPage({ category, views: list, pager, urls, total }) {
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Categories', url: '/c' }, { name: category }]))}
return html`${raw(breadcrumbsHtml([{ name: 'Deals', url: '/' }, { name: 'Categories', url: '/c' }, { name: category }]))}
@@ -470,4 +493,4 @@ function message({ heading, text, action }) {
@@ -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>` : ''}`;
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 };
new file mode 100644
new file mode 100644
@@ -0,0 +1,32 @@
@@ -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'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\.<your domain>/, '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); });