← back to Sublease Agentabrams
Individual broker profile pages (/brokers/:id + /api/brokers/:id); directory cards link to them
df5c9ac28d2a1b721e6404cda0e94bcf7e57e0eb · 2026-07-20 13:58:48 -0700 · Steve
Files touched
A public/broker-profile.htmlM public/index.htmlM server.js
Diff
commit df5c9ac28d2a1b721e6404cda0e94bcf7e57e0eb
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 20 13:58:48 2026 -0700
Individual broker profile pages (/brokers/:id + /api/brokers/:id); directory cards link to them
---
public/broker-profile.html | 46 ++++++++++++++++++++++++++++++++++++++++++++++
public/index.html | 2 +-
server.js | 13 +++++++++++++
3 files changed, 60 insertions(+), 1 deletion(-)
diff --git a/public/broker-profile.html b/public/broker-profile.html
new file mode 100644
index 0000000..4c04434
--- /dev/null
+++ b/public/broker-profile.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<html lang="en"><head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Broker · Sublease Marketplace</title>
+<style>
+ :root{--ink:#14181d;--mut:#6b7480;--line:#e7e9ee;--bg:#fbfbfc;--accent:#1a5f3c}
+ *{box-sizing:border-box}body{margin:0;font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;color:var(--ink);background:var(--bg)}
+ a{color:var(--accent);text-decoration:none}.wrap{max-width:760px;margin:0 auto;padding:0 22px}
+ header.top{border-bottom:1px solid var(--line);background:#fff}.top .wrap{display:flex;align-items:center;height:60px}
+ .top a{color:var(--mut);font-size:14px}
+ .head{background:linear-gradient(135deg,#14181d,#233041);color:#fff;padding:38px 0}
+ .head h1{margin:0 0 4px;font-size:26px}.head .firm{color:#c6cdd6;font-size:15px}
+ .head .tags{margin-top:14px;display:flex;gap:8px;flex-wrap:wrap}
+ .badge{font-size:11px;padding:3px 9px;border-radius:20px;background:rgba(255,255,255,.14);color:#fff;text-transform:uppercase;letter-spacing:.05em}
+ .card{background:#fff;border:1px solid var(--line);border-radius:14px;padding:22px;margin:24px 0}
+ .row{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid var(--line);font-size:14.5px}
+ .row:last-child{border-bottom:0}.row .k{width:120px;color:var(--mut);flex:none}
+ .cta{display:inline-block;margin-top:8px;background:var(--accent);color:#fff;padding:9px 16px;border-radius:8px;font-weight:600;font-size:14px}
+ .muted{color:var(--mut)}
+</style></head><body>
+<header class="top"><div class="wrap"><a href="/#brokers">← Broker directory</a></div></header>
+<div class="head"><div class="wrap" id="head"></div></div>
+<div class="wrap"><div class="card" id="body"></div>
+ <p class="muted" style="font-size:12.5px">Broker contact data aggregated from public commercial-real-estate sources (CRUnifiedDB). Verify before transacting.</p>
+</div>
+<script>
+const id=location.pathname.split('/').pop();
+(async()=>{
+ const d=await fetch('/api/brokers/'+id).then(r=>r.json()).catch(()=>({}));
+ if(!d.broker){document.getElementById('head').innerHTML='<h1>Broker not found</h1>';document.getElementById('body').innerHTML='';return;}
+ const b=d.broker;
+ document.getElementById('head').innerHTML=`<h1>${b.name}</h1><div class="firm">${b.firm||''}</div>
+ <div class="tags">${b.agent_type?`<span class="badge">${b.agent_type}</span>`:''}
+ ${b.total_assets?`<span class="badge">${b.total_assets} listings tracked</span>`:''}</div>`;
+ const row=(k,v)=>v?`<div class="row"><span class="k">${k}</span><span>${v}</span></div>`:'';
+ document.getElementById('body').innerHTML=
+ row('Firm',b.firm)+
+ row('Phone',b.phone?`<a href="tel:${b.phone}">${b.phone}</a>`:'')+
+ row('Email',b.email?`<a href="mailto:${b.email}">${b.email}</a>`:'')+
+ row('Website',b.website?`<a href="${b.website}" target="_blank" rel="noopener">${b.website}</a>`:'')+
+ row('LinkedIn',b.linkedin?`<a href="${b.linkedin}" target="_blank" rel="noopener">profile ↗</a>`:'')+
+ row('Office',b.office_addr)+
+ row('Specialties',b.specialties)+
+ (b.email?`<a class="cta" href="mailto:${b.email}">Contact ${b.name.split(' ')[0]}</a>`:'');
+})();
+</script></body></html>
diff --git a/public/index.html b/public/index.html
index ea3c76b..01fad3d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -195,7 +195,7 @@ async function loadBrokers(qs){
const d=await fetch('/api/brokers?limit=48&q='+encodeURIComponent(qs)).then(r=>r.json());
$('#bkNote').textContent=`${d.count.toLocaleString()} brokers`;
$('#bgrid').innerHTML=d.brokers.length?d.brokers.map(b=>`<div class="bk">
- <div class="n">${b.name}</div><div class="f">${b.firm||''}</div>
+ <div class="n"><a href="/brokers/${b.id}" style="color:inherit">${b.name}</a></div><div class="f">${b.firm||''}</div>
${b.phone?`<div class="row">☎ ${b.phone}</div>`:''}
${b.email?`<div class="row">✉ <a href="mailto:${b.email}">${b.email}</a></div>`:''}
${b.office_addr?`<div class="row" style="color:var(--mut)">${b.office_addr}</div>`:''}
diff --git a/server.js b/server.js
index 858362a..bec1342 100644
--- a/server.js
+++ b/server.js
@@ -129,6 +129,18 @@ app.get('/api/brokers', async (req, res) => {
} catch (e) { res.status(500).json({ error: String(e) }); }
});
+app.get('/api/brokers/:id', async (req, res) => {
+ try {
+ const id = +req.params.id;
+ if (USE_SNAPSHOT) {
+ const b = (snap.brokers || []).find(x => x.id === id);
+ return b ? res.json({ broker: b }) : res.status(404).json({ error: 'not found' });
+ }
+ const [b] = await q(`SELECT * FROM brokers WHERE id=$1`, [id]);
+ return b ? res.json({ broker: b }) : res.status(404).json({ error: 'not found' });
+ } catch (e) { res.status(500).json({ error: String(e) }); }
+});
+
app.get('/api/stats', async (_q, res) => {
try {
if (USE_SNAPSHOT) return res.json({ ...snap.stats, recent_runs: snap.recent_runs });
@@ -145,5 +157,6 @@ app.get('/api/stats', async (_q, res) => {
});
app.get('/broker/:slug', (_q, r) => r.sendFile(path.join(PUB, 'broker.html')));
+app.get('/brokers/:id', (_q, r) => r.sendFile(path.join(PUB, 'broker-profile.html')));
app.use(express.static(PUB, { extensions: ['html'] }));
app.listen(PORT, () => console.log(`sublease-agentabrams on ${PORT} (${USE_SNAPSHOT ? 'snapshot' : 'CRUnifiedDB'})`));
← ae3f692 Outreach drafts: feed-request (5 brokers) + financing-partne
·
back to Sublease Agentabrams
·
Map marker clustering (leaflet.markercluster) — handles the 0669d4c →