1 <!DOCTYPE html> 2<html lang="en"> 3<head> 4<meta charset="utf-8"> 5<meta name="viewport" content="width=device-width, initial-scale=1"> 6<meta name="csrf-token" content="bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw"> 7<title>Flats/Apartments for sale in Sector 71 Mohali</title> 8<meta name="description" content="Explore Flats/Apartments for sale in Sector 71 Mohali on Lets Rentz.Check 100% verified flats in Sector 71 Mohali for sale with 0% brokerage option"> 9<link rel="canonical" href="https://www.letsrentz.com/buy/Flat-for-sale-in-Sector-71-Mohali/1/30.7046/76.7179"> 10 11<meta name="apple-itunes-app" content="app-id=1634945591, app-argument=https://www.letsrentz.com/buy/Flat-for-sale-in-Sector-71-Mohali/1/30.7046/76.7179"> 12<link rel="shortcut icon" href="https://www.letsrentz.com/front/assets/images/favicon.svg"> 13 14<meta property="og:title" content="Flats/Apartments for sale in Sector 71 Mohali"/> 15<meta property="og:description" content="Explore Flats/Apartments for sale in Sector 71 Mohali on Lets Rentz.Check 100% verified flats in Sector 71 Mohali for sale with 0% brokerage option"/> 16<meta property="og:url" content="https://www.letsrentz.com/buy/Flat-for-sale-in-Sector-71-Mohali/1/30.7046/76.7179"/> 17<meta property="og:image" content="https://images.letsrentz.com/AppBanners/fbShare_letsrentz.png"/> 18<meta property="og:image:height" content="360"/> 19<meta property="og:image:width" content="540"/> 20<meta property="og:site_name" content="LetsRentz"/> 21<meta property="og:type" content="product"/> 22<meta name="twitter:site" content="@letsrentz"> 23<meta name="twitter:title" content="Flats/Apartments for sale in Sector 71 Mohali"> 24<meta name="twitter:description" content="Explore Flats/Apartments for sale in Sector 71 Mohali on Lets Rentz.Check 100% verified flats in Sector 71 Mohali for sale with 0% brokerage option"> 25<meta name="twitter:image" content="https://images.letsrentz.com/AppBanners/fbShare_letsrentz.png"> 26 27 28 29
30<script> 31(function () { 32 try { 33 var p = new URLSearchParams(location.search), d = {}, has = false; 34 ['utm_source','utm_medium','utm_campaign','utm_content','utm_term','fbclid','gclid'].forEach(function (k) { 35 var v = p.get(k); 36 if (v) { d[k] = v.slice(0, 160); has = true; } 37 }); 38 var ref = ''; 39 try { ref = document.referrer ? new URL(document.referrer).hostname : ''; } catch (e) {} 40 var external = ref && ref.indexOf('letsrentz.com') === -1; 41 var existing = /(?:^|;\s*)lr_acq=/.test(document.cookie); 42 // Write when the URL carries campaign params, or when an external 43 // referrer arrives and nothing is remembered yet. Anything else (direct 44 // visit, internal navigation, organic visit while a paid touch is 45 // remembered) leaves the cookie alone. 46 if (!has && !(external && !existing)) { return; } 47 d.lp = location.pathname.slice(0, 200); 48 d.ref = external ? ref.slice(0, 120) : ''; 49 d.ts = Date.now(); 50 document.cookie = 'lr_acq=' + encodeURIComponent(JSON.stringify(d)) + 51 '; max-age=2592000; path=/; SameSite=Lax' + (location.protocol === 'https:' ? '; Secure' : ''); 52 } catch (e) {} 53})(); 54</script>
54 55<link rel="preconnect" href="https://fonts.googleapis.com"> 56<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 57<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:[email protected]&display=swap"> 58<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"> 59<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/styles.css?v=1.29"> 60<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/responsive.css?v=1.29"> 61<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/custom.css?v=1.49"> 62<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/styles-locality.css?v=1.70"> 63<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/responsive-locality.css?v=1.84"> 64 65<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/loginv2.css?v=1.0"> 66<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/owl.carousel.min.css"> 67<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css"> 68<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css"/> 69<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
70<script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
70 71
72<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
vendor: 1 bytes, line 72
72
73<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js"></script>
vendor: 1 bytes, line 73
73
74<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
vendor: 1 bytes, line 74
74
75<script src="https://www.letsrentz.com/front/custom.js"></script>
vendor: 1 bytes, line 75
75
76<script src="https://www.letsrentz.com/front/assets/js/owl.carousel.min.js"></script>
76 77 78 79 80<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css"> 81<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/style1.css?v=60"> 82<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/responsive1.css?v=59"> 83 84<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/onboarding-v2.css?v=2"> 85 86<link rel="stylesheet" href="https://www.letsrentz.com/front/assets/css/site-header.css?v=1"> 87 88 89<!-- Google Tag Manager -->
90<script>(function(w,d,s,l,i){ 91 if (d.querySelector('script[src*="googletagmanager.com/gtm.js?id="]')) { return; } 92 w[l]=w[l]||[];w[l].push({'gtm.start': 93 new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 94 j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 95 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 96})(window,document,'script','dataLayer','GTM-T7B3K67W');</script>
96 97<!-- End Google Tag Manager --> 98<!-- Global site tag (gtag.js) - Google Analytics -->
99<script> 100 (function () { 101 var hpGA4AlreadyLoaded = (typeof window.gtag === 'function'); 102 window.dataLayer = window.dataLayer || []; 103 if (!hpGA4AlreadyLoaded) { 104 var gtagScript = document.createElement('script'); 105 gtagScript.async = true; 106 gtagScript.src = 'https://www.googletagmanager.com/gtag/js?id=G-3HSRHHWD2P'; 107 document.head.appendChild(gtagScript); 108 window.gtag = function () { dataLayer.push(arguments); }; 109 gtag('js', new Date()); 110 gtag('config', 'G-3HSRHHWD2P'); 111 gtag('config', 'AW-11395525747'); 112 } 113 })(); 114 115 // Same real GA4-client-id reader as template.blade.php, so the backend's 116 // server-side sign_up event (AuthController::register()) ties back to 117 // this browser session exactly like it does on the live site. 118 function getGA4ClientId() { 119 var match = document.cookie.match(/(?:^|;\s*)_ga=([^;]+)/); 120 if (!match) return null; 121 var parts = match[1].split('.'); 122 if (parts.length < 4) return null; 123 return parts.slice(-2).join('.'); 124 } 125</script>
125 126 127<!-- Meta Pixel Code -->
128<script> 129 var hpFbPixelAlreadyLoaded = !!window.fbq; 130 !function(f,b,e,v,n,t,s) 131 {if(f.fbq)return;n=f.fbq=function(){n.callMethod? 132 n.callMethod.apply(n,arguments):n.queue.push(arguments)}; 133 if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0'; 134 n.queue=[];t=b.createElement(e);t.async=!0; 135 t.src=v;s=b.getElementsByTagName(e)[0]; 136 s.parentNode.insertBefore(t,s)}(window, document,'script', 137 'https://connect.facebook.net/en_US/fbevents.js'); 138 if (!hpFbPixelAlreadyLoaded) { 139 fbq('init', '824029695503400'); 140 fbq('track', 'PageView'); 141 } 142</script>
142 143<noscript><img height="1" width="1" style="display:none" 144src="https://www.facebook.com/tr?id=824029695503400&ev=PageView&noscript=1" 145/></noscript> 146<!-- End Meta Pixel Code --> 147 148
149<script type="application/ld+json"> 150 { 151 "@context": "https://schema.org", 152 "@type": "Organization", 153 "name": "LetsRentz", 154 "url": "https://www.letsrentz.com", 155 "logo": "https://www.letsrentz.com/front/assets/images/logo.svg", 156 "contactPoint": { 157 "@type": "ContactPoint", 158 "telephone": "+91-7719669498", 159 "contactType": "customer service", 160 "areaServed": "IN", 161 "availableLanguage": "English" 162 } 163 } 164</script>
vendor: 1 bytes, line 164
164
165<script type="application/ld+json"> 166{ 167 "@context": "https://schema.org", 168 "@type": "WebSite", 169 "name": "LetsRentz", 170 "url": "https://www.letsrentz.com", 171 "potentialAction": { 172 "@type": "SearchAction", 173 "target": { 174 "@type": "EntryPoint", 175 "urlTemplate": "https://www.letsrentz.com/properties?address={search_term_string}" 176 }, 177 "query-input": "required name=search_term_string" 178 } 179} 180</script>
180 181</head> 182<body class=""> 183<!-- Google Tag Manager (noscript) -->
vendor: 68 bytes, lines 183-184
183 184<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
184GTM-T7B3K67W
vendor: 83 bytes, lines 184-185
184" 185height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
186<!-- End Google Tag Manager (noscript) --> 187<div class="hp-preview"> 188 189 190 <div class="hp-app-banner" id="hpAppBanner"> 191 <div class="hp-app-banner-inner"> 192 <span class="hp-app-banner-icon"><i class="bi bi-phone"></i></span> 193 <div class="hp-app-banner-copy"> 194 <span class="hp-app-banner-title">Better experience on the app</span> 195 <span class="hp-app-banner-sub"> 196 <span class="hp-app-banner-rating"><i class="bi bi-star-fill"></i> 4.3</span> 197 <span class="hp-app-banner-dot">·</span> 198 <span id="hpAppBannerStoreLabel">Play Store</span> 199 </span> 200 </div> 201 <a href="javascript:void(0)" class="hp-app-banner-cta" id="hpAppBannerCta">Open</a> 202 <button type="button" class="hp-app-banner-close" id="hpAppBannerClose" aria-label="Dismiss">×</button> 203 </div> 204</div> 205 206<header class="hv2 hv2-header"> 207 <div class="hv2-wrap"> 208 <a class="hv2-logo" href="https://www.letsrentz.com"><img src="https://www.letsrentz.com/front/assets/images/details-images/logo.png" alt="Lets Rentz"></a> 209 <div class="hv2-spacer"></div> 210 <a class="hv2-hbtn hv2-hbtn--ghost hv2-hide-m" href="https://www.letsrentz.com/propertysearchwithpid">Search by PID</a> 211 <button type="button" class="hv2-hbtn hv2-hide-m" data-hp-login-trigger>Log in</button> 212 <button type="button" class="hv2-hbtn" id="hv2MenuBtn" data-hv2-menu-open aria-controls="hv2Drawer" aria-expanded="false"> 213 <i class="bi bi-list" style="font-size:18px"></i><span class="hv2-hide-m">Menu</span> 214 </button> 215 </div> 216</header> 217 218<div class="hv2-scrim" id="hv2Scrim"></div> 219<aside class="hv2 hv2-drawer" id="hv2Drawer" aria-hidden="true" aria-label="Menu"> 220 <div class="hv2-dr-head"> 221 <a class="hv2-logo" href="https://www.letsrentz.com"><img src="https://www.letsrentz.com/front/assets/images/details-images/logo.png" alt="Lets Rentz"></a> 222 <button type="button" class="hv2-dr-x" id="hv2MenuClose" aria-label="Close menu"><i class="bi bi-x-lg"></i></button> 223 </div> 224 225 <div class="hv2-dr-auth"> 226 <button type="button" class="hv2-hbtn hv2-hbtn--solid" data-hp-login-trigger data-hv2-menu-close>Log in / Sign up</button> 227 </div> 228 229 <nav> 230 231 <p>Find a home</p> 232 <a href="https://www.letsrentz.com/propertysearch/Flat-for-rent-in-Sector-71-Mohali/2/30.7046/76.7179"><i class="bi bi-house-door"></i>Rent a home</a> 233 <a href="https://www.letsrentz.com/propertysearch/PG-in-Sector-71-Mohali/3/30.7046/76.7179"><i class="bi bi-door-open"></i>PG & co-living</a> 234 <a href="https://www.letsrentz.com/buy/Flat-for-sale-in-Sector-71-Mohali/1/30.7046/76.7179"><i class="bi bi-key"></i>Buy a property</a> 235 <a href="https://www.letsrentz.com/propertysearch/Commercial-for-rent-in-Sector-71-Mohali/5/30.7046/76.7179"><i class="bi bi-building"></i>Commercial</a> 236 <a href="https://www.letsrentz.com/propertysearchwithpid"><i class="bi bi-upc-scan"></i>Search by PID</a> 237 <a href="https://www.letsrentz.com/tenant/plans"><i class="bi bi-star"></i>Premium plans</a> 238 239 <p>For owners & brokers</p> 240 <a href="https://www.letsrentz.com/post-property-for-rent" data-hv2-menu-close><i class="bi bi-plus-circle"></i>Post property <em>FREE</em></a> 241 <a href="https://www.letsrentz.com/rent-agreement"><i class="bi bi-file-earmark-text"></i>Rent agreement</a> 242 <a href="https://www.letsrentz.com/rent-agreement"><i class="bi bi-shield-check"></i>Police verification</a> 243 244 <p>More</p> 245 <a href="https://www.letsrentz.com/rent-calculator"><i class="bi bi-calculator"></i>Rent calculator</a> 246 <a href="https://www.letsrentz.com/localities"><i class="bi bi-geo-alt"></i>Explore localities</a> 247 <a href="https://blog.letsrentz.com/" target="_blank" rel="noopener"><i class="bi bi-journal-text"></i>Blog</a> 248 <a href="https://www.letsrentz.com/faq"><i class="bi bi-question-circle"></i>FAQ</a> 249 <a href="https://wa.me/918847073862" target="_blank" rel="noopener"><i class="bi bi-whatsapp"></i>Contact us on WhatsApp</a> 250 251 </nav> 252</aside> 253
254<script> 255(function () { 256 var drawer = document.getElementById('hv2Drawer'); 257 var scrim = document.getElementById('hv2Scrim'); 258 var menuBtn = document.getElementById('hv2MenuBtn'); 259 if (!drawer || !scrim || !menuBtn) return; 260 function setMenu(open) { 261 drawer.classList.toggle('is-open', open); 262 scrim.classList.toggle('is-open', open); 263 document.body.classList.toggle('hv2-locked', open); 264 drawer.setAttribute('aria-hidden', open ? 'false' : 'true'); 265 menuBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); 266 if (open) document.getElementById('hv2MenuClose').focus(); 267 } 268 document.querySelectorAll('[data-hv2-menu-open]').forEach(function (b) { b.addEventListener('click', function () { setMenu(true); }); }); 269 document.getElementById('hv2MenuClose').addEventListener('click', function () { setMenu(false); }); 270 scrim.addEventListener('click', function () { setMenu(false); }); 271 // Items that open a modal (login, post property, logout) close the drawer first. 272 document.querySelectorAll('[data-hv2-menu-close]').forEach(function (b) { b.addEventListener('click', function () { setMenu(false); }); }); 273 document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && drawer.classList.contains('is-open')) setMenu(false); }); 274})(); 275</script>
275 276 277 278 279<div class="hp-modal-overlay" id="hpNotifHelpOverlay" aria-hidden="true"> 280 <div class="hp-modal-card" role="dialog" aria-modal="true" aria-labelledby="hpNotifHelpTitle"> 281 <div class="hp-modal-icon"><i class="bi bi-bell-slash"></i></div> 282 <h3 id="hpNotifHelpTitle">Notifications are blocked</h3> 283 <p id="hpNotifHelpBody">You've blocked notifications for Lets Rentz in this browser. To turn them back on, open 284 the site settings from your address bar (click the icon next to the URL), allow notifications, then reload the 285 page.</p> 286 <div class="hp-modal-actions"> 287 <button type="button" class="hp-modal-btn hp-modal-btn-ghost" id="hpNotifHelpClose">Got it</button> 288 </div> 289 </div> 290</div> 291 292 293<div class="bl-preview"> 294 295 296 <div class="bl-mobile-topbar"> 297 <button type="button" class="bl-mobile-back" aria-label="Back" onclick="window.location.href='https://www.letsrentz.com'"> 298 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M15 6l-6 6 6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> 299 </button> 300 <div class="bl-mobile-search-pill"> 301 <svg width="15" height="15" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"/><path d="m20 20-3.5-3.5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> 302 <input type="text" class="autocomplete-google-listing" id="bl_address_mobile" placeholder="Choose by Location..." value="Sector 71 Mohali"> 303 </div> 304 <button type="button" class="bl-mobile-icon-btn" data-bs-toggle="modal" data-bs-target="#mobileFiltersModal" aria-label="Filters"> 305 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M4 6h16M7 12h10M10 18h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg> 306 </button> 307 <button type="button" class="bl-mobile-icon-btn" aria-label="Log in to view saved properties" onclick="openLoginPopup(event)"> 308 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 21s-7.5-4.9-10-9.4C.6 8 2.4 4 6.3 4c2 0 3.6 1 5.7 3.3C14.1 5 15.7 4 17.7 4c3.9 0 5.7 4 4.3 7.6C19.5 16.1 12 21 12 21Z" stroke="currentColor" stroke-width="1.6"/></svg> 309 </button> 310 </div> 311 312 <div class="bl-mobile-filter-row"> 313 <button type="button" class="bl-mobile-sort-pill" data-bs-toggle="modal" data-bs-target="#sortByModal"> 314 Sort By 315 <svg width="11" height="7" viewBox="0 0 24 24" fill="none"><path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg> 316 </button> 317 </div> 318 319 <div class="container bl-pill-search-wrap bl-desktop-search"> 320 <div class="bl-pill-search"> 321 <select id="listing_transaction_type" aria-label="Listing type"> 322 <option value="buy" selected>Buy</option> 323 <option value="rent">Rent</option> 324 </select> 325 <input type="hidden" id="selected_property_type" value="1"> 326 <input type="text" class="autocomplete-google bl-pill-loc" id="bl_address_desktop" value="Sector 71 Mohali"> 327 <button type="button" class="bl-pill-search-btn btn_buy_listing_search" aria-label="Search"> 328 <svg width="15" height="15" viewBox="0 0 24 24" fill="none"><circle cx="11" cy="11" r="7" stroke="#fff" stroke-width="2"/><path d="m20 20-3.5-3.5" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg> 329 </button> 330 </div> 331 <a class="bl-pill-pid" href="https://www.letsrentz.com/propertysearchforsalewithpid">Search PID 332 <svg width="12" height="12" viewBox="0 0 24 24" fill="none"><path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="2"/></svg> 333 </a> 334 </div> 335 336 <div class="container bl-breadcrumb"> 337 <a href="https://www.letsrentz.com">Home</a><span class="sep">/</span><span class="current">Flats/Apartments for sale in Sector 71 Mohali</span> 338 </div> 339 340 <div class="container"> 341 342 <div class="bl-results-head"> 343 <h1 class="property-results">0+ Flats/Apartments for sale in Sector 71 Mohali</h1> 344 </div> 345 346 <div class="card bl-insight-banner"> 347 <div class="bl-insight-copy">
348 <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M12 21s-7-6.3-7-11a7 7 0 1 1 14 0c0 4.7-7 11-7 11Z" stroke="#B8860B" stroke-width="1.8"/></svg> 349 <span>Get to know more about Phase 3B2, Sector 60, Mohali</span> 350 </div> 351 <a href="https://www.letsrentz.com/localities/mohali-sas-nagar/phase-3b2">View Insights ›</a> 352 </div> 353 354 <div class="bl-pill-row"> 355 356 <select id="updated_dates" class="bl-updated-select bl-hidden-not-in-design"> 357 <option value="all">All Updated Dates</option> 358 <option value="7">Last 7 Days</option> 359 <option value="15">Last 15 Days</option> 360 <option value="30">Last 1 Month</option> 361 </select> 362 <select id="sort_by" class="bl-sort-select"> 363 <option>Most relevant</option> 364 <option>Recently added</option> 365 <option>Price: Low to High</option> 366 <option>Price: High to Low</option> 367 </select> 368 </div> 369 370 <div class="bl-layout"> 371 372 373 <aside class="bl-sidebar"> 374 375 <div class="card bl-filter-group"> 376 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blBudget" aria-expanded="true"> 377 <span>Budget</span><span class="chev">▾</span> 378 </div> 379 <div class="collapse show" id="blBudget"> 380 <div class="bl-filter-body"> 381 <div class="bl-row"> 382 <select id="budgetMin"> 383 <option value="0">No min</option> 384 <option value="2500000">25 L</option> 385 <option value="5000000">50 L</option> 386 <option value="7500000">75 L</option> 387 <option value="10000000">1 Cr</option> 388 <option value="15000000">1.5 Cr</option> 389 <option value="20000000">2 Cr</option> 390 <option value="30000000">3 Cr</option> 391 <option value="50000000">5 Cr</option> 392 </select> 393 <select id="budgetMax"> 394 <option value="1000000000">No max</option> 395 <option value="5000000">50 L</option> 396 <option value="10000000">1 Cr</option> 397 <option value="15000000">1.5 Cr</option> 398 <option value="20000000">2 Cr</option> 399 <option value="30000000">3 Cr</option> 400 <option value="50000000">5 Cr</option> 401 <option value="100000000">10 Cr</option> 402 </select> 403 </div> 404 </div> 405 </div> 406 </div> 407 408 <div class="card bl-filter-group"> 409 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blCategory" aria-expanded="true"> 410 <span>Type of property</span><span class="chev">▾</span> 411 </div> 412 <div class="collapse show" id="blCategory"> 413 <div class="bl-filter-body" id="category-container"></div> 414 </div> 415 </div> 416 417 <div class="card bl-filter-group"> 418 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blBedrooms" aria-expanded="true"> 419 <span>No. of Bedrooms</span><span class="chev">▾</span> 420 </div> 421 <div class="collapse show" id="blBedrooms"> 422 <div class="bl-filter-body"> 423 <div class="bl-chip-grid" id="bedroom-container"> 424 <span class="bl-chip-mini bedroom-chip" data-value="1">1 RK/1 BHK</span> 425 <span class="bl-chip-mini bedroom-chip" data-value="2">2 BHK</span> 426 <span class="bl-chip-mini bedroom-chip" data-value="3">3 BHK</span>
427 <span class="bl-chip-mini bedroom-chip" data-value="4">4 BHK</span> 428 <span class="bl-chip-mini bedroom-chip" data-value="5">5 BHK</span> 429 <span class="bl-chip-mini bedroom-chip bl-more-hidden" data-value="6" style="display:none;">6 BHK</span> 430 <span class="bl-chip-mini bedroom-chip bl-more-hidden" data-value="7" style="display:none;">7 BHK</span> 431 <span class="bl-chip-mini bedroom-chip bl-more-hidden" data-value="8" style="display:none;">8 BHK</span> 432 <span class="bl-chip-mini bedroom-chip bl-more-hidden" data-value="9" style="display:none;">9 BHK</span> 433 <span class="bl-chip-mini bedroom-chip bl-more-hidden" data-value="10" style="display:none;">10 BHK</span> 434 </div> 435 <span class="more-link" id="bedroomMoreLink">+ 5 more</span> 436 </div> 437 </div> 438 </div> 439 440 441 <div class="card bl-filter-group bl-filter-not-in-design"> 442 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blFurnish" aria-expanded="true"> 443 <span>Furnished type</span><span class="chev">▾</span> 444 </div> 445 <div class="collapse show" id="blFurnish"> 446 <div class="bl-filter-body" id="furnish-container"></div> 447 </div> 448 </div> 449 450 <div class="card bl-filter-group"> 451 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blStatus" aria-expanded="false"> 452 <span>Construction Status</span><span class="chev">▾</span> 453 </div> 454 <div class="collapse" id="blStatus"> 455 <div class="bl-filter-body"> 456 <label class="bl-check-line"><input type="checkbox" class="construction-status-checkbox" value="under_construction">Under Construction</label> 457 <label class="bl-check-line"><input type="checkbox" class="construction-status-checkbox" value="ready_to_move">Ready to move</label> 458 </div> 459 </div> 460 </div> 461 462 463 <div class="card bl-filter-group bl-filter-not-in-design"> 464 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blPurchase" aria-expanded="true"> 465 <span>Purchase Type</span><span class="chev">▾</span> 466 </div> 467 <div class="collapse show" id="blPurchase"> 468 <div class="bl-filter-body"> 469 <label class="bl-check-line"><input type="checkbox" class="selling-type-checkbox" value="new_booking">New Booking</label> 470 <label class="bl-check-line"><input type="checkbox" class="selling-type-checkbox" value="resale">Resale</label> 471 </div> 472 </div> 473 </div> 474 475 <div class="card bl-filter-group"> 476 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blListedBy" aria-expanded="false"> 477 <span>Posted by</span><span class="chev">▾</span> 478 </div> 479 <div class="collapse" id="blListedBy"> 480 <div class="bl-filter-body"> 481 <label class="bl-check-line"><input type="checkbox" class="listed-by-checkbox" value="user">Owner</label> 482 <label class="bl-check-line"><input type="checkbox" class="listed-by-checkbox" value="broker">Broker</label> 483 </div> 484 </div> 485 </div> 486 487 <div class="card bl-filter-group"> 488 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blArea" aria-expanded="false">
489 <span>Area (sqft)</span><span class="chev">▾</span> 490 </div> 491 <div class="collapse" id="blArea"> 492 <div class="bl-filter-body"> 493 <div class="bl-row"> 494 <input type="number" id="minAreaInput" placeholder="Min Area" min="0"> 495 <input type="number" id="maxAreaInput" placeholder="Max Area" min="0"> 496 </div> 497 </div> 498 </div> 499 </div> 500 501 502 <div class="card bl-filter-group bl-filter-not-in-design"> 503 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blAge" aria-expanded="true"> 504 <span>Property Age</span><span class="chev">▾</span> 505 </div> 506 <div class="collapse show" id="blAge"> 507 <div class="bl-filter-body"> 508 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox" value="0-1 years">0-1 year</label> 509 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox" value="1-5 years">1-5 year</label> 510 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox" value="5-10 years">5-10 year</label> 511 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox" value="10+ years">10+ year</label> 512 </div> 513 </div> 514 </div> 515 516 <div class="card bl-filter-group"> 517 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blLocalities" aria-expanded="false"> 518 <span>Localities near you</span><span class="chev">▾</span> 519 </div> 520 <div class="collapse" id="blLocalities"> 521 <div class="bl-filter-body"> 522 <a href="https://www.letsrentz.com/flats-for-sale-in-mohali" class="bl-loc-row"><span>Apartment / Flats for Sale in Mohali - Flats in Mohali</span> ›</a> 523 <a href="https://www.letsrentz.com/residential-plots-for-sale-in-mohali" class="bl-loc-row"><span>Residential Plots / Land For Sale in Mohali - Plots in Mohali</span> ›</a> 524 <a href="https://www.letsrentz.com/1-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>1 BHK Flats for Sale in Mohali | 1 BHK Flats in Mohali</span> ›</a> 525 <a href="https://www.letsrentz.com/2-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>2 BHK Flats for Sale in Mohali | 2 BHK Flats in Mohali</span> ›</a> 526 <a href="https://www.letsrentz.com/3-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>3 BHK Flats for Sale in Mohali | 3 BHK Flats in Mohali</span> ›</a> 527 </div> 528 </div> 529 </div> 530 531 <div class="card bl-filter-group"> 532 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blAmenities" aria-expanded="false"> 533 <span>Amenities</span><span class="chev">▾</span> 534 </div> 535 <div class="collapse" id="blAmenities"> 536 <div class="bl-filter-body" id="amenities-container"> 537 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox" value="parking">Parking</label> 538 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox" value="gated_security">Gated Society</label> 539 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox" value="park">Park</label> 540 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox" value="power_backup">Power Backup</label> 541 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="gym">Gym</label> 542 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="club_house">Club House</label> 543 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="swimming_pool">Swimming Pool</label> 544 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="lift">Lift</label> 545 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="fire_safety">Fire Safety</label> 546 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="intercom">Intercom</label> 547 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="children_play_area">Kids Play Area</label> 548 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="shopping_center">Shopping Center</label> 549 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="sewage_treatment_plant">Waste Treatment</label> 550 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="visitor_par
550king">Visitor Parking</label> 551 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="gas_pipeline">Gas Pipeline</label> 552 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="internet_provider">Internet Provider</label> 553 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox" value="water_supply">Water Supply</label> 554 <span class="more-link" id="amenitiesMoreLink">+ 13 more</span> 555 </div> 556 </div> 557 </div> 558 559 <div class="card bl-filter-group"> 560 <div class="bl-filter-header" data-bs-toggle="collapse" data-bs-target="#blRera" aria-expanded="false"> 561 <span>RERA Approved</span><span class="chev">▾</span> 562 </div> 563 <div class="collapse" id="blRera"> 564 <div class="bl-filter-body"> 565 <label class="bl-check-line"><input type="checkbox" class="rera-status-checkbox" value="rera_approved">Show only RERA registered</label> 566 </div> 567 </div> 568 </div> 569 570 </aside> 571 572 573 <div class="results"> 574 <div id="propertyListings"></div> 575 <div class="bl-show-more-wrap"> 576 <button class="btn btn-ghost btn-show-more" data-offset="0">Show More</button> 577 </div> 578 </div> 579 </div> 580 </div> 581 582 583 584 <div class="modal fade mobile-filters-modal" id="mobileFiltersModal" tabindex="-1" aria-hidden="true"> 585 <div class="modal-dialog modal-dialog-scrollable modal-fullscreen"> 586 <div class="modal-content"> 587 <div class="modal-header mobile-filter-modal-header"> 588 <button type="button" class="btn btn-link mobile-filter-clear" id="clearFilters">Clear</button> 589 <h5 class="modal-title">Filters</h5> 590 <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> 591 </div> 592 <div class="modal-body"> 593 <div class="mobile-filter-section-card"> 594 <div class="mobile-filter-section-header">Property Type</div> 595 <select id="selected_property_type_mobile" class="form-select form-select-sm mt-2"> 596 <option value="1">Flat/Apartment</option> 597 <option value="2">Independent House/Villa</option> 598 <option value="3">Independent Builder Floor</option> 599 <option value="4">Plot/Land</option> 600 <option value="5">Farmhouse</option> 601 <option value="6">Commercial</option> 602 </select> 603 </div> 604 <div class="mobile-filter-section-card"> 605 <div class="mobile-filter-section-header">Type of property</div> 606 <div class="bl-filter-body" id="category_container1"></div> 607 </div> 608 <div class="mobile-filter-section-card"> 609 <div class="mobile-filter-section-header">No. of Bedrooms</div> 610 <div class="bl-chip-grid mt-2" id="bedroom-container-mobile"> 611 <span class="bl-chip-mini bedroom-chip-mobile" data-value="1">1 RK/1 BHK</span> 612 <span class="bl-chip-mini bedroom-chip-mobile" data-value="2">2 BHK</span> 613 <span class="bl-chip-mini bedroom-chip-mobile" data-value="3">3 BHK</span> 614 <span class="bl-chip-mini bedroom-chip-mobile" data-value="4">4 BHK</span> 615 <span class="bl-chip-mini bedroom-chip-mobile" data-value="5">5 BHK</span> 616 <sp
616an class="bl-chip-mini bedroom-chip-mobile bl-more-hidden" data-value="6" style="display:none;">6 BHK</span> 617 <span class="bl-chip-mini bedroom-chip-mobile bl-more-hidden" data-value="7" style="display:none;">7 BHK</span> 618 <span class="bl-chip-mini bedroom-chip-mobile bl-more-hidden" data-value="8" style="display:none;">8 BHK</span> 619 <span class="bl-chip-mini bedroom-chip-mobile bl-more-hidden" data-value="9" style="display:none;">9 BHK</span> 620 <span class="bl-chip-mini bedroom-chip-mobile bl-more-hidden" data-value="10" style="display:none;">10 BHK</span> 621 </div> 622 <span class="more-link" id="bedroomMoreLinkMobile">+ 5 more</span> 623 </div> 624 625 <div class="mobile-filter-section-card bl-filter-not-in-design"> 626 <div class="mobile-filter-section-header">Furnished type</div> 627 <div class="bl-filter-body" id="furnish_container1"></div> 628 </div> 629 <div class="mobile-filter-section-card"> 630 <div class="mobile-filter-section-header">Property Status</div> 631 <label class="bl-check-line"><input type="checkbox" class="construction-status-checkbox-mobile" value="under_construction">Under Construction</label> 632 <label class="bl-check-line"><input type="checkbox" class="construction-status-checkbox-mobile" value="ready_to_move">Ready to move</label> 633 </div> 634 635 <div class="mobile-filter-section-card bl-filter-not-in-design"> 636 <div class="mobile-filter-section-header">Purchase Type</div> 637 <label class="bl-check-line"><input type="checkbox" class="selling-type-checkbox-mobile" value="new_booking">New Booking</label> 638 <label class="bl-check-line"><input type="checkbox" class="selling-type-checkbox-mobile" value="resale">Resale</label> 639 </div> 640 <div class="mobile-filter-section-card"> 641 <div class="mobile-filter-section-header">Listed By</div> 642 <label class="bl-check-line"><input type="checkbox" class="listed-by-checkbox-mobile" value="user">Owner</label> 643 <label class="bl-check-line"><input type="checkbox" class="listed-by-checkbox-mobile" value="broker">Broker</label> 644 </div> 645 <div class="mobile-filter-section-card"> 646 <div class="mobile-filter-section-header">Area (sqft)</div> 647 <div class="bl-row mt-2"> 648 <input type="number" id="minAreaInput1" placeholder="Min Area" min="0"> 649 <input type="number" id="maxAreaInput1" placeholder="Max Area" min="0"> 650 </div> 651 </div> 652 <div class="mobile-filter-section-card"> 653 <div class="mobile-filter-section-header">Localities near you</div> 654 <div class="bl-filter-body bl-mobile-localities"> 655 <a href="https://www.letsrentz.com/flats-for-sale-in-mohali" class="bl-loc-row"><span>Apartment / Flats for Sale in Mohali - Flats in Mohali</span> ›</a> 656 <a href="https://www.letsrentz.com/residential-plots-for-sale-in-mohali" class="bl-loc-row"><span>Residential Plots / Land For Sale in Mohali - Plots in Mohali</span> ›</a> 657 <a href="https://www.letsrentz.com/1-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>1 BHK Flats for Sale in Mohali | 1 BHK Flats in Mohali</span> ›</a> 658 <a href="https://www.letsrentz.com/2-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>2 BHK Flats for Sale in Mohali | 2 BHK Flats in Mohali</span> ›</a> 659 <a href="https://www.letsrentz.com/3-bhk-flats-for-sale-in-mohali" class="bl-loc-row"><span>3 BHK Flats for Sale in Mohali | 3 BHK Flats in Mohali</span> ›</a> 660 </div> 661 </div> 662 <div class="mobile-filter-section-card"> 663 <div class="mobile-filter-section-header">Amenities</div> 664 <div class="bl-filter-body bl-mobile-amenities" id="amenities-container-mobile"> 665 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox-mobile" value="parking">Parking</label> 666 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox-mobile" value="gated_security">Gated Society</label> 667 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox-mobile" value="park">Park</label> 668 <label class="bl-check-line"><input type="checkbox" class="amenity-checkbox-mobile" value="power_backup">Power Backup</label> 669 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="gym">Gym</label> 670 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="club_house">Club House</label> 671 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="swimming_pool">Swimming Pool</label> 672 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="lift">Lift</label> 673 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="fire_safety">Fire Safety</label> 674 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="intercom">Intercom</label> 675 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="children_play_area">Kids Play Area</label> 676 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="shopping_center">Shopping Center</label> 677 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="sewage_treatment_plant">Waste Treatment</label> 678 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="visitor_par
678king">Visitor Parking</label> 679 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="gas_pipeline">Gas Pipeline</label> 680 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="internet_provider">Internet Provider</label> 681 <label class="bl-check-line bl-more-hidden" style="display:none;"><input type="checkbox" class="amenity-checkbox-mobile" value="water_supply">Water Supply</label> 682 <span class="more-link" id="amenitiesMoreLinkMobile">+ 13 more</span> 683 </div> 684 </div> 685 686 <div class="mobile-filter-section-card bl-filter-not-in-design"> 687 <div class="mobile-filter-section-header">Property Age</div> 688 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox-mobile" value="0-1 years">0-1 year</label> 689 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox-mobile" value="1-5 years">1-5 year</label> 690 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox-mobile" value="5-10 years">5-10 year</label> 691 <label class="bl-check-line"><input type="checkbox" class="property-age-checkbox-mobile" value="10+ years">10+ year</label> 692 </div> 693 <div class="mobile-filter-section-card"> 694 <div class="mobile-filter-section-header">Budget</div> 695 <div class="bl-row mt-2"> 696 <select id="budgetMinMobile"> 697 <option value="0">No min</option> 698 <option value="2500000">25 L</option> 699 <option value="5000000">50 L</option> 700 <option value="7500000">75 L</option> 701 <option value="10000000">1 Cr</option> 702 <option value="15000000">1.5 Cr</option> 703 <option value="20000000">2 Cr</option> 704 <option value="30000000">3 Cr</option> 705 <option value="50000000">5 Cr</option> 706 </select> 707 <select id="budgetMaxMobile"> 708 <option value="1000000000">No max</option> 709 <option value="5000000">50 L</option> 710 <option value="10000000">1 Cr</option> 711 <option value="15000000">1.5 Cr</option> 712 <option value="20000000">2 Cr</option> 713 <option value="30000000">3 Cr</option> 714 <option value="50000000">5 Cr</option> 715 <option value="100000000">10 Cr</option> 716 </select> 717 </div> 718 </div> 719 <div class="mobile-filter-section-card"> 720 <div class="mobile-filter-section-header">RERA Status</div> 721 <label class="bl-check-line"><input type="checkbox" class="rera-status-checkbox-mobile" value="rera_approved">RERA Approved Properties</label> 722 </div> 723 </div> 724 <div class="modal-footer"> 725 <div><span id="mobileFilterResultsCount">0</span> Property found</div> 726 <button type="button" class="btn btn-view-results btn_apply_filters" data-bs-dismiss="modal">View Results</button> 727 </div> 728 </div> 729 </div> 730 </div> 731 732 733 <div class="modal fade mobile-updated-dates-modal" id="updatedDatesModal" tabindex="-1" aria-modal="true"> 734 <div class="modal-dialog modal-dialog-scrollable modal-fullscreen"> 735 <div class="modal-content"> 736 <div class="modal-header mobile-updated-dates-modal-header"><h5 class="modal-title">Updated Dates</h5></div> 737 <div class="modal-body"> 738 <div class="form-check"><input class="form-check-input" type="radio" name="updatedDateOption" id="updatedDateAll" value="all" checked><label class="form-check-label" for="updatedDateAll">All</label></div> 739 <div class="form-check"><input class="form-check-input" type="radio" name="updatedDateOption" id="updatedDateLast7Days" value="last-7-days"><label class="form-check-label" for="updatedDateLast7Days">Last 7 Days</label></div> 740 <div class="form-check"><input class="form-check-input" type="radio" name="updatedDateOption" id="updatedDateLast15Days" value="last-15-days"><label class="form-check-label" for="updatedDateLast15Days">Last 15 Days</label></div> 741 <div class="form-check"><input class="form-check-input" type="radio" name="updatedDateOption" id="updatedDateLast1Month" value="last-1-month"><label class="form-check-label" for="updatedDateLast1Month">Last 1 Month</label></div> 742 </div> 743 <div class="modal-footer"><button type="button" class="btn-done-updated-dates" data-bs-dismiss="modal">Done</button></div> 744 </div> 745 </div> 746 </div> 747 748 749 <div class="modal fade mobile-sort-by-modal" id="sortByModal" tabindex="-1" aria-labelledby="sortByModalLabel"> 750 <div class="modal-dialog modal-dialog-scrollable modal-fullscreen"> 751 <div class="modal-content"> 752 <div class="modal-header mobile-sort-by-modal-header"> 753 <h5 class="modal-title mobile-sort-by-modal-title" id="sortByModalLabel">Sort By</h5> 754 </div> 755 <div class="modal-body mobile-sort-by-modal-body"> 756 <div class="mobile-sort-options"> 757 <div class="form-check mobile-sort-option-item"><input class="form-check-input" type="radio" name="sortOption" id="sortMostRelevant" value="most-relevant" checked><label class="form-check-label" for="sortMostRelevant">Most Relevant</label></div> 758 <div class="form-check mobile-sort-option-item"><input class="form-check-input" type="radio" name="sortOption" id="sortPriceLow" value="price-low"><label class="form-check-label" for="sortPriceLow">Price: Low to High</label></div> 759 <div class="form-check mobile-sort-option-item"><input class="form-check-input" type="radio" name="sortOption" id="sortPriceHigh" value="price-high"><label class="form-check-label" for="sortPriceHigh">Price: High to Low</label></div> 760 <div class="form-check mobile-sort-option-item"><input class="form-check-input" type="radio" name="sortOption" id="sort
760Newest" value="newest"><label class="form-check-label" for="sortNewest">Newest First</label></div> 761 </div> 762 </div> 763 <div class="modal-footer mobile-sort-by-footer"><button type="button" class="btn btn-apply-sort" id="sort_by_mobile" data-bs-dismiss="modal">Done</button></div> 764 </div> 765 </div> 766 </div> 767</div> 768
769<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDSj3nda3aEN7QXQNaDllvJQxkwAGBbDkM&libraries=places&callback=initGoogleAutocompleteListing" async defer></script>
vendor: 1 bytes, line 769
769
770<script> 771function initGoogleAutocompleteListing() { 772 const inputs = document.querySelectorAll('.autocomplete-google-listing, .autocomplete-google'); 773 inputs.forEach(input => { 774 if (window.getComputedStyle(input).display === "none") return; 775 const autocomplete = new google.maps.places.Autocomplete(input, { 776 types: ['geocode'], 777 componentRestrictions: { country: 'in' } 778 }); 779 // Matches the live /buy/... page: selecting a place here fills the 780 // input via Google's widget but isn't wired to re-run the search 781 // (search-by-typed-address isn't functional there either today). 782 autocomplete.addListener('place_changed', function () {}); 783 }); 784} 785window.initGoogleAutocompleteListing = initGoogleAutocompleteListing; 786 787$(document).ready(function() { 788 var buySellPropertyTypes = [{"id":1,"name":"Flat\/Apartment","parent_id":0,"status":1,"created_at":"2025-05-11T18:34:09.000000Z","updated_at":"2025-05-11T18:34:09.000000Z","sub_property_type":[{"id":7,"name":"1BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:36:06.000000Z","updated_at":"2025-05-11T18:36:06.000000Z"},{"id":8,"name":"2BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:36:24.000000Z","updated_at":"2025-05-11T18:36:24.000000Z"},{"id":10,"name":"3+1 BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:36:45.000000Z","updated_at":"2025-05-11T18:36:45.000000Z"},{"id":9,"name":"3BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:36:33.000000Z","updated_at":"2025-05-11T18:36:33.000000Z"},{"id":12,"name":"4+1 BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:37:03.000000Z","updated_at":"2025-05-11T18:37:03.000000Z"},{"id":11,"name":"4BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:36:53.000000Z","updated_at":"2025-05-11T18:36:53.000000Z"},{"id":14,"name":"5+1 BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:37:23.000000Z","updated_at":"2025-05-11T18:37:23.000000Z"},{"id":13,"name":"5BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:37:13.000000Z","updated_at":"2025-05-11T18:37:13.000000Z"},{"id":15,"name":"6BHK","parent_id":1,"status":1,"created_at":"2025-05-11T18:37:34.000000Z","updated_at":"2025-05-11T18:37:34.000000Z"},{"id":16,"name":"Studio Apartment","parent_id":1,"status":1,"created_at":"2025-05-11T18:38:11.000000Z","updated_at":"2025-05-11T18:38:11.000000Z"}]},{"id":2,"name":"Independent House\/Villa","parent_id":0,"status":1,"created_at":"2025-05-11T18:34:25.000000Z","updated_at":"2025-05-11T18:34:25.000000Z","sub_property_type":[{"id":17,"name":"1BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:38:38.000000Z","updated_at":"2025-05-11T18:38:38.000000Z"},{"id":18,"name":"2BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:38:55.000000Z","updated_at":"2025-05-11T18:38:55.000000Z"},{"id":19,"name":"3BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:39:04.000000Z","updated_at":"2025-05-11T18:39:04.000000Z"},{"id":20,"name":"4BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:39:12.000000Z","updated_at":"2025-05-11T18:39:12.000000Z"},{"id":21,"name":"5BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:39:21.000000Z","updated_at":"2025-05-11T18:39:21.000000Z"},{"id":22,"name":"6BHK","parent_id":2,"status":1,"created_at":"2025-05-11T18:39:29.000000Z","updated_at":"2025-05-11T18:39:29.000000Z"},{"id":49,"name":"7BHK","parent_id":2,"status":1,"created_at":"2025-06-19T10:53:37.000000Z","updated_at":"2025-06-19T10:53:37.000000Z"},{"id":50,"name":"8BHK","parent_id":2,"status":1,"created_at":"2025-06-19T10:54:02.000000Z","updated_at":"2025-06-19T10:54:02.000000Z"},{"id":51,"name":"9BHK","parent_id":2,"status":1,"created_at":"2025-06-19T10:54:23.000000Z","updated_at":"2025-06-19T10:54:23.000000Z"}]},{"id":3,"name":"Independent Builder Floor","parent_id":0,"status":1,"created_at":"2025-05-11T18:35:15.000000Z","updated_at":"2025-05-25T09:47:30.000000Z","sub_property_type":[{"id":23,"name":"1BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:39:56.000000Z","updated_at":"2025-05-11T18:39:56.000000Z"},{"id":24,"name":"2BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:05.000000Z","updated_at":"2025-05-11T18:40:05.000000Z"},{"id":26,"name":"3+1 BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:20.000000Z","updated_at":"2025-05-11T18:40:20.000000Z"},{"id":25,"name":"3BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:11.000000Z","updated_at":"2025-05-11T18:40:11.000000Z"},{"id":28,"name":"4+1 BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:36.000000Z","updated_at":"2025-05-11T18:40:36.000000Z"},{"id":27,"name":"4BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:28.000000Z","updated_at":"2025-05-11T18:40:28.000000Z"},{"id":30,"name":"5+1 BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:53.000000Z","updated_at":"2025-05-11T18:40:53.000000Z"},{"id":29,"name":"5BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:40:45.000000Z","updated_at":"2025-05-11T18:40:45.000000Z"}
788,{"id":31,"name":"6BHK","parent_id":3,"status":1,"created_at":"2025-05-11T18:41:01.000000Z","updated_at":"2025-05-11T18:41:01.000000Z"}]},{"id":4,"name":"Plot\/Land","parent_id":0,"status":1,"created_at":"2025-05-11T18:35:22.000000Z","updated_at":"2025-05-11T18:35:22.000000Z","sub_property_type":[{"id":32,"name":"Plot","parent_id":4,"status":1,"created_at":"2025-05-11T18:41:51.000000Z","updated_at":"2025-05-11T18:41:51.000000Z"}]},{"id":5,"name":"Farmhouse","parent_id":0,"status":1,"created_at":"2025-05-11T18:35:30.000000Z","updated_at":"2025-05-11T18:35:30.000000Z","sub_property_type":[{"id":33,"name":"2BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:12.000000Z","updated_at":"2025-05-11T18:42:12.000000Z"},{"id":34,"name":"3BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:20.000000Z","updated_at":"2025-05-11T18:42:20.000000Z"},{"id":35,"name":"4BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:28.000000Z","updated_at":"2025-05-11T18:42:28.000000Z"},{"id":36,"name":"5BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:36.000000Z","updated_at":"2025-05-11T18:42:36.000000Z"},{"id":37,"name":"6BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:43.000000Z","updated_at":"2025-05-11T18:42:43.000000Z"},{"id":38,"name":"7BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:42:53.000000Z","updated_at":"2025-05-11T18:42:53.000000Z"},{"id":39,"name":"8BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:43:04.000000Z","updated_at":"2025-05-11T18:43:15.000000Z"},{"id":40,"name":"9BHK","parent_id":5,"status":1,"created_at":"2025-05-11T18:43:27.000000Z","updated_at":"2025-05-11T18:43:27.000000Z"}]},{"id":6,"name":"Commercial","parent_id":0,"status":1,"created_at":"2025-05-11T18:35:42.000000Z","updated_at":"2025-05-11T18:35:42.000000Z","sub_property_type":[{"id":43,"name":"Co-Working","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:10.000000Z","updated_at":"2025-05-11T18:44:10.000000Z"},{"id":45,"name":"Godown\/Warehouse","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:30.000000Z","updated_at":"2025-05-11T18:44:30.000000Z"},{"id":47,"name":"Hotel\/Resorts","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:45.000000Z","updated_at":"2025-05-11T18:44:45.000000Z"},{"id":46,"name":"Industrial Building","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:38.000000Z","updated_at":"2025-05-11T18:44:38.000000Z"},{"id":41,"name":"Office Space","parent_id":6,"status":1,"created_at":"2025-05-11T18:43:53.000000Z","updated_at":"2025-05-11T18:43:53.000000Z"},{"id":48,"name":"Other Business","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:54.000000Z","updated_at":"2025-05-11T18:44:54.000000Z"},{"id":42,"name":"Plot","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:02.000000Z","updated_at":"2025-05-11T18:44:02.000000Z"},{"id":44,"name":"Showroom","parent_id":6,"status":1,"created_at":"2025-05-11T18:44:22.000000Z","updated_at":"2025-05-11T18:44:22.000000Z"}]}]; 789 var furnishTypes = [{"id":1,"name":"Fully Furnished","status":1,"created_at":"2022-07-17T11:02:24.000000Z","updated_at":"2022-07-17T11:02:24.000000Z"},{"id":2,"name":"Semi Furnished","status":1,"created_at":"2022-07-17T11:02:32.000000Z","updated_at":"2022-07-17T11:02:32.000000Z"},{"id":3,"name":"Unfurnished","status":1,"created_at":"2022-07-17T11:02:40.000000Z","updated_at":"2022-07-17T11:02:40.000000Z"}]; 790 var selected_property_type_id = '1'; 791 var offset = 0; 792 var min_price = '0'; 793 var max_price = '1000000000'; 794 var property_type_ids = [""+1]; 795 var category_ids = []; 796 var furnished_ids = []; 797 var bedroom_ids = []; 798 var sortBy = 'distance'; 799 var sortByOrder = 'asc'; 800 var lati = '30.7046'; 801 var longi = '76.7179'; 802 var address = 'Sector 71 Mohali'; 803 var seaching_title = 'Flats/Apartments for sale in Sector 71 Mohali'; 804 var construction_status_ids = []; 805 var selling_type_ids = []; 806 var rera_status_ids = []; 807 var listed_by_types = []; 808 var age_of_property_ids = []; 809 var min_area = ""; 810 var max_area = ""; 811 var amenity_ids = []; 812 813 // Desktop transaction switcher. Keep the real property-type filter in 814 // #selected_property_type while routing Rent to the equivalent address. 815 $('#listing_transaction_type').on('change', function () { 816 if (this.value !== 'rent') return; 817 var cleanAddress = String(address || '').trim().replace(/^Flat-for-sale-in-/i, ''); 818 var rentSlug = 'Flat-for-rent-in-' + cleanAddress.replace(/\s+/g, '-'); 819 window.location.href = 'https://www.letsrentz.com/propertysearch/' + encodeURIComponent(rentSlug) + '/1/' + lati + '/' + longi; 820 }); 821 var updated_since = "all"; 822 var selected_category_id = []; 823 var selected_furnished_id = []; 824 var user_data = null; 825 var user_id = user_data ? user_data.user_id : ''; 826 827 $('#bl_address_desktop, #bl_address_mobile').val(address); 828
829 // ---------- Budget (dual select, matches the new HTML design) ---------- 830 $('#budgetMin, #budgetMax').val(function(){ 831 return this.id === 'budgetMin' ? min_price : max_price; 832 }); 833 $('#budgetMin, #budgetMax').on('change', function () { 834 min_price = parseInt($('#budgetMin').val()); 835 max_price = parseInt($('#budgetMax').val()); 836 offset = 0; 837 filteredProperties(offset, 'fromfunction'); 838 }); 839 $('#budgetMinMobile, #budgetMaxMobile').on('change', function () { 840 min_price = parseInt($('#budgetMinMobile').val()); 841 max_price = parseInt($('#budgetMaxMobile').val()); 842 }); 843 844 // ---------- Show first 5 items, "+ N more" reveals the rest 845 // (matches the new HTML design's "+ 1 more" / "+ 5 more" pattern) ---------- 846 function appendWithShowMore(containerSelector, items, limit) { 847 limit = limit || 5; 848 items.forEach(function (item, idx) { 849 if (idx >= limit) { item.hide(); } 850 $(containerSelector).append(item); 851 }); 852 if (items.length > limit) { 853 var moreLink = $('<span class="more-link">+ ' + (items.length - limit) + ' more</span>'); 854 moreLink.on('click', function () { 855 $(containerSelector).children().show(); 856 $(this).remove(); 857 }); 858 $(containerSelector).append(moreLink); 859 } 860 } 861 862 // ---------- Category (sub-type) checkboxes: populate for the 863 // currently selected top-level property type ---------- 864 function populateCategoryCheckboxes(containerSelector, checkboxClass) { 865 $(containerSelector).empty(); 866 var items = []; 867 buySellPropertyTypes.forEach(function(property_type) { 868 if (property_type.id == property_type_ids[0]) { 869 property_type.sub_property_type.forEach(function(sub_category) { 870 var wrap = $('<label class="bl-check-line">'); 871 var checkbox = $('<input type="checkbox" class="' + checkboxClass + '" value="' + sub_category.id + '">'); 872 checkbox.prop('checked', selected_category_id.indexOf(String(sub_category.id)) !== -1 || selected_category_id.indexOf(sub_category.id) !== -1); 873 wrap.append(checkbox).append(document.createTextNode(sub_category.name)); 874 items.push(wrap); 875 }); 876 } 877 }); 878 appendWithShowMore(containerSelector, items, 5); 879 } 880 populateCategoryCheckboxes('#category-container', 'sub_category_checkbox'); 881 populateCategoryCheckboxes('#category_container1', 'sub_category_checkbox1'); 882 if (selected_category_id.length > 0) { 883 selected_category_id.forEach(function(cat_id){ category_ids.push('' + cat_id); }); 884 } 885 886 $('#category-container').on('change', '.sub_category_checkbox', function() { 887 toggleInArray(category_ids, $(this).val(), $(this).is(':checked')); 888 offset = 0; 889 filteredProperties(offset, 'fromfunction'); 890 }); 891 $('#category_container1').on('change', '.sub_category_checkbox1', function() { 892 toggleInArray(category_ids, $(this).val(), $(this).is(':checked')); 893 }); 894 895 function refreshCategoryForPropertyType() { 896 category_ids = []; 897 populateCategoryCheckboxes('#category-container', 'sub_category_checkbox'); 898 populateCategoryCheckboxes('#category_container1', 'sub_category_checkbox1'); 899 } 900 901 $('#selected_property_type').val(selected_property_type_id).on('change', function() { 902 property_type_ids = ["" + $(this).val()]; 903 $('#selected_property_type_mobile').val($(this).val()); 904 refreshCategoryForPropertyType(); 905 offset = 0; 906 filteredProperties(offset, 'fromfunction'); 907 }); 908 $('#selected_property_type_mobile').val(selected_property_type_id).on('change', function() { 909 property_type_ids = ["" + $(this).val()]; 910 $('#selected_property_type').val($(this).val()); 911 refreshCategoryForPropertyType(); 912 offset = 0; 913 filteredProperties(offset, 'fromfunction'); 914 }); 915 916 // ---------- Furnish type checkboxes ---------- 917 function populateFurnishCheckboxes(containerSelector, checkboxClass) { 918 furnishTypes.forEach(function(furnish) { 919 var wrap = $('<label class="bl-check-line">'); 920 var checkbox = $('<input type="checkbox" class="' + checkboxClass + '" value="' + furnish.id + '">'); 921 checkbox.prop('checked', selected_furnished_id.indexOf(String(furnish.id)) !== -1 || selected_furnished_id.indexOf(furnish.id) !== -1); 922 wrap.append(checkbox).append(document.createTextNode(furnish.name)); 923 $(containerSelector).append(wrap); 924 }); 925 } 926 populateFurnishCheckboxes('#furnish-container', 'furnish-checkbox'); 927 populateFurnishCheckboxes('#furnish_container1', 'furnish-checkbox-mobile'); 928 if (selected_furnished_id.length > 0) {
929 selected_furnished_id.forEach(function(furn_id){ furnished_ids.push('' + furn_id); }); 930 } 931 932 $('#furnish-container').on('change', '.furnish-checkbox', function () { 933 toggleInArray(furnished_ids, $(this).val(), $(this).is(':checked')); 934 offset = 0; 935 filteredProperties(offset, 'fromfunction'); 936 }); 937 $('#furnish_container1').on('change', '.furnish-checkbox-mobile', function () { 938 toggleInArray(furnished_ids, $(this).val(), $(this).is(':checked')); 939 }); 940 941 // ---------- Generic helper: toggle a value in/out of an array ---------- 942 function toggleInArray(arr, value, checked) { 943 if (checked) { 944 if (arr.indexOf(value) === -1) arr.push(value); 945 } else { 946 var index = arr.indexOf(value); 947 if (index !== -1) arr.splice(index, 1); 948 } 949 } 950 951 // Property Status 952 $('.construction-status-checkbox').on('change', function () { toggleInArray(construction_status_ids, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 953 $('.construction-status-checkbox-mobile').on('change', function () { toggleInArray(construction_status_ids, $(this).val(), $(this).is(':checked')); }); 954 // Purchase Type 955 $('.selling-type-checkbox').on('change', function () { toggleInArray(selling_type_ids, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 956 $('.selling-type-checkbox-mobile').on('change', function () { toggleInArray(selling_type_ids, $(this).val(), $(this).is(':checked')); }); 957 // RERA 958 $('.rera-status-checkbox').on('change', function () { toggleInArray(rera_status_ids, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 959 $('.rera-status-checkbox-mobile').on('change', function () { toggleInArray(rera_status_ids, $(this).val(), $(this).is(':checked')); }); 960 // Listed By 961 $('.listed-by-checkbox').on('change', function () { toggleInArray(listed_by_types, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 962 $('.listed-by-checkbox-mobile').on('change', function () { toggleInArray(listed_by_types, $(this).val(), $(this).is(':checked')); }); 963 // Property Age 964 $('.property-age-checkbox').on('change', function () { toggleInArray(age_of_property_ids, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 965 $('.property-age-checkbox-mobile').on('change', function () { toggleInArray(age_of_property_ids, $(this).val(), $(this).is(':checked')); }); 966 967 // Amenities (real filter against buy_sell_property_amenities / covered_parking+open_parking, 968 // matches the property must have ALL selected amenities - see applyAmenityFilters() server-side) 969 $('.amenity-checkbox').on('change', function () { toggleInArray(amenity_ids, $(this).val(), $(this).is(':checked')); offset = 0; filteredProperties(offset, 'fromfunction'); }); 970 $('.amenity-checkbox-mobile').on('change', function () { toggleInArray(amenity_ids, $(this).val(), $(this).is(':checked')); }); 971 $('#amenitiesMoreLink').on('click', function () { 972 $('#amenities-container .bl-more-hidden').show(); 973 $(this).remove(); 974 }); 975 $('#amenitiesMoreLinkMobile').on('click', function () { 976 $('#amenities-container-mobile .bl-more-hidden').show(); 977 $(this).remove(); 978 }); 979 980 // "+ 5 more" reveals the hidden 6-10 BHK chips (matches the new HTML 981 // design's "+ N more" pattern) 982 $('#bedroomMoreLink').on('click', function () { 983 $('#bedroom-container .bl-more-hidden').show(); 984 $(this).remove(); 985 }); 986 $('#bedroomMoreLinkMobile').on('click', function () { 987 $('#bedroom-container-mobile .bl-more-hidden').show(); 988 $(this).remove(); 989 }); 990 991 // Bedrooms (new filter, wired to the real `bedroom` column) 992 $('.bedroom-chip').on('click', function () { 993 $(this).toggleClass('active'); 994 toggleInArray(bedroom_ids, $(this).data('value').toString(), $(this).hasClass('active')); 995 offset = 0; 996 filteredProperties(offset, 'fromfunction'); 997 }); 998 $('.bedroom-chip-mobile').on('click', function () { 999 $(this).toggleClass('active'); 1000 toggleInArray(bedroom_ids, $(this).data('value').toString(), $(this).hasClass('active')); 1001 }); 1002 1003 // Area 1004 $('#minAreaInput, #maxAreaInput').on('change', function () { min_area = $('#minAreaInput').val(); max_area = $('#maxAreaInput').val(); offset = 0; filteredProperties(offset, 'fromfunction'); }); 1005 $('#minAreaInput1, #maxAreaInput1').on('change', function () { min_area = $('#minAreaInput1').val(); max_area = $('#maxAreaInput1').val(); }); 1006 1007 // Apply mobile-deferred filters 1008 $('.btn_apply_filters').on('click', function () { offset = 0; filteredProperties(offset, 'fromfunction'); }); 1009 1010 // Clear all filters 1011 $('#clearFilters').on('click', function () { 1012 $('.construction-status-checkbox, .construction-status-checkbox-mobile,' + 1013 '.selling-type-checkbox, .selling-type-checkbox-mobile,' + 1014 '.rera-status-checkbox, .rera-status-checkbox-mobile,' + 1015 '.listed-by-checkbox, .listed-by-checkbox-mobile,' + 1016 '.property-age-checkbox, .property-age-checkbox-mobile,' + 1017 '.furnish-checkbox, .furnish-checkbox-mobile,' + 1018 '.sub_category_checkbox, .sub_category_checkbox1,' + 1019 '.amenity-checkbox, .amenity-checkbox-mobile').prop('checked', false); 1020 $('.bedroom-chip, .bedroom-chip-mobile').removeClass('active'); 1021 1022 construction_status_ids = []; selling_type_ids = []; rera_status_ids = []; 1023 listed_by_types = []; age_of_property_ids = []; furnished_ids = []; 1024 category_ids = []; bedroom_ids = []; amenity_ids = []; 1025 min_area = ""; max_area = ""; 1026 $('#minAreaInput, #minAreaInput1, #maxAreaInput, #maxAreaInput1').val(''); 1027 updated_since = "all"; 1028 $('#updated_dates').val('all'); 1029 $('#updatedDateAll').prop('checked', true); 1030 1031 min_price = 0; max_price = 1000000000; 1032 $('#budgetMin, #budgetMinMobile').val('0'); 1033 $('#budgetMax, #budgetMaxMobile').val('1000000000'); 1034 1035 offset = 0; 1036 filteredProperties(offset, 'fromfunction'); 1037 }); 1038 1039 // Sort 1040 $('#sort_by').on('change', function () { 1041 var v = $(this).val(); 1042 if (v == "Recently added") { sortBy = "updated_at"; sortByOrder = "desc"; } 1043 else if (v == "Price: Low to High") { sortBy = "price"; sortByOrder = "asc"; } 1044 else if (v == "Price: High to Low") { sortBy = "price"; sortByOrder = "desc"; } 1045 else { sortBy = "distance"; sortByOrder = "asc"; } 1046 offset = 0; 1047 filteredProperties(offset, 'fromfunction'); 1048 }); 1049 $('#sort_by_mobile').on('click', function () { 1050 var v = $('input[name="sortOption"]:checked').val(); 1051 if (v == "newest") { sortBy = "updated_at"; sortByOrder = "desc"; } 1052 else if (v == "price-low") { sortBy = "price"; sortByOrder = "asc"; } 1053 else if (v == "price-high") { sortBy = "price"; sortByOrder = "desc"; } 1054 else { sortBy = "distance"; sortByOrder = "asc"; } 1055 offset = 0; 1056 filteredProperties(offset, 'fromfunction'); 1057 }); 1058 1059 // Updated Since 1060 $('#updated_dates').on('change', function () { updated_since = $(this).val(); offset = 0; filteredProperties(offset, 'fromfunction'); }); 1061 $('.btn-done-updated-dates').on('click', function () { 1062 var selected = $('input[name="updatedDateOption"]:checked').val(); 1063 var mobileMap = { 'all': 'all', 'last-7-days': '7', 'last-15-days': '15', 'last-1-month': '30' }; 1064 updated_since = mobileMap[selected] || 'all'; 1065 offset = 0; 1066 filteredProperties(offset, 'fromfunction'); 1067 }); 1068 1069 // Show more / results fetch 1070 $(document).on('click', '.btn-show-more', function () { filteredProperties($(this).data('offset')); }); 1071 1072 function filteredProperties(offsetArg, coming_from) { 1073 $('.btn-show-more').prop('disabled', true).text('Loading...'); 1074 if (offsetArg == 0) { $('#propertyListings').empty(); } 1075 $.ajax({ 1076 url: 'https://www.letsrentz.com/buy/filteredProperties', 1077 method: 'GET', 1078 data: { 1079 preview: 1, 1080 property_type_ids: property_type_ids, 1081 category_ids: category_ids, 1082 furnished_id: furnished_ids, 1083 bedroom_ids: bedroom_ids, 1084 offset: offsetArg, 1085 min_price: min_price, 1086 max_price: max_price, 1087 sortBy: sortBy, 1088 sortByOrder: sortByOrder, 1089 lati: lati, 1090 longi: longi, 1091 user_id: user_id, 1092 construction_status_ids: construction_status_ids, 1093 selling_type_ids: selling_type_ids,
1094 rera_status_ids: rera_status_ids, 1095 listed_by_types: listed_by_types, 1096 age_of_property_ids: age_of_property_ids, 1097 min_area: min_area, 1098 max_area: max_area, 1099 updated_since: updated_since, 1100 amenity_ids: amenity_ids, 1101 address: address 1102 }, 1103 success: function (data) { 1104 if (data.status) { 1105 if (offsetArg == 0) { $('#propertyListings').html(data.html); } 1106 else { $('#propertyListings').append(data.html); } 1107 } 1108 $('#mobileFilterResultsCount').text(data.resultCount); 1109 $('.property-results').text(data.resultCount + "+ " + seaching_title); 1110 $('.btn-show-more').data('offset', data.offset); 1111 if (!data.has_more) { $('.btn-show-more').hide(); } 1112 else { $('.btn-show-more').prop('disabled', false).text('Show More').show(); } 1113 }, 1114 error: function (error) { console.log(error); } 1115 }); 1116 } 1117 1118 offset = 0; 1119 filteredProperties(offset, 'direct'); 1120 1121 // Save / wishlist heart button - mirrors the working .lp-card-save 1122 // handler already live on the redesigned rent listing page 1123 // (front/property/listing.blade.php), since this button has no 1124 // handler anywhere in the current live buy-listing page. 1125 $(document).off('click', '.bl-card-save').on('click', '.bl-card-save', function (e) { 1126 e.preventDefault(); 1127 e.stopPropagation(); 1128 if (user_data == null) { openLoginPopup(event); return; } 1129 var btn = $(this); 1130 $.ajax({ 1131 type: 'POST', 1132 url: 'https://www.letsrentz.com/wishListProperty', 1133 headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') }, 1134 data: { property_id: btn.attr('data-property-id'), type: btn.attr('data-property-type') }, 1135 success: function (response) { 1136 if (response.status) { 1137 btn.toggleClass('active', response.is_wishlist == 1); 1138 btn.attr('data-property-status', response.is_wishlist); 1139 } 1140 } 1141 }); 1142 }); 1143}); 1144</script>
1144 1145 1146 <footer class="hp-footer"> 1147 <div class="hp-footer-grid"> 1148 <div> 1149 <a href="https://www.letsrentz.com" class="hp-footer-logo-link"> 1150 <img src="https://www.letsrentz.com/front/assets/images/listing_images/footer.png" alt="Lets Rentz" class="hp-footer-logo"> 1151 </a> 1152 <p class="hp-footer-tagline">You are one click away from your next dream property. Verified listings, zero brokerage on rent.</p> 1153 <div class="hp-footer-socials"> 1154 <a href="https://www.instagram.com/letsrentz/" target="_blank" class="hp-footer-social"><i class="bi bi-instagram"></i></a> 1155 <a href="https://twitter.com/LetsRentz" target="_blank" class="hp-footer-social"><i class="bi bi-twitter"></i></a> 1156 <a href="https://www.facebook.com/letsrentz/" target="_blank" class="hp-footer-social"><i class="bi bi-facebook"></i></a> 1157 <a href="https://www.linkedin.com/company/lets-rentz/" target="_blank" class="hp-footer-social"><i class="bi bi-linkedin"></i></a> 1158 <a href="https://www.youtube.com/channel/UCr3A91ZLRcN0tFUzThC543g" target="_blank" class="hp-footer-social"><i class="bi bi-youtube"></i></a> 1159 </div> 1160 </div> 1161 <div class="hp-footer-cols-wrap"> 1162 <div class="hp-footer-col-company"> 1163 <div class="hp-footer-col-head">Company</div> 1164 <div class="hp-footer-links"> 1165 <a href="https://www.letsrentz.com">Home</a> 1166 <a href="https://www.letsrentz.com/tenant/pgplan">PG Relax Plan</a> 1167 </div> 1168 </div> 1169 <div class="hp-footer-col-explore"> 1170 <div class="hp-footer-col-head">Explore</div> 1171 <div class="hp-footer-links"> 1172 1173 <a href="https://www.letsrentz.com">Rent a home</a> 1174 <a href="https://www.letsrentz.com/buy/Flat-for-sale-in-Sector-71-Mohali/1/30.7046/76.7179">Buy a property</a> 1175 <a href="https://www.letsrentz.com/post-property-for-rent">List your property</a> 1176 <a href="https://www.letsrentz.com/propertysearch/PG-in-Sector-71-Mohali/3/30.7046/76.7179">PG & co-living</a> 1177 <a href="https://www.letsrentz.com/propertysearch/Commercial-for-rent-in-Sector-71-Mohali/5/30.7046/76.7179">Commercial</a> 1178 </div> 1179 </div> 1180 <div class="hp-footer-col-legal"> 1181 <div class="hp-footer-col-head">Legal</div> 1182 <div class="hp-footer-links"> 1183 <a href="https://www.letsrentz.com/terms">Terms of Use</a> 1184 <a href="https://www.letsrentz.com/privacy">Privacy Policy</a> 1185 <a href="https://www.letsrentz.com/sitemap">Sitemap</a> 1186 <a href="https://www.letsrentz.com/faq">FAQ</a> 1187 <a href="https://blog.letsrentz.com/" target="_blank">Blog</a> 1188 </div> 1189 </div> 1190 </div> 1191 <div class="hp-footer-contact-col"> 1192 <div class="hp-footer-col-head">Contact us</div> 1193 <div class="hp-footer-contact"><i class="bi bi-telephone-fill"></i> +91 8847073862<br><i class="bi bi-envelope-fill"></i> <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4d3e383d3d223f390d2128393e3f28233937632e2220">[email protected]</a></div> 1194 <div class="hp-footer-apps"> 1195 <a href="https://play.google.com/store/apps/details?id=com.decodershub.letsrentz" target="_blank" class="hp-footer-app-btn"><i class="bi bi-google-play"></i> Play</a> 1196 <a href="https://apps.apple.com/us/app/lets-rentz-make-it-easy/id1634945591" target="_blank" class="hp-footer-app-btn"><i class="bi bi-apple"></i> Store</a> 1197 </div> 1198 </div> 1199 </div> 1200 <div class="hp-footer-bottom">© 2022â2026 Lets Rentz Innovate Private Limited. All rights reserved.</div> 1201 </footer> 1202 1203 1204 <nav class="hp-bottom-nav"> 1205 <a href="https://www.letsrentz.com" class="hp-bottom-nav-item "><i class="bi bi-house-door-fill"></i><span>Home</span></a> 1206 <a href="https://www.letsrentz.com/propertysearch" class="hp-bottom-nav-item active"><i class="bi bi-search"></i><span>Search</span></a> 1207 <a href="https://www.letsrentz.com/savedpropertylisting" class="hp-bottom-nav-item "><i class="bi bi-heart"></i><span>Saved</span></a> 1208 <a href="https://www.letsrentz.com/post-property-for-rent"
1208class="hp-bottom-nav-item "><i class="bi bi-plus-circle"></i><span>Post</span></a> 1209 <button type="button" class="hp-bottom-nav-item" data-hp-login-trigger style="border:none;background:none;font-family:inherit;"><i class="bi bi-person-circle"></i><span>Account</span></button> 1210 </nav> 1211 1212 1213 1214 <!-- Phone number modal --> 1215 <div id="loginModalOverlay" class="property-selector-overlay"> 1216 <div class="property-selector" onclick="event.stopPropagation()"> 1217 <button class="property-close-btn" onclick="closeLoginModal(event)"><i class="bi bi-x-lg"></i></button> 1218 <div class="selector-content"> 1219 <div class="mobile-header"> 1220 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1221 </div> 1222 <div class="mobile-card"> 1223 <div class="ob-progress"></div> 1224 <div class="ob-step-kicker"></div> 1225 <h5 class="mobile-card-title">What's your number?</h5> 1226 <p class="ob-sub">We'll text a 4-digit code. Nothing else — no calls, no spam.</p> 1227 <div class="login-phone-wrapper"> 1228 <div class="country-code">+91</div> 1229 <input type="tel" id="loginPhoneNumber" class="login-phone-input" maxlength="10" placeholder="98765 43210"> 1230 </div> 1231 <div class="login-phone-error">Oh no! This mobile number is invalid</div> 1232 <button type="button" class="continue-btn btn_login" onclick="loginButton()">Send code</button> 1233 <p class="mobile-terms-text">By clicking, I accept the <a href="https://www.letsrentz.com/terms">Terms of Service</a> and <a href="https://www.letsrentz.com/privacy">Privacy Policy</a></p> 1234 </div> 1235 </div> 1236 </div> 1237 </div> 1238 1239 <!-- OTP verification modal --> 1240 <div id="otpModalOverlay" class="property-selector-overlay"> 1241 <div class="property-selector" onclick="event.stopPropagation()"> 1242 <button class="property-close-btn" onclick="closeOTPModal(event)"><i class="bi bi-x-lg"></i></button> 1243 <div class="selector-content"> 1244 <div class="mobile-header"> 1245 <h5 class="login-modal-heading">Verify Your Mobile Number</h5> 1246 </div> 1247 <div class="mobile-card"> 1248 <div class="ob-progress"></div> 1249 <div class="ob-step-kicker"></div> 1250 <h5 class="mobile-card-title">Verify it's you</h5> 1251 <p class="otp-subtitle">Sent to <strong id="otpMobileNumber">+91 XXXXX XXXXX</strong> <button type="button" class="otp-edit-number-btn" onclick="editOTPPhoneNumber(event)" aria-label="Edit phone number"><i class="bi bi-pencil-fill"></i></button></p> 1252 <div class="otp-input-wrapper"> 1253 <input type="text" id="otp1" maxlength="1" inputmode="numeric" pattern="[0-9]*" class="otp-input"> 1254 <input type="text" maxlength="1" inputmode="numeric" pattern="[0-9]*" class="otp-input"> 1255 <input type="text" maxlength="1" inputmode="numeric" pattern="[0-9]*" class="otp-input"> 1256 <input type="text" maxlength="1" inputmode="numeric" pattern="[0-9]*" class="otp-input"> 1257 </div> 1258 <div class="otp_verification_error" style="display:none;color:#e53935;font-size:12px;margin-top:-14px;margin-bottom:14px;"><p></p></div> 1259 <div class="otp-resend-section"> 1260 <span>Didn't receive code? </span> 1261 <a href="javascript:void(0)" class="otp-resend-link otp-resend-btn" onclick="resendOTP(event)">Resend OTP</a> 1262 </div> 1263 <button class="otp_continue_btn continue-btn" onclick="otpVerificationBtn()">Verify</button> 1264 <p class="popup-terms-text">By clicking, I accept the <a href="https://www.letsrentz.com/terms">Terms of Service</a> and <a href="https://www.letsrentz.com/privacy">Privacy Policy</a></p> 1265 </div> 1266 </div> 1267 </div> 1268 </div> 1269 1270 <!-- Full name modal (new users only) --> 1271 <div id="nameModalOverlay" class="property-selector-overlay" onclick="closeNameModal(event)"> 1272 <div class="property-selector" onclick="event.stopPropagation()"> 1273 <button class="property-close-btn" onclick="closeNameModal(event)"><i class="bi bi-x-lg"></i></button> 1274 <div class="selector-content"> 1275 <div class="mobile-header"> 1276 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1277 </div> 1278 <div class="mobile-card"> 1279 <div class="ob-progress"></div> 1280 <div class="ob-step-kicker"></div> 1281 <h5 class="mobile-card-title">What should we call you?</h5> 1282 <p class="ob-sub">Owners see this when you reach out — first name is fine.</p> 1283 <div class="name-input-wrapper"> 1284 <input type="text" id="register_full_name" class="name-input" placeholder="Your name"> 1285 <div class="login-error-name-message" style="display:none;">Please enter your full name</div> 1286 </div> 1287 <button type="button" class="continue-btn name_continue_btn">Continue</button> 1288 <p class="onboarding-terms">By clicking, I accept the <a href="https://www.letsrentz.com/terms" class="onboarding-link">Terms of Service</a> and <a href="https://www.letsrentz.com/privacy" class="onboarding-link">Privacy Policy</a></p> 1289 </div> 1290 </div> 1291 </div> 1292 </div> 1293 1294 <!-- Purpose modal (search / own a property / broker - resolves
1295 user_type directly, see .purpose_continue_btn in template.blade.php. 1296 Tenant vs. Property Buyer used to be a separate account-type screen; 1297 both already posted the same 'user' value there, so folding it into 1298 this one screen doesn't lose any real distinction. --> 1299 <div id="purposeModalOverlay" class="property-selector-overlay"> 1300 <div class="property-selector" onclick="event.stopPropagation()"> 1301 <button class="property-close-btn" onclick="closePurposeModal(event)"><i class="bi bi-x-lg"></i></button> 1302 <div class="selector-content"> 1303 <div class="mobile-header"> 1304 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1305 </div> 1306 <div class="mobile-card"> 1307 <div class="ob-progress"></div> 1308 <div class="ob-step-kicker"></div> 1309 <h5 class="mobile-card-title">What brings you here?</h5> 1310 <p class="ob-sub">Just so the next screens ask the right things.</p> 1311 <div class="property-options"> 1312 <div class="property-item"> 1313 <input type="radio" id="purposeSearch" name="userPurpose" value="search"> 1314 <label for="purposeSearch"> 1315 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m20 20-4.5-4.5"/></svg> 1316 Searching for a home 1317 </label> 1318 </div> 1319 <div class="property-item"> 1320 <input type="radio" id="purposeOwner" name="userPurpose" value="owner"> 1321 <label for="purposeOwner"> 1322 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="8" cy="15" r="3.2"/><path d="M10.3 12.7 19 4"/><path d="M15.5 8.5 18 11"/><path d="M17.5 6.5 20 9"/></svg> 1323 I own a property to list 1324 </label> 1325 </div> 1326 <div class="property-item"> 1327 <input type="radio" id="purposeBroker" name="userPurpose" value="broker"> 1328 <label for="purposeBroker"> 1329 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3.5" y="8" width="17" height="11" rx="1.5"/><path d="M8.5 8V6a2 2 0 0 1 2-2h3a2 2 0 0 1 2 2v2"/><path d="M3.5 13h17"/></svg> 1330 I'm a broker / agent 1331 </label> 1332 </div> 1333 </div> 1334 <button class="purpose_continue_btn continue-btn">Continue</button> 1335 <p class="onboarding-terms">By clicking, I accept the <a href="https://www.letsrentz.com/terms" class="onboarding-link">Terms of Service</a> and <a href="https://www.letsrentz.com/privacy" class="onboarding-link">Privacy Policy</a></p> 1336 </div> 1337 </div> 1338 </div> 1339 </div> 1340 1341 <!-- Account type modal --> 1342 <div id="accountTypeModalOverlay" class="property-selector-overlay"> 1343 <div class="property-selector" onclick="event.stopPropagation()"> 1344 <button class="property-close-btn" onclick="closeAccountTypeModal(event)"><i class="bi bi-x-lg"></i></button> 1345 <div class="selector-content"> 1346 <div class="mobile-header"> 1347 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1348 </div> 1349 <div class="mobile-card"> 1350 <h5 class="mobile-card-title">Create Account as:</h5> 1351 <div class="property-options"> 1352 <div class="property-item account-type-item-search"> 1353 <input type="radio" id="accountTypeTenant" name="accountType" value="user"> 1354 <label for="accountTypeTenant">Tenant</label> 1355 </div> 1356 <div class="property-item account-type-item-search"> 1357 <input type="radio" id="accountTypeBuyer" name="accountType" value="user"> 1358 <label for="accountTypeBuyer">Property Buyer</label> 1359 </div> 1360 <div class="property-item account-type-item-post"> 1361 <input type="radio" id="accountTypeOwner" name="accountType" value="owner"> 1362 <label for="accountTypeOwner">Property Owner</label> 1363 </div> 1364 <div class="property-item account-type-item-post"> 1365 <input type="radio" id="accountTypeBroker" name="accountType" value="broker"> 1366 <label for="accountTypeBroker">Broker (Dealer)</label> 1367 </div> 1368 </div> 1369 <button class="account_type_continue_btn continue-btn">Continue</button> 1370 <p class="onboarding-terms">By clicking, I accept the <a href="https://www.letsrentz.com/terms" class="onboarding-link">Terms of Service</a> and <a href="https://www.letsrentz.com/privacy" class="onboarding-link">Privacy Policy</a></p> 1371 </div> 1372 </div> 1373 </div> 1374 </div> 1375 1376 <!-- Preferred location modal --> 1377 <div id="locationModalOverlay" class="property-selector-overlay"> 1378 <div class="property-selector" onclick="event.stopPropagation()"> 1379 <div class="selector-content"> 1380 <div class="mobile-header"> 1381 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1382 </div> 1383 <div class="mobile-card"> 1384 <div class="ob-progress"></div> 1385 <div class="ob-step-kicker"></div> 1386 <h6 class="login-modal-title">Where are you looking?</h6> 1387 <p class="location-modal-description">A city or locality is enough to start.</p> 1388 <div class="location-search-wrapper">
1389 <span class="location-search-icon" aria-hidden="true"> 1390 <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> 1391 <path d="M10 18C10 18 16 12.5 16 7.5C16 4.18629 13.3137 1.5 10 1.5C6.68629 1.5 4 4.18629 4 7.5C4 12.5 10 18 10 18Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> 1392 <circle cx="10" cy="7.5" r="2" stroke="currentColor" stroke-width="1.5"/> 1393 </svg> 1394 </span> 1395 <input type="text" id="locationInput" class="location-search-input autocomplete-google" placeholder="Sector 88A, Gurugram"> 1396 </div> 1397 <form class="login-modal-form"> 1398 <button type="button" class="login-continue-btn-a" onclick="updateLocation(this);">Continue</button> 1399 </form> 1400 </div> 1401 </div> 1402 </div> 1403 </div> 1404 1405 <!-- Property type modal (tenants only) --> 1406 <div id="propertyTypeModalOverlay" class="property-selector-overlay"> 1407 <div class="property-selector" onclick="event.stopPropagation()"> 1408 <div class="selector-content"> 1409 <div class="mobile-header"> 1410 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1411 </div> 1412 <div class="mobile-card"> 1413 <div class="ob-progress"></div> 1414 <div class="ob-step-kicker"></div> 1415 <h2>What kind of place?</h2> 1416 <p class="ob-sub">We'll only show you these going forward.</p> 1417 <div class="property-options"> 1418 <div class="property-item"> 1419 <input type="radio" id="property_1" name="property_type" value="1"> 1420 <label for="property_1"> 1421 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M4 11 12 4l8 7"/><path d="M6 10v9h12v-9"/><path d="M10 19v-5h4v5"/></svg> 1422 House 1423 </label> 1424 </div> 1425 <div class="property-item"> 1426 <input type="radio" id="property_2" name="property_type" value="2"> 1427 <label for="property_2"> 1428 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M4 11 12 4l8 7"/><path d="M6 10v9h12v-9"/><path d="M10 19v-5h4v5"/></svg> 1429 Flat 1430 </label> 1431 </div> 1432 <div class="property-item"> 1433 <input type="radio" id="property_3" name="property_type" value="3"> 1434 <label for="property_3"> 1435 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M4 11 12 4l8 7"/><path d="M6 10v9h12v-9"/><path d="M10 19v-5h4v5"/></svg> 1436 PG 1437 </label> 1438 </div> 1439 <div class="property-item"> 1440 <input type="radio" id="property_5" name="property_type" value="5"> 1441 <label for="property_5"> 1442 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M4 11 12 4l8 7"/><path d="M6 10v9h12v-9"/><path d="M10 19v-5h4v5"/></svg> 1443 Commercial 1444 </label> 1445 </div> 1446 </div> 1447 <button class="property_type_continue_btn continue-btn">Continue</button> 1448 </div> 1449 </div> 1450 </div> 1451 </div> 1452 1453 <!-- Category (sub property type) modal --> 1454 <div id="categoryModalOverlay" class="property-selector-overlay"> 1455 <div class="property-selector" onclick="event.stopPropagation()"> 1456 <div class="selector-content"> 1457 <div class="mobile-header"> 1458 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1459 </div> 1460 <div class="mobile-card"> 1461 <div class="ob-progress"></div> 1462 <div class="ob-step-kicker"></div> 1463 <h2>How big?</h2> 1464 <p class="ob-sub">Roughly is fine — you can widen this later.</p> 1465 <div id="categoryOptions" class="property-options"></div> 1466 <button class="category_continue_btn continue-btn">Continue</button> 1467 </div> 1468 </div> 1469 </div> 1470 </div> 1471 1472 <!-- Tenant type modal --> 1473 <div id="tenantTypeModalOverlay" class="property-selector-overlay"> 1474 <div class="property-selector" onclick="event.stopPropagation()"> 1475 <div class="selector-content"> 1476 <div class="mobile-header"> 1477 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1478 </div> 1479 <div class="mobile-card"> 1480 <div class="ob-progress"></div> 1481 <div class="ob-step-kicker"></div> 1482 <h2>Who's moving in?</h2> 1483 <p class="ob-sub">The one thing we can't tell from the listing itself.</p> 1484 <div class="property-options"> 1485 <div class="property-item"> 1486 <input type="radio" id="req_tenant_family" name="tenant_type" value="family"> 1487 <label for="req_tenant_family"> 1488 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="8" cy="7" r="2.6"/><circle cx="16" cy="7" r="2.6"/><path d="M3 20c0-3 2.2-5 5-5s5 2 5 5"/><path d="M11 20c0-3 2.2-5 5-5s5 2 5 5" stroke-opacity=".55"/></svg> 1489 Family 1490 </label> 1491 </div> 1492 <div class="property-item"> 1493 <input type="radio" id="req_tenant_boys" name="tenant_type" value="boys"> 1494 <label for="req_tenant_boys"> 1495 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="7" r="3.2"/>
1495<path d="M5 20c0-3.8 3-6.5 7-6.5s7 2.7 7 6.5"/></svg> 1496 Boys 1497 </label> 1498 </div> 1499 <div class="property-item"> 1500 <input type="radio" id="req_tenant_girls" name="tenant_type" value="girls"> 1501 <label for="req_tenant_girls"> 1502 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="12" cy="7" r="3.2"/><path d="M12 10.2 8.6 20h6.8L12 10.2Z"/></svg> 1503 Girls 1504 </label> 1505 </div> 1506 <div class="property-item"> 1507 <input type="radio" id="req_tenant_livin_couple" name="tenant_type" value="livin_couple"> 1508 <label for="req_tenant_livin_couple"> 1509 <svg data-ob-icon viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><circle cx="9" cy="7" r="2.6"/><circle cx="15" cy="7" r="2.6"/><path d="M4 20c0-3 2.2-5 5-5M20 20c0-3-2.2-5-5-5"/></svg> 1510 Livin Couple 1511 </label> 1512 </div> 1513 </div> 1514 <button class="tenant_type_continue_btn continue-btn">Finish</button> 1515 </div> 1516 </div> 1517 </div> 1518 </div> 1519 1520 <!-- Budget estimate modal --> 1521 <div id="budgetEstimateModalOverlay" class="property-selector-overlay"> 1522 <div class="property-selector" onclick="event.stopPropagation()"> 1523 <div class="selector-content"> 1524 <div class="mobile-header"> 1525 <h5 class="login-modal-heading">Lets Find A House For You</h5> 1526 </div> 1527 <div class="mobile-card"> 1528 <div class="ob-progress"></div> 1529 <div class="ob-step-kicker"></div> 1530 <h2>What's your budget?</h2> 1531 <p class="ob-sub">Monthly rent, roughly.</p> 1532 <div class="property-options"> 1533 <div class="property-item"><input type="radio" id="budget_8k" name="budget_estimate" value="8000"><label for="budget_8k">Below 8K</label></div> 1534 <div class="property-item"><input type="radio" id="budget_8k_10k" name="budget_estimate" value="10000"><label for="budget_8k_10k">8k - 10k</label></div> 1535 <div class="property-item"><input type="radio" id="budget_10k_15k" name="budget_estimate" value="15000"><label for="budget_10k_15k">10k - 15k</label></div> 1536 <div class="property-item"><input type="radio" id="budget_15k_20k" name="budget_estimate" value="20000"><label for="budget_15k_20k">15k - 20k</label></div> 1537 <div class="property-item"><input type="radio" id="budget_20k_25k" name="budget_estimate" value="25000"><label for="budget_20k_25k">20k - 25k</label></div> 1538 <div class="property-item"><input type="radio" id="budget_25k_30k" name="budget_estimate" value="30000"><label for="budget_25k_30k">25k - 30k</label></div> 1539 <div class="property-item"><input type="radio" id="budget_above_30k" name="budget_estimate" value="100000"><label for="budget_above_30k">Above 30k</label></div> 1540 </div> 1541 <button class="budget_estimate_continue_btn continue-btn">Finish</button> 1542 </div> 1543 </div> 1544 </div> 1545 </div> 1546 1547 <div class="lrv2-modal-overlay lrv2-scope" id="lrv2ModalOverlay"> 1548 <div class="lrv2-modal" role="dialog" aria-modal="true" aria-label="Log in or sign up" onclick="event.stopPropagation()"> 1549 <button type="button" class="lrv2-modal-close" aria-label="Close">×</button> 1550 <div class="lrv2-brand"> 1551 <img src="https://www.letsrentz.com/front/assets/images/loginv2-mark.png" alt="Lets Rentz"> 1552 <b>LetsRentz</b> 1553 </div> 1554 <div class="lrv2-steps"> 1555 <div class="lrv2-step" data-step="phone" data-cta-label="Continue"> 1556 1557 <h4 class="lrv2-h lrv2-h-desktop">Log in or <em>sign up</em></h4> 1558 <h2 class="lrv2-h lrv2-h-mobile">Your next <em>ghar</em> is closer than you think.</h2> 1559 <p class="lrv2-lede lrv2-lede-desktop">We'll text you a four-digit code. No passwords, ever.</p> 1560 <p class="lrv2-lede lrv2-lede-mobile">38,000+ homes across Tricity & NCR. One number gets you in.</p> 1561 <div class="lrv2-field">
1562 <span class="lrv2-cc">+91</span> 1563 <input type="tel" class="lrv2-phone-input" inputmode="numeric" autocomplete="tel-national" maxlength="10" placeholder="98765 43210" aria-label="Mobile number"> 1564 </div> 1565 1566 <p class="lrv2-fine lrv2-fine-desktop">By continuing you agree to our <a href="https://www.letsrentz.com/terms">Terms</a> & <a href="https://www.letsrentz.com/privacy">Privacy Policy</a></p> 1567</div> 1568 1569<div class="lrv2-step" data-step="otp" data-cta-label="Verify"> 1570 <h4 class="lrv2-h">Almost there â <em>just one code.</em></h4> 1571 <p class="lrv2-lede">Sent by SMS to <span data-otp-number>+91 __________</span> · WhatsApp fallback on resend.</p> 1572 <div class="lrv2-otp-row" role="group" aria-label="Enter the 4-digit code"> 1573 <input class="lrv2-otp" inputmode="numeric" pattern="[0-9]*" maxlength="1" autocomplete="one-time-code" aria-label="Digit 1"> 1574 <input class="lrv2-otp" inputmode="numeric" pattern="[0-9]*" maxlength="1" autocomplete="off" aria-label="Digit 2"> 1575 <input class="lrv2-otp" inputmode="numeric" pattern="[0-9]*" maxlength="1" autocomplete="off" aria-label="Digit 3"> 1576 <input class="lrv2-otp" inputmode="numeric" pattern="[0-9]*" maxlength="1" autocomplete="off" aria-label="Digit 4"> 1577 </div> 1578 <p class="lrv2-resend">Didn't get it? <button type="button" class="lrv2-resend-btn" disabled>Resend on WhatsApp · <span data-resend-timer>0:40</span></button></p> 1579 <button type="button" class="lrv2-edit-number"><i class="bi bi-pencil-fill" aria-hidden="true"></i> Edit number</button> 1580</div> 1581 1582 1583 1584 1585 1586<div class="lrv2-step" data-step="done" data-hide-cta="1"> 1587 <div class="lrv2-done"> 1588 <div class="lrv2-done-ring">â</div> 1589 <h4 class="lrv2-h">You're <em>in.</em></h4> 1590 <p class="lrv2-lede">Verified â pick up right where you left off.</p> 1591 <button type="button" class="lrv2-cta lrv2-cta-block" style="margin-top:22px" data-lrv2-modal-close>Continue browsing</button> 1592 </div> 1593</div> 1594 </div> 1595 <div class="lrv2-foot"> 1596 <div class="lrv2-dots" aria-hidden="true"></div> 1597 <button type="button" class="lrv2-cta">Continue <span>â</span></button> 1598 </div> 1599 </div> 1600</div> 1601 <div class="pp2-preview" id="ppInlineRoot"> 1602 <div class="pp-overlay" id="ppInlineOverlay" aria-hidden="true"> 1603 <div class="pp-card" role="dialog" aria-modal="true" aria-labelledby="ppInlineTitle"> 1604 1605 <button type="button" class="pp-close" id="ppInlineClose" aria-label="Close"> 1606 <svg width="15" height="15" viewBox="0 0 24 24" fill="none"><path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg> 1607 </button> 1608 1609 <div class="pp-head"> 1610 <span class="eyebrow">Step 1 of 5</span> 1611 <h2 id="ppInlineTitle">What would you like to do?</h2> 1612 <p>Pick an option to get started — we'll shape the rest of the form around it.</p> 1613 </div> 1614 1615 <div class="pp-body"> 1616 <div class="pp-block"> 1617 <span class="pp-label">Your purpose</span> 1618 <div class="intent-grid cols-2" role="radiogroup" aria-label="What would you like to do?"> 1619 1620 <label class="intent is-on" data-intent="rent"> 1621 <input type="radio" name="ppInlineIntent" value="rent" checked> 1622 <span class="tick"><svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M20 6 9 17l-5-5" stroke="currentColor" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span> 1623 <span class="ic"><svg width="26" height="26" viewBox="0 0 24 24" fill="none"><path d="M3.5 10.5 12 4l8.5 6.5V19a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 19v-8.5Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9.5 20.5v-6h5v6" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></span> 1624 <b>Rent your Property</b> 1625 <small>Find a tenant</small> 1626 </label> 1627 1628 <label class="intent" data-intent="sell"> 1629 <input type="radio" name="ppInlineIntent" value="sell" >
1630 <span class="tick"><svg width="13" height="13" viewBox="0 0 24 24" fill="none"><path d="M20 6 9 17l-5-5" stroke="currentColor" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/></svg></span> 1631 <span class="ic"><svg width="26" height="26" viewBox="0 0 24 24" fill="none"><path d="M4 20.5V9l6-4.5 6 4.5v11.5" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M16 12h4v8.5M2.5 20.5h19" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M8 12h4M8 16h4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg></span> 1632 <b>Sell your Property</b> 1633 <small>Find a buyer</small> 1634 </label> 1635 1636 1637 </div> 1638 </div> 1639 1640 <div class="pp-block"> 1641 <span class="pp-label">Property type</span> 1642 <h3>What are you posting?</h3> 1643 <p class="hint" id="ppInlineTypeHint">Select the type of property you want to put on rent.</p> 1644 1645 <div class="type-list" data-for-intent="rent" id="ppInlineTypesRent" role="radiogroup" aria-label="Type of property to rent" ></div> 1646 <div class="type-list" data-for-intent="sell" id="ppInlineTypesSell" role="radiogroup" aria-label="Type of property to sell" hidden></div> 1647 <p class="hint" id="ppInlineLoading">Loading property types…</p> 1648 </div> 1649 </div> 1650 1651 <div class="pp-foot"> 1652 <button type="button" class="btn btn-primary btn-block" id="ppInlineContinue" disabled> 1653 <span id="ppInlineContinueLabel">Continue</span> 1654 <svg width="17" height="17" viewBox="0 0 24 24" fill="none"><path d="M5 12h14M13 6l6 6-6 6" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg> 1655 </button> 1656 <p class="pp-terms">By continuing you agree to our <a href="#">Terms</a> & <a href="#">Posting Policy</a>.</p> 1657 </div> 1658 1659 </div> 1660 </div> 1661</div>
vendor: 110 bytes, lines 1661-1663
1661 1662 1663<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script>
1663<script> 1664(function(){ 1665 const overlay = document.getElementById('ppInlineOverlay'); 1666 const closeBtn = document.getElementById('ppInlineClose'); 1667 const contBtn = document.getElementById('ppInlineContinue'); 1668 const contLbl = document.getElementById('ppInlineContinueLabel'); 1669 const typeHint = document.getElementById('ppInlineTypeHint'); 1670 const loading = document.getElementById('ppInlineLoading'); 1671 const typeLists = { 1672 rent: document.getElementById('ppInlineTypesRent'), 1673 sell: document.getElementById('ppInlineTypesSell'), 1674 flatmate: document.getElementById('ppInlineTypesFlatmate') 1675 }; 1676 1677 const HINTS = { 1678 rent: 'Select the type of property you want to put on rent.', 1679 sell: 'Select the type of property you want to sell.', 1680 flatmate: 'Select the type of place you want to share.' 1681 }; 1682 1683 let loaded = false; 1684 1685 /* Match each type's name to a category-appropriate icon so the list 1686 reads visually the same as the standalone /post-property page instead 1687 of a single generic icon repeated for every row. First matching rule 1688 wins; unmatched names (rare custom categories) fall back to the flat 1689 (building) icon. */ 1690 const TYPE_ICONS = [ 1691 { test: /\bpg\b/i, svg: '<path d="M3 17v-4a2 2 0 0 1 2-2h11a3 3 0 0 1 3 3v3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M3 17h18M3 20v-3M21 20v-3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/><path d="M6.5 11V8.5A1.5 1.5 0 0 1 8 7h3a1.5 1.5 0 0 1 1.5 1.5V11" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>' }, 1692 { test: /\broom\b/i, svg: '<path d="M4 20V6.2a1 1 0 0 1 .8-1l9-1.8a1 1 0 0 1 1.2 1V20" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M3 20h18M12.2 12.4v1.4" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>' }, 1693 { test: /commercial|shop|office|warehouse/i, svg: '<path d="M4 9.5h16V20H4V9.5Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M3 9.5 5 4.5h14l2 5" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M9.5 20v-5.5h5V20" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>' }, 1694 { test: /plot|land/i, svg: '<rect x="3.5" y="9" width="17" height="10.5" rx="1.5" stroke="currentColor" stroke-width="1.7"/><path d="M8 9V4.5M8 4.5h7l-2 2.25 2 2.25H8" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>' }, 1695 { test: /house|villa|farmhouse/i, svg: '<path d="M3.5 10.5 12 4l8.5 6.5V20H3.5v-9.5Z" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/><path d="M10 20v-5h4v5" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/>' }, 1696 { test: /flat|apartment|floor/i, svg: '<rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 7h3M13 7h3M8 11h3M13 11h3M8 15h3M13 15h3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>' } 1697 ]; 1698 const DEFAULT_ICON_SVG = '<rect x="4" y="3" width="16" height="18" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 7h3M13 7h3M8 11h3M13 11h3M8 15h3M13 15h3" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>'; 1699 1700 function iconSvgFor(name){ 1701 const match = TYPE_ICONS.find(t => t.test.test(name)); 1702 return '<svg width="20" height="20" viewBox="0 0 24 24" fill="none">' + (match ? match.svg : DEFAULT_ICON_SVG) + '</svg>'; 1703 } 1704 1705 function rowHtml(intent, t, i){ 1706 return '<label class="type-row' + (i === 0 ? ' is-on' : '') + '" data-type="' + t.id + '">' + 1707 '<input type="radio" name="ppInlineType_' + intent + '" value="' + t.id + '"' + (i === 0 ? ' checked' : '') + '>' + 1708 '<span class="ic">' + iconSvgFor(t.name) + '</span>' + 1709 '<span class="txt"><b>' + t.name + '</b></span>' + 1710 '<span class="dot"></span>' + 1711 '</label>'; 1712 } 1713 1714 function wireTypeRow(row){ 1715 row.querySelector('input').addEventListener('change', () => { 1716 row.closest('.type-list').querySelectorAll('.type-row').forEach(r => r.classList.toggle('is-on', r.querySelector('input').checked)); 1717 }); 1718 } 1719 1720 function loadTypes(){ 1721 if(loaded) return; 1722 fetch("https://www.letsrentz.com/post-property/type-options").then(r => r.json()).then(data => { 1723 if(!data.status) return; 1724 ['rent','sell','flatmate'].forEach(intent => { 1725 if(!typeLists[intent]) return; // flatmate list isn't rendered for owner/broker accounts 1726 const items = data[intent] || []; 1727 typeLists[intent].innerHTML = items.map((t, i) => rowHtml(intent, t, i)).join(''); 1728 typeLists[intent].classList.toggle('odd-count', items.length % 2 !== 0); 1729 typeLists[intent].querySelectorAll('.type-row').forEach(wireTypeRow); 1730 }); 1731 loaded = true; 1732 loading.style.display = 'none'; 1733 contBtn.disabled = false;
1734 }).catch(() => { 1735 loading.textContent = 'Could not load property types â please refresh and try again.'; 1736 }); 1737 } 1738 1739 function selectIntent(intent){ 1740 const input = document.querySelector('input[name="ppInlineIntent"][value="' + intent + '"]'); 1741 if(!input || input.checked) return; 1742 input.checked = true; 1743 input.dispatchEvent(new Event('change')); 1744 } 1745 1746 function openPopup(intent){ 1747 overlay.classList.add('show'); 1748 overlay.setAttribute('aria-hidden','false'); 1749 document.body.classList.add('locked'); 1750 loadTypes(); 1751 if(intent){ selectIntent(intent); } 1752 } 1753 function closePopup(){ 1754 overlay.classList.remove('show'); 1755 overlay.setAttribute('aria-hidden','true'); 1756 document.body.classList.remove('locked'); 1757 } 1758 1759 // Any element on the page can open this popup by adding a [data-pp-open] 1760 // attribute (header's "Post Property FREE" button, mypropertylisting's 1761 // "List Your Property" button, homepage tab CTAs, etc.) â this partial is 1762 // included once, site-wide, from front.template.blade.php. An optional 1763 // value (data-pp-open="flatmate") pre-selects that intent tab. 1764 document.querySelectorAll('[data-pp-open]').forEach(function(el){ 1765 el.addEventListener('click', function(e){ 1766 e.preventDefault(); 1767 openPopup(el.getAttribute('data-pp-open')); 1768 }); 1769 }); 1770 window.ppOpenPostPropertyPopup = openPopup; 1771 1772 closeBtn.addEventListener('click', closePopup); 1773 overlay.addEventListener('mousedown', e => { if(e.target === overlay) closePopup(); }); 1774 document.addEventListener('keydown', e => { 1775 if(e.key === 'Escape' && overlay.classList.contains('show')) closePopup(); 1776 }); 1777 1778 document.querySelectorAll('input[name="ppInlineIntent"]').forEach(input => { 1779 input.addEventListener('change', () => { 1780 document.querySelectorAll('#ppInlineOverlay .intent').forEach(c => c.classList.toggle('is-on', c.contains(input))); 1781 typeHint.textContent = HINTS[input.value]; 1782 Object.keys(typeLists).forEach(intent => { if(typeLists[intent]) typeLists[intent].hidden = intent !== input.value; }); 1783 }); 1784 }); 1785 1786 contBtn.addEventListener('click', () => { 1787 const intent = document.querySelector('input[name="ppInlineIntent"]:checked').value; 1788 const typeInput = typeLists[intent].querySelector('input:checked'); 1789 if(!typeInput) return; 1790 const typeId = typeInput.value; 1791 1792 if(intent === 'sell'){ 1793 window.location.href = "https://www.letsrentz.com/post-property/sell-flow?type_id=" + typeId; 1794 }else{ 1795 window.location.href = "https://www.letsrentz.com/post-property/rent-flow?category=" + intent + "&type_id=" + typeId; 1796 } 1797 }); 1798})(); 1799</script>
1799 1800 </div> 1801 1802 1803
1803<script> 1804 var address=""; 1805 var lati=""; 1806 var longi=""; 1807 function initGoogleAutocomplete() { 1808 // const input = document.querySelector('.autocomplete-google'); 1809 // console.log(input); 1810 1811 // if (!input) return; 1812 const inputs = document.querySelectorAll('.autocomplete-google'); 1813 inputs.forEach(input => { 1814 if (window.getComputedStyle(input).display === "none") return; 1815 1816 const autocomplete = new google.maps.places.Autocomplete(input, { 1817 types: ['geocode'], 1818 componentRestrictions: { country: 'in' } 1819 }); 1820 1821 autocomplete.addListener('place_changed', function () { 1822 const place = autocomplete.getPlace(); 1823 if (!place.geometry) { 1824 alert("No details available for the selected location."); 1825 return; 1826 } 1827 1828 const fullAddress = place.formatted_address; 1829 const latitude = place.geometry.location.lat(); 1830 const longitude = place.geometry.location.lng(); 1831 1832 // Format address like your old logic (replace commas, spaces, #) 1833 let replacedCommas = fullAddress.replace(/,\s+/g, '-'); 1834 let finalAddress = replacedCommas.replace(/\s+/g, '-').replace(/#/g, ''); 1835 1836 address=finalAddress; 1837 lati=latitude; 1838 longi=longitude; 1839 $('#full_address').val(address); 1840 $('#rental_latitude').val(latitude); 1841 $('#rental_longitude').val(longitude); 1842 // Optional: update hidden fields or trigger a search 1843 }); 1844 1845 // Google's place_changed only fires when a suggestion is 1846 // actually selected (mouse click, or arrow-key highlight + 1847 // Enter). Typing an address and pressing Enter directly â 1848 // a common desktop habit, less common on mobile where 1849 // tapping a listed suggestion is the natural gesture â 1850 // never selects anything, leaving lati/longi silently 1851 // empty and the requirement flow stuck with no place
1852 // selected. If Enter is pressed with no suggestion 1853 // highlighted, select the top one instead of doing nothing. 1854 input.addEventListener('keydown', function (e) { 1855 if (e.key !== 'Enter') return; 1856 var containers = document.querySelectorAll('.pac-container'); 1857 var activeContainer = null; 1858 containers.forEach(function (c) { 1859 if (getComputedStyle(c).display !== 'none' && c.querySelector('.pac-item')) { 1860 activeContainer = c; 1861 } 1862 }); 1863 if (!activeContainer) return; 1864 if (activeContainer.querySelector('.pac-item-selected')) return; 1865 var first = activeContainer.querySelector('.pac-item'); 1866 if (!first) return; 1867 e.preventDefault(); 1868 e.stopPropagation(); 1869 ['mousedown', 'mouseup', 'click'].forEach(function (type) { 1870 first.dispatchEvent(new MouseEvent(type, { bubbles: true, cancelable: true, view: window })); 1871 }); 1872 }, true); 1873 }); 1874 1875 1876 } 1877 window.initGoogleAutocomplete = initGoogleAutocomplete; 1878 1879 // Single shared loader (2026-08-27) â this block and the homepage's 1880 // hpLoadMapsIfNeeded below used to each guard against re-injecting 1881 // the script with their OWN separate flag (__lrMapsScriptInserted / 1882 // __hpMapsScriptInserted), so neither saw the other already had one 1883 // in flight. Both run on the same window.load+400ms timing on EVERY 1884 // page (this template is the base layout for all of them), so 1885 // whenever GTM's own independent Maps bootstrap hadn't already 1886 // finished by then, BOTH would inject their own
1886<script src="maps/ 1887 // api/js?...">, triggering Google's "included multiple times" 1888 // warning and â per the comment above, confirmed in practice â 1889 // leaving one of the two callbacks never firing. Worse on slower 1890 // connections (mobile) where GTM's own load is less likely to have 1891 // already won the race by 400ms, matching reports of location 1892 // search failing on mobile web but not desktop. One shared in-flight 1893 // flag + callback queue means at most one script tag ever gets 1894 // inserted, and every caller's callback fires once it resolves. 1895 window.__lrGoogleMapsCallbacks = window.__lrGoogleMapsCallbacks || []; 1896 window.__lrLoadGoogleMaps = window.__lrLoadGoogleMaps || function (cb) { 1897 if (window.google && window.google.maps && window.google.maps.places) { 1898 cb(); 1899 return; 1900 } 1901 window.__lrGoogleMapsCallbacks.push(cb); 1902 if (window.__lrMapsScriptInserted) return; 1903 window.__lrMapsScriptInserted = true; 1904 window.__lrGoogleMapsReady = function () { 1905 window.__lrGoogleMapsCallbacks.splice(0).forEach(function (fn) { fn(); }); 1906 }; 1907 var s = document.createElement('script'); 1908 s.src = 'https://maps.googleapis.com/maps/api/js?key=AIzaSyDSj3nda3aEN7QXQNaDllvJQxkwAGBbDkM&libraries=places&callback=__lrGoogleMapsReady'; 1909 s.async = true; 1910 document.head.appendChild(s); 1911 }; 1912 // Pages that set window.__lrDeferMaps = true (the property detail page) 1913 // hold the ~250 KB Maps/Places bundle back until the first real user 1914 // interaction, so it never competes with first paint or the LCP image. 1915 // Everything else keeps the original load + 400ms behaviour. 1916 window.__lrWhenMapsWanted = function (fn) { 1917 window.addEventListener('load', function () { 1918 if (!window.__lrDeferMaps) { setTimeout(fn, 400); return; } 1919 var fired = false; 1920 var go = function () { if (!fired) { fired = true; fn(); } }; 1921 ['pointerdown', 'touchstart', 'keydown', 'scroll', 'focusin'].forEach(function (ev) { 1922 window.addEventListener(ev, go, { passive: true, once: true }); 1923 }); 1924 }); 1925 }; 1926 window.__lrWhenMapsWanted(function () { window.__lrLoadGoogleMaps(initGoogleAutocomplete); }); 1927 </script>
1927 1928 1929
1929<script src="https://www.gstatic.com/firebasejs/9.0.0/firebase-app-compat.js"></script>
1929 1930
1930<script src="https://www.gstatic.com/firebasejs/9.0.0/firebase-messaging-compat.js"></script>
1930 1931
1931<script> 1932 (function () { 1933 window.__lrFcmToken = null; 1934 var lrMessaging = null; 1935 var LR_VAPID_KEY = 'BOlWeWtMF2QcspIBdMxodt0s_o7bOmbNCZs-uDnCx5PNv2GCuqa7Iyu2oN9k3QoXa1GVQ35B4ErYfCJdIgHGyXA'; 1936 1937 try { 1938 firebase.initializeApp({ 1939 apiKey: "AIzaSyA6MD5GuIoAVKpcyogZstW_UR6f5JPKZ4k", 1940 authDomain: "lets-rentz.firebaseapp.com", 1941 databaseURL: "https://lets-rentz-default-rtdb.firebaseio.com", 1942 projectId: "lets-rentz", 1943 storageBucket: "lets-rentz.appspot.com", 1944 messagingSenderId: "100691140179", 1945 appId: "1:100691140179:web:75d17337ff39e3c6cef0a6", 1946 measurementId: "G-NEFBMB2PYJ" 1947 }); 1948 lrMessaging = firebase.messaging(); 1949 1950 // The service worker's onBackgroundMessage (firebase-messaging-sw.js) 1951 // only fires when this tab isn't focused â for an active/foreground 1952 // tab, Firebase hands the message to the page instead, so we have to 1953 // show the notification ourselves. Routed through the SW registration 1954 // (not `new Notification(...)`) so the existing notificationclick 1955 // handler in firebase-messaging-sw.js still applies to it. 1956 lrMessaging.onMessage(function (payload) { 1957 if (Notification.permission !== 'granted' || !('serviceWorker' in navigator)) { return; } 1958 var notificationTitle = (payload.notification && payload.notification.title) || 'Lets Rentz'; 1959 // No icon â the browser/OS falls back to its own default. 1960 // Rebuild a clean, minimal data object instead of passing 1961 // payload.data through as-is. Firebase's own compat SDK 1962 // secretly registers its OWN notificationclick listener 1963 // in the service worker; if a notification's data carries 1964 // Firebase's internal tracking marker, that listener takes 1965 // over the click FIRST (stopImmediatePropagation), sending 1966 // the user to Firebase's own fallback destination instead 1967 // of the property page. Only copying the one field we 1968 // actually need keeps that marker out. 1969 var notificationOptions = { 1970 body: (payload.notification && payload.notification.body) || '', 1971 data: { 1972 property_id: (payload.data && payload.data.property_id) || null, 1973 click_action: (payload.data && payload.data.click_action) || null, 1974 property_notification_id: (payload.data && payload.data.property_notification_id) || null 1975 } 1976 }; 1977 if (payload.notification && payload.notification.image) { 1978 notificationOptions.image = payload.notification.image; 1979 } 1980 navigator.serviceWorker.ready.then(function (registration) { 1981 registration.showNotification(notificationTitle, notificationOptions); 1982 }); 1983 }); 1984 } catch (e) { 1985 console.warn('Web push unavailable:', e); 1986 } 1987 1988 // Persist a freshly obtained token against this browser session 1989 // (matched server-side by login_token). Handles both the initial 1990 // permission grant and later token rotation (refresh). 1991 function lrSaveFcmToken(token) { 1992 var loginToken = localStorage.getItem('login_token'); 1993 if (!token || !loginToken) { return; } 1994 if (localStorage.getItem('lr_saved_fcm_token') === token) { return; } 1995 var deviceType = detectDevice() === 'mobile' ? 'mobile_web' : 'desktop_web'; 1996 $.ajax({ 1997 url: 'https://www.letsrentz.com/auth/saveFcmToken', 1998 method: 'POST', 1999 data: { 2000 fcm_token: token, 2001 login_token: loginToken, 2002 device_type: deviceType, 2003 _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 2004 }, 2005 success: function (res) { 2006 if (res && res.status) { 2007 localStorage.setItem('lr_saved_fcm_token', token); 2008 } 2009 } 2010 }); 2011 } 2012 2013 // Fetch the token (only resolves once permission is granted) and 2014 // cache + save it. Registers the SW first. 2015 function lrFetchAndSaveToken() { 2016 if (!lrMessaging || !('serviceWorker' in navigator)) { 2017 return Promise.resolve(null); 2018 } 2019 return navigator.serviceWorker.register('/firebase-messaging-sw.js') 2020 .then(function (registration) { 2021 return lrMessaging.getToken({
2022 vapidKey: LR_VAPID_KEY, 2023 serviceWorkerRegistration: registration 2024 }); 2025 }) 2026 .then(function (token) { 2027 if (token) { 2028 window.__lrFcmToken = token; 2029 lrSaveFcmToken(token); 2030 } 2031 return token; 2032 }) 2033 .catch(function (err) { 2034 console.warn('getToken failed:', err); 2035 return null; 2036 }); 2037 } 2038 2039 // On every page load: register the SW, and if permission is 2040 // already granted, refresh + re-save the token (Phase 5). Never 2041 // prompt here. 2042 window.addEventListener('load', function () { 2043 if (!('Notification' in window)) { return; } 2044 if (Notification.permission === 'granted') { 2045 lrFetchAndSaveToken(); 2046 } else if ('serviceWorker' in navigator) { 2047 navigator.serviceWorker.register('/firebase-messaging-sw.js').catch(function () {}); 2048 } 2049 }); 2050 2051 // Called after the tenant finishes the requirement flow: prompt 2052 // for notification permission, then grab + save the token. 2053 // Resolves regardless of the user's choice so the caller can 2054 // proceed (e.g. reload) either way. 2055 window.lrRequestPushPermission = function () { 2056 if (!('Notification' in window) || !lrMessaging) { 2057 return Promise.resolve(null); 2058 } 2059 if (Notification.permission === 'granted') { 2060 return lrFetchAndSaveToken(); 2061 } 2062 if (Notification.permission === 'denied') { 2063 return Promise.resolve(null); 2064 } 2065 return Notification.requestPermission() 2066 .then(function (permission) { 2067 if (permission === 'granted') { 2068 return lrFetchAndSaveToken(); 2069 } 2070 return null; 2071 }) 2072 .catch(function () { return null; }); 2073 }; 2074 })(); 2075 </script>
2075 2076 2077 2078 2079 2080
2080<script> 2081 (function () { 2082 var banner = document.getElementById('hpAppBanner'); 2083 if (!banner) { return; } 2084 // iPhone Safari gets Apple's native Smart App Banner instead (the 2085 // apple-itunes-app meta in <head>) - it knows whether the app is 2086 // installed and opens it on the same property. Chrome/Firefox/in-app 2087 // browsers on iPhone don't support that banner, so they keep this one. 2088 var ua = navigator.userAgent || ''; 2089 if (/iPhone|iPod/.test(ua) && /Safari\//.test(ua) && !/CriOS|FxiOS|EdgiOS|OPiOS|FBAN|FBAV|Instagram/.test(ua)) { 2090 banner.classList.add('hp-app-banner-hide'); 2091 return; 2092 } 2093 if (localStorage.getItem('lr_app_banner_dismissed') === '1') { 2094 banner.classList.add('hp-app-banner-hide'); 2095 return; 2096 } 2097 2098 var ANDROID_LINK = 'https://play.google.com/store/apps/details?id=com.decodershub.letsrentz&hl=en_IN&utm_source=website&utm_medium=appbannertop'; 2099 var IOS_LINK = 'https://apps.apple.com/in/app/lets-rentz-make-it-easy/id1634945591'; 2100 var isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); 2101 2102 var storeLabel = document.getElementById('hpAppBannerStoreLabel'); 2103 var cta = document.getElementById('hpAppBannerCta'); 2104 if (isIOS) { 2105 if (storeLabel) { storeLabel.textContent = 'App Store'; } 2106 } 2107 2108 if (cta) { 2109 cta.addEventListener('click', function () { 2110 if (typeof fbq === 'function') { 2111 fbq('trackCustom', 'website_appbannertop', { platform: isIOS ? 'ios' : 'android' }); 2112 } 2113 window.location.href = isIOS ? IOS_LINK : ANDROID_LINK; 2114 }); 2115 } 2116 2117 var closeBtn = document.getElementById('hpAppBannerClose'); 2118 if (closeBtn) { 2119 closeBtn.addEventListener('click', function () { 2120 localStorage.setItem('lr_app_banner_dismissed', '1'); 2121 banner.classList.add('hp-app-banner-hide'); 2122 }); 2123 } 2124 })(); 2125 </script>
2125 2126
2127<script> 2128 // Shares the single loader defined above (window.__lrLoadGoogleMaps) â 2129 // this used to have its own independent script-injection + guard flag, 2130 // which is what raced against the block above and caused the "included 2131 // multiple times" / unreliable-callback bug documented there. See that 2132 // comment for the full story. 2133 window.__lrWhenMapsWanted(function () { 2134 window.__lrLoadGoogleMaps(function () { 2135 if (typeof window.hpInitAutocomplete === 'function') window.hpInitAutocomplete(); 2136 }); 2137 }); 2138</script>
vendor: 1 bytes, line 2138
2138
2139<script> 2140 // Shared across every page that extends this template: real Google 2141 // Places autocomplete init, the full OTP/register/location/property-type/ 2142 // category/tenant-type/budget auth wizard, and the header's profile-menu/ 2143 // logout/login-trigger wiring. Originally built only for the homepage 2144 // preview and later promoted here so every page gets a working login, 2145 // not just the homepage - see header-new.blade.php / login-modal.blade.php 2146 // for the markup this drives. 2147 var hpLati = '', hpLongi = '', hpAddress = '', hpCategory = 'rent'; 2148 // Page-render-time auth flag, shared across every page (unlike 2149 // detail.blade.php's own local `isLoggedIn`, which only exists on the 2150 // property detail page) - resumePendingAction() below needs this on 2151 // every page a gated click can happen from. 2152 var lrIsLoggedIn = false; 2153 2154 function hpInitAutocomplete() { 2155 if (!window.google || !window.google.maps) return; 2156 // hpLocationInput only exists on the homepage's own search card - on 2157 // every other page this is skipped and only the shared auth-flow 2158 // field below gets wired up. (Previously this whole function bailed 2159 // out here whenever hpLocationInput was missing, which silently broke 2160 // the auth flow's own location autocomplete on every non-homepage 2161 // page - fixed as part of making this script shared across the site.) 2162 var input = document.getElementById('hpLocationInput'); 2163 if (input) { 2164 var autocomplete = new google.maps.places.Autocomplete(input, { 2165 types: ['geocode'], 2166 componentRestrictions: { country: 'in' } 2167 }); 2168 autocomplete.addListener('place_changed', function () { 2169 var place = autocomplete.getPlace(); 2170 if (!place.geometry) { alert('No details available for the selected location.'); return; } 2171 var replacedCommas = place.formatted_address.replace(/,\s+/g, '-'); 2172 hpAddress = replacedCommas.replace(/\s+/g, '-').replace(/#/g, ''); 2173 hpLati = place.geometry.location.lat(); 2174 hpLongi = place.geometry.location.lng(); 2175 }); 2176 } 2177 2178 // The real auth flow's own location modal (#locationInput) needs the 2179 // same autocomplete wiring, writing to the real global address/lati/ 2180 // longi vars that updateLocation() below reads. 2181 var authLocationInput = document.getElementById('locationInput'); 2182 if (authLocationInput) { 2183 var authAutocomplete = new google.maps.places.Autocomplete(authLocationInput, { 2184 types: ['geocode'], 2185 componentRestrictions: { country: 'in' } 2186 }); 2187 authAutocomplete.addListener('place_changed', function () { 2188 var place = authAutocomplete.getPlace(); 2189 if (!place.geometry) { showToast('No details available for the selected location.'); return; } 2190 var replacedCommas = place.formatted_address.replace(/,\s+/g, '-'); 2191 address = replacedCommas.replace(/\s+/g, '-').replace(/#/g, ''); 2192 lati = place.geometry.location.lat(); 2193 longi = place.geometry.location.lng(); 2194 }); 2195 } 2196 } 2197 window.hpInitAutocomplete = hpInitAutocomplete; 2198 2199 // ========================================================== 2200 // REAL AUTH FLOW - ported verbatim (function names, endpoints, field 2201 // names, sequencing) from template.blade.php's login/OTP/register/ 2202 // location/property-type/category/tenant-type/budget wizard, per 2203 // explicit decision to reuse the real, proven flow rather than 2204 // re-implement a second copy of business-critical logic. Copied (not 2205 // shared via an include) so this standalone page still doesn't load the 2206 // old site's script bundle - if the real flow changes, this needs 2207 // updating to match. 2208 // 2209 // fbq/gtag/getGA4ClientId are the real ones now (see the GTM/GA4/Meta 2210 // Pixel script block in <head>) - only toastr stays stubbed, since this 2211 // isolated page doesn't load the real toastr.js bundle and nothing here 2212 // depends on its visual styling, just the no-op call sites. 2213 // ========================================================== 2214 if (typeof toastr === 'undefined') { window.toastr = { success: function () {}, error: function () {} }; } 2215 function showToast(message) { 2216 var toast = $('<div class="toast">' + message + '</div>'); 2217 $('body').append(toast); 2218 toast.addClass('show'); 2219 setTimeout(function () { 2220 toast.removeClass('show'); 2221 setTimeout(function () { toast.remove(); }, 600); 2222 }, 3000); 2223 } 2224 2225 const authPropertyTypes = [{"id":1,"name":"House","parent_id":0,"status":1,"created_at":"2022-07-17T11:06:00.000000Z","updated_at":"2022-07-17T11:06:00.000000Z","sub_property_type":[{"id":34,"name":"1 Room with Kitchen","parent_id":1,"status":1,"created_at":"2023-08-09T11:27:15.000000Z","updated_at":"2026-06-11T11:20:50.000000Z"},{"id":6,"name":"1BHK","parent_id":1,"status":1,"created_at":"2022-07-17T11:06:48.000000Z","updated_at":"2022-07-17T11:06:48.000000Z"},{"id":36,"name":"2 Room with Kitchen","parent_id":1,"status":1,"created_at":"2025-12-24T11:17:12.000000Z","updated_at":"2026-06-11T11:21:23.000000Z"},{"id":7,"name":"2BHK","parent_id":1,"status":1,"created_at":"2022-07-17T11:06:53.000000Z","updated_at":"2022-07-17T11:06:53.000000Z"},{"id":37,"name":"3 Room with Kitchen","parent_id":1,"status":1,"created_at":"2025-12-24T11:17:20.000000Z","updated_at":"2026-06-11T11:21:35.000000Z"},{"id":8,"name":"3BHK","parent_id":1,"status":1,"created_at":"2022-07-17T11:06:59.000000Z","updated_at":"2022-07-17T11:06:59.000000Z"},{"id":10,"name":"4+ BHK","parent_id":1,"status":1,"created_at":"2022-07-17T11:07:20.000000Z","updated_at":"2022-07-17T11:07:20.000000Z"},{"id":9,"name":"4BHK","parent_id":1,"status":1,"created_at":"2022-07-17T11:07:06.000000Z","updated_at":"2022-07-17T11:07:06.000000Z"},{"id":33,"name":"Annexy","parent_id":1,"status":1,"created_at":"2023-08-09T11:24:27.000000Z","updated_at":"2023-08-09T11:24:27.000000Z"}]},{"id":2,"name":"Flat","parent_id":0,"status":1,"created_at":"2022-07-17T11:06:13.000000Z","updated_at":"2022-07-17T11:06:13.000000Z","sub_property_type":[{"id":11,"name":"1BHK","parent_id":2,"status":1,"created_at":"2022-07-17T11:07:34.000000Z","updated_at":"2022-07-17T11:07:34.000000Z"},{"id":12,"name":"2BHK","parent_id":2,"status":1,"created_at":"2022-07-17T11:07:38.000000Z","updated_at":"2022-07-17T11:07:38.000000Z"},{"id":13,"name":"3BHK","parent_id":2,"status":1,"created_at":"2022-07-17T11:07:45.000000Z","updated_at":"2022-07-17T11:07:45.000000Z"},{"id":15,"name":"4+ BHK","parent_id":2,"status":1,"created_at":"2022-07-17T11:08:00.000000Z","updated_at":"2022-07-17T11:08:00.000000Z"},{"id":14,"name":"4BHK","parent_id":2,"status":1,"created_at":"2022-07-17T11:07:54.000000Z","updated_at":"2022-07-17T11:07:54.000000Z"},{"id":32,"name":"Annexy","parent_id":2,"status":1,"created_at":"2023-08-09T11:23:59.000000Z","updated_at":"2023-08-09T11:27:48.000000Z"},{"id":35,"name":"Studio Apartment","parent_id":2,"status":1,"created_at":"2023-08-09T11:27:54.000000Z","updated_at":"2023-08-09T11:27:54.000000Z"}]},{"id":3,"name":"PG","parent_id":0,"status":1,"created_at":"2022-07-17T11:06:21.000000Z","updated_at":"2022-07-17T11:06:21.000000Z","sub_property_type":[{"id":38,"name":"Five Sharing","parent_id":3,"status":1,"created_at":"2025-12-26T11:30:58.000000Z","updated_at":"2025-12-26T11:30:58.000000Z"},{"id":26,"name":"Four Sharing","parent_id":3,"status":1,"created_at":"2022-07-27T05:59:38.000000Z","updated_at":"2025-12-26T11:30:51.000000Z"},{"id":16,"name":"Single Room","parent_id":3,"status":1,"created_at":"2022-07-17T11:08:27.000000Z","updated_at":"2025-12-26T11:30:19.000000Z"},{"id":25,"name":"Three Sharing","parent_id":3,"status":1,"created_at":"2022-07-21T05:28:17.000000Z","updated_at":"2025-12-26T11:30:41.000000Z"},{"id":17,"name":"Two Sharing","parent_id":3,"status":1,"created_at":"2022-07-17T11:08:40.000000Z","updated_at":"2025-12-26T11:30:33.000000Z"}]},{"id":5,"name":"Commercial","parent_id":0,"status":1,"created_at":"2022-07-17T11:06:37.000000Z","updated_at":"2022-07-17T11:06:37.000000Z","sub_property_type":[{"id":20,"name":"Co-working","parent_id":5,"status":1,"created_at":"2022-07-17T11:09:59.000000Z","updated_at":"2022-07-17T11:09:59.000000Z"},{"id":22,"name":"Office","parent_id":5,"status":1,"created_at":"2022-07-17T11:10:31.000000Z","updated_at":"2022-07-17T11:10:31.000000Z"},{"id":30,"name":"PG Building","parent_id":5,"status":1,"created_at":"2023-07-03T08:04:07.000000Z","updated_at":"2023-07-03T08:04:07.000000Z"},{"id":23,"name":"Shop","parent_id":5,"status":1,"created_at":"2022-07-17T11:10:39.000000Z","updated_at":"2022-07-17T11:10:39.000000Z"},{"id":24,"name":"Showroom","parent_id":5,"status":1,"created_at":"2022-07-17T11:10:46.000000Z","updated_at":"2022-07-17T11:10:46.000000Z"},{"id":21,"name":"Single Chair","parent_id":5,"status":1,"created_at":"2022-07-17T11:10:14.000000Z","updated_at":"2022-07-17T11:10:14.000000Z"}]}]; 2226 let userPropertySearchData = { 2227 selectedUserPropertyTypeId: null, 2228 selectedUserBudget: null, 2229 selectedUserTenantType: null, 2230 selectedUserCategoryId: null, 2231 selectedUserPreferenceId: null 2232 }; 2233 2234 var address = ""; 2235 var lati = ""; 2236 var longi = ""; 2237 var otp = ""; 2238 var userPurpose = null; 2239 var userType = null;
2240 var user_type = "user"; 2241 var user_image = ""; 2242 var register_full_name = ""; 2243 var register_phone_number = ""; 2244 var register_email = ""; 2245 var register_password = ""; 2246 var social_type = "normal"; 2247 var social_id = ""; 2248 var referred_from = ""; 2249 var request_type = ""; 2250 var trail_user = ""; 2251 2252 function detectDevice() { 2253 var userAgent = navigator.userAgent || navigator.vendor || window.opera; 2254 if (/android|iPhone|iPad|iPod|Windows Phone|mobile/i.test(userAgent)) return "mobile"; 2255 return "desktop"; 2256 } 2257 2258 // Every front page's logged-in/out chrome (header avatar, "Login" 2259 // prompts, Post Property gating, etc.) is baked into the server-rendered 2260 // HTML at the time the page loaded. The browser's back/forward cache 2261 // (bfcache) can restore that exact HTML from before a login happened 2262 // instead of re-requesting the page - eg: open a listing, tap into a 2263 // property detail, log in there (that page reloads and shows the new 2264 // state correctly), then hit back - bfcache hands back the listing page 2265 // exactly as it looked while still logged out, still prompting to log 2266 // in. `pageshow`'s `persisted` flag is true specifically for a bfcache 2267 // restore (never on a normal/first load). 2268 // 2269 // Unconditionally reloading here (the original fix) forced a full 2270 // navigation on every single back-navigation, even when auth hadn't 2271 // actually changed - which threw away client-side listing/pagination 2272 // state (infinite-scroll position, loaded pages) on the very common 2273 // "browse listing -> open detail -> back" path. Instead, check whether 2274 // the restored page's auth state actually disagrees with the real one 2275 // (via /auth/status) and only reload on a genuine mismatch. 2276 window.addEventListener('pageshow', function (event) { 2277 if (!event.persisted) return; 2278 var pageThinksGuest = !!document.querySelector('[data-hp-login-trigger]'); 2279 fetch('https://www.letsrentz.com/auth/status', { credentials: 'same-origin' }) 2280 .then(function (r) { return r.json(); }) 2281 .then(function (data) { 2282 var actuallyLoggedIn = !!data.loggedIn; 2283 if (pageThinksGuest === actuallyLoggedIn) { 2284 window.location.reload(); 2285 } 2286 }) 2287 .catch(function () { /* network hiccup - don't force a reload */ }); 2288 }); 2289 2290 // Body scroll lock for the modal chain (phone/OTP/name/purpose/account 2291 // type/location/requirement steps). `overflow:hidden` on its own does 2292 // not reliably stop touch-drag scrolling on mobile Safari/Chrome, so 2293 // dragging on a modal (eg its drag-handle) was scrolling the page 2294 // behind it. Pinning the body with position:fixed at its current 2295 // scroll offset - restored on unlock - blocks that at the source. 2296 var hpBodyScrollLockY = 0; 2297 function hpLockBodyScroll() { 2298 hpBodyScrollLockY = window.scrollY || window.pageYOffset || 0; 2299 document.body.style.position = 'fixed'; 2300 document.body.style.top = (-hpBodyScrollLockY) + 'px'; 2301 document.body.style.left = '0'; 2302 document.body.style.right = '0'; 2303 document.body.style.width = '100%'; 2304 document.body.style.overflow = 'hidden'; 2305 } 2306 function hpUnlockBodyScroll() { 2307 document.body.style.position = ''; 2308 document.body.style.top = ''; 2309 document.body.style.left = ''; 2310 document.body.style.right = ''; 2311 document.body.style.width = ''; 2312 document.body.style.overflow = ''; 2313 // Explicit 'instant' - the site-wide `html { scroll-behavior: smooth }` 2314 // rule makes the legacy scrollTo(x, y) form animate instead of 2315 // snapping, so restoring the pre-lock offset visibly scrolled to the 2316 // top and glided back down instead of jumping straight there. 2317 window.scrollTo({ top: hpBodyScrollLockY, left: 0, behavior: 'instant' }); 2318 } 2319 2320 // Keeps an open modal (and whatever input the user just focused) above 2321 // the on-screen keyboard on mobile. Resizing the overlay's own box 2322 // (via inset top/height) turned out unreliable on real iOS Safari - 2323 // the overlay would shrink to roughly where the sheet already sat 2324 // instead of down to the keyboard, leaving a visible gap between the 2325 // sheet and the keyboard. Shifting the sheet itself up by the 2326 // keyboard's height is the more direct, reliable fix: the overlay 2327 // stays a normal full-screen `position:fixed; inset:0` backdrop 2328 // (unaffected by the keyboard, which is fine - the keyboard renders 2329 // on top of it anyway), and only the sheet moves.
2330 if (window.visualViewport) { 2331 window.visualViewport.addEventListener('resize', function () { 2332 var vv = window.visualViewport; 2333 // How much of the layout viewport's bottom the keyboard now 2334 // covers. The layout viewport (and this fixed-position overlay) 2335 // doesn't shrink for the keyboard the way the visual viewport 2336 // does, so this is the gap we need to close by hand. 2337 var keyboardInset = Math.max(window.innerHeight - vv.height - vv.offsetTop, 0); 2338 document.querySelectorAll('.property-selector-overlay.show').forEach(function (overlay) { 2339 var sheet = overlay.querySelector('.property-selector'); 2340 if (!sheet) return; 2341 sheet.style.transform = keyboardInset > 0 ? 'translateY(-' + keyboardInset + 'px)' : ''; 2342 2343 // The mobile bottom sheet itself is capped at a static 2344 // max-height:88vh in CSS, which has no idea the keyboard just 2345 // ate part of the screen - once the keyboard opens, 88vh can 2346 // be taller than what's actually left above it, so the sheet 2347 // overflows upward past the top of the screen instead of 2348 // shrinking to fit. Override it with the real available 2349 // height so the sheet (and its Continue/Verify button) always 2350 // stays fully on-screen, above the keyboard. 2351 sheet.style.maxHeight = Math.max(vv.height - 16, 200) + 'px'; 2352 }); 2353 }); 2354 } 2355 2356 // ========================================================== 2357 // PENDING-ACTION RESUME - remembers which gated element the tenant 2358 // clicked before the auth wall interrupted them (Contact Now, 2359 // Shortlist, WhatsApp, gallery nav, ...), and re-fires that same click 2360 // once they're authenticated and back on the page. Backed by 2361 // sessionStorage rather than a JS variable because completing auth 2362 // today always involves a full page reload (returning tenant, see 2363 // loginRequest() below) or a redirect (new tenant, see 2364 // registerRequest()/saveRequirements() below) - anything "remembered" 2365 // has to survive that. 2366 // ========================================================== 2367 var LR_PENDING_ACTION_KEY = 'lr_pending_action'; 2368 2369 // Attribute names this site uses, across different card markups, to 2370 // identify which property/listing a gated button belongs to - a plain 2371 // class selector alone (e.g. ".btn_contact_owner") matches every card 2372 // on a search-results page, not just the one actually clicked. Detail 2373 // pages use data-property-id/data-property-type (see paywallScripts. 2374 // blade.php's .btn-hart-icon); listing cards use p_id/p_user_id (see 2375 // _propertyCardV2.blade.php's .btn_contact_owner). Checked as a list so 2376 // one function covers both without callers needing to know which. 2377 var LR_PENDING_ACTION_ID_ATTRS = ['data-property-id', 'data-property-type', 'p_id', 'p_user_id']; 2378 2379 function setPendingAction(el) { 2380 if (!el) return; 2381 try { 2382 var classSelector = el.className ? '.' + String(el.className).trim().split(/\s+/).join('.') : el.tagName.toLowerCase(); 2383 var selector = classSelector; 2384 LR_PENDING_ACTION_ID_ATTRS.forEach(function (attr) { 2385 var value = el.getAttribute && el.getAttribute(attr); 2386 if (value) selector += '[' + attr + '="' + value + '"]'; 2387 }); 2388 sessionStorage.setItem(LR_PENDING_ACTION_KEY, JSON.stringify({ 2389 selector: selector, 2390 return_url: window.location.href 2391 })); 2392 } catch (e) { /* sessionStorage unavailable (private mode, etc.) - fail silent, worst case the tenant just re-taps */ } 2393 } 2394 2395 function getPendingAction() { 2396 try { 2397 var raw = sessionStorage.getItem(LR_PENDING_ACTION_KEY); 2398 return raw ? JSON.parse(raw) : null; 2399 } catch (e) { return null; } 2400 } 2401 2402 function clearPendingAction() { 2403 try { sessionStorage.removeItem(LR_PENDING_ACTION_KEY); } catch (e) { /* noop */ } 2404 } 2405 2406 var LR_SHOW_MATCH_MORE_TTL_MS = 15000; 2407 var LR_RESUME_POLL_INTERVAL_MS = 300; 2408 var LR_RESUME_POLL_MAX_ATTEMPTS = 20; // ~6s total - covers the listing page's AJAX card load, not just a static page 2409 2410 function resumePendingAction() { 2411 var pending = getPendingAction(); 2412 if (!pending || !pending.selector) return; 2413 // Still mid-auth (e.g. OTP modal reopened after a resend) - leave 2414 // the marker in place rather than clearing it prematurely. 2415 if (!lrIsLoggedIn) return; 2416 clearPendingAction(); 2417 // Timestamped (not a plain flag) so it self-expires - read by two 2418 // places: the resumed action's own success handler 2419 // (consumeShowMatchMoreFlag(), below) to decide whether to offer the 2420 // optional "get matched" card, and the $needsRequirement composer 2421 // check further down this file, to avoid popping the full wizard 2422 // open on top of a resume that's about to self-heal via 2423 // silentSaveRequirementFromProperty() within moments. Set now, 2424 // before the element is even found - the composer check runs 2425 // synchronously at this same page load, well before the polling 2426 // below could resolve on a slow AJAX-loaded page, so it needs the 2427 // flag set up front, not only once a click actually fires.
2428 try { sessionStorage.setItem('lr_show_match_more', String(Date.now())); } catch (e) { /* noop */ } 2429 2430 // Deferred to jQuery's own ready queue, not fired directly off this 2431 // native DOMContentLoaded listener - paywallScripts.blade.php (and 2432 // every other gated handler) binds its clicks inside $(document). 2433 // ready(...), and jQuery's ready callbacks aren't guaranteed to run 2434 // in the same tick/order as native DOMContentLoaded listeners. 2435 // Confirmed the hard way: the very first version of this fired 2436 // el.click() before that handler was bound, so the click landed on 2437 // nothing - no error, just silently no-op, with the pending action 2438 // already cleared above. Queuing through $(fn) instead guarantees 2439 // this runs after every ready() handler already registered by the 2440 // time this line executes, including paywallScripts.blade.php's 2441 // (it's included earlier in the page than this script block). 2442 $(function () { 2443 // Polls rather than a single querySelector attempt: the detail 2444 // page's buttons exist by now, but search-results pages 2445 // (property/listing.blade.php) load their cards via a separate 2446 // AJAX call after ready fires (see filteredProperties()/ 2447 // listingLoadV2.blade.php) - a one-shot lookup would still miss 2448 // those. 2449 var attempts = 0; 2450 (function tryResume() { 2451 var el; 2452 try { el = document.querySelector(pending.selector); } catch (e) { el = null; } 2453 if (el) { 2454 el.click(); 2455 return; 2456 } 2457 attempts++; 2458 if (attempts < LR_RESUME_POLL_MAX_ATTEMPTS) { 2459 setTimeout(tryResume, LR_RESUME_POLL_INTERVAL_MS); 2460 } 2461 // Gives up silently past the timeout - worst case the tenant 2462 // just taps the button again themselves, same as today. 2463 })(); 2464 }); 2465 } 2466 2467 function consumeShowMatchMoreFlag() { 2468 try { 2469 var setAt = parseInt(sessionStorage.getItem('lr_show_match_more') || '0', 10); 2470 sessionStorage.removeItem('lr_show_match_more'); 2471 return !!setAt && (Date.now() - setAt) < LR_SHOW_MATCH_MORE_TTL_MS; 2472 } catch (e) { return false; } 2473 } 2474 2475 // Onboarding progress bar / step-kicker (see login-modal.blade.php's 2476 // .ob-progress/.ob-step-kicker, styled in onboarding-v2.css). Driven by 2477 // a MutationObserver on the 'show' class rather than patching every 2478 // open*Modal()/addClass("show") call site individually - additive, 2479 // can't miss a path, can't interfere with the existing show/hide logic. 2480 // 2481 // Three orders: SHORT (property-triggered, pending action present), 2482 // OWNER (generic entry, owner/broker), FULL (generic entry, search 2483 // chosen). Mobile/OTP/name/purpose default to OWNER's shorter count 2484 // (a real prefix of FULL, not a guess) rather than front-loading FULL's 2485 // bigger total before anyone's said who they are - it only expands to 2486 // FULL once "Searching for a home" is actually chosen, so an owner/ 2487 // broker never sees a bigger number than what they'll actually face. 2488 // 2489 // Owner/broker still see locationModalOverlay after purpose - it's not 2490 // skipped, unlike the rest of the tenant-only wizard (property type/ 2491 // category/tenant type/budget). openLocationModal()'s owner/broker copy 2492 // ("Enter your house/office location") shows it captures the owner's 2493 // own property address via updateLocation(), not a search preference - 2494 // skipping it would silently drop real data this feature depends on. 2495 // A returning user's journey is two screens: sendOTP finds the number 2496 // (User::firstWhere('phone_number')) and returns the user record, accountOTP 2497 // sets request_type='login', and otpVerification() then closes the modal and 2498 // reloads. There is no third screen for them, so every longer order below 2499 // over-promises by at least three. 2500 var LR_OB_RETURNING_ORDER = ['loginModalOverlay', 'otpModalOverlay']; 2501 var LR_OB_SHORT_ORDER = ['loginModalOverlay', 'otpModalOverlay', 'nameModalOverlay', 'tenantTypeModalOverlay']; 2502 var LR_OB_OWNER_ORDER = ['loginModalOverlay', 'otpModalOverlay', 'nameModalOverlay', 'purposeModalOverlay', 'locationModalOverlay']; 2503 var LR_OB_FULL_ORDER = ['loginModalOverlay', 'otpModalOverlay', 'nameModalOverlay', 'purposeModalOverlay', 'locationModalOverlay', 'propertyTypeModalOverlay', 'categoryModalOverlay', 'tenantTypeModalOverlay', 'budgetEstimateModalOverlay']; 2504 2505 function lrUpdateOnboardingProgress(overlay) { 2506 // Opened on its own rather than as a wizard step - the homepage asking a 2507 // logged-out visitor where they are looking. There is no step 1 to have 2508 // come from and no step 6 to go to, so "STEP 5 OF 5" over a single 2509 // question is just confusing. Blank the indicator rather than leaving 2510 // whatever the last wizard run wrote into it. 2511 if (overlay.dataset && overlay.dataset.lrStandalone === '1') { 2512 var pEl = overlay.querySelector('.ob-progress'); 2513 var kEl = overlay.querySelector('.ob-step-kicker'); 2514 if (pEl) pEl.innerHTML = ''; 2515 if (kEl) kEl.textContent = ''; 2516 return; 2517 } 2518 2519 // request_type is set by accountOTP() from the sendOTP response BEFORE 2520 // otpVerification() opens the OTP screen, so it is already correct by the 2521 // time the observer fires here. Checked first: a returning user's path is 2522 // two screens regardless of pending action or purpose. 2523 var order = request_type === 'login' 2524 ? LR_OB_RETURNING_ORDER 2525 : (getPendingAction() ? LR_OB_SHORT_ORDER : (userPurpose === 'search' ? LR_OB_FULL_ORDER : LR_OB_OWNER_ORDER)); 2526 var idx = order.indexOf(overlay.id); 2527 if (idx === -1) return; 2528 var progressEl = overlay.querySelector('.ob-progress'); 2529 var kickerEl = overlay.querySelector('.ob-step-kicker'); 2530 if (progressEl) { 2531 progressEl.innerHTML = ''; 2532 for (var i = 0; i < order.length; i++) { 2533 var seg = document.createElement('i'); 2534 if (i <= idx - 1) seg.classList.add('ob-done'); 2535 progressEl.appendChild(seg); 2536 } 2537 } 2538 // The total is genuinely unknown on the first two screens. A RETURNING 2539 // user finishes at the OTP step - otpVerification() calls closeOTPModal() 2540 // and reloads, so there is no step 3 for them - while a new user still 2541 // faces 2 to 7 more depending on purpose. Nothing before OTP verification 2542 // distinguishes the two, so any total shown there is a guess, and for a 2543 // returning user "STEP 1 OF 5" is a guess that's wrong by three screens. 2544 // 2545 // Show the position without the claim until the path is known. 2546 // The phone screen still cannot know - nothing has looked the number up 2547 // yet. From the OTP screen on, request_type distinguishes returning from 2548 // new, so the total is real and worth showing. 2549 if (kickerEl) { 2550 kickerEl.textContent = idx >= 1 2551 ? 'STEP ' + (idx + 1) + ' OF ' + order.length 2552 : 'STEP ' + (idx + 1); 2553 } 2554 } 2555 2556 document.addEventListener('DOMContentLoaded', function () { 2557 var lrObObserver = new MutationObserver(function (mutations) { 2558 mutations.forEach(function (m) { 2559 var el = m.target; 2560 if (el.classList && el.classList.contains('property-selector-overlay') && el.classList.contains('show')) { 2561 lrUpdateOnboardingProgress(el); 2562 } 2563 }); 2564 }); 2565 document.querySelectorAll('.property-selector-overlay').forEach(function (el) { 2566 lrObObserver.observe(el, { attributes: true, attributeFilter: ['class'] }); 2567 }); 2568 }); 2569 2570 // Keyboard-gap VisualViewport tracker removed - see onboarding-v2.css's 2571 // comment near the bottom for why (broke tap/focus on the phone-number 2572 // input on a real mobile device). 2573 2574 function openLoginPopup(event) { 2575 if (event) { event.stopPropagation(); event.preventDefault(); }
2576 var overlay = document.getElementById('loginModalOverlay'); 2577 if (overlay) { overlay.classList.add('show'); hpLockBodyScroll(); } 2578 } 2579 function closeLoginModal(event) { 2580 if (event) event.stopPropagation(); 2581 var overlay = document.getElementById('loginModalOverlay'); 2582 if (overlay) { overlay.classList.remove('show'); hpUnlockBodyScroll(); } 2583 } 2584 function openOTPModal() { 2585 $("#otpModalOverlay").addClass("show"); 2586 hpLockBodyScroll(); 2587 setTimeout(function () { $("#otp1").focus(); }, 200); 2588 } 2589 function closeOTPModal(event) { 2590 if (event) event.stopPropagation(); 2591 $("#otpModalOverlay").removeClass("show"); 2592 hpUnlockBodyScroll(); 2593 } 2594 // Lets a user who mistyped their number back out of the OTP screen 2595 // without starting over - closes OTP, reopens the phone number modal 2596 // with what they typed still in the box so they only need to fix it. 2597 function editOTPPhoneNumber(event) { 2598 if (event) { event.preventDefault(); event.stopPropagation(); } 2599 closeOTPModal(); 2600 openLoginPopup(); 2601 var phoneInput = document.getElementById('loginPhoneNumber'); 2602 if (phoneInput) { 2603 phoneInput.value = register_phone_number || ''; 2604 phoneInput.focus(); 2605 } 2606 } 2607 function openNameModal(event) { 2608 if (event) { event.stopPropagation(); event.preventDefault(); } 2609 var overlay = document.getElementById('nameModalOverlay'); 2610 if (overlay) { overlay.classList.add('show'); hpLockBodyScroll(); } 2611 } 2612 function closeNameModal(event) { 2613 if (event) event.stopPropagation(); 2614 var overlay = document.getElementById('nameModalOverlay'); 2615 if (overlay) { overlay.classList.remove('show'); hpUnlockBodyScroll(); } 2616 } 2617 function openAccountTypeModal() { 2618 var overlay = document.getElementById('accountTypeModalOverlay'); 2619 if (overlay) { overlay.classList.add('show'); hpLockBodyScroll(); } 2620 } 2621 function closeAccountTypeModal(event) { 2622 if (event) event.stopPropagation(); 2623 var overlay = document.getElementById('accountTypeModalOverlay'); 2624 if (overlay) { overlay.classList.remove('show'); hpUnlockBodyScroll(); } 2625 } 2626 function closePurposeModal(event) { 2627 if (event) event.stopPropagation(); 2628 var overlay = document.getElementById('purposeModalOverlay'); 2629 if (overlay) { overlay.classList.remove('show'); hpUnlockBodyScroll(); } 2630 } 2631 /** 2632 * @param standalone Opened outside the signup wizard - the homepage asking a 2633 * logged-out visitor where they are looking. Suppresses the 2634 * step indicator and lets the visitor dismiss it, because 2635 * unlike a wizard step there is nothing to complete: the 2636 * listings are already rendered behind it on the server's 2637 * default location. 2638 */ 2639 function openLocationModal(userTypeArg, standalone) { 2640 var overlay = document.getElementById('locationModalOverlay'); 2641 var title = document.querySelector('.login-modal-title'); 2642 var description = document.querySelector('.location-modal-description'); 2643 if (overlay) { overlay.dataset.lrStandalone = standalone ? '1' : '0'; } 2644 if (userTypeArg === 'owner') { 2645 title.innerHTML = 'Where\'s your place?'; 2646 description.innerText = 'This is what tenants and buyers will see, so get it right.'; 2647 } else if (userTypeArg === 'broker') { 2648 title.innerHTML = 'Where\'s your office?'; 2649 description.innerText = 'This is what tenants and buyers will see, so get it right.'; 2650 } else if (userTypeArg === 'user') { 2651 title.innerHTML = 'Where are you looking?'; 2652 description.innerText = 'A city or locality is enough to start.'; 2653 } else { 2654 description.innerText = 'Please select your preferred location.'; 2655 } 2656 if (overlay) { 2657 // Standalone only: clicking the backdrop dismisses. In the wizard this 2658 // stays trapped on purpose - there the answer is required. Here it is 2659 // not, and a first-time visitor who cannot get past it to see a single 2660 // listing is the friction this was meant to avoid. The inner card stops 2661 // propagation, so this fires only for the backdrop itself. 2662 overlay.onclick = standalone 2663 ? function (e) { if (e.target === overlay) { closeLocationModal(); } } 2664 : null; 2665 overlay.classList.add('show'); 2666 hpLockBodyScroll(); 2667 } 2668 } 2669 function closeLocationModal(event) { 2670 if (event) event.stopPropagation();
2671 var overlay = document.getElementById('locationModalOverlay'); 2672 if (overlay) { overlay.classList.remove('show'); hpUnlockBodyScroll(); } 2673 } 2674 function closeCategoryModal(event) { 2675 if (event) event.stopPropagation(); 2676 $("#categoryModalOverlay").removeClass("show"); 2677 hpUnlockBodyScroll(); 2678 } 2679 2680 function loginButton() { 2681 var registerPhoneNumber = $('#loginPhoneNumber').val(); 2682 var btn = document.querySelector('.btn_login'); 2683 if (registerPhoneNumber === '') { 2684 $('.login-phone-error').text('Please enter mobile number').show(); 2685 $('.login-phone-wrapper').addClass('error'); 2686 return; 2687 } 2688 if (registerPhoneNumber.length !== 10) { 2689 $('.login-phone-error').text('Mobile number must be 10 digits').show(); 2690 $('.login-phone-wrapper').addClass('error'); 2691 return; 2692 } 2693 $('.login-phone-error').hide(); 2694 $('.login-phone-wrapper').removeClass('error'); 2695 register_phone_number = registerPhoneNumber; 2696 btn.innerText = 'Processing...'; 2697 btn.disabled = true; 2698 btn.style.backgroundColor = '#b0b0b0'; 2699 btn.style.cursor = 'not-allowed'; 2700 accountOTP(user_type, '', register_full_name, register_phone_number, register_email, register_password, 'normal', '', ''); 2701 } 2702 2703 function accountOTP(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id, trail_user) { 2704 $.ajax({ 2705 url: 'https://www.letsrentz.com/auth/sendOTP', 2706 method: 'POST', 2707 data: { 2708 user_type: user_type, full_name: register_full_name, phone_number: register_phone_number, 2709 email: register_email, password: register_password, social_type: social_type, social_id: social_id, 2710 _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 2711 }, 2712 success: function (model) { 2713 var btn = document.querySelector('.btn_login'); 2714 if (model.status) { 2715 // Clear any digits left over from a previous OTP screen 2716 // (eg going back via the edit-number pencil, then 2717 // resubmitting) - otherwise old digits stay visible even 2718 // though they don't match the OTP that was just (re)sent. 2719 // Re-filled below if this response auto-fills a fresh one. 2720 $('.otp-input').val(''); 2721 if (btn) { btn.innerText = 'Continue'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2722 closeLoginModal(); 2723 if (model.data == null) { 2724 request_type = 'register'; 2725 if (model.is_phone != 1 && model.otp_verification == 1) { 2726 if (model.automate_otp == 1 && model.otp) { 2727 var otpStr = model.otp.toString().trim(); 2728 var inputs = document.querySelectorAll('.otp-input'); 2729 inputs.forEach(function (input, index) { input.value = otpStr[index] || ''; }); 2730 var continueBtn = document.querySelector('.otp_continue_btn'); 2731 if (continueBtn) { continueBtn.style.backgroundColor = 'rgba(255,84,3,1)'; continueBtn.style.color = '#fff'; continueBtn.disabled = false; } 2732 } 2733 otpVerification(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id, model.otp, request_type, trail_user); 2734 } 2735 } else { 2736 request_type = 'login'; 2737 if (model.automate_otp == 1 && model.otp) { 2738 var otpStr2 = model.otp.toString().trim(); 2739 var inputs2 = document.querySelectorAll('.otp-input'); 2740 inputs2.forEach(function (input, index) { input.value = otpStr2[index] || ''; }); 2741 var continueBtn2 = document.querySelector('.otp_continue_btn'); 2742 if (continueBtn2) { continueBtn2.style.backgroundColor = 'rgba(255,84,3,1)'; continueBtn2.style.color = '#fff';
2742 continueBtn2.disabled = false; } 2743 } 2744 otpVerification(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id, model.otp, request_type, trail_user); 2745 } 2746 } else { 2747 if (btn) { btn.innerText = 'Continue'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2748 showToast(model.message); 2749 } 2750 }, 2751 error: function () { 2752 var btn = document.querySelector('.btn_login'); 2753 if (btn) { btn.innerText = 'Continue'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2754 showToast('Something went wrong. Please try again.'); 2755 } 2756 }); 2757 } 2758 2759 function otpVerification(newUserType, newUserImage, newRegisterFullName, newRegisterPhoneNumber, newRegisterEmail, newRegisterPassword, newSocialType, newSocialId, newOtp, requestTypeArg, newTrailUser) { 2760 closeLoginModal(); 2761 openOTPModal(); 2762 $("#otpMobileNumber").text('+91 | ' + newRegisterPhoneNumber); 2763 otp = newOtp; 2764 user_type = newUserType; 2765 register_full_name = newRegisterFullName; 2766 register_phone_number = newRegisterPhoneNumber; 2767 register_email = newRegisterEmail; 2768 register_password = newRegisterPassword; 2769 social_type = newSocialType; 2770 trail_user = newTrailUser; 2771 } 2772 2773 function resendOTP(event) { 2774 if (event) { event.preventDefault(); event.stopPropagation(); } 2775 if (!register_phone_number) return; 2776 $('.otp-input').val(''); 2777 $('#otp1').focus(); 2778 toastr.success('OTP resent successfully'); 2779 accountOTP(user_type, '', register_full_name, register_phone_number, register_email, register_password, social_type, '', trail_user); 2780 } 2781 2782 function getOTP() { 2783 var otpInputs = $('.otp-input').get(); 2784 var result = ''; 2785 otpInputs.forEach(function (input) { result += input.value; }); 2786 return result; 2787 } 2788 2789 function otpVerificationBtn() { 2790 var btn = document.querySelector('.otp_continue_btn'); 2791 if (btn) { btn.innerText = 'Processing...'; btn.disabled = true; btn.style.backgroundColor = '#b0b0b0'; } 2792 var fullOTP = getOTP(); 2793 if (otp == fullOTP) { 2794 if (request_type == 'login') { 2795 // OTP modal stays open (showing the Processing state set 2796 // above) until loginRequest's own ajax actually resolves - 2797 // closeOTPModal() used to run right here, synchronously, 2798 // before the request had even been sent (ajax is async), so 2799 // the popup vanished immediately and the tenant stared at a 2800 // blank page for the whole round-trip with no sign anything 2801 // was happening. loginRequest()'s own success/error handlers 2802 // close it (or reset the button on failure) once they 2803 // actually know the outcome. 2804 loginRequest(register_phone_number); 2805 } else if (trail_user == 'trail_user') { 2806 registerRequest(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id); 2807 } else if (request_type == 'register') { 2808 openNameModal(); 2809 closeOTPModal(); 2810 } 2811 } else { 2812 if (btn) { btn.innerText = 'Verify'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2813 $('.otp_verification_error').show(); 2814 $('.otp_verification_error p').text('Please enter valid OTP'); 2815 } 2816 } 2817 2818 function loginRequest(loginPhoneNumber) { 2819 var loginDeviceType = detectDevice() === 'mobile' ? 'mobile_web' : 'desktop_web'; 2820 $.ajax({ 2821 url: 'https://www.letsrentz.com/auth/login', 2822 method: 'POST', 2823 data: { phone_number: loginPhoneNumber, device_type: loginDeviceType, fcm_token: '', _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' }, 2824 success: function (response) { 2825 if (response.status) { 2826 // Same real login tracking as template.blade.php's loginRequest(). 2827 window.dataLayer = window.dataLayer || []; 2828 window.dataLayer.push({ 2829 'event': 'login', 2830 'authentication_method': response.data.social_type, 2831 'user_id': response.data.user_id, 2832 }); 2833 fbq('track', 'login', { 2834 user_id: response.data.user_id, 2835 authentication_method: response.data.social_type,
2836 user_type: response.data.user_type, 2837 }); 2838 localStorage.setItem('login_token', response.data.loginToken); 2839 closeOTPModal(); 2840 if (response.data.user_type === 'owner') { 2841 window.location.href = 'https://www.letsrentz.com/mypropertylisting'; 2842 } else { 2843 window.location.reload(); 2844 } 2845 } else { 2846 lrResetOtpButton(); 2847 showToast(response.message || 'Something went wrong. Please try again.'); 2848 } 2849 }, 2850 error: function () { lrResetOtpButton(); showToast('Something went wrong. Please try again.'); } 2851 }); 2852 } 2853 // Restores the OTP screen's Verify button after a failed login/register 2854 // ajax call - otherwise it's left stuck disabled/"Processing..." with 2855 // no way to retry, since the modal now stays open through the request 2856 // (see otpVerificationBtn()) instead of closing before it's sent. 2857 function lrResetOtpButton() { 2858 var btn = document.querySelector('.otp_continue_btn'); 2859 if (btn) { btn.innerText = 'Verify'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2860 } 2861 2862 function registerRequest(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id) { 2863 var deviceType = detectDevice() === 'mobile' ? 'mobile_web' : 'desktop_web'; 2864 $.ajax({ 2865 url: 'https://www.letsrentz.com/auth/register', 2866 method: 'POST', 2867 data: { 2868 user_type: user_type, full_name: register_full_name, phone_number: register_phone_number, 2869 email: register_email, password: register_password, device_type: deviceType, 2870 social_type: social_type, social_id: social_id, referred_from: referred_from, 2871 ga_client_id: getGA4ClientId(), fcm_token: '', _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 2872 }, 2873 success: function (response) { 2874 if (response.status) { 2875 localStorage.setItem('login_token', response.data.loginToken); 2876 // A real session now exists server-side (AuthController::register() 2877 // calls Auth::attempt), but updateLocation()'s @if(!Auth::check()) 2878 // was compiled when this page first rendered, before signup - it 2879 // can't see this. This flag lets that branch tell the two cases 2880 // apart at runtime instead of always assuming logged-out. 2881 window.lrJustRegistered = true; 2882 // Same real registration tracking as template.blade.php's 2883 // registerRequest() - GA4 'sign_up' fires server-side (see 2884 // AuthController::register(), using the ga_client_id sent 2885 // above) so it isn't duplicated here either. 2886 window.dataLayer = window.dataLayer || []; 2887 window.dataLayer.push({ 2888 'event': 'register', 2889 'user_type': response.data.user_type, 2890 'authentication_method': response.data.social_type, 2891 'user_id': response.data.user_id, 2892 }); 2893 gtag('event', 'conversion', { 2894 'send_to': 'AW-11395525747/doGBCL2NmPYZEPPY57kq', 2895 'value': 1.0, 2896 'currency': 'INR' 2897 }); 2898 // eventID matches the CAPI event_id sent server-side for the 2899 // same user_id, so Meta dedupes the browser + server fires 2900 // into a single counted registration instead of two. 2901 fbq('track', 'CompleteRegistration', { 2902 user_type: response.data.user_type, 2903 user_id: response.data.user_id 2904 }, {eventID: response.data.user_id + '_complete_registration'}); 2905 if (response.data.user_type === 'owner') { 2906 fbq('track', 'owner_register', { 2907 user_id: response.data.user_id, 2908 authentication_method: response.data.social_type, 2909 }, {eventID: response.data.user_id + '_owner_register'}); 2910 } else if (response.data.user_type === 'broker') { 2911 fbq('track', 'broker_register', { 2912 user_id: response.data.user_id, 2913 authentication_method: response.data.social_type, 2914 }, {eventID: response.data.user_id + '_broker_register'}); 2915 } else if (response.data.user_type === 'user') { 2916 fbq('track', 'tenant_register', { 2917 user_id: response.data.user_id, 2918 authentication_method: response.data.social_type, 2919 }, {eventID: response.data.user_id + '_tenant_register'}); 2920 } 2921 closeAccountTypeModal(); 2922 // Also close the OTP modal - it stays open through this
2923 // request when registerRequest() is reached straight from 2924 // otpVerificationBtn()'s trail_user branch (no name/purpose 2925 // screen in between); harmless no-op otherwise, since it's 2926 // already closed by the time purpose_continue_btn calls in. 2927 closeOTPModal(); 2928 var pendingAfterRegister = getPendingAction(); 2929 if (trail_user == 'trail_user') { 2930 window.location.href = 'https://www.letsrentz.com/mypropertylisting'; 2931 } else if (pendingAfterRegister) { 2932 // Registered because of a gated tap on a specific 2933 // listing - skip the location/property-type/category/ 2934 // tenant-type/budget wizard entirely (requirement 2935 // capture doesn't depend on it, see 2936 // silentSaveRequirementFromProperty()) and go straight 2937 // back to finish what the tenant actually came to do. 2938 // register() already established a real session 2939 // server-side (Auth::attempt in AuthController), so this 2940 // navigation lands authenticated and resumePendingAction() 2941 // (wired into the shared DOMContentLoaded listener) picks 2942 // up from sessionStorage and re-fires the original click. 2943 window.location.href = pendingAfterRegister.return_url; 2944 } else { 2945 openLocationModal(response.data.user_type); 2946 } 2947 } else { 2948 // Whichever button actually triggered this call - name/ 2949 // account-type (legacy path), purpose (current path), or 2950 // otp (trail_user, skips straight from OTP) - restore it so 2951 // the tenant isn't stuck on a permanently-disabled button. 2952 var btn = document.querySelector('.name_continue_btn:disabled') || document.querySelector('.account_type_continue_btn:disabled') 2953 || document.querySelector('.purpose_continue_btn:disabled') || document.querySelector('.otp_continue_btn:disabled'); 2954 if (btn) { btn.innerText = 'Continue'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2955 showToast(response.message || 'Something went wrong. Please try again.'); 2956 } 2957 }, 2958 error: function () { 2959 var btn = document.querySelector('.name_continue_btn:disabled') || document.querySelector('.account_type_continue_btn:disabled') 2960 || document.querySelector('.purpose_continue_btn:disabled') || document.querySelector('.otp_continue_btn:disabled'); 2961 if (btn) { btn.innerText = 'Continue'; btn.disabled = false; btn.style.backgroundColor = '#FC4A0A'; } 2962 showToast('Something went wrong. Please try again.'); 2963 } 2964 }); 2965 } 2966 2967 /* 2968 * The search location a visitor is browsing in, cached on the device. 2969 * 2970 * The apps keep this in their own storage and the account is the source of 2971 * truth once someone logs in; the web had nowhere to keep it at all, so every 2972 * logged-out visitor fell back to a hardcoded Mohali and a tenant in Delhi was 2973 * shown homes in Punjab. Same three values as the apps, written together - 2974 * a label that describes different coordinates is the bug this whole 2975 * workstream exists to remove. 2976 */ 2977 var LR_SEARCH_LOCATION_KEY = 'lr_search_location'; 2978 2979 function lrGetSearchLocation() { 2980 try { 2981 var raw = window.localStorage.getItem(LR_SEARCH_LOCATION_KEY); 2982 if (!raw) { return null; } 2983 var loc = JSON.parse(raw); 2984 // A partial cache entry is worse than none - it would search one place 2985 // while the header named another. 2986 if (!loc || !loc.address || !loc.lati || !loc.longi) { return null; } 2987 return loc; 2988 } catch (e) { return null; } 2989 } 2990 2991 function lrSaveSearchLocation(addressVal, latiVal, longiVal) { 2992 if (!addressVal || !latiVal || !longiVal) { return null; } 2993 var loc = { address: String(addressVal), lati: String(latiVal), longi: String(longiVal) };
2994 try { window.localStorage.setItem(LR_SEARCH_LOCATION_KEY, JSON.stringify(loc)); } catch (e) {} 2995 // Anything showing listings for this location can re-render off this. 2996 try { document.dispatchEvent(new CustomEvent('lr:search-location-changed', { detail: loc })); } catch (e) {} 2997 return loc; 2998 } 2999 3000 function updateLocation(btn) { 3001 var locationInputVal = $('#locationInput').val(); 3002 if (locationInputVal == '') { showToast('Please enter your location.'); return false; } 3003 3004 // Logged out: there is no account to write to, so the pick is kept on the 3005 // device only and the page re-renders against it. Skipping the POST also 3006 // avoids sending an unauthenticated write the endpoint would reject. 3007 if (!lati || !longi) { 3008 showToast('Please select your location from the suggestions list.'); 3009 return; 3010 } 3011 lrSaveSearchLocation(address, lati, longi); 3012 closeLocationModal(); 3013 // This @if was compiled true at page-render time, before any signup 3014 // happened - but registerRequest() can complete mid-page-load and 3015 // establish a real session before this runs (see lrJustRegistered). 3016 // Reload so the header picks up the new logged-in state instead of 3017 // silently staying on the stale logged-out render. 3018 if (window.lrJustRegistered) { 3019 window.location.reload(); 3020 } 3021 return; 3022 var originalText = btn.innerText; 3023 btn.innerText = 'Processing...'; 3024 btn.disabled = true; 3025 btn.style.backgroundColor = '#b0b0b0'; 3026 if (!lati || !longi) { 3027 showToast('Please select your location from the suggestions list.'); 3028 btn.innerText = originalText; 3029 btn.disabled = false; 3030 btn.style.backgroundColor = ''; 3031 return; 3032 } 3033 $.ajax({ 3034 url: 'https://www.letsrentz.com/update-user-location', 3035 type: 'POST', 3036 data: { _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw', address: address, lati: lati, longi: longi }, 3037 success: function (response) { 3038 btn.innerText = 'Continue'; 3039 btn.disabled = false; 3040 btn.style.backgroundColor = '#FC4A0A'; 3041 if (response.status) { 3042 var pendingAfterLocation = getPendingAction(); 3043 if (user_type == 'user' && pendingAfterLocation) { 3044 // Registered because of a gated tap on a specific 3045 // listing (Contact Now, Shortlist, ...) - the rest of 3046 // the wizard (property type/category/tenant type/ 3047 // budget) is deferred to the optional post-success card 3048 // instead of blocking here. Requirement capture itself 3049 // doesn't depend on this wizard finishing - see 3050 // silentSaveRequirementFromProperty(), fired from the 3051 // gated actions themselves once the tenant is back. 3052 closeLocationModal(); 3053 window.location.href = pendingAfterLocation.return_url; 3054 } else if (user_type == 'user') { 3055 closeLocationModal(); 3056 $("#propertyTypeModalOverlay").addClass("show"); 3057 } else if (user_type == 'owner') { 3058 window.location.href = 'https://www.letsrentz.com/mypropertylisting'; 3059 } else { 3060 window.location.href = 'https://www.letsrentz.com/profiledetail'; 3061 } 3062 } else { 3063 showToast('Failed to update location'); 3064 } 3065 }, 3066 error: function () { 3067 btn.innerText = originalText; 3068 btn.disabled = false; 3069 btn.style.backgroundColor = ''; 3070 showToast('Something went wrong. Please try again.'); 3071 } 3072 }); 3073 } 3074 3075 // Keeps PlanRequirements current in the background from a property's 3076 // own known data, independent of whether the tenant ever runs the 3077 // location/type/category/tenant-type/budget wizard - see gated-action 3078 // call sites (Contact Now, Shortlist, WhatsApp) below. Only meant for 3079 // user_type=='user' accounts (tenant/buyer share that value, see 3080 // login-modal.blade.php) - callers are expected to check that before 3081 // calling this. Reuses AuthController::savePlanRequirements() as-is, 3082 // flagged silent=1 so it doesn't fire the broker "New Verified Tenant 3083 // Lead" push on every single click - see that method's own comment. 3084 var LR_TENANT_TYPE_MAP = { 'Family': 'family', 'Boys': 'boys', 'Girls': 'girls' }; 3085 // Reads the compact data-lr-req JSON attribute that listing/search- 3086 // result cards carry (see _propertyCardV2.blade.php) - those don't have 3087 // a page-wide property_detail/lrPropertyDetail object like the detail 3088 // page does, since one page renders many cards, so the fields 3089 // silentSaveRequirementFromProperty() needs travel on the element 3090 // itself instead. 3091 function lrReqFromEl(el) {
3092 try { 3093 var raw = el && el.getAttribute && el.getAttribute('data-lr-req'); 3094 return raw ? JSON.parse(raw) : null; 3095 } catch (e) { return null; } 3096 } 3097 3098 // DISABLED 2026-09-26: every Contact/WhatsApp/Shortlist tap was 3099 // overwriting the tenant's own requirement with that listing's exact 3100 // address, type, size and rent - the main source of wrong requirements. 3101 // Tenants now set it themselves in onboarding / Edit Requirement. Kept 3102 // as a no-op so the ~10 call sites don't need touching; the server also 3103 // ignores silent=1 saves (AuthController::savePlanRequirements) for 3104 // pages still cached with the old script. 3105 function silentSaveRequirementFromProperty(propertyDetail) { 3106 return; 3107 if (!propertyDetail || !propertyDetail.address) return; 3108 var payload = { 3109 silent: '1', 3110 address: propertyDetail.address, 3111 lati: propertyDetail.lati, 3112 longi: propertyDetail.longi, 3113 property_type_id: propertyDetail.property_type_id, 3114 category_id_new: propertyDetail.category_id, 3115 max_rent: propertyDetail.rent, 3116 _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 3117 }; 3118 // See lrResolveTenantType() below - the backend's filled() guard on 3119 // tenant_type means leaving this undefined here never blanks out a 3120 // value the tenant already gave through some other path. 3121 payload.tenant_type = lrResolveTenantType(propertyDetail); 3122 var url = 'https://www.letsrentz.com/saveTenantPlanRequirements'; 3123 // sendBeacon when available - some of these calls (Contact Now) 3124 // happen right before the page navigates away, and a plain $.ajax 3125 // request has no guarantee of completing once that starts. 3126 if (navigator.sendBeacon) { 3127 var formData = new FormData(); 3128 Object.keys(payload).forEach(function (key) { 3129 if (payload[key] !== undefined && payload[key] !== null) formData.append(key, payload[key]); 3130 }); 3131 navigator.sendBeacon(url, formData); 3132 } else { 3133 $.ajax({ url: url, type: 'POST', data: payload }); 3134 } 3135 } 3136 3137 // Optional, non-blocking engagement touch shown once, right after a 3138 // freshly-registered tenant's resumed action succeeds (flagged by 3139 // resumePendingAction() via consumeShowMatchMoreFlag() - never shown for 3140 // Contact Now, which navigates away before this could render, and never 3141 // shown from ordinary same-day browsing). The requirement itself is 3142 // already saved by the time this shows (silentSaveRequirementFromProperty 3143 // ran first) - this is purely "want us to actively notify you", not the 3144 // data-capture step. 3145 // Resolves tenant_type the same way for both the silent background 3146 // save and this card's own explicit submit: the tenant's own answer 3147 // (asked once, right after the name screen during property-triggered 3148 // registration - see .tenant_type_continue_btn) always wins; falls 3149 // back to the listing's own tag only when that answer was never asked 3150 // (a returning tenant's ordinary Shortlist click, say) and only when 3151 // the tag is unambiguous ("All"/"Company"/combos like "Family & Girls" 3152 // don't map to a single value). 3153 function lrResolveTenantType(propertyDetail) { 3154 var chosen = null; 3155 try { chosen = sessionStorage.getItem('lr_chosen_tenant_type'); } catch (e) { /* noop */ } 3156 if (chosen) return chosen; 3157 var tenantName = propertyDetail && propertyDetail.tenant && propertyDetail.tenant.name; 3158 return (tenantName && LR_TENANT_TYPE_MAP[tenantName]) || undefined; 3159 } 3160 3161 // DISABLED 2026-09-26 with silentSaveRequirementFromProperty() above - 3162 // its "Yes, keep me posted" also replaced the requirement the tenant 3163 // just picked with the listing's own details. 3164 function showMatchMoreCard(propertyDetail) { 3165 return; 3166 if (!propertyDetail || document.getElementById('lrMatchMoreCard')) return; 3167 var typeName = (propertyDetail.property_type && propertyDetail.property_type.name) || ''; 3168 var categoryName = (propertyDetail.category && propertyDetail.category.name) || ''; 3169 var budget = propertyDetail.rent ? 'â¹' + Number(propertyDetail.rent).toLocaleStr
3169ing('en-IN') : ''; 3170 3171 var card = document.createElement('div'); 3172 card.id = 'lrMatchMoreCard'; 3173 card.setAttribute('role', 'dialog'); 3174 card.setAttribute('aria-label', 'Get matched with more properties like this'); 3175 card.style.cssText = 'position:fixed;left:16px;right:16px;bottom:16px;max-width:420px;margin:0 auto;background:#fff;border-radius:14px;box-shadow:0 8px 30px rgba(0,0,0,.18);padding:16px 18px;z-index:2100;font-family:inherit;'; 3176 card.innerHTML = 3177 '<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:8px;">' + 3178 '<strong style="font-size:15px;">Get matched with more like this?</strong>' + 3179 '<button type="button" data-lr-close aria-label="Dismiss" style="background:none;border:none;font-size:18px;line-height:1;cursor:pointer;color:#999;">×</button>' + 3180 '</div>' + 3181 '<div style="display:flex;flex-wrap:wrap;gap:6px;margin:10px 0;">' + 3182 (propertyDetail.address ? '<span style="font-size:12px;background:#f3f3f3;border-radius:6px;padding:3px 8px;">' + propertyDetail.address + '</span>' : '') + 3183 (typeName ? '<span style="font-size:12px;background:#f3f3f3;border-radius:6px;padding:3px 8px;">' + typeName + '</span>' : '') + 3184 (categoryName ? '<span style="font-size:12px;background:#f3f3f3;border-radius:6px;padding:3px 8px;">' + categoryName + '</span>' : '') + 3185 (budget ? '<span style="font-size:12px;background:#f3f3f3;border-radius:6px;padding:3px 8px;">~' + budget + '</span>' : '') + 3186 '</div>' + 3187 '<div style="display:flex;gap:10px;margin-top:4px;">' + 3188 '<button type="button" data-lr-yes style="flex:1;background:#FC4A0A;color:#fff;border:none;border-radius:8px;padding:10px;font-size:14px;cursor:pointer;">Yes, keep me posted</button>' + 3189 '<button type="button" data-lr-not-now style="background:none;border:1px solid #ddd;border-radius:8px;padding:10px 14px;font-size:14px;cursor:pointer;">Not now</button>' + 3190 '</div>'; 3191 document.body.appendChild(card); 3192 3193 function dismiss() { if (card.parentNode) card.parentNode.removeChild(card); } 3194 card.querySelector('[data-lr-close]').addEventListener('click', dismiss); 3195 card.querySelector('[data-lr-not-now]').addEventListener('click', dismiss); 3196 3197 card.querySelector('[data-lr-yes]').addEventListener('click', function () { 3198 var yesBtn = card.querySelector('[data-lr-yes]'); 3199 yesBtn.disabled = true; yesBtn.textContent = 'Saving...'; 3200 $.ajax({ 3201 url: 'https://www.letsrentz.com/saveTenantPlanRequirements', 3202 type: 'POST', 3203 data: { 3204 address: propertyDetail.address, 3205 lati: propertyDetail.lati, 3206 longi: propertyDetail.longi, 3207 property_type_id: propertyDetail.property_type_id, 3208 category_id_new: propertyDetail.category_id, 3209 max_rent: propertyDetail.rent, 3210 tenant_type: lrResolveTenantType(propertyDetail), 3211 _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 3212 }, 3213 success: function () { showToast("You're all set - we'll keep you posted."); dismiss(); }, 3214 error: function () { showToast('Something went wrong. Please try again.'); yesBtn.disabled = false; yesBtn.textContent = 'Yes, keep me posted'; } 3215 }); 3216 }); 3217 } 3218 3219 function saveRequirements(button) { 3220 $.ajax({ 3221 url: 'https://www.letsrentz.com/saveTenantPlanRequirements', 3222 type: "POST", 3223 data: { 3224 property_type_id: userPropertySearchData.selectedUserPropertyTypeId, 3225 category_id_new: userPropertySearchData.selectedUserCategoryId, 3226 tenant_type: userPropertySearchData.selectedTenantType, 3227 max_rent: userPropertySearchData.selectedUserBudget, 3228 address: address, lati: lati, longi: longi, 3229 _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' 3230 }, 3231 success: function (response) { 3232 if (response.status) { 3233 $("#budgetEstimateModalOverlay").removeClass("show"); 3234 // A pending action means this wizard run started from a
3235 // gated tap on a specific listing (only possible today via 3236 // the generic entry point reaching this far, e.g. the 3237 // account icon with no property in view then choosing to 3238 // fill the wizard anyway) - return to that listing instead 3239 // of the generic search results. Otherwise, unchanged: 3240 // send them straight to matching listings, same redirect 3241 // the edit-requirement page uses. 3242 var pendingAfterSave = getPendingAction(); 3243 if (pendingAfterSave) { 3244 clearPendingAction(); 3245 window.location.href = pendingAfterSave.return_url; 3246 } else { 3247 window.location.href = response.redirect_url || window.location.href; 3248 } 3249 } else { 3250 button.prop('disabled', false).text('Continue'); 3251 showToast(response.message || 'Something went wrong. Please try again.'); 3252 } 3253 }, 3254 error: function () { 3255 button.prop('disabled', false).text('Continue'); 3256 showToast('Something went wrong. Please try again.'); 3257 } 3258 }); 3259 } 3260 document.addEventListener('DOMContentLoaded', function () { 3261 // Re-fire whatever gated action (Contact Now, Shortlist, ...) got 3262 // interrupted by the auth wall, now that we're back and logged in. 3263 resumePendingAction(); 3264 3265 // OTP auto-advance/back, matching the real site's .otp-input handling. 3266 var otpInputs = document.querySelectorAll('.otp-input'); 3267 otpInputs.forEach(function (input, index) { 3268 input.addEventListener('input', function (e) { 3269 // .ob-filled is purely visual (see onboarding-v2.css) - doesn't 3270 // change any of the existing auto-advance/validation logic. 3271 e.target.classList.toggle('ob-filled', e.target.value.length === 1); 3272 if (e.target.value.length === 1 && index < otpInputs.length - 1) otpInputs[index + 1].focus(); 3273 }); 3274 input.addEventListener('keydown', function (e) { 3275 if (e.key === 'Backspace' && e.target.value === '' && index > 0) otpInputs[index - 1].focus(); 3276 }); 3277 }); 3278 $('.otp-input').on('keydown', function (e) { 3279 var index = $('.otp-input').index($(this)); 3280 if (index == 3) { 3281 var continueBtn = document.querySelector('.otp-verify-btn'); 3282 if (continueBtn) { continueBtn.style.backgroundColor = 'rgba(255,84,3,1)'; continueBtn.style.color = '#fff'; continueBtn.disabled = false; } 3283 } 3284 }); 3285 3286 $(".name_continue_btn").on("click", function () { 3287 var fullName = $("#register_full_name").val().trim(); 3288 if (!fullName) { $(".login-error-name-message").show(); return; } 3289 $(".login-error-name-message").hide(); 3290 register_full_name = fullName; 3291 $("#nameModalOverlay").removeClass("show"); 3292 3293 // Registering because of a gated tap on a specific listing 3294 // (Contact Now, Shortlist, ...) - Tenant and Property Buyer both 3295 // post the same user_type ('user'; only Owner/Broker differ, see 3296 // login-modal.blade.php), so there's nothing the purpose/ 3297 // account-type screens would tell us that the entry point hasn't 3298 // already answered. Skip those straight to registering. Generic 3299 // entry (no pending action, e.g. the header account icon) keeps 3300 // asking both, unchanged - that's the one case with nothing to 3301 // infer from. 3302 // 3303 // tenant_type is different: unlike location/type/category/budget, 3304 // it's genuinely not inferable from the listing (most properties 3305 // are tagged "All" or a multi-audience combo, not a single clean 3306 // value - see LR_TENANT_TYPE_MAP's comment) - so it's asked here 3307 // too, reusing the same #tenantTypeModalOverlay the full wizard 3308 // uses, before resuming the original action. 3309 var pendingAfterName = getPendingAction(); 3310 if (pendingAfterName) { 3311 userType = 'user';
3311 user_type = 'user'; 3312 $("#tenantTypeModalOverlay").addClass("show"); 3313 return; 3314 } 3315 3316 $("#purposeModalOverlay").addClass("show"); 3317 }); 3318 3319 // Purpose now resolves user_type directly (search -> user, owner -> 3320 // owner, broker -> broker) instead of leading into a separate 3321 // account-type screen - Tenant/Property Buyer already collapsed to 3322 // the same 'user' value there, so that second screen never told us 3323 // anything the three purpose options don't already say. Registers 3324 // immediately on continue, same as the account-type screen used to. 3325 // #accountTypeModalOverlay and its handlers are left in place but 3326 // unreferenced - dead code, removed in a later cleanup pass once 3327 // this is confirmed stable in production. 3328 $("input[name='userPurpose']").on("change", function () { userPurpose = $(this).val(); }); 3329 $(".purpose_continue_btn").on("click", function () { 3330 if (!userPurpose) { toastr.success("Please select an option"); return; } 3331 userType = userPurpose === 'search' ? 'user' : userPurpose; 3332 user_type = userType; 3333 var btn = $(this); 3334 btn.text('Processing...').prop('disabled', true).css({ 'background-color': '#b0b0b0', 'cursor': 'not-allowed' }); 3335 $("#purposeModalOverlay").removeClass("show"); 3336 registerRequest(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id); 3337 }); 3338 3339 $("input[name='accountType']").on("change", function () { 3340 userType = $(this).val(); 3341 user_type = $(this).val(); 3342 }); 3343 $(".account_type_continue_btn").on("click", function () { 3344 if (!userType) { toastr.success("Please select user type"); return; } 3345 var btn = $(this); 3346 btn.text('Processing...').prop('disabled', true).css({ 'background-color': '#b0b0b0', 'cursor': 'not-allowed' }); 3347 registerRequest(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id); 3348 }); 3349 3350 $("input[name='property_type']").on("change", function () { 3351 userPropertySearchData.selectedUserPropertyTypeId = $(this).val(); 3352 }); 3353 $(".property_type_continue_btn").on("click", function () { 3354 var selectedUserPropertyTypeId = $("input[name='property_type']:checked").val(); 3355 if (!selectedUserPropertyTypeId) { showToast("Please select property type"); return; } 3356 userPropertySearchData.selectedUserPropertyTypeId = selectedUserPropertyTypeId; 3357 var propertyIdToUse = selectedUserPropertyTypeId; 3358 if (selectedUserPropertyTypeId == 2) { propertyIdToUse = 1; } 3359 var selectedProperty = authPropertyTypes.find(function (property) { return property.id == propertyIdToUse; }); 3360 var categoryHtml = ''; 3361 if (selectedProperty && selectedProperty.sub_property_type && selectedProperty.sub_property_type.length) { 3362 selectedProperty.sub_property_type.forEach(function (category) { 3363 categoryHtml += '<div class="property-item"><input type="radio" id="category_' + category.id + '" name="sub_category_type" value="' + category.id + '"><label for="category_' + category.id + '">' + category.name + '</label></div>'; 3364 }); 3365 } 3366 $("#categoryOptions").html(categoryHtml); 3367 $("#propertyTypeModalOverlay").removeClass("show"); 3368 $("#categoryModalOverlay").addClass("show"); 3369 }); 3370 3371 $(document).on("change", "input[name='sub_category_type']", function () { 3372 userPropertySearchData.selectedUserCategoryId = $(this).val(); 3373 }); 3374 $(".category_continue_btn").on("click", function () { 3375 var selectedUserCategoryId = $("input[name='sub_category_type']:checked").val(); 3376 if (!selectedUserCategoryId) { showToast("Please select category"); return; } 3377 userPropertySearchData.selectedUserCategoryId = selectedUserCategoryId; 3378 $("#categoryModalOverlay").removeClass("show"); 3379 $("#tenantTypeModalOverlay").addClass("show"); 3380 }); 3381 3382 $(document).on("change", "input[name='tenant_type']", function () { 3383 userPropertySearchData.selectedUserTenantType = $(this).val(); 3384 }); 3385 // Mirrors CommonLibrary::$categoryBudgetFloors server-side â kept
3386 // here too so an unrealistic budget bucket (e.g. "Below 8K" for a 3387 // 3BHK) is hidden before the tenant ever picks it, rather than only 3388 // rejected after. The server-side check is still the enforced one; 3389 // this is just the UX layer. 3390 var categoryBudgetFloors = { 3391 '1BHK': 6000, '2BHK': 9000, '3BHK': 13000, '4BHK': 20000, '4+ BHK': 18000, 3392 'Annexy': 8000, 'Studio Apartment': 6000, 3393 '1 Room with Kitchen': 2500, '2 Room with Kitchen': 5000, '3 Room with Kitchen': 8000, 3394 'Single Room': 2500, 'Two Sharing': 2000, 'Three Sharing': 2000, 'Four Sharing': 2500, 'Five Sharing': 2500 3395 }; 3396 3397 function applyCategoryBudgetFloor(categoryId) { 3398 var floor = 0; 3399 (authPropertyTypes || []).forEach(function (property) { 3400 (property.sub_property_type || []).forEach(function (category) { 3401 if (String(category.id) === String(categoryId) && categoryBudgetFloors.hasOwnProperty(category.name)) { 3402 floor = categoryBudgetFloors[category.name]; 3403 } 3404 }); 3405 }); 3406 $("input[name='budget_estimate']").each(function () { 3407 var option = $(this).closest('.property-item'); 3408 if (Number($(this).val()) < floor) { 3409 $(this).prop('checked', false); 3410 option.hide(); 3411 } else { 3412 option.show(); 3413 } 3414 }); 3415 } 3416 3417 $(".tenant_type_continue_btn").on("click", function () { 3418 var selectedTenantType = $("input[name='tenant_type']:checked").val(); 3419 if (!selectedTenantType) { showToast("Please select tenant type"); return; } 3420 3421 // Reached from the property-triggered quick path (see 3422 // .name_continue_btn above) rather than the full generic-entry 3423 // wizard - store the tenant's own answer for 3424 // silentSaveRequirementFromProperty() to use (it takes priority 3425 // there over guessing from the listing's own tenant tag), then go 3426 // straight to registering instead of the property-type/category/ 3427 // budget screens, which came from the listing itself already. 3428 var pendingBeforeRegister = getPendingAction(); 3429 if (pendingBeforeRegister) { 3430 try { sessionStorage.setItem('lr_chosen_tenant_type', selectedTenantType); } catch (e) { /* noop */ } 3431 $("#tenantTypeModalOverlay").removeClass("show"); 3432 var tenantBtn = $(this); 3433 tenantBtn.text('Processing...').prop('disabled', true); 3434 registerRequest(user_type, user_image, register_full_name, register_phone_number, register_email, register_password, social_type, social_id); 3435 return; 3436 } 3437 3438 userPropertySearchData.selectedTenantType = selectedTenantType; 3439 applyCategoryBudgetFloor(userPropertySearchData.selectedUserCategoryId); 3440 $("#tenantTypeModalOverlay").removeClass("show"); 3441 $("#budgetEstimateModalOverlay").addClass("show"); 3442 }); 3443 3444 $(".budget_estimate_continue_btn").on("click", function () { 3445 var selectedBudgetEstimate = $("input[name='budget_estimate']:checked").val(); 3446 if (!selectedBudgetEstimate) { showToast("Please select your budget"); return; } 3447 userPropertySearchData.selectedUserBudget = selectedBudgetEstimate; 3448 var button = $(this); 3449 button.prop("disabled", true).text("Please wait..."); 3450 saveRequirements(button); 3451 }); 3452 }); 3453 3454 document.addEventListener('DOMContentLoaded', function () { 3455 // Profile dropdown (desktop + mobile triggers share the same markup/behavior) 3456 document.querySelectorAll('[data-hp-profile-trigger]').forEach(function (btn) { 3457 var menu = btn.closest('.hp-profile-trigger').querySelector('.hp-profile-menu'); 3458 btn.addEventListener('click', function (e) { 3459 e.stopPropagation(); 3460 var wasOpen = menu.classList.contains('open'); 3461 document.querySelectorAll('.hp-profile-menu.open').forEach(function (m) { m.classList.remove('open'); }); 3462 if (!wasOpen) menu.classList.add('open'); 3463 }); 3464 }); 3465 document.addEventListener('click', function () { 3466 document.querySelectorAll('.hp-profile-menu.open').forEach(function (m) { m.classList.remove('open'); }); 3467 }); 3468 3469 // Real logout - same PUT /auth/logout + login_token as the live site's
3470 // logoutRequest() in template.blade.php. Asks for confirmation in the 3471 // shared #hpLogoutOverlay modal (header.blade.php) first, then shows a 3472 // spinner on the confirm button while the request is in flight. 3473 var hpLogoutOverlay = document.getElementById('hpLogoutOverlay'); 3474 var hpLogoutCancel = document.getElementById('hpLogoutCancel'); 3475 var hpLogoutConfirm = document.getElementById('hpLogoutConfirm'); 3476 3477 function openHpLogoutModal() { hpLogoutOverlay.classList.add('show'); hpLogoutOverlay.setAttribute('aria-hidden', 'false'); } 3478 function closeHpLogoutModal() { hpLogoutOverlay.classList.remove('show'); hpLogoutOverlay.setAttribute('aria-hidden', 'true'); } 3479 3480 function hpLogout(e) { 3481 e.preventDefault(); 3482 openHpLogoutModal(); 3483 } 3484 3485 if (hpLogoutOverlay) { 3486 hpLogoutCancel.addEventListener('click', closeHpLogoutModal); 3487 hpLogoutOverlay.addEventListener('click', function (e) { if (e.target === hpLogoutOverlay) closeHpLogoutModal(); }); 3488 document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && hpLogoutOverlay.classList.contains('show')) closeHpLogoutModal(); }); 3489 3490 hpLogoutConfirm.addEventListener('click', function () { 3491 hpLogoutConfirm.disabled = true; 3492 hpLogoutConfirm.innerHTML = 'Logging out...'; 3493 $.ajax({ 3494 url: 'https://www.letsrentz.com/auth/logout', 3495 method: 'PUT', 3496 data: { login_token: localStorage.getItem('login_token'), _token: 'bvRQeC7zDEZMxhJdt8x263POgOGJvjDEH4HLTaIw' }, 3497 success: function () { 3498 localStorage.removeItem('login_token'); 3499 clearPendingAction(); 3500 window.location.reload(); 3501 }, 3502 error: function () { window.location.reload(); } 3503 }); 3504 }); 3505 } 3506 3507 var hpLogoutDesktop = document.getElementById('hpLogoutBtnDesktop'); 3508 var hpLogoutMobile = document.getElementById('hpLogoutBtnMobile'); 3509 if (hpLogoutDesktop) hpLogoutDesktop.addEventListener('click', hpLogout); 3510 if (hpLogoutMobile) hpLogoutMobile.addEventListener('click', hpLogout); 3511 3512 // Bottom-nav "Account" tab (footer.blade.php) - opens the same 3513 // account menu as the header's profile avatar, as a bottom sheet. 3514 var hpAccountSheetOverlay = document.getElementById('hpAccountSheetOverlay'); 3515 var hpAccountSheetClose = document.getElementById('hpAccountSheetClose'); 3516 function openHpAccountSheet(e) { 3517 if (e) e.preventDefault(); 3518 if (hpAccountSheetOverlay) { hpAccountSheetOverlay.classList.add('show'); hpLockBodyScroll(); } 3519 } 3520 function closeHpAccountSheet(e) { 3521 if (e) e.stopPropagation(); 3522 if (hpAccountSheetOverlay) { hpAccountSheetOverlay.classList.remove('show'); hpUnlockBodyScroll(); } 3523 } 3524 document.querySelectorAll('[data-hp-account-sheet-trigger]').forEach(function (btn) { 3525 btn.addEventListener('click', openHpAccountSheet); 3526 }); 3527 if (hpAccountSheetOverlay) { 3528 if (hpAccountSheetClose) hpAccountSheetClose.addEventListener('click', closeHpAccountSheet); 3529 hpAccountSheetOverlay.addEventListener('click', function (e) { if (e.target === hpAccountSheetOverlay) closeHpAccountSheet(e); }); 3530 document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && hpAccountSheetOverlay.classList.contains('show')) closeHpAccountSheet(e); }); 3531 } 3532 var hpLogoutBottomNav = document.getElementById('hpLogoutBtnBottomNav'); 3533 if (hpLogoutBottomNav) { 3534 hpLogoutBottomNav.addEventListener('click', function (e) { 3535 closeHpAccountSheet(); 3536 hpLogout(e); 3537 }); 3538 } 3539 3540 // Header notification bell (header.blade.php) - persistent way back 3541 // in to enable web push once the soft-ask banner above has been 3542 // snoozed or missed. Reflects Notification.permission as its state 3543 // and, when blocked, points at the browser's own site settings 3544 // (there's no JS API to re-prompt once denied). 3545 // Instructions differ by browser (icon, menu labels) since there's 3546 // no API to open site settings for the user - the best we can do is 3547 // point at the right spot. Order matters: Edge/Samsung/Opera UAs 3548 // also contain "Chrome", so those checks must come first. 3549 function hpNotifHelpText() { 3550 var ua = navigator.userAgent || ''; 3551 var isAndroid = /Android/.test(ua); 3552 var isMac = /Macintosh/.test(ua) && !isAndroid; 3553 if (/EdgA?\//.test(ua)) { 3554 return isAndroid 3555 ? 'You\'ve blocked notifications for Lets Rentz in Edge. Tap the <b>lock icon</b> next to the web address, open <b>Permissions</b>, then turn <b>Notifications</b> on. Reload this page afterward.' 3556 : 'You\'ve blocked notifications for Lets Rentz in Edge. Click the <b>lock icon</b> in the address bar, choose <b>Permissions for this site</b>, then set <b>Notifications</b> to <b>Allow</b>. Reload this page afterward.'; 3557 } 3558 if (/SamsungBrowser/.test(ua)) { 3559 return 'You\'ve blocked notifications for Lets Rentz. Tap the <b>lock icon</b> next to the web address, open <b>Site settings â Notifications</b>, and turn it on. Reload this page afterward.'; 3560 } 3561 if (/Firefox/.test(ua)) { 3562 return 'You\'ve blocked notifications for Lets Rentz in Firefox. Click the <b>lock icon</b> in the address bar, open <b>Permissions</b>, and set <b>Send Notifications</b> to <b>Allow</b>. Reload this page afterward.'; 3563 } 3564 if (/Chrome/.test(ua)) { 3565 return isAndroid 3566 ? 'You\'ve blocked notifications for Lets Rentz in Chrome. Tap the <b>lock</b> or <b>â icon</b> just left of the web address, tap <b>Permissions â Notifications</b>, and switch it to <b>Allow</b>. Reload this page afterward.' 3567 : 'You\'ve blocked notifications for Lets Rentz in Chrome. Click the <b>lock icon</b> in the address bar, choose <b>Site settings</b>, then set <b>Notifications</b> to <b>Allow</b>. Reload this page afterward.'; 3568 } 3569 if (/Safari/.test(ua) && isMac) { 3570 return 'You\'ve blocked notifications for Lets Rentz in Safari. Open the <b>Safari</b> menu â <b>Settings</b> â <b>Websites</b> â <b>Notifications</b>, find this site in the list, and set it to <b>Allow</b>.'; 3571 } 3572 return 'You\'ve blocked notifications for Lets Rentz in this browser. To turn them back on, open the site settings from your address bar (click the icon next to the URL), allow notifications, then reload the page.'; 3573 } 3574 3575 var hpNotifHelpOverlay = document.getElementById('hpNotifHelpOverlay'); 3576 var hpNotifHelpClose = document.getElementById('hpNotifHelpClose'); 3577 var hpNotifHelpBody = document.getElementById('hpNotifHelpBody'); 3578 if (hpNotifHelpOverlay && hpNotifHelpClose) { 3579 hpNotifHelpClose.addEventListener('click', function () { 3580 hpNotifHelpOverlay.classList.remove('show'); 3581 hpNotifHelpOverlay.setAttribute('aria-hidden', 'true'); 3582 }); 3583 hpNotifHelpOverlay.addEventListener('click', function (e) { 3584 if (e.target === hpNotifHelpOverlay) { 3585 hpNotifHelpOverlay.classList.remove('show'); 3586 hpNotifHelpOverlay.setAttribute('aria-hidden', 'true'); 3587 } 3588 }); 3589 } 3590 3591 function hpInitNotifBell(btn) { 3592 if (!btn) { return; } 3593 if (!('Notification' in window)) { btn.style.display = 'none'; return; } 3594 3595 function applyState() { 3596 var perm = Notification.permission; 3597 btn.classList.remove('is-on', 'is-denied'); 3598 var icon = btn.querySelector('i'); 3599 if (perm === 'granted') { 3600 btn.classList.add('is-on'); 3601 btn.title = 'Notifications are on'; 3602 if (icon) icon.className = 'bi bi-bell-fill'; 3603 } else if (perm === 'denied') { 3604 btn.classList.add('is-denied'); 3605 btn.title = 'Notifications blocked - click for help'; 3606 if (icon) icon.className = 'bi bi-bell-slash'; 3607 } else { 3608 btn.title = 'Enable notifications'; 3609 if (icon) icon.className = 'bi bi-bell'; 3610 } 3611 } 3612 3613 applyState(); 3614 btn.addEventListener('click', function () { 3615 var perm = Notification.permission; 3616 if (perm === 'granted') { return; } 3617 if (perm === 'denied') { 3618 if (hpNotifHelpOverlay) { 3619 if (hpNotifHelpBody) { hpNotifHelpBody.innerHTML = hpNotifHelpText(); } 3620 hpNotifHelpOverlay.classList.add('show'); 3621 hpNotifHelpOverlay.setAttribute('aria-hidden', 'false'); 3622 } 3623 return; 3624 } 3625 if (typeof window.lrRequestPushPermission === 'function') { 3626 window.lrRequestPushPermission().then(applyState); 3627 } 3628 }); 3629 } 3630 hpInitNotifBell(document.getElementById('hpNotifBellDesktop')); 3631 hpInitNotifBell(document.getElementById('hpNotifBellMobile')); 3632 3633 document.querySelectorAll('[data-hp-login-trigger]').forEach(function (btn) { 3634 btn.addEventListener('click', function (e) { openLoginPopup(e); }); 3635 }); 3636 3637 // Any logged-in tenant with no plan_requirements row yet (registered 3638 // and left mid-flow, or an old account that skipped it entirely, or 3639 // just logged in rather than registered) gets the requirement wizard 3640 // resumed here. loginRequest() reloads the page on success, so this 3641 // also covers "just logged in" without needing its own trigger.
3642 // Skipped when a pending action just resumed on this same load 3643 // (registerRequest()'s success handler redirected here specifically 3644 // to finish that, not to fill this wizard). By the time this runs, 3645 // resumePendingAction() (wired into the earlier DOMContentLoaded 3646 // listener above, which fires first) has already cleared the 3647 // pending-action marker itself - lr_show_match_more is the one that 3648 // survives to here (timestamped, not consumed until the resumed 3649 // click's own success handler reads it), so this check has 3650 // something to read. Bounded to LR_SHOW_MATCH_MORE_TTL_MS so a 3651 // failed/slow resume can't suppress this nudge indefinitely - it's 3652 // still the fallback safety net for every other "needs a 3653 // requirement" case, this is only a few seconds' grace to let the 3654 // resume self-heal via silentSaveRequirementFromProperty() first. 3655 3656 lpInitVideoCards(); 3657 }); 3658 3659 // Listing-card video previews (99acres-style): the cover image stays as 3660 // the poster until a card scrolls into view, at which point a muted, 3661 // no-controls YouTube embed is created and capped to a short ~10s loop 3662 // via end=10&loop=1&playlist=<id> (YouTube's loop param only works 3663 // combined with playlist=<own id> for a single video). Global (not 3664 // inside the DOMContentLoaded closure above) so listing.blade.php can 3665 // call lpInitVideoCards() again after it appends more cards via AJAX - 3666 // the :not([data-video-observed]) guard means already-wired cards are 3667 // skipped on repeat calls. 3668 var lpVideoObserver = null; 3669 var LP_DEFAULT_VIDEO_ASPECT_RATIO = 16 / 9; // used until the real ratio loads 3670 var lpVideoAspectRatioCache = {}; // videoId -> ratio, so repeat cards/resizes skip the fetch 3671 3672 // Property tour videos aren't all landscape 16:9 - some are vertical 3673 // Shorts/reel-style uploads, which YouTube pillarboxes hard inside a 3674 // landscape card. YouTube's own oEmbed endpoint can't tell us which: 3675 // it returns a fixed ~16:9 size for a /watch URL and a fixed ~9:16 3676 // size for a /shorts URL regardless of the actual video, so it's 3677 // useless for detection. Our own /getVideoAspectRatio endpoint reads 3678 // the real encoded stream dimensions server-side instead (cached 3679 // after the first look-up) so the cover math below is correct for 3680 // either orientation. 3681 function lpFetchVideoAspectRatio(videoId, callback) { 3682 if (lpVideoAspectRatioCache[videoId]) { 3683 callback(lpVideoAspectRatioCache[videoId]); 3684 return; 3685 } 3686 fetch('https://www.letsrentz.com/getVideoAspectRatio/' + videoId) 3687 .then(function (res) { return res.ok ? res.json() : null; }) 3688 .then(function (data) { 3689 var ratio = (data && data.status && data.ratio) ? data.ratio : LP_DEFAULT_VIDEO_ASPECT_RATIO; 3690 lpVideoAspectRatioCache[videoId] = ratio; 3691 callback(ratio); 3692 }) 3693 .catch(function () { 3694 lpVideoAspectRatioCache[videoId] = LP_DEFAULT_VIDEO_ASPECT_RATIO; 3695 callback(LP_DEFAULT_VIDEO_ASPECT_RATIO); 3696 }); 3697 } 3698 3699 // iframes can't use object-fit:cover (it only applies to replaced 3700 // elements like img/video), and YouTube's own embed letterboxes/ 3701 // pillarboxes the video to fit whatever box the iframe is - which is 3702 // why a card box that isn't the video's own aspect ratio was showing 3703 // black bars. This is the standard "cover" trick instead: size the 3704 // iframe bigger than the visible box (by container-vs-video aspect 3705 // ratio) and center it, so the letterboxed edges land outside 3706 // .lp-card-video's overflow:hidden bounds and only the filled video 3707 // is visible - same idea whether the source is landscape or a 3708 // vertical reel. 3709 function lpSizeVideoIframe(el, iframe, videoRatio) { 3710 var w = el.offsetWidth; 3711 var h = el.offsetHeight; 3712 if (!w || !h) return; 3713 var ratio = videoRatio || LP_DEFAULT_VIDEO_ASPECT_RATIO; 3714 var containerRatio = w / h; 3715 var videoW, videoH; 3716 if (containerRatio > ratio) { 3717 videoW = w; 3718 videoH = w / ratio; 3719 } else { 3720 videoH = h; 3721 videoW = h * ratio; 3722 } 3723 iframe.style.width = videoW + 'px'; 3724 iframe.style.height = videoH + 'px'; 3725 iframe.style.left = ((w - videoW) / 2) + 'px'; 3726 iframe.style.top = ((h - videoH) / 2) + 'px'; 3727 } 3728 3729 function lpGetVideoObserver() { 3730 if (lpVideoObserver) return lpVideoObserver; 3731 lpVideoObserver = new IntersectionObserver(function (entries) {
3732 entries.forEach(function (entry) { 3733 var el = entry.target; 3734 var iframe = el.querySelector('iframe'); 3735 if (entry.isIntersecting) { 3736 if (!iframe) { 3737 var videoId = el.getAttribute('data-video-id'); 3738 iframe = document.createElement('iframe'); 3739 iframe.src = 'https://www.youtube.com/embed/' + videoId 3740 + '?autoplay=1&mute=1&controls=0&rel=0&playsinline=1&enablejsapi=1&loop=1&end=10&playlist=' + videoId; 3741 iframe.setAttribute('allow', 'autoplay; encrypted-media'); 3742 iframe.setAttribute('frameborder', '0'); 3743 el.insertBefore(iframe, el.firstChild); 3744 lpSizeVideoIframe(el, iframe, lpVideoAspectRatioCache[videoId]); 3745 el.classList.add('is-playing'); 3746 lpFetchVideoAspectRatio(videoId, function (ratio) { 3747 if (document.body.contains(iframe)) { 3748 lpSizeVideoIframe(el, iframe, ratio); 3749 } 3750 }); 3751 } else { 3752 iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'playVideo', args: [] }), '*'); 3753 } 3754 } else if (iframe) { 3755 iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func: 'pauseVideo', args: [] }), '*'); 3756 } 3757 }); 3758 }, { threshold: 0.5 }); 3759 return lpVideoObserver; 3760 } 3761 3762 function lpInitVideoCards(root) { 3763 var scope = root || document; 3764 var observer = lpGetVideoObserver(); 3765 scope.querySelectorAll('.lp-card-video:not([data-video-observed])').forEach(function (el) { 3766 el.setAttribute('data-video-observed', '1'); 3767 observer.observe(el); 3768 3769 var muteBtn = el.querySelector('.lp-card-video-mute'); 3770 if (!muteBtn) return; 3771 muteBtn.addEventListener('click', function (e) { 3772 e.preventDefault(); 3773 e.stopPropagation(); 3774 var iframe = el.querySelector('iframe'); 3775 if (!iframe) return; 3776 var icon = muteBtn.querySelector('i'); 3777 var isMuted = icon.classList.contains('bi-volume-mute-fill'); 3778 iframe.contentWindow.postMessage(JSON.stringify({ event: 'command', func: isMuted ? 'unMute' : 'mute', args: [] }), '*'); 3779 icon.classList.toggle('bi-volume-mute-fill', !isMuted); 3780 icon.classList.toggle('bi-volume-up-fill', isMuted); 3781 }); 3782 }); 3783 } 3784 3785 // Card widths change at layout breakpoints (grid columns, sidebar 3786 // collapsing) - re-run the cover sizing for any videos already 3787 // playing so they don't end up letterboxed after a resize. 3788 var lpVideoResizeTimer = null; 3789 window.addEventListener('resize', function () { 3790 clearTimeout(lpVideoResizeTimer); 3791 lpVideoResizeTimer = setTimeout(function () { 3792 document.querySelectorAll('.lp-card-video.is-playing iframe').forEach(function (iframe) { 3793 var el = iframe.closest('.lp-card-video'); 3794 lpSizeVideoIframe(el, iframe, lpVideoAspectRatioCache[el.getAttribute('data-video-id')]); 3795 }); 3796 }, 200); 3797 }); 3798</script>
3798 3799
3800<script src="https://www.letsrentz.com/front/assets/js/loginv2.js?v=1.1"></script>
vendor: 1 bytes, line 3800
3800
3801<script>window.LR_LOGIN_V2_WEB_ENABLED = true;</script>
vendor: 1 bytes, line 3801
3801
3802<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a441bb85dc82f939',t:'MTc5MDkyMzQwMw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script>
3802<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"36f75870209544a590e47eba55e8bc11","r":1,"spa":2}' crossorigin="anonymous"></script>
3802 3803</body> 3804</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.