@nuitee/booking-widget 1.0.8 → 1.0.10
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 +3 -3
- package/dist/booking-widget-standalone.js +102 -14
- package/dist/booking-widget.css +3 -4
- package/dist/booking-widget.js +63 -8
- package/dist/core/booking-api.js +43 -6
- package/dist/core/styles.css +3 -4
- package/dist/react/BookingWidget.jsx +17 -5
- package/dist/react/styles.css +3 -4
- package/dist/utils/config-service.js +53 -11
- package/dist/vue/BookingWidget.vue +18 -7
- package/dist/vue/styles.css +3 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,15 +94,15 @@ widget.open();
|
|
|
94
94
|
No bundler: load the script and CSS from the CDN, then create the widget.
|
|
95
95
|
|
|
96
96
|
```html
|
|
97
|
-
<link rel="stylesheet" href="https://cdn.thehotelplanet.com/booking-widget/v1.0.
|
|
98
|
-
<script src="https://cdn.thehotelplanet.com/booking-widget/v1.0.
|
|
97
|
+
<link rel="stylesheet" href="https://cdn.thehotelplanet.com/booking-widget/v1.0.10/dist/booking-widget.css">
|
|
98
|
+
<script src="https://cdn.thehotelplanet.com/booking-widget/v1.0.10/dist/booking-widget-standalone.js"></script>
|
|
99
99
|
|
|
100
100
|
<div id="booking-widget-container"></div>
|
|
101
101
|
|
|
102
102
|
<script>
|
|
103
103
|
const widget = new BookingWidget({
|
|
104
104
|
containerId: 'booking-widget-container',
|
|
105
|
-
cssUrl: 'https://cdn.thehotelplanet.com/booking-widget/v1.0.
|
|
105
|
+
cssUrl: 'https://cdn.thehotelplanet.com/booking-widget/v1.0.10/dist/booking-widget.css',
|
|
106
106
|
propertyKey: 'your-property-key',
|
|
107
107
|
onOpen: () => console.log('Opened'),
|
|
108
108
|
onClose: () => console.log('Closed'),
|
|
@@ -287,6 +287,20 @@ function createBookingApi(config = {}) {
|
|
|
287
287
|
|
|
288
288
|
// Fetch get_properties first to get currency_code (e.g. 'EUR') and room details
|
|
289
289
|
let propertyRooms = {};
|
|
290
|
+
const normalizeRoomsById = (rooms) => {
|
|
291
|
+
if (!rooms) return {};
|
|
292
|
+
if (Array.isArray(rooms)) {
|
|
293
|
+
return rooms.reduce((acc, room) => {
|
|
294
|
+
if (!room || typeof room !== 'object') return acc;
|
|
295
|
+
const key = room.id ?? room.room_id;
|
|
296
|
+
if (key == null) return acc;
|
|
297
|
+
acc[String(key)] = room;
|
|
298
|
+
return acc;
|
|
299
|
+
}, {});
|
|
300
|
+
}
|
|
301
|
+
if (typeof rooms === 'object') return rooms;
|
|
302
|
+
return {};
|
|
303
|
+
};
|
|
290
304
|
let propertyCurrency = currency;
|
|
291
305
|
const propQuery = propertyKey
|
|
292
306
|
? `key=${encodeURIComponent(propertyKey)}${sandbox ? '&sandbox=true' : ''}`
|
|
@@ -302,7 +316,7 @@ function createBookingApi(config = {}) {
|
|
|
302
316
|
const propRes = await _fetch(propFullUrl, getOpts);
|
|
303
317
|
if (propRes.ok) {
|
|
304
318
|
const propData = await propRes.json();
|
|
305
|
-
propertyRooms = propData?.rooms
|
|
319
|
+
propertyRooms = normalizeRoomsById(propData?.rooms);
|
|
306
320
|
propertyCurrency = (typeof propData?.currency_code === 'string' && propData.currency_code.trim())
|
|
307
321
|
? propData.currency_code.trim()
|
|
308
322
|
: currency;
|
|
@@ -361,18 +375,41 @@ function createBookingApi(config = {}) {
|
|
|
361
375
|
return (a?.price ?? a?.total ?? r?.price ?? 0) || 0;
|
|
362
376
|
};
|
|
363
377
|
const fallbackImage = 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=800&q=80';
|
|
378
|
+
const toImageUrl = (value) => {
|
|
379
|
+
if (typeof value !== 'string') return '';
|
|
380
|
+
const v = value.trim();
|
|
381
|
+
if (!v) return '';
|
|
382
|
+
if (/^https?:\/\//i.test(v)) return v;
|
|
383
|
+
if (v.startsWith('//')) return `https:${v}`;
|
|
384
|
+
if (s3BaseUrl) return `${s3BaseUrl}/${v.replace(/^\//, '')}`;
|
|
385
|
+
return '';
|
|
386
|
+
};
|
|
364
387
|
|
|
365
388
|
return availableRoomIds.map((roomId) => {
|
|
366
|
-
const roomData = propertyRooms[roomId] ?? propertyRooms[
|
|
389
|
+
const roomData = propertyRooms[String(roomId)] ?? propertyRooms[roomId] ?? {};
|
|
367
390
|
const roomRates = filteredRates.filter((r) => String(r.room_id) === String(roomId));
|
|
368
391
|
const minPrice = roomRates.length
|
|
369
392
|
? Math.min(...roomRates.map(getPrice).filter((p) => p > 0)) || roomData.base_price || 0
|
|
370
393
|
: roomData.base_price || 0;
|
|
371
394
|
|
|
372
|
-
const photos = roomData.photos
|
|
373
|
-
const mainPhoto = photos.find((p) => p
|
|
374
|
-
const
|
|
375
|
-
const
|
|
395
|
+
const photos = Array.isArray(roomData.photos) ? roomData.photos : [];
|
|
396
|
+
const mainPhoto = photos.find((p) => (typeof p === 'object' && p?.main)) ?? photos[0] ?? {};
|
|
397
|
+
const photoValue = typeof mainPhoto === 'string' ? mainPhoto : '';
|
|
398
|
+
const imageCandidates = [
|
|
399
|
+
photoValue,
|
|
400
|
+
mainPhoto.path,
|
|
401
|
+
mainPhoto.url,
|
|
402
|
+
mainPhoto.src,
|
|
403
|
+
mainPhoto.image,
|
|
404
|
+
mainPhoto.image_url,
|
|
405
|
+
mainPhoto.secure_url,
|
|
406
|
+
roomData.image,
|
|
407
|
+
roomData.image_url,
|
|
408
|
+
roomData.imageUrl,
|
|
409
|
+
roomData.thumbnail,
|
|
410
|
+
roomData.thumbnail_url,
|
|
411
|
+
];
|
|
412
|
+
const image = imageCandidates.map(toImageUrl).find(Boolean) || fallbackImage;
|
|
376
413
|
|
|
377
414
|
const sizeVal = roomData.size?.value ?? roomData.size_value;
|
|
378
415
|
let sizeUnit = roomData.size?.unit ?? roomData.size_unit ?? 'm²';
|
|
@@ -986,20 +1023,20 @@ if (typeof window !== 'undefined') {
|
|
|
986
1023
|
this.stripeInstance = null;
|
|
987
1024
|
this.elementsInstance = null;
|
|
988
1025
|
|
|
989
|
-
|
|
1026
|
+
this.baseSteps = [
|
|
990
1027
|
{ key: 'dates', label: 'Dates & Guests', num: '01' },
|
|
991
1028
|
{ key: 'rooms', label: 'Room', num: '02' },
|
|
992
1029
|
{ key: 'rates', label: 'Rate', num: '03' },
|
|
993
1030
|
{ key: 'summary', label: 'Summary', num: '04' },
|
|
994
1031
|
];
|
|
995
|
-
this.
|
|
996
|
-
this.STEPS = this.hasStripe ? [...baseSteps, { key: 'payment', label: 'Payment', num: '05' }] : baseSteps;
|
|
1032
|
+
this._recomputeSteps();
|
|
997
1033
|
|
|
998
1034
|
this.ROOMS = [];
|
|
999
1035
|
this.RATES = [];
|
|
1000
1036
|
this.bookingApi = this.options.bookingApi || ((this.options.propertyKey || defaultApiBase) && typeof window.createBookingApi === 'function'
|
|
1001
1037
|
? window.createBookingApi({
|
|
1002
1038
|
availabilityBaseUrl: defaultApiBase || '',
|
|
1039
|
+
s3BaseUrl: this.options.s3BaseUrl || undefined,
|
|
1003
1040
|
propertyKey: this.options.propertyKey || undefined,
|
|
1004
1041
|
mode: this.options.mode === 'sandbox' ? 'sandbox' : undefined,
|
|
1005
1042
|
})
|
|
@@ -1222,6 +1259,34 @@ if (typeof window !== 'undefined') {
|
|
|
1222
1259
|
};
|
|
1223
1260
|
}
|
|
1224
1261
|
|
|
1262
|
+
_applyApiS3BaseUrl(value) {
|
|
1263
|
+
const s3 = (typeof value === 'string' ? value.trim() : '').replace(/\/$/, '');
|
|
1264
|
+
if (!s3) return;
|
|
1265
|
+
if (!this.options.s3BaseUrl) this.options.s3BaseUrl = s3;
|
|
1266
|
+
if (this.options.bookingApi || typeof window.createBookingApi !== 'function') return;
|
|
1267
|
+
const defaultApiBase = (typeof window !== 'undefined' && window.__BOOKING_WIDGET_API_BASE_URL__) ? window.__BOOKING_WIDGET_API_BASE_URL__ : '';
|
|
1268
|
+
this.bookingApi = window.createBookingApi({
|
|
1269
|
+
availabilityBaseUrl: defaultApiBase || '',
|
|
1270
|
+
s3BaseUrl: this.options.s3BaseUrl || undefined,
|
|
1271
|
+
propertyKey: this.options.propertyKey || undefined,
|
|
1272
|
+
mode: this.options.mode === 'sandbox' ? 'sandbox' : undefined,
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
_applyApiStripeKey(value) {
|
|
1277
|
+
const key = (typeof value === 'string' ? value.trim() : '');
|
|
1278
|
+
if (!key || this.options.stripePublishableKey) return;
|
|
1279
|
+
this.options.stripePublishableKey = key;
|
|
1280
|
+
this._recomputeSteps();
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
_recomputeSteps() {
|
|
1284
|
+
this.hasStripe = !!(this.options.stripePublishableKey && typeof this.options.createPaymentIntent === 'function');
|
|
1285
|
+
this.STEPS = this.hasStripe
|
|
1286
|
+
? this.baseSteps.concat([{ key: 'payment', label: 'Payment', num: '05' }])
|
|
1287
|
+
: this.baseSteps.slice();
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1225
1290
|
_fetchRuntimeConfig() {
|
|
1226
1291
|
const self = this;
|
|
1227
1292
|
const key = String(this.options.propertyKey).trim();
|
|
@@ -1232,6 +1297,8 @@ if (typeof window !== 'undefined') {
|
|
|
1232
1297
|
if (__bwConfigCache[cacheKey]) {
|
|
1233
1298
|
const cached = __bwConfigCache[cacheKey];
|
|
1234
1299
|
this._applyApiColors(cached);
|
|
1300
|
+
this._applyApiS3BaseUrl(cached._s3BaseUrl);
|
|
1301
|
+
this._applyApiStripeKey(cached._stripePublishableKey);
|
|
1235
1302
|
this._configState = 'loaded';
|
|
1236
1303
|
this.applyColors();
|
|
1237
1304
|
this._initPosthog(cached._posthogKey || '');
|
|
@@ -1248,15 +1315,36 @@ if (typeof window !== 'undefined') {
|
|
|
1248
1315
|
return res.json();
|
|
1249
1316
|
})
|
|
1250
1317
|
.then(function (data) {
|
|
1318
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
1251
1319
|
const apiColors = {};
|
|
1252
|
-
if (data.widgetBackground) apiColors.background = data.widgetBackground;
|
|
1253
|
-
if (data.widgetTextColor)
|
|
1254
|
-
if (data.primaryColor)
|
|
1255
|
-
if (data.buttonTextColor)
|
|
1256
|
-
if (data.widgetCardColor)
|
|
1320
|
+
if (data.widgetBackground || cfg.widgetBackground) apiColors.background = data.widgetBackground || cfg.widgetBackground;
|
|
1321
|
+
if (data.widgetTextColor || cfg.widgetTextColor) apiColors.text = data.widgetTextColor || cfg.widgetTextColor;
|
|
1322
|
+
if (data.primaryColor || cfg.primaryColor) apiColors.primary = data.primaryColor || cfg.primaryColor;
|
|
1323
|
+
if (data.buttonTextColor || cfg.buttonTextColor) apiColors.primaryText = data.buttonTextColor || cfg.buttonTextColor;
|
|
1324
|
+
if (data.widgetCardColor || cfg.widgetCardColor) apiColors.card = data.widgetCardColor || cfg.widgetCardColor;
|
|
1257
1325
|
apiColors._posthogKey = ((data.posthogKey || data.POSTHOG_KEY) ?? '').trim();
|
|
1326
|
+
apiColors._s3BaseUrl = String(
|
|
1327
|
+
data.VITE_AWS_S3_PATH
|
|
1328
|
+
|| data.vite_aws_s3_path
|
|
1329
|
+
|| data.s3BaseUrl
|
|
1330
|
+
|| data.s3_base_url
|
|
1331
|
+
|| data.CONFIG?.VITE_AWS_S3_PATH
|
|
1332
|
+
|| ''
|
|
1333
|
+
).trim();
|
|
1334
|
+
apiColors._stripePublishableKey = String(
|
|
1335
|
+
data.STRIPE_PUBLISHABLE_KEY
|
|
1336
|
+
|| data.STRIPE_PUBLICED_KEY
|
|
1337
|
+
|| data.stripePublishableKey
|
|
1338
|
+
|| data.stripe_publishable_key
|
|
1339
|
+
|| data.CONFIG?.STRIPE_PUBLISHABLE_KEY
|
|
1340
|
+
|| data.CONFIG?.STRIPE_PUBLICED_KEY
|
|
1341
|
+
|| data.CONFIG?.stripePublishableKey
|
|
1342
|
+
|| ''
|
|
1343
|
+
).trim();
|
|
1258
1344
|
__bwConfigCache[cacheKey] = apiColors;
|
|
1259
1345
|
self._applyApiColors(apiColors);
|
|
1346
|
+
self._applyApiS3BaseUrl(apiColors._s3BaseUrl);
|
|
1347
|
+
self._applyApiStripeKey(apiColors._stripePublishableKey);
|
|
1260
1348
|
self._configState = 'loaded';
|
|
1261
1349
|
self.applyColors();
|
|
1262
1350
|
self._initPosthog(apiColors._posthogKey);
|
package/dist/booking-widget.css
CHANGED
|
@@ -1073,7 +1073,6 @@
|
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
.booking-widget-modal .room-grid .room-card-price strong {
|
|
1076
|
-
font-family: var(--font-serif);
|
|
1077
1076
|
color: var(--primary);
|
|
1078
1077
|
font-size: 1.15em;
|
|
1079
1078
|
font-weight: 600;
|
|
@@ -1375,7 +1374,7 @@
|
|
|
1375
1374
|
}
|
|
1376
1375
|
|
|
1377
1376
|
.booking-widget-modal .rate-price strong {
|
|
1378
|
-
font-family: var(--font-serif);
|
|
1377
|
+
/* font-family: var(--font-serif); */
|
|
1379
1378
|
/* color: var(--primary); */
|
|
1380
1379
|
font-size: 1.25em;
|
|
1381
1380
|
}
|
|
@@ -1601,7 +1600,7 @@
|
|
|
1601
1600
|
}
|
|
1602
1601
|
|
|
1603
1602
|
.booking-widget-modal .payment-step-summary .payment-total-amount {
|
|
1604
|
-
font-family: var(--font-serif);
|
|
1603
|
+
/* font-family: var(--font-serif); */
|
|
1605
1604
|
font-size: 1.75em;
|
|
1606
1605
|
font-weight: 600;
|
|
1607
1606
|
color: var(--primary);
|
|
@@ -1646,7 +1645,7 @@
|
|
|
1646
1645
|
}
|
|
1647
1646
|
|
|
1648
1647
|
.booking-widget-modal .summary-total-price {
|
|
1649
|
-
font-family: var(--font-
|
|
1648
|
+
font-family: var(--font-sans);
|
|
1650
1649
|
font-size: 1.5em;
|
|
1651
1650
|
color: var(--primary);
|
|
1652
1651
|
}
|
package/dist/booking-widget.js
CHANGED
|
@@ -208,14 +208,13 @@ class BookingWidget {
|
|
|
208
208
|
guest: { firstName: '', lastName: '', email: '', phone: '', specialRequests: '' },
|
|
209
209
|
};
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
this.baseSteps = [
|
|
212
212
|
{ key: 'dates', label: 'Dates & Guests', num: '01' },
|
|
213
213
|
{ key: 'rooms', label: 'Room', num: '02' },
|
|
214
214
|
{ key: 'rates', label: 'Rate', num: '03' },
|
|
215
215
|
{ key: 'summary', label: 'Summary', num: '04' },
|
|
216
216
|
];
|
|
217
|
-
this.
|
|
218
|
-
this.STEPS = this.hasStripe ? [...baseSteps, { key: 'payment', label: 'Payment', num: '05' }] : baseSteps;
|
|
217
|
+
this._recomputeSteps();
|
|
219
218
|
|
|
220
219
|
this.ROOMS = [];
|
|
221
220
|
this.RATES = [];
|
|
@@ -489,6 +488,39 @@ class BookingWidget {
|
|
|
489
488
|
};
|
|
490
489
|
}
|
|
491
490
|
|
|
491
|
+
_applyApiS3BaseUrl(value) {
|
|
492
|
+
const s3 = (typeof value === 'string' ? value.trim() : '').replace(/\/$/, '');
|
|
493
|
+
if (!s3) return;
|
|
494
|
+
if (!this.options.s3BaseUrl) this.options.s3BaseUrl = s3;
|
|
495
|
+
if (this.options.bookingApi || typeof window.createBookingApi !== 'function') return;
|
|
496
|
+
const defaultApiBase = (typeof window !== 'undefined' && window.__BOOKING_WIDGET_API_BASE_URL__) ? window.__BOOKING_WIDGET_API_BASE_URL__ : '';
|
|
497
|
+
const opts = this.options;
|
|
498
|
+
this.bookingApi = window.createBookingApi({
|
|
499
|
+
baseUrl: opts.apiBaseUrl || opts.availabilityBaseUrl || defaultApiBase || '',
|
|
500
|
+
availabilityBaseUrl: opts.availabilityBaseUrl || defaultApiBase || undefined,
|
|
501
|
+
propertyBaseUrl: opts.propertyBaseUrl || undefined,
|
|
502
|
+
s3BaseUrl: opts.s3BaseUrl || undefined,
|
|
503
|
+
propertyId: opts.propertyId != null && opts.propertyId !== '' ? String(opts.propertyId) : undefined,
|
|
504
|
+
propertyKey: opts.propertyKey || undefined,
|
|
505
|
+
mode: opts.mode === 'sandbox' ? 'sandbox' : undefined,
|
|
506
|
+
headers: opts.apiSecret ? { 'X-API-Key': opts.apiSecret } : undefined,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
_applyApiStripeKey(value) {
|
|
511
|
+
const key = (typeof value === 'string' ? value.trim() : '');
|
|
512
|
+
if (!key || this.options.stripePublishableKey) return;
|
|
513
|
+
this.options.stripePublishableKey = key;
|
|
514
|
+
this._recomputeSteps();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
_recomputeSteps() {
|
|
518
|
+
this.hasStripe = !!(this.options.stripePublishableKey && typeof this.options.createPaymentIntent === 'function');
|
|
519
|
+
this.STEPS = this.hasStripe
|
|
520
|
+
? [...this.baseSteps, { key: 'payment', label: 'Payment', num: '05' }]
|
|
521
|
+
: [...this.baseSteps];
|
|
522
|
+
}
|
|
523
|
+
|
|
492
524
|
/**
|
|
493
525
|
* Fetch runtime styling config from /load-config.
|
|
494
526
|
* Caches result in __bwConfigCache (module-level, shared across instances).
|
|
@@ -506,6 +538,8 @@ class BookingWidget {
|
|
|
506
538
|
if (__bwConfigCache[cacheKey]) {
|
|
507
539
|
const cached = __bwConfigCache[cacheKey];
|
|
508
540
|
this._applyApiColors(cached);
|
|
541
|
+
this._applyApiS3BaseUrl(cached._s3BaseUrl);
|
|
542
|
+
this._applyApiStripeKey(cached._stripePublishableKey);
|
|
509
543
|
this._configState = 'loaded';
|
|
510
544
|
this.applyColors();
|
|
511
545
|
this._initPosthog(cached._posthogKey || '');
|
|
@@ -522,15 +556,36 @@ class BookingWidget {
|
|
|
522
556
|
return res.json();
|
|
523
557
|
})
|
|
524
558
|
.then(function (data) {
|
|
559
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
525
560
|
const apiColors = {};
|
|
526
|
-
if (data.widgetBackground) apiColors.background = data.widgetBackground;
|
|
527
|
-
if (data.widgetTextColor)
|
|
528
|
-
if (data.primaryColor)
|
|
529
|
-
if (data.buttonTextColor)
|
|
530
|
-
if (data.widgetCardColor)
|
|
561
|
+
if (data.widgetBackground || cfg.widgetBackground) apiColors.background = data.widgetBackground || cfg.widgetBackground;
|
|
562
|
+
if (data.widgetTextColor || cfg.widgetTextColor) apiColors.text = data.widgetTextColor || cfg.widgetTextColor;
|
|
563
|
+
if (data.primaryColor || cfg.primaryColor) apiColors.primary = data.primaryColor || cfg.primaryColor;
|
|
564
|
+
if (data.buttonTextColor || cfg.buttonTextColor) apiColors.primaryText = data.buttonTextColor || cfg.buttonTextColor;
|
|
565
|
+
if (data.widgetCardColor || cfg.widgetCardColor) apiColors.card = data.widgetCardColor || cfg.widgetCardColor;
|
|
531
566
|
apiColors._posthogKey = ((data.posthogKey || data.POSTHOG_KEY) ?? '').trim();
|
|
567
|
+
apiColors._s3BaseUrl = String(
|
|
568
|
+
data.VITE_AWS_S3_PATH
|
|
569
|
+
|| data.vite_aws_s3_path
|
|
570
|
+
|| data.s3BaseUrl
|
|
571
|
+
|| data.s3_base_url
|
|
572
|
+
|| data.CONFIG?.VITE_AWS_S3_PATH
|
|
573
|
+
|| ''
|
|
574
|
+
).trim();
|
|
575
|
+
apiColors._stripePublishableKey = String(
|
|
576
|
+
data.STRIPE_PUBLISHABLE_KEY
|
|
577
|
+
|| data.STRIPE_PUBLICED_KEY
|
|
578
|
+
|| data.stripePublishableKey
|
|
579
|
+
|| data.stripe_publishable_key
|
|
580
|
+
|| data.CONFIG?.STRIPE_PUBLISHABLE_KEY
|
|
581
|
+
|| data.CONFIG?.STRIPE_PUBLICED_KEY
|
|
582
|
+
|| data.CONFIG?.stripePublishableKey
|
|
583
|
+
|| ''
|
|
584
|
+
).trim();
|
|
532
585
|
__bwConfigCache[cacheKey] = apiColors;
|
|
533
586
|
self._applyApiColors(apiColors);
|
|
587
|
+
self._applyApiS3BaseUrl(apiColors._s3BaseUrl);
|
|
588
|
+
self._applyApiStripeKey(apiColors._stripePublishableKey);
|
|
534
589
|
self._configState = 'loaded';
|
|
535
590
|
self.applyColors();
|
|
536
591
|
self._initPosthog(apiColors._posthogKey);
|
package/dist/core/booking-api.js
CHANGED
|
@@ -286,6 +286,20 @@ function createBookingApi(config = {}) {
|
|
|
286
286
|
|
|
287
287
|
// Fetch get_properties first to get currency_code (e.g. 'EUR') and room details
|
|
288
288
|
let propertyRooms = {};
|
|
289
|
+
const normalizeRoomsById = (rooms) => {
|
|
290
|
+
if (!rooms) return {};
|
|
291
|
+
if (Array.isArray(rooms)) {
|
|
292
|
+
return rooms.reduce((acc, room) => {
|
|
293
|
+
if (!room || typeof room !== 'object') return acc;
|
|
294
|
+
const key = room.id ?? room.room_id;
|
|
295
|
+
if (key == null) return acc;
|
|
296
|
+
acc[String(key)] = room;
|
|
297
|
+
return acc;
|
|
298
|
+
}, {});
|
|
299
|
+
}
|
|
300
|
+
if (typeof rooms === 'object') return rooms;
|
|
301
|
+
return {};
|
|
302
|
+
};
|
|
289
303
|
let propertyCurrency = currency;
|
|
290
304
|
const propQuery = propertyKey
|
|
291
305
|
? `key=${encodeURIComponent(propertyKey)}${sandbox ? '&sandbox=true' : ''}`
|
|
@@ -301,7 +315,7 @@ function createBookingApi(config = {}) {
|
|
|
301
315
|
const propRes = await _fetch(propFullUrl, getOpts);
|
|
302
316
|
if (propRes.ok) {
|
|
303
317
|
const propData = await propRes.json();
|
|
304
|
-
propertyRooms = propData?.rooms
|
|
318
|
+
propertyRooms = normalizeRoomsById(propData?.rooms);
|
|
305
319
|
propertyCurrency = (typeof propData?.currency_code === 'string' && propData.currency_code.trim())
|
|
306
320
|
? propData.currency_code.trim()
|
|
307
321
|
: currency;
|
|
@@ -360,18 +374,41 @@ function createBookingApi(config = {}) {
|
|
|
360
374
|
return (a?.price ?? a?.total ?? r?.price ?? 0) || 0;
|
|
361
375
|
};
|
|
362
376
|
const fallbackImage = 'https://images.unsplash.com/photo-1618773928121-c32242e63f39?w=800&q=80';
|
|
377
|
+
const toImageUrl = (value) => {
|
|
378
|
+
if (typeof value !== 'string') return '';
|
|
379
|
+
const v = value.trim();
|
|
380
|
+
if (!v) return '';
|
|
381
|
+
if (/^https?:\/\//i.test(v)) return v;
|
|
382
|
+
if (v.startsWith('//')) return `https:${v}`;
|
|
383
|
+
if (s3BaseUrl) return `${s3BaseUrl}/${v.replace(/^\//, '')}`;
|
|
384
|
+
return '';
|
|
385
|
+
};
|
|
363
386
|
|
|
364
387
|
return availableRoomIds.map((roomId) => {
|
|
365
|
-
const roomData = propertyRooms[roomId] ?? propertyRooms[
|
|
388
|
+
const roomData = propertyRooms[String(roomId)] ?? propertyRooms[roomId] ?? {};
|
|
366
389
|
const roomRates = filteredRates.filter((r) => String(r.room_id) === String(roomId));
|
|
367
390
|
const minPrice = roomRates.length
|
|
368
391
|
? Math.min(...roomRates.map(getPrice).filter((p) => p > 0)) || roomData.base_price || 0
|
|
369
392
|
: roomData.base_price || 0;
|
|
370
393
|
|
|
371
|
-
const photos = roomData.photos
|
|
372
|
-
const mainPhoto = photos.find((p) => p
|
|
373
|
-
const
|
|
374
|
-
const
|
|
394
|
+
const photos = Array.isArray(roomData.photos) ? roomData.photos : [];
|
|
395
|
+
const mainPhoto = photos.find((p) => (typeof p === 'object' && p?.main)) ?? photos[0] ?? {};
|
|
396
|
+
const photoValue = typeof mainPhoto === 'string' ? mainPhoto : '';
|
|
397
|
+
const imageCandidates = [
|
|
398
|
+
photoValue,
|
|
399
|
+
mainPhoto.path,
|
|
400
|
+
mainPhoto.url,
|
|
401
|
+
mainPhoto.src,
|
|
402
|
+
mainPhoto.image,
|
|
403
|
+
mainPhoto.image_url,
|
|
404
|
+
mainPhoto.secure_url,
|
|
405
|
+
roomData.image,
|
|
406
|
+
roomData.image_url,
|
|
407
|
+
roomData.imageUrl,
|
|
408
|
+
roomData.thumbnail,
|
|
409
|
+
roomData.thumbnail_url,
|
|
410
|
+
];
|
|
411
|
+
const image = imageCandidates.map(toImageUrl).find(Boolean) || fallbackImage;
|
|
375
412
|
|
|
376
413
|
const sizeVal = roomData.size?.value ?? roomData.size_value;
|
|
377
414
|
let sizeUnit = roomData.size?.unit ?? roomData.size_unit ?? 'm²';
|
package/dist/core/styles.css
CHANGED
|
@@ -1073,7 +1073,6 @@
|
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
.booking-widget-modal .room-grid .room-card-price strong {
|
|
1076
|
-
font-family: var(--font-serif);
|
|
1077
1076
|
color: var(--primary);
|
|
1078
1077
|
font-size: 1.15em;
|
|
1079
1078
|
font-weight: 600;
|
|
@@ -1375,7 +1374,7 @@
|
|
|
1375
1374
|
}
|
|
1376
1375
|
|
|
1377
1376
|
.booking-widget-modal .rate-price strong {
|
|
1378
|
-
font-family: var(--font-serif);
|
|
1377
|
+
/* font-family: var(--font-serif); */
|
|
1379
1378
|
/* color: var(--primary); */
|
|
1380
1379
|
font-size: 1.25em;
|
|
1381
1380
|
}
|
|
@@ -1601,7 +1600,7 @@
|
|
|
1601
1600
|
}
|
|
1602
1601
|
|
|
1603
1602
|
.booking-widget-modal .payment-step-summary .payment-total-amount {
|
|
1604
|
-
font-family: var(--font-serif);
|
|
1603
|
+
/* font-family: var(--font-serif); */
|
|
1605
1604
|
font-size: 1.75em;
|
|
1606
1605
|
font-weight: 600;
|
|
1607
1606
|
color: var(--primary);
|
|
@@ -1646,7 +1645,7 @@
|
|
|
1646
1645
|
}
|
|
1647
1646
|
|
|
1648
1647
|
.booking-widget-modal .summary-total-price {
|
|
1649
|
-
font-family: var(--font-
|
|
1648
|
+
font-family: var(--font-sans);
|
|
1650
1649
|
font-size: 1.5em;
|
|
1651
1650
|
color: var(--primary);
|
|
1652
1651
|
}
|
|
@@ -56,9 +56,11 @@ const BookingWidget = ({
|
|
|
56
56
|
/** PostHog: optional override for API host (defaults to VITE_POSTHOG_HOST). */
|
|
57
57
|
posthogHost = '',
|
|
58
58
|
}) => {
|
|
59
|
-
const
|
|
59
|
+
const [runtimeStripePublishableKey, setRuntimeStripePublishableKey] = useState('');
|
|
60
|
+
const [runtimeApiBaseUrl, setRuntimeApiBaseUrl] = useState('');
|
|
61
|
+
const stripePublishableKey = stripePublishableKeyProp || runtimeStripePublishableKey || STRIPE_PUBLISHABLE_KEY;
|
|
60
62
|
const env = typeof import.meta !== 'undefined' && import.meta.env ? import.meta.env : {};
|
|
61
|
-
const apiBase = (env.VITE_API_BASE_URL || API_BASE_URL || '').replace(/\/$/, '');
|
|
63
|
+
const apiBase = (runtimeApiBaseUrl || env.VITE_API_BASE_URL || API_BASE_URL || '').replace(/\/$/, '');
|
|
62
64
|
const effectivePaymentIntentUrl = (apiBase ? apiBase + '/proxy/create-payment-intent' : '').trim();
|
|
63
65
|
const isSandbox = mode === 'sandbox';
|
|
64
66
|
const hasPropertyKey = !!(propertyKey && String(propertyKey).trim());
|
|
@@ -113,6 +115,7 @@ const BookingWidget = ({
|
|
|
113
115
|
const [configLoaded, setConfigLoaded] = useState(false);
|
|
114
116
|
const [configError, setConfigError] = useState(null);
|
|
115
117
|
const [runtimeWidgetStyles, setRuntimeWidgetStyles] = useState({});
|
|
118
|
+
const [runtimeS3BaseUrl, setRuntimeS3BaseUrl] = useState('');
|
|
116
119
|
const [configRetryCount, setConfigRetryCount] = useState(0);
|
|
117
120
|
const widgetRef = useRef(null);
|
|
118
121
|
const stripeRef = useRef(null);
|
|
@@ -121,11 +124,11 @@ const BookingWidget = ({
|
|
|
121
124
|
|
|
122
125
|
// Use package .env when props not passed (e.g. examples with envDir pointing at root)
|
|
123
126
|
const effectiveApiBaseUrl = apiBaseUrl || env.VITE_API_URL || '';
|
|
124
|
-
const effectiveConfirmationBaseUrl = (env.VITE_API_BASE_URL || API_BASE_URL || 'https://ai.thehotelplanet.com').replace(/\/$/, '');
|
|
127
|
+
const effectiveConfirmationBaseUrl = (runtimeApiBaseUrl || env.VITE_API_BASE_URL || API_BASE_URL || 'https://ai.thehotelplanet.com').replace(/\/$/, '');
|
|
125
128
|
const apiBaseForAvailability = (env.VITE_API_BASE_URL || API_BASE_URL || '').replace(/\/$/, '');
|
|
126
129
|
const effectiveAvailabilityBaseUrl = availabilityBaseUrl !== '' ? availabilityBaseUrl : apiBaseForAvailability;
|
|
127
130
|
const effectivePropertyBaseUrl = propertyBaseUrl || env.VITE_PROPERTY_BASE_URL || '';
|
|
128
|
-
const effectiveS3BaseUrl = s3BaseUrl || env.VITE_AWS_S3_PATH || '';
|
|
131
|
+
const effectiveS3BaseUrl = s3BaseUrl || runtimeS3BaseUrl || env.VITE_AWS_S3_PATH || '';
|
|
129
132
|
|
|
130
133
|
const bookingApiRef = useMemo(() => {
|
|
131
134
|
if (bookingApiProp && typeof bookingApiProp.fetchRooms === 'function') return bookingApiProp;
|
|
@@ -276,6 +279,9 @@ const BookingWidget = ({
|
|
|
276
279
|
setConfigLoading(false);
|
|
277
280
|
setConfigLoaded(false);
|
|
278
281
|
setRuntimeWidgetStyles({});
|
|
282
|
+
setRuntimeS3BaseUrl('');
|
|
283
|
+
setRuntimeStripePublishableKey('');
|
|
284
|
+
setRuntimeApiBaseUrl('');
|
|
279
285
|
return;
|
|
280
286
|
}
|
|
281
287
|
let cancelled = false;
|
|
@@ -283,17 +289,23 @@ const BookingWidget = ({
|
|
|
283
289
|
setConfigError(null);
|
|
284
290
|
setConfigLoaded(false);
|
|
285
291
|
fetchRuntimeConfig(propertyKey, colors, mode)
|
|
286
|
-
.then(({ widgetStyles, posthogKey: configPosthogKey }) => {
|
|
292
|
+
.then(({ widgetStyles, posthogKey: configPosthogKey, s3BaseUrl: configS3BaseUrl, stripePublishableKey: configStripeKey, apiBaseUrl: configApiBaseUrl }) => {
|
|
287
293
|
if (cancelled) return;
|
|
288
294
|
const analyticsKey = posthogKey || configPosthogKey || '';
|
|
289
295
|
initAnalytics(analyticsKey || posthogHost ? { key: analyticsKey || undefined, host: posthogHost || undefined } : {});
|
|
290
296
|
setRuntimeWidgetStyles(widgetStyles);
|
|
297
|
+
setRuntimeS3BaseUrl(configS3BaseUrl || '');
|
|
298
|
+
setRuntimeStripePublishableKey(configStripeKey || '');
|
|
299
|
+
setRuntimeApiBaseUrl(configApiBaseUrl || '');
|
|
291
300
|
setConfigLoaded(true);
|
|
292
301
|
setConfigLoading(false);
|
|
293
302
|
})
|
|
294
303
|
.catch((err) => {
|
|
295
304
|
if (cancelled) return;
|
|
296
305
|
setConfigError(err?.message || 'Failed to load widget configuration.');
|
|
306
|
+
setRuntimeS3BaseUrl('');
|
|
307
|
+
setRuntimeStripePublishableKey('');
|
|
308
|
+
setRuntimeApiBaseUrl('');
|
|
297
309
|
setConfigLoading(false);
|
|
298
310
|
});
|
|
299
311
|
return () => { cancelled = true; };
|
package/dist/react/styles.css
CHANGED
|
@@ -1073,7 +1073,6 @@
|
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
.booking-widget-modal .room-grid .room-card-price strong {
|
|
1076
|
-
font-family: var(--font-serif);
|
|
1077
1076
|
color: var(--primary);
|
|
1078
1077
|
font-size: 1.15em;
|
|
1079
1078
|
font-weight: 600;
|
|
@@ -1375,7 +1374,7 @@
|
|
|
1375
1374
|
}
|
|
1376
1375
|
|
|
1377
1376
|
.booking-widget-modal .rate-price strong {
|
|
1378
|
-
font-family: var(--font-serif);
|
|
1377
|
+
/* font-family: var(--font-serif); */
|
|
1379
1378
|
/* color: var(--primary); */
|
|
1380
1379
|
font-size: 1.25em;
|
|
1381
1380
|
}
|
|
@@ -1601,7 +1600,7 @@
|
|
|
1601
1600
|
}
|
|
1602
1601
|
|
|
1603
1602
|
.booking-widget-modal .payment-step-summary .payment-total-amount {
|
|
1604
|
-
font-family: var(--font-serif);
|
|
1603
|
+
/* font-family: var(--font-serif); */
|
|
1605
1604
|
font-size: 1.75em;
|
|
1606
1605
|
font-weight: 600;
|
|
1607
1606
|
color: var(--primary);
|
|
@@ -1646,7 +1645,7 @@
|
|
|
1646
1645
|
}
|
|
1647
1646
|
|
|
1648
1647
|
.booking-widget-modal .summary-total-price {
|
|
1649
|
-
font-family: var(--font-
|
|
1648
|
+
font-family: var(--font-sans);
|
|
1650
1649
|
font-size: 1.5em;
|
|
1651
1650
|
color: var(--primary);
|
|
1652
1651
|
}
|
|
@@ -16,9 +16,47 @@ const _nativeFetch = typeof fetch === 'function' ? fetch : /* istanbul ignore ne
|
|
|
16
16
|
|
|
17
17
|
const CONFIG_BASE_URL = 'https://ai.thehotelplanet.com/load-config';
|
|
18
18
|
|
|
19
|
-
/** In-memory cache: propertyKey → { apiColors, posthogKey } */
|
|
19
|
+
/** In-memory cache: propertyKey → { apiColors, posthogKey, s3BaseUrl, stripePublishableKey } */
|
|
20
20
|
const _configCache = new Map();
|
|
21
21
|
|
|
22
|
+
function extractS3BaseUrl(data) {
|
|
23
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
24
|
+
return String(
|
|
25
|
+
data?.VITE_AWS_S3_PATH
|
|
26
|
+
|| data?.vite_aws_s3_path
|
|
27
|
+
|| data?.s3BaseUrl
|
|
28
|
+
|| data?.s3_base_url
|
|
29
|
+
|| cfg?.VITE_AWS_S3_PATH
|
|
30
|
+
|| ''
|
|
31
|
+
).trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function extractStripePublishableKey(data) {
|
|
35
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
36
|
+
return String(
|
|
37
|
+
data?.STRIPE_PUBLISHABLE_KEY
|
|
38
|
+
|| data?.STRIPE_PUBLICED_KEY
|
|
39
|
+
|| data?.stripePublishableKey
|
|
40
|
+
|| data?.stripe_publishable_key
|
|
41
|
+
|| cfg?.STRIPE_PUBLISHABLE_KEY
|
|
42
|
+
|| cfg?.STRIPE_PUBLICED_KEY
|
|
43
|
+
|| cfg?.stripePublishableKey
|
|
44
|
+
|| ''
|
|
45
|
+
).trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extractApiBaseUrl(data) {
|
|
49
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
50
|
+
return String(
|
|
51
|
+
data?.API_BASE_URL
|
|
52
|
+
|| data?.apiBaseUrl
|
|
53
|
+
|| data?.api_base_url
|
|
54
|
+
|| cfg?.API_BASE_URL
|
|
55
|
+
|| cfg?.apiBaseUrl
|
|
56
|
+
|| ''
|
|
57
|
+
).trim().replace(/\/$/, '');
|
|
58
|
+
}
|
|
59
|
+
|
|
22
60
|
/**
|
|
23
61
|
* Map the /load-config response field names to the internal color keys used
|
|
24
62
|
* throughout the widget.
|
|
@@ -31,12 +69,13 @@ const _configCache = new Map();
|
|
|
31
69
|
* widgetCardColor → card
|
|
32
70
|
*/
|
|
33
71
|
function mapApiColors(data) {
|
|
72
|
+
const cfg = (data && typeof data.CONFIG === 'object' && data.CONFIG) ? data.CONFIG : {};
|
|
34
73
|
const mapped = {};
|
|
35
|
-
if (data.widgetBackground) mapped.background = data.widgetBackground;
|
|
36
|
-
if (data.widgetTextColor)
|
|
37
|
-
if (data.primaryColor)
|
|
38
|
-
if (data.buttonTextColor)
|
|
39
|
-
if (data.widgetCardColor)
|
|
74
|
+
if (data.widgetBackground || cfg.widgetBackground) mapped.background = data.widgetBackground || cfg.widgetBackground;
|
|
75
|
+
if (data.widgetTextColor || cfg.widgetTextColor) mapped.text = data.widgetTextColor || cfg.widgetTextColor;
|
|
76
|
+
if (data.primaryColor || cfg.primaryColor) mapped.primary = data.primaryColor || cfg.primaryColor;
|
|
77
|
+
if (data.buttonTextColor || cfg.buttonTextColor) mapped.primaryText = data.buttonTextColor || cfg.buttonTextColor;
|
|
78
|
+
if (data.widgetCardColor || cfg.widgetCardColor) mapped.card = data.widgetCardColor || cfg.widgetCardColor;
|
|
40
79
|
return mapped;
|
|
41
80
|
}
|
|
42
81
|
|
|
@@ -74,7 +113,7 @@ export function mergeColors(mappedApiColors, installerColors) {
|
|
|
74
113
|
* @param {string} propertyKey - The hotel property key / API key.
|
|
75
114
|
* @param {object|null} installerColors - Optional installer color overrides.
|
|
76
115
|
* @param {string} [mode] - Pass 'sandbox' to append &mode=sandbox to the request.
|
|
77
|
-
* @returns {Promise<{ apiColors: object, colors: object, widgetStyles: object }>}
|
|
116
|
+
* @returns {Promise<{ apiColors: object, colors: object, widgetStyles: object, posthogKey: string, s3BaseUrl: string, stripePublishableKey: string, apiBaseUrl: string }>}
|
|
78
117
|
*/
|
|
79
118
|
export async function fetchRuntimeConfig(propertyKey, installerColors = null, mode = null) {
|
|
80
119
|
if (!propertyKey || !String(propertyKey).trim()) {
|
|
@@ -87,9 +126,9 @@ export async function fetchRuntimeConfig(propertyKey, installerColors = null, mo
|
|
|
87
126
|
const cacheKey = isSandbox ? `${key}:sandbox` : key;
|
|
88
127
|
|
|
89
128
|
if (_configCache.has(cacheKey)) {
|
|
90
|
-
const { apiColors, posthogKey } = _configCache.get(cacheKey);
|
|
129
|
+
const { apiColors, posthogKey, s3BaseUrl, stripePublishableKey, apiBaseUrl } = _configCache.get(cacheKey);
|
|
91
130
|
const colors = mergeColors(apiColors, installerColors);
|
|
92
|
-
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors), posthogKey };
|
|
131
|
+
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors), posthogKey, s3BaseUrl, stripePublishableKey, apiBaseUrl };
|
|
93
132
|
}
|
|
94
133
|
|
|
95
134
|
const url = `${CONFIG_BASE_URL}?apikey=${encodeURIComponent(key)}${isSandbox ? '&mode=sandbox' : ''}`;
|
|
@@ -101,8 +140,11 @@ export async function fetchRuntimeConfig(propertyKey, installerColors = null, mo
|
|
|
101
140
|
const data = await res.json();
|
|
102
141
|
const apiColors = mapApiColors(data);
|
|
103
142
|
const posthogKey = ((data.posthogKey || data.POSTHOG_KEY) ?? '').trim();
|
|
104
|
-
|
|
143
|
+
const s3BaseUrl = extractS3BaseUrl(data);
|
|
144
|
+
const stripePublishableKey = extractStripePublishableKey(data);
|
|
145
|
+
const apiBaseUrl = extractApiBaseUrl(data);
|
|
146
|
+
_configCache.set(cacheKey, { apiColors, posthogKey, s3BaseUrl, stripePublishableKey, apiBaseUrl });
|
|
105
147
|
|
|
106
148
|
const colors = mergeColors(apiColors, installerColors);
|
|
107
|
-
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors), posthogKey };
|
|
149
|
+
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors), posthogKey, s3BaseUrl, stripePublishableKey, apiBaseUrl };
|
|
108
150
|
}
|
|
@@ -549,6 +549,8 @@ export default {
|
|
|
549
549
|
configLoaded: false,
|
|
550
550
|
configError: null,
|
|
551
551
|
runtimeWidgetStyles: {},
|
|
552
|
+
runtimeS3BaseUrl: '',
|
|
553
|
+
runtimeStripePublishableKey: '',
|
|
552
554
|
calendarMonth: new Date().getMonth(),
|
|
553
555
|
calendarYear: new Date().getFullYear(),
|
|
554
556
|
pickState: 0,
|
|
@@ -590,7 +592,10 @@ export default {
|
|
|
590
592
|
return this.propertyBaseUrl || (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_PROPERTY_BASE_URL) || '';
|
|
591
593
|
},
|
|
592
594
|
effectiveS3BaseUrl() {
|
|
593
|
-
return this.s3BaseUrl || (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_AWS_S3_PATH) || '';
|
|
595
|
+
return this.s3BaseUrl || this.runtimeS3BaseUrl || (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.VITE_AWS_S3_PATH) || '';
|
|
596
|
+
},
|
|
597
|
+
effectiveStripePublishableKey() {
|
|
598
|
+
return this.stripePublishableKey || this.runtimeStripePublishableKey || '';
|
|
594
599
|
},
|
|
595
600
|
effectivePaymentIntentUrl() {
|
|
596
601
|
const env = typeof import.meta !== 'undefined' && import.meta.env ? import.meta.env : {};
|
|
@@ -603,7 +608,7 @@ export default {
|
|
|
603
608
|
return String(base).replace(/\/$/, '');
|
|
604
609
|
},
|
|
605
610
|
hasStripe() {
|
|
606
|
-
return !!(this.
|
|
611
|
+
return !!(this.effectiveStripePublishableKey && typeof this.effectiveCreatePaymentIntent === 'function');
|
|
607
612
|
},
|
|
608
613
|
STEPS() {
|
|
609
614
|
return this.hasStripe ? [...BASE_STEPS, { key: 'payment', label: 'Payment', num: '05' }] : BASE_STEPS;
|
|
@@ -805,19 +810,25 @@ export default {
|
|
|
805
810
|
this.configLoading = false;
|
|
806
811
|
this.configLoaded = false;
|
|
807
812
|
this.runtimeWidgetStyles = {};
|
|
813
|
+
this.runtimeS3BaseUrl = '';
|
|
814
|
+
this.runtimeStripePublishableKey = '';
|
|
808
815
|
return;
|
|
809
816
|
}
|
|
810
817
|
this.configLoading = true;
|
|
811
818
|
this.configError = null;
|
|
812
819
|
this.configLoaded = false;
|
|
813
820
|
try {
|
|
814
|
-
const { widgetStyles, posthogKey: configPosthogKey } = await fetchRuntimeConfig(this.propertyKey, this.colors, this.mode);
|
|
821
|
+
const { widgetStyles, posthogKey: configPosthogKey, s3BaseUrl: configS3BaseUrl, stripePublishableKey: configStripeKey } = await fetchRuntimeConfig(this.propertyKey, this.colors, this.mode);
|
|
815
822
|
const analyticsKey = this.posthogKey || configPosthogKey || '';
|
|
816
823
|
initAnalytics(analyticsKey || this.posthogHost ? { key: analyticsKey || undefined, host: this.posthogHost || undefined } : {});
|
|
817
824
|
this.runtimeWidgetStyles = widgetStyles;
|
|
825
|
+
this.runtimeS3BaseUrl = configS3BaseUrl || '';
|
|
826
|
+
this.runtimeStripePublishableKey = configStripeKey || '';
|
|
818
827
|
this.configLoaded = true;
|
|
819
828
|
} catch (err) {
|
|
820
829
|
this.configError = err?.message || 'Failed to load widget configuration.';
|
|
830
|
+
this.runtimeS3BaseUrl = '';
|
|
831
|
+
this.runtimeStripePublishableKey = '';
|
|
821
832
|
} finally {
|
|
822
833
|
this.configLoading = false;
|
|
823
834
|
}
|
|
@@ -866,7 +877,7 @@ export default {
|
|
|
866
877
|
pollOnce();
|
|
867
878
|
},
|
|
868
879
|
async loadStripePaymentElement() {
|
|
869
|
-
if (this.state.step !== 'payment' || !this.checkoutShowPaymentForm || !this.
|
|
880
|
+
if (this.state.step !== 'payment' || !this.checkoutShowPaymentForm || !this.effectiveStripePublishableKey || typeof this.effectiveCreatePaymentIntent !== 'function') return;
|
|
870
881
|
const paymentIntentPayload = buildPaymentIntentPayload(this.state, { propertyKey: this.propertyKey || undefined, sandbox: this.isSandbox });
|
|
871
882
|
this.paymentElementReady = false;
|
|
872
883
|
try {
|
|
@@ -878,7 +889,7 @@ export default {
|
|
|
878
889
|
this.apiError = 'Payment setup failed: no client secret returned';
|
|
879
890
|
return;
|
|
880
891
|
}
|
|
881
|
-
const stripe = await loadStripe(this.
|
|
892
|
+
const stripe = await loadStripe(this.effectiveStripePublishableKey);
|
|
882
893
|
if (!stripe || this.state.step !== 'payment') return;
|
|
883
894
|
this.stripeInstance = stripe;
|
|
884
895
|
const elements = stripe.elements({ clientSecret, appearance: { theme: 'flat', variables: { borderRadius: '8px' } } });
|
|
@@ -987,14 +998,14 @@ export default {
|
|
|
987
998
|
if (!this.canSubmit) return;
|
|
988
999
|
const payload = buildCheckoutPayload(this.state, { propertyId: this.propertyId != null && this.propertyId !== '' ? this.propertyId : undefined, propertyKey: this.propertyKey || undefined, sandbox: this.isSandbox });
|
|
989
1000
|
// Summary step with Stripe: go to payment step (form loads there)
|
|
990
|
-
if (this.state.step === 'summary' && this.
|
|
1001
|
+
if (this.state.step === 'summary' && this.effectiveStripePublishableKey && typeof this.effectiveCreatePaymentIntent === 'function') {
|
|
991
1002
|
this.apiError = null;
|
|
992
1003
|
this.goToStep('payment');
|
|
993
1004
|
return;
|
|
994
1005
|
}
|
|
995
1006
|
|
|
996
1007
|
// Payment step: confirm payment
|
|
997
|
-
if (this.state.step === 'payment' && this.
|
|
1008
|
+
if (this.state.step === 'payment' && this.effectiveStripePublishableKey && typeof this.effectiveCreatePaymentIntent === 'function' && this.stripeInstance && this.elementsInstance) {
|
|
998
1009
|
this.apiError = null;
|
|
999
1010
|
this.stripeInstance.confirmPayment({
|
|
1000
1011
|
elements: this.elementsInstance,
|
package/dist/vue/styles.css
CHANGED
|
@@ -1073,7 +1073,6 @@
|
|
|
1073
1073
|
}
|
|
1074
1074
|
|
|
1075
1075
|
.booking-widget-modal .room-grid .room-card-price strong {
|
|
1076
|
-
font-family: var(--font-serif);
|
|
1077
1076
|
color: var(--primary);
|
|
1078
1077
|
font-size: 1.15em;
|
|
1079
1078
|
font-weight: 600;
|
|
@@ -1375,7 +1374,7 @@
|
|
|
1375
1374
|
}
|
|
1376
1375
|
|
|
1377
1376
|
.booking-widget-modal .rate-price strong {
|
|
1378
|
-
font-family: var(--font-serif);
|
|
1377
|
+
/* font-family: var(--font-serif); */
|
|
1379
1378
|
/* color: var(--primary); */
|
|
1380
1379
|
font-size: 1.25em;
|
|
1381
1380
|
}
|
|
@@ -1601,7 +1600,7 @@
|
|
|
1601
1600
|
}
|
|
1602
1601
|
|
|
1603
1602
|
.booking-widget-modal .payment-step-summary .payment-total-amount {
|
|
1604
|
-
font-family: var(--font-serif);
|
|
1603
|
+
/* font-family: var(--font-serif); */
|
|
1605
1604
|
font-size: 1.75em;
|
|
1606
1605
|
font-weight: 600;
|
|
1607
1606
|
color: var(--primary);
|
|
@@ -1646,7 +1645,7 @@
|
|
|
1646
1645
|
}
|
|
1647
1646
|
|
|
1648
1647
|
.booking-widget-modal .summary-total-price {
|
|
1649
|
-
font-family: var(--font-
|
|
1648
|
+
font-family: var(--font-sans);
|
|
1650
1649
|
font-size: 1.5em;
|
|
1651
1650
|
color: var(--primary);
|
|
1652
1651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuitee/booking-widget",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "A beautiful, customizable booking widget modal that can be embedded in any website. Supports vanilla JavaScript, React, and Vue.js.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|