← back to Ventura Claw Leads

views/public/business.ejs

254 lines

<%- include('../partials/head', {
  title,
  ogImage: typeof ogImage !== 'undefined' && ogImage ? ogImage : null
}) %>
<%- include('../partials/header') %>

<%
  function _safeUrl(u) {
    if (!u) return null;
    try { var x = new URL(u.startsWith('http') ? u : 'https://' + u); return (x.protocol === 'http:' || x.protocol === 'https:') ? x.toString() : null; }
    catch(e){ return null; }
  }
  var _site = _safeUrl(business.website);
  var _addressOneline = [business.street, business.neighborhood || business.city, business.state, business.zip].filter(Boolean).join(', ');

  // JSON-LD LocalBusiness — Google search rich-results + voice-search surface.
  // We always emit type=LocalBusiness rather than mapping to subtypes (Restaurant,
  // BeautySalon, etc.) because subtype mapping requires careful per-vertical
  // schema.org alignment; LocalBusiness is the safe parent for all 8 verticals.
  var _publicUrl = (typeof publicUrl !== 'undefined' && publicUrl) ? publicUrl.replace(/\/+$/, '') : 'https://leads.venturaclaw.com';
  var _ldBusiness = {
    '@context': 'https://schema.org',
    '@type': 'LocalBusiness',
    '@id': _publicUrl + '/business/' + business.slug,
    name: business.business_name,
    url: _publicUrl + '/business/' + business.slug
  };
  if (business.headline) _ldBusiness.description = business.headline;
  if (business.phone) _ldBusiness.telephone = business.phone;
  if (business.email) _ldBusiness.email = business.email;
  if (_site) _ldBusiness.sameAs = [_site];
  if (business.photo_path) _ldBusiness.image = _publicUrl + business.photo_path;
  if (business.street || business.city) {
    _ldBusiness.address = {
      '@type': 'PostalAddress',
      streetAddress: business.street || undefined,
      addressLocality: business.city || undefined,
      addressRegion: business.state || 'CA',
      postalCode: business.zip || undefined,
      addressCountry: 'US'
    };
  }
  if (business.latitude && business.longitude) {
    _ldBusiness.geo = { '@type': 'GeoCoordinates', latitude: business.latitude, longitude: business.longitude };
  }
%>
<script type="application/ld+json"><%- JSON.stringify(_ldBusiness) %></script>

<%
  // BreadcrumbList — paired with the visible <nav> below. Google uses this
  // to render rich-result breadcrumb chips above the result snippet.
  var _crumbs = [
    { name: 'Home', url: _publicUrl + '/' },
    { name: 'Find a business', url: _publicUrl + '/find' }
  ];
  if (verticalMeta) _crumbs.push({ name: verticalMeta.label, url: _publicUrl + '/find?vertical=' + business.vertical });
  if (business.city)         _crumbs.push({ name: business.city, url: _publicUrl + '/find?city=' + encodeURIComponent(business.city) + (business.vertical ? '&vertical=' + business.vertical : '') });
  _crumbs.push({ name: business.business_name, url: _publicUrl + '/business/' + business.slug });
  var _ldCrumbs = {
    '@context': 'https://schema.org',
    '@type': 'BreadcrumbList',
    itemListElement: _crumbs.map(function(c, i){
      return { '@type': 'ListItem', position: i + 1, name: c.name, item: c.url };
    })
  };
%>
<script type="application/ld+json"><%- JSON.stringify(_ldCrumbs) %></script>

<% if (business.claim_status === 'unclaimed') { %>
  <div role="note" style="background:var(--bg);border-bottom:1px solid var(--border);padding:10px 28px;font-size:13px;text-align:center;color:var(--fg-muted)">
    <span style="display:inline-block;vertical-align:middle">📋  This listing is unclaimed — sourced from Ventura corridor public records.</span>
    <a href="#claim" style="display:inline-block;vertical-align:middle;margin-left:6px;font-weight:600;color:var(--brass)">Are you the owner? Claim it free →</a>
  </div>
<% } %>

<nav class="breadcrumb" aria-label="Breadcrumb" style="padding:16px 28px 0;max-width:1100px;margin:0 auto;font-size:12px;color:var(--fg-muted);letter-spacing:0.04em">
  <% _crumbs.forEach(function(c, i){
    var isLast = i === _crumbs.length - 1;
  %>
    <% if (isLast) { %>
      <span style="color:var(--fg)"><%= c.name %></span>
    <% } else { %>
      <a href="<%= c.url.replace(_publicUrl, '') %>" style="border:none;color:var(--fg-muted)"><%= c.name %></a>
      <span style="margin:0 6px;opacity:0.5">›</span>
    <% } %>
  <% }); %>
</nav>

<% if (business.photo_path) { %>
  <div class="vertical-hero vertical-hero-photo">
    <img src="<%= business.photo_path %>" alt="<%= business.business_name %>" loading="lazy" decoding="async">
  </div>
<% } else { %>
  <div class="vertical-hero vertical-hero-<%= business.vertical %>" aria-hidden="true">
    <div class="vertical-hero-glyph vertical-hero-glyph-svg">
      <%- include('../partials/vertical-glyph', { verticalKey: business.vertical }) %>
    </div>
  </div>
<% } %>

<section class="profile-hero">
  <p class="kicker"><%= verticalMeta ? verticalMeta.icon + '  ' + verticalMeta.label : business.vertical %></p>
  <h1><%= business.business_name %></h1>
  <p class="biz-meta"><%= [business.neighborhood, business.city, business.state].filter(Boolean).join(' · ') %></p>
  <% if (business.headline) { %><p class="lede" style="margin-top:14px"><%= business.headline %></p><% } %>
  <div class="profile-actions">
    <a href="#contact" class="btn btn-primary btn-lg">Send a message</a>
    <% if (_site) { %><a href="<%= _site %>" target="_blank" rel="noopener nofollow" class="btn btn-ghost">Visit website ↗</a><% } %>
    <% if (business.phone) { %><a href="tel:<%= business.phone %>" class="btn btn-ghost">Call <%= business.phone %></a><% } %>
    <button type="button" class="btn btn-ghost" data-share-url="<%= _publicUrl %>/business/<%= business.slug %>" data-share-title="<%= business.business_name %>">Share <span data-share-status></span></button>
  </div>
</section>
<script>
  (function(){
    var slug = '<%= business.slug %>';
    function trackShare() {
      // Fire-and-forget — never block the UX on the counter ping.
      try { fetch('/api/businesses/' + encodeURIComponent(slug) + '/share-track', { method: 'POST' }); } catch(e) {}
    }
    document.querySelectorAll('[data-share-url]').forEach(function(btn){
      btn.addEventListener('click', async function(){
        var url = btn.getAttribute('data-share-url');
        var title = btn.getAttribute('data-share-title');
        var status = btn.querySelector('[data-share-status]');
        // Web Share API on mobile/Safari, clipboard fallback elsewhere.
        if (navigator.share) {
          try { await navigator.share({ title: title, url: url }); trackShare(); return; }
          catch(e){ /* user cancelled — fall through to clipboard */ }
        }
        try {
          await navigator.clipboard.writeText(url);
          trackShare();
          if (status) { status.textContent = '· copied'; setTimeout(function(){ status.textContent=''; }, 2000); }
        } catch(e) {
          if (status) { status.textContent = '· press ⌘C'; }
        }
      });
    });
  })();
</script>

<section class="profile-body">
  <div class="profile-bio">
    <% if (business.description) { %>
      <h2>About</h2>
      <p><%= business.description %></p>
    <% } else if (business.claim_status === 'unclaimed') { %>
      <h2>About</h2>
      <p class="muted">This listing is sourced from Ventura corridor public records and hasn't been claimed by the business yet.
        If you're the owner, you can <a href="#claim">claim this page free</a> to add hours, photos, and a real description.
      </p>
    <% } else { %>
      <h2>About</h2>
      <p class="muted">This business hasn't added a full description yet. Reach out below — they'll send you details directly.</p>
    <% } %>

    <h2 id="contact" style="margin-top:32px">Send <%= business.business_name %> a message</h2>
    <form class="contact-form" action="/business/<%= business.slug %>/contact" method="post">
      <input type="hidden" name="_csrf" value="<%= csrfToken %>">
      <p class="helper">Goes straight to the business — no middleman, no markup. They'll reply to your email.</p>
      <div class="row">
        <label>Your name<input type="text" name="name" required maxlength="120"></label>
        <label>Email<input type="email" name="email" required maxlength="200"></label>
      </div>
      <div class="row">
        <label>Phone (optional)<input type="tel" name="phone" maxlength="32"></label>
        <label>ZIP (optional)<input type="text" name="zip" maxlength="10"></label>
      </div>
      <label>Message<textarea name="message" rows="4" maxlength="1500" placeholder="What are you looking for? Hours, services, pricing — they'll know best."></textarea></label>
      <button type="submit" class="btn btn-primary btn-lg">Send message →</button>
      <p class="muted" style="font-size:11px;margin-top:10px;line-height:1.5">By sending, you agree your contact info will be shared with <%= business.business_name %> only. Ventura Claw does not arrange transactions, set prices, or warrant goods or services.</p>
    </form>
  </div>

  <aside class="profile-side">
    <% if (typeof showActivity !== 'undefined' && showActivity) { %>
      <div class="side-card" style="background:var(--bg);border-color:var(--brass)">
        <% if (activity.msgs_30d >= 3) { %>
          <div style="text-align:center;<%= activity.views_30d >= 25 ? 'border-bottom:1px dotted var(--border);padding-bottom:12px;margin-bottom:12px' : '' %>">
            <p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= activity.msgs_30d %></p>
            <p style="margin:6px 0 0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">messages routed · last 30 days</p>
          </div>
        <% } %>
        <% if (activity.views_30d >= 25) { %>
          <div style="text-align:center;<%= (business.share_count || 0) >= 10 ? 'border-bottom:1px dotted var(--border);padding-bottom:12px;margin-bottom:12px' : '' %>">
            <p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= activity.views_30d %></p>
            <p style="margin:6px 0 0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">profile views · last 30 days</p>
          </div>
        <% } %>
        <% if ((business.share_count || 0) >= 10) { %>
          <div style="text-align:center">
            <p style="font-family:var(--serif);font-size:32px;color:var(--brass);margin:0;line-height:1"><%= business.share_count %></p>
            <p style="margin:6px 0 0;font-size:11px;letter-spacing:0.06em;text-transform:uppercase;color:var(--fg-muted)">times shared</p>
          </div>
        <% } %>
      </div>
    <% } %>
    <div class="side-card">
      <h3>Quick info</h3>
      <% if (_addressOneline) { %><div class="info-row"><strong>Address</strong><span><%= _addressOneline %></span></div><% } %>
      <% if (business.phone) { %><div class="info-row"><strong>Phone</strong><span><a href="tel:<%= business.phone %>"><%= business.phone %></a></span></div><% } %>
      <% if (business.email) { %><div class="info-row"><strong>Email</strong><span><a href="mailto:<%= business.email %>"><%= business.email %></a></span></div><% } %>
      <% if (_site) { %><div class="info-row"><strong>Web</strong><span><a href="<%= _site %>" target="_blank" rel="noopener nofollow"><%= _site.replace(/^https?:\/\//, '').replace(/\/+$/,'') %></a></span></div><% } %>
      <% if (business.instagram_handle) { %><div class="info-row"><strong>Instagram</strong><span><a href="https://instagram.com/<%= encodeURIComponent(business.instagram_handle) %>" target="_blank" rel="noopener nofollow">@<%= business.instagram_handle %></a></span></div><% } %>
      <div class="info-row"><strong>Status</strong><span><%= business.claim_status === 'unclaimed' ? 'Public listing — not yet claimed by the business' : 'Claimed by the business' %></span></div>
    </div>

    <% if (business.claim_status === 'unclaimed') { %>
      <div class="side-card" style="background:var(--bg);border-color:var(--brass)" id="claim">
        <h3 style="color:var(--brass)">Own this business?</h3>
        <p style="font-size:13px;line-height:1.5;margin:0 0 10px">Claim your listing for free — control your description, photos, and how customers reach you. We'll email a confirmation link to verify ownership.</p>
        <form action="/business/<%= business.slug %>/claim" method="post" style="display:flex;gap:6px;flex-wrap:wrap;margin:8px 0 0">
          <input type="hidden" name="_csrf" value="<%= csrfToken %>">
          <input type="email" name="email" required placeholder="you@<%= business.slug.split('-')[0] %>.com" style="flex:1;min-width:0">
          <button type="submit" class="btn btn-primary btn-sm">Claim</button>
        </form>
      </div>
    <% } else if (currentBusiness && currentBusiness.id === business.id) { %>
      <div class="side-card" style="background:#dcfce7;border-color:#16a34a;color:#166534">
        <h3 style="color:#166534">You own this listing</h3>
        <p style="font-size:13px;line-height:1.5;margin:0 0 10px">Open your <a href="/admin">dashboard</a> to edit details or check leads.</p>
      </div>
    <% } %>
  </aside>
</section>

<% if (typeof nearby !== 'undefined' && nearby && nearby.length > 0) { %>
  <section class="similar-nearby">
    <h2 style="font-family:var(--serif);font-weight:500;font-size:24px;margin:0 0 6px">More <%= verticalMeta ? verticalMeta.label.toLowerCase() : '' %> nearby</h2>
    <p class="muted" style="margin:0 0 18px;font-size:13px">Other Ventura Blvd businesses close to <%= business.business_name %>.</p>
    <div class="similar-grid">
      <% nearby.forEach(function(n){
           var nMeta = verticals.find(function(vv){return vv.key===n.vertical});
      %>
        <a class="business-card similar-card" href="/business/<%= n.slug %>">
          <% if (n.photo_path) { %>
            <div class="biz-card-hero biz-card-hero-photo">
              <img src="<%= n.photo_path %>" alt="<%= n.business_name %>" loading="lazy" decoding="async">
            </div>
          <% } else { %>
            <div class="biz-card-hero vertical-hero-<%= n.vertical %>" aria-hidden="true">
              <%- include('../partials/vertical-glyph', { verticalKey: n.vertical }) %>
            </div>
          <% } %>
          <p class="biz-name" style="font-size:16px;margin:6px 0 2px"><%= n.business_name %></p>
          <p class="biz-loc" style="font-size:12px;margin:0"><%= [n.neighborhood, n.city].filter(Boolean).join(' · ') %></p>
        </a>
      <% }); %>
    </div>
  </section>
<% } %>

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