← back to Permit Radar
public/permit.html
297 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Permit Detail — Permit Radar</title>
<style>
:root { --accent: #1d4ed8; --accent-light: #eff6ff; }
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 15px/1.5 -apple-system, Segoe UI, Roboto, sans-serif; color: #1a1a1a; background: #f3f4f6; }
.sample-banner { background: #fef3c7; border-bottom: 2px solid #f59e0b; padding: 8px 20px; font-size: 13px; text-align: center; color: #78350f; }
nav { background: #1e293b; color: #fff; padding: 12px 20px; display: flex; align-items: center; gap: 16px; }
.logo { font-size: 18px; font-weight: 800; color: #fff; text-decoration: none; }
.logo span { color: #60a5fa; }
.nav-links { margin-left: auto; display: flex; gap: 16px; }
.nav-links a { color: #cbd5e1; font-size: 13px; text-decoration: none; }
.nav-links a:hover { color: #fff; }
.container { max-width: 800px; margin: 32px auto; padding: 0 20px 40px; }
.back-link { font-size: 13px; color: var(--accent); text-decoration: none; margin-bottom: 16px; display: inline-block; }
.back-link:hover { text-decoration: underline; }
.permit-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; }
.permit-header { background: linear-gradient(135deg, #1e293b 0%, #1d4ed8 100%); color: #fff; padding: 28px; }
.permit-header-top { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.permit-id-label { font-size: 12px; color: #93c5fd; font-weight: 600; letter-spacing: .5px; margin-bottom: 4px; }
.permit-address-h { font-size: 22px; font-weight: 800; margin: 0 0 2px; }
.permit-location { font-size: 14px; color: #bfdbfe; }
.status-chip { font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 99px; white-space: nowrap; margin-top: 4px; }
.status-approved { background: #dcfce7; color: #15803d; }
.status-pending { background: #fef9c3; color: #854d0e; }
.permit-body { padding: 28px; }
.section-title { font-size: 11px; font-weight: 700; color: #94a3b8; letter-spacing: 1px; text-transform: uppercase; margin: 0 0 12px; }
.fields-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 16px; margin-bottom: 24px; }
.field { }
.field-label { font-size: 11px; color: #94a3b8; font-weight: 600; margin-bottom: 2px; }
.field-value { font-size: 15px; font-weight: 600; color: #1e293b; }
.field-value.big { font-size: 20px; color: #15803d; }
.permit-desc { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 16px; font-size: 14px; color: #374151; line-height: 1.6; margin-bottom: 24px; }
.cta-box { background: var(--accent-light); border: 1px solid #bfdbfe; border-radius: 10px; padding: 20px; }
.cta-box h3 { font-size: 16px; margin: 0 0 6px; color: #1e40af; }
.cta-box p { font-size: 13px; color: #3b82f6; margin: 0 0 14px; }
.cta-row { display: flex; gap: 10px; flex-wrap: wrap; }
.btn-lead {
padding: 10px 20px; background: #f59e0b; color: #1a1a1a; border: none;
border-radius: 8px; font: 700 14px inherit; cursor: pointer;
}
.btn-lead:hover { background: #fbbf24; }
.btn-subscribe {
padding: 10px 20px; background: var(--accent); color: #fff; border: none;
border-radius: 8px; font: 700 14px inherit; cursor: pointer; text-decoration: none; display: inline-block;
}
.btn-subscribe:hover { background: #1e40af; }
/* Lead modal */
.modal-bg { display: none; position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 100; align-items: center; justify-content: center; }
.modal-bg.open { display: flex; }
.modal { background: #fff; border-radius: 12px; padding: 28px; width: 100%; max-width: 420px; margin: 20px; position: relative; }
.modal h3 { margin: 0 0 6px; }
.modal p { color: #64748b; font-size: 13px; margin: 0 0 16px; }
.modal-close { position: absolute; top: 14px; right: 16px; font-size: 20px; cursor: pointer; color: #94a3b8; background: none; border: none; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
.form-row.single { grid-template-columns: 1fr; }
label { display: block; font-size: 12px; font-weight: 600; color: #475569; margin-bottom: 4px; }
select, input { width: 100%; padding: 9px 12px; border: 1px solid #d1d5db; border-radius: 6px; font: inherit; font-size: 14px; }
.btn-submit { width: 100%; padding: 12px; background: var(--accent); color: #fff; border: none; border-radius: 8px; font: 700 15px inherit; cursor: pointer; margin-top: 8px; }
.success-msg { background: #dcfce7; color: #15803d; border-radius: 6px; padding: 10px; margin-top: 10px; font-size: 13px; text-align: center; }
.error-msg { background: #fee2e2; color: #991b1b; border-radius: 6px; padding: 10px; margin-top: 10px; font-size: 13px; text-align: center; }
.loading { color: #94a3b8; padding: 60px; text-align: center; }
/* ── SHOPIFY CROSS-SELL (permit detail) ── */
.xsell-strip {
max-width: 800px; margin: 24px auto 0; padding: 0 20px 40px;
}
.xsell-inner {
background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px 24px;
}
.xsell-header {
display: flex; align-items: baseline; gap: 10px; margin-bottom: 14px; flex-wrap: wrap;
}
.xsell-label {
font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
color: #94a3b8; border: 1px solid #e2e8f0; border-radius: 4px; padding: 2px 8px;
}
.xsell-title { font-size: 15px; font-weight: 700; color: #1e293b; margin: 0; }
.xsell-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 10px;
}
.xsell-card {
border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; background: #f8fafc;
text-decoration: none; color: inherit; display: flex; flex-direction: column;
transition: box-shadow .15s;
}
.xsell-card:hover { box-shadow: 0 3px 14px rgba(0,0,0,.08); }
.xsell-img-wrap { aspect-ratio: 1; overflow: hidden; background: #e2e8f0; }
.xsell-img-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.xsell-info { padding: 7px 9px 9px; flex: 1; }
.xsell-product-title { font-size: 11px; font-weight: 600; color: #1e293b; line-height: 1.3;
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.xsell-vendor { font-size: 10px; color: #94a3b8; }
.xsell-price { font-size: 12px; font-weight: 700; color: #15803d; margin-top: 3px; }
.xsell-browse { display: inline-block; margin-top: 12px; font-size: 12px; color: #1d4ed8; font-weight: 600; text-decoration: none; }
.xsell-browse:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="sample-banner">⚠️ <strong>SAMPLE / REPRESENTATIVE DATA</strong> — not from any county portal.</div>
<nav>
<a class="logo" href="/"><span>📡</span> Permit Radar</a>
<div class="nav-links">
<a href="/">Directory</a>
<a href="/#get-alerts">Get Alerts</a>
<a href="/admin">Admin</a>
</div>
</nav>
<div class="container">
<a class="back-link" href="/">← Back to Directory</a>
<div id="content" class="loading">Loading permit…</div>
</div>
<!-- Lead modal -->
<div class="modal-bg" id="lead-modal" onclick="if(event.target===this)closeLead()">
<div class="modal">
<button class="modal-close" onclick="closeLead()">×</button>
<h3>Request This Lead</h3>
<p id="modal-permit-label">—</p>
<form id="lead-form" onsubmit="submitLead(event)">
<input type="hidden" id="modal-permit-id" name="permit_id">
<div class="form-row">
<div>
<label>Your Name</label>
<input type="text" name="name" placeholder="Jane Smith">
</div>
<div>
<label>Trade Type *</label>
<select name="trade_type" required>
<option value="">Select…</option>
<option value="general_contractor">General Contractor</option>
<option value="kitchen_bath">Kitchen & Bath</option>
<option value="interior_designer">Interior Designer</option>
<option value="roofing">Roofer</option>
<option value="flooring">Flooring</option>
<option value="hvac">HVAC</option>
<option value="home_goods_vendor">Home Goods Vendor</option>
<option value="wallcovering_vendor">Wallcovering Vendor</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="form-row">
<div>
<label>Email *</label>
<input type="email" name="email" required placeholder="you@company.com">
</div>
<div>
<label>Phone</label>
<input type="tel" name="phone" placeholder="555-555-5555">
</div>
</div>
<div class="form-row single">
<div>
<label>Message</label>
<input type="text" name="message" placeholder="Services you offer">
</div>
</div>
<button type="submit" class="btn-submit">Request Lead →</button>
<div id="lead-msg"></div>
</form>
</div>
</div>
<script>
'use strict';
const esc = s => (s||'').replace(/[&<>"]/g, c => ({'&':'&','<':'<','>':'>','"':'"'}[c]));
const fmtVal = v => v ? '$' + Number(v).toLocaleString() : '—';
const fmtDate = d => d ? new Date(d + 'T12:00:00').toLocaleDateString('en-US', { month:'long', day:'numeric', year:'numeric' }) : '—';
async function load() {
const id = decodeURIComponent(location.pathname.replace('/permit/', '').replace(/\/$/, ''));
const r = await fetch('/api/permits/' + encodeURIComponent(id));
const content = document.getElementById('content');
if (!r.ok) { content.innerHTML = '<p style="color:#ef4444">Permit not found.</p>'; return; }
const { permit: p } = await r.json();
document.title = p.address + ' — Permit Radar';
const statusClass = 'status-' + (p.status || 'pending');
content.innerHTML = `
<div class="permit-card">
<div class="permit-header">
<div class="permit-header-top">
<div>
<div class="permit-id-label">Permit ${esc(p.id)}</div>
<div class="permit-address-h">${esc(p.address)}</div>
<div class="permit-location">${esc(p.city)}, ${esc(p.state)} ${esc(p.zip)} — ${esc(p.jurisdiction)}</div>
</div>
<span class="status-chip ${statusClass}">${esc(p.status)}</span>
</div>
</div>
<div class="permit-body">
<div class="section-title">Permit Details</div>
<div class="fields-grid">
<div class="field"><div class="field-label">Date Filed</div><div class="field-value">${fmtDate(p.date_filed)}</div></div>
<div class="field"><div class="field-label">Permit Type</div><div class="field-value">${esc(p.permit_type_label || p.permit_type)}</div></div>
<div class="field"><div class="field-label">Valuation</div><div class="field-value big">${fmtVal(p.valuation)}</div></div>
<div class="field"><div class="field-label">Sq Ft Affected</div><div class="field-value">${p.sqft_affected ? p.sqft_affected.toLocaleString() + ' sq ft' : '—'}</div></div>
<div class="field"><div class="field-label">Status</div><div class="field-value">${esc(p.status)}</div></div>
<div class="field"><div class="field-label">Bedrooms (home)</div><div class="field-value">${p.bedrooms || '—'}</div></div>
</div>
${p.contractor ? `
<div class="section-title">Contractor on Record</div>
<div class="fields-grid">
<div class="field"><div class="field-label">Contractor</div><div class="field-value">${esc(p.contractor)}</div></div>
<div class="field"><div class="field-label">License</div><div class="field-value">${esc(p.contractor_license || '—')}</div></div>
</div>` : ''}
<div class="section-title">Description</div>
<div class="permit-desc">${esc(p.description)}</div>
<div class="cta-box">
<h3>Reach this homeowner at the renovation moment</h3>
<p>Request this lead directly, or subscribe for alerts on every permit like it in your area.</p>
<div class="cta-row">
<button class="btn-lead" onclick="openLead('${esc(p.id)}', '${esc(p.address + ', ' + p.city)}')">Request This Lead</button>
<a class="btn-subscribe" href="/#get-alerts">Get Area Alerts →</a>
</div>
</div>
</div>
</div>`;
}
load();
function openLead(id, label) {
document.getElementById('modal-permit-id').value = id;
document.getElementById('modal-permit-label').textContent = label;
document.getElementById('lead-msg').innerHTML = '';
document.getElementById('lead-form').reset();
document.getElementById('modal-permit-id').value = id;
document.getElementById('lead-modal').classList.add('open');
}
function closeLead() { document.getElementById('lead-modal').classList.remove('open'); }
async function submitLead(e) {
e.preventDefault();
const body = Object.fromEntries(new FormData(e.target));
const msg = document.getElementById('lead-msg');
msg.innerHTML = '';
const r = await fetch('/api/lead-request', {
method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body)
});
const d = await r.json();
if (d.ok) { msg.innerHTML = '<div class="success-msg">✓ Lead request submitted! ID: ' + esc(d.id) + '</div>'; setTimeout(closeLead, 2500); }
else { msg.innerHTML = '<div class="error-msg">Error: ' + esc(d.error) + '</div>'; }
}
// ── Shopify cross-sell (permit detail) ───────────────────────────────────────
(async function loadXsell() {
try {
const r = await fetch('/api/shopify-crosssell');
const d = await r.json();
if (!d.products || !d.products.length) return;
const strip = document.createElement('div');
strip.className = 'xsell-strip';
strip.innerHTML = `
<div class="xsell-inner">
<div class="xsell-header">
<span class="xsell-label">Trade cross-sell — links to live DW store</span>
<p class="xsell-title">Outfit the remodel — shop Designer Wallcoverings</p>
</div>
<div class="xsell-grid">
${d.products.map(p => `
<a class="xsell-card" href="${esc(p.url)}" target="_blank" rel="noopener noreferrer" title="${esc(p.title)}">
<div class="xsell-img-wrap"><img src="${esc(p.image)}" alt="${esc(p.title)}" loading="lazy"></div>
<div class="xsell-info">
<div class="xsell-product-title">${esc(p.title)}</div>
<div class="xsell-vendor">${esc(p.vendor)}</div>
<div class="xsell-price">${p.price ? '$' + Number(p.price).toFixed(2) : ''}</div>
</div>
</a>`).join('')}
</div>
<a class="xsell-browse" href="https://www.designerwallcoverings.com" target="_blank" rel="noopener noreferrer">
Browse full collection at designerwallcoverings.com →
</a>
</div>`;
// Insert after .container (the permit detail area)
const container = document.querySelector('.container');
if (container) container.parentNode.insertBefore(strip, container.nextSibling);
} catch (e) {}
})();
</script>
</body>
</html>