@nuitee/booking-widget 1.0.3 → 1.0.5
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 +44 -22
- package/dist/booking-widget.css +2 -1
- package/dist/booking-widget.js +40 -19
- package/dist/core/styles.css +2 -1
- package/dist/react/BookingWidget.jsx +2 -4
- package/dist/react/styles.css +2 -1
- package/dist/utils/config-service.js +13 -9
- package/dist/vue/BookingWidget.vue +4 -1
- package/dist/vue/styles.css +2 -1
- 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.5/dist/booking-widget.css">
|
|
98
|
+
<script src="https://cdn.thehotelplanet.com/booking-widget/v1.0.5/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.5/dist/booking-widget.css',
|
|
106
106
|
propertyKey: 'your-property-key',
|
|
107
107
|
onOpen: () => console.log('Opened'),
|
|
108
108
|
onClose: () => console.log('Closed'),
|
|
@@ -872,6 +872,7 @@ if (typeof window !== 'undefined') {
|
|
|
872
872
|
var styles = { '--primary': primary, '--primary-fg': primaryFg, '--bg': bg, '--fg': fg, '--card-fg': fg };
|
|
873
873
|
if (primaryRgb) styles['--primary-rgb'] = primaryRgb[0] + ', ' + primaryRgb[1] + ', ' + primaryRgb[2];
|
|
874
874
|
styles['--card'] = c.card || (bgHsl ? 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 2) + '%)' : bg);
|
|
875
|
+
styles['--card-solid'] = styles['--card'];
|
|
875
876
|
if (bgHsl) {
|
|
876
877
|
styles['--secondary'] = 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 10) + '%)';
|
|
877
878
|
styles['--border'] = 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 14) + '%)';
|
|
@@ -1189,18 +1190,21 @@ if (typeof window !== 'undefined') {
|
|
|
1189
1190
|
_fetchRuntimeConfig() {
|
|
1190
1191
|
const self = this;
|
|
1191
1192
|
const key = String(this.options.propertyKey).trim();
|
|
1193
|
+
const isSandbox = this.options.mode === 'sandbox';
|
|
1194
|
+
// Cache key is mode-aware so sandbox and live configs are stored separately.
|
|
1195
|
+
const cacheKey = isSandbox ? key + ':sandbox' : key;
|
|
1192
1196
|
|
|
1193
|
-
if (__bwConfigCache[
|
|
1194
|
-
this._applyApiColors(__bwConfigCache[
|
|
1197
|
+
if (__bwConfigCache[cacheKey]) {
|
|
1198
|
+
this._applyApiColors(__bwConfigCache[cacheKey]);
|
|
1195
1199
|
this._configState = 'loaded';
|
|
1196
1200
|
this.applyColors();
|
|
1197
|
-
return Promise.resolve(__bwConfigCache[
|
|
1201
|
+
return Promise.resolve(__bwConfigCache[cacheKey]);
|
|
1198
1202
|
}
|
|
1199
1203
|
|
|
1200
1204
|
this._configState = 'loading';
|
|
1201
1205
|
this.render();
|
|
1202
1206
|
|
|
1203
|
-
const url = 'https://ai.thehotelplanet.com/load-config?apikey=' + encodeURIComponent(key) + '&mode=sandbox';
|
|
1207
|
+
const url = 'https://ai.thehotelplanet.com/load-config?apikey=' + encodeURIComponent(key) + (isSandbox ? '&mode=sandbox' : '');
|
|
1204
1208
|
this._configPromise = fetch(url)
|
|
1205
1209
|
.then(function (res) {
|
|
1206
1210
|
if (!res.ok) throw new Error('Failed to load widget configuration (HTTP ' + res.status + ').');
|
|
@@ -1213,7 +1217,7 @@ if (typeof window !== 'undefined') {
|
|
|
1213
1217
|
if (data.primaryColor) apiColors.primary = data.primaryColor;
|
|
1214
1218
|
if (data.buttonTextColor) apiColors.primaryText = data.buttonTextColor;
|
|
1215
1219
|
if (data.widgetCardColor) apiColors.card = data.widgetCardColor;
|
|
1216
|
-
__bwConfigCache[
|
|
1220
|
+
__bwConfigCache[cacheKey] = apiColors;
|
|
1217
1221
|
self._applyApiColors(apiColors);
|
|
1218
1222
|
self._configState = 'loaded';
|
|
1219
1223
|
self.applyColors();
|
|
@@ -1259,17 +1263,24 @@ if (typeof window !== 'undefined') {
|
|
|
1259
1263
|
}
|
|
1260
1264
|
|
|
1261
1265
|
injectCSS() {
|
|
1262
|
-
if (document.getElementById('booking-widget-styles'))
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1266
|
+
if (!document.getElementById('booking-widget-styles')) {
|
|
1267
|
+
if (this.options.cssUrl) {
|
|
1268
|
+
const link = document.createElement('link');
|
|
1269
|
+
link.id = 'booking-widget-styles';
|
|
1270
|
+
link.rel = 'stylesheet';
|
|
1271
|
+
link.href = this.options.cssUrl;
|
|
1272
|
+
document.head.appendChild(link);
|
|
1273
|
+
} else {
|
|
1274
|
+
// Inline CSS would go here in a real bundle
|
|
1275
|
+
console.warn('CSS not loaded. Please include booking-widget.css or provide cssUrl option.');
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (!document.getElementById('booking-widget-overrides')) {
|
|
1280
|
+
const style = document.createElement('style');
|
|
1281
|
+
style.id = 'booking-widget-overrides';
|
|
1282
|
+
style.textContent = '.booking-widget-modal .confirm-icon { font-size: 1em; }';
|
|
1283
|
+
document.head.appendChild(style);
|
|
1273
1284
|
}
|
|
1274
1285
|
}
|
|
1275
1286
|
|
|
@@ -1491,10 +1502,17 @@ if (typeof window !== 'undefined') {
|
|
|
1491
1502
|
this.render();
|
|
1492
1503
|
}
|
|
1493
1504
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1505
|
+
// force=true resets the view when the user opens the picker.
|
|
1506
|
+
// - If a check-in date is already selected, starts from that month/year.
|
|
1507
|
+
// - Otherwise starts from today.
|
|
1508
|
+
// force=false (default) preserves the current month/year so mid-pick re-renders
|
|
1509
|
+
// don't snap the calendar back to the current month.
|
|
1510
|
+
initCalendar(force = false) {
|
|
1511
|
+
if (force || this.calendarMonth === null || this.calendarYear === null) {
|
|
1512
|
+
const anchor = this.state.checkIn || new Date();
|
|
1513
|
+
this.calendarMonth = anchor.getMonth();
|
|
1514
|
+
this.calendarYear = anchor.getFullYear();
|
|
1515
|
+
}
|
|
1498
1516
|
// If we have check-in but no check-out, next click sets check-out; otherwise next click sets check-in (allows changing check-in when both are set)
|
|
1499
1517
|
this.pickState = (this.state.checkIn && !this.state.checkOut) ? 1 : 0;
|
|
1500
1518
|
}
|
|
@@ -1503,7 +1521,7 @@ if (typeof window !== 'undefined') {
|
|
|
1503
1521
|
const popup = this.widget.querySelector('.calendar-popup');
|
|
1504
1522
|
popup.classList.toggle('open');
|
|
1505
1523
|
if (popup.classList.contains('open')) {
|
|
1506
|
-
this.initCalendar();
|
|
1524
|
+
this.initCalendar(true);
|
|
1507
1525
|
this.renderCalendar();
|
|
1508
1526
|
}
|
|
1509
1527
|
}
|
|
@@ -1559,10 +1577,14 @@ if (typeof window !== 'undefined') {
|
|
|
1559
1577
|
|
|
1560
1578
|
pickDate(y, m, d) {
|
|
1561
1579
|
const date = new Date(y, m, d);
|
|
1580
|
+
// Save the current view so render() cannot shift the calendar.
|
|
1581
|
+
const savedMonth = this.calendarMonth;
|
|
1582
|
+
const savedYear = this.calendarYear;
|
|
1562
1583
|
if (this.pickState === 0 || (this.state.checkIn && date <= this.state.checkIn)) {
|
|
1563
1584
|
this.state.checkIn = date; this.state.checkOut = null; this.pickState = 1;
|
|
1564
1585
|
this.render();
|
|
1565
|
-
|
|
1586
|
+
// Restore view after render (render calls initCalendar which may reset month/year).
|
|
1587
|
+
if (savedMonth !== null) { this.calendarMonth = savedMonth; this.calendarYear = savedYear; }
|
|
1566
1588
|
this.pickState = 1;
|
|
1567
1589
|
const popup = this.widget.querySelector('.calendar-popup');
|
|
1568
1590
|
if (popup) popup.classList.add('open');
|
package/dist/booking-widget.css
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
--bg: hsl(30, 10%, 8%);
|
|
82
82
|
--fg: hsl(40, 20%, 90%);
|
|
83
83
|
--card: hsl(30, 8%, 12%);
|
|
84
|
+
--card-solid: hsl(30, 8%, 12%);
|
|
84
85
|
--card-fg: hsl(40, 20%, 90%);
|
|
85
86
|
--primary: hsl(38, 60%, 55%);
|
|
86
87
|
--primary-fg: hsl(30, 10%, 8%);
|
|
@@ -611,7 +612,7 @@
|
|
|
611
612
|
max-width: calc(100vw - 2em);
|
|
612
613
|
box-sizing: border-box;
|
|
613
614
|
z-index: 10;
|
|
614
|
-
background: var(--card-solid
|
|
615
|
+
background: var(--card-solid);
|
|
615
616
|
border: 1px solid var(--border);
|
|
616
617
|
border-radius: var(--radius);
|
|
617
618
|
padding: 1em;
|
package/dist/booking-widget.js
CHANGED
|
@@ -84,6 +84,7 @@ function deriveWidgetStyles(c) {
|
|
|
84
84
|
var styles = { '--primary': primary, '--primary-fg': primaryFg, '--bg': bg, '--fg': fg, '--card-fg': fg };
|
|
85
85
|
if (primaryRgb) styles['--primary-rgb'] = primaryRgb[0] + ', ' + primaryRgb[1] + ', ' + primaryRgb[2];
|
|
86
86
|
styles['--card'] = c.card || (bgHsl ? 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 2) + '%)' : bg);
|
|
87
|
+
styles['--card-solid'] = styles['--card'];
|
|
87
88
|
if (bgHsl) {
|
|
88
89
|
styles['--secondary'] = 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 10) + '%)';
|
|
89
90
|
styles['--border'] = 'hsl(' + bgHsl[0] + ', ' + bgHsl[1] + '%, ' + Math.min(95, bgHsl[2] + 14) + '%)';
|
|
@@ -467,18 +468,21 @@ class BookingWidget {
|
|
|
467
468
|
const self = this;
|
|
468
469
|
const propertyKey = this.options.propertyKey;
|
|
469
470
|
const key = String(propertyKey).trim();
|
|
471
|
+
const isSandbox = this.options.mode === 'sandbox';
|
|
472
|
+
// Cache key is mode-aware so sandbox and live configs are stored separately.
|
|
473
|
+
const cacheKey = isSandbox ? key + ':sandbox' : key;
|
|
470
474
|
|
|
471
|
-
if (__bwConfigCache[
|
|
472
|
-
this._applyApiColors(__bwConfigCache[
|
|
475
|
+
if (__bwConfigCache[cacheKey]) {
|
|
476
|
+
this._applyApiColors(__bwConfigCache[cacheKey]);
|
|
473
477
|
this._configState = 'loaded';
|
|
474
478
|
this.applyColors();
|
|
475
|
-
return Promise.resolve(__bwConfigCache[
|
|
479
|
+
return Promise.resolve(__bwConfigCache[cacheKey]);
|
|
476
480
|
}
|
|
477
481
|
|
|
478
482
|
this._configState = 'loading';
|
|
479
483
|
this.render();
|
|
480
484
|
|
|
481
|
-
const url = 'https://ai.thehotelplanet.com/load-config?apikey=' + encodeURIComponent(key) + '&mode=sandbox';
|
|
485
|
+
const url = 'https://ai.thehotelplanet.com/load-config?apikey=' + encodeURIComponent(key) + (isSandbox ? '&mode=sandbox' : '');
|
|
482
486
|
this._configPromise = fetch(url)
|
|
483
487
|
.then(function (res) {
|
|
484
488
|
if (!res.ok) throw new Error('Failed to load widget configuration (HTTP ' + res.status + ').');
|
|
@@ -491,7 +495,7 @@ class BookingWidget {
|
|
|
491
495
|
if (data.primaryColor) apiColors.primary = data.primaryColor;
|
|
492
496
|
if (data.buttonTextColor) apiColors.primaryText = data.buttonTextColor;
|
|
493
497
|
if (data.widgetCardColor) apiColors.card = data.widgetCardColor;
|
|
494
|
-
__bwConfigCache[
|
|
498
|
+
__bwConfigCache[cacheKey] = apiColors;
|
|
495
499
|
self._applyApiColors(apiColors);
|
|
496
500
|
self._configState = 'loaded';
|
|
497
501
|
self.applyColors();
|
|
@@ -508,13 +512,20 @@ class BookingWidget {
|
|
|
508
512
|
}
|
|
509
513
|
|
|
510
514
|
injectCSS() {
|
|
511
|
-
if (document.getElementById('booking-widget-styles'))
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
515
|
+
if (!document.getElementById('booking-widget-styles')) {
|
|
516
|
+
const link = document.createElement('link');
|
|
517
|
+
link.id = 'booking-widget-styles';
|
|
518
|
+
link.rel = 'stylesheet';
|
|
519
|
+
link.href = this.options.cssUrl || './booking-widget.css';
|
|
520
|
+
document.head.appendChild(link);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!document.getElementById('booking-widget-overrides')) {
|
|
524
|
+
const style = document.createElement('style');
|
|
525
|
+
style.id = 'booking-widget-overrides';
|
|
526
|
+
style.textContent = '.booking-widget-modal .confirm-icon { font-size: 1em; }';
|
|
527
|
+
document.head.appendChild(style);
|
|
528
|
+
}
|
|
518
529
|
}
|
|
519
530
|
|
|
520
531
|
// ===== Render =====
|
|
@@ -760,10 +771,17 @@ class BookingWidget {
|
|
|
760
771
|
}
|
|
761
772
|
|
|
762
773
|
// --- Calendar ---
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
774
|
+
// force=true resets the view when the user opens the picker.
|
|
775
|
+
// - If a check-in date is already selected, starts from that month/year.
|
|
776
|
+
// - Otherwise starts from today.
|
|
777
|
+
// force=false (default) preserves the current month/year so mid-pick re-renders
|
|
778
|
+
// don't snap the calendar back to the current month.
|
|
779
|
+
initCalendar(force = false) {
|
|
780
|
+
if (force || this.calendarMonth === null || this.calendarYear === null) {
|
|
781
|
+
const anchor = this.state.checkIn || new Date();
|
|
782
|
+
this.calendarMonth = anchor.getMonth();
|
|
783
|
+
this.calendarYear = anchor.getFullYear();
|
|
784
|
+
}
|
|
767
785
|
// If we have check-in but no check-out, next click sets check-out; otherwise next click sets check-in (allows changing check-in when both are set)
|
|
768
786
|
this.pickState = (this.state.checkIn && !this.state.checkOut) ? 1 : 0;
|
|
769
787
|
}
|
|
@@ -772,7 +790,7 @@ class BookingWidget {
|
|
|
772
790
|
const popup = this.widget.querySelector('.calendar-popup');
|
|
773
791
|
popup.classList.toggle('open');
|
|
774
792
|
if (popup.classList.contains('open')) {
|
|
775
|
-
this.initCalendar();
|
|
793
|
+
this.initCalendar(true);
|
|
776
794
|
this.renderCalendar();
|
|
777
795
|
}
|
|
778
796
|
}
|
|
@@ -828,6 +846,9 @@ class BookingWidget {
|
|
|
828
846
|
|
|
829
847
|
pickDate(y, m, d) {
|
|
830
848
|
const date = new Date(y, m, d);
|
|
849
|
+
// Save the current view so render() cannot shift the calendar.
|
|
850
|
+
const savedMonth = this.calendarMonth;
|
|
851
|
+
const savedYear = this.calendarYear;
|
|
831
852
|
if (this.pickState === 0 || (this.state.checkIn && date <= this.state.checkIn)) {
|
|
832
853
|
this.state.checkIn = date; this.state.checkOut = null; this.pickState = 1;
|
|
833
854
|
} else {
|
|
@@ -835,9 +856,9 @@ class BookingWidget {
|
|
|
835
856
|
this.widget.querySelector('.calendar-popup').classList.remove('open');
|
|
836
857
|
}
|
|
837
858
|
this.render();
|
|
859
|
+
// Restore view after render (render calls initCalendar which may reset month/year).
|
|
860
|
+
if (savedMonth !== null) { this.calendarMonth = savedMonth; this.calendarYear = savedYear; }
|
|
838
861
|
if (this.pickState === 1) {
|
|
839
|
-
this.initCalendar();
|
|
840
|
-
this.pickState = 1;
|
|
841
862
|
const popup = this.widget.querySelector('.calendar-popup');
|
|
842
863
|
if (popup) popup.classList.add('open');
|
|
843
864
|
this.renderCalendar();
|
package/dist/core/styles.css
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
--bg: hsl(30, 10%, 8%);
|
|
82
82
|
--fg: hsl(40, 20%, 90%);
|
|
83
83
|
--card: hsl(30, 8%, 12%);
|
|
84
|
+
--card-solid: hsl(30, 8%, 12%);
|
|
84
85
|
--card-fg: hsl(40, 20%, 90%);
|
|
85
86
|
--primary: hsl(38, 60%, 55%);
|
|
86
87
|
--primary-fg: hsl(30, 10%, 8%);
|
|
@@ -611,7 +612,7 @@
|
|
|
611
612
|
max-width: calc(100vw - 2em);
|
|
612
613
|
box-sizing: border-box;
|
|
613
614
|
z-index: 10;
|
|
614
|
-
background: var(--card-solid
|
|
615
|
+
background: var(--card-solid);
|
|
615
616
|
border: 1px solid var(--border);
|
|
616
617
|
border-radius: var(--radius);
|
|
617
618
|
padding: 1em;
|
|
@@ -240,7 +240,7 @@ const BookingWidget = ({
|
|
|
240
240
|
setConfigLoading(true);
|
|
241
241
|
setConfigError(null);
|
|
242
242
|
setConfigLoaded(false);
|
|
243
|
-
fetchRuntimeConfig(propertyKey, colors)
|
|
243
|
+
fetchRuntimeConfig(propertyKey, colors, mode)
|
|
244
244
|
.then(({ widgetStyles }) => {
|
|
245
245
|
if (cancelled) return;
|
|
246
246
|
setRuntimeWidgetStyles(widgetStyles);
|
|
@@ -253,7 +253,7 @@ const BookingWidget = ({
|
|
|
253
253
|
setConfigLoading(false);
|
|
254
254
|
});
|
|
255
255
|
return () => { cancelled = true; };
|
|
256
|
-
}, [propertyKey, colors, configRetryCount]);
|
|
256
|
+
}, [propertyKey, colors, mode, configRetryCount]);
|
|
257
257
|
|
|
258
258
|
// Apply resolved CSS custom properties to the widget element.
|
|
259
259
|
useEffect(() => {
|
|
@@ -399,8 +399,6 @@ const BookingWidget = ({
|
|
|
399
399
|
setCalendarOpen(false);
|
|
400
400
|
}
|
|
401
401
|
if (pickState === 0) {
|
|
402
|
-
setCalendarMonth(new Date().getMonth());
|
|
403
|
-
setCalendarYear(new Date().getFullYear());
|
|
404
402
|
setPickState(1);
|
|
405
403
|
}
|
|
406
404
|
};
|
package/dist/react/styles.css
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
--bg: hsl(30, 10%, 8%);
|
|
82
82
|
--fg: hsl(40, 20%, 90%);
|
|
83
83
|
--card: hsl(30, 8%, 12%);
|
|
84
|
+
--card-solid: hsl(30, 8%, 12%);
|
|
84
85
|
--card-fg: hsl(40, 20%, 90%);
|
|
85
86
|
--primary: hsl(38, 60%, 55%);
|
|
86
87
|
--primary-fg: hsl(30, 10%, 8%);
|
|
@@ -611,7 +612,7 @@
|
|
|
611
612
|
max-width: calc(100vw - 2em);
|
|
612
613
|
box-sizing: border-box;
|
|
613
614
|
z-index: 10;
|
|
614
|
-
background: var(--card-solid
|
|
615
|
+
background: var(--card-solid);
|
|
615
616
|
border: 1px solid var(--border);
|
|
616
617
|
border-radius: var(--radius);
|
|
617
618
|
padding: 1em;
|
|
@@ -64,27 +64,31 @@ export function mergeColors(mappedApiColors, installerColors) {
|
|
|
64
64
|
* - Throws synchronously (or rejects) when propertyKey is null, undefined, or
|
|
65
65
|
* an empty string. Callers must catch this and render a "Missing Configuration"
|
|
66
66
|
* error state rather than attempting to render the normal widget UI.
|
|
67
|
-
* - Caches the raw API color payload by propertyKey. Subsequent calls for
|
|
68
|
-
* same key re-use the cache and only re-merge installer overrides.
|
|
67
|
+
* - Caches the raw API color payload by propertyKey + mode. Subsequent calls for
|
|
68
|
+
* the same key+mode re-use the cache and only re-merge installer overrides.
|
|
69
69
|
*
|
|
70
|
-
* @param {string}
|
|
71
|
-
* @param {object|null} installerColors
|
|
70
|
+
* @param {string} propertyKey - The hotel property key / API key.
|
|
71
|
+
* @param {object|null} installerColors - Optional installer color overrides.
|
|
72
|
+
* @param {string} [mode] - Pass 'sandbox' to append &mode=sandbox to the request.
|
|
72
73
|
* @returns {Promise<{ apiColors: object, colors: object, widgetStyles: object }>}
|
|
73
74
|
*/
|
|
74
|
-
export async function fetchRuntimeConfig(propertyKey, installerColors = null) {
|
|
75
|
+
export async function fetchRuntimeConfig(propertyKey, installerColors = null, mode = null) {
|
|
75
76
|
if (!propertyKey || !String(propertyKey).trim()) {
|
|
76
77
|
throw new Error('propertyKey is required to initialize the booking widget.');
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
const key = String(propertyKey).trim();
|
|
81
|
+
const isSandbox = mode === 'sandbox';
|
|
82
|
+
// Cache separately for sandbox vs live so switching modes gets a fresh fetch.
|
|
83
|
+
const cacheKey = isSandbox ? `${key}:sandbox` : key;
|
|
80
84
|
|
|
81
|
-
if (_configCache.has(
|
|
82
|
-
const apiColors = _configCache.get(
|
|
85
|
+
if (_configCache.has(cacheKey)) {
|
|
86
|
+
const apiColors = _configCache.get(cacheKey);
|
|
83
87
|
const colors = mergeColors(apiColors, installerColors);
|
|
84
88
|
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors) };
|
|
85
89
|
}
|
|
86
90
|
|
|
87
|
-
const url = `${CONFIG_BASE_URL}?apikey=${encodeURIComponent(key)}&mode=sandbox`;
|
|
91
|
+
const url = `${CONFIG_BASE_URL}?apikey=${encodeURIComponent(key)}${isSandbox ? '&mode=sandbox' : ''}`;
|
|
88
92
|
const res = await fetch(url);
|
|
89
93
|
if (!res.ok) {
|
|
90
94
|
throw new Error(`Failed to load widget configuration (HTTP ${res.status}).`);
|
|
@@ -92,7 +96,7 @@ export async function fetchRuntimeConfig(propertyKey, installerColors = null) {
|
|
|
92
96
|
|
|
93
97
|
const data = await res.json();
|
|
94
98
|
const apiColors = mapApiColors(data);
|
|
95
|
-
_configCache.set(
|
|
99
|
+
_configCache.set(cacheKey, apiColors);
|
|
96
100
|
|
|
97
101
|
const colors = mergeColors(apiColors, installerColors);
|
|
98
102
|
return { apiColors, colors, widgetStyles: deriveWidgetStyles(colors) };
|
|
@@ -730,6 +730,9 @@ export default {
|
|
|
730
730
|
propertyKey() {
|
|
731
731
|
this._initRuntimeConfig();
|
|
732
732
|
},
|
|
733
|
+
mode() {
|
|
734
|
+
this._initRuntimeConfig();
|
|
735
|
+
},
|
|
733
736
|
colors: {
|
|
734
737
|
deep: true,
|
|
735
738
|
handler() { this._initRuntimeConfig(); },
|
|
@@ -779,7 +782,7 @@ export default {
|
|
|
779
782
|
this.configError = null;
|
|
780
783
|
this.configLoaded = false;
|
|
781
784
|
try {
|
|
782
|
-
const { widgetStyles } = await fetchRuntimeConfig(this.propertyKey, this.colors);
|
|
785
|
+
const { widgetStyles } = await fetchRuntimeConfig(this.propertyKey, this.colors, this.mode);
|
|
783
786
|
this.runtimeWidgetStyles = widgetStyles;
|
|
784
787
|
this.configLoaded = true;
|
|
785
788
|
} catch (err) {
|
package/dist/vue/styles.css
CHANGED
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
--bg: hsl(30, 10%, 8%);
|
|
82
82
|
--fg: hsl(40, 20%, 90%);
|
|
83
83
|
--card: hsl(30, 8%, 12%);
|
|
84
|
+
--card-solid: hsl(30, 8%, 12%);
|
|
84
85
|
--card-fg: hsl(40, 20%, 90%);
|
|
85
86
|
--primary: hsl(38, 60%, 55%);
|
|
86
87
|
--primary-fg: hsl(30, 10%, 8%);
|
|
@@ -611,7 +612,7 @@
|
|
|
611
612
|
max-width: calc(100vw - 2em);
|
|
612
613
|
box-sizing: border-box;
|
|
613
614
|
z-index: 10;
|
|
614
|
-
background: var(--card-solid
|
|
615
|
+
background: var(--card-solid);
|
|
615
616
|
border: 1px solid var(--border);
|
|
616
617
|
border-radius: var(--radius);
|
|
617
618
|
padding: 1em;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuitee/booking-widget",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
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",
|