(function() {
'use strict';
let swiperInstances = {};
function loadSwiper() {
return new Promise((resolve) => {
// Check if Swiper is already loaded
if (window.Swiper) {
resolve();
return;
}
// Load Swiper CSS
const swiperCSS = document.createElement('link');
swiperCSS.rel = 'stylesheet';
swiperCSS.href = 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.css';
document.head.appendChild(swiperCSS);
// Load Swiper JS
const swiperJS = document.createElement('script');
swiperJS.src = 'https://cdnjs.cloudflare.com/ajax/libs/Swiper/11.0.5/swiper-bundle.min.js';
swiperJS.onload = () => resolve();
document.head.appendChild(swiperJS);
});
}
// Widget configuration
const WIDGET_CONFIG = {
apiBase: window.API_BASE || '/api',
propertyId: window.PROPERTY_ID,
theme: 'light'
};
// Widget state
let widgetData = {
property: null,
unitPlans: [],
selectedPlan: null,
filters: {
bedrooms: 'all',
priceRange: 'all',
sortBy: 'price'
}
};
// Utility functions
function createElement(tag, className, innerHTML) {
const element = document.createElement(tag);
if (className) element.className = className;
if (innerHTML) element.innerHTML = innerHTML;
return element;
}
function formatPrice(price) {
if (!price) return 'Contact for pricing';
return `$${price.toLocaleString()}`;
}
function formatPriceRange(min, max) {
if (!min && !max) return 'Contact for pricing';
if (min && max && min !== max) return `${formatPrice(min)} - ${formatPrice(max)}`;
return `Starting at ${formatPrice(min || max)}`;
}
// API functions
async function fetchPropertyData() {
try {
let propertyIdToFetch = WIDGET_CONFIG.propertyId; // Start with the existing config ID as a fallback
// 1. Try to resolve the slug first to potentially get a new propertyId
if (window.PROPERTY_SLUG) {
console.log(window.PROPERTY_SLUG);
const resolveResponse = await fetch(`${WIDGET_CONFIG.apiBase}/widget/resolve-slug/${window.PROPERTY_SLUG}`);
if (!resolveResponse.ok) {
// Log the error but continue to try the fallback propertyId
console.warn('Failed to resolve property slug, falling back to WIDGET_CONFIG.propertyId.');
} else {
const resolveData = await resolveResponse.json();
console.log(resolveData, "eh");
// Assuming the propertyId is directly in the resolved data object (e.g., resolveData.propertyId)
// You might need to adjust 'propertyId' based on the actual structure of the response
if (resolveData && resolveData.propertyId) {
propertyIdToFetch = resolveData.propertyId;
console.log('Resolved new propertyId:', propertyIdToFetch);
} else {
console.warn('Resolved data is missing propertyId, falling back to WIDGET_CONFIG.propertyId.');
}
}
}
// Ensure we have an ID before making the main property fetch
if (!propertyIdToFetch) {
throw new Error('No property ID available for fetching data.');
}
// 2. Fetch the property data using the obtained or fallback propertyId
const propertyResponse = await fetch(`${WIDGET_CONFIG.apiBase}/widget/property/${propertyIdToFetch}`);
if (!propertyResponse.ok) {
throw new Error('Failed to fetch final property data');
}
return await propertyResponse.json();
} catch (error) {
console.error('Error fetching property data:', error);
return null;
}
}
async function trackApplicationClick(planId) {
try {
await fetch(`${WIDGET_CONFIG.apiBase}/widget/track-application`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
property_id: WIDGET_CONFIG.propertyId,
plan_id: planId,
referral_source: 'widget'
})
});
} catch (error) {
console.error('Error tracking application click:', error);
}
}
function renderPropertyHeader(property) {
const header = createElement('div', 'property-header');
// Banner (if enabled)
if (property.banner_enabled && property.banner_text) {
const banner = createElement('div', 'property-banner');
banner.style.backgroundColor = property.banner_background_color || '#28a745';
banner.style.color = property.banner_text_color || '#ffffff';
banner.innerHTML = `
${property.banner_text}
`;
header.appendChild(banner);
}
// Property info
const info = createElement('div', 'property-info');
info.innerHTML = `
${property.property_logo ? `
` : ''}
${property.property_name}
${property.property_address}, ${property.property_city}, ${property.property_state} ${property.property_zip}
${property.property_description ? `
${property.property_description}
` : ''}
`;
header.appendChild(info);
// Add Property Gallery Section
if (property.images && property.images.length > 0) {
const gallerySection = createElement('div', 'property-gallery-section');
const galleryHTML = `
${property.images.map(image => `
`).join('')}
${property.images.length > 1 ? `
${property.images.map(image => `
`).join('')}
` : ''}
`;
gallerySection.innerHTML = galleryHTML;
header.appendChild(gallerySection);
setTimeout(() => initPropertyGallery(property.images.length), 100);
}
// Add Property Amenities Section
if (property.property_amenities && property.property_amenities.length > 0) {
const amenitiesSection = createElement('div', 'property-amenities-section');
amenitiesSection.innerHTML = `
${property.property_amenities.map(amenity => `
`).join('')}
`;
header.appendChild(amenitiesSection);
}
return header;
}
function renderFilters() {
const filters = createElement('div', 'widget-filters');
filters.innerHTML = `
Bedrooms:
All
Studio
1 Bedroom
2 Bedrooms
3 Bedrooms
4+ Bedrooms
Sort by:
Price (Low to High)
Price (High to Low)
Bedrooms
Square Feet
`;
// Add event listeners
filters.querySelector('#bedrooms-filter').addEventListener('change', (e) => {
widgetData.filters.bedrooms = e.target.value;
renderUnitPlans();
});
filters.querySelector('#sort-filter').addEventListener('change', (e) => {
widgetData.filters.sortBy = e.target.value;
renderUnitPlans();
});
return filters;
}
function renderUnitPlan(plan) {
const planElement = createElement('div', 'unit-plan');
const primaryImage = plan.media.find(m => m.is_featured) || plan.media[0];
planElement.innerHTML = `
${primaryImage ? `
` : '
Image Coming Soon
'}
${plan.bedrooms === 0 ? 'Studio' : `${plan.bedrooms} Bed`}
${plan.bathrooms} Bath
${plan.square_feet_new ? `${plan.square_feet_new} sq ft ` : ''}
${plan.availability ?
`
${plan.availability}
` :
'
Contact for availability
'
}
${plan.availability_notes ? `
${plan.availability_notes}
` : ''}
View Details
Apply Now
Pay Rent
`;
return planElement;
}
function renderUnitPlans() {
const container = document.querySelector('.unit-plans-container');
if (!container) return;
// Filter and sort unit plans
let filteredPlans = [...widgetData.unitPlans];
// Filter by bedrooms
if (widgetData.filters.bedrooms !== 'all') {
const bedroomCount = parseInt(widgetData.filters.bedrooms);
filteredPlans = filteredPlans.filter(plan => {
if (bedroomCount === 4) return plan.bedrooms >= 4;
return plan.bedrooms === bedroomCount;
});
}
// Sort plans
filteredPlans.sort((a, b) => {
switch (widgetData.filters.sortBy) {
case 'price':
const priceA = a.rent_range_min || a.base_rent_new || 0;
const priceB = b.rent_range_min || b.base_rent_new || 0;
return priceA - priceB;
case 'price-desc':
const priceDescA = a.rent_range_max || a.base_rent_new || 0;
const priceDescB = b.rent_range_max || b.base_rent_new || 0;
return priceDescB - priceDescA;
case 'bedrooms':
return a.bedrooms - b.bedrooms;
case 'sqft':
// Handle both numeric and range values for sorting
const getSqFtValue = (sqft) => {
if (!sqft) return 0;
// If it's a range, use the minimum value for sorting
if (sqft.includes('-')) {
return parseInt(sqft.split('-')[0]);
}
return parseInt(sqft) || 0;
};
return getSqFtValue(b.square_feet_new) - getSqFtValue(a.square_feet_new);
default:
return 0;
}
});
// Clear and render
container.innerHTML = '';
filteredPlans.forEach(plan => {
container.appendChild(renderUnitPlan(plan));
});
if (filteredPlans.length === 0) {
container.innerHTML = 'No units match your criteria.
';
}
}
async function renderGalleryModal(plan) {
// Load Swiper if not already loaded
await loadSwiper();
const modalId = `gallery-modal-${plan.plan_id}`;
const modal = createElement('div', 'plan-modal');
modal.innerHTML = `
${plan.media.length > 0 ? `
${plan.media.map(media => `
`).join('')}
${plan.media.length > 1 ? `
${plan.media.map(media => `
`).join('')}
` : ''}
` : '
Image Coming Soon
'}
`;
document.body.appendChild(modal);
// Initialize Swiper after modal is added to DOM
if (plan.media.length > 0) {
setTimeout(() => {
// Initialize thumbnail swiper if more than 1 image
let galleryThumbs = null;
if (plan.media.length > 1) {
galleryThumbs = new Swiper(`.gallery-thumbs-${plan.plan_id}`, {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
320: {
slidesPerView: 3,
},
640: {
slidesPerView: 4,
},
768: {
slidesPerView: 5,
}
}
});
swiperInstances[`thumbs-${plan.plan_id}`] = galleryThumbs;
}
// Initialize main swiper
const gallerySwiper = new Swiper(`.gallery-swiper-${plan.plan_id}`, {
spaceBetween: 10,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
thumbs: galleryThumbs ? {
swiper: galleryThumbs,
} : null,
loop: plan.media.length > 1,
keyboard: {
enabled: true,
}
});
swiperInstances[`main-${plan.plan_id}`] = gallerySwiper;
}, 100);
}
// Close modal when clicking outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closePlanModal();
}
});
}
async function renderPlanModal(plan) {
await loadSwiper();
const modal = createElement('div', 'plan-modal');
modal.innerHTML = `
${plan.media.length > 0 ? `
${plan.media.map(media => `
`).join('')}
${plan.media.length > 1 ? `
${plan.media.map(media => `
`).join('')}
` : ''}
` : '
Image Coming Soon
'}
Pricing
$${plan.base_rent_new}
${plan.deposit_amount ? `
Deposit: ${formatPrice(plan.deposit_amount)}
` : ''}
${plan.application_fee ? `
Application Fee: ${formatPrice(plan.application_fee)}
` : ''}
Availability
${plan.availability ?
`${plan.availability}` :
'Contact for availability'
}
${plan.availability_notes ? `
${plan.availability_notes}
` : ''}
${plan.description ? `
` : ''}
${plan.amenities && plan.amenities.length > 0 ? `
Amenities
${plan.amenities.map(amenity => `
`).join('')}
` : ''}
${plan.appliances && plan.appliances.length > 0 ? `
Appliances
${plan.appliances.map(appliance => `
`).join('')}
` : ''}
`;
document.body.appendChild(modal);
if (plan.media.length > 0) {
setTimeout(() => {
// Initialize thumbnail swiper if more than 1 image
let detailThumbs = null;
if (plan.media.length > 1) {
detailThumbs = new Swiper(`.detail-thumbs-${plan.plan_id}`, {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
320: { slidesPerView: 3 },
640: { slidesPerView: 4 },
768: { slidesPerView: 5 },
1024: { slidesPerView: 6 }
}
});
swiperInstances[`detail-thumbs-${plan.plan_id}`] = detailThumbs;
}
const detailSwiper = new Swiper(`.detail-swiper-${plan.plan_id}`, {
spaceBetween: 0,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
dynamicBullets: true,
},
thumbs: detailThumbs ? {
swiper: detailThumbs,
} : null,
loop: plan.media.length > 1,
keyboard: {
enabled: true,
},
effect: 'fade',
fadeEffect: {
crossFade: true
}
});
swiperInstances[`detail-main-${plan.plan_id}`] = detailSwiper;
}, 100);
}
// Close modal when clicking outside
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closePlanModal();
}
});
}
// Global functions (attached to window for onclick handlers)
window.showPlanDetails = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan) {
renderPlanModal(plan);
}
};
window.showGallery = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan) {
renderGalleryModal(plan);
}
};
window.closePlanModal = function() {
const modal = document.querySelector('.plan-modal');
if (modal) {
// Destroy Swiper instances to prevent memory leaks
Object.keys(swiperInstances).forEach(key => {
if (swiperInstances[key]) {
swiperInstances[key].destroy();
delete swiperInstances[key];
}
});
modal.remove();
}
};
window.openLightbox = function(imageSrc) {
let lightbox = document.querySelector('.image-lightbox');
if (!lightbox) {
lightbox = document.createElement('div');
lightbox.className = 'image-lightbox';
lightbox.innerHTML = '× ';
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox || e.target.classList.contains('image-lightbox-close')) {
lightbox.classList.remove('active');
}
});
document.body.appendChild(lightbox);
}
lightbox.querySelector('img').src = imageSrc;
lightbox.classList.add('active');
};
window.applyNow = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan && widgetData.property) {
// Track the application click
trackApplicationClick(planId);
// Redirect to application URL
const applicationUrl = widgetData.property.default_application_url;
if (applicationUrl) {
window.open(applicationUrl, '_blank');
} else {
alert('Please contact the property for application information.');
}
const payRent = widgetData.property.pay_rent_link;
if (payRent) {
window.open(payRent, '_blank');
} else {
alert('Please contact the property for application information.');
}
}
};
window.payRent = function(planId) {
const plan = widgetData.unitPlans.find(p => p.plan_id === planId);
if (plan && widgetData.property) {
// Track the application click
trackApplicationClick(planId);
const payRent = widgetData.property.pay_rent_link;
if (payRent) {
window.open(payRent, '_blank');
} else {
alert('Please contact the property for application information.');
}
}
};
// Widget styles
function injectStyles() {
const primaryColor = widgetData.property?.widget_primary_color || '#007bff';
const secondaryColor = widgetData.property?.widget_secondary_color || '#6c757d';
const fontFamily = widgetData.property?.widget_font_family || 'Arial, sans-serif';
const styles = `
`;
document.head.insertAdjacentHTML('beforeend', styles);
}
async function initPropertyGallery(imageCount) {
await loadSwiper();
// Initialize thumbnail swiper if more than 1 image
let propertyThumbs = null;
if (imageCount > 1) {
propertyThumbs = new Swiper('.property-gallery-thumbs', {
spaceBetween: 10,
slidesPerView: 4,
freeMode: true,
watchSlidesProgress: true,
breakpoints: {
320: { slidesPerView: 2 },
480: { slidesPerView: 3 },
640: { slidesPerView: 4 },
768: { slidesPerView: 5 },
1024: { slidesPerView: 6 }
}
});
swiperInstances['property-thumbs'] = propertyThumbs;
}
// Initialize main property gallery swiper
const galleryConfig = {
spaceBetween: 0,
navigation: {
nextEl: '.property-gallery-next',
prevEl: '.property-gallery-prev',
},
pagination: {
el: '.property-gallery-pagination',
clickable: true,
dynamicBullets: true,
},
loop: imageCount > 1,
keyboard: {
enabled: true,
},
autoplay: imageCount > 1 ? {
delay: 5000,
disableOnInteraction: false,
} : false,
effect: 'fade',
fadeEffect: {
crossFade: true
}
};
if (propertyThumbs) {
galleryConfig.thumbs = { swiper: propertyThumbs };
}
const propertyGallery = new Swiper('.property-gallery-swiper', galleryConfig);
swiperInstances['property-main'] = propertyGallery;
}
// Initialize widget
async function initWidget() {
const container = document.getElementById('widget-container');
if (!container) {
console.error('Widget container not found');
return;
}
// Show loading
container.innerHTML = 'Loading property information...
';
// Fetch data
const data = await fetchPropertyData();
if (!data) {
container.innerHTML = 'Failed to load property information. Please try again later.
';
return;
}
widgetData.property = data.property;
widgetData.unitPlans = data.unitPlans;
widgetData.property.images = data.images;
// Inject styles (including property gallery styles)
injectStyles();
// Render widget
const widget = createElement('div', 'property-availability-widget');
// Property header
widget.appendChild(renderPropertyHeader(widgetData.property));
// Filters
widget.appendChild(renderFilters());
// Unit plans container
const unitPlansContainer = createElement('div', 'unit-plans-container');
widget.appendChild(unitPlansContainer);
// Replace container content
container.innerHTML = '';
container.appendChild(widget);
// Initial render of unit plans
renderUnitPlans();
}
// Start the widget when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWidget);
} else {
initWidget();
}
})();