[object Object]

← back to Dw Photo Capture

All-Shopify cards: per-card 'Replace all — make this the only image' toggle (DTD verdict C); default keeps existing imagery, explicit opt-in to wipe

708fac74cd8724a222802f15d3cbe6fa6ab9bfbe · 2026-06-24 17:56:37 -0700 · Steve

Files touched

Diff

commit 708fac74cd8724a222802f15d3cbe6fa6ab9bfbe
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 17:56:37 2026 -0700

    All-Shopify cards: per-card 'Replace all — make this the only image' toggle (DTD verdict C); default keeps existing imagery, explicit opt-in to wipe
---
 data/build.json   | 5 +++--
 public/index.html | 8 +++++++-
 server.js         | 6 ++++++
 3 files changed, 16 insertions(+), 3 deletions(-)

diff --git a/data/build.json b/data/build.json
index d75c025..45cda72 100644
--- a/data/build.json
+++ b/data/build.json
@@ -1,5 +1,5 @@
 {
-  "next": 13,
+  "next": 14,
   "map": {
     "578af86f": 2,
     "bc95fdb0": 3,
@@ -11,6 +11,7 @@
     "09a337f9": 9,
     "423881b4": 10,
     "76a90668": 11,
-    "5ef2ebe2": 12
+    "5ef2ebe2": 12,
+    "788487db": 13
   }
 }
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index 311e93e..073c241 100644
--- a/public/index.html
+++ b/public/index.html
@@ -45,6 +45,10 @@
   .price{color:var(--green);font-weight:700}
   .price.no{color:var(--muted)}
   .when{font-size:11px;color:var(--muted)}
+  /* "make this the only image" — destructive opt-in, only shown when an existing image would be wiped */
+  .onlyimg{display:flex;align-items:center;gap:7px;font-size:11.5px;color:var(--muted);margin-top:2px;cursor:pointer;user-select:none}
+  .onlyimg input{accent-color:var(--red);width:15px;height:15px;flex:0 0 auto}
+  .onlyimg.armed{color:var(--red);font-weight:600}
   .actions{display:flex;gap:8px;margin-top:auto;padding-top:6px}
   .shoot{flex:1;position:relative;overflow:hidden;background:var(--gold);color:#1b1407;border:none;border-radius:12px;padding:13px;font-weight:700;font-size:15px;text-align:center;cursor:pointer}
   .shoot input{position:absolute;inset:0;opacity:0;font-size:200px;cursor:pointer}
@@ -239,6 +243,7 @@ function render(){
         </div>
         ${x.ts?`<div class="when">🕓 ${fmt(x.ts)}${x.live?(x.created?' · 🟢 CREATED + LIVE':' · 🟢 LIVE on store'):x.shopify_pushed?(x.live_reason?(' · photo on Shopify ('+x.live_reason+')'):' · photo on Shopify'):''}</div>`:''}
         ${x.push_err?`<div class="when" style="color:var(--red)">${x.push_err}</div>`:''}
+        ${(x.keep_images&&x.image)?`<label class="onlyimg${x._replaceAll?' armed':''}"><input type="checkbox" class="onlyimg-cb"${x._replaceAll?' checked':''}> Replace all — make this the only image</label>`:''}
         <div class="actions">
           ${adjustable?'<button class="enh">✨ Enhance</button>':''}
           <label class="shoot">${isNew?'📷 Create + Go Live':photographed?'📷 Update Photo':'📷 Add Photo'}<input type="file" accept="image/*" capture="environment"></label>
@@ -247,6 +252,7 @@ function render(){
       </div>`;
     const editSrc=()=> (x.photo||('/api/current-image?pid='+x.product_id))+(x.photo?'?':'&')+'t='+Date.now();
     el.querySelector('.shoot input').addEventListener('change',e=>shoot(x,e.target.files[0]));
+    const oi=el.querySelector('.onlyimg-cb'); if(oi) oi.addEventListener('change',e=>{ x._replaceAll=e.target.checked; el.querySelector('.onlyimg').classList.toggle('armed',e.target.checked); });
     const enh=el.querySelector('.enh'); if(enh) enh.addEventListener('click',()=>openEditor(x,editSrc()));
     const th=el.querySelector('.thumb.tap'); if(th) th.addEventListener('click',()=>openEditor(x,editSrc()));
     const sk=el.querySelector('.skip'); if(sk) sk.addEventListener('click',()=>skip(x));
@@ -276,7 +282,7 @@ async function uploadPhoto(x,dataUrl){
   toast((x.needs_create?'Creating ':'Uploading ')+x.dw_sku+'…');
   try{
     const r=await fetch('/api/photo',{method:'POST',headers:{'Content-Type':'application/json'},
-      body:JSON.stringify({dw_sku:x.dw_sku,product_id:x.product_id,dataUrl,keep_images:!!x.keep_images})});
+      body:JSON.stringify({dw_sku:x.dw_sku,product_id:x.product_id,dataUrl,keep_images:!!x.keep_images&&!x._replaceAll})});
     const d=await r.json();
     if(d.ok){ Object.assign(x,{done:true,needs_create:false,created:d.created,photo:d.photo,ts:new Date().toISOString(),shopify_pushed:d.shopify_pushed,push_err:d.push_err,live:d.live,live_reason:d.live_reason});
       toast(d.created&&d.live?('🟢 Created + LIVE · '+x.dw_sku):d.created?('Created (draft) · '+x.dw_sku+(d.live_reason?' — '+d.live_reason:'')):d.live?('🟢 '+x.dw_sku+' is LIVE!'):d.shopify_pushed?('✓ Photo on Shopify · '+x.dw_sku+(d.live_reason?(' (needs '+d.live_reason.replace('needs ','')+')'):'')):('Saved '+x.dw_sku+(d.push_err?' (push failed)':''))); render();
diff --git a/server.js b/server.js
index ae8e977..0ea5571 100644
--- a/server.js
+++ b/server.js
@@ -182,6 +182,12 @@ const server = http.createServer((req, res) => {
     return send(res, 200, { total: matches.length, items: matches, indexed: INDEX.length });
   }
 
+  // All sheet GRS items that don't exist in Shopify yet — the "create on photo" worklist.
+  if (u.pathname === '/api/new' && req.method === 'GET') {
+    const items = INDEX.filter(x => x.needs_create);
+    return send(res, 200, { total: items.length, items });
+  }
+
   if (u.pathname === '/api/reindex' && req.method === 'POST') {
     buildCatalog().then(() => {}).catch(() => {});
     return send(res, 200, { ok: true, indexing: true, current: CATALOG.length });

← 05042e0 Any-SKU: include all 170 TWIL-sheet GRS items; photographing  ·  back to Dw Photo Capture  ·  auto-save: 2026-06-24T17:58:43 (3 files) — data/build.json p 06a420e →