class MatchDetailsController { constructor() { this.initializeElements(); this.serverUrl = 'https://api.hems-app.com/api:H8-XQm8i'; this.mapHandler = new MapHandler(); this.slider = null; this.initializeEventListeners(); } initializeElements() { // Initialize DOM elements this.listingModule = document.querySelector('.listing-module-hems-home'); this.sliderContainer = document.getElementById('slider-container'); this.roomsElement = document.getElementById('rooms-match-value'); this.priceElement = document.getElementById('price-match-value'); this.sizeElement = document.getElementById('size-match-value'); this.messageLocationElement = document.getElementById('message-location-match-value'); this.locationElement = document.getElementById('location-match-value'); this.streetsignElement = document.getElementById('streetsign-cont'); this.streetsignTextElement = document.getElementById('streetsign-match-value'); this.coldRentPricingElement = document.getElementById('cold-rent-match-value'); this.addCostPricingElement = document.getElementById('add-cost-match-value'); this.warmRentPricingElement = document.getElementById('warm-rent-match-value'); this.afterRentpricingElement = document.getElementById('after-rent-match-value'); this.notesElement = document.getElementById('notes-listing'); this.providerPersonElement = document.getElementById('person-provider-match-value'); this.providerBusinessElement = document.getElementById('business-provider-match-value'); this.displayMatchIdVal = document.getElementById('match-id-match-value'); this.publishedOnElement = document.getElementById('published-since-match-value'); this.applyButton = document.getElementById('link-match-value'); this.saveButton = document.getElementById('ctaSaveMatch'); } initializeEventListeners() { if (this.saveButton) { this.saveButton.addEventListener('click', () => this.handleSaveListing()); } } async init() { try { // Get match_id from URL const urlParams = new URLSearchParams(window.location.search); const matchId = urlParams.get('id'); if (!matchId) { console.error('No match ID provided in URL'); return; } await Promise.all([ this.fetchAndDisplayListing(matchId), this.mapHandler.initialize() ]); } catch (error) { console.error('Error initializing:', error); } } async fetchAndDisplayListing(matchId) { try { const response = await fetch(`${this.serverUrl}/12/content/${matchId}`, { method: 'POST', mode: 'cors', credentials: 'include' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); this.displayListing(data.listing); // Update map if location is available if (data.listing.location && data.listing.location.original_address) { await this.mapHandler.updateLocation(data.listing.location.original_address); } } catch (error) { console.error('Error fetching listing:', error); } } displayListing(listing) { // Handle images and slider if (listing.images && listing.images.length > 0) { this.resetSlider(listing.images); } else { this.sliderContainer.innerHTML = '

No images available

'; } // Handle address container visibility const container = document.querySelector('#cont-full-address'); if (container) { container.classList.toggle('hidden', !!listing.location.full_address); } // Update all listing information this.roomsElement.textContent = listing.room_count || 'N/A'; this.priceElement.textContent = listing.warm_rent ? `${listing.warm_rent} €` : 'N/A'; this.sizeElement.textContent = listing.size ? `${listing.size}m²` : 'N/A'; this.messageLocationElement.textContent = listing.location.message || 'N/A'; this.locationElement.textContent = listing.location.address || 'N/A'; this.coldRentPricingElement.textContent = listing.cold_rent ? `${listing.cold_rent} €` : 'N/A'; this.addCostPricingElement.textContent = listing.additional_cost ? `${listing.additional_cost} €` : 'N/A'; this.warmRentPricingElement.textContent = listing.warm_rent ? `${listing.warm_rent} €` : 'N/A'; this.afterRentpricingElement.textContent = listing.after_rent ? `${listing.after_rent} €` : 'N/A'; this.notesElement.textContent = listing.general_description || 'Es wurde keine Notiz beigefügt.'; this.providerPersonElement.textContent = listing.provider_name || 'Anbieter Unbekannt'; this.providerBusinessElement.textContent = listing.provider_company_name || 'Unbekannt'; this.displayMatchIdVal.textContent = listing.match_id || 'N/A'; this.publishedOnElement.textContent = listing.published_on ? `online seit ${listing.published_on}` : 'Unbekannt'; // Handle street sign visibility if (listing.streetname) { this.streetsignElement.classList.remove('hidden'); this.streetsignTextElement.textContent = listing.streetname; } else { this.streetsignElement.classList.add('hidden'); } // Set up apply button if (this.applyButton) { this.applyButton.textContent = 'Inserat Link öffnen'; this.applyButton.href = listing.link_cleaned || '#'; // Track link clicks this.setupLinkTracking(listing.match_id, listing.link_cleaned); } } resetSlider(images) { if (this.slider) { this.sliderContainer.innerHTML = ''; this.slider = null; } if (images && images.length > 0) { this.slider = new AdaptiveImageSlider('slider-container', images, { transitionDuration: 250, }); } else { this.sliderContainer.innerHTML = '

No images available

'; } } setupLinkTracking(matchId, linkUrl) { if (!this.applyButton) return; const newButton = this.applyButton.cloneNode(true); this.applyButton.parentNode.replaceChild(newButton, this.applyButton); this.applyButton = newButton; this.applyButton.addEventListener('click', async (e) => { if (linkUrl && linkUrl !== '#') { try { const formData = new FormData(); formData.append('match_id', matchId); await fetch(`${this.serverUrl}/link_opened`, { method: 'POST', body: formData, credentials: 'include', mode: 'cors' }); console.log('Link click tracked successfully'); } catch (error) { console.error('Error tracking link click:', error); } } }); if (!this.applyButton.classList.contains('external')) { this.applyButton.classList.add('external'); } } async handleSaveListing() { const matchId = this.displayMatchIdVal.textContent; if (matchId === 'N/A') return; try { const formData = new FormData(); formData.append('match_id', matchId); const response = await fetch(`${this.serverUrl}/save_match`, { method: 'POST', body: formData, credentials: 'include', mode: 'cors' }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); this.updateSaveButtonState(); } catch (error) { console.error('Error toggling save state:', error); } } updateSaveButtonState() { if (!this.saveButton) return; const isSaved = this.saveButton.classList.contains('saved'); const saveIcon = this.saveButton.querySelector('img'); if (!isSaved) { this.saveButton.classList.add('saved'); if (saveIcon) { const loader = document.createElement('div'); loader.className = 'loader'; saveIcon.style.display = 'none'; this.saveButton.appendChild(loader); setTimeout(() => { loader.remove(); saveIcon.src = 'https://cdn.prod.website-files.com/61918430ea8005fe5b5d3b6c/67347a60e86d2bb3049987f3_hems-save-icon-filled-white.svg'; saveIcon.style.display = ''; }, 800); } } else { this.saveButton.classList.remove('saved'); if (saveIcon) { saveIcon.src = 'https://cdn.prod.website-files.com/61918430ea8005fe5b5d3b6c/66f2de0883d0f669f37a3908_hems-save-icon.svg'; } } } } // Initialize the controller when the DOM is loaded document.addEventListener('DOMContentLoaded', () => { window.matchDetails = new MatchDetailsController(); window.matchDetails.init(); }); class MapHandler { constructor() { this.map = null; this.marker = null; this.initPromise = null; this.mapStyle = [ { featureType: "all", elementType: "geometry.fill", stylers: [{ color: "#F9F4F1" }] }, { featureType: "water", elementType: "geometry", stylers: [{ color: "#b3e6f4" }] }, { featureType: "landscape", elementType: "geometry", stylers: [{ color: "#e8e6e3" }] }, { featureType: "road", elementType: "geometry", stylers: [{ color: "#ffffff" }] }, { featureType: "road.highway", elementType: "geometry", stylers: [{ color: "#dadada" }] }, { featureType: "poi.park", elementType: "geometry", stylers: [{ color: "#d6ecc7" }] }, { featureType: "landscape.man_made", elementType: "geometry", stylers: [{ color: "#F9F4F1" }] }, { featureType: "poi", elementType: "labels", stylers: [{ visibility: "off" }] }, { featureType: "administrative", elementType: "labels", stylers: [{ visibility: "off" }] }, { featureType: "transit", stylers: [{ visibility: "off" }] }, { featureType: "road", elementType: "labels.text.fill", stylers: [{ color: "#9ca5b3" }] } ]; } initialize() { if (this.initPromise) { return this.initPromise; } this.initPromise = new Promise((resolve, reject) => { // Define the callback function window.initMap = () => { try { this.initializeMap(); resolve(); } catch (error) { console.error('Error initializing map:', error); reject(error); } }; // Load the Google Maps script if (!window.google) { const script = document.createElement('script'); script.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyB2E9n1ZhK7TEdy0K-QFFlIq1bOBnDKmHU&callback=initMap'; script.async = true; script.defer = true; script.onerror = (error) => { console.error('Error loading Google Maps script:', error); reject(error); }; document.head.appendChild(script); } else { // If Google Maps is already loaded, just initialize the map window.initMap(); } }); return this.initPromise; } initializeMap() { const mapElement = document.getElementById('targetmap'); if (!mapElement) { throw new Error('Map container element not found'); } this.map = new google.maps.Map(mapElement, { center: { lat: 48.1351, lng: 11.5937 }, // Munich zoom: 11, // Changed from 14 to 11 for wider city view styles: this.mapStyle, disableDefaultUI: true, zoomControl: false, fullscreenControl: false, streetViewControl: false, mapTypeControl: false, gestureHandling: 'cooperative', scrollwheel: false }); this.marker = new google.maps.Marker({ map: this.map, icon: { url: 'https://cdn.prod.website-files.com/61918430ea8005fe5b5d3b6c/66d03265f2a7b52a72456b1d_pin-hems-location-simple.svg', scaledSize: new google.maps.Size(65, 65), anchor: new google.maps.Point(20, 40) } }); // Set up custom zoom controls const zoomInButton = document.getElementById('cta-zoomin-map'); const zoomOutButton = document.getElementById('cta-zoomout-map'); if (zoomInButton) { zoomInButton.onclick = () => this.map.setZoom(this.map.getZoom() + 1); } if (zoomOutButton) { zoomOutButton.onclick = () => this.map.setZoom(this.map.getZoom() - 1); } } async updateLocation(address) { try { // Ensure map is initialized before updating location await this.initPromise; if (!this.map || !this.marker) { console.error('Map or marker not initialized'); return; } const geocoder = new google.maps.Geocoder(); return new Promise((resolve, reject) => { geocoder.geocode({ address }, (results, status) => { if (status === 'OK') { const location = results[0].geometry.location; this.map.setCenter(location); this.map.setZoom(11); // Added to ensure wide view when updating location this.marker.setPosition(location); resolve(); } else { console.error('Geocode was not successful:', status); reject(new Error(`Geocoding failed: ${status}`)); } }); }); } catch (error) { console.error('Error updating location:', error); } } } class AdaptiveImageSlider { constructor(containerId, images, options = {}) { this.container = document.getElementById(containerId); this.images = images; this.currentIndex = 0; this.options = { transitionDuration: 250, heightTransitionDuration: 400, ...options }; this.imageDimensions = []; this.indicator = null; this.isExpanded = false; this.isInitialized = false; this.isDragging = false; this.initialize(); } async initialize() { this.container.style.position = 'relative'; this.container.style.overflow = 'hidden'; this.slider = document.createElement('div'); this.slider.style.position = 'relative'; this.slider.style.width = '100%'; // Remove any transition initially this.slider.style.transition = 'none'; this.container.appendChild(this.slider); await this.loadImageDimensions(); // Set initial height without transition this.setOptimalHeight(false); this.loadImages(); this.setupEventListeners(); const dotsContainer = document.getElementById('sliderdots'); if (dotsContainer) { this.indicator = new SliderIndicator('sliderdots', this.images.length); } this.addIndicatorStyles(); // Only add height transition after a delay AND first user interaction this.container.addEventListener('mouseover', () => { if (!this.isInitialized) { this.isInitialized = true; this.slider.style.transition = `height ${this.options.heightTransitionDuration}ms ease-out`; } }, { once: true }); } async loadImageDimensions() { const promises = this.images.map(imgUrl => new Promise((resolve) => { const image = new Image(); image.onload = () => { resolve({ width: image.width, height: image.height }); }; image.src = imgUrl; }) ); this.imageDimensions = await Promise.all(promises); } setOptimalHeight(useTransition = true) { const containerWidth = this.container.offsetWidth; const orientations = this.imageDimensions.map(img => img.width > img.height ? 'horizontal' : 'vertical'); const horizontalCount = orientations.filter(o => o === 'horizontal').length; const isHorizontalMajority = horizontalCount > this.imageDimensions.length / 2; let optimalHeight; if (isHorizontalMajority) { const horizontalImages = this.imageDimensions.filter(img => img.width > img.height); const avgAspectRatio = horizontalImages.reduce((sum, img) => sum + img.width / img.height, 0) / horizontalImages.length; optimalHeight = containerWidth / avgAspectRatio; } else { optimalHeight = this.imageDimensions.reduce((maxHeight, img) => { const aspectRatio = img.width / img.height; const heightForThisImage = containerWidth / aspectRatio; return Math.max(maxHeight, heightForThisImage); }, 0); } // Temporarily remove transition if useTransition is false if (!useTransition) { const currentTransition = this.slider.style.transition; this.slider.style.transition = 'none'; this.slider.style.height = `${optimalHeight}px`; // Force reflow this.slider.offsetHeight; // Restore transition this.slider.style.transition = currentTransition; } else { this.slider.style.height = `${optimalHeight}px`; } } loadImages() { this.images.forEach((imgUrl, index) => { const slide = document.createElement('div'); slide.style.position = 'absolute'; slide.style.top = '0'; slide.style.left = '0'; slide.style.width = '100%'; slide.style.height = '100%'; slide.style.transform = `translateX(${(index - this.currentIndex) * 100}%)`; slide.style.cursor = 'pointer'; const img = document.createElement('img'); img.src = imgUrl; img.alt = `Image ${index + 1}`; img.style.width = '100%'; img.style.height = '100%'; img.style.objectFit = 'cover'; img.style.objectPosition = 'center middle'; slide.addEventListener('click', () => this.toggleExpansion()); slide.appendChild(img); this.slider.appendChild(slide); }); } toggleExpansion() { // Only apply transition if initialized if (this.isInitialized) { this.isExpanded = !this.isExpanded; if (this.isExpanded) { const containerWidth = this.container.offsetWidth; const currentImage = this.imageDimensions[this.currentIndex]; const aspectRatio = currentImage.width / currentImage.height; const fullHeight = containerWidth / aspectRatio; this.slider.style.height = `${fullHeight}px`; } else { this.setOptimalHeight(); } } } setupEventListeners() { let startX, startY, isDragging = false; const minSwipeDistance = 50; // Minimum distance to trigger a swipe this.container.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; isDragging = false; }, { passive: false }); this.container.addEventListener('touchmove', (e) => { if (!startX || !startY) return; const currentX = e.touches[0].clientX; const currentY = e.touches[0].clientY; const diffX = startX - currentX; const diffY = startY - currentY; // Check if the swipe is more horizontal than vertical if (Math.abs(diffX) > Math.abs(diffY)) { isDragging = true; e.preventDefault(); // Prevent default scrolling } }, { passive: false }); this.container.addEventListener('touchend', (e) => { if (!startX || !startY) return; const endX = e.changedTouches[0].clientX; const endY = e.changedTouches[0].clientY; const diffX = startX - endX; const diffY = startY - endY; // Only process as a swipe if we detected horizontal dragging if (isDragging && Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > minSwipeDistance) { if (diffX > 0) { this.nextSlide(); } else { this.prevSlide(); } } startX = startY = null; isDragging = false; }, { passive: false }); window.addEventListener('resize', () => { if (!this.isExpanded) { this.setOptimalHeight(); } }); // Remove the binding of handleKeyPress document.addEventListener('keydown', (e) => this.handleKeyPress(e)); } // Add the handleKeyPress method handleKeyPress(e) { if (e.key === 'ArrowLeft') { this.prevSlide(); } else if (e.key === 'ArrowRight') { this.nextSlide(); } } prevSlide() { if (this.currentIndex > 0) { if (this.isExpanded) { this.isExpanded = false; this.setOptimalHeight(); } this.currentIndex--; this.updateSlider(this.options.transitionDuration); if (this.indicator) { this.indicator.updateCurrentSlide(this.currentIndex); } } } nextSlide() { if (this.currentIndex < this.images.length - 1) { if (this.isExpanded) { this.isExpanded = false; this.setOptimalHeight(); } this.currentIndex++; this.updateSlider(this.options.transitionDuration); if (this.indicator) { this.indicator.updateCurrentSlide(this.currentIndex); } } } addIndicatorStyles() { const style = document.createElement('style'); style.textContent = ` .slider-indicator { width: 7px; height: 7px; border-radius: 50%; background-color: #E7E7E7; transition: all 0.3s ease; } .slider-indicator.active { background-color: #C99A6D; transform: scale(1.1); } .slider-indicator.std { opacity: 0.7; } .slider-indicator.small { transform: scale(0.8); opacity: 0.5; } .slider-indicator.micro { transform: scale(0.5); opacity: 0.3; } .slider-indicator.hidden { opacity: 0; width: 0; margin: 0; } `; document.head.appendChild(style); } updateSlider(duration) { Array.from(this.slider.children).forEach((slide, index) => { slide.style.transition = `transform ${duration}ms ease`; slide.style.transform = `translateX(${(index - this.currentIndex) * 100}%)`; }); setTimeout(() => { Array.from(this.slider.children).forEach(slide => { slide.style.transition = 'none'; }); }, duration); } } class SliderIndicator { constructor(containerId, totalSlides, options = {}) { this.container = document.getElementById(containerId); if (!this.container) { throw new Error(`Container with ID "${containerId}" not found.`); } this.totalSlides = totalSlides; this.currentSlide = 0; this.options = { maxVisibleIndicators: 4, ...options }; this.indicators = []; this.min = 0; this.max = this.options.maxVisibleIndicators; this.initialize(); } initialize() { // Clear any existing content in the container this.container.innerHTML = ''; // Set styles for the container this.container.style.display = 'flex'; this.container.style.justifyContent = 'center'; this.container.style.alignItems = 'center'; this.container.style.gap = '5px'; for (let i = 0; i < this.totalSlides; i++) { const indicator = document.createElement('div'); indicator.className = this.getIndicatorClass(i); this.container.appendChild(indicator); this.indicators.push(indicator); } } updateCurrentSlide(current) { this.currentSlide = current; if (current < this.min) { this.min = current; this.max = this.min + this.options.maxVisibleIndicators; if (this.max > this.totalSlides) { this.max = this.totalSlides; } } if (current > this.max) { this.max = current; this.min = this.max - this.options.maxVisibleIndicators; if (this.min < 0) { this.min = 0; } } this.updateIndicators(); } updateIndicators() { this.indicators.forEach((indicator, index) => { indicator.className = this.getIndicatorClass(index); }); } getIndicatorClass(index) { if (index === this.currentSlide) { return 'slider-indicator active'; } if (index >= this.min && index <= this.max) { return 'slider-indicator std'; } if (index === this.min - 1 || index === this.max + 1) { return 'slider-indicator small'; } if (index === this.min - 2 || index === this.max + 2) { return 'slider-indicator micro'; } return 'slider-indicator hidden'; } }