Extracting and Sharing Modal Booking Widget Across Multi-Property SailJada Infrastructure
What Was Done
SailJada's crew booking page was rendering a broken modal widget—the calendar picker and CTA buttons weren't functional across 17 subpages in production. Rather than inline the modal code in each subpage template, we extracted the booking widget into a shared JavaScript asset (jada-modal.js), deployed it to S3, cached it through CloudFront, and patched all affected pages to load it as a global dependency. This eliminated code duplication, fixed the modal rendering bug, and established a pattern for sharing interactive components across SailJada's multi-property ecosystem.
Root Cause: Template Generator Injection Bug
The SailJada subpages are generated by a bulk-injection script that pulls HTML blocks from a central source and injects them into per-page templates. During that process, an f-string formatting bug was corrupting CSS variable substitution in the modal widget:
/* Broken in injected template: */
background: url("/assets/qr/{qr_file}"); /* f-string braces not escaped */
When the Python injection script processed the template, it tried to interpolate {qr_file} as a variable, failed, and output malformed CSS. The modal widget loaded but rendered with broken QR codes and non-functional date picker styling.
Technical Architecture: Extraction and Deployment
Step 1: Identify Widget Dependencies
We audited the modal HTML across all 17 SailJada subpages (located at s3://sailjada-prod/crew/, s3://sailjada-prod/rates/, s3://sailjada-prod/about/, etc.) and extracted:
- HTML: Modal markup, form elements, QR image tags
- CSS: Modal styling, calendar widget theming, button states
- JavaScript: Date picker initialization, form submission handlers, modal open/close logic
- Global Dependencies: Stripe.js, Google Analytics (tag
G-N6HKL4KLKT)
By inspecting the production HTML in s3://sailjada-prod/, we identified exact byte ranges where the modal block appeared, allowing precise extraction without regex fragility.
Step 2: Build Shared Asset with IIFE Pattern
We created /Users/cb/Documents/repos/tools/jada-modal.js as an Immediately-Invoked Function Expression (IIFE) to avoid polluting the global scope and prevent conflicts with per-page scripts:
(function() {
const MODAL_TRIGGER_SELECTOR = '[data-jada-booking]';
const CALENDAR_API_ENDPOINT = '/api/availability';
function initializeModal() {
document.querySelectorAll(MODAL_TRIGGER_SELECTOR).forEach(btn => {
btn.addEventListener('click', openBookingModal);
});
}
function openBookingModal(event) {
// Render calendar, bind date selection, fire GA event
gtag('event', 'booking_modal_opened');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeModal);
} else {
initializeModal();
}
})();
The IIFE avoids polluting global state and ensures the modal initializes only after the DOM is ready, regardless of script tag position.
Step 3: Fix Path References and Deploy to S3
The original injected widget used relative paths like /assets/qr/zelle.png. We converted these to absolute S3 CloudFront URLs:
/* Before (relative, breaks across subpages): */
background: url("/assets/qr/zelle.png");
/* After (absolute via CloudFront): */
background: url("https://d2k7n4wbsz9o4p.cloudfront.net/assets/qr/zelle.png");
This ensures the QR code loads correctly regardless of which subpage loads the modal.
We uploaded the built asset to:
s3://sailjada-prod/assets/jada-modal.js
s3://sailjada-prod/assets/jada-modal.js.map
CloudFront distribution d2k7n4wbsz9o4p.cloudfront.net (Route53 CNAME: cdn.sailjada.com) automatically cached and served the asset with a 1-hour TTL.
Step 4: Patch All 17 Subpages
For each affected subpage HTML file in s3://sailjada-prod/, we:
- Removed the inline modal block (HTML + embedded CSS + script)
- Added a single script tag in the
<head>or before closing</body>:
<script src="https://d2k7n4wbsz9o4p.cloudfront.net/assets/jada-modal.js" defer></script>
The defer attribute ensures the script loads asynchronously and executes after the DOM is parsed.
Affected pages included:
/crew/index.html(primary booking page)/crew/captains/index.html/rates/index.html/about/index.html- And 13 additional depth-1 and depth-2 subpages
Infrastructure: Staging → Production Promotion
We deployed changes through a staged rollout:
Staging Deployment
Uploaded patched files and the new asset to the staging S3 bucket:
s3://sailjada-staging/crew/index.html
s3://sailjada-staging/assets/jada-modal.js
CloudFront distribution for staging (d1staging7x8k.cloudfront.net) invalidated exact paths to bypass cache:
/crew/index.html
/assets/jada-modal.js
This avoided wildcards that could accidentally invalidate unrelated assets.
Smoke Testing
Before promoting to production, we verified:
- Modal opens when any CTA button is clicked
- Calendar date picker renders without console errors
- QR code (Zelle payment) displays correctly
- Form submission fires GA events:
gtag('event', 'booking_submitted') - Stripe.js loads and initializes without conflicts
Production Promotion
After staging validation, we promoted to production by uploading to s3://sailjada-prod/ and invalidating specific CloudFront paths (never wildcards). We created a snapshot of production state before promotion in case rollback was