[object Object]

← back to Trade Portal

Add Buy on Shopify cart button, test-mode banner, trade pricing caveat

7904fa51c5b7ffe745881309fc0b500c2f2bedf0 · 2026-07-16 07:37:14 -0700 · steve@designerwallcoverings.com

- TEST-MODE banner: warns checkout is live Shopify; PayPal toggle note
- Per-card: qty input + 'Buy on Shopify' link -> /cart/<variant_id>:<qty>
  qty is adjustable; link updated via onclick before navigation
- Trade users see Phase-2 checkout caveat: Shopify charges store price,
  not on-site trade/net estimate; ?discount=CODE param placeholder noted
- Non-Kravet trade price labeled 'est.' with inline caveat text
- Public view: retail only, trade null, no cost exposed (verified)
- Sort + density persist in localStorage; Color sort option added
- Admin cards: created date+time chip unchanged (HARD RULE preserved)

Files touched

Diff

commit 7904fa51c5b7ffe745881309fc0b500c2f2bedf0
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Thu Jul 16 07:37:14 2026 -0700

    Add Buy on Shopify cart button, test-mode banner, trade pricing caveat
    
    - TEST-MODE banner: warns checkout is live Shopify; PayPal toggle note
    - Per-card: qty input + 'Buy on Shopify' link -> /cart/<variant_id>:<qty>
      qty is adjustable; link updated via onclick before navigation
    - Trade users see Phase-2 checkout caveat: Shopify charges store price,
      not on-site trade/net estimate; ?discount=CODE param placeholder noted
    - Non-Kravet trade price labeled 'est.' with inline caveat text
    - Public view: retail only, trade null, no cost exposed (verified)
    - Sort + density persist in localStorage; Color sort option added
    - Admin cards: created date+time chip unchanged (HARD RULE preserved)
---
 data/applications.json |  2 +-
 public/index.html      | 61 +++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 59 insertions(+), 4 deletions(-)

diff --git a/data/applications.json b/data/applications.json
index fe51488..0637a08 100644
--- a/data/applications.json
+++ b/data/applications.json
@@ -1 +1 @@
-[]
+[]
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 997277b..56b1f99 100644
--- a/public/index.html
+++ b/public/index.html
@@ -14,6 +14,9 @@
   .topbar a:hover{color:#fff}
   .trade-badge{background:var(--gold);color:#fff;font-size:11px;font-weight:700;padding:2px 8px;border-radius:3px;letter-spacing:.05em;text-transform:uppercase}
   #session-info{font-size:13px;color:#cce0ff;display:flex;align-items:center;gap:10px}
+  /* ── TEST-MODE banner ── */
+  .test-banner{background:#7b3f00;color:#ffe8cc;font-size:12px;padding:7px 24px;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
+  .test-banner strong{font-size:12px;letter-spacing:.04em}
   .controls{display:flex;gap:12px;align-items:center;flex-wrap:wrap;padding:12px 24px;border-bottom:1px solid #e2ddd7;background:#fff;position:sticky;top:0;z-index:10}
   .controls h2{margin:0;font-size:13px;font-weight:600;color:#666;flex:1}
   label{font-size:12px;color:#666;display:flex;gap:6px;align-items:center}
@@ -23,10 +26,10 @@
   .nav-links a{font-size:13px;color:var(--accent);text-decoration:none;font-weight:500;cursor:pointer}
   .nav-links a:hover{text-decoration:underline}
   .grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px;padding:24px}
-  .card{background:#fff;border:1px solid #e2ddd7;border-radius:8px;overflow:hidden;transition:box-shadow .15s}
+  .card{background:#fff;border:1px solid #e2ddd7;border-radius:8px;overflow:hidden;transition:box-shadow .15s;display:flex;flex-direction:column}
   .card:hover{box-shadow:0 4px 16px rgba(0,0,0,.1)}
   .card img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;background:#eee}
-  .card-body{padding:10px 12px}
+  .card-body{padding:10px 12px;flex:1;display:flex;flex-direction:column}
   .card-title{font-size:13px;font-weight:600;margin:0 0 2px;line-height:1.3}
   .card-vendor{font-size:11px;color:#888;margin:0 0 4px}
   .card-type{font-size:11px;color:#aaa}
@@ -37,8 +40,17 @@
   .price-trade-wrap{margin-top:4px}
   .price-trade{font-size:14px;font-weight:700;color:var(--accent);background:#eaf2ff;padding:3px 7px;border-radius:4px;display:inline-block}
   .price-trade-label{font-size:10px;color:var(--gold);font-weight:700;text-transform:uppercase;letter-spacing:.04em}
+  .price-est-note{font-size:10px;color:#999;margin-top:2px;font-style:italic}
   .trade-locked{font-size:12px;color:#bbb;font-style:italic;margin-top:4px}
   .kravet-badge{font-size:10px;background:#fef3e2;color:var(--gold);border:1px solid #f5d68a;padding:1px 5px;border-radius:3px;display:inline-block;margin-top:3px}
+  /* ── Cart / Buy button area ── */
+  .card-actions{margin-top:auto;padding-top:10px;display:flex;gap:6px;align-items:center;flex-wrap:wrap}
+  .qty-input{width:48px;padding:5px 6px;border:1px solid #ddd;border-radius:4px;font-size:12px;text-align:center}
+  .btn-buy{display:inline-block;padding:6px 12px;background:var(--accent);color:#fff;font-size:12px;font-weight:600;border-radius:5px;text-decoration:none;border:none;cursor:pointer;white-space:nowrap}
+  .btn-buy:hover{background:#0d2440}
+  .btn-buy-disabled{display:inline-block;padding:6px 12px;background:#e0e0e0;color:#999;font-size:12px;font-weight:600;border-radius:5px;cursor:default;white-space:nowrap}
+  .checkout-caveat{font-size:10px;color:#999;margin-top:4px;font-style:italic;line-height:1.4}
+  /* ── Trade CTA banner ── */
   .empty{padding:60px;text-align:center;color:#999}
   #trade-cta{background:linear-gradient(135deg,#1a3a5c,#0d2440);color:#fff;padding:20px 24px;display:flex;align-items:center;gap:20px;flex-wrap:wrap}
   #trade-cta h3{margin:0 0 4px;font-size:15px}
@@ -85,6 +97,14 @@
   </div>
 </div>
 
+<!-- TEST-MODE banner -->
+<div class="test-banner">
+  <strong>TEST</strong>
+  &mdash; "Buy on Shopify" hands off to the <strong>live</strong> Shopify checkout at designerwallcoverings.com.
+  Some trade lines require a customer login at checkout (store setting).
+  PayPal shows when enabled in Shopify Payments (store admin toggle).
+</div>
+
 <div id="trade-cta">
   <div>
     <h3>Access Net / Trade Pricing</h3>
@@ -110,6 +130,7 @@
         <option value="price-asc">Price &#x2191;</option>
         <option value="price-desc">Price &#x2193;</option>
         <option value="vendor">Vendor</option>
+        <option value="color">Color</option>
       </select>
     </label>
     <label>Density <input id="density" type="range" min="2" max="6" value="4"></label>
@@ -254,9 +275,18 @@ async function loadProducts() {
     : '<div class="empty">No products found.</div>';
 }
 
+// Build cart URL client-side for qty changes: https://domain/cart/<variant_id>:<qty>
+function buildCartUrl(baseCartUrl, qty) {
+  if (!baseCartUrl) return null;
+  // base already has variant_id:1 — swap the qty
+  return baseCartUrl.replace(/:(\d+)$/, ':' + (parseInt(qty)||1));
+}
+
 function cardHTML(p, isTrade) {
   const retailFmt = p.retail != null ? `$${p.retail.toFixed(2)}` : '—';
   const isKravet  = p.pricing_rule === 'kravet-map';
+  const isEstTrade = p.pricing_rule === 'standard-est-25pct';
+
   let priceHTML = '';
   if (isTrade && p.trade != null) {
     priceHTML = `<div class="price-row">
@@ -265,14 +295,38 @@ function cardHTML(p, isTrade) {
         <div class="price-trade-label">Your Trade Price</div>
         <div class="price-trade">$${p.trade.toFixed(2)}</div>
         ${isKravet ? '<div class="kravet-badge">MAP Pricing (Kravet family)</div>' : ''}
+        ${isEstTrade ? '<div class="price-est-note">est. &mdash; trade discount applied at checkout via account/code</div>' : ''}
       </div>
     </div>`;
   } else {
     priceHTML = `<div class="price-row">
       <div class="price-retail"><span class="plabel">Retail </span>${esc(retailFmt)}</div>
-      <div class="trade-locked">🔒 Trade price — login to reveal</div>
+      <div class="trade-locked">&#128274; Trade price &mdash; login to reveal</div>
     </div>`;
   }
+
+  // Cart / Buy button
+  const hasVariant = !!p.variant_id && !!p.cart_url;
+  const qtyId = 'qty-' + esc(p.sku || p.handle || Math.random().toString(36).slice(2));
+  let cartHTML = '';
+  if (hasVariant) {
+    // Phase-2 note shown for trade users only (checkout charges store price, not trade price)
+    const tradeCheckoutNote = isTrade
+      ? `<div class="checkout-caveat">&#9432; Checkout charges the store price. Trade pricing applied via account or discount code at checkout &mdash; Phase 2 gap. <em>?discount=CODE</em> param accepted.</div>`
+      : '';
+    cartHTML = `<div class="card-actions">
+      <input class="qty-input" id="${esc(qtyId)}" type="number" min="1" max="999" value="1" title="Quantity">
+      <a class="btn-buy" id="buy-${esc(p.sku||p.handle)}"
+         href="${esc(p.cart_url)}"
+         target="_blank" rel="noopener noreferrer"
+         onclick="this.href=buildCartUrl('${esc(p.cart_url)}', document.getElementById('${esc(qtyId)}').value); return true;"
+         title="Opens Shopify hosted checkout">Buy on Shopify &#8599;</a>
+    </div>
+    ${tradeCheckoutNote}`;
+  } else {
+    cartHTML = `<div class="card-actions"><span class="btn-buy-disabled">No variant</span></div>`;
+  }
+
   return `<div class="card">
     <img src="${esc(p.image||'')}" alt="${esc(p.title||'')}" loading="lazy">
     <div class="card-body">
@@ -281,6 +335,7 @@ function cardHTML(p, isTrade) {
       <div class="card-type">${esc(p.product_type||'')}</div>
       ${priceHTML}
       <div class="card-sku">${esc(p.sku||'')}</div>
+      ${cartHTML}
     </div>
   </div>`;
 }

← 20df63d Switch product source to shopify-catalog.json; add cart perm  ·  back to Trade Portal  ·  security: bind server to 127.0.0.1 so the tunnel/proxy basic e5b8824 →