(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_phone ? `
${property.property_phone}
` : ''} ${property.property_email ? `
${property.property_email}
` : ''} ${property.property_website ? ` ` : ''} ${property.default_application_url ? ` ` : ''} ${property.pay_rent_link ? ` ` : ''}
${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.length > 1 ? ` ` : ''} `; 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 Amenities

${property.property_amenities.map(amenity => `
${amenity}
`).join('')}
`; header.appendChild(amenitiesSection); } return header; } function renderFilters() { const filters = createElement('div', 'widget-filters'); filters.innerHTML = `
`; // 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 ? `${plan.plan_name}` : '
Image Coming Soon
'}

${plan.plan_name}

$${plan.base_rent_new}
${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}
` : ''}
`; 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 = ` `; 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 = ` `; 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 = 'Full size image'; 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(); } })();