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