← 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'));
}