← back to Council 0809 Builds
rolls-ar-deeplink/deeplink.js
83 lines
/**
* "How Many Rolls?" AR deep-link builder (Council idea #5, 2026-08-09)
*
* Two modes (Steve approved BOTH Path A and Path B on 2026-08-09,
* "A and b approved / Dust2026"):
*
* - buildDeepLink() PATH A / full: emits only when width AND a
* numeric repeat are present, so the shopper lands
* in the AR tool with the exact rolls answer
* pre-computable. Enabled once the
* custom.pattern_repeat_number backfill lands.
*
* - buildDeepLinkWidthOnly() PATH B / ship-now: emits on width ALONE; the AR
* measure tool collects the repeat + wall size
* in-tool. Broadly eligible today (width is the
* widely-populated metafield).
*
* Missing the minimum data -> null -> the PDP hides the button rather than
* showing a broken "0 rolls" flow (the idea's own reliability gate).
*/
const BASE = 'https://nph.designerwallcoverings.com/measure';
function num(v) {
if (v == null) return null;
// tolerate free-text like '36" Wide' or '20.5 (52 cm)' -> first number
const m = String(v).match(/[0-9]+(\.[0-9]+)?/);
if (!m) return null;
const n = parseFloat(m[0]);
return Number.isFinite(n) && n > 0 ? n : null;
}
/**
* PATH A (full): needs width AND at least one repeat.
* @param {object} mf { width_in, vertical_repeat_in, horizontal_repeat_in }
* @param {string} dwSku
* @returns {string|null}
*/
function buildDeepLink(mf = {}, dwSku = '') {
const w = num(mf.width_in);
const vr = num(mf.vertical_repeat_in);
const hr = num(mf.horizontal_repeat_in);
if (!w || (!vr && !hr)) return null;
const q = new URLSearchParams();
q.set('w', w);
if (vr) q.set('vr', vr);
if (hr) q.set('hr', hr);
if (dwSku) q.set('sku', dwSku);
q.set('mode', 'full');
return `${BASE}?${q.toString()}`;
}
/**
* PATH B (ship-now): needs width only. The AR tool collects repeat in-tool.
* @param {object} mf { width_in, vertical_repeat_in?, horizontal_repeat_in? }
* @param {string} dwSku
* @returns {string|null}
*/
function buildDeepLinkWidthOnly(mf = {}, dwSku = '') {
const w = num(mf.width_in);
if (!w) return null; // width is the only hard requirement for Path B
const q = new URLSearchParams();
q.set('w', w);
// pass repeat through when we happen to have it (progressive enhancement)
const vr = num(mf.vertical_repeat_in);
const hr = num(mf.horizontal_repeat_in);
if (vr) q.set('vr', vr);
if (hr) q.set('hr', hr);
if (dwSku) q.set('sku', dwSku);
q.set('mode', vr || hr ? 'full' : 'width');
return `${BASE}?${q.toString()}`;
}
module.exports = { buildDeepLink, buildDeepLinkWidthOnly, num };
// quick self-test when run directly
if (require.main === module) {
console.log('A full :', buildDeepLink({ width_in: 27, vertical_repeat_in: 18, horizontal_repeat_in: 13.5 }, 'DWKK-100367'));
console.log('A no repeat->null:', buildDeepLink({ width_in: 27 }, 'DW-2'));
console.log('B width-only :', buildDeepLinkWidthOnly({ width_in: '36" Wide' }, 'DW-9'));
console.log('B width+repeat :', buildDeepLinkWidthOnly({ width_in: 20.5, vertical_repeat_in: 25 }, 'DW-1'));
console.log('B no width->null:', buildDeepLinkWidthOnly({ vertical_repeat_in: 18 }, 'DW-3'));
}