vue-hotel-search-widget 1.0.0
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.
- package/README.md +109 -0
- package/package.json +52 -0
- package/src/components/HotelSearchWidget.vue +1410 -0
- package/src/components/hotel-search-widget.styles.js +740 -0
- package/src/index.js +4 -0
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="thsw-root sw-container" :style="themeStyle">
|
|
3
|
+
<div class="sw-card-box">
|
|
4
|
+
<form class="sw-srh-h-grid" @submit.prevent="handleSubmit">
|
|
5
|
+
<!-- Destination Field -->
|
|
6
|
+
<div
|
|
7
|
+
:class="[
|
|
8
|
+
'sw-form-field',
|
|
9
|
+
'sw-pos-rel',
|
|
10
|
+
'br-left',
|
|
11
|
+
{ 'thsw-field-error': fieldErrors.destination }
|
|
12
|
+
]"
|
|
13
|
+
>
|
|
14
|
+
<label for="destination">Destination</label>
|
|
15
|
+
<div class="sw-custom-dropdown" ref="destinationDropdownRef">
|
|
16
|
+
<div class="sw-custom-dropdown-trigger">
|
|
17
|
+
<input
|
|
18
|
+
class="hotel-destination-input"
|
|
19
|
+
:value="destinationQuery"
|
|
20
|
+
@input="handleDestinationQueryChange"
|
|
21
|
+
@focus="setIsDestinationDropdownOpen(true)"
|
|
22
|
+
placeholder="Where are you going?"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
<div v-if="isDestinationDropdownOpen" class="sw-dropdown">
|
|
26
|
+
<ul class="sw-dropdown-list">
|
|
27
|
+
<li class="sw-srh-loc" @click="handleAroundCurrentLocation">
|
|
28
|
+
<div class="sw-flex sw-align-center sw-gap-5 sw-justify-between">
|
|
29
|
+
<div class="sw-flex sw-align-center sw-gap-15">
|
|
30
|
+
<span class="sw-hotel-search-form-icon sw-fill">
|
|
31
|
+
<svg
|
|
32
|
+
width="26px"
|
|
33
|
+
height="26px"
|
|
34
|
+
fill="currentColor"
|
|
35
|
+
viewBox="0 0 24 24"
|
|
36
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
37
|
+
data-testid="MyLocationIcon"
|
|
38
|
+
class="sw-bg-color-loc"
|
|
39
|
+
>
|
|
40
|
+
<path
|
|
41
|
+
fillRule="evenodd"
|
|
42
|
+
d="M12.0283 3a.7614.7614 0 0 1 .7614.7614v1.1544c3.328.3533 5.9726 2.9958 6.329 6.3228h1.1482a.7614.7614 0 0 1 0 1.5228h-1.1467c-.3505 3.3337-2.9978 5.9831-6.3305 6.3368v1.1404a.7614.7614 0 0 1-1.5228 0v-1.1404c-3.3327-.3537-5.98-3.0031-6.3306-6.3368H3.7897a.7614.7614 0 0 1 0-1.5228h1.1481c.3565-3.327 3.001-5.9696 6.3291-6.3228V3.7614A.7614.7614 0 0 1 12.0283 3m5.6086 9.007c0 3.0976-2.5111 5.6086-5.6086 5.6086s-5.6086-2.511-5.6086-5.6086 2.511-5.6085 5.6086-5.6085 5.6086 2.511 5.6086 5.6085m-3.5479-.0001c0 1.1381-.9226 2.0607-2.0607 2.0607-1.138 0-2.0606-.9226-2.0606-2.0607 0-1.138.9226-2.0606 2.0606-2.0606s2.0607.9226 2.0607 2.0606m1.5228 0c0 1.9791-1.6044 3.5835-3.5835 3.5835s-3.5834-1.6044-3.5834-3.5835 1.6044-3.5834 3.5834-3.5834 3.5835 1.6044 3.5835 3.5834"
|
|
43
|
+
clipRule="evenodd"
|
|
44
|
+
></path>
|
|
45
|
+
</svg>
|
|
46
|
+
</span>
|
|
47
|
+
<h5 class="sw-fw500">Around Current Location</h5>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</li>
|
|
51
|
+
<li v-if="isDestinationLoading" class="sw-dropdown-list-item">
|
|
52
|
+
<div class="sw-dropdown-list-card">
|
|
53
|
+
<h6 class="sw-text-label">Searching...</h6>
|
|
54
|
+
</div>
|
|
55
|
+
</li>
|
|
56
|
+
<template
|
|
57
|
+
v-if="
|
|
58
|
+
!isDestinationLoading &&
|
|
59
|
+
destinationQuery.trim().length < MIN_SEARCH_CHARS
|
|
60
|
+
"
|
|
61
|
+
>
|
|
62
|
+
<li
|
|
63
|
+
v-for="option in STATIC_LOCATIONS"
|
|
64
|
+
:key="option.geoId"
|
|
65
|
+
class="sw-dropdown-list-item"
|
|
66
|
+
@click="handleDestinationSelect(option)"
|
|
67
|
+
>
|
|
68
|
+
<div class="sw-dropdown-list-card">
|
|
69
|
+
<span class="sw-hotel-search-form-icon">
|
|
70
|
+
<svg
|
|
71
|
+
width="30px"
|
|
72
|
+
height="30px"
|
|
73
|
+
fill="currentColor"
|
|
74
|
+
viewBox="0 0 24 24"
|
|
75
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
76
|
+
data-testid="LocationIcon"
|
|
77
|
+
>
|
|
78
|
+
<path
|
|
79
|
+
d="M13.5 8.0056c0 .8294-.6716 1.5017-1.5 1.5017s-1.5-.6723-1.5-1.5017.6716-1.5017 1.5-1.5017 1.5.6723 1.5 1.5017"
|
|
80
|
+
></path>
|
|
81
|
+
<path
|
|
82
|
+
fillRule="evenodd"
|
|
83
|
+
d="M12 17.0359c2.7614 0 5-6.2457 5-9.0102C17 5.261 14.7614 3.02 12 3.02S7 5.2611 7 8.0257c0 2.7645 2.2386 9.0102 5 9.0102m2.1803-4.1114C15.0205 11.1538 15.5 9.1236 15.5 8.0257 15.5 6.088 13.9314 4.52 12 4.52S8.5 6.088 8.5 8.0257c0 1.098.4795 3.1281 1.3197 4.8988.414.8726.8717 1.5863 1.3208 2.0583.4658.4896.7566.5531.8595.5531s.3937-.0635.8595-.5531c.4491-.472.9068-1.1857 1.3208-2.0583"
|
|
84
|
+
clipRule="evenodd"
|
|
85
|
+
></path>
|
|
86
|
+
<path
|
|
87
|
+
d="M16.8904 14.2118c-.1965.4142.0122.9154.4396 1.0813q.2178.0845.4184.175c1.5348.6914 1.7516 1.3369 1.7516 1.4976 0 .1606-.2168.8061-1.7516 1.4975-1.394.628-3.425 1.0553-5.7484 1.0553s-4.3544-.4273-5.7484-1.0553C4.7168 17.7718 4.5 17.1263 4.5 16.9657s.2168-.8062 1.7516-1.4976a8.5 8.5 0 0 1 .4184-.175c.4274-.1659.6361-.6671.4396-1.0813-.1528-.322-.5183-.493-.8522-.3682C4.2674 14.5873 3 15.7098 3 16.9657c0 2.2392 4.0294 4.0545 9 4.0545s9-1.8153 9-4.0545c0-1.2559-1.2674-2.3784-3.2574-3.1221-.3339-.1248-.6994.0462-.8522.3682"
|
|
88
|
+
></path>
|
|
89
|
+
</svg>
|
|
90
|
+
</span>
|
|
91
|
+
<div>
|
|
92
|
+
<h5 class="sw-fw500 sw-text-ellipsis">{{ option.geoTitle }}</h5>
|
|
93
|
+
<h6 class="sw-text-label sw-text-ellipsis">
|
|
94
|
+
{{ option.displayName }}
|
|
95
|
+
</h6>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
</template>
|
|
100
|
+
<li
|
|
101
|
+
v-if="
|
|
102
|
+
!isDestinationLoading &&
|
|
103
|
+
destinationQuery.trim().length >= MIN_SEARCH_CHARS &&
|
|
104
|
+
destinationResults.length === 0
|
|
105
|
+
"
|
|
106
|
+
class="sw-dropdown-list-item"
|
|
107
|
+
>
|
|
108
|
+
<div class="sw-dropdown-list-card">
|
|
109
|
+
<h6 class="sw-text-label">No results found</h6>
|
|
110
|
+
</div>
|
|
111
|
+
</li>
|
|
112
|
+
<li
|
|
113
|
+
v-for="option in destinationResults"
|
|
114
|
+
:key="option.geoId"
|
|
115
|
+
class="sw-dropdown-list-item"
|
|
116
|
+
@click="handleDestinationSelect(option)"
|
|
117
|
+
>
|
|
118
|
+
<div class="sw-dropdown-list-card">
|
|
119
|
+
<span class="sw-hotel-search-form-icon">
|
|
120
|
+
<svg
|
|
121
|
+
width="30px"
|
|
122
|
+
height="30px"
|
|
123
|
+
fill="currentColor"
|
|
124
|
+
viewBox="0 0 24 24"
|
|
125
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
126
|
+
data-testid="LocationIcon"
|
|
127
|
+
>
|
|
128
|
+
<path
|
|
129
|
+
d="M13.5 8.0056c0 .8294-.6716 1.5017-1.5 1.5017s-1.5-.6723-1.5-1.5017.6716-1.5017 1.5-1.5017 1.5.6723 1.5 1.5017"
|
|
130
|
+
></path>
|
|
131
|
+
<path
|
|
132
|
+
fillRule="evenodd"
|
|
133
|
+
d="M12 17.0359c2.7614 0 5-6.2457 5-9.0102C17 5.261 14.7614 3.02 12 3.02S7 5.2611 7 8.0257c0 2.7645 2.2386 9.0102 5 9.0102m2.1803-4.1114C15.0205 11.1538 15.5 9.1236 15.5 8.0257 15.5 6.088 13.9314 4.52 12 4.52S8.5 6.088 8.5 8.0257c0 1.098.4795 3.1281 1.3197 4.8988.414.8726.8717 1.5863 1.3208 2.0583.4658.4896.7566.5531.8595.5531s.3937-.0635.8595-.5531c.4491-.472.9068-1.1857 1.3208-2.0583"
|
|
134
|
+
clipRule="evenodd"
|
|
135
|
+
></path>
|
|
136
|
+
<path
|
|
137
|
+
d="M16.8904 14.2118c-.1965.4142.0122.9154.4396 1.0813q.2178.0845.4184.175c1.5348.6914 1.7516 1.3369 1.7516 1.4976 0 .1606-.2168.8061-1.7516 1.4975-1.394.628-3.425 1.0553-5.7484 1.0553s-4.3544-.4273-5.7484-1.0553C4.7168 17.7718 4.5 17.1263 4.5 16.9657s.2168-.8062 1.7516-1.4976a8.5 8.5 0 0 1 .4184-.175c.4274-.1659.6361-.6671.4396-1.0813-.1528-.322-.5183-.493-.8522-.3682C4.2674 14.5873 3 15.7098 3 16.9657c0 2.2392 4.0294 4.0545 9 4.0545s9-1.8153 9-4.0545c0-1.2559-1.2674-2.3784-3.2574-3.1221-.3339-.1248-.6994.0462-.8522.3682"
|
|
138
|
+
></path>
|
|
139
|
+
</svg>
|
|
140
|
+
</span>
|
|
141
|
+
<div>
|
|
142
|
+
<h5 class="sw-fw500 sw-text-ellipsis">{{ option.geoTitle }}</h5>
|
|
143
|
+
<h6 class="sw-text-label sw-text-ellipsis">
|
|
144
|
+
{{ option.displayName }}
|
|
145
|
+
</h6>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</li>
|
|
149
|
+
</ul>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<!-- Nationality Field -->
|
|
155
|
+
<div
|
|
156
|
+
:class="[
|
|
157
|
+
'sw-form-field',
|
|
158
|
+
'sw-pos-rel',
|
|
159
|
+
{ 'thsw-field-error': fieldErrors.nationality }
|
|
160
|
+
]"
|
|
161
|
+
>
|
|
162
|
+
<label for="nationality">Nationality</label>
|
|
163
|
+
<div class="sw-custom-dropdown" ref="nationalityDropdownRef">
|
|
164
|
+
<div class="sw-custom-dropdown-trigger">
|
|
165
|
+
<input
|
|
166
|
+
class="hotel-destination-input"
|
|
167
|
+
:value="nationalityQuery"
|
|
168
|
+
@input="handleNationalityQueryChange"
|
|
169
|
+
@focus="setIsNationalityDropdownOpen(true)"
|
|
170
|
+
:placeholder="formData.nationality"
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
<div v-if="isNationalityDropdownOpen" class="sw-dropdown">
|
|
174
|
+
<ul class="sw-dropdown-list">
|
|
175
|
+
<li v-if="isCountryLoading" class="sw-dropdown-list-item">
|
|
176
|
+
<div class="sw-dropdown-list-box">
|
|
177
|
+
<div>
|
|
178
|
+
<h5 class="sw-fw500 sw-text-ellipsis">Searching...</h5>
|
|
179
|
+
</div>
|
|
180
|
+
</div>
|
|
181
|
+
</li>
|
|
182
|
+
<template
|
|
183
|
+
v-if="
|
|
184
|
+
!isCountryLoading &&
|
|
185
|
+
nationalityQuery.trim().length < MIN_SEARCH_CHARS
|
|
186
|
+
"
|
|
187
|
+
>
|
|
188
|
+
<li
|
|
189
|
+
v-for="option in STATIC_COUNTRIES"
|
|
190
|
+
:key="option.country_id"
|
|
191
|
+
class="sw-dropdown-list-item"
|
|
192
|
+
@click="handleNationalitySelect(option)"
|
|
193
|
+
>
|
|
194
|
+
<div class="sw-dropdown-list-box">
|
|
195
|
+
<div>
|
|
196
|
+
<h5 class="sw-fw500 sw-text-ellipsis">
|
|
197
|
+
{{ option.country_name }}
|
|
198
|
+
</h5>
|
|
199
|
+
</div>
|
|
200
|
+
<h6 class="sw-dropdown-item-badge sw-en-font">
|
|
201
|
+
{{ option.country_code }}
|
|
202
|
+
</h6>
|
|
203
|
+
</div>
|
|
204
|
+
</li>
|
|
205
|
+
</template>
|
|
206
|
+
<li
|
|
207
|
+
v-if="
|
|
208
|
+
!isCountryLoading &&
|
|
209
|
+
nationalityQuery.trim().length >= MIN_SEARCH_CHARS &&
|
|
210
|
+
countryResults.length === 0
|
|
211
|
+
"
|
|
212
|
+
class="sw-dropdown-list-item"
|
|
213
|
+
>
|
|
214
|
+
<div class="sw-dropdown-list-box">
|
|
215
|
+
<div>
|
|
216
|
+
<h5 class="sw-fw500 sw-text-ellipsis">No results found</h5>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</li>
|
|
220
|
+
<template
|
|
221
|
+
v-if="
|
|
222
|
+
!isCountryLoading &&
|
|
223
|
+
nationalityQuery.trim().length >= MIN_SEARCH_CHARS
|
|
224
|
+
"
|
|
225
|
+
>
|
|
226
|
+
<li
|
|
227
|
+
v-for="option in filteredNationalityOptions"
|
|
228
|
+
:key="option.country_id"
|
|
229
|
+
class="sw-dropdown-list-item"
|
|
230
|
+
@click="handleNationalitySelect(option)"
|
|
231
|
+
>
|
|
232
|
+
<div class="sw-dropdown-list-box">
|
|
233
|
+
<div>
|
|
234
|
+
<h5 class="sw-fw500 sw-text-ellipsis">
|
|
235
|
+
{{ option.country_name }}
|
|
236
|
+
</h5>
|
|
237
|
+
</div>
|
|
238
|
+
<h6 class="sw-dropdown-item-badge sw-en-font">
|
|
239
|
+
{{ option.country_code }}
|
|
240
|
+
</h6>
|
|
241
|
+
</div>
|
|
242
|
+
</li>
|
|
243
|
+
</template>
|
|
244
|
+
</ul>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<!-- Check-in/Check-out Dates -->
|
|
250
|
+
<div class="sw-srh-wrap">
|
|
251
|
+
<div
|
|
252
|
+
:class="[
|
|
253
|
+
'sw-form-field',
|
|
254
|
+
'sw-pos-rel',
|
|
255
|
+
'sw-com-cal',
|
|
256
|
+
{ 'thsw-field-error': fieldErrors.checkIn }
|
|
257
|
+
]"
|
|
258
|
+
>
|
|
259
|
+
<label for="checkIn">Check-in</label>
|
|
260
|
+
<input
|
|
261
|
+
id="checkIn"
|
|
262
|
+
class="hotel-destination-input"
|
|
263
|
+
readonly
|
|
264
|
+
:value="formatDate(formData.checkIn)"
|
|
265
|
+
placeholder="Select date"
|
|
266
|
+
@click="showCalendar"
|
|
267
|
+
/>
|
|
268
|
+
<Calendar
|
|
269
|
+
ref="calendarRef"
|
|
270
|
+
v-model="dateRange"
|
|
271
|
+
selectionMode="range"
|
|
272
|
+
:numberOfMonths="2"
|
|
273
|
+
:minDate="new Date()"
|
|
274
|
+
dateFormat="dd M yy"
|
|
275
|
+
class="thsw-range-calendar"
|
|
276
|
+
@update:modelValue="handleDateRangeChange"
|
|
277
|
+
/>
|
|
278
|
+
</div>
|
|
279
|
+
<div
|
|
280
|
+
:class="[
|
|
281
|
+
'sw-form-field',
|
|
282
|
+
'sw-pos-rel',
|
|
283
|
+
'sw-com-cal',
|
|
284
|
+
{ 'thsw-field-error': fieldErrors.checkOut }
|
|
285
|
+
]"
|
|
286
|
+
>
|
|
287
|
+
<label for="checkOut">Check-out</label>
|
|
288
|
+
<input
|
|
289
|
+
id="checkOut"
|
|
290
|
+
class="hotel-destination-input"
|
|
291
|
+
readonly
|
|
292
|
+
:value="formatDate(formData.checkOut)"
|
|
293
|
+
placeholder="Select date"
|
|
294
|
+
@click="showCalendar"
|
|
295
|
+
/>
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
<!-- Rooms & Guests -->
|
|
300
|
+
<div class="sw-form-field">
|
|
301
|
+
<label for="rooms">Rooms & Guests</label>
|
|
302
|
+
<div class="sw-custom-dropdown" ref="roomsGuestsMenuRef">
|
|
303
|
+
<div
|
|
304
|
+
class="sw-custom-dropdown-trigger"
|
|
305
|
+
@click="setIsRoomsGuestsMenuOpen(!isRoomsGuestsMenuOpen)"
|
|
306
|
+
>
|
|
307
|
+
<span>{{ formData.roomsGuests }}</span>
|
|
308
|
+
</div>
|
|
309
|
+
<div v-if="isRoomsGuestsMenuOpen" class="sw-rooms-guests-menu">
|
|
310
|
+
<h3 class="sw-rooms-guests-title">Number of Travellers</h3>
|
|
311
|
+
<div
|
|
312
|
+
v-for="(room, index) in rooms"
|
|
313
|
+
:key="index"
|
|
314
|
+
class="sw-room-section"
|
|
315
|
+
>
|
|
316
|
+
<div class="sw-room-header">
|
|
317
|
+
<svg
|
|
318
|
+
width="24"
|
|
319
|
+
height="24"
|
|
320
|
+
viewBox="0 0 24 24"
|
|
321
|
+
version="1.1"
|
|
322
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
323
|
+
>
|
|
324
|
+
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
|
|
325
|
+
<g transform="translate(-4092.000000, -824.000000)">
|
|
326
|
+
<g transform="translate(4092.000000, 824.000000)">
|
|
327
|
+
<rect
|
|
328
|
+
fill="#FFFFFF"
|
|
329
|
+
opacity="0"
|
|
330
|
+
x="0"
|
|
331
|
+
y="0"
|
|
332
|
+
width="24"
|
|
333
|
+
height="24"
|
|
334
|
+
></rect>
|
|
335
|
+
<g
|
|
336
|
+
transform="translate(3.000000, 6.000000)"
|
|
337
|
+
fill="var(--primary)"
|
|
338
|
+
fillRule="nonzero"
|
|
339
|
+
>
|
|
340
|
+
<path
|
|
341
|
+
d="M18.011072,5.62815523 L18.011072,1.13048375 C18.011072,0.514180505 17.4892314,0.012765343 16.8478226,0.012765343 L2.14333676,0.012765343 C1.50192802,0.012765343 0.980087404,0.514180505 0.980087404,1.13048375 L0.980087404,5.62820217 C0.42596144,5.70920578 0,6.17002527 0,6.72475451 L0,9.50863177 C0,9.70302166 0.164015424,9.86061733 0.366323907,9.86061733 L0.75877892,9.86061733 L0.75877892,11.5970794 C0.75877892,11.7914693 0.922794344,11.949065 1.12510283,11.949065 L1.78639075,11.949065 L1.78639075,12.6268014 C1.78639075,12.8211913 1.95040617,12.978787 2.15271465,12.978787 C2.35502314,12.978787 2.51903856,12.8211913 2.51903856,12.6268014 L2.51903856,11.949065 L16.4721697,11.949065 L16.4721697,12.6268014 C16.4721697,12.8211913 16.6361362,12.978787 16.8384936,12.978787 C17.0408509,12.978787 17.2048175,12.8211913 17.2048175,12.6268014 L17.2048175,11.949065 L17.8661054,11.949065 C18.0684627,11.949065 18.2324293,11.7914693 18.2324293,11.5970794 L18.2324293,9.86061733 L18.6248355,9.86061733 C18.8271928,9.86061733 18.9911594,9.70302166 18.9911594,9.50863177 L18.9911594,6.72475451 C18.9911594,6.17002527 18.5651979,5.70915884 18.011072,5.62815523 Z M1.71268638,1.13048375 C1.71268638,0.902350181 1.90586118,0.716736462 2.14328792,0.716736462 L16.8477738,0.716736462 C17.0852005,0.716736462 17.2783753,0.902350181 17.2783753,1.13048375 L17.2783753,5.61553069 L15.99209,5.61553069 C16.2480283,5.35079061 16.5083625,5.0723935 16.7779769,4.77451986 C16.8978869,4.64203249 16.897838,4.44492058 16.777928,4.31248014 C15.7181285,3.14210469 14.8307943,2.28950181 13.6130848,1.27151264 C13.4752494,1.1562491 13.2700591,1.1562491 13.1321748,1.27151264 C11.9144165,2.28954874 11.0271311,3.14215162 9.96738046,4.31248014 C9.84747044,4.44492058 9.84742159,4.64203249 9.96733162,4.77451986 C10.2369949,5.07244043 10.4972802,5.35083755 10.7532185,5.61553069 L8.23764781,5.61553069 C8.49358612,5.35079061 8.75396915,5.0723935 9.02358355,4.77451986 C9.14349357,4.64203249 9.14349357,4.44492058 9.0235347,4.31248014 C7.96388175,3.14229242 7.07654756,2.28968953 5.85869152,1.27151264 C5.72085604,1.1562491 5.51566581,1.1562491 5.37778149,1.27151264 C4.15992545,2.28968953 3.27259126,3.14229242 2.2129383,4.31248014 C2.09297943,4.44492058 2.09297943,4.64203249 2.21288946,4.77451986 C2.4825527,5.0723935 2.74288689,5.35079061 2.99882519,5.61553069 L1.71268638,5.61553069 L1.71268638,1.13048375 Z M11.7640283,5.61553069 C11.4269614,5.28306859 11.0864756,4.92981588 10.7309949,4.54352347 C11.6067044,3.59142599 12.3820411,2.84643682 13.3726787,2.00519134 C14.3633162,2.84638989 15.138653,3.59142599 16.0144113,4.54352347 C15.6589794,4.92981588 15.3184447,5.28306859 14.9813779,5.61553069 L11.7640283,5.61553069 Z M4.0096838,5.61553069 C3.67261697,5.28302166 3.33208226,4.92981588 2.97660154,4.54352347 C3.85216452,3.59161372 4.62764781,2.84648375 5.61833419,2.00519134 C6.60902057,2.84648375 7.38450386,3.59161372 8.26006684,4.54352347 C7.90463496,4.92981588 7.56410026,5.28302166 7.22698458,5.61553069 L4.0096838,5.61553069 Z M17.4997815,11.2450939 L1.49142674,11.2450939 L1.49142674,9.86061733 L17.4997815,9.86061733 L17.4997815,11.2450939 Z M18.2585116,9.15664621 L0.732647815,9.15664621 L0.732647815,6.72475451 C0.732647815,6.50131408 0.921866324,6.31950181 1.15445758,6.31950181 L17.8367018,6.31950181 C18.0692931,6.31950181 18.2585116,6.50131408 18.2585116,6.72475451 L18.2585116,9.15664621 Z"
|
|
342
|
+
></path>
|
|
343
|
+
</g>
|
|
344
|
+
</g>
|
|
345
|
+
</g>
|
|
346
|
+
</g>
|
|
347
|
+
</svg>
|
|
348
|
+
Room {{ index + 1 }}
|
|
349
|
+
</div>
|
|
350
|
+
<div class="sw-room-controls-group">
|
|
351
|
+
<div class="sw-room-control-item">
|
|
352
|
+
<div class="sw-room-control-label">
|
|
353
|
+
<span>Adult (18+ years)</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div class="sw-room-control-buttons">
|
|
356
|
+
<button
|
|
357
|
+
type="button"
|
|
358
|
+
:class="[
|
|
359
|
+
'sw-count-button',
|
|
360
|
+
room.adults === 1 ? 'sw-disabled' : 'sw-active'
|
|
361
|
+
]"
|
|
362
|
+
@click="decrementAdults(index)"
|
|
363
|
+
:disabled="room.adults === 1"
|
|
364
|
+
>
|
|
365
|
+
<svg
|
|
366
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
367
|
+
height="20"
|
|
368
|
+
fill="var(--sw-white)"
|
|
369
|
+
viewBox="0 -960 960 960"
|
|
370
|
+
>
|
|
371
|
+
<path d="M200-440v-80h560v80H200Z"></path>
|
|
372
|
+
</svg>
|
|
373
|
+
</button>
|
|
374
|
+
<span class="sw-count-value">{{ room.adults }}</span>
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
:class="[
|
|
378
|
+
'sw-count-button',
|
|
379
|
+
room.adults >= MAX_ADULTS_PER_ROOM
|
|
380
|
+
? 'sw-disabled'
|
|
381
|
+
: 'sw-active'
|
|
382
|
+
]"
|
|
383
|
+
@click="incrementAdults(index)"
|
|
384
|
+
:disabled="room.adults >= MAX_ADULTS_PER_ROOM"
|
|
385
|
+
>
|
|
386
|
+
<svg
|
|
387
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
388
|
+
height="20"
|
|
389
|
+
fill="var(--sw-white)"
|
|
390
|
+
viewBox="0 -960 960 960"
|
|
391
|
+
>
|
|
392
|
+
<path
|
|
393
|
+
d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"
|
|
394
|
+
></path>
|
|
395
|
+
</svg>
|
|
396
|
+
</button>
|
|
397
|
+
</div>
|
|
398
|
+
</div>
|
|
399
|
+
<div class="sw-room-control-item">
|
|
400
|
+
<div class="sw-room-control-label">
|
|
401
|
+
<span>Child (0-17 years)</span>
|
|
402
|
+
</div>
|
|
403
|
+
<div class="sw-room-control-buttons">
|
|
404
|
+
<button
|
|
405
|
+
type="button"
|
|
406
|
+
:class="[
|
|
407
|
+
'sw-count-button',
|
|
408
|
+
room.children === 0 ? 'sw-disabled' : 'sw-active'
|
|
409
|
+
]"
|
|
410
|
+
@click="decrementChildren(index)"
|
|
411
|
+
:disabled="room.children === 0"
|
|
412
|
+
>
|
|
413
|
+
<svg
|
|
414
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
415
|
+
height="20"
|
|
416
|
+
fill="var(--sw-white)"
|
|
417
|
+
viewBox="0 -960 960 960"
|
|
418
|
+
>
|
|
419
|
+
<path d="M200-440v-80h560v80H200Z"></path>
|
|
420
|
+
</svg>
|
|
421
|
+
</button>
|
|
422
|
+
<span class="sw-count-value">{{ room.children }}</span>
|
|
423
|
+
<button
|
|
424
|
+
type="button"
|
|
425
|
+
:class="[
|
|
426
|
+
'sw-count-button',
|
|
427
|
+
room.children >= 4 ? 'sw-disabled' : ''
|
|
428
|
+
]"
|
|
429
|
+
@click="incrementChildren(index)"
|
|
430
|
+
:disabled="room.children >= 4"
|
|
431
|
+
>
|
|
432
|
+
<svg
|
|
433
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
434
|
+
height="20"
|
|
435
|
+
fill="var(--sw-white)"
|
|
436
|
+
viewBox="0 -960 960 960"
|
|
437
|
+
>
|
|
438
|
+
<path
|
|
439
|
+
d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"
|
|
440
|
+
></path>
|
|
441
|
+
</svg>
|
|
442
|
+
</button>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
</div>
|
|
446
|
+
<div class="">
|
|
447
|
+
<div v-if="room.children > 0" class="sw-room-controls-child">
|
|
448
|
+
<span
|
|
449
|
+
v-for="(_, childIndex) in Array.from({ length: room.children })"
|
|
450
|
+
:key="childIndex"
|
|
451
|
+
class="sw-room-control-label"
|
|
452
|
+
>
|
|
453
|
+
Child {{ childIndex + 1 }}
|
|
454
|
+
</span>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="sw-room-controls-child">
|
|
457
|
+
<div
|
|
458
|
+
v-for="(_, childIndex) in Array.from({ length: room.children })"
|
|
459
|
+
:key="childIndex"
|
|
460
|
+
class="sw-child-age-item"
|
|
461
|
+
>
|
|
462
|
+
<Dropdown
|
|
463
|
+
:modelValue="room.childAges[childIndex] ?? 0"
|
|
464
|
+
:options="childAgeOptions"
|
|
465
|
+
@update:modelValue="
|
|
466
|
+
(value) => updateChildAge(index, childIndex, value)
|
|
467
|
+
"
|
|
468
|
+
placeholder="Age"
|
|
469
|
+
class="child-age-dropdown"
|
|
470
|
+
/>
|
|
471
|
+
</div>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
<button
|
|
475
|
+
v-if="rooms.length > 1 && index > 0"
|
|
476
|
+
type="button"
|
|
477
|
+
class="sw-remove-room-button"
|
|
478
|
+
@click="handleRemoveRoom(index)"
|
|
479
|
+
:aria-label="`Remove Room ${index + 1}`"
|
|
480
|
+
>
|
|
481
|
+
×
|
|
482
|
+
</button>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="sw-rooms-guests-footer">
|
|
485
|
+
<button
|
|
486
|
+
v-if="rooms.length < 6"
|
|
487
|
+
type="button"
|
|
488
|
+
class="sw-add-room-button"
|
|
489
|
+
@click="handleAddRoom"
|
|
490
|
+
>
|
|
491
|
+
<svg
|
|
492
|
+
width="14"
|
|
493
|
+
height="14"
|
|
494
|
+
viewBox="0 0 16 16"
|
|
495
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
496
|
+
>
|
|
497
|
+
<path fill="none" d="M0 0h16v16H0z"></path>
|
|
498
|
+
<path d="M15 7H9V1H7v6H1v2h6v6h2V9h6z"></path>
|
|
499
|
+
</svg>
|
|
500
|
+
Add another room
|
|
501
|
+
</button>
|
|
502
|
+
<div class="sw-rooms-guests-cta">
|
|
503
|
+
<button
|
|
504
|
+
type="button"
|
|
505
|
+
class="sw-reset-button"
|
|
506
|
+
@click="resetRooms"
|
|
507
|
+
>
|
|
508
|
+
Reset
|
|
509
|
+
</button>
|
|
510
|
+
<button
|
|
511
|
+
type="button"
|
|
512
|
+
class="sw-apply-button"
|
|
513
|
+
@click="setIsRoomsGuestsMenuOpen(false)"
|
|
514
|
+
>
|
|
515
|
+
Apply
|
|
516
|
+
</button>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<!-- Search Button -->
|
|
524
|
+
<Button
|
|
525
|
+
type="submit"
|
|
526
|
+
label="Search"
|
|
527
|
+
icon="pi pi-search"
|
|
528
|
+
class="sw-search-button br-right"
|
|
529
|
+
/>
|
|
530
|
+
</form>
|
|
531
|
+
|
|
532
|
+
<!-- Advanced Search Options -->
|
|
533
|
+
<div class="sw-advanced-search-options">
|
|
534
|
+
<Accordion>
|
|
535
|
+
<AccordionTab header="Advanced Search options">
|
|
536
|
+
<div class="sw-hsrh-advan-cont">
|
|
537
|
+
<h5>Star Rating:</h5>
|
|
538
|
+
<div class="sw-hsrh-chk-main">
|
|
539
|
+
<label
|
|
540
|
+
v-for="rating in [5, 4, 3, 2, 1]"
|
|
541
|
+
:key="rating"
|
|
542
|
+
class="sw-star-checkbox-wrap sw-checkbox-wrap"
|
|
543
|
+
>
|
|
544
|
+
<input
|
|
545
|
+
type="checkbox"
|
|
546
|
+
:checked="selectedStarRatings.includes(rating)"
|
|
547
|
+
@change="handleStarRatingToggle(rating)"
|
|
548
|
+
/>
|
|
549
|
+
<div class="sw-star-checkbox">
|
|
550
|
+
<span class="sw-en-font">{{ rating }}</span>
|
|
551
|
+
<svg
|
|
552
|
+
width="14"
|
|
553
|
+
height="14"
|
|
554
|
+
viewBox="0 0 18 17"
|
|
555
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
556
|
+
>
|
|
557
|
+
<path
|
|
558
|
+
d="M3.35375 16.6825L4.7865 10.5133L0 6.36525L6.31525 5.81725L8.77875 0L11.2422 5.81725L17.5575 6.36525L12.771 10.5133L14.2038 16.6825L8.77875 13.4095L3.35375 16.6825Z"
|
|
559
|
+
fill="#ffae43"
|
|
560
|
+
/>
|
|
561
|
+
</svg>
|
|
562
|
+
</div>
|
|
563
|
+
</label>
|
|
564
|
+
</div>
|
|
565
|
+
</div>
|
|
566
|
+
<div class="sw-hsrh-advan-cont">
|
|
567
|
+
<h5>Meal Type:</h5>
|
|
568
|
+
<div class="sw-hsrh-chk-main">
|
|
569
|
+
<label
|
|
570
|
+
v-for="mealType in mealTypeOptions"
|
|
571
|
+
:key="mealType.value"
|
|
572
|
+
class="sw-checkbox-wrap"
|
|
573
|
+
>
|
|
574
|
+
<input
|
|
575
|
+
type="checkbox"
|
|
576
|
+
:checked="selectedMealTypes.includes(mealType.value)"
|
|
577
|
+
@change="handleMealTypeToggle(mealType.value)"
|
|
578
|
+
/>
|
|
579
|
+
<div class="sw-star-checkbox">{{ mealType.label }}</div>
|
|
580
|
+
</label>
|
|
581
|
+
</div>
|
|
582
|
+
</div>
|
|
583
|
+
</AccordionTab>
|
|
584
|
+
</Accordion>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
</div>
|
|
588
|
+
</template>
|
|
589
|
+
|
|
590
|
+
<script setup>
|
|
591
|
+
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
|
592
|
+
import Button from 'primevue/button'
|
|
593
|
+
import Dropdown from 'primevue/dropdown'
|
|
594
|
+
import Calendar from 'primevue/calendar'
|
|
595
|
+
import Accordion from 'primevue/accordion'
|
|
596
|
+
import AccordionTab from 'primevue/accordiontab'
|
|
597
|
+
import { JSEncrypt } from 'jsencrypt'
|
|
598
|
+
import { hotelSearchWidgetStyles } from './hotel-search-widget.styles.js'
|
|
599
|
+
|
|
600
|
+
const props = defineProps({
|
|
601
|
+
config: {
|
|
602
|
+
type: Object,
|
|
603
|
+
default: () => ({})
|
|
604
|
+
},
|
|
605
|
+
minWidth: {
|
|
606
|
+
type: [Number, String],
|
|
607
|
+
default: undefined
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
const STYLE_ELEMENT_ID = 'thsw-hotel-styles'
|
|
612
|
+
const PRIMEICONS_LINK_ID = 'thsw-primeicons'
|
|
613
|
+
const MIN_SEARCH_CHARS = 3
|
|
614
|
+
const MAX_ADULTS_PER_ROOM = 6
|
|
615
|
+
|
|
616
|
+
const STATIC_LOCATIONS = [
|
|
617
|
+
{
|
|
618
|
+
geoId: '618704',
|
|
619
|
+
geoTitle: 'Dubai',
|
|
620
|
+
geoName: 'United Arab Emirates',
|
|
621
|
+
displayName: 'Dubai, United Arab Emirates',
|
|
622
|
+
type: 'city',
|
|
623
|
+
countryCode: 'AE',
|
|
624
|
+
lattitude: 25.27063,
|
|
625
|
+
longitude: 55.30037
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
geoId: '804926',
|
|
629
|
+
geoTitle: 'Dubai Hills',
|
|
630
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
631
|
+
displayName: 'Dubai Hills, Dubai, United Arab Emirates',
|
|
632
|
+
type: 'neighborhood',
|
|
633
|
+
countryCode: 'AE',
|
|
634
|
+
lattitude: 25.103307,
|
|
635
|
+
longitude: 55.247966
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
geoId: '618818',
|
|
639
|
+
geoTitle: 'Dubai Media City',
|
|
640
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
641
|
+
displayName: 'Dubai Media City, Dubai, United Arab Emirates',
|
|
642
|
+
type: 'neighborhood',
|
|
643
|
+
countryCode: 'AE',
|
|
644
|
+
lattitude: 25.094279,
|
|
645
|
+
longitude: 55.154095
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
geoId: '618493',
|
|
649
|
+
geoTitle: 'Dubai Internet City',
|
|
650
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
651
|
+
displayName: 'Dubai Internet City, Dubai, United Arab Emirates',
|
|
652
|
+
type: 'neighborhood',
|
|
653
|
+
countryCode: 'AE',
|
|
654
|
+
lattitude: 25.097792,
|
|
655
|
+
longitude: 55.163026
|
|
656
|
+
},
|
|
657
|
+
{
|
|
658
|
+
geoId: '618494',
|
|
659
|
+
geoTitle: 'Dubai Studio City',
|
|
660
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
661
|
+
displayName: 'Dubai Studio City, Dubai, United Arab Emirates',
|
|
662
|
+
type: 'neighborhood',
|
|
663
|
+
countryCode: 'AE',
|
|
664
|
+
lattitude: 25.041623,
|
|
665
|
+
longitude: 55.251897
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
geoId: '618670',
|
|
669
|
+
geoTitle: 'Dubai Sports City',
|
|
670
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
671
|
+
displayName: 'Dubai Sports City, Dubai, United Arab Emirates',
|
|
672
|
+
type: 'neighborhood',
|
|
673
|
+
countryCode: 'AE',
|
|
674
|
+
lattitude: 25.036236,
|
|
675
|
+
longitude: 55.217676
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
geoId: '618631',
|
|
679
|
+
geoTitle: 'Dubai Production City',
|
|
680
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
681
|
+
displayName: 'Dubai Production City, Dubai, United Arab Emirates',
|
|
682
|
+
type: 'neighborhood',
|
|
683
|
+
countryCode: 'AE',
|
|
684
|
+
lattitude: 25.032507,
|
|
685
|
+
longitude: 55.189725
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
geoId: '618733',
|
|
689
|
+
geoTitle: 'Ski Dubai',
|
|
690
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
691
|
+
displayName: 'Ski Dubai, Dubai, United Arab Emirates',
|
|
692
|
+
type: 'point_of_interest',
|
|
693
|
+
countryCode: 'AE',
|
|
694
|
+
lattitude: 25.117395,
|
|
695
|
+
longitude: 55.198021
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
geoId: '2442684',
|
|
699
|
+
geoTitle: 'Dubai',
|
|
700
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
701
|
+
displayName: 'Dubai, Dubai, United Arab Emirates',
|
|
702
|
+
type: 'hotel',
|
|
703
|
+
countryCode: '',
|
|
704
|
+
lattitude: 25.273935,
|
|
705
|
+
longitude: 55.32799
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
geoId: '1487637',
|
|
709
|
+
geoTitle: 'Dubai Luxury Stay - Downtown Dubai',
|
|
710
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
711
|
+
displayName: 'Dubai Luxury Stay - Downtown Dubai, Dubai, United Arab Emirates',
|
|
712
|
+
type: 'hotel',
|
|
713
|
+
countryCode: '',
|
|
714
|
+
lattitude: 25.076485,
|
|
715
|
+
longitude: 55.144405
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
geoId: '1910103',
|
|
719
|
+
geoTitle: 'Dubai Arch',
|
|
720
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
721
|
+
displayName: 'Dubai Arch, Dubai, United Arab Emirates',
|
|
722
|
+
type: 'hotel',
|
|
723
|
+
countryCode: '',
|
|
724
|
+
lattitude: 25.071701,
|
|
725
|
+
longitude: 55.143578
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
geoId: '737327',
|
|
729
|
+
geoTitle: 'Angsana Dubai',
|
|
730
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
731
|
+
displayName: 'Angsana Dubai, Dubai, United Arab Emirates',
|
|
732
|
+
type: 'hotel',
|
|
733
|
+
countryCode: '',
|
|
734
|
+
lattitude: 25.215,
|
|
735
|
+
longitude: 55.279
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
geoId: '2231257',
|
|
739
|
+
geoTitle: 'Dubai Backpackers',
|
|
740
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
741
|
+
displayName: 'Dubai Backpackers, Dubai, United Arab Emirates',
|
|
742
|
+
type: 'hotel',
|
|
743
|
+
countryCode: '',
|
|
744
|
+
lattitude: 25.09714,
|
|
745
|
+
longitude: 55.17065
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
geoId: '2322561',
|
|
749
|
+
geoTitle: 'Dubai Hostel',
|
|
750
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
751
|
+
displayName: 'Dubai Hostel, Dubai, United Arab Emirates',
|
|
752
|
+
type: 'hotel',
|
|
753
|
+
countryCode: '',
|
|
754
|
+
lattitude: 25.09696,
|
|
755
|
+
longitude: 55.1776
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
geoId: '2323808',
|
|
759
|
+
geoTitle: 'Hostel Dubai',
|
|
760
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
761
|
+
displayName: 'Hostel Dubai, Dubai, United Arab Emirates',
|
|
762
|
+
type: 'hotel',
|
|
763
|
+
countryCode: '',
|
|
764
|
+
lattitude: 25.19179,
|
|
765
|
+
longitude: 55.26195
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
geoId: '2442649',
|
|
769
|
+
geoTitle: 'Dubai 360',
|
|
770
|
+
geoName: 'Dubai, United Arab Emirates',
|
|
771
|
+
displayName: 'Dubai 360, Dubai, United Arab Emirates',
|
|
772
|
+
type: 'hotel',
|
|
773
|
+
countryCode: '',
|
|
774
|
+
lattitude: 25.088923,
|
|
775
|
+
longitude: 55.146818
|
|
776
|
+
}
|
|
777
|
+
]
|
|
778
|
+
|
|
779
|
+
const RSA_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
780
|
+
MIICITANBgkqhkiG9w0BAQEFAAOCAg4AMIICCQKCAgB0rRSHGZK/FtK15IRfQxtl
|
|
781
|
+
dN5ToPtfXGzTNBe1vGhow5xqp6BLubMKKPwCj3zOJiAm1Ip1VNqqYxWnaL8ZErBF
|
|
782
|
+
rErtE0r7kn50QgGgJCpdrWJ4unhaoe1su207d9oY6D6OBH90v1aXA6ZjN7zTJmiq
|
|
783
|
+
FJZOOTQE6t/XwNAOphq/B6Lcr1iQSQmSqaMozy4OQ4NvIzNTQn5tcs9qJBMsKFeU
|
|
784
|
+
cSp8VK76JBNEqSuEHNmbV2d/IQ5XAqVUiAs34kH2xWRSbBmy0WK3c3QKGzC1MRWM
|
|
785
|
+
9JauLN56TKUFOmUSjfWY3caJru6HxsiMTGKnvflXHfgA2aLAPxu72pIHoCTy5Hxj
|
|
786
|
+
AUoKM7qXUn/kQj4Ci0M/0LRF4boqd37wpbyVNeXNvqm+dLjxklf/+NHMfNL2Wvb4
|
|
787
|
+
ekHUVI8nMVPgswIcQ7q/Njf9DC+LA+/duf8m9JDjI3J6r9y1gQugdkLETGzF2Gj1
|
|
788
|
+
ToU0Pocrg6CFHImqq2z+cOeWnURrV0f0I1UWWGCb79OZxZ5oDjM9uCLRsoe8+Npg
|
|
789
|
+
ACZNf4AbnM7OUNj5TEYs6t/tUYouvTB7gH452I3EH6KWBl+ZTgCg0RFkOMazNOjn
|
|
790
|
+
4J0yfbaPvpVgKzF2WRyn63mTO2tdLRl5s9uqMFhXmCs6IzEqBpxMKL1aS6ONZLDG
|
|
791
|
+
5qPtpCPJnUFQEBTVlSO15QIDAQAB
|
|
792
|
+
-----END PUBLIC KEY-----`
|
|
793
|
+
|
|
794
|
+
const HOTEL_SEARCH_BASE_URL = 'https://hotelapihub.dev.futuretravelplatform.com'
|
|
795
|
+
const HOTEL_SEARCH_PATH = '/api/v1/hotels/search-form'
|
|
796
|
+
const HOTEL_SEARCH_URL = `${HOTEL_SEARCH_BASE_URL}${HOTEL_SEARCH_PATH}`
|
|
797
|
+
const COUNTRY_SEARCH_BASE_URL = 'https://adminapi.dev.futuretravelplatform.com'
|
|
798
|
+
const COUNTRY_SEARCH_PATH = '/api/MasterSearch/GetAllCountry/EN'
|
|
799
|
+
|
|
800
|
+
const STATIC_COUNTRIES = [
|
|
801
|
+
{
|
|
802
|
+
country_code: 'KW',
|
|
803
|
+
country_name: 'Kuwait',
|
|
804
|
+
country_id: '2108231234183749619'
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
country_code: 'BH',
|
|
808
|
+
country_name: 'Bahrain',
|
|
809
|
+
country_id: '2108231234168861036'
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
country_code: 'QA',
|
|
813
|
+
country_name: 'Qatar',
|
|
814
|
+
country_id: '2108231234193188713'
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
country_code: 'OM',
|
|
818
|
+
country_name: 'Oman',
|
|
819
|
+
country_id: '2108231234191323057'
|
|
820
|
+
}
|
|
821
|
+
]
|
|
822
|
+
|
|
823
|
+
const mealTypeOptions = [
|
|
824
|
+
{ label: 'Breakfast Only', value: 2 },
|
|
825
|
+
{ label: 'Breakfast or Lunch or Dinner', value: 3 },
|
|
826
|
+
{ label: 'Breakfast or Lunch and Dinner', value: 4 },
|
|
827
|
+
{ label: 'All Inclusive', value: 5 },
|
|
828
|
+
{ label: 'Room Only', value: 1 }
|
|
829
|
+
]
|
|
830
|
+
|
|
831
|
+
const childAgeOptions = [
|
|
832
|
+
{ label: 'Under 1', value: 0 },
|
|
833
|
+
...Array.from({ length: 17 }, (_, i) => ({
|
|
834
|
+
label: `${i + 1}`,
|
|
835
|
+
value: i + 1
|
|
836
|
+
}))
|
|
837
|
+
]
|
|
838
|
+
|
|
839
|
+
const theme = computed(() => props.config?.theme || {})
|
|
840
|
+
const primaryColor = computed(() => theme.value.primary ?? '#2c0a82')
|
|
841
|
+
const secondaryColor = computed(() => theme.value.secondary ?? '#2c0a82')
|
|
842
|
+
const primaryLightColor = computed(() => theme.value.primaryLight ?? '#f3e2ff')
|
|
843
|
+
const fontName = computed(() => props.config?.fontName)
|
|
844
|
+
const redirectionDomain = computed(() => props.config?.redirectionDomain)
|
|
845
|
+
|
|
846
|
+
const getInitialDates = () => {
|
|
847
|
+
const today = new Date()
|
|
848
|
+
today.setHours(0, 0, 0, 0)
|
|
849
|
+
const tomorrow = new Date(today)
|
|
850
|
+
tomorrow.setDate(today.getDate() + 1)
|
|
851
|
+
return { today, tomorrow }
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const { today, tomorrow } = getInitialDates()
|
|
855
|
+
|
|
856
|
+
const formData = ref({
|
|
857
|
+
destination: 'Destination',
|
|
858
|
+
nationality: 'Select Nationality',
|
|
859
|
+
checkIn: today,
|
|
860
|
+
checkOut: tomorrow,
|
|
861
|
+
roomsGuests: '1 Room, 2 Adult'
|
|
862
|
+
})
|
|
863
|
+
|
|
864
|
+
const destinationQuery = ref('')
|
|
865
|
+
const nationalityQuery = ref('')
|
|
866
|
+
const isDestinationDropdownOpen = ref(false)
|
|
867
|
+
const isNationalityDropdownOpen = ref(false)
|
|
868
|
+
const isRoomsGuestsMenuOpen = ref(false)
|
|
869
|
+
|
|
870
|
+
const destinationDropdownRef = ref(null)
|
|
871
|
+
const nationalityDropdownRef = ref(null)
|
|
872
|
+
const roomsGuestsMenuRef = ref(null)
|
|
873
|
+
const calendarRef = ref(null)
|
|
874
|
+
|
|
875
|
+
const rooms = ref([{ adults: 2, children: 0, childAges: [] }])
|
|
876
|
+
|
|
877
|
+
const destinationResults = ref([])
|
|
878
|
+
const isDestinationLoading = ref(false)
|
|
879
|
+
const countryResults = ref([])
|
|
880
|
+
const isCountryLoading = ref(false)
|
|
881
|
+
const selectedDestination = ref(null)
|
|
882
|
+
const selectedNationality = ref(null)
|
|
883
|
+
const isAroundCurrentLocation = ref(false)
|
|
884
|
+
const currentLocationCoords = ref(null)
|
|
885
|
+
|
|
886
|
+
const fieldErrors = ref({
|
|
887
|
+
destination: false,
|
|
888
|
+
nationality: false,
|
|
889
|
+
checkIn: false,
|
|
890
|
+
checkOut: false
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
const selectedStarRatings = ref([])
|
|
894
|
+
const selectedMealTypes = ref([])
|
|
895
|
+
const dateRange = ref([today, tomorrow])
|
|
896
|
+
|
|
897
|
+
const filteredNationalityOptions = computed(() => {
|
|
898
|
+
return nationalityQuery.value.trim().length === 0
|
|
899
|
+
? STATIC_COUNTRIES
|
|
900
|
+
: countryResults.value
|
|
901
|
+
})
|
|
902
|
+
|
|
903
|
+
const themeStyle = computed(() => {
|
|
904
|
+
const style = {
|
|
905
|
+
'--primary': primaryColor.value,
|
|
906
|
+
'--secondary': secondaryColor.value,
|
|
907
|
+
'--primary-light': primaryLightColor.value
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
if (fontName.value) {
|
|
911
|
+
style['--font-family'] = fontName.value
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (props.minWidth !== undefined) {
|
|
915
|
+
style.minWidth =
|
|
916
|
+
typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return style
|
|
920
|
+
})
|
|
921
|
+
|
|
922
|
+
const ensureStylesInjected = () => {
|
|
923
|
+
if (typeof document === 'undefined') {
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (document.getElementById(STYLE_ELEMENT_ID)) {
|
|
928
|
+
return
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const style = document.createElement('style')
|
|
932
|
+
style.id = STYLE_ELEMENT_ID
|
|
933
|
+
style.textContent = hotelSearchWidgetStyles
|
|
934
|
+
document.head.appendChild(style)
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const ensurePrimeIconsLoaded = () => {
|
|
938
|
+
if (typeof document === 'undefined') {
|
|
939
|
+
return
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (document.getElementById(PRIMEICONS_LINK_ID)) {
|
|
943
|
+
return
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
const link = document.createElement('link')
|
|
947
|
+
link.id = PRIMEICONS_LINK_ID
|
|
948
|
+
link.rel = 'stylesheet'
|
|
949
|
+
link.href = 'https://unpkg.com/primeicons@7.0.0/primeicons.css'
|
|
950
|
+
document.head.appendChild(link)
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const buildRequestToken = () => {
|
|
954
|
+
if (!redirectionDomain.value) {
|
|
955
|
+
return ''
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
const domainKey = `${redirectionDomain.value}~${Date.now()}`
|
|
959
|
+
const encryptor = new JSEncrypt()
|
|
960
|
+
encryptor.setPublicKey(RSA_PUBLIC_KEY)
|
|
961
|
+
return encryptor.encrypt(domainKey) || ''
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const handleInputChange = (field, value) => {
|
|
965
|
+
formData.value[field] = value
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
const handleDestinationQueryChange = (event) => {
|
|
969
|
+
const value = event.target.value
|
|
970
|
+
destinationQuery.value = value
|
|
971
|
+
selectedDestination.value = null
|
|
972
|
+
isAroundCurrentLocation.value = false
|
|
973
|
+
currentLocationCoords.value = null
|
|
974
|
+
if (value.trim().length > 0) {
|
|
975
|
+
fieldErrors.value.destination = false
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
const handleNationalityQueryChange = (event) => {
|
|
980
|
+
const value = event.target.value
|
|
981
|
+
nationalityQuery.value = value
|
|
982
|
+
selectedNationality.value = null
|
|
983
|
+
if (value.trim().length > 0) {
|
|
984
|
+
fieldErrors.value.nationality = false
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const handleDestinationSelect = (option) => {
|
|
989
|
+
handleInputChange('destination', option.displayName)
|
|
990
|
+
destinationQuery.value = option.displayName
|
|
991
|
+
selectedDestination.value = option
|
|
992
|
+
isAroundCurrentLocation.value = false
|
|
993
|
+
currentLocationCoords.value = null
|
|
994
|
+
isDestinationDropdownOpen.value = false
|
|
995
|
+
fieldErrors.value.destination = false
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
const handleNationalitySelect = (option) => {
|
|
999
|
+
handleInputChange('nationality', option.country_name)
|
|
1000
|
+
nationalityQuery.value = option.country_name
|
|
1001
|
+
selectedNationality.value = option
|
|
1002
|
+
isNationalityDropdownOpen.value = false
|
|
1003
|
+
fieldErrors.value.nationality = false
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const handleAroundCurrentLocation = () => {
|
|
1007
|
+
const label = 'Around Current Location'
|
|
1008
|
+
handleInputChange('destination', label)
|
|
1009
|
+
destinationQuery.value = label
|
|
1010
|
+
selectedDestination.value = null
|
|
1011
|
+
isAroundCurrentLocation.value = true
|
|
1012
|
+
isDestinationDropdownOpen.value = false
|
|
1013
|
+
fieldErrors.value.destination = false
|
|
1014
|
+
|
|
1015
|
+
if (!navigator.geolocation) {
|
|
1016
|
+
currentLocationCoords.value = null
|
|
1017
|
+
return
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
navigator.geolocation.getCurrentPosition(
|
|
1021
|
+
(position) => {
|
|
1022
|
+
currentLocationCoords.value = {
|
|
1023
|
+
lat: position.coords.latitude,
|
|
1024
|
+
lng: position.coords.longitude
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
() => {
|
|
1028
|
+
currentLocationCoords.value = null
|
|
1029
|
+
}
|
|
1030
|
+
)
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const handleStarRatingToggle = (rating) => {
|
|
1034
|
+
if (selectedStarRatings.value.includes(rating)) {
|
|
1035
|
+
selectedStarRatings.value = selectedStarRatings.value.filter(
|
|
1036
|
+
(value) => value !== rating
|
|
1037
|
+
)
|
|
1038
|
+
} else {
|
|
1039
|
+
selectedStarRatings.value = [...selectedStarRatings.value, rating].sort(
|
|
1040
|
+
(a, b) => a - b
|
|
1041
|
+
)
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
const handleMealTypeToggle = (mealType) => {
|
|
1046
|
+
if (selectedMealTypes.value.includes(mealType)) {
|
|
1047
|
+
selectedMealTypes.value = selectedMealTypes.value.filter(
|
|
1048
|
+
(value) => value !== mealType
|
|
1049
|
+
)
|
|
1050
|
+
} else {
|
|
1051
|
+
selectedMealTypes.value = [...selectedMealTypes.value, mealType]
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const handleRemoveRoom = (roomIndex) => {
|
|
1056
|
+
if (rooms.value.length <= 1) {
|
|
1057
|
+
return
|
|
1058
|
+
}
|
|
1059
|
+
rooms.value = rooms.value.filter((_, index) => index !== roomIndex)
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
const handleAddRoom = () => {
|
|
1063
|
+
if (rooms.value.length >= 6) {
|
|
1064
|
+
return
|
|
1065
|
+
}
|
|
1066
|
+
rooms.value = [...rooms.value, { adults: 1, children: 0, childAges: [] }]
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const resetRooms = () => {
|
|
1070
|
+
rooms.value = [{ adults: 2, children: 0, childAges: [] }]
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
const incrementAdults = (index) => {
|
|
1074
|
+
if (rooms.value[index].adults >= MAX_ADULTS_PER_ROOM) {
|
|
1075
|
+
return
|
|
1076
|
+
}
|
|
1077
|
+
const newRooms = [...rooms.value]
|
|
1078
|
+
newRooms[index].adults += 1
|
|
1079
|
+
rooms.value = newRooms
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
const decrementAdults = (index) => {
|
|
1083
|
+
const newRooms = [...rooms.value]
|
|
1084
|
+
newRooms[index].adults = Math.max(1, newRooms[index].adults - 1)
|
|
1085
|
+
rooms.value = newRooms
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
const incrementChildren = (index) => {
|
|
1089
|
+
if (rooms.value[index].children >= 4) {
|
|
1090
|
+
return
|
|
1091
|
+
}
|
|
1092
|
+
const newRooms = [...rooms.value]
|
|
1093
|
+
newRooms[index].children += 1
|
|
1094
|
+
newRooms[index].childAges = [...newRooms[index].childAges, 0]
|
|
1095
|
+
rooms.value = newRooms
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const decrementChildren = (index) => {
|
|
1099
|
+
const newRooms = [...rooms.value]
|
|
1100
|
+
newRooms[index].children = Math.max(0, newRooms[index].children - 1)
|
|
1101
|
+
newRooms[index].childAges = newRooms[index].childAges.slice(0, -1)
|
|
1102
|
+
rooms.value = newRooms
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
const updateChildAge = (roomIndex, childIndex, value) => {
|
|
1106
|
+
const newRooms = [...rooms.value]
|
|
1107
|
+
newRooms[roomIndex].childAges[childIndex] = value
|
|
1108
|
+
rooms.value = newRooms
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const formatDate = (date) => {
|
|
1112
|
+
if (!date) {
|
|
1113
|
+
return ''
|
|
1114
|
+
}
|
|
1115
|
+
return date.toLocaleDateString('en-GB', {
|
|
1116
|
+
weekday: 'short',
|
|
1117
|
+
day: '2-digit',
|
|
1118
|
+
month: 'short'
|
|
1119
|
+
})
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
const formatQueryDate = (date) => {
|
|
1123
|
+
if (!date) {
|
|
1124
|
+
return ''
|
|
1125
|
+
}
|
|
1126
|
+
return date.toLocaleDateString('en-GB', {
|
|
1127
|
+
day: '2-digit',
|
|
1128
|
+
month: 'short',
|
|
1129
|
+
year: 'numeric'
|
|
1130
|
+
})
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const handleDateRangeChange = (value) => {
|
|
1134
|
+
dateRange.value = value
|
|
1135
|
+
const start = value?.[0] ?? null
|
|
1136
|
+
const end = value?.[1] ?? null
|
|
1137
|
+
handleInputChange('checkIn', start)
|
|
1138
|
+
handleInputChange('checkOut', end)
|
|
1139
|
+
|
|
1140
|
+
if (start && end) {
|
|
1141
|
+
if (calendarRef.value) {
|
|
1142
|
+
calendarRef.value.hide()
|
|
1143
|
+
}
|
|
1144
|
+
fieldErrors.value.checkIn = false
|
|
1145
|
+
fieldErrors.value.checkOut = false
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
const showCalendar = () => {
|
|
1150
|
+
if (calendarRef.value) {
|
|
1151
|
+
calendarRef.value.show()
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
const validateRequiredFields = () => {
|
|
1156
|
+
const destinationValid = destinationQuery.value.trim().length > 0
|
|
1157
|
+
const nationalityValid = nationalityQuery.value.trim().length > 0
|
|
1158
|
+
const checkInValid = !!formData.value.checkIn
|
|
1159
|
+
const checkOutValid = !!formData.value.checkOut
|
|
1160
|
+
|
|
1161
|
+
fieldErrors.value = {
|
|
1162
|
+
destination: !destinationValid,
|
|
1163
|
+
nationality: !nationalityValid,
|
|
1164
|
+
checkIn: !checkInValid,
|
|
1165
|
+
checkOut: !checkOutValid
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
return destinationValid && nationalityValid && checkInValid && checkOutValid
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const handleSearch = () => {
|
|
1172
|
+
const destination = selectedDestination.value
|
|
1173
|
+
const nationality = selectedNationality.value
|
|
1174
|
+
const roomParams = rooms.value.map((room, index) => {
|
|
1175
|
+
const parts = [room.adults, room.children, ...room.childAges]
|
|
1176
|
+
return `room${index + 1}=${parts.join('-')}`
|
|
1177
|
+
})
|
|
1178
|
+
const latitudeValue = isAroundCurrentLocation.value
|
|
1179
|
+
? (currentLocationCoords.value?.lat?.toString() ?? '0')
|
|
1180
|
+
: (destination?.lattitude?.toString() ?? '0')
|
|
1181
|
+
const longitudeValue = isAroundCurrentLocation.value
|
|
1182
|
+
? (currentLocationCoords.value?.lng?.toString() ?? '0')
|
|
1183
|
+
: (destination?.longitude?.toString() ?? '0')
|
|
1184
|
+
const queryParams = {
|
|
1185
|
+
curr: 'INR',
|
|
1186
|
+
geoId: isAroundCurrentLocation.value ? '' : (destination?.geoId ?? ''),
|
|
1187
|
+
Country: isAroundCurrentLocation.value ? '' : (destination?.countryCode ?? ''),
|
|
1188
|
+
nationality: nationality?.country_code ?? '',
|
|
1189
|
+
checkinDate: formData.value.checkIn ? formatQueryDate(formData.value.checkIn) : '',
|
|
1190
|
+
checkoutDate: formData.value.checkOut ? formatQueryDate(formData.value.checkOut) : '',
|
|
1191
|
+
lc: 'EN',
|
|
1192
|
+
StarRatings:
|
|
1193
|
+
selectedStarRatings.value.length > 0
|
|
1194
|
+
? selectedStarRatings.value.join(',')
|
|
1195
|
+
: '',
|
|
1196
|
+
MealTypes:
|
|
1197
|
+
selectedMealTypes.value.length > 0 ? selectedMealTypes.value.join(',') : '',
|
|
1198
|
+
FreeCancellation: 'false',
|
|
1199
|
+
geoName: isAroundCurrentLocation.value
|
|
1200
|
+
? 'Around Current Location'
|
|
1201
|
+
: (destination?.displayName ?? ''),
|
|
1202
|
+
Lat: latitudeValue,
|
|
1203
|
+
Long: longitudeValue,
|
|
1204
|
+
is_around_current_location: isAroundCurrentLocation.value ? 'true' : 'false',
|
|
1205
|
+
currtime: Date.now().toString(),
|
|
1206
|
+
mgcc: '',
|
|
1207
|
+
type: isAroundCurrentLocation.value ? '' : (destination?.type ?? '')
|
|
1208
|
+
}
|
|
1209
|
+
const encodedPairs = Object.entries(queryParams).map(
|
|
1210
|
+
([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
|
1211
|
+
)
|
|
1212
|
+
const queryString = `/hotel/result?${[...encodedPairs, ...roomParams].join('&')}`
|
|
1213
|
+
console.log('Query string:', queryString)
|
|
1214
|
+
if (redirectionDomain.value) {
|
|
1215
|
+
const domainOnly = redirectionDomain.value
|
|
1216
|
+
.replace(/^https?:\/\//i, '')
|
|
1217
|
+
.split('/')[0]
|
|
1218
|
+
const normalizedDomain = `https://${domainOnly}`
|
|
1219
|
+
const redirectUrl = `${normalizedDomain}${queryString}`
|
|
1220
|
+
window.open(redirectUrl, '_blank', 'noopener,noreferrer')
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const handleSubmit = (e) => {
|
|
1225
|
+
e.preventDefault()
|
|
1226
|
+
if (!validateRequiredFields()) {
|
|
1227
|
+
return
|
|
1228
|
+
}
|
|
1229
|
+
handleSearch()
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// Watch for destination query changes
|
|
1233
|
+
let destinationSearchTimeout = null
|
|
1234
|
+
watch(destinationQuery, (query) => {
|
|
1235
|
+
const trimmedQuery = query.trim()
|
|
1236
|
+
if (trimmedQuery.length < MIN_SEARCH_CHARS) {
|
|
1237
|
+
destinationResults.value = []
|
|
1238
|
+
return
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (destinationSearchTimeout) {
|
|
1242
|
+
clearTimeout(destinationSearchTimeout)
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
destinationSearchTimeout = setTimeout(async () => {
|
|
1246
|
+
const controller = new AbortController()
|
|
1247
|
+
|
|
1248
|
+
try {
|
|
1249
|
+
isDestinationLoading.value = true
|
|
1250
|
+
const headers = {
|
|
1251
|
+
'Content-Type': 'application/json'
|
|
1252
|
+
}
|
|
1253
|
+
const token = buildRequestToken()
|
|
1254
|
+
if (token) {
|
|
1255
|
+
headers.token = token
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const response = await fetch(HOTEL_SEARCH_URL, {
|
|
1259
|
+
method: 'POST',
|
|
1260
|
+
headers,
|
|
1261
|
+
body: JSON.stringify({
|
|
1262
|
+
searchTerm: trimmedQuery,
|
|
1263
|
+
languageCode: 'EN',
|
|
1264
|
+
lattitude: '',
|
|
1265
|
+
longitude: ''
|
|
1266
|
+
}),
|
|
1267
|
+
signal: controller.signal
|
|
1268
|
+
})
|
|
1269
|
+
|
|
1270
|
+
if (!response.ok) {
|
|
1271
|
+
throw new Error('Destination search failed')
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
const data = await response.json()
|
|
1275
|
+
destinationResults.value = data.data?.locations ?? data.locations ?? []
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
if (!(error instanceof DOMException && error.name === 'AbortError')) {
|
|
1278
|
+
destinationResults.value = []
|
|
1279
|
+
}
|
|
1280
|
+
} finally {
|
|
1281
|
+
isDestinationLoading.value = false
|
|
1282
|
+
}
|
|
1283
|
+
}, 300)
|
|
1284
|
+
})
|
|
1285
|
+
|
|
1286
|
+
// Watch for nationality query changes
|
|
1287
|
+
let nationalitySearchTimeout = null
|
|
1288
|
+
watch(nationalityQuery, (query) => {
|
|
1289
|
+
const trimmedQuery = query.trim()
|
|
1290
|
+
if (trimmedQuery.length < MIN_SEARCH_CHARS) {
|
|
1291
|
+
countryResults.value = []
|
|
1292
|
+
return
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (nationalitySearchTimeout) {
|
|
1296
|
+
clearTimeout(nationalitySearchTimeout)
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
nationalitySearchTimeout = setTimeout(async () => {
|
|
1300
|
+
const controller = new AbortController()
|
|
1301
|
+
|
|
1302
|
+
try {
|
|
1303
|
+
isCountryLoading.value = true
|
|
1304
|
+
const headers = {
|
|
1305
|
+
'Content-Type': 'application/json'
|
|
1306
|
+
}
|
|
1307
|
+
const token = buildRequestToken()
|
|
1308
|
+
if (token) {
|
|
1309
|
+
headers.token = token
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
const response = await fetch(
|
|
1313
|
+
`${COUNTRY_SEARCH_BASE_URL}${COUNTRY_SEARCH_PATH}/${encodeURIComponent(trimmedQuery)}`,
|
|
1314
|
+
{
|
|
1315
|
+
method: 'GET',
|
|
1316
|
+
headers,
|
|
1317
|
+
signal: controller.signal
|
|
1318
|
+
}
|
|
1319
|
+
)
|
|
1320
|
+
|
|
1321
|
+
if (!response.ok) {
|
|
1322
|
+
throw new Error('Nationality search failed')
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
const data = await response.json()
|
|
1326
|
+
countryResults.value = data ?? []
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
if (!(error instanceof DOMException && error.name === 'AbortError')) {
|
|
1329
|
+
countryResults.value = []
|
|
1330
|
+
}
|
|
1331
|
+
} finally {
|
|
1332
|
+
isCountryLoading.value = false
|
|
1333
|
+
}
|
|
1334
|
+
}, 300)
|
|
1335
|
+
})
|
|
1336
|
+
|
|
1337
|
+
// Watch for rooms changes to update display text
|
|
1338
|
+
watch(
|
|
1339
|
+
rooms,
|
|
1340
|
+
() => {
|
|
1341
|
+
const totalAdults = rooms.value.reduce((sum, room) => sum + room.adults, 0)
|
|
1342
|
+
const totalChildren = rooms.value.reduce((sum, room) => sum + room.children, 0)
|
|
1343
|
+
const roomsText = rooms.value.length === 1 ? 'Room' : 'Rooms'
|
|
1344
|
+
const adultsText = totalAdults === 1 ? 'Adult' : 'Adults'
|
|
1345
|
+
const childrenText =
|
|
1346
|
+
totalChildren === 0
|
|
1347
|
+
? ''
|
|
1348
|
+
: totalChildren === 1
|
|
1349
|
+
? `, ${totalChildren} Child`
|
|
1350
|
+
: `, ${totalChildren} Children`
|
|
1351
|
+
const displayText = `${rooms.value.length} ${roomsText}, ${totalAdults} ${adultsText}${childrenText}`
|
|
1352
|
+
handleInputChange('roomsGuests', displayText)
|
|
1353
|
+
},
|
|
1354
|
+
{ deep: true }
|
|
1355
|
+
)
|
|
1356
|
+
|
|
1357
|
+
// Handle click outside
|
|
1358
|
+
const handleClickOutside = (event) => {
|
|
1359
|
+
const target = event.target
|
|
1360
|
+
|
|
1361
|
+
const isInsidePrimeDropdown =
|
|
1362
|
+
target.closest('.p-dropdown-panel') !== null ||
|
|
1363
|
+
target.closest('.p-dropdown-items-wrapper') !== null ||
|
|
1364
|
+
target.closest('.p-dropdown-item') !== null ||
|
|
1365
|
+
target.closest('.p-component')?.classList.contains('p-dropdown') !== false ||
|
|
1366
|
+
target.closest('[data-pc-section="panel"]') !== null ||
|
|
1367
|
+
target.closest('[data-pc-name="dropdown"]') !== null
|
|
1368
|
+
|
|
1369
|
+
if (
|
|
1370
|
+
destinationDropdownRef.value &&
|
|
1371
|
+
!destinationDropdownRef.value.contains(target)
|
|
1372
|
+
) {
|
|
1373
|
+
isDestinationDropdownOpen.value = false
|
|
1374
|
+
}
|
|
1375
|
+
if (
|
|
1376
|
+
nationalityDropdownRef.value &&
|
|
1377
|
+
!nationalityDropdownRef.value.contains(target)
|
|
1378
|
+
) {
|
|
1379
|
+
isNationalityDropdownOpen.value = false
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
if (
|
|
1383
|
+
!isInsidePrimeDropdown &&
|
|
1384
|
+
roomsGuestsMenuRef.value &&
|
|
1385
|
+
!roomsGuestsMenuRef.value.contains(target)
|
|
1386
|
+
) {
|
|
1387
|
+
isRoomsGuestsMenuOpen.value = false
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
onMounted(() => {
|
|
1392
|
+
ensureStylesInjected()
|
|
1393
|
+
ensurePrimeIconsLoaded()
|
|
1394
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
1395
|
+
})
|
|
1396
|
+
|
|
1397
|
+
onUnmounted(() => {
|
|
1398
|
+
document.removeEventListener('mousedown', handleClickOutside)
|
|
1399
|
+
if (destinationSearchTimeout) {
|
|
1400
|
+
clearTimeout(destinationSearchTimeout)
|
|
1401
|
+
}
|
|
1402
|
+
if (nationalitySearchTimeout) {
|
|
1403
|
+
clearTimeout(nationalitySearchTimeout)
|
|
1404
|
+
}
|
|
1405
|
+
})
|
|
1406
|
+
</script>
|
|
1407
|
+
|
|
1408
|
+
<style>
|
|
1409
|
+
/* Styles are injected via JavaScript */
|
|
1410
|
+
</style>
|