[object Object]

← back to Wallco Ai

feat(design page): split tabs into LEFT (public) + RIGHT (admin) hamburgers

60f4226218a89832bfbc0b52a01397249396eb60 · 2026-05-25 00:37:20 -0700 · Steve Abrams

Steve 2026-05-25: 'move the tabs into the left and right panels in
hamburgers'.

LEFT hamburger (existing edit-hamburger, repositioned from right→left):
  Adjust / Color / Info / Style / Save tabs in the existing edit-modal.
  All public/buyer tabs already lived in this drawer — just flipped its
  side anchor.

RIGHT hamburger (new ADMIN button, only renders when isAdmin):
  Slides in a side-sheet (max 420px / 92vw, .25s transition) containing
  the tone-on-tone fashion-house palettes block. Hydrated client-side
  via DOMContentLoaded — moves the existing .recolor-wheel + its inline
  script element into the new sheet so all click handlers keep working
  unchanged.

ESC + backdrop click both close the admin sheet. Non-admins see no
ADMIN button and no sheet markup (the whole isAdmin-gated section is
omitted server-side, not just hidden client-side).

Only restructures the visible chrome on /design/:id; no behavioral
changes to the existing fashion-palette recolor handlers, no API
changes. Other admin blocks (curated designer, ghost-review, settlement
controls, etc.) remain in their current inline positions for now — this
ship is just the tone-on-tone drawer per Steve's most-active ask.

Files touched

Diff

commit 60f4226218a89832bfbc0b52a01397249396eb60
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon May 25 00:37:20 2026 -0700

    feat(design page): split tabs into LEFT (public) + RIGHT (admin) hamburgers
    
    Steve 2026-05-25: 'move the tabs into the left and right panels in
    hamburgers'.
    
    LEFT hamburger (existing edit-hamburger, repositioned from right→left):
      Adjust / Color / Info / Style / Save tabs in the existing edit-modal.
      All public/buyer tabs already lived in this drawer — just flipped its
      side anchor.
    
    RIGHT hamburger (new ADMIN button, only renders when isAdmin):
      Slides in a side-sheet (max 420px / 92vw, .25s transition) containing
      the tone-on-tone fashion-house palettes block. Hydrated client-side
      via DOMContentLoaded — moves the existing .recolor-wheel + its inline
      script element into the new sheet so all click handlers keep working
      unchanged.
    
    ESC + backdrop click both close the admin sheet. Non-admins see no
    ADMIN button and no sheet markup (the whole isAdmin-gated section is
    omitted server-side, not just hidden client-side).
    
    Only restructures the visible chrome on /design/:id; no behavioral
    changes to the existing fashion-palette recolor handlers, no API
    changes. Other admin blocks (curated designer, ghost-review, settlement
    controls, etc.) remain in their current inline positions for now — this
    ship is just the tone-on-tone drawer per Steve's most-active ask.
---
 server.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 69 insertions(+), 2 deletions(-)

diff --git a/server.js b/server.js
index eb068ed..496aabe 100644
--- a/server.js
+++ b/server.js
@@ -2217,6 +2217,21 @@ app.get('/trade/auth', (req, res) => {
   res.redirect(safeReturn);
 });
 
+// GET /logout — unified logout: clears every auth cookie + flips localhost-admin
+// to view-as-live, then redirects. Backs the "Logout" link in the hamburger nav.
+// 2026-05-25: separate from /trade/logout (trade-only) so a single hamburger link
+// works whether the user is admin, trade, both, or just a localhost-auto-admin.
+app.get('/logout', (req, res) => {
+  res.clearCookie('dw_auth', { path: '/' });
+  res.clearCookie('wallco_trade_session', { path: '/' });
+  res.clearCookie('wallco_admin', { path: '/' });
+  // Force localhost-auto-admin into public view so the change is visible without
+  // hostname juggling. Toggleable back via the corner view-toggle pill.
+  res.cookie('view_mode', 'live', { path: '/', maxAge: 1000 * 60 * 60 * 24 * 7 });
+  const returnTo = (req.query.next && typeof req.query.next === 'string' && req.query.next.startsWith('/')) ? req.query.next : '/';
+  res.redirect(302, returnTo);
+});
+
 // GET /trade/logout — clear cookie, send home.
 app.get('/trade/logout', (req, res) => {
   logEvent(req, 'trade_logout', null);
@@ -2963,6 +2978,7 @@ function htmlHeader(active) {
   ).join('\n  ')}
   <a href="/me/saved" class="${active === '/me/saved' ? 'active' : ''}">My Saved</a>
   <a href="/me/activity" class="${active === '/me/activity' ? 'active' : ''}">My Activity</a>
+  <a href="/logout" class="nav-logout">⎋ Logout</a>
   <button class="theme-toggle" id="theme-toggle" aria-label="Toggle dark/light mode">
     <svg class="sun-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
     <svg class="moon-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
@@ -9141,11 +9157,49 @@ ${htmlHeader('/designs')}
 </style>
   <div class="detail-layout">
     <div class="detail-image-col" style="position:relative">
-      <!-- HAMBURGER ICON (top-right of image) opens the INFO + EDIT modal ─── -->
+      <!-- LEFT HAMBURGER (public tabs: Adjust/Color/Info/Style/Save) -->
       <button id="edit-hamburger" class="edit-hamburger" aria-label="Open design info, color &amp; style"
               aria-haspopup="dialog" aria-expanded="false">
         <span></span><span></span><span></span>
       </button>
+      ${_isAdmin ? `
+      <!-- RIGHT HAMBURGER (admin-only tabs: tone-on-tone fashion palettes) -->
+      <button id="admin-hamburger" class="admin-hamburger" aria-label="Open admin tools"
+              aria-haspopup="dialog" aria-expanded="false" title="Admin tools">ADMIN</button>
+      <div id="admin-sheet-backdrop" class="admin-sheet-backdrop" data-close-admin="1"></div>
+      <aside id="admin-sheet" class="admin-sheet" role="dialog" aria-modal="false" aria-label="Admin tools">
+        <div class="admin-sheet-head">
+          <h3>Admin · this design</h3>
+          <button class="admin-sheet-close" data-close-admin="1" aria-label="Close">✕</button>
+        </div>
+        <div id="admin-sheet-body"><!-- tone-on-tone block hydrated client-side --></div>
+      </aside>
+      <script>
+      (function(){
+        const btn  = document.getElementById('admin-hamburger');
+        const sh   = document.getElementById('admin-sheet');
+        const bd   = document.getElementById('admin-sheet-backdrop');
+        if (!btn || !sh) return;
+        const open  = () => { sh.classList.add('open'); bd.classList.add('open'); btn.setAttribute('aria-expanded','true'); };
+        const close = () => { sh.classList.remove('open'); bd.classList.remove('open'); btn.setAttribute('aria-expanded','false'); };
+        btn.addEventListener('click', () => sh.classList.contains('open') ? close() : open());
+        document.addEventListener('click', (e) => { if (e.target && e.target.matches('[data-close-admin]')) close(); });
+        document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
+        // After DOM settles, MOVE the tone-on-tone block into this sheet.
+        // The block has an in-line script that wires its own click handlers,
+        // so we move the whole .recolor-wheel + its sibling <script> together.
+        document.addEventListener('DOMContentLoaded', () => {
+          const tone = document.querySelector('.recolor-wheel');
+          const sib  = tone && tone.nextElementSibling && tone.nextElementSibling.tagName === 'SCRIPT' ? tone.nextElementSibling : null;
+          const body = document.getElementById('admin-sheet-body');
+          if (tone && body) {
+            body.appendChild(tone);
+            if (sib) body.appendChild(sib);
+          }
+        });
+      })();
+      </script>
+      ` : ''}
 
       <!-- INFO MODAL ───────────────────────────────────────────────────────── -->
       <div id="edit-modal" class="edit-modal" hidden role="dialog" aria-modal="true" aria-labelledby="edit-modal-title">
@@ -9274,9 +9328,22 @@ ${htmlHeader('/designs')}
 
       <style>
         /* Hamburger button — small, top-right of the design image */
-        .edit-hamburger { position:absolute; top:14px; right:14px; width:42px; height:42px; background:rgba(20,18,15,.85); backdrop-filter:blur(8px); color:#f0eadc; border:1px solid rgba(255,255,255,.1); border-radius:8px; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; padding:0; z-index:18; transition:background .15s; }
+        .edit-hamburger { position:absolute; top:14px; left:14px; width:42px; height:42px; background:rgba(20,18,15,.85); backdrop-filter:blur(8px); color:#f0eadc; border:1px solid rgba(255,255,255,.1); border-radius:8px; cursor:pointer; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px; padding:0; z-index:18; transition:background .15s; }
         .edit-hamburger:hover { background:rgba(20,18,15,.95); border-color:#d2b15c; }
         .edit-hamburger span { display:block; width:18px; height:2px; background:currentColor; border-radius:1px; }
+        /* RIGHT-side ADMIN hamburger — separate panel for admin-only tabs
+           (tone-on-tone fashion palettes, etc.). Public tabs live in the
+           LEFT edit-hamburger above. Steve 2026-05-25: "move the tabs into
+           the left and right panels in hamburgers". */
+        .admin-hamburger { position:absolute; top:14px; right:14px; width:42px; height:42px; background:rgba(60,20,15,.85); backdrop-filter:blur(8px); color:#fdd6a0; border:1px solid rgba(255,180,120,.2); border-radius:8px; cursor:pointer; display:flex; align-items:center; justify-content:center; padding:0; z-index:18; transition:background .15s; font:600 11px var(--sans,system-ui); letter-spacing:.06em; }
+        .admin-hamburger:hover { background:rgba(60,20,15,.95); border-color:#d2b15c; }
+        .admin-sheet { position:fixed; top:0; right:0; bottom:0; width:min(420px,92vw); background:#faf7f1; box-shadow:-12px 0 40px rgba(0,0,0,.2); border-left:1px solid var(--line,#ddd); transform:translateX(100%); transition:transform .25s ease; z-index:30; overflow-y:auto; padding:18px 18px 32px; }
+        .admin-sheet.open { transform:translateX(0); }
+        .admin-sheet-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.35); opacity:0; pointer-events:none; transition:opacity .25s ease; z-index:29; }
+        .admin-sheet-backdrop.open { opacity:1; pointer-events:auto; }
+        .admin-sheet-head { display:flex; align-items:center; justify-content:space-between; margin:0 0 14px; padding-bottom:10px; border-bottom:1px solid var(--line,#ddd); }
+        .admin-sheet-head h3 { margin:0; font:600 12px var(--sans,system-ui); letter-spacing:.12em; text-transform:uppercase; color:#5a4628; }
+        .admin-sheet-close { background:transparent; border:0; font:18px var(--sans,system-ui); color:#7a6e5a; cursor:pointer; padding:4px 8px; }
 
         /* Modal */
         .edit-modal { position:fixed; inset:0; z-index:200; display:flex; align-items:center; justify-content:center; }

← 3a16b54 bg-tester picker: switch to unified /api/bg-textures, hide v  ·  back to Wallco Ai  ·  stack both hamburgers in upper-right corner of design image 9b4e075 →