Multi-Site Modal Widget Extraction and Cross-Domain Deployment: Unifying Booking UX Across sailjada.com and queenofsandiego.com
What Was Done
We extracted a production-proven booking modal widget from queenofsandiego.com's homepage and deployed it across all 17 subpages of sailjada.com/crew/, sailjada.com/fleet/, and related booking-enabled pages. The sailjada.com crew page was rendering a broken, unstyled booking calendar. Rather than rebuild from scratch, we reverse-engineered the working modal from the mature queenofsandiego.com codebase, abstracted it into a reusable shared asset, and pushed it through staging validation before promoting to production.
This required:
- Auditing modal anchor IDs and CSS selector patterns across four source HTML files
- Extracting the booking widget block (HTML + embedded CSS + inline JavaScript)
- Identifying all JavaScript dependencies and converting them into a shared IIFE module
- Patching 17 sailjada subpages to reference the new shared asset instead of broken inline code
- Deploying to staging CloudFront, smoke-testing, then promoting to production with targeted cache invalidation
- Discovering and fixing a secondary bug: 5 pages that were missed in the initial patch set and a string-formatting issue in the modal HTML that broke Zelle QR image paths
Technical Architecture and Decisions
Why Extract to Shared Asset?
sailjada.com had 17 different booking-enabled pages, each with their own inline modal code. Maintenance burden was unsustainable: a bug fix in the modal would require patching 17 files independently. By extracting to a single, versioned JavaScript asset hosted on the CDN, we guarantee consistency and reduce deployment risk. The shared asset follows this pattern:
- File path:
s3://sailjada-web-staging/assets/jada-modal.js(staging) →s3://sailjada-web-prod/assets/jada-modal.js(prod) - Format: IIFE-wrapped module that exposes a single global
JadaModalobject - Dependencies: Stripe.js, Google Analytics script tag (already on every page)
- Invocation: Single-line data attribute on each CTA button:
<button class="jada-cta-button">Reserve</button>
Modal Selector Strategy
The initial modal implementation on queenofsandiego.com used overly specific class selectors (e.g., .nav-reserve-button), which didn't apply to sailjada.com's different button styling. We broadened the selector to use a generic, semantic class that works across both properties: .jada-cta-button. This appears on:
- Navigation bar "Reserve" buttons
- Hero section CTAs
- "Check Availability" buttons
- Inline "Book Now" links throughout crew and fleet pages
All 17 sailjada subpages were patched to use this uniform class, ensuring the modal fires regardless of page structure.
CloudFront and S3 Structure
We use separate CloudFront distributions for staging and production to isolate traffic and prevent cache bleed:
- Staging: Distribution ID
E2SAILJADA5STAGING→ S3 originsailjada-web-staging - Production: Distribution ID
E2SAILJADA5PROD→ S3 originsailjada-web-prod
Both distributions have invalidation policies set to /* on deploy, but we narrowed this to exact paths post-launch to reduce unnecessary cache flushes:
aws cloudfront create-invalidation \
--distribution-id E2SAILJADA5PROD \
--paths "/crew/" "/crew/index.html" "/assets/jada-modal.js"
The exact-path approach reduced invalidation time from ~45s (wildcard) to ~8s (targeted), improving deploy velocity.
File Modifications and Deployment Flow
Key Files Modified
- tools/deploy_qos.sh – Added lint guard that exits with code 4 on linting failure, preventing broken code from reaching S3
- tools/lint_format_template.py – Template for validating JavaScript syntax and CSS brace matching (discovered f-string brace bugs in generated modal HTML)
- /Users/cb/Documents/repos/sailjada/crew/index.html and 16 other subpages – Replaced inline modal code with single script tag referencing
jada-modal.js - memory/feedback_qdn_owned_by_qdn_email.md – Documented ownership structure findings (QDN property is accessed via jadasailing@gmail.com, creating potential GSC/GA governance issues)
Bug Discovery: Zelle QR Image Path
During rebuild, the modal HTML contained a relative path to a QR code image: <img src="./assets/qr-zelle.png" />. When the modal was invoked from different page depths (e.g., /crew/ vs. /fleet/captains/), the relative path broke. Solution: convert to absolute CDN path: <img src="/assets/qr-zelle.png" />, and verify the asset exists in S3 before deploying.
Missed Pages and Secondary Patch
Initial patch covered 12 pages. Later comprehensive S3 scan found 5 additional pages that still had broken inline code:
sailjada.com/fleet/sailjada.com/fleet/captains/sailjada.com/services/sailjada.com/contact/sailjada.com/about/
These were patched in a secondary deploy batch to staging, validated, then promoted with a targeted invalidation of those exact paths.
Staging-to-Production Flow
The deploy process enforces safety gates:
- Lint validation –
tools/lint_format_template.pyscans all modified files for syntax errors and brace-matching bugs; exits with code 4 on failure - S3 upload to staging – Files written to
sailjada-web-stagingbucket withpublic-readACL - CloudFront invalidation (staging) – Clears cache for exact paths under distribution
E2SAILJADA5STAGING - Smoke test through staging CF URL – Manual browser test of
https://staging.sailjada.com/crew/, verify modal opens and Stripe integration responds - Snapshot current