@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,959 @@
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 OFFER_CATEGORIES = {
406
+ "DERNI\xC8RE MINUTE": "735fe5e0858671cf3a5c14cbcc2470fe",
407
+ "R\xC9SERVEZ T\xD4T": "c5abb23caf056891f3902ff42ae2f9e0"
408
+ };
409
+ var apiBaseUrl = "https://deshotelsetdesiles.ccordier.workers.dev/v2/";
410
+ var hotelAndFlightURLBaseUrl = "https://deshotelsetdesiles.i-planet.fr/dhdi-public/searchform.cgi";
411
+ var DEFAULT_COUNTRY_CODE = "FR";
412
+ var GTM_GL = "1*q1lzz9*_gcl_au*MTMyMzcyMDY5NC4xNzM5Mjg5MzYx*_ga*NTAxMzI1MzMwLjE3MzkyODkzNjE.*_ga_Q0RXHLR7D4*MTczOTI5NzEzNC4zLjAuMTczOTI5NzE3Mi4yMi4wLjA.";
413
+
414
+ // src/utils/countries/userCountry.ts
415
+ var getUserCountryCode = async () => {
416
+ return new Promise((resolve) => {
417
+ geoip2.country(
418
+ (successResponse) => {
419
+ resolve(successResponse.country.iso_code);
420
+ },
421
+ () => {
422
+ resolve(DEFAULT_COUNTRY_CODE);
423
+ }
424
+ );
425
+ });
426
+ };
427
+
428
+ // src/utils/buildQueryParams.ts
429
+ var buildQueryParams = (queryParams) => {
430
+ let url = "?";
431
+ for (const [key, value] of Object.entries(queryParams)) {
432
+ if (value) {
433
+ url += `${key}=${value}&`;
434
+ }
435
+ }
436
+ return url;
437
+ };
438
+
439
+ // src/utils/ApiClient.ts
440
+ var ApiClient = class {
441
+ /**
442
+ *
443
+ * @param queryParams to include as part of query for the request
444
+ * @returns offers data for a group of hotels
445
+ */
446
+ async getGroupOffersData(queryParams) {
447
+ const url = apiBaseUrl + "groupOffers" + buildQueryParams(queryParams);
448
+ const response = await fetch(url);
449
+ const body = await response.json();
450
+ if (body.error || !body.data)
451
+ return null;
452
+ return body;
453
+ }
454
+ /**
455
+ *
456
+ * @param queryParams to include as part of query for the request
457
+ * @returns offers data for a single hotel
458
+ */
459
+ async getSingleHotelOffersData(queryParams) {
460
+ const url = apiBaseUrl + "offers" + buildQueryParams(queryParams);
461
+ const response = await fetch(url);
462
+ const body = await response.json();
463
+ if (body.error || !body.data)
464
+ return null;
465
+ return body;
466
+ }
467
+ /**
468
+ *
469
+ * @param queryParams to include as part of query for the request
470
+ * @returns Starting CMS offers data
471
+ */
472
+ async getCMSOffers() {
473
+ const url = apiBaseUrl + "cmsOffers";
474
+ const response = await fetch(url);
475
+ const body = await response.json();
476
+ if (!body.items)
477
+ return null;
478
+ return body;
479
+ }
480
+ /**
481
+ * @returns all CMS countries
482
+ */
483
+ async getCountries() {
484
+ const url = apiBaseUrl + "cmsCountries";
485
+ const response = await fetch(url);
486
+ const body = await response.json();
487
+ if (!body.items)
488
+ return null;
489
+ return body;
490
+ }
491
+ };
492
+
493
+ // src/utils/utils.ts
494
+ var listenForLanguageChange = () => {
495
+ const isitProduction = isProduction();
496
+ if (!isitProduction) {
497
+ const currentStagingLang = localStorage.getItem("wglang") || "fr";
498
+ window.Weglot.switchTo(currentStagingLang);
499
+ }
500
+ window.Weglot.on("languageChanged", () => {
501
+ if (isitProduction) {
502
+ window.location.reload();
503
+ } else {
504
+ const newLang = window.Weglot.getCurrentLang();
505
+ localStorage.setItem("wglang", newLang);
506
+ if (window.loadFromServer) {
507
+ window.loadFromServer();
508
+ }
509
+ }
510
+ });
511
+ };
512
+ var isProduction = () => {
513
+ return window.location.hostname !== "deshotelsetdesiles.webflow.io";
514
+ };
515
+ var getLocale = () => {
516
+ const lang = getLanguage();
517
+ return LANG_TO_LOCALE[lang];
518
+ };
519
+ var getLanguage = () => {
520
+ if (!isProduction()) {
521
+ return localStorage.getItem("wglang") || "fr";
522
+ }
523
+ try {
524
+ const language = window.Weglot.getCurrentLang();
525
+ if (language) {
526
+ return language;
527
+ }
528
+ } catch (error) {
529
+ console.error("Error getting language", error);
530
+ return "fr";
531
+ }
532
+ return "fr";
533
+ };
534
+ var cmsIDToHotelIdMap = () => {
535
+ const map = {};
536
+ Object.entries(SNIPPET_CODE_TO_HOTEL).forEach(([key, value]) => {
537
+ map[value.cmsId] = key;
538
+ });
539
+ return map;
540
+ };
541
+ var offerSlug = (hotelId, offerName) => {
542
+ return `${hotelId.toLowerCase().trim()}-${offerName.toLowerCase().trim()}`;
543
+ };
544
+ var hotelAndFlightURL = (currency, hotelID, accessCodeValue) => {
545
+ const lang = getLanguage().toUpperCase();
546
+ const hotelCode = SNIPPET_CODE_TO_HOTEL[hotelID].codeIPlanet.toUpperCase();
547
+ let url = `${hotelAndFlightURLBaseUrl}?Lang=${lang}&currency=${currency.toUpperCase()}&HotelCode=${hotelCode}`;
548
+ const gaTag = getCookie("_ga");
549
+ const glTag = GTM_GL;
550
+ const gclAuTag = getCookie("_gcl_au");
551
+ url += `&_ga=${gaTag}&_gl=${glTag}&_gcl_au=${gclAuTag}`;
552
+ if (accessCodeValue) {
553
+ url = `${url}&HotelPromo=${accessCodeValue}`;
554
+ }
555
+ return url;
556
+ };
557
+ var getCookie = (name) => {
558
+ const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
559
+ return match ? match[2] : null;
560
+ };
561
+
562
+ // src/utils/bindQuotationData.ts
563
+ var bindQuotationData = (container, rate, hotelId, cmsOfferData) => {
564
+ const {
565
+ quotation,
566
+ rate: { distribution }
567
+ } = rate;
568
+ const { pricePerNight, currency, plainBookLink } = quotation;
569
+ const { accessCode } = distribution || {};
570
+ let accessCodeValue = "";
571
+ if (accessCode && accessCode.length > 0) {
572
+ [accessCodeValue] = accessCode;
573
+ }
574
+ updatePriceElement(container, pricePerNight, currency);
575
+ const hotelReserveEls = container.querySelector(SELECTORS.hotelReserveLink);
576
+ if (hotelReserveEls) {
577
+ hotelReserveEls.addEventListener("click", (e) => {
578
+ e.preventDefault();
579
+ e.stopPropagation();
580
+ window.open(plainBookLink, "_blank");
581
+ });
582
+ }
583
+ const hotelVillaReserveEl = container.querySelector(
584
+ SELECTORS.hotelVillaReserveLink
585
+ );
586
+ if (hotelVillaReserveEl) {
587
+ hotelVillaReserveEl.addEventListener("click", (e) => {
588
+ e.preventDefault();
589
+ e.stopPropagation();
590
+ window.open(hotelAndFlightURL(currency, hotelId, accessCodeValue), "_blank");
591
+ });
592
+ }
593
+ if (cmsOfferData) {
594
+ const {
595
+ fieldData: { "discount-percentage": discountPercentage }
596
+ } = cmsOfferData;
597
+ const discountEl = container.querySelector(SELECTORS.discount);
598
+ if (discountEl && discountPercentage)
599
+ discountEl.innerHTML = `-${discountPercentage}%`;
600
+ const discountContainerEl = container.querySelector(
601
+ SELECTORS.discountContainer
602
+ );
603
+ if (discountContainerEl && !discountPercentage) {
604
+ discountContainerEl.style.display = "none";
605
+ }
606
+ }
607
+ };
608
+ var updatePriceElement = (container, pricePerNight, currency) => {
609
+ const priceEl = container.querySelector(SELECTORS.price);
610
+ if (priceEl)
611
+ priceEl.innerHTML = Math.floor(pricePerNight).toString();
612
+ const currencyEl = container.querySelector(SELECTORS.currency);
613
+ if (currencyEl)
614
+ currencyEl.innerHTML = CURRENCY_TO_SYMBOL[currency];
615
+ };
616
+
617
+ // src/utils/Offers.ts
618
+ var Offers = class {
619
+ /**
620
+ *
621
+ * @param listInstance The list that the offers will be displayed in
622
+ * @param itemTemplateElement The template element to create new items from
623
+ */
624
+ constructor(listInstance, itemTemplateElement) {
625
+ this.listInstance = listInstance;
626
+ this.itemTemplateElement = itemTemplateElement;
627
+ this.cmsFilterEmptyState = document.querySelector(
628
+ SELECTORS.cmsFilterEmptyState
629
+ );
630
+ if (this.cmsFilterEmptyState) {
631
+ this.cmsFilterEmptyState.classList.add("hide-empty-state");
632
+ }
633
+ listInstance.clearItems();
634
+ listenForLanguageChange();
635
+ this.loadingSpinner = document.querySelector(SELECTORS.loadingSpinner);
636
+ this.countryCode = DEFAULT_COUNTRY_CODE;
637
+ }
638
+ /** The loading spinner element */
639
+ loadingSpinner;
640
+ /** The empty state element */
641
+ cmsFilterEmptyState;
642
+ /** A lookup object for the cms offers data */
643
+ offerLookup = {};
644
+ /** The api client */
645
+ apiClient = new ApiClient();
646
+ /** The user's country code */
647
+ countryCode;
648
+ /** All available countries in CMS */
649
+ availableCountries = {};
650
+ /**
651
+ * Retrieve hotel offer for given hotel IDs
652
+ * @param options to filter the offers
653
+ */
654
+ async getGroupOffers(options) {
655
+ const { clearItems = true, filter } = options || {};
656
+ if (clearItems)
657
+ this.listInstance.clearItems();
658
+ const cmsOffersData = await this.apiClient.getCMSOffers();
659
+ if (!cmsOffersData)
660
+ return;
661
+ const cmsCountries = await this.apiClient.getCountries();
662
+ if (!cmsCountries)
663
+ return;
664
+ cmsCountries.items.forEach((c) => {
665
+ this.availableCountries[c.id] = c.fieldData.title?.trim();
666
+ });
667
+ this.countryCode = await getUserCountryCode();
668
+ const filteredOffers = cmsOffersData.items.filter(filter).filter(this.countryCodeFilter);
669
+ const generatedHotelIds = this.generateHotelIds(filteredOffers);
670
+ let hids = this.generateHotelIds(filteredOffers).join(",");
671
+ const singleHotel = generatedHotelIds.length === 1;
672
+ if (singleHotel) {
673
+ hids = [hids, hids].join(",");
674
+ }
675
+ const { offerMetaData } = this.generateOfferData(filteredOffers);
676
+ const locale = getLocale();
677
+ const hotelOffersData = await this.apiClient.getGroupOffersData({
678
+ hids,
679
+ locale
680
+ });
681
+ if (!hotelOffersData || hotelOffersData.error || !hotelOffersData.data) {
682
+ this.hideSpinnerAndShowFilter();
683
+ return;
684
+ }
685
+ if (singleHotel) {
686
+ hotelOffersData.data = [hotelOffersData.data[0]];
687
+ }
688
+ const offers = this.getAllOffers(hotelOffersData, offerMetaData);
689
+ this.addHotelToCollection(offers);
690
+ this.hideSpinnerAndShowFilter();
691
+ }
692
+ /**
693
+ * Retrieve hotel offer for given offer category
694
+ * @param categoryId
695
+ * @param allOffers
696
+ */
697
+ async getGroupCategoryOffers(categoryId, allOffers = false) {
698
+ const filter = (value) => {
699
+ const { isArchived, isDraft, fieldData } = value;
700
+ if (isArchived || isDraft)
701
+ return false;
702
+ if (allOffers) {
703
+ return (!fieldData.thematiques || fieldData.thematiques.length === 0) && !fieldData.slug.includes("derniere-minute") && !fieldData.slug.includes("reservez-tot");
704
+ }
705
+ return fieldData.category === categoryId;
706
+ };
707
+ await this.getGroupOffers({ filter });
708
+ }
709
+ /**
710
+ * Retrieve hotel offer for given offer theme
711
+ * @param themeId
712
+ */
713
+ async getGroupThemeOffers(themeId) {
714
+ const filter = (value) => {
715
+ const { isArchived, isDraft, fieldData } = value;
716
+ if (isArchived || isDraft || !fieldData.thematiques)
717
+ return false;
718
+ return fieldData.thematiques.includes(themeId);
719
+ };
720
+ await this.getGroupOffers({ filter });
721
+ }
722
+ /**
723
+ * Retrieve hotel offer for the given offer slug (single offer page).
724
+ * @param slug The offer slug from the page URL (e.g. forfait-special-ete-jungle-bay).
725
+ */
726
+ async getGroupSlugOffers(slug) {
727
+ const filter = (value) => {
728
+ const { isArchived, isDraft, fieldData } = value;
729
+ if (isArchived || isDraft)
730
+ return false;
731
+ return fieldData.slug === slug;
732
+ };
733
+ await this.getGroupOffers({ filter });
734
+ }
735
+ /**
736
+ * Retrieve all other offers (same as "all offers" list) excluding the given slug.
737
+ * Used for "Nos autres offres" on the offer detail page.
738
+ * @param excludeSlug The current page's offer slug to exclude from the list.
739
+ */
740
+ async getGroupOtherOffers(excludeSlug) {
741
+ const filter = (value) => {
742
+ const { isArchived, isDraft, fieldData } = value;
743
+ if (isArchived || isDraft)
744
+ return false;
745
+ if (fieldData.slug === excludeSlug)
746
+ return false;
747
+ return (!fieldData.thematiques || fieldData.thematiques.length === 0) && !fieldData.slug.includes("derniere-minute") && !fieldData.slug.includes("reservez-tot");
748
+ };
749
+ await this.getGroupOffers({ filter });
750
+ }
751
+ /**
752
+ * Hide the loading spinner and show the empty state if no offers are found
753
+ * @private
754
+ */
755
+ hideSpinnerAndShowFilter() {
756
+ if (this.loadingSpinner)
757
+ this.loadingSpinner.style.display = "none";
758
+ if (this.cmsFilterEmptyState) {
759
+ this.cmsFilterEmptyState.classList.remove("hide-empty-state");
760
+ }
761
+ }
762
+ /**
763
+ * Filter the offers based on the country code
764
+ * @param item
765
+ */
766
+ countryCodeFilter = (item) => {
767
+ const { fieldData } = item;
768
+ if (!fieldData["supported-countries"] || fieldData["supported-countries"].length === 0)
769
+ return true;
770
+ const itemCountryCodes = fieldData["supported-countries"].map(
771
+ (c) => this.availableCountries[c]
772
+ );
773
+ return itemCountryCodes.includes(this.countryCode);
774
+ };
775
+ /**
776
+ *
777
+ * @param cmsOffers to generate hotel ids from
778
+ * @returns hotel ids based on the cms offers
779
+ */
780
+ generateHotelIds(cmsOffers) {
781
+ const hotelIds = [];
782
+ const cmsIDToHotelIds = cmsIDToHotelIdMap();
783
+ cmsOffers.forEach((offer) => {
784
+ const {
785
+ fieldData: { "hotel-villa": hotelCMSId }
786
+ } = offer;
787
+ hotelIds.push(cmsIDToHotelIds[hotelCMSId]);
788
+ });
789
+ return Array.from(new Set(hotelIds));
790
+ }
791
+ /**
792
+ * Retrieve all offers from the group offers data in a flat structure - sorted by CMS offer order
793
+ * @param hotelOffersData to get all rates from
794
+ * @param offerMetaData
795
+ * @returns the offer with each rate as a separate item
796
+ */
797
+ getAllOffers(hotelOffersData, offerMetaData) {
798
+ const rates = [];
799
+ hotelOffersData.data.forEach((offer) => {
800
+ const {
801
+ prop: { hid },
802
+ rates: offerRates
803
+ } = offer;
804
+ offerRates.forEach((rate) => {
805
+ const { name, title } = rate.rate;
806
+ const includeOffer = offerMetaData.includes(offerSlug(hid, name)) || offerMetaData.includes(offerSlug(hid, title));
807
+ if (includeOffer) {
808
+ rates.push({
809
+ ...offer,
810
+ rates: [rate]
811
+ });
812
+ }
813
+ });
814
+ });
815
+ rates.sort((a, b) => {
816
+ const cmsOfferA = this.offerLookup[offerSlug(a.prop.hid, a.rates[0].rate.title)] || this.offerLookup[offerSlug(a.prop.hid, a.rates[0].rate.name)];
817
+ const cmsOfferB = this.offerLookup[offerSlug(b.prop.hid, b.rates[0].rate.title)] || this.offerLookup[offerSlug(b.prop.hid, b.rates[0].rate.name)];
818
+ const aOrder = Number(cmsOfferA.fieldData.order) || 0;
819
+ const bOrder = Number(cmsOfferB.fieldData.order) || 0;
820
+ return aOrder - bOrder;
821
+ });
822
+ return rates;
823
+ }
824
+ /**
825
+ *
826
+ * @param filteredOffers to generate offer data from CMS offers
827
+ * generate the offer metadata and create a lookup object for the cms offers data (useful when looking up for offers discount percentage)
828
+ * @returns
829
+ */
830
+ generateOfferData(filteredOffers) {
831
+ const offerMetaData = [];
832
+ filteredOffers.forEach((offer) => {
833
+ const {
834
+ fieldData: { "hotel-or-property-api-id": hotelId, "offer-api-name": name }
835
+ } = offer;
836
+ const slug = offerSlug(hotelId, name);
837
+ offerMetaData.push(slug);
838
+ this.offerLookup[slug] = offer;
839
+ });
840
+ return { offerMetaData };
841
+ }
842
+ /**
843
+ *
844
+ * @param offers
845
+ */
846
+ addHotelToCollection(offers) {
847
+ for (const { prop, rates } of offers) {
848
+ const items = rates.map((item) => this.createItem(prop, item, this.itemTemplateElement));
849
+ this.listInstance.addItems(items);
850
+ }
851
+ }
852
+ /**
853
+ * Creates an item from the template element.
854
+ * @param offerProperty
855
+ * @param rate The Rate data to create the item from.
856
+ * @param templateElement The template element.
857
+ *
858
+ * @returns A new Collection Item element.
859
+ */
860
+ createItem(offerProperty, rate, templateElement) {
861
+ const { rate: rateData } = rate;
862
+ const { hid, title: hotelName, property } = offerProperty;
863
+ const { image, title, name } = rateData;
864
+ const offerCMSData = this.offerLookup[offerSlug(hid || property, title)] || this.offerLookup[offerSlug(hid || property, name)];
865
+ const newItem = templateElement.cloneNode(true);
866
+ const hotelMeta = SNIPPET_CODE_TO_HOTEL[hid || property];
867
+ if (!hotelMeta)
868
+ return newItem;
869
+ const { destination, slug } = hotelMeta;
870
+ this.bindOffersMetaData(newItem, { destination, image, hotelName, title, slug });
871
+ bindQuotationData(newItem, rate, hid, offerCMSData);
872
+ const moreDetails = newItem.querySelector(SELECTORS.moreDetails);
873
+ if (moreDetails) {
874
+ moreDetails.addEventListener("click", () => {
875
+ const offersSlug = this.getOffersCmsSlug(hid, property, name);
876
+ window.location.href = `/offers/${offersSlug}`;
877
+ });
878
+ }
879
+ newItem.style.display = "flex";
880
+ return newItem;
881
+ }
882
+ getOffersCmsSlug(hid, property, title) {
883
+ const offerCMSData = this.offerLookup[offerSlug(hid || property, title)];
884
+ return offerCMSData?.fieldData.slug;
885
+ }
886
+ /**
887
+ *
888
+ * @param container for data item
889
+ * @param destination for offer
890
+ * @param image for offer
891
+ * @param hotelName for hotel
892
+ * @param title for offer
893
+ * @param slug
894
+ * @param description
895
+ */
896
+ bindOffersMetaData(container, {
897
+ destination,
898
+ image,
899
+ hotelName,
900
+ title,
901
+ slug,
902
+ description
903
+ }) {
904
+ const destinationEl = container.querySelector(SELECTORS.destination);
905
+ const imageEl = container.querySelector(SELECTORS.image);
906
+ const hotelNameEl = container.querySelector(SELECTORS.hotelName);
907
+ const nameEl = container.querySelector(SELECTORS.name);
908
+ const descriptionEl = container.querySelector(SELECTORS.description);
909
+ const hotelLinkEl = container.querySelector(SELECTORS.hotelLink);
910
+ const language = getLanguage();
911
+ if (destinationEl)
912
+ destinationEl.textContent = destination[language];
913
+ if (imageEl)
914
+ imageEl.src = image?.url;
915
+ if (hotelNameEl)
916
+ hotelNameEl.innerHTML = hotelName;
917
+ if (nameEl)
918
+ nameEl.innerHTML = title;
919
+ if (descriptionEl && description)
920
+ descriptionEl.innerHTML = description;
921
+ if (hotelLinkEl && slug) {
922
+ hotelLinkEl.addEventListener("click", () => {
923
+ window.location.href = slug;
924
+ });
925
+ }
926
+ }
927
+ };
928
+
929
+ // src/utils/createOffersInstance.ts
930
+ var createOffersInstance = (filtersInstances) => {
931
+ const [filtersInstance] = filtersInstances;
932
+ const { listInstance } = filtersInstance;
933
+ if (listInstance.items.length === 0) {
934
+ throw new Error("No items found in the list instance - Cannot create an instance of Offers");
935
+ }
936
+ return createOfferForList(listInstance);
937
+ };
938
+ var createOfferForList = (listInstance) => {
939
+ const [firstItem] = listInstance.items;
940
+ if (!firstItem)
941
+ throw new Error("No items found in the list instance");
942
+ const itemTemplateElement = firstItem.element;
943
+ return new Offers(listInstance, itemTemplateElement);
944
+ };
945
+
946
+ // src/pages/OffersDeMinute.ts
947
+ window.fsAttributes = window.fsAttributes || [];
948
+ window.loadFromServer = () => {
949
+ window.fsAttributes.push([
950
+ "cmsfilter",
951
+ async (filtersInstances) => {
952
+ const offers = createOffersInstance(filtersInstances);
953
+ await offers.getGroupCategoryOffers(OFFER_CATEGORIES["DERNI\xC8RE MINUTE"]);
954
+ }
955
+ ]);
956
+ };
957
+ window.loadFromServer();
958
+ })();
959
+ //# sourceMappingURL=OffersDeMinute.js.map