← 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();
}
})();`;
}