[object Object]

← back to Marketing Command Center

composer: Banners tab renders a LIVE banner preview (real /api/layouts/render HTML in the middle zone) instead of a dead deep-link

01d65e1dfd4f729737b286eb653376e0dc6e4224 · 2026-07-17 08:14:18 -0700 · Steve Abrams

Files touched

Diff

commit 01d65e1dfd4f729737b286eb653376e0dc6e4224
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 17 08:14:18 2026 -0700

    composer: Banners tab renders a LIVE banner preview (real /api/layouts/render HTML in the middle zone) instead of a dead deep-link
---
 public/panels/composer.js | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/public/panels/composer.js b/public/panels/composer.js
index 9471cfe..59f97a5 100644
--- a/public/panels/composer.js
+++ b/public/panels/composer.js
@@ -54,14 +54,19 @@ window.MCC_PANELS['composer'] = {
           grid.appendChild(d1);
         });
       } else {
+        // Banners: click a template → render it LIVE (real HTML from /api/layouts/render)
+        // into the middle preview, using the current caption as the headline and the
+        // selected asset (if any) as the banner's product photo. This is a genuine
+        // designed preview. Posting a banner AS AN IMAGE (HTML→PNG) is the next step,
+        // drafted for Steve — see pending-approval/mcc-composer-banner-image.md.
         const d = await jget('/api/layouts/templates');
         const items = (d && d.templates) || [];
-        grid.innerHTML = `<div class="bd-empty" style="grid-column:1/-1">Banners are built in the <a href="#layouts">Layouts</a> panel (they render to a designed HTML card). Pick one there, export the image, then add it to Assets to post it here.</div>`;
-        // still list the template names as quick links
+        grid.innerHTML = `<div class="bd-empty" style="grid-column:1/-1;font-size:11.5px">Click a template to preview it here → the caption becomes the headline, a selected asset becomes the photo. <b>Preview only</b> for now; one-click "post as image" is coming.</div>`;
         items.forEach(t => {
-          const b = document.createElement('a');
-          b.href = '#layouts'; b.className = 'cmp-tgt'; b.style.textDecoration = 'none'; b.style.gridColumn = '1/-1';
-          b.innerHTML = `<span class="ico">🎏</span><span class="nm">${esc(t.name || t.title || t.id || t)}</span><span class="st on">open →</span>`;
+          const b = document.createElement('div');
+          b.className = 'cmp-tgt'; b.style.gridColumn = '1/-1'; b.style.cursor = 'pointer';
+          b.innerHTML = `<span class="ico">🎏</span><span class="nm">${esc(t.name || t.id)}</span><span class="st on" style="text-transform:none">${esc(t.aspect || t.kind || '')}</span>`;
+          b.addEventListener('click', () => renderBanner(t.id, b));
           grid.appendChild(b);
         });
       }
@@ -71,8 +76,25 @@ window.MCC_PANELS['composer'] = {
       el.classList.add('sel');
       state.mediaUrl = url;
       const img = $('#cmp-img'); img.src = url; img.style.display = 'block';
+      const bf = $('#cmp-banner'); if (bf) bf.style.display = 'none';   // asset chosen → hide any banner preview
       $('#cmp-preview').querySelector('.cmp-ph').style.display = 'none';
     }
+    // Render a Layouts template LIVE into the preview via /api/layouts/render (returns HTML).
+    async function renderBanner(templateId, el) {
+      root.querySelectorAll('.cmp-tgt.sel').forEach(x => x.classList.remove('sel')); if (el) el.classList.add('sel');
+      const caption = ($('#cmp-caption').value || '').trim();
+      const headline = caption.split('\n')[0].slice(0, 80) || 'Designer Wallcoverings';
+      const r = await jpost('/api/layouts/render', {
+        template: templateId, headline, subhead: 'New arrivals', cta: 'Shop the collection',
+        productName: 'Designer Wallcoverings', imageUrl: state.mediaUrl || '',
+      });
+      if (!r || !r.html) return;
+      const img = $('#cmp-img'); img.style.display = 'none';
+      $('#cmp-preview').querySelector('.cmp-ph').style.display = 'none';
+      let f = $('#cmp-banner');
+      if (!f) { f = document.createElement('iframe'); f.id = 'cmp-banner'; f.setAttribute('sandbox', 'allow-same-origin'); f.style.cssText = 'width:100%;min-height:320px;border:0;background:#fff;border-radius:12px'; $('#cmp-preview').appendChild(f); }
+      f.style.display = 'block'; f.srcdoc = r.html;
+    }
     root.querySelectorAll('.cmp-tab').forEach(tab => tab.addEventListener('click', () => {
       root.querySelectorAll('.cmp-tab').forEach(t => t.classList.remove('on'));
       tab.classList.add('on'); state.src = tab.dataset.src; loadSource(state.src);

← a4a92b1 layouts: fix malformed placeholder SKUs — DW-GL-001 → DWGL-1  ·  back to Marketing Command Center  ·  composer: banner preview respects template aspect-ratio (#2) 7877a92 →