1import { selectAppLocale } from "./store-search-utils.js"; 2 3const template = document.createElement("template"); 4template.innerHTML = ` 5<style> 6 .lirt-storeselector-flyer-addon { 7 --background-brand: #F5F8FC; 8 --background-white: #F5F5F5; 9 --gray-darker: #636D79; 10 --success: #00704E; 11 --danger: #E60A14; 12 --primary: #0050AA; 13 --warning: #a84700; 14 15 display: flex; 16 height: 2.5rem; 17 padding: 0 0.5rem; 18 align-items: center; 19 justify-content: center; 20 } 21 22 .lirt-storeselector-flyer-addon__setStore, 23 .lirt-storeselector-flyer-addon__storeInfo { 24 display: flex; 25 flex-direction: row; 26 align-items: center; 27 justify-content: center; 28 width: calc(100vw - 1rem); 29 max-width: 21.5rem; 30 padding: 0.5rem; 31 gap: 0.25rem; 32 border-radius: 0.625rem; 33 background-color: unset; 34 border: none; 35 cursor: pointer; 36 37 &:focus-visible { 38 position: relative; 39 outline: none; 40 } 41 42 &:focus-visible:before { 43 content: ""; 44 display: block; 45 width: 100%; 46 height: 100%; 47 position: absolute; 48 outline: solid .125rem #ffffff; 49 outline-offset: .25rem; 50 pointer-events: none; 51 left: 0; 52 top: 0; 53 } 54 55 &:focus-visible:after { 56 content: ""; 57 display: block; 58 width: 100%; 59 height: 100%; 60 position: absolute; 61 outline: solid .125rem var(--primary); 62 outline-offset: .125rem; 63 pointer-events: none; 64 left: 0; 65 top: 0; 66 } 67 } 68 69 @media (min-width: 768px) { 70 .lirt-storeselector-flyer-addon__setStore, 71 .lirt-storeselector-flyer-addon__storeInfo { 72 width: 24rem; 73 max-width: unset; 74 } 75 } 76 @media (min-width: 1024px) { 77 .lirt-storeselector-flyer-addon__setStore, 78 .lirt-storeselector-flyer-addon__storeInfo { 79 width: 29rem; 80 } 81 } 82 83 @media (min-width: 1440px) { 84 .lirt-storeselector-flyer-addon__setStore, 85 .lirt-storeselector-flyer-addon__storeInfo { 86 width: 39.125rem; 87 } 88 } 89 90 :host([wonheader]) .lirt-storeselector-flyer-addon__setStore, 91 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo { 92 width: auto; 93 padding: 0.5rem 1rem; 94 gap 0.5rem; 95 background-color: #f0f7ff; 96 border-radius: 62.4375rem; 97 98 @media (min-width: 768px) { 99 padding: 0.75rem 1rem; 100 } 101 } 102 103 /* When the component has the 'wonheader' attribute, left-align the content */ 104 :host([wonheader]) .lirt-storeselector-flyer-addon__setStore, 105 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo { 106 justify-content: flex-start; 107 } 108 109 /* Truncate setStore headline only when 'wonheader' is active */ 110 :host([wonheader]) .lirt-storeselector-flyer-addon__setStore-text { 111 /* Allow text container to take remaining space and clip overflow in flex layout */ 112 min-width: 0; /* required for text-overflow inside flex item */ 113 width: 100%; 114 overflow: hidden; 115 } 116 117 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo { 118 white-space: nowrap; 119 } 120 121 :host([wonheader]) .lirt-storeselector-flyer-addon__setStoreHeadline, 122 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfoHeadline { 123 display: block; /* ensure ellipsis works reliably */ 124 text-align: left; 125 white-space: nowrap; 126 overflow: hidden; 127 text-overflow: ellipsis; 128 } 129 130 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfoHeadline { 131 min-width: 2.5rem; 132 max-width: 10rem; 133 134 @media (min-width: 1440px) { 135 max-width: 14rem; 136 } 137 } 138 139 :host([wonheader]) .lirt-storeselector-flyer-addon__setStore, 140 :host([wonheader]) .lirt-storeselector-flyer-addon__storeInfo { 141 @media (min-width: 768px) { 142 max-width: 16rem; 143 } 144 145 @media (min-width: 1440px) { 146 max-width: 24rem; 147 } 148 } 149 150 :host([wonheader]) .lirt-storeselector-flyer-addon__addInfo, 151 :host([wonheader]) .lirt-storeselector-flyer-addon__separator { 152 display: none; 153 } 154 155 :host([wonheader]) .lirt-storeselector-flyer-addon { 156 width: 100%; 157 padding: 0; 158 } 159 160 .lirt-storeselector-flyer-addon__storeInfo-text { 161 display: flex; 162 overflow: hidden; 163 gap: 0.25rem; 164 align-items: center; 165 166 @media (min-width: 1440px) { 167 gap: 0.5rem; 168 } 169 } 170 171 .lirt-storeselector-flyer-addon__setStoreHeadline, 172 .lirt-storeselector-flyer-addon__storeInfoHeadline { 173 font-family: 'LidlFontPro'; 174 font-style: normal; 175 font-weight: 400; 176 font-size: 0.688rem; 177 line-height: 1rem; 178 color: var(--primary); 179 text-decoration: underline; 180 181 @media (min-width: 1440px) { 182 font-size: 0.813rem; 183 line-height: 1.25rem; 184 } 185 } 186 187 .lirt-storeselector-flyer-addon__storeInfoHeadline { 188 189 white-space: nowrap; 190 overflow: hidden; 191 text-overflow: ellipsis; 192 flex-shrink: 1; 193 } 194 195 .lirt-storeselector-flyer-addon__storeInfoSubheadline { 196 display: flex; 197 flex-shrink: 0; 198 gap: 0.25rem; 199 } 200 201 .lirt-storeselector-flyer-addon__setStore-icons, 202 .lirt-storeselector-flyer-addon__storeInfo-icons { 203 height: 1rem; 204 flex-shrink: 0; 205 fill: var(--primary); 206 } 207 208 .lirt-storeselector-flyer-addon__status, 209 .lirt-storeselector-flyer-addon__separator, 210 .lirt-storeselector-flyer-addon__addInfo { 211 font-family: 'LidlFontPro'; 212 font-style: normal; 213 font-size: 0.688rem; 214 line-height: 1rem; 215 216 @media (min-width: 1440px) { 217 font-size: 0.813rem; 218 line-height: 1.25rem; 219 } 220 } 221 222 .lirt-storeselector-flyer-addon__status, 223 .lirt-storeselector-flyer-addon__addInfo { 224 white-space: nowrap; 225 } 226 227 .lirt-storeselector-flyer-addon__status { 228 font-weight: 400; 229 } 230 231 .lirt-storeselector-flyer-addon__separator, 232 .lirt-storeselector-flyer-addon__addInfo { 233 font-weight: 400; 234 235 } 236 237 .lirt-storeselector-flyer-addon__status--open { 238 color: var(--success); 239 } 240 241 .lirt-storeselector-flyer-addon__status--closed { 242 color: var(--danger); 243 } 244 245 .lirt-storeselector-flyer-addon__status--closingSoon { 246 color: var(--warning); 247 } 248 249 .hidden\\@all {
250 display: none; 251 } 252</style> 253<article class="lirt-storeselector-flyer-addon"> 254 <button class="lirt-storeselector-flyer-addon__setStore" part="set-store"> 255 <svg role="img" class="lirt-storeselector-flyer-addon__setStore-icons" part="icon" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 24 24"> 256 <path d="M12 2c3.584 0 6.5 2.916 6.5 6.5 0 3.685-4.07 9.51-6.5 12.792C9.57 18.01 5.5 12.185 5.5 8.5 5.5 4.916 8.416 2 12 2m0-1a7.5 7.5 0 0 0-7.5 7.5c0 4.142 4.286 10.179 7.5 14.464 3.214-4.286 7.5-10.322 7.5-14.464A7.5 7.5 0 0 0 12 1z"/> 257 <path d="M12 12.5c-2.206 0-4-1.794-4-4s1.794-4 4-4 4 1.794 4 4-1.794 4-4 4zm0-7c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3z"/> 258 </svg> 259 <span class="lirt-storeselector-flyer-addon__setStore-text"> 260 <span class="lirt-storeselector-flyer-addon__setStoreHeadline" part="text"></span> 261 </span> 262 </button> 263 264 <button class="lirt-storeselector-flyer-addon__storeInfo hidden@all"> 265 266 <span class="lirt-storeselector-flyer-addon__storeInfo-text"> 267 <svg role="img" class="lirt-storeselector-flyer-addon__storeInfo-icons" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> 268 <defs> 269 <path d="M18.5881634,55.3343014 L18.5881634,55.3343014 C19.2657532,56.2218995 20.643519,56.2218995 21.3211088,55.3343014 L21.3211088,55.3343014 C33.4273793,39.5090762 40,29.0635616 40,19.2133872 C40,8.63798019 31.0445219,0.032608061 20.0223951,6.73387979e-05 C7.14818954,-0.0323381438 -1.98797913,11.63633 0.372291886,23.7596215 C2.04367997,32.3649937 8.32267847,41.9120858 18.5881634,55.3343014 Z" id="path-1"></path> 270 <path d="M21.4105263,1.67480719 C19.92,0.372627585 18.0589474,-0.198132955 16.1726316,0.0682219635 L16.1263158,0.0766776752 C15.9789474,0.0978169544 15.8315789,0.127411945 15.6884211,0.157006936 C14.2736842,0.465640413 12.9642105,1.30698373 12,2.50346693 C11.0357895,1.30698373 9.72631579,0.461412557 8.31578947,0.15277908 C8.16842105,0.118956234 8.02526316,0.0935890986 7.87789474,0.0724498193 L7.82736842,0.0639941076 C5.94105263,-0.202360811 4.08,0.368399729 2.58947368,1.67057933 C0.943157895,3.11227818 0,5.25157324 0,7.54307111 L0,7.80519817 C0,10.0375061 0.808421053,12.1472061 2.26947368,13.7495635 L11.3768421,23.7273033 C11.7094737,24.0908989 12.2863158,24.0908989 12.6189474,23.7273033 L21.7263158,13.7495635 C23.1915789,12.1472061 24,10.0375061 24,7.80519817 L24,7.54307111 C24,5.25157324 23.0568421,3.11227818 21.4105263,1.67480719 Z" id="path-3"></path> 271 <path d="M32.24,0.46012468 C31.96,0.43012468 31.72,0.63012468 31.69,0.90012468 L31.67,1.11012468 C31.52,2.44012468 32.44,3.65012468 33.76,3.86012468 C39.08,4.68012468 41.51,5.89012468 42,6.50012468 C41.05,7.78012468 34.11,9.92012468 22.6,9.92012468 C11.09,9.92012468 4.15,7.79012468 3.2,6.50012468 C3.69,5.89012468 6.1,4.68012468 11.38,3.87012468 C12.7,3.67012468 13.62,2.45012468 13.47,1.12012468 L13.45,0.91012468 C13.42,0.63012468 13.17,0.43012468 12.9,0.47012468 C6.9,1.19012468 0,2.83012468 0,6.50012468 C0,13.0101247 21.69,13.0801247 22.61,13.0801247 C23.53,13.0801247 45.21,13.0101247 45.21,6.50012468 C45.21,2.81012468 38.28,1.18012468 32.24,0.46012468 Z" id="path-5"></path> 272 </defs> 273 <g id="map-/-locator-/-favorite" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> 274 <g id="lidl_icon_pin_default" transform="translate(12.000000, 0.000000)"> 275 <mask id="mask-2" fill="white"> 276 <use xlink:href="#path-1"></use> 277 </mask> 278 <use id="Shape" fill="#353B42" fill-rule="nonzero" xlink:href="#path-1"></use> 279 <g id="Group" mask="url(#mask-2)"> 280 <g transform="translate(-12.000000, 0.000000)" id="colors-/-01-brand-primary-/-10-base-#0050AA"> 281 <rect id="Rectangle" fill="#353B42" x="0" y="0" width="64" height="64"></rect> 282 </g> 283 </g> 284 <g id="Group" mask="url(#mask-2)"> 285 <g transform="translate(8.000000, 8.000000)" id="logos-/-RGB"> 286 <mask id="mask-4" fill="white"> 287 <use xlink:href="#path-3"></use> 288 </mask> 289 <g id="Shape" fill-rule="nonzero"></g> 290 <g id="Group" mask="url(#mask-4)" fill="#FFF"> 291 <g id=""> 292 <rect id="Rectangle" x="0" y="0" width="24" height="24"></rect> 293 </g> 294 </g> 295 </g> 296 </g> 297 </g> 298 </g> 299 </svg>
300 <span class="lirt-storeselector-flyer-addon__storeInfoHeadline"></span> 301 <span class="lirt-storeselector-flyer-addon__storeInfoSubheadline"> 302 <span class="lirt-storeselector-flyer-addon__status"></span> 303 <span class="lirt-storeselector-flyer-addon__separator hidden@all">●</span> 304 <span class="lirt-storeselector-flyer-addon__addInfo hidden@all"></span> 305 </span> 306 </span> 307 </button> 308</article> 309`; 310 311// web component 312class StoreselectorFlyerAddon extends HTMLElement { 313 constructor() { 314 super(); 315 316 this.locale = selectAppLocale( 317 window.mindShiftNav && window.mindShiftNav.locale, 318 ); 319 const timestamp = 320 this.getAttribute("data-deploy-timestamp") || "0000000000000"; 321 this._queryTranslations(timestamp).then((translations) => { 322 this.translations = translations; 323 const shadowRoot = this.attachShadow({ mode: "closed" }); 324 // clone template content nodes to the shadow DOM 325 shadowRoot.appendChild(template.content.cloneNode(true)); 326 327 this.setStore = shadowRoot.querySelector( 328 ".lirt-storeselector-flyer-addon__setStore", 329 ); 330 this.storeInfo = shadowRoot.querySelector( 331 ".lirt-storeselector-flyer-addon__storeInfo", 332 ); 333 this.setStoreHeadline = shadowRoot.querySelector( 334 ".lirt-storeselector-flyer-addon__setStoreHeadline", 335 ); 336 this.setStoreSubheadline = shadowRoot.querySelector( 337 ".lirt-storeselector-flyer-addon__setStoreSubheadline", 338 ); 339 this.storeInfoSubheadline = shadowRoot.querySelector( 340 ".lirt-storeselector-flyer-addon__storeInfoSubheadline", 341 ); 342 this.fallback = shadowRoot.querySelector( 343 ".lirt-storeselector-flyer-addon__fallback", 344 ); 345 this.storeName = shadowRoot.querySelector( 346 ".lirt-storeselector-flyer-addon__storeInfoHeadline", 347 ); 348 this.storeStatus = shadowRoot.querySelector( 349 ".lirt-storeselector-flyer-addon__status", 350 ); 351 this.storeAddInfo = shadowRoot.querySelector( 352 ".lirt-storeselector-flyer-addon__addInfo", 353 ); 354 this.seperator = shadowRoot.querySelector( 355 ".lirt-storeselector-flyer-addon__separator", 356 ); 357 this.pinIconDefault = shadowRoot.querySelector( 358 ".lirt-storeselector-flyer-addon__setStore-icons", 359 ); 360 this.pinIconFavorite = shadowRoot.querySelector( 361 ".lirt-storeselector-flyer-addon__storeInfo-icons", 362 ); 363 364 this.countryCode = this.locale.slice(3, 5); 365 366 window.addEventListener("storesearch", async (e) => { 367 if (e.detail.cookieSet === false) { 368 this.storeInfo.classList.add("hidden@all"); 369 this.setStore.classList.remove("hidden@all"); 370 } else { 371 let cookieValue = this._getCookie("ar"); 372 if (cookieValue !== undefined) { 373 await this._setValues(this._getDataFromCookie(cookieValue)); 374 this.setStore.classList.add("hidden@all"); 375 this.storeInfo.classList.remove("hidden@all"); 376 } 377 } 378 }); 379 380 // set text from translations 381 this.setStoreHeadline.innerText = 382 translations[ 383 `storeSearch.storeselectorFlyerAddon.textSetStoreHeadline` 384 ]; 385 386 this.pinIconDefault.setAttribute( 387 "aria-label", 388 translations[ 389 `storeSearch.storeselectorFlyerAddon.a11yLabelPinIconDefault` 390 ], 391 ); 392 393 this.pinIconFavorite.setAttribute( 394 "aria-label", 395 translations[ 396 `storeSearch.storeselectorFlyerAddon.a11yLabelPinIconFavorite` 397 ], 398 ); 399 400 // get cookie if it is available 401 let cookieValue = this._getCookie("ar"); 402 if (cookieValue !== undefined) { 403 (async () => { 404 await this._setValues(this._getDataFromCookie(cookieValue)); 405 })(); 406 } 407 }); 408 } 409 410 async _queryTranslations(timestamp) { 411 const url = `/s/translation/${this.locale}.json?_=${timestamp}`; 412 try { 413 let response = await fetch(url, { 414 method: "GET", 415 headers: { 416 "Content-Type": "application/json", 417 }, 418 }); 419 if (response.ok) { 420 return await response.json(); 421 } else { 422 console.error("unable to fetch translations"); 423 return null; 424 } 425 } catch (error) { 426 console.error("unable to fetch translations", error); 427 return null; 428 } 429 } 430 431 /** 432 * fetch data from timings: 433 * @param countryCode {string}
434 * @param entityID {string} 435 * @returns {Promise<any>} 436 */ 437 async _queryTimings(countryCode, entityID) { 438 // entityID needs to be 4 digits with leading 0 if it is less 439 let storeId = "00000" + entityID; 440 storeId = storeId.slice(-5); 441 442 let stage = ""; 443 if ( 444 window.location.href.indexOf("localhost") !== -1 || 445 window.location.href.indexOf("stores-frontend.local") !== -1 446 ) { 447 stage = "test"; 448 } else if (window.location.href.indexOf("https://dev.lidl") !== -1) { 449 stage = "dev"; 450 } else if (window.location.href.indexOf("https://test.lidl") !== -1) { 451 stage = "test"; 452 } else if (window.location.href.indexOf("https://qa.lidl") !== -1) { 453 stage = "qas"; 454 } else { 455 stage = "live"; 456 } 457 458 if (countryCode === "XI") { 459 countryCode = "NI"; 460 } 461 462 const queryUrl = `https://${stage}.api.schwarz/odj/stores-api/v2/myapi/stores-frontend/stores?object_number=${countryCode}${storeId}`; 463 464 try { 465 let response = await fetch(queryUrl, { 466 method: "GET", 467 headers: { 468 "Content-Type": "application/json", 469 "x-apikey": "KxboQtt40BG4VpBL16IhaRd2CXh0QbAc", 470 }, 471 }); 472 if (response.ok) { 473 const responseData = await response.json(); 474 return responseData; 475 } else { 476 return null; 477 } 478 } catch (error) { 479 console.error("unable to fetch data", error); 480 return null; 481 } 482 } 483 484 /** 485 * returns a cookie 486 * @param cookieName {string} 487 * @returns {string|undefined} 488 */ 489 _getCookie(cookieName) { 490 let matches = document.cookie.match( 491 new RegExp( 492 "(?:^|; )" + 493 cookieName.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, "\\$1") + 494 "=([^;]*)", 495 ), 496 ); 497 return matches ? decodeURIComponent(matches[1]) : undefined; 498 } 499 500 _mapV2ToV1(v2Response) { 501 if (!v2Response?.items?.[0]) return null; 502 const store = v2Response.items[0]; 503 const tz = store.openingHours.timeZone || "Europe/Berlin"; 504 const items = store.openingHours.items; 505 const todayStr = new Date().toLocaleDateString("en-CA", { timeZone: tz }); 506 const todayIndex = items.findIndex((i) => i.date === todayStr); 507 const start = todayIndex === -1 ? 0 : todayIndex; 508 509 const fixTime = (iso) => { 510 const time = iso?.split("T")[1] || "00:00:00"; 511 return time.startsWith("24:") ? "23:59:59" : time; 512 }; 513 514 return { 515 timeZone: tz, 516 specialStatus: store.status, 517 currentDate: { 518 date: items[start].date, 519 closed: items[start].timeRanges.length === 0, 520 reason: items[start].reason, 521 openingHours: items[start].timeRanges.map((r) => ({ 522 from: fixTime(r.from), 523 to: fixTime(r.to), 524 })), 525 }, 526 nextOpenDate: items.slice(start + 1).find((i) => i.timeRanges.length > 0), 527 }; 528 } 529 530 /** 531 * returns the store data from the cookie 532 * @param {string} cookieValue 533 * @returns {Map<any, any>|null} 534 * @private 535 */ 536 _getDataFromCookie(cookieValue) { 537 if (typeof cookieValue !== "undefined") { 538 this.setStore.classList.add("hidden@all"); 539 this.storeInfo.classList.remove("hidden@all"); 540 541 const cookieDataMap = new Map(); 542 const cookieData = cookieValue.split(";"); 543 for (const entry of cookieData) { 544 cookieDataMap.set(entry.split("=")[0], entry.split("=")[1]); 545 } 546 return cookieDataMap; 547 } else { 548 return null; 549 } 550 } 551 552 _formatDisplayTime(timeStr) { 553 if (!timeStr) return ""; 554 555 // Extract hours and minutes from the "HH:mm" string 556 const [hours, minutes] = timeStr.split(":").map(Number); 557 558 // Create a temporary date object to format the clock face 559 const dummyDate = new Date(); 560 dummyDate.setHours(hours, minutes, 0, 0); 561 // Explicitly force 12-hour format if the locale or country is US 562 const is12Hour = this.locale.includes("US") || this.locale.includes("XI"); 563 564 return new Intl.DateTimeFormat(this.locale, { 565 hour: "numeric", 566 minute: "2-digit", 567 hour12: is12Hour ? true : undefined, // 'undefined' falls back to the local country convention 568 }).format(dummyDate); 569 } 570 571 _isOpenTomorrow(currentDate, nextOpenDate) { 572 const sharedT = (key) => 573 this.translations[`shared.openingHours.${key}`].toLowerCase() || key; 574 const storeToday = new Date(currentDate.date); 575 const storeTomorrow = new Date(storeToday); 576 storeTomorrow.setDate(storeTomorrow.getDate() + 1); 577 const tomorrowStr = storeTomorrow.toLocaleDateString("en-CA"); 578 579 let msg = ""; 580 if (nextOpenDate && nextOpenDate.date === tomorrowStr) { 581 if (nextOpenDate.timeRanges?.[0]) { 582 const nextTime = nextOpenDate.timeRanges[0].from 583 .split("T")[1] 584 .slice(0, 5); 585 msg += ` - ${sharedT("opensAt")} ${this._formatDisplayTime(nextTime)}`; 586 } 587 } 588 589 if (currentDate.reason === "HOLIDAY") { 590 msg += ` (${sharedT("reason.publicHoliday")})`; 591 } 592 593 return msg; 594 } 595 596 _calculateCurrentStatus(data) { 597 const t = (key) => 598 this.translations[`storeSearch.storeselectorFlyerAddon.${key}`] || key; 599 const sharedT = (key) => 600 this.translations[`shared.openingHours.${key}`] || key; 601 602 const formatter = new Intl.DateTimeFormat("en-GB", { 603 timeZone: data.timeZone, 604 hour: "numeric", 605 minute: "numeric", 606 hour12: false, 607 }); 608 609 // Get current "Wall Clock" time at the store 610 const parts = formatter.formatToParts(new Date()); 611 const storeHour = parseInt(parts.find((p) => p.type === "hour").value); 612 const storeMinute = parseInt(parts.find((p) => p.type === "minute").value); 613 const storeTotalMins = storeHour * 60 + storeMinute; // current time in minutes based on stores timezone 614 615 const toMins = (h) => { 616 const [hour, min] = h.split(":").map(Number); 617 return hour * 60 + min; 618 }; 619 620 const today = data.currentDate; 621 const ranges = today.openingHours; 622 623 if (data.specialStatus.name === "new_opening") { 624 const label = `${t("textWillOpen")} ${this._formatShortDate(data.specialStatus.from)}`; 625 return { msg: label, class: "closed" }; 626 } 627 628 if (today.closed && today.reason === "TEMPORARY_CLOSING") { 629 return { msg: t("textStatusTemporaryClosed"), class: "closed" }; 630 } 631 632 // Check every range 633 for (let i = 0; i < ranges.length; i++) { 634 const openMins = toMins(ranges[i].from);
634 // converting opening hours to minutes 635 const closeMins = toMins(ranges[i].to); 636 637 const closeTimeIn24HFormat = ranges[i].to.slice(0, 5); 638 639 // ARE WE OPEN? 640 if (storeTotalMins < openMins) { 641 let label = `${sharedT("closed")}`; 642 643 label += this._isOpenTomorrow(today, data.nextOpenDate); 644 return { 645 msg: label, 646 class: "closed", 647 }; 648 } else if (storeTotalMins >= openMins && storeTotalMins < closeMins) { 649 const diff = closeMins - storeTotalMins; 650 const isClosingSoon = diff <= 60; // Within 1 hour 651 652 const state = isClosingSoon ? "closingSoon" : "open"; 653 const label = isClosingSoon 654 ? sharedT("closesSoonAt") 655 : sharedT("openTill"); 656 657 let secondLabel = ""; 658 if (today.reason === "HOLIDAY") { 659 secondLabel += `(${sharedT("reason.publicHoliday").toLowerCase()})`; 660 } 661 662 return { 663 msg: `${label} ${this._formatDisplayTime(closeTimeIn24HFormat)} ${secondLabel}`, 664 class: state, 665 }; 666 } 667 } 668 669 let finalMsg = 670 sharedT("closed") + this._isOpenTomorrow(today, data.nextOpenDate); 671 672 return { msg: finalMsg, class: "closed" }; 673 } 674 675 /** 676 * display the store data from the cookie and the timings endpoint 677 * @param storeDataFromCookie 678 * @returns {Promise<void>} 679 * @private 680 */ 681 682 async _setValues(storeDataFromCookie) { 683 if (storeDataFromCookie.size > 0) { 684 const street = (storeDataFromCookie.get("street") || "").replace( 685 /,$/, 686 "", 687 ); 688 this.storeName.innerText = `${street}, ${storeDataFromCookie.get("city")}`; 689 690 const v2RawData = await this._queryTimings( 691 this.countryCode, 692 storeDataFromCookie.get("EntityID"), 693 ); 694 const openingTimes = this._mapV2ToV1(v2RawData); 695 if (openingTimes) { 696 const status = this._calculateCurrentStatus(openingTimes); 697 698 this.storeStatus.innerText = status.msg; 699 this.storeStatus.className = `lirt-storeselector-flyer-addon__status lirt-storeselector-flyer-addon__status--${status.class}`; 700 701 this.storeInfo.classList.remove("hidden@all"); 702 this.setStore.classList.add("hidden@all"); 703 this.storeInfoSubheadline.classList.remove("hidden@all"); 704 } 705 } 706 } 707 708 /** 709 * format a date to a format to display: hh:mm 710 * @param {Date} time 711 * @returns {string} 712 * @private 713 */ 714 _formatShortDate(dateStr) { 715 const date = new Date(dateStr); 716 717 return new Intl.DateTimeFormat(this.locale, { 718 weekday: "short", 719 day: "2-digit", 720 month: "2-digit", 721 }).format(date); 722 } 723} 724 725// register component 726customElements.define("store-selector-flyer-addon", StoreselectorFlyerAddon);
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.