← back to Ventura Corridor

src/server/appointments/embed-loader.ts

70 lines

/**
 * /embed/appointments.js — the loader served to third-party origins
 * (Shopify, custom HTML, anywhere).
 *
 * Usage on the host page:
 *   <div id="appt-widget" data-owner="info@designerwallcoverings.com"></div>
 *   <script src="https://venturacorridor.com/embed/appointments.js" async></script>
 *
 * The loader:
 *   1. Finds every <div id^="appt-widget"> (supports multiple).
 *   2. Reads data-owner.
 *   3. Uses IntersectionObserver to defer the iframe until the widget
 *      scrolls into view — keeps the host page Lighthouse score clean.
 *   4. Iframe has a sandbox attribute that allows scripts/forms but
 *      blocks top-level navigation so the iframe can never break out.
 */
export function renderEmbedLoader(origin: string): string {
  return `/* Smart Scheduling embed loader — venturacorridor.com */
(function(){
  var ORIGIN = ${JSON.stringify(origin)};
  function mount(host){
    if (host.__apptMounted) return;
    host.__apptMounted = true;
    var owner = host.getAttribute('data-owner') || 'info@designerwallcoverings.com';
    var bizId = host.getAttribute('data-business-id') || '';
    var src   = ORIGIN + '/book?owner=' + encodeURIComponent(owner) + '&embed=1'
              + (bizId ? '&business_id=' + encodeURIComponent(bizId) : '');
    var iframe = document.createElement('iframe');
    iframe.src = src;
    iframe.title = 'Book an appointment';
    iframe.setAttribute('loading', 'lazy');
    iframe.setAttribute('sandbox', 'allow-scripts allow-forms allow-same-origin allow-popups');
    iframe.setAttribute('referrerpolicy', 'no-referrer-when-downgrade');
    iframe.style.width = '100%';
    iframe.style.minHeight = '820px';
    iframe.style.border = '0';
    iframe.style.borderRadius = '0';
    iframe.style.colorScheme = 'normal';
    iframe.style.display = 'block';
    host.innerHTML = '';
    host.appendChild(iframe);
  }
  function lazyMount(host){
    if (!('IntersectionObserver' in window)){
      // older browsers: just mount
      mount(host);
      return;
    }
    var io = new IntersectionObserver(function(entries){
      entries.forEach(function(e){
        if (e.isIntersecting){
          mount(host);
          io.disconnect();
        }
      });
    }, { rootMargin: '300px' });
    io.observe(host);
  }
  function init(){
    var nodes = document.querySelectorAll('[id^="appt-widget"]');
    for (var i = 0; i < nodes.length; i++) lazyMount(nodes[i]);
  }
  if (document.readyState === 'loading'){
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }
})();`;
}