[object Object]

← back to Wallco Ai

/me/saved: surface 'Saved Xh ago' badge per card (SELECT already pulled saved_at, render dropped it)

80b15e737254540066aa33a94c50c12d98610c0b · 2026-05-12 20:28:42 -0700 · SteveStudio2

Files touched

Diff

commit 80b15e737254540066aa33a94c50c12d98610c0b
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Tue May 12 20:28:42 2026 -0700

    /me/saved: surface 'Saved Xh ago' badge per card (SELECT already pulled saved_at, render dropped it)
---
 server.js | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/server.js b/server.js
index 4978991..5981985 100644
--- a/server.js
+++ b/server.js
@@ -1129,17 +1129,38 @@ app.get('/me/saved', (req, res) => {
   // snapshot we already render everywhere). Designs that no longer exist in
   // the catalog are silently filtered out.
   let savedIds = [];
+  // Carry saved_at + note through to the card render so users can see
+  // "Saved 2h ago" without opening DevTools — the rail audit gap that
+  // previously dropped this metadata immediately after the SELECT.
+  let savedMetaById = new Map();
   try {
     const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.saved_at DESC), '[]'::json) FROM (
       SELECT design_id, note, saved_at FROM wallco_saved_designs
       WHERE user_id=${parseInt(u.id, 10)}) t;`);
     const rows = JSON.parse(raw || '[]');
     savedIds = rows.map(r => r.design_id);
+    savedMetaById = new Map(rows.map(r => [r.design_id, r]));
   } catch (e) {
     console.error('[me/saved] query failed:', e.message);
   }
   const byId = new Map(DESIGNS.map(d => [d.id, d]));
   const designs = savedIds.map(id => byId.get(id)).filter(Boolean);
+  // Relative-time formatter (server-rendered so the badge ships in HTML — no JS
+  // flash). Uses 'just now' under 60s, '12m', '4h', '3d', else date.
+  const _meSavedRelTime = (iso) => {
+    if (!iso) return '';
+    const t = Date.parse(iso);
+    if (!t) return '';
+    const dMs = Date.now() - t;
+    if (dMs < 60_000) return 'just now';
+    const m = Math.floor(dMs / 60_000);
+    if (m < 60) return m + 'm ago';
+    const h = Math.floor(m / 60);
+    if (h < 24) return h + 'h ago';
+    const days = Math.floor(h / 24);
+    if (days < 30) return days + 'd ago';
+    return new Date(t).toISOString().slice(0, 10);
+  };
 
   const cards = designs.map(d => {
     const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
@@ -1149,6 +1170,12 @@ app.get('/me/saved', (req, res) => {
     const roomOverlay = roomKey
       ? `<img class="card-room" src="/designs/room/design_${d.id}_${roomKey}.png" loading="lazy" alt="" aria-hidden="true">`
       : '';
+    const meta = savedMetaById.get(d.id) || {};
+    const rel = _meSavedRelTime(meta.saved_at);
+    const absISO = meta.saved_at ? new Date(meta.saved_at).toISOString() : '';
+    const savedBadge = rel
+      ? `<span class="saved-when" title="Saved ${_escTrade(absISO)}" style="position:absolute;top:8px;left:8px;background:rgba(15,14,12,.78);color:#e8e2d6;border-radius:4px;padding:3px 7px;font:11px var(--sans,system-ui);letter-spacing:.02em;pointer-events:none">Saved ${_escTrade(rel)}</span>`
+      : '';
     return `
     <div class="saved-card-wrap" data-design-id="${d.id}" style="position:relative">
       <a href="/design/${d.id}" class="design-card${roomKey ? ' has-room' : ''}" aria-label="${_escTrade(d.title || ('Design #' + d.id))}">
@@ -1159,6 +1186,7 @@ app.get('/me/saved', (req, res) => {
           <span class="color-dot" style="background:${_escTrade(d.dominant_hex || '#ccc')}" aria-hidden="true"></span>
         </div>
       </a>
+      ${savedBadge}
       <button type="button" class="btn-remove-saved" data-id="${d.id}" title="Remove from saved"
               style="position:absolute;top:8px;right:8px;background:rgba(15,14,12,.85);color:#e8e2d6;border:0;border-radius:4px;padding:5px 9px;font:11px var(--sans,system-ui);cursor:pointer;letter-spacing:.04em;text-transform:uppercase">
         Remove

← 69c2529 drunk-animals: hero block on /designs?cat=drunk-animals + gh  ·  back to Wallco Ai  ·  /compare: ETag + 304 keyed on (catalog lastmod, view, ordere 73f6770 →