[object Object]

← back to Reno Visualizer

Handle new catalog fields: buyable, sample_only, price_display, product_url

915041e00fd56be69b1b329376dd308db203070d · 2026-07-16 08:48:25 -0700 · steve@designerwallcoverings.com

- server.js loadProducts(): pass through price_display (pre-formatted string),
  buyable (bool), sample_only (bool), product_url (live DW product page);
  derive price_display fallback if absent so no null ever reaches the client
- index.html pattern grid: show price via price_display (never fmtPrice/NaN);
  sample-only badge uses sample_only field; append "/roll" only for buyable
- Step 3 selected-pattern info + Step 4 shop preview: same price_display fix;
  product_url used for "View on Designer Wallcoverings" links throughout

No $null / $NaN possible: price_display is always a non-empty string from the
catalog (e.g. "Roll price on request" for the 83 sample-only products, "$94.42"
for the 167 buyable products).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files touched

Diff

commit 915041e00fd56be69b1b329376dd308db203070d
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Thu Jul 16 08:48:25 2026 -0700

    Handle new catalog fields: buyable, sample_only, price_display, product_url
    
    - server.js loadProducts(): pass through price_display (pre-formatted string),
      buyable (bool), sample_only (bool), product_url (live DW product page);
      derive price_display fallback if absent so no null ever reaches the client
    - index.html pattern grid: show price via price_display (never fmtPrice/NaN);
      sample-only badge uses sample_only field; append "/roll" only for buyable
    - Step 3 selected-pattern info + Step 4 shop preview: same price_display fix;
      product_url used for "View on Designer Wallcoverings" links throughout
    
    No $null / $NaN possible: price_display is always a non-empty string from the
    catalog (e.g. "Roll price on request" for the 83 sample-only products, "$94.42"
    for the 167 buyable products).
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 public/index.html | 13 +++++++------
 server.js         |  8 ++++++--
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/public/index.html b/public/index.html
index e179a8e..510902a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -554,9 +554,9 @@ function renderPatterns(products) {
     return `<div class="pattern-card${isSelected ? ' selected' : ''}" id="pc-${esc(p.sku)}" onclick="selectPattern(${JSON.stringify(JSON.stringify(p))})">
       <img src="${esc(imgSrc)}" alt="${esc(p.title)}" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22200%22 height=%22200%22><rect width=%22200%22 height=%22200%22 fill=%22%23e8e4dc%22/></svg>'">
       <div class="pattern-meta">
-        <div class="pattern-title">${esc(p.title)}${p.is_sample ? '<span class="pattern-sample-badge">Sample</span>' : ''}</div>
+        <div class="pattern-title">${esc(p.title)}${p.sample_only ? '<span class="pattern-sample-badge">Sample only</span>' : ''}</div>
         <div class="pattern-sub">${esc(p.vendor || '')}</div>
-        ${p.price ? `<div class="pattern-price">${fmtPrice(p.price)}/roll</div>` : ''}
+        ${p.price_display ? `<div class="pattern-price">${esc(p.price_display)}${p.buyable ? '/roll' : ''}</div>` : ''}
       </div>
     </div>`;
   }).join('');
@@ -617,7 +617,7 @@ function updateSelectedPatternInfo() {
     <div class="selected-pattern-meta">
       <div class="title">${esc(p.title)}</div>
       <div class="sub">${esc(p.vendor || '')} · ${esc(p.color || '')}</div>
-      <div class="sub" style="margin-top:2px;font-weight:600;color:var(--brand)">${fmtPrice(p.price)}/roll</div>
+      <div class="sub" style="margin-top:2px;font-weight:600;color:var(--brand)">${esc(p.price_display || '')}${p.buyable ? '/roll' : ''}</div>
     </div>`;
 }
 
@@ -819,14 +819,15 @@ function initShopStep() {
   if (!p) return;
   const imgSrc = p.image_url || p.image || '';
   const roomSrc = r?.custom ? r.dataUrl : (r?.src || '');
-  const productUrl = p.shopify_url || (p.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
+  const productUrl = p.product_url || p.shopify_url || (p.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
+  const priceLabel = p.price_display ? (p.price_display + (p.buyable ? '/roll' : '')) : '';
   $('#look-preview').innerHTML = `
     <img class="look-room-thumb" src="${esc(roomSrc)}" alt="Room" onerror="this.style.display='none'">
     <img class="look-pattern-thumb" src="${esc(imgSrc)}" alt="${esc(p.title)}" onerror="this.style.background='${esc(p.hex||'#e8e4dc')}'">
     <div class="look-info">
       <h3>${esc(p.title)}</h3>
       <p>${esc(p.vendor || '')}${p.color ? ' · ' + esc(p.color) : ''}${p.collection ? ' · ' + esc(p.collection) : ''}</p>
-      <p class="price">${fmtPrice(p.price)}/roll</p>
+      ${priceLabel ? `<p class="price">${esc(priceLabel)}</p>` : ''}
       ${productUrl ? `<p style="margin-top:6px;"><a href="${esc(productUrl)}" target="_blank" rel="noopener noreferrer" style="font-size:12px;color:var(--brand-light);text-decoration:underline;">View on Designer Wallcoverings →</a></p>` : ''}
     </div>`;
 }
@@ -839,7 +840,7 @@ async function submitLead(e) {
 
   const p = state.selectedPattern;
   const r = state.selectedRoom;
-  const productUrl = p?.shopify_url || (p?.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
+  const productUrl = p?.product_url || p?.shopify_url || (p?.handle ? `https://www.designerwallcoverings.com/products/${p.handle}` : '');
   const body = {
     email: $('#lead-email').value.trim(),
     name: $('#lead-name').value.trim(),
diff --git a/server.js b/server.js
index 1edc643..2db7f6c 100644
--- a/server.js
+++ b/server.js
@@ -53,10 +53,14 @@ function loadProducts() {
       type: p.type,
       image: p.image,
       image_url: p.image,
-      price: p.price,
+      price: p.price,                           // numeric or null (null = sample_only)
+      price_display: p.price_display || (p.price != null ? '$' + Number(p.price).toFixed(2) : 'Roll price on request'),
+      buyable: !!p.buyable,
+      sample_only: !!p.sample_only,
+      product_url: p.product_url || `https://${checkoutDomain}/products/${p.handle}`,
       handle: p.handle,
       variant_id: p.variant_id,
-      shopify_url: `https://${checkoutDomain}/products/${p.handle}`,
+      shopify_url: p.product_url || `https://${checkoutDomain}/products/${p.handle}`,
     }));
   } catch {
     try { return JSON.parse(fs.readFileSync(path.join(DIR, 'data', 'products.json'), 'utf8')); }

← 258fcf0 shopify: refresh catalog — real roll prices, no $4.25 sample  ·  back to Reno Visualizer  ·  shopify: refresh catalog — keep sample-only as 'by request' 572601b →