[object Object]

← back to Ventura Claw Leads

yolo tick 13: /map pin hover preview + /admin/billing recent activity feed

65ee0ecf24729fb170219aca641fd2daad656021 · 2026-05-06 19:51:52 -0700 · Steve Abrams

/MAP HOVER PREVIEW
- views/public/map.ejs: every Leaflet marker now has bindTooltip with the
  business name + vertical kicker + neighborhood. Renders on hover (no
  click needed). Tooltip themed to match the site (var(--bg)/--fg/--border)
  with a subtle drop shadow.
- Tooltip arrow stripped via .leaflet-tooltip.pin-hover::before {display:none}
  for a cleaner look against light + dark themes.
- popup (click) still works with the full action buttons.

/ADMIN/BILLING ACTIVITY FEED
- routes/admin.js: GET /admin/billing now also queries subscription_events
  filtered by payload->>'metadata'->>'business_id' = current business id,
  newest 20. JSON-path filter is the right approach since the same Stripe
  account fires events to all sister projects (NPH, lawyer, etc.).
- views/admin/billing.ejs: 'Recent activity' table above 'Manage
  subscription'. Per-row: timestamp, event_type (mono), detail (amount in
  USD if amount_paid/amount_due present, else status). Status='active'
  renders green.

46/46 tests still pass. /map and /admin/billing both 200/302 on prod.

Files touched

Diff

commit 65ee0ecf24729fb170219aca641fd2daad656021
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 19:51:52 2026 -0700

    yolo tick 13: /map pin hover preview + /admin/billing recent activity feed
    
    /MAP HOVER PREVIEW
    - views/public/map.ejs: every Leaflet marker now has bindTooltip with the
      business name + vertical kicker + neighborhood. Renders on hover (no
      click needed). Tooltip themed to match the site (var(--bg)/--fg/--border)
      with a subtle drop shadow.
    - Tooltip arrow stripped via .leaflet-tooltip.pin-hover::before {display:none}
      for a cleaner look against light + dark themes.
    - popup (click) still works with the full action buttons.
    
    /ADMIN/BILLING ACTIVITY FEED
    - routes/admin.js: GET /admin/billing now also queries subscription_events
      filtered by payload->>'metadata'->>'business_id' = current business id,
      newest 20. JSON-path filter is the right approach since the same Stripe
      account fires events to all sister projects (NPH, lawyer, etc.).
    - views/admin/billing.ejs: 'Recent activity' table above 'Manage
      subscription'. Per-row: timestamp, event_type (mono), detail (amount in
      USD if amount_paid/amount_due present, else status). Status='active'
      renders green.
    
    46/46 tests still pass. /map and /admin/billing both 200/302 on prod.
---
 routes/admin.js         | 28 +++++++++++++++++++++-------
 views/admin/billing.ejs | 29 +++++++++++++++++++++++++++++
 views/public/map.ejs    | 17 +++++++++++++++++
 3 files changed, 67 insertions(+), 7 deletions(-)

diff --git a/routes/admin.js b/routes/admin.js
index d5bf018..98f06c9 100644
--- a/routes/admin.js
+++ b/routes/admin.js
@@ -306,13 +306,27 @@ router.post('/team/:id(\\d+)/remove', async (req, res, next) => {
 function escapeHtml(s) { return String(s || '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
 
 // ─── Billing ───────────────────────────────────────────────────────────
-router.get('/billing', (req, res) => {
-  res.render('admin/billing', {
-    title: 'Billing · Ventura Claw',
-    stripeLive: stripe.isLive(),
-    tiers: stripe.VCL_TIERS,
-    flash: req.query
-  });
+router.get('/billing', async (req, res, next) => {
+  try {
+    // Recent Stripe events for THIS business — pulled from the audited
+    // subscription_events table, filtered by metadata.business_id matching
+    // the current business so a multi-tenant Stripe account stays clean.
+    const events = await db.many(`
+      SELECT id, stripe_event_id, event_type, payload, created_at
+        FROM subscription_events
+       WHERE (payload -> 'data' -> 'object' -> 'metadata' ->> 'business_id') = $1
+       ORDER BY created_at DESC
+       LIMIT 20
+    `, [String(res.locals.currentBusiness.id)]);
+
+    res.render('admin/billing', {
+      title: 'Billing · Ventura Claw',
+      stripeLive: stripe.isLive(),
+      tiers: stripe.VCL_TIERS,
+      events,
+      flash: req.query
+    });
+  } catch (err) { next(err); }
 });
 
 router.post('/billing/checkout', async (req, res, next) => {
diff --git a/views/admin/billing.ejs b/views/admin/billing.ejs
index 2cf8716..0285cf3 100644
--- a/views/admin/billing.ejs
+++ b/views/admin/billing.ejs
@@ -68,6 +68,35 @@
     <% }); %>
   </div>
 
+  <% if (typeof events !== 'undefined' && events && events.length > 0) { %>
+    <h2 style="margin-top:48px">Recent activity</h2>
+    <p class="muted" style="margin:0 0 16px;font-size:13px">Stripe events for your subscription, newest first.</p>
+    <table style="width:100%;border-collapse:collapse;font-size:13px">
+      <thead>
+        <tr style="border-bottom:1px solid var(--border);text-align:left">
+          <th style="padding:8px;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted);font-weight:500">When</th>
+          <th style="padding:8px;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted);font-weight:500">Event</th>
+          <th style="padding:8px;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted);font-weight:500">Detail</th>
+        </tr>
+      </thead>
+      <tbody>
+        <% events.forEach(function(ev){
+          var obj = (ev.payload && ev.payload.data && ev.payload.data.object) || {};
+          var detail = '';
+          if (obj.amount_paid != null) detail = '$' + (obj.amount_paid / 100).toFixed(2);
+          else if (obj.amount_due != null) detail = '$' + (obj.amount_due / 100).toFixed(2);
+          else if (obj.status) detail = obj.status;
+        %>
+          <tr style="border-bottom:1px solid var(--border)">
+            <td style="padding:10px 8px;color:var(--fg-muted);white-space:nowrap"><%= new Date(ev.created_at).toLocaleString('en-US',{ month:'short', day:'numeric', hour:'numeric', minute:'2-digit' }) %></td>
+            <td style="padding:10px 8px;font-family:var(--mono,monospace);font-size:12px"><%= ev.event_type %></td>
+            <td style="padding:10px 8px;color:<%= obj.status === 'active' ? '#16a34a' : 'var(--fg-muted)' %>"><%= detail || '—' %></td>
+          </tr>
+        <% }); %>
+      </tbody>
+    </table>
+  <% } %>
+
   <% if (currentBusiness.stripe_customer_id) { %>
     <h2 style="margin-top:48px">Manage subscription</h2>
     <p>Update your payment method, see invoices, or cancel from the secure Stripe portal.</p>
diff --git a/views/public/map.ejs b/views/public/map.ejs
index 775bfd0..39b3f6c 100644
--- a/views/public/map.ejs
+++ b/views/public/map.ejs
@@ -31,6 +31,14 @@
   .pin-popup .pin-actions a.primary { background: #0e0e0e; color: #fff; }
   .pin-popup .pin-actions a.ghost { background: transparent; color: #0e0e0e; border: 1px solid #ccc; }
   .pin-popup .pin-headline { font-size: 0.78rem; color: #444; margin: 0 0 4px; line-height: 1.4; }
+  .leaflet-tooltip.pin-hover {
+    background: var(--bg, #fff); color: var(--fg, #0e0e0e);
+    border: 1px solid var(--border, #d8d2c0); border-radius: 4px;
+    padding: 6px 10px; font-family: var(--sans, -apple-system, sans-serif);
+    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+    font-size: 13px; line-height: 1.35;
+  }
+  .leaflet-tooltip.pin-hover::before { display: none; }
 </style>
 
 <section class="map-page">
@@ -127,6 +135,15 @@
         if (typeof b.latitude !== 'number' || typeof b.longitude !== 'number') return;
         var m = L.marker([b.latitude, b.longitude], { icon: pinIcon(b) });
         m.bindPopup(popupHtml(b));
+        // Hover preview — quick name + vertical without forcing a click.
+        // 'sticky' keeps it pinned to the cursor on the marker; auto-hide on leave.
+        var verticalLabel = (b.vertical || '').replace(/_/g, ' ');
+        m.bindTooltip(
+          '<strong>' + escHtml(b.business_name) + '</strong>'
+            + '<br><span style="font-size:11px;color:#b8860b;letter-spacing:0.06em;text-transform:uppercase">' + escHtml(verticalLabel) + '</span>'
+            + (b.neighborhood ? '<br><span style="font-size:11px;color:#666">' + escHtml(b.neighborhood) + '</span>' : ''),
+          { direction: 'top', offset: [0, -8], opacity: 0.95, sticky: false, className: 'pin-hover' }
+        );
         cluster.addLayer(m);
         allMarkers.push({ data: b, marker: m });
       });

← abf7235 yolo tick 12: 30-day lead-trend sparkline on admin dashboard  ·  back to Ventura Claw Leads  ·  yolo tick 14: /admin/leads search-within-messages 294e68a →