← back to NationalPaperHangers

views/public/map.ejs

193 lines

<%- include('../partials/head', { title }) %>
<link rel="stylesheet" href="/vendor/leaflet/leaflet.css">
<style>
  .near-me-page { max-width: 1200px; margin: 0 auto; padding: 32px 24px; }
  .near-me-head h1 { font-size: 32px; letter-spacing: 0.005em; margin: 0 0 8px; }
  .near-me-head p.lede { margin: 0 0 20px; max-width: 560px; line-height: 1.55; font-size: 15px; }
  .near-me-form {
    display: flex; gap: 10px; flex-wrap: wrap; margin: 0 0 24px;
    padding: 16px; border: 1px solid var(--border, #ddd); border-radius: 8px;
    background: var(--bg-subtle, #faf9f7);
  }
  .near-me-form label { flex: 1; min-width: 280px; font-size: 13px; color: #666; }
  .near-me-form input[type="text"] {
    width: 100%; padding: 12px 14px; font-size: 15px;
    border: 1px solid var(--border, #ddd); border-radius: 6px;
    margin-top: 4px;
  }
  .near-me-form button { padding: 12px 22px; font-size: 15px; }
  .near-me-geo-btn {
    background: transparent; border: 1px solid var(--border, #ddd);
    color: inherit; cursor: pointer; padding: 12px 18px;
    border-radius: 6px; font-size: 14px;
    margin-top: 19px;
  }
  .near-me-layout { display: grid; grid-template-columns: 1fr 1.2fr; gap: 24px; align-items: start; }
  @media (max-width: 800px) { .near-me-layout { grid-template-columns: 1fr; } }
  #near-me-map { height: 520px; border-radius: 8px; overflow: hidden; border: 1px solid var(--border, #ddd); }
  .results-pane h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.08em; color: #999; margin: 0 0 12px; }
  .results-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 12px; max-height: 520px; overflow-y: auto; }
  .result-card { padding: 14px 16px; border: 1px solid var(--border, #ddd); border-radius: 6px; transition: border-color 0.15s; }
  .result-card:hover { border-color: #0e0e0e; }
  .result-card .name { font-weight: 600; font-size: 15px; }
  .result-card .meta { font-size: 13px; color: #666; margin-top: 4px; }
  .result-card .reasons { font-size: 12px; color: #15803d; margin-top: 6px; }
  .tier-badge { display: inline-block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; padding: 2px 8px; border-radius: 10px; vertical-align: middle; margin-left: 6px; }
  .tier-signature, .tier-enterprise { background: #0e0e0e; color: #fff; }
  .tier-pro { background: #f3f4f6; color: #374151; }
  .empty-state { padding: 32px; border: 1px dashed var(--border, #ddd); border-radius: 8px; text-align: center; color: #666; }
</style>
<%- include('../partials/header') %>

<section class="near-me-page">
  <header class="near-me-head">
    <h1>Who services my area?</h1>
    <p class="lede">Enter your address and we'll show every wallcovering installer who services your location — sorted by distance. <%= stats.pinned %>+ studios are mapped.</p>
  </header>

  <form method="get" action="/map" class="near-me-form" autocomplete="off">
    <label>
      Your address, city, or ZIP
      <input type="text" name="address" value="<%= address || '' %>" required
             placeholder="e.g. 90210, or 123 Main St, Brooklyn NY"
             autofocus>
    </label>
    <button type="submit" class="btn btn-primary">Show installers near me →</button>
    <button type="button" class="near-me-geo-btn" id="useLocBtn" title="Use my browser-detected location">📍 Use my location</button>
  </form>

  <% if (geocodeFailed) { %>
    <div class="callout" style="padding:14px 18px;border:1px solid #fca5a5;background:#fef2f2;border-radius:6px;margin-bottom:24px">
      <strong>Couldn't find that address.</strong> Try adding the city + state (e.g. "Brooklyn, NY") or a ZIP code.
    </div>
  <% } %>

  <% if (location) { %>
    <div class="near-me-layout">
      <div>
        <h2 style="margin:0 0 8px;font-size:14px;text-transform:uppercase;letter-spacing:0.08em;color:#999">Your area</h2>
        <p style="margin:0 0 12px;font-size:14px"><%= location.display_name %></p>
        <div id="near-me-map" data-lat="<%= location.lat %>" data-lng="<%= location.lng %>"
             data-installers='<%- JSON.stringify(results.map(r => ({ id:r.id, slug:r.slug, name:r.business_name, lat:r.lat, lng:r.lng, distance:r.distance_miles, tier:r.tier }))) %>'></div>
      </div>

      <div class="results-pane">
        <h2><%= results.length %> studio<%= results.length === 1 ? '' : 's' %> service this area</h2>
        <% if (!results.length) { %>
          <div class="empty-state">
            <p style="margin:0 0 6px"><strong>No installer-marked service coverage for this location.</strong></p>
            <p style="margin:0;font-size:13px">Try a nearby ZIP, or <a href="/find">browse all studios</a> and reach out directly — many will travel for the right project.</p>
          </div>
        <% } else { %>
          <ul class="results-list">
            <% results.forEach(function(r){ %>
              <li class="result-card">
                <a href="/installer/<%= r.slug %>" style="text-decoration:none;color:inherit;display:block">
                  <div>
                    <span class="name"><%= r.business_name %></span>
                    <% if (r.tier === 'signature' || r.tier === 'enterprise') { %>
                      <span class="tier-badge tier-<%= r.tier %>"><%= r.tier %></span>
                    <% } else if (r.tier === 'pro') { %>
                      <span class="tier-badge tier-pro">pro</span>
                    <% } %>
                    <% if (r.verified) { %><span style="font-size:12px;color:#888"> · verified</span><% } %>
                  </div>
                  <div class="meta">
                    <%= r.distance_miles %> mi · <%= r.city %><% if (r.state) { %>, <%= r.state %><% } %>
                    <% if (r.response_time_hours && r.claim_status !== 'unclaimed') { %>
                      · replies in &lt; <%= r.response_time_hours %>h
                    <% } %>
                    <% if (r.verified_brands_count) { %>
                      · <%= r.verified_brands_count %> brand-trained
                    <% } %>
                  </div>
                  <% if (r.match_reasons && r.match_reasons.length) { %>
                    <div class="reasons">✓ <%= r.match_reasons.join(' · ') %></div>
                  <% } %>
                </a>
              </li>
            <% }); %>
          </ul>
        <% } %>
      </div>
    </div>
  <% } %>

</section>

<% if (location) { %>
<script src="/vendor/leaflet/leaflet.js"></script>
<script>
(function(){
  var mapEl = document.getElementById('near-me-map');
  if (!mapEl || !window.L) return;
  var ulat = parseFloat(mapEl.dataset.lat);
  var ulng = parseFloat(mapEl.dataset.lng);
  var insts = [];
  try { insts = JSON.parse(mapEl.dataset.installers || '[]'); } catch(e) {}

  var maxDist = insts.length ? Math.max.apply(null, insts.map(function(i){return i.distance;})) : 5;
  var zoom = maxDist < 5 ? 12 : maxDist < 20 ? 10 : maxDist < 60 ? 9 : 8;

  var map = L.map(mapEl).setView([ulat, ulng], zoom);
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 18,
    attribution: '© OpenStreetMap'
  }).addTo(map);

  L.circleMarker([ulat, ulng], {
    radius: 9, color: '#dc2626', weight: 3, fillColor: '#fff', fillOpacity: 1
  }).addTo(map).bindPopup('You');

  insts.forEach(function(i){
    var tierColor = i.tier === 'signature' || i.tier === 'enterprise' ? '#0e0e0e'
                  : i.tier === 'pro' ? '#374151'
                  : '#9ca3af';
    L.circleMarker([i.lat, i.lng], {
      radius: 7, color: tierColor, weight: 2, fillColor: '#fff', fillOpacity: 1
    }).addTo(map).bindPopup(
      '<a href="/installer/' + i.slug + '" style="color:inherit;font-weight:600">' + i.name + '</a><br>' +
      '<span style="font-size:12px;color:#666">' + i.distance + ' mi away</span>'
    );
  });

  if (insts.length) {
    var pts = insts.map(function(i){ return [i.lat, i.lng]; }).concat([[ulat, ulng]]);
    map.fitBounds(L.latLngBounds(pts), { padding: [40, 40], maxZoom: 12 });
  }
})();
</script>
<% } %>

<script>
(function(){
  var btn = document.getElementById('useLocBtn');
  var input = document.querySelector('input[name="address"]');
  if (!btn || !navigator.geolocation) { if (btn) btn.style.display = 'none'; return; }
  btn.addEventListener('click', function(){
    btn.textContent = '📍 Finding…';
    btn.disabled = true;
    navigator.geolocation.getCurrentPosition(function(pos){
      var url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=' +
                pos.coords.latitude + '&lon=' + pos.coords.longitude;
      fetch(url, { headers: { 'Accept': 'application/json' } })
        .then(function(r){ return r.json(); })
        .then(function(d){
          input.value = (d && d.display_name) || (pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5));
          input.form.submit();
        })
        .catch(function(){
          input.value = pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5);
          input.form.submit();
        });
    }, function(err){
      btn.textContent = '📍 Use my location';
      btn.disabled = false;
      alert('Could not get your location: ' + err.message);
    }, { timeout: 8000 });
  });
})();
</script>

<%- include('../partials/footer') %>