← back to Marketing Command Center
vendors panel: '👥 My vendor contacts (N)' button + /contacts + /contacts/sheet routes (internal contact sheet in the command center; hidden if no data)
f5354e1eca233b2687f5e186566f6e6db6ffc56a · 2026-08-31 13:28:20 -0700 · Steve Abrams
Files touched
M modules/vendors/index.jsM public/panels/vendors.htmlM public/panels/vendors.js
Diff
commit f5354e1eca233b2687f5e186566f6e6db6ffc56a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 31 13:28:20 2026 -0700
vendors panel: '👥 My vendor contacts (N)' button + /contacts + /contacts/sheet routes (internal contact sheet in the command center; hidden if no data)
---
modules/vendors/index.js | 14 ++++++++++++++
public/panels/vendors.html | 9 ++++++---
public/panels/vendors.js | 12 ++++++++++++
3 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/modules/vendors/index.js b/modules/vendors/index.js
index 18a984a..fecdc07 100644
--- a/modules/vendors/index.js
+++ b/modules/vendors/index.js
@@ -302,5 +302,19 @@ module.exports = {
}
res.json({ ok: true, harvested: ok, failed, total: targets.length });
});
+
+ // GET /contacts — "who I know inside each DW vendor" (LinkedIn connections ×
+ // vendor_registry, built by scripts/build-vendor-contact-sheet.mjs). INTERNAL/PII;
+ // this panel is Basic-Auth gated. Reference only — never a public surface.
+ const LI_CONTACTS_JSON = path.join(__dirname, '..', '..', 'data', 'vendor-contacts.json');
+ const LI_CONTACTS_HTML = path.join(__dirname, '..', '..', 'data', 'vendor-contacts.html');
+ router.get('/contacts', (_req, res) => {
+ try { res.json(JSON.parse(fs.readFileSync(LI_CONTACTS_JSON, 'utf8'))); }
+ catch { res.json({ vendors: 0, totalContacts: 0, rows: [], note: 'Run scripts/build-vendor-contact-sheet.mjs first.' }); }
+ });
+ router.get('/contacts/sheet', (_req, res) => {
+ try { res.type('html').send(fs.readFileSync(LI_CONTACTS_HTML, 'utf8')); }
+ catch { res.type('html').send('<p style="font:16px sans-serif;margin:40px">No contact sheet yet — run <code>scripts/build-vendor-contact-sheet.mjs</code>.</p>'); }
+ });
},
};
diff --git a/public/panels/vendors.html b/public/panels/vendors.html
index 47b6f5b..e2229e1 100644
--- a/public/panels/vendors.html
+++ b/public/panels/vendors.html
@@ -36,9 +36,12 @@
<div class="card" id="vend-li-card">
<div class="row" style="justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap">
<h2 style="margin:0">Vendor brands · LinkedIn</h2>
- <div style="display:flex;flex-direction:column;align-items:flex-end;gap:3px">
- <button id="vp-li-harvest" class="btn amp-strong" title="Public Open-Graph pull (thumbnail + text) of every mapped vendor LinkedIn company page — no API, no login">⬇ Harvest ALL LinkedIn pages</button>
- <small class="muted" id="vp-li-when"></small>
+ <div style="display:flex;align-items:center;gap:8px">
+ <a id="vp-li-contacts" class="btn" target="_blank" rel="noopener" href="/api/vendors/contacts/sheet" title="Who I know inside each DW vendor — from my LinkedIn connections (internal)" style="text-decoration:none;display:none">👥 My vendor contacts <span id="vp-li-contacts-n"></span></a>
+ <div style="display:flex;flex-direction:column;align-items:flex-end;gap:3px">
+ <button id="vp-li-harvest" class="btn amp-strong" title="Public Open-Graph pull (thumbnail + text) of every mapped vendor LinkedIn company page — no API, no login">⬇ Harvest ALL LinkedIn pages</button>
+ <small class="muted" id="vp-li-when"></small>
+ </div>
</div>
</div>
<div class="muted" style="margin:8px 0 4px">Vendor LinkedIn <b>company pages</b> (B2B/trade). Public Open-Graph pull only — <b>no LinkedIn API, no login</b>. <b>Reference + attribution-amplify only</b>: browse, copy the text, share with a link back to the vendor. <span style="color:#8a6d2f">✓ verified</span> = slug confirmed against the real company page; <span style="color:#a1341f">? unverified</span> = best guess, confirm before trusting.</div>
diff --git a/public/panels/vendors.js b/public/panels/vendors.js
index c1721ea..db0d344 100644
--- a/public/panels/vendors.js
+++ b/public/panels/vendors.js
@@ -17,6 +17,18 @@ window.MCC_PANELS['vendors'] = {
};
setStats();
+ // "My vendor contacts" — reveal the button + count if the internal contact sheet exists.
+ (async () => {
+ try {
+ const c = await (await fetch('/api/vendors/contacts')).json();
+ if (c && c.totalContacts > 0) {
+ const a = root.querySelector('#vp-li-contacts');
+ root.querySelector('#vp-li-contacts-n').textContent = `(${c.totalContacts})`;
+ if (a) a.style.display = '';
+ }
+ } catch { /* no sheet yet — button stays hidden */ }
+ })();
+
// Owned · DW Fleet — all 35 DW-OWNED Instagram accounts from the canonical
// source (window.MCC_ACCOUNTS), each showing its LAST 6 posts inline (thumb +
// title + date · type), mirroring the external-vendor rows. The owned posts come
← 2e93a4c vendors: strict LinkedIn-connection x vendor_registry contac
·
back to Marketing Command Center
·
auto-data-snapshot: 2026-08-31T13:51:21 (1 data files) — pub 67ef2f1 →