← 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
M data/applications.jsonM public/index.html
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>
+ — "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 ↑</option>
<option value="price-desc">Price ↓</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. — 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">🔒 Trade price — 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">ⓘ Checkout charges the store price. Trade pricing applied via account or discount code at checkout — 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 ↗</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 →