@influence-society-web/deshotelsetdesiles 4.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,955 @@
1
+ "use strict";
2
+ (() => {
3
+ // src/utils/constants.ts
4
+ var prefix = "data";
5
+ var SELECTORS = {
6
+ startingPriceList: `[${prefix}=starting-price-list]`,
7
+ price: `[${prefix}=price]`,
8
+ discount: `[${prefix}=discount]`,
9
+ discountContainer: `[${prefix}=discount-container]`,
10
+ currency: `[${prefix}=currency]`,
11
+ beHid: `[${prefix}-be-hid]`,
12
+ destination: `[${prefix}=destination]`,
13
+ offersList: `[${prefix}=offers-list]`,
14
+ /** Offer slug page: wrapper with existing main offer. Match both data="..." and data-... for Webflow. */
15
+ offerSlugDetail: `[${prefix}=offer-slug-detail], [data-offer-slug-detail]`,
16
+ /** Offer slug page: "Nos autres offres" list container. */
17
+ offerSlugOthersList: `[${prefix}=offer-slug-others-list], [data-offer-slug-others-list]`,
18
+ /** Offer slug page: card template inside offer-slug-others-list (use .offer-others_item if no data-list-item). */
19
+ offerSlugOthersListItem: ".offer-others_item",
20
+ /** Offer slug page: "Nos autres offres" section wrapper. Shown when other offers are rendered. */
21
+ offersMoreWrapper: `[${prefix}=offers-more-wrapper]`,
22
+ offersListContainer: `[${prefix}=offers-list-container]`,
23
+ listItem: `[${prefix}=list-item]`,
24
+ image: `[${prefix}=image]`,
25
+ name: `[${prefix}=name]`,
26
+ description: `[${prefix}=description]`,
27
+ hotelName: `[${prefix}=hotel-name]`,
28
+ hotelLink: `[${prefix}=hotel-link]`,
29
+ hotelStartingPriceContainer: `[${prefix}=hotel-starting-price-container]`,
30
+ moreDetails: `[${prefix}=more-details]`,
31
+ offersPopup: `[${prefix}=offers-popup]`,
32
+ offersPopupClose: `[${prefix}=close-btn]`,
33
+ hotelReserveLink: `[${prefix}=hotel-reserve-link]`,
34
+ hotelVillaReserveLink: `[${prefix}=hotel-villa-reserve-link]`,
35
+ hotelURLS: `[${prefix}=hotel-urls]`,
36
+ languageDropdown: `[${prefix}=language-dropdown]`,
37
+ selectedLanguage: `[${prefix}=selected-language]`,
38
+ loadingSpinner: `[${prefix}=loading-spinner]`,
39
+ cmsFilterEmptyState: `[fs-cmsfilter-element=empty]`,
40
+ startingfromItem: (id) => `[${prefix}-be-hid=${id}]`,
41
+ popupOverlay: `[fs-smartlightbox-element=trigger-close]`
42
+ };
43
+ var GUADELOUPE = {
44
+ en: "Guadeloupe Islands",
45
+ fr: "Les \xCEles de Guadeloupe",
46
+ es: "Las islas de Guadalupe",
47
+ it: "Le isole della Guadalupa"
48
+ };
49
+ var DOMINIQUE = {
50
+ en: "La Dominique",
51
+ fr: "La Dominique",
52
+ es: "Dominica",
53
+ it: "Dominica"
54
+ };
55
+ var SAINT_LUCIE = {
56
+ en: "Saint Lucia",
57
+ fr: "Sainte-Lucie",
58
+ es: "Santa Luc\xEDa",
59
+ it: "Santa Lucia"
60
+ };
61
+ var MARTINIQUE = {
62
+ en: "Martinique",
63
+ fr: "Martinique",
64
+ es: "Martinica",
65
+ it: "Martinica"
66
+ };
67
+ var ILE_MAURICE = {
68
+ en: "Mauritius",
69
+ fr: "\xCEle Maurice",
70
+ es: "Mauricio",
71
+ it: "Mauritius"
72
+ };
73
+ var SAINT_MARTIN = {
74
+ en: "Saint Martin",
75
+ fr: "Saint-Martin",
76
+ es: "Saint-Martin",
77
+ it: "Saint Martin"
78
+ };
79
+ var ANTIGUA = {
80
+ en: "Antigua",
81
+ fr: "Antigua",
82
+ es: "Antigua",
83
+ it: "Antigua"
84
+ };
85
+ var ANGUILLA = {
86
+ en: "Anguilla",
87
+ fr: "Anguilla",
88
+ es: "Anguila",
89
+ it: "Anguilla"
90
+ };
91
+ var SNIPPET_CODE_TO_HOTEL = {
92
+ gpgos12767: {
93
+ destination: GUADELOUPE,
94
+ slug: "/hotels-et-villas/creole-beach-hotel-spa",
95
+ codeIPlanet: "FRAN414",
96
+ cmsId: "67c809b5f63deab8a71cf8e9"
97
+ },
98
+ gptro12772: {
99
+ destination: GUADELOUPE,
100
+ slug: "/hotels-et-villas/le-jardin-malanga",
101
+ codeIPlanet: "FRAN416",
102
+ cmsId: "67c809b5f63deab8a71cf90d"
103
+ },
104
+ gpsai12770: {
105
+ destination: GUADELOUPE,
106
+ slug: "/hotels-et-villas/la-toubana-hotel-spa",
107
+ codeIPlanet: "FRAN411",
108
+ cmsId: "67c809b5f63deab8a71cf8ef"
109
+ },
110
+ gpgos12769: {
111
+ destination: GUADELOUPE,
112
+ slug: "/hotels-et-villas/mahogany-hotel-residence-spa",
113
+ codeIPlanet: "FRAN423",
114
+ cmsId: "67c809b5f63deab8a71cf91f"
115
+ },
116
+ gpgua27143: {
117
+ destination: GUADELOUPE,
118
+ slug: "/hotels-et-villas/langley-resort-fort-royal",
119
+ codeIPlanet: "GPAN32",
120
+ cmsId: "67c809b5f63deab8a71cf8f0"
121
+ },
122
+ mqros18592: {
123
+ destination: DOMINIQUE,
124
+ slug: "/hotels-et-villas/jungle-bay-dominica",
125
+ codeIPlanet: "DMAN1",
126
+ cmsId: "67c809b5f63deab8a71cf8ed"
127
+ },
128
+ dmtib00001: {
129
+ destination: DOMINIQUE,
130
+ slug: "/hotels-et-villas/secret-bay-dominique",
131
+ codeIPlanet: "DMAN2",
132
+ cmsId: "67c809b5f63deab8a71cfefd"
133
+ },
134
+ agste21689: {
135
+ destination: SAINT_LUCIE,
136
+ slug: "/hotels-et-villas/jade-mountain",
137
+ codeIPlanet: "AGAN6",
138
+ cmsId: "67c809b5f63deab8a71cf92a"
139
+ },
140
+ zzzzz25376: {
141
+ destination: SAINT_LUCIE,
142
+ slug: "/hotels-et-villas/windjammer-landing",
143
+ codeIPlanet: "LCAN4",
144
+ cmsId: "67c809b5f63deab8a71cf92b"
145
+ },
146
+ lcgro30400: {
147
+ destination: SAINT_LUCIE,
148
+ slug: "/hotels-et-villas/cap-maison",
149
+ codeIPlanet: "LCLC8",
150
+ cmsId: "67c809b5f63deab8a71cf929"
151
+ },
152
+ agste21688: {
153
+ destination: SAINT_LUCIE,
154
+ slug: "/hotels-et-villas/anse-chastanet",
155
+ codeIPlanet: "AGAN5",
156
+ cmsId: "67c809b5f63deab8a71cf928"
157
+ },
158
+ mqsai18593: {
159
+ destination: MARTINIQUE,
160
+ slug: "/hotels-et-villas/plein-soleil",
161
+ codeIPlanet: "MQAN16",
162
+ cmsId: "67c809b5f63deab8a71cf925"
163
+ },
164
+ zzzzz25378: {
165
+ destination: ILE_MAURICE,
166
+ slug: "/hotels-et-villas/shanti-maurice",
167
+ codeIPlanet: "MUAN22",
168
+ cmsId: "67c809b5f63deab8a71cf933"
169
+ },
170
+ zzzzz25377: {
171
+ destination: ILE_MAURICE,
172
+ slug: "/hotels-et-villas/lux-le-morne",
173
+ codeIPlanet: "MUAN21",
174
+ cmsId: "67c809b5f63deab8a71cf931"
175
+ },
176
+ zzzzz25379: {
177
+ destination: ILE_MAURICE,
178
+ slug: "/hotels-et-villas/constance-belle-mare-plage",
179
+ codeIPlanet: "MUAN20",
180
+ cmsId: "67c809b5f63deab8a71cf92f"
181
+ },
182
+ mupos25678: {
183
+ destination: ILE_MAURICE,
184
+ slug: "/hotels-et-villas/constance-le-prince-maurice",
185
+ codeIPlanet: "MUAN24",
186
+ cmsId: "67c809b5f63deab8a71cf930"
187
+ },
188
+ zzzzz25380: {
189
+ destination: ILE_MAURICE,
190
+ slug: "/hotels-et-villas/anahita-the-resort",
191
+ codeIPlanet: "MUAN19",
192
+ cmsId: "67c809b5f63deab8a71cf92e"
193
+ },
194
+ agstm21687: {
195
+ destination: SAINT_MARTIN,
196
+ slug: "/hotels-et-villas/la-samanna",
197
+ codeIPlanet: "FRAN2350",
198
+ cmsId: "67c809b5f63deab8a71cf926"
199
+ },
200
+ frsai31536: {
201
+ destination: SAINT_MARTIN,
202
+ slug: "/hotels-et-villas/le-grand-case-beach-club",
203
+ codeIPlanet: "FRFRA3",
204
+ cmsId: "67c809b5f63deab8a71cf927"
205
+ },
206
+ ageng20433: {
207
+ destination: ANTIGUA,
208
+ slug: "/hotels-et-villas/the-inn-at-english-harbour",
209
+ codeIPlanet: "AGAN4",
210
+ cmsId: "67c809b5f63deab8a71cf8ec"
211
+ },
212
+ agcro20496: {
213
+ destination: ANTIGUA,
214
+ slug: "/hotels-et-villas/blue-waters",
215
+ codeIPlanet: "AGAN1",
216
+ cmsId: "67c809b5f63deab8a71cf8eb"
217
+ },
218
+ gpang25884: {
219
+ destination: ANGUILLA,
220
+ slug: "/hotels-et-villas/aurora-anguilla-resort-golf-club",
221
+ codeIPlanet: "GPAN30",
222
+ cmsId: "67c809b5f63deab8a71cf8ea"
223
+ }
224
+ };
225
+ var CURRENCY_TO_SYMBOL = {
226
+ AED: "\u062F.\u0625.",
227
+ AFN: "Af",
228
+ ALL: "L",
229
+ AMD: "\u058F",
230
+ ANG: "\u0192",
231
+ AOA: "Kz",
232
+ ARS: "AR$",
233
+ AUD: "AU$",
234
+ AWG: "\u0192",
235
+ AZN: "\u043C\u0430\u043D",
236
+ BAM: "KM",
237
+ BBD: "BBD$",
238
+ BDT: "\u09F3",
239
+ BGN: "\u043B\u0432.",
240
+ BHD: "BD",
241
+ BIF: "FBu",
242
+ BMD: "$",
243
+ BND: "B$",
244
+ BOB: "Bs.",
245
+ BRL: "R$",
246
+ BSD: "$",
247
+ BTN: "Nu.",
248
+ BWP: "P",
249
+ BYN: "Br",
250
+ BZD: "BZ$",
251
+ CAD: "CA$",
252
+ CDF: "FC",
253
+ CHF: "Fr.",
254
+ CKD: "$",
255
+ CLP: "CL$",
256
+ CNY: "CN\xA5",
257
+ COP: "CO$",
258
+ CRC: "\u20A1",
259
+ CUC: "CUC$",
260
+ CUP: "$MN",
261
+ CVE: "CV$",
262
+ CZK: "K\u010D",
263
+ DJF: "Fdj",
264
+ DKK: "kr.",
265
+ DOP: "RD$",
266
+ DZD: "DA",
267
+ EGP: "E\xA3",
268
+ EHP: "Ptas.",
269
+ ERN: "Nkf",
270
+ ETB: "Br",
271
+ EUR: "\u20AC",
272
+ FJD: "FJ$",
273
+ FKP: "FK\xA3",
274
+ FOK: "kr",
275
+ GBP: "\xA3",
276
+ GEL: "\u20BE",
277
+ GGP: "\xA3",
278
+ GHS: "GH\u20B5",
279
+ GIP: "\xA3",
280
+ GMD: "D",
281
+ GNF: "FG",
282
+ GTQ: "Q",
283
+ GYD: "G$",
284
+ HKD: "HK$",
285
+ HNL: "L",
286
+ HRK: "kn",
287
+ HTG: "G",
288
+ HUF: "Ft",
289
+ IDR: "Rp",
290
+ ILS: "\u20AA",
291
+ IMP: "\xA3",
292
+ INR: "Rs.",
293
+ IQD: "\u062F.\u0639.",
294
+ IRR: "\uFDFC",
295
+ ISK: "kr",
296
+ JEP: "\xA3",
297
+ JMD: "J$",
298
+ JOD: "JD",
299
+ JPY: "\xA5",
300
+ KES: "KSh",
301
+ KGS: "\u0441",
302
+ KHR: "\u17DB",
303
+ KID: "$",
304
+ KMF: "CF",
305
+ KPW: "\u20A9",
306
+ KRW: "\u20A9",
307
+ KWD: "KD",
308
+ KYD: "CI$",
309
+ KZT: "\u20B8",
310
+ LAK: "\u20ADN",
311
+ LBP: "LL.",
312
+ LKR: "Rs.",
313
+ LRD: "L$",
314
+ LSL: "L",
315
+ LYD: "LD",
316
+ MAD: "DH",
317
+ MDL: "L",
318
+ MGA: "Ar",
319
+ MKD: "den",
320
+ MMK: "Ks",
321
+ MNT: "\u20AE",
322
+ MOP: "MOP$",
323
+ MRU: "UM",
324
+ MUR: "Rs.",
325
+ MVR: "MRf",
326
+ MWK: "MK",
327
+ MXN: "MX$",
328
+ MYR: "RM",
329
+ MZN: "MTn",
330
+ NAD: "N$",
331
+ NGN: "\u20A6",
332
+ NIO: "C$",
333
+ NOK: "kr",
334
+ NPR: "Rs.",
335
+ NZD: "NZ$",
336
+ OMR: "OR",
337
+ PAB: "B/.",
338
+ PEN: "S/.",
339
+ PGK: "K",
340
+ PHP: "\u20B1",
341
+ PKR: "Rs.",
342
+ PLN: "z\u0142",
343
+ PND: "$",
344
+ PRB: "\u0440.",
345
+ PYG: "\u20B2",
346
+ QAR: "QR",
347
+ RON: "L",
348
+ RSD: "din",
349
+ RUB: "\u20BD",
350
+ RWF: "FRw",
351
+ SAR: "SR",
352
+ SBD: "SI$",
353
+ SCR: "Rs.",
354
+ SDG: "\xA3SD",
355
+ SEK: "kr",
356
+ SGD: "S$",
357
+ SHP: "\xA3",
358
+ SLL: "Le",
359
+ SLS: "Sl",
360
+ SOS: "Sh.So.",
361
+ SRD: "Sr$",
362
+ SSP: "SS\xA3",
363
+ STN: "Db",
364
+ SVC: "\u20A1",
365
+ SYP: "LS",
366
+ SZL: "L",
367
+ THB: "\u0E3F",
368
+ TJS: "SM",
369
+ TMT: "m.",
370
+ TND: "DT",
371
+ TOP: "T$",
372
+ TRY: "TL",
373
+ TTD: "TT$",
374
+ TVD: "$",
375
+ TWD: "NT$",
376
+ TZS: "TSh",
377
+ UAH: "\u20B4",
378
+ UGX: "USh",
379
+ USD: "$",
380
+ UYU: "$U",
381
+ UZS: "\u0441\u0443\u043C",
382
+ VED: "Bs.",
383
+ VES: "Bs.F",
384
+ VND: "\u20AB",
385
+ VUV: "VT",
386
+ WST: "T",
387
+ XAF: "Fr",
388
+ XCD: "$",
389
+ XOF: "\u20A3",
390
+ XPF: "\u20A3",
391
+ YER: "YR",
392
+ ZAR: "R",
393
+ ZMW: "ZK",
394
+ ZWB: "",
395
+ ZWL: "Z$",
396
+ Abkhazia: "",
397
+ Artsakh: "\u0564\u0580."
398
+ };
399
+ var LANG_TO_LOCALE = {
400
+ it: "it_IT",
401
+ es: "es_ES",
402
+ fr: "fr_FR",
403
+ en: "en_GB"
404
+ };
405
+ var apiBaseUrl = "https://deshotelsetdesiles.ccordier.workers.dev/v2/";
406
+ var hotelAndFlightURLBaseUrl = "https://deshotelsetdesiles.i-planet.fr/dhdi-public/searchform.cgi";
407
+ var DEFAULT_COUNTRY_CODE = "FR";
408
+ var GTM_GL = "1*q1lzz9*_gcl_au*MTMyMzcyMDY5NC4xNzM5Mjg5MzYx*_ga*NTAxMzI1MzMwLjE3MzkyODkzNjE.*_ga_Q0RXHLR7D4*MTczOTI5NzEzNC4zLjAuMTczOTI5NzE3Mi4yMi4wLjA.";
409
+
410
+ // src/utils/countries/userCountry.ts
411
+ var getUserCountryCode = async () => {
412
+ return new Promise((resolve) => {
413
+ geoip2.country(
414
+ (successResponse) => {
415
+ resolve(successResponse.country.iso_code);
416
+ },
417
+ () => {
418
+ resolve(DEFAULT_COUNTRY_CODE);
419
+ }
420
+ );
421
+ });
422
+ };
423
+
424
+ // src/utils/buildQueryParams.ts
425
+ var buildQueryParams = (queryParams) => {
426
+ let url = "?";
427
+ for (const [key, value] of Object.entries(queryParams)) {
428
+ if (value) {
429
+ url += `${key}=${value}&`;
430
+ }
431
+ }
432
+ return url;
433
+ };
434
+
435
+ // src/utils/ApiClient.ts
436
+ var ApiClient = class {
437
+ /**
438
+ *
439
+ * @param queryParams to include as part of query for the request
440
+ * @returns offers data for a group of hotels
441
+ */
442
+ async getGroupOffersData(queryParams) {
443
+ const url = apiBaseUrl + "groupOffers" + buildQueryParams(queryParams);
444
+ const response = await fetch(url);
445
+ const body = await response.json();
446
+ if (body.error || !body.data)
447
+ return null;
448
+ return body;
449
+ }
450
+ /**
451
+ *
452
+ * @param queryParams to include as part of query for the request
453
+ * @returns offers data for a single hotel
454
+ */
455
+ async getSingleHotelOffersData(queryParams) {
456
+ const url = apiBaseUrl + "offers" + buildQueryParams(queryParams);
457
+ const response = await fetch(url);
458
+ const body = await response.json();
459
+ if (body.error || !body.data)
460
+ return null;
461
+ return body;
462
+ }
463
+ /**
464
+ *
465
+ * @param queryParams to include as part of query for the request
466
+ * @returns Starting CMS offers data
467
+ */
468
+ async getCMSOffers() {
469
+ const url = apiBaseUrl + "cmsOffers";
470
+ const response = await fetch(url);
471
+ const body = await response.json();
472
+ if (!body.items)
473
+ return null;
474
+ return body;
475
+ }
476
+ /**
477
+ * @returns all CMS countries
478
+ */
479
+ async getCountries() {
480
+ const url = apiBaseUrl + "cmsCountries";
481
+ const response = await fetch(url);
482
+ const body = await response.json();
483
+ if (!body.items)
484
+ return null;
485
+ return body;
486
+ }
487
+ };
488
+
489
+ // src/utils/utils.ts
490
+ var listenForLanguageChange = () => {
491
+ const isitProduction = isProduction();
492
+ if (!isitProduction) {
493
+ const currentStagingLang = localStorage.getItem("wglang") || "fr";
494
+ window.Weglot.switchTo(currentStagingLang);
495
+ }
496
+ window.Weglot.on("languageChanged", () => {
497
+ if (isitProduction) {
498
+ window.location.reload();
499
+ } else {
500
+ const newLang = window.Weglot.getCurrentLang();
501
+ localStorage.setItem("wglang", newLang);
502
+ if (window.loadFromServer) {
503
+ window.loadFromServer();
504
+ }
505
+ }
506
+ });
507
+ };
508
+ var isProduction = () => {
509
+ return window.location.hostname !== "deshotelsetdesiles.webflow.io";
510
+ };
511
+ var getLocale = () => {
512
+ const lang = getLanguage();
513
+ return LANG_TO_LOCALE[lang];
514
+ };
515
+ var getLanguage = () => {
516
+ if (!isProduction()) {
517
+ return localStorage.getItem("wglang") || "fr";
518
+ }
519
+ try {
520
+ const language = window.Weglot.getCurrentLang();
521
+ if (language) {
522
+ return language;
523
+ }
524
+ } catch (error) {
525
+ console.error("Error getting language", error);
526
+ return "fr";
527
+ }
528
+ return "fr";
529
+ };
530
+ var cmsIDToHotelIdMap = () => {
531
+ const map = {};
532
+ Object.entries(SNIPPET_CODE_TO_HOTEL).forEach(([key, value]) => {
533
+ map[value.cmsId] = key;
534
+ });
535
+ return map;
536
+ };
537
+ var offerSlug = (hotelId, offerName) => {
538
+ return `${hotelId.toLowerCase().trim()}-${offerName.toLowerCase().trim()}`;
539
+ };
540
+ var hotelAndFlightURL = (currency, hotelID, accessCodeValue) => {
541
+ const lang = getLanguage().toUpperCase();
542
+ const hotelCode = SNIPPET_CODE_TO_HOTEL[hotelID].codeIPlanet.toUpperCase();
543
+ let url = `${hotelAndFlightURLBaseUrl}?Lang=${lang}&currency=${currency.toUpperCase()}&HotelCode=${hotelCode}`;
544
+ const gaTag = getCookie("_ga");
545
+ const glTag = GTM_GL;
546
+ const gclAuTag = getCookie("_gcl_au");
547
+ url += `&_ga=${gaTag}&_gl=${glTag}&_gcl_au=${gclAuTag}`;
548
+ if (accessCodeValue) {
549
+ url = `${url}&HotelPromo=${accessCodeValue}`;
550
+ }
551
+ return url;
552
+ };
553
+ var getCookie = (name) => {
554
+ const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
555
+ return match ? match[2] : null;
556
+ };
557
+
558
+ // src/utils/bindQuotationData.ts
559
+ var bindQuotationData = (container, rate, hotelId, cmsOfferData) => {
560
+ const {
561
+ quotation,
562
+ rate: { distribution }
563
+ } = rate;
564
+ const { pricePerNight, currency, plainBookLink } = quotation;
565
+ const { accessCode } = distribution || {};
566
+ let accessCodeValue = "";
567
+ if (accessCode && accessCode.length > 0) {
568
+ [accessCodeValue] = accessCode;
569
+ }
570
+ updatePriceElement(container, pricePerNight, currency);
571
+ const hotelReserveEls = container.querySelector(SELECTORS.hotelReserveLink);
572
+ if (hotelReserveEls) {
573
+ hotelReserveEls.addEventListener("click", (e) => {
574
+ e.preventDefault();
575
+ e.stopPropagation();
576
+ window.open(plainBookLink, "_blank");
577
+ });
578
+ }
579
+ const hotelVillaReserveEl = container.querySelector(
580
+ SELECTORS.hotelVillaReserveLink
581
+ );
582
+ if (hotelVillaReserveEl) {
583
+ hotelVillaReserveEl.addEventListener("click", (e) => {
584
+ e.preventDefault();
585
+ e.stopPropagation();
586
+ window.open(hotelAndFlightURL(currency, hotelId, accessCodeValue), "_blank");
587
+ });
588
+ }
589
+ if (cmsOfferData) {
590
+ const {
591
+ fieldData: { "discount-percentage": discountPercentage }
592
+ } = cmsOfferData;
593
+ const discountEl = container.querySelector(SELECTORS.discount);
594
+ if (discountEl && discountPercentage)
595
+ discountEl.innerHTML = `-${discountPercentage}%`;
596
+ const discountContainerEl = container.querySelector(
597
+ SELECTORS.discountContainer
598
+ );
599
+ if (discountContainerEl && !discountPercentage) {
600
+ discountContainerEl.style.display = "none";
601
+ }
602
+ }
603
+ };
604
+ var updatePriceElement = (container, pricePerNight, currency) => {
605
+ const priceEl = container.querySelector(SELECTORS.price);
606
+ if (priceEl)
607
+ priceEl.innerHTML = Math.floor(pricePerNight).toString();
608
+ const currencyEl = container.querySelector(SELECTORS.currency);
609
+ if (currencyEl)
610
+ currencyEl.innerHTML = CURRENCY_TO_SYMBOL[currency];
611
+ };
612
+
613
+ // src/utils/Offers.ts
614
+ var Offers = class {
615
+ /**
616
+ *
617
+ * @param listInstance The list that the offers will be displayed in
618
+ * @param itemTemplateElement The template element to create new items from
619
+ */
620
+ constructor(listInstance, itemTemplateElement) {
621
+ this.listInstance = listInstance;
622
+ this.itemTemplateElement = itemTemplateElement;
623
+ this.cmsFilterEmptyState = document.querySelector(
624
+ SELECTORS.cmsFilterEmptyState
625
+ );
626
+ if (this.cmsFilterEmptyState) {
627
+ this.cmsFilterEmptyState.classList.add("hide-empty-state");
628
+ }
629
+ listInstance.clearItems();
630
+ listenForLanguageChange();
631
+ this.loadingSpinner = document.querySelector(SELECTORS.loadingSpinner);
632
+ this.countryCode = DEFAULT_COUNTRY_CODE;
633
+ }
634
+ /** The loading spinner element */
635
+ loadingSpinner;
636
+ /** The empty state element */
637
+ cmsFilterEmptyState;
638
+ /** A lookup object for the cms offers data */
639
+ offerLookup = {};
640
+ /** The api client */
641
+ apiClient = new ApiClient();
642
+ /** The user's country code */
643
+ countryCode;
644
+ /** All available countries in CMS */
645
+ availableCountries = {};
646
+ /**
647
+ * Retrieve hotel offer for given hotel IDs
648
+ * @param options to filter the offers
649
+ */
650
+ async getGroupOffers(options) {
651
+ const { clearItems = true, filter } = options || {};
652
+ if (clearItems)
653
+ this.listInstance.clearItems();
654
+ const cmsOffersData = await this.apiClient.getCMSOffers();
655
+ if (!cmsOffersData)
656
+ return;
657
+ const cmsCountries = await this.apiClient.getCountries();
658
+ if (!cmsCountries)
659
+ return;
660
+ cmsCountries.items.forEach((c) => {
661
+ this.availableCountries[c.id] = c.fieldData.title?.trim();
662
+ });
663
+ this.countryCode = await getUserCountryCode();
664
+ const filteredOffers = cmsOffersData.items.filter(filter).filter(this.countryCodeFilter);
665
+ const generatedHotelIds = this.generateHotelIds(filteredOffers);
666
+ let hids = this.generateHotelIds(filteredOffers).join(",");
667
+ const singleHotel = generatedHotelIds.length === 1;
668
+ if (singleHotel) {
669
+ hids = [hids, hids].join(",");
670
+ }
671
+ const { offerMetaData } = this.generateOfferData(filteredOffers);
672
+ const locale = getLocale();
673
+ const hotelOffersData = await this.apiClient.getGroupOffersData({
674
+ hids,
675
+ locale
676
+ });
677
+ if (!hotelOffersData || hotelOffersData.error || !hotelOffersData.data) {
678
+ this.hideSpinnerAndShowFilter();
679
+ return;
680
+ }
681
+ if (singleHotel) {
682
+ hotelOffersData.data = [hotelOffersData.data[0]];
683
+ }
684
+ const offers = this.getAllOffers(hotelOffersData, offerMetaData);
685
+ this.addHotelToCollection(offers);
686
+ this.hideSpinnerAndShowFilter();
687
+ }
688
+ /**
689
+ * Retrieve hotel offer for given offer category
690
+ * @param categoryId
691
+ * @param allOffers
692
+ */
693
+ async getGroupCategoryOffers(categoryId, allOffers = false) {
694
+ const filter = (value) => {
695
+ const { isArchived, isDraft, fieldData } = value;
696
+ if (isArchived || isDraft)
697
+ return false;
698
+ if (allOffers) {
699
+ return (!fieldData.thematiques || fieldData.thematiques.length === 0) && !fieldData.slug.includes("derniere-minute") && !fieldData.slug.includes("reservez-tot");
700
+ }
701
+ return fieldData.category === categoryId;
702
+ };
703
+ await this.getGroupOffers({ filter });
704
+ }
705
+ /**
706
+ * Retrieve hotel offer for given offer theme
707
+ * @param themeId
708
+ */
709
+ async getGroupThemeOffers(themeId) {
710
+ const filter = (value) => {
711
+ const { isArchived, isDraft, fieldData } = value;
712
+ if (isArchived || isDraft || !fieldData.thematiques)
713
+ return false;
714
+ return fieldData.thematiques.includes(themeId);
715
+ };
716
+ await this.getGroupOffers({ filter });
717
+ }
718
+ /**
719
+ * Retrieve hotel offer for the given offer slug (single offer page).
720
+ * @param slug The offer slug from the page URL (e.g. forfait-special-ete-jungle-bay).
721
+ */
722
+ async getGroupSlugOffers(slug) {
723
+ const filter = (value) => {
724
+ const { isArchived, isDraft, fieldData } = value;
725
+ if (isArchived || isDraft)
726
+ return false;
727
+ return fieldData.slug === slug;
728
+ };
729
+ await this.getGroupOffers({ filter });
730
+ }
731
+ /**
732
+ * Retrieve all other offers (same as "all offers" list) excluding the given slug.
733
+ * Used for "Nos autres offres" on the offer detail page.
734
+ * @param excludeSlug The current page's offer slug to exclude from the list.
735
+ */
736
+ async getGroupOtherOffers(excludeSlug) {
737
+ const filter = (value) => {
738
+ const { isArchived, isDraft, fieldData } = value;
739
+ if (isArchived || isDraft)
740
+ return false;
741
+ if (fieldData.slug === excludeSlug)
742
+ return false;
743
+ return (!fieldData.thematiques || fieldData.thematiques.length === 0) && !fieldData.slug.includes("derniere-minute") && !fieldData.slug.includes("reservez-tot");
744
+ };
745
+ await this.getGroupOffers({ filter });
746
+ }
747
+ /**
748
+ * Hide the loading spinner and show the empty state if no offers are found
749
+ * @private
750
+ */
751
+ hideSpinnerAndShowFilter() {
752
+ if (this.loadingSpinner)
753
+ this.loadingSpinner.style.display = "none";
754
+ if (this.cmsFilterEmptyState) {
755
+ this.cmsFilterEmptyState.classList.remove("hide-empty-state");
756
+ }
757
+ }
758
+ /**
759
+ * Filter the offers based on the country code
760
+ * @param item
761
+ */
762
+ countryCodeFilter = (item) => {
763
+ const { fieldData } = item;
764
+ if (!fieldData["supported-countries"] || fieldData["supported-countries"].length === 0)
765
+ return true;
766
+ const itemCountryCodes = fieldData["supported-countries"].map(
767
+ (c) => this.availableCountries[c]
768
+ );
769
+ return itemCountryCodes.includes(this.countryCode);
770
+ };
771
+ /**
772
+ *
773
+ * @param cmsOffers to generate hotel ids from
774
+ * @returns hotel ids based on the cms offers
775
+ */
776
+ generateHotelIds(cmsOffers) {
777
+ const hotelIds = [];
778
+ const cmsIDToHotelIds = cmsIDToHotelIdMap();
779
+ cmsOffers.forEach((offer) => {
780
+ const {
781
+ fieldData: { "hotel-villa": hotelCMSId }
782
+ } = offer;
783
+ hotelIds.push(cmsIDToHotelIds[hotelCMSId]);
784
+ });
785
+ return Array.from(new Set(hotelIds));
786
+ }
787
+ /**
788
+ * Retrieve all offers from the group offers data in a flat structure - sorted by CMS offer order
789
+ * @param hotelOffersData to get all rates from
790
+ * @param offerMetaData
791
+ * @returns the offer with each rate as a separate item
792
+ */
793
+ getAllOffers(hotelOffersData, offerMetaData) {
794
+ const rates = [];
795
+ hotelOffersData.data.forEach((offer) => {
796
+ const {
797
+ prop: { hid },
798
+ rates: offerRates
799
+ } = offer;
800
+ offerRates.forEach((rate) => {
801
+ const { name, title } = rate.rate;
802
+ const includeOffer = offerMetaData.includes(offerSlug(hid, name)) || offerMetaData.includes(offerSlug(hid, title));
803
+ if (includeOffer) {
804
+ rates.push({
805
+ ...offer,
806
+ rates: [rate]
807
+ });
808
+ }
809
+ });
810
+ });
811
+ rates.sort((a, b) => {
812
+ const cmsOfferA = this.offerLookup[offerSlug(a.prop.hid, a.rates[0].rate.title)] || this.offerLookup[offerSlug(a.prop.hid, a.rates[0].rate.name)];
813
+ const cmsOfferB = this.offerLookup[offerSlug(b.prop.hid, b.rates[0].rate.title)] || this.offerLookup[offerSlug(b.prop.hid, b.rates[0].rate.name)];
814
+ const aOrder = Number(cmsOfferA.fieldData.order) || 0;
815
+ const bOrder = Number(cmsOfferB.fieldData.order) || 0;
816
+ return aOrder - bOrder;
817
+ });
818
+ return rates;
819
+ }
820
+ /**
821
+ *
822
+ * @param filteredOffers to generate offer data from CMS offers
823
+ * generate the offer metadata and create a lookup object for the cms offers data (useful when looking up for offers discount percentage)
824
+ * @returns
825
+ */
826
+ generateOfferData(filteredOffers) {
827
+ const offerMetaData = [];
828
+ filteredOffers.forEach((offer) => {
829
+ const {
830
+ fieldData: { "hotel-or-property-api-id": hotelId, "offer-api-name": name }
831
+ } = offer;
832
+ const slug = offerSlug(hotelId, name);
833
+ offerMetaData.push(slug);
834
+ this.offerLookup[slug] = offer;
835
+ });
836
+ return { offerMetaData };
837
+ }
838
+ /**
839
+ *
840
+ * @param offers
841
+ */
842
+ addHotelToCollection(offers) {
843
+ for (const { prop, rates } of offers) {
844
+ const items = rates.map((item) => this.createItem(prop, item, this.itemTemplateElement));
845
+ this.listInstance.addItems(items);
846
+ }
847
+ }
848
+ /**
849
+ * Creates an item from the template element.
850
+ * @param offerProperty
851
+ * @param rate The Rate data to create the item from.
852
+ * @param templateElement The template element.
853
+ *
854
+ * @returns A new Collection Item element.
855
+ */
856
+ createItem(offerProperty, rate, templateElement) {
857
+ const { rate: rateData } = rate;
858
+ const { hid, title: hotelName, property } = offerProperty;
859
+ const { image, title, name } = rateData;
860
+ const offerCMSData = this.offerLookup[offerSlug(hid || property, title)] || this.offerLookup[offerSlug(hid || property, name)];
861
+ const newItem = templateElement.cloneNode(true);
862
+ const hotelMeta = SNIPPET_CODE_TO_HOTEL[hid || property];
863
+ if (!hotelMeta)
864
+ return newItem;
865
+ const { destination, slug } = hotelMeta;
866
+ this.bindOffersMetaData(newItem, { destination, image, hotelName, title, slug });
867
+ bindQuotationData(newItem, rate, hid, offerCMSData);
868
+ const moreDetails = newItem.querySelector(SELECTORS.moreDetails);
869
+ if (moreDetails) {
870
+ moreDetails.addEventListener("click", () => {
871
+ const offersSlug = this.getOffersCmsSlug(hid, property, name);
872
+ window.location.href = `/offers/${offersSlug}`;
873
+ });
874
+ }
875
+ newItem.style.display = "flex";
876
+ return newItem;
877
+ }
878
+ getOffersCmsSlug(hid, property, title) {
879
+ const offerCMSData = this.offerLookup[offerSlug(hid || property, title)];
880
+ return offerCMSData?.fieldData.slug;
881
+ }
882
+ /**
883
+ *
884
+ * @param container for data item
885
+ * @param destination for offer
886
+ * @param image for offer
887
+ * @param hotelName for hotel
888
+ * @param title for offer
889
+ * @param slug
890
+ * @param description
891
+ */
892
+ bindOffersMetaData(container, {
893
+ destination,
894
+ image,
895
+ hotelName,
896
+ title,
897
+ slug,
898
+ description
899
+ }) {
900
+ const destinationEl = container.querySelector(SELECTORS.destination);
901
+ const imageEl = container.querySelector(SELECTORS.image);
902
+ const hotelNameEl = container.querySelector(SELECTORS.hotelName);
903
+ const nameEl = container.querySelector(SELECTORS.name);
904
+ const descriptionEl = container.querySelector(SELECTORS.description);
905
+ const hotelLinkEl = container.querySelector(SELECTORS.hotelLink);
906
+ const language = getLanguage();
907
+ if (destinationEl)
908
+ destinationEl.textContent = destination[language];
909
+ if (imageEl)
910
+ imageEl.src = image?.url;
911
+ if (hotelNameEl)
912
+ hotelNameEl.innerHTML = hotelName;
913
+ if (nameEl)
914
+ nameEl.innerHTML = title;
915
+ if (descriptionEl && description)
916
+ descriptionEl.innerHTML = description;
917
+ if (hotelLinkEl && slug) {
918
+ hotelLinkEl.addEventListener("click", () => {
919
+ window.location.href = slug;
920
+ });
921
+ }
922
+ }
923
+ };
924
+
925
+ // src/utils/createOffersInstance.ts
926
+ var createOffersInstance = (filtersInstances) => {
927
+ const [filtersInstance] = filtersInstances;
928
+ const { listInstance } = filtersInstance;
929
+ if (listInstance.items.length === 0) {
930
+ throw new Error("No items found in the list instance - Cannot create an instance of Offers");
931
+ }
932
+ return createOfferForList(listInstance);
933
+ };
934
+ var createOfferForList = (listInstance) => {
935
+ const [firstItem] = listInstance.items;
936
+ if (!firstItem)
937
+ throw new Error("No items found in the list instance");
938
+ const itemTemplateElement = firstItem.element;
939
+ return new Offers(listInstance, itemTemplateElement);
940
+ };
941
+
942
+ // src/pages/Offers.ts
943
+ window.fsAttributes = window.fsAttributes || [];
944
+ window.loadFromServer = () => {
945
+ window.fsAttributes.push([
946
+ "cmsfilter",
947
+ async (filtersInstances) => {
948
+ const offers = createOffersInstance(filtersInstances);
949
+ await offers.getGroupCategoryOffers(void 0, true);
950
+ }
951
+ ]);
952
+ };
953
+ window.loadFromServer();
954
+ })();
955
+ //# sourceMappingURL=Offers.js.map