@getspot/spot-widget 0.1.1 → 0.1.4

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.
@@ -1,13 +1,13 @@
1
1
 
2
- > @getspot/spot-widget@0.1.1 build /builds/getspot/spot-widget/packages/core
2
+ > @getspot/spot-widget@0.1.4 build /builds/getspot/spot-widget/packages/core
3
3
  > vite build
4
4
 
5
5
  The CJS build of Vite's Node API is deprecated. See https://vite.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.
6
6
  vite v5.4.18 building for production...
7
7
  transforming...
8
- ✓ 4 modules transformed.
8
+ ✓ 5 modules transformed.
9
9
  rendering chunks...
10
10
  computing gzip size...
11
- dist/index.umd.js 19.31 kB │ gzip: 6.19 kB
12
- dist/index.es.js 20.80 kB │ gzip: 6.36 kB
13
- ✓ built in 132ms
11
+ dist/index.umd.js 22.00 kB │ gzip: 7.06 kB
12
+ dist/index.es.js 24.07 kB │ gzip: 7.30 kB
13
+ ✓ built in 152ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @getspot/spot-widget
2
2
 
3
+ ## 0.1.4
4
+
5
+ ### Patch Changes
6
+
7
+ - c4d64a9: fix api urls
8
+
9
+ ## 0.1.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 4fe833f: add validation
14
+
15
+ ## 0.1.2
16
+
17
+ ### Patch Changes
18
+
19
+ - 5d7ddb4: add configuration validation
20
+
3
21
  ## 0.1.1
4
22
 
5
23
  ### Patch Changes
package/dist/index.es.js CHANGED
@@ -1,112 +1,196 @@
1
- async function f(s, t, o) {
1
+ async function b(s, e, t) {
2
2
  try {
3
- const r = await fetch(s, {
3
+ const o = await fetch(s, {
4
4
  method: "POST",
5
5
  headers: {
6
6
  "Content-Type": "application/json",
7
- "X-Spot-Partner-Id": t
7
+ "X-Spot-Partner-Id": e
8
8
  },
9
- body: JSON.stringify(o)
10
- }), i = await r.json();
11
- if (!r.ok) {
12
- const n = new Error((i == null ? void 0 : i.message) || "Failed to fetch quote");
13
- throw n.status = r.status, n.responseBody = i, n;
9
+ body: JSON.stringify(t)
10
+ }), n = await o.json();
11
+ if (!o.ok) {
12
+ const r = new Error((n == null ? void 0 : n.message) || "Failed to fetch quote");
13
+ throw r.status = o.status, r.responseBody = n, r;
14
14
  }
15
- return i;
16
- } catch (r) {
17
- throw r instanceof Error ? r : new Error("Unknown error occurred while fetching quote");
15
+ return n;
16
+ } catch (o) {
17
+ throw o instanceof Error ? o : new Error("Unknown error occurred while fetching quote");
18
18
  }
19
19
  }
20
- function e(s, { text: t, className: o, parent: r, innerHTML: i } = {}) {
21
- const n = document.createElement(s);
22
- return o && (n.className = o), t != null && (n.textContent = t), i != null && (n.innerHTML = i), r && r.appendChild(n), n;
20
+ const w = {
21
+ sandbox: "https://api.sandbox.getspot.com/v1/quote",
22
+ production: "https://api.getspot.com/v1/quote"
23
+ };
24
+ function _(s) {
25
+ const {
26
+ apiConfig: e = {},
27
+ quoteRequestData: t,
28
+ callbacks: o = {},
29
+ location: n,
30
+ theme: r
31
+ } = s, {
32
+ environment: a = "sandbox",
33
+ partnerId: p,
34
+ endpoint: l
35
+ } = e;
36
+ if (!p || typeof p != "string")
37
+ throw new Error("Invalid or missing partnerId in apiConfig");
38
+ if (!(l || w[a]))
39
+ throw new Error(`Invalid environment in apiConfig: ${a}`);
40
+ if (!t || typeof t != "object")
41
+ throw new Error("quoteRequestData must be a non-null object");
42
+ [
43
+ "startDate",
44
+ "endDate",
45
+ "currencyCode",
46
+ "eventType",
47
+ "productType",
48
+ "productDuration",
49
+ "productPrice",
50
+ "productId",
51
+ "productName"
52
+ ].forEach((c) => {
53
+ if (!Object.prototype.hasOwnProperty.call(t, c) || t[c] === void 0 || t[c] === null)
54
+ throw new Error(`Missing required quoteRequestData field: '${c}'`);
55
+ });
56
+ const f = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
57
+ if (!f.test(t.startDate))
58
+ throw new Error("startDate must be a valid ISO8601 string");
59
+ if (!f.test(t.endDate))
60
+ throw new Error("endDate must be a valid ISO8601 string");
61
+ if (typeof t.currencyCode != "string")
62
+ throw new Error("currencyCode must be a string");
63
+ if (!["USD", "CAD", "AUD"].includes(t.currencyCode))
64
+ throw new Error(`Invalid currency code: ${t.currencyCode}`);
65
+ if (typeof t.eventType != "string")
66
+ throw new Error("eventType must be a string");
67
+ if (typeof t.productType != "string")
68
+ throw new Error("productType must be a string");
69
+ const u = ["Pass", "Trip", "Registration"];
70
+ if (!u.includes(t.productType))
71
+ throw new Error(
72
+ `productType must be one of ${u.join(", ")}`
73
+ );
74
+ if (typeof t.productDuration != "string")
75
+ throw new Error("productDuration must be a string");
76
+ const h = ["Daily", "Seasonal", "Trip", "Event"];
77
+ if (!h.includes(t.productDuration))
78
+ throw new Error(
79
+ `productDuration must be one of ${h.join(", ")}`
80
+ );
81
+ if (typeof t.productPrice != "number" || isNaN(t.productPrice))
82
+ throw new Error("productPrice must be a valid number");
83
+ if (typeof t.productId != "string")
84
+ throw new Error("productId must be a string");
85
+ if (typeof t.productName != "string")
86
+ throw new Error("productName must be a string");
87
+ if ([
88
+ "onOptIn",
89
+ "onOptOut",
90
+ "onQuoteRetrieved",
91
+ "onError",
92
+ "noMatchingQuote"
93
+ ].forEach((c) => {
94
+ const g = o[c];
95
+ if (g && typeof g != "function")
96
+ throw new Error(`Callback '${c}' must be a function.`);
97
+ }), typeof n == "string" && !document.querySelector(n))
98
+ throw new Error(`Invalid location selector: '${n}'`);
99
+ if (r && typeof r != "object")
100
+ throw new Error(
101
+ "Theme must be an object with CSS variables, do not include the '--' prefix"
102
+ );
103
+ }
104
+ function i(s, { text: e, className: t, parent: o, innerHTML: n } = {}) {
105
+ const r = document.createElement(s);
106
+ return t && (r.className = t), e != null && (r.textContent = e), n != null && (r.innerHTML = n), o && o.appendChild(r), r;
23
107
  }
24
- function h(s, { name: t, description: o }) {
25
- e("div", {
108
+ function y(s, { name: e, description: t }) {
109
+ i("div", {
26
110
  className: "spot-header__title",
27
- text: t,
111
+ text: e,
28
112
  parent: s
29
- }), e("div", {
113
+ }), i("div", {
30
114
  className: "spot-header__description",
31
- text: o,
115
+ text: t,
32
116
  parent: s
33
117
  });
34
118
  }
35
- function u(s, t = []) {
36
- const o = e("ul", {
119
+ function C(s, e = []) {
120
+ const t = i("ul", {
37
121
  className: "spot-benefits__list",
38
122
  parent: s
39
123
  });
40
- t.forEach((r) => {
41
- const i = e("li", { parent: o });
42
- i.innerHTML = `
124
+ e.forEach((o) => {
125
+ const n = i("li", { parent: t });
126
+ n.innerHTML = `
43
127
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
44
128
  <path d="M11.6666 3.5L5.24998 9.91667L2.33331 7"
45
129
  stroke="#2E2E2E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
46
- </svg>`, e("span", { text: r, parent: i });
130
+ </svg>`, i("span", { text: o, parent: n });
47
131
  });
48
132
  }
49
- function g(s, t = []) {
50
- const o = e("div", {
133
+ function v(s, e = []) {
134
+ const t = i("div", {
51
135
  className: "spot-table__container",
52
136
  parent: s
53
- }), r = e("table", {
137
+ }), o = i("table", {
54
138
  className: "spot-refund__table spot-table--dynamic",
55
- parent: o
56
- }), i = e("thead", { parent: r }), n = e("tr", { parent: i });
57
- e("th", { text: "When you cancel", parent: n }), e("th", { text: "You will receive", parent: n });
58
- const a = e("tbody", { parent: r });
59
- t.forEach(({ text: l, percent: p, amount: c }) => {
60
- const d = e("tr", { parent: a });
61
- e("td", { text: l, parent: d });
62
- const m = p === "Not eligible for refund" ? "Not eligible for a refund" : `$${c} refund`;
63
- e("td", { text: m, parent: d });
139
+ parent: t
140
+ }), n = i("thead", { parent: o }), r = i("tr", { parent: n });
141
+ i("th", { text: "When you cancel", parent: r }), i("th", { text: "You will receive", parent: r });
142
+ const a = i("tbody", { parent: o });
143
+ e.forEach(({ text: p, percent: l, amount: d }) => {
144
+ const m = i("tr", { parent: a });
145
+ i("td", { text: p, parent: m });
146
+ const f = l === "Not eligible for refund" ? "Not eligible for a refund" : `$${d} refund`;
147
+ i("td", { text: f, parent: m });
64
148
  });
65
149
  }
66
- function _(s, t, o) {
67
- const r = e("div", {
150
+ function E(s, e, t) {
151
+ const o = i("div", {
68
152
  className: "spot-selection__options",
69
153
  parent: s
70
- }), i = e("label", {
71
- className: `spot-selection__option ${o ? "selected" : ""}`,
72
- parent: r
73
- }), n = e("input", { parent: i });
74
- n.type = "radio", n.name = "selection", n.value = "yes", o && (n.checked = !0), e("strong", {
75
- text: `Yes, protect my booking for $${t}`,
76
- parent: i
77
- }), e("span", {
154
+ }), n = i("label", {
155
+ className: `spot-selection__option ${t ? "selected" : ""}`,
156
+ parent: o
157
+ }), r = i("input", { parent: n });
158
+ r.type = "radio", r.name = "selection", r.value = "yes", t && (r.checked = !0), i("strong", {
159
+ text: `Yes, protect my booking for $${e}`,
160
+ parent: n
161
+ }), i("span", {
78
162
  className: "spot-selection__recommended-tag",
79
163
  text: "Recommended",
80
- parent: i
164
+ parent: n
81
165
  });
82
- const a = e("label", {
166
+ const a = i("label", {
83
167
  className: "spot-selection__option",
84
- parent: r
85
- }), l = e("input", { parent: a });
86
- return l.type = "radio", l.name = "selection", l.value = "no", e("span", { text: "No, do not protect my booking", parent: a }), r;
168
+ parent: o
169
+ }), p = i("input", { parent: a });
170
+ return p.type = "radio", p.name = "selection", p.value = "no", i("span", { text: "No, do not protect my booking", parent: a }), o;
87
171
  }
88
- function b(s, t) {
89
- const o = e("div", {
172
+ function k(s, e) {
173
+ const t = i("div", {
90
174
  className: "spot-footer__container",
91
175
  parent: s
92
- }), r = e("div", {
176
+ }), o = i("div", {
93
177
  className: "spot-footer__terms",
94
- parent: o
178
+ parent: t
95
179
  });
96
- e("span", {
97
- innerHTML: t.communication.legalDisclaimer,
98
- parent: r
99
- }), e("br", { parent: r }), e("a", {
100
- href: t.communication.termsAndConditionsUrl,
180
+ i("span", {
181
+ innerHTML: e.communication.legalDisclaimer,
182
+ parent: o
183
+ }), i("br", { parent: o }), i("a", {
184
+ href: e.communication.termsAndConditionsUrl,
101
185
  className: "spot-footer__terms-link",
102
186
  text: "Refund Guarantee Terms and Conditions",
103
- parent: r
187
+ parent: o
104
188
  });
105
- const i = e("p", {
189
+ const n = i("p", {
106
190
  className: "spot-footer__powered-by",
107
- parent: o
191
+ parent: t
108
192
  });
109
- return i.innerHTML = `
193
+ return n.innerHTML = `
110
194
  <svg width="145" height="28" viewBox="0 0 145 28" fill="none" xmlns="http://www.w3.org/2000/svg">
111
195
  <rect width="145" height="28"/>
112
196
  <rect x="-655" y="-270" width="819" height="325" rx="10"/>
@@ -121,82 +205,92 @@ function b(s, t) {
121
205
  <rect width="45.405" height="14.8867" fill="white" transform="translate(87 8)"/>
122
206
  </clipPath>
123
207
  </defs>
124
- </svg>`, o;
208
+ </svg>`, t;
125
209
  }
126
- const C = ":root{--spot-font-family: Arial;--spot-padding: 1.25rem;--spot-background-color: #ffffff;--spot-font-color: #000000;--spot-border-radius: .5rem;--spot-title-font-size: 1.25rem;--spot-title-font-weight: 700;--spot-title-padding: 0 0 1.25rem 0;--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-padding: 0 .625rem;--spot-radio-border: #000000;--spot-radio-border-radius: .625rem;--spot-radio-checked-background: #000000;--spot-radio-text-font-size: .875rem;--spot-radio-text-font-weight: 400;--spot-radio-text-padding: .625rem;--spot-radio-selection-background: #f4f4f4;--spot-radio-selection-border-radius: .625rem;--spot-radio-selection-padding: .625rem;--spot-recommended-tag-background: #000000;--spot-recommended-tag-font-color: #ffffff;--spot-recommended-tag-font-size: .875rem;--spot-recommended-tag-font-weight: 700;--spot-recommended-tag-padding: .25rem .5rem;--spot-recommended-tag-border-radius: .5rem;--spot-selection-error-font-color: #ff0000;--spot-selection-error-font-size: .875rem;--spot-selection-error-padding: .5rem;--spot-terms-font-size: .75rem;--spot-terms-font-weight: 400;--spot-terms-font-color: #636569;--spot-terms-padding: 0;--spot-terms-link-text-decoration: underline;--spot-terms-link-font-size: .75rem;--spot-terms-link-font-weight: 400;--spot-terms-link-font-color: #636569;--spot-terms-link-padding: 0}.spot-refund-guarantee{font-family:var(--spot-font-family);padding:var(--spot-padding);background-color:var(--spot-background-color);color:var(--spot-font-color);border:.0625rem solid #e0e0e0;border-radius:var(--spot-border-radius);max-width:51rem;margin:1rem}.spot-refund-guarantee *{color:inherit}.spot-header__title{font-size:var(--spot-title-font-size);font-weight:var(--spot-title-font-weight);padding:var(--spot-title-padding);color:var(--spot-title-font-color);font-family:var(--spot-title-font-family);line-height:120%;letter-spacing:-.03125rem}.spot-header__description{font-size:var(--spot-description-font-size);font-weight:var(--spot-description-font-weight);color:var(--spot-description-font-color);font-family:var(--spot-description-font-family);padding:var(--spot-description-padding);line-height:125%;letter-spacing:-.025rem}.spot-content__wrapper{display:flex;flex-direction:column}@media (min-width: 48rem){.spot-content__wrapper.desktop-layout{display:grid;grid-template-columns:1fr 20.3125rem;align-items:start;gap:1rem}.desktop-layout .spot-benefits__list{grid-row:1}.desktop-layout .spot-selection__options{grid-row:2}.desktop-layout .spot-table__container{grid-row:1 / span 2}}@media (max-width: 52.438rem){.spot-selection__recommended-tag{display:inline-block;margin-left:0}}@media (max-width: 47.938rem){.spot-selection__recommended-tag{margin-top:0rem}}@media (max-width: 32.125rem){.spot-selection__recommended-tag{margin-top:.5rem}}@media (max-width: 47.9375rem){.spot-table__container{display:flex;justify-content:center}.spot-selection__recommended-tag{display:inline-block;margin-left:0}.spot-footer__container{flex-direction:column;margin-top:.5rem}.spot-refund__table{width:100%;table-layout:auto}.spot-refund__table th{padding:0rem}}.spot-benefits__list{list-style-type:none;line-height:125%;gap:.5625rem;font-size:var(--spot-bullets-font-size);font-weight:var(--spot-bullets-font-weight);color:var(--spot-bullets-font-color);font-family:var(--spot-bullets-font-family);padding:var(--spot-bullets-padding);margin-block-start:0rem;margin-block-end:0rem}.spot-benefits__list li{margin-bottom:.5rem;display:flex;align-items:flex-start;gap:.5rem}.spot-benefits__list li svg{flex-shrink:0;position:relative;top:.125rem}.spot-table__container{width:100%}.spot-refund__table{max-width:22rem;border-radius:var(--spot-table-border-radius);overflow:hidden;border:.09375rem solid #636569;table-layout:fixed;margin-bottom:1.5rem;margin-top:.25rem;padding:.625rem}.spot-refund__table--dynamic{height:auto!important;min-height:7.5rem}.spot-refund__table td,.spot-refund__table th{padding:.375rem .625rem;text-align:left}.spot-refund__table th{text-align:left;font-size:var(--spot-table-header-font-size);font-weight:var(--spot-table-header-font-weight);color:var(--spot-table-header-font-color);font-family:var(--spot-table-header-font-family);padding:var(--spot-table-header-padding)}.spot-refund__table td{text-align:left;font-size:var(--spot-table-cell-font-size);font-weight:var(--spot-table-cell-font-weight);color:var(--spot-table-cell-font-color);font-family:var(--spot-table-cell-font-family);padding:var(--spot-table-cell-padding)}input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:.75rem;height:.75rem;border:.0625rem solid var(--spot-radio-border);border-radius:var(--spot-radio-border-radius);margin-right:.5rem;position:relative;vertical-align:middle;top:-.0625rem;cursor:pointer}input[type=radio]:checked{background:var(--spot-radio-checked-background);box-shadow:inset 0 0 0 .0625rem #fff}.spot-selection__options{display:flex;flex-direction:column;gap:.5rem}.spot-selection__option{display:block;position:relative;transition:background .2s;cursor:pointer;font-size:var(--spot-radio-text-font-size);font-weight:var(--spot-radio-text-font-weight);color:var(--spot-radio-text-font-color);font-family:var(--spot-radio-text-font-family);padding:var(--spot-radio-text-padding);margin-right:.5rem;gap:.5rem;flex:1 0 0;align-self:stretch}.spot-selection__option.selected{background:var(--spot-radio-selection-background);border-radius:var(--spot-radio-selection-border-radius);padding:var(--spot-radio-selection-padding)}.spot-selection__recommended-tag{background:var(--spot-recommended-tag-background);color:var(--spot-recommended-tag-font-color);font-size:var(--spot-recommended-tag-font-size);font-weight:var(--spot-recommended-tag-font-weight);padding:var(--spot-recommended-tag-padding);border-radius:var(--spot-recommended-tag-border-radius);margin-left:1.5rem;white-space:nowrap}.spot-selection__error{color:var(--spot-selection-error-font-color);font-size:var(--spot-selection-error-font-size);padding:var(--spot-selection-error-padding);display:none}.spot-footer__terms{margin-top:.625rem;margin-right:.25rem;font-size:var(--spot-terms-font-size);font-weight:var(--spot-terms-font-weight);color:var(--spot-terms-font-color);font-family:var(--spot-terms-font-family);padding:var(--spot-terms-padding)}.spot-footer__terms-link{text-decoration:var(--spot-terms-link-text-decoration);font-size:var(--spot-terms-link-font-size);font-weight:var(--spot-terms-link-font-weight);color:var(--spot-terms-link-font-color);font-family:var(--spot-terms-link-font-family);padding:var(--spot-terms-link-padding)}.spot-footer__container{display:flex;justify-content:space-between;align-items:center}.spot-footer__powered-by{margin-top:1.5rem}";
127
- function v(s) {
128
- const t = document.createElement("style");
129
- t.textContent = s, document.head.appendChild(t);
210
+ const x = ":root{--spot-font-family: Arial;--spot-padding: 1.25rem;--spot-background-color: #ffffff;--spot-font-color: #000000;--spot-border-radius: .5rem;--spot-title-font-size: 1.25rem;--spot-title-font-weight: 700;--spot-title-padding: 0 0 1.25rem 0;--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-padding: 0 .625rem;--spot-radio-border: #000000;--spot-radio-border-radius: .625rem;--spot-radio-checked-background: #000000;--spot-radio-text-font-size: .875rem;--spot-radio-text-font-weight: 400;--spot-radio-text-padding: .625rem;--spot-radio-selection-background: #f4f4f4;--spot-radio-selection-border-radius: .625rem;--spot-radio-selection-padding: .625rem;--spot-recommended-tag-background: #000000;--spot-recommended-tag-font-color: #ffffff;--spot-recommended-tag-font-size: .875rem;--spot-recommended-tag-font-weight: 700;--spot-recommended-tag-padding: .25rem .5rem;--spot-recommended-tag-border-radius: .5rem;--spot-selection-error-font-color: #ff0000;--spot-selection-error-font-size: .875rem;--spot-selection-error-padding: .5rem;--spot-terms-font-size: .75rem;--spot-terms-font-weight: 400;--spot-terms-font-color: #636569;--spot-terms-padding: 0;--spot-terms-link-text-decoration: underline;--spot-terms-link-font-size: .75rem;--spot-terms-link-font-weight: 400;--spot-terms-link-font-color: #636569;--spot-terms-link-padding: 0}.spot-refund-guarantee{font-family:var(--spot-font-family);padding:var(--spot-padding);background-color:var(--spot-background-color);color:var(--spot-font-color);border:.0625rem solid #e0e0e0;border-radius:var(--spot-border-radius);max-width:51rem;margin:1rem}.spot-refund-guarantee *{color:inherit}.spot-header__title{font-size:var(--spot-title-font-size);font-weight:var(--spot-title-font-weight);padding:var(--spot-title-padding);color:var(--spot-title-font-color);font-family:var(--spot-title-font-family);line-height:120%;letter-spacing:-.03125rem}.spot-header__description{font-size:var(--spot-description-font-size);font-weight:var(--spot-description-font-weight);color:var(--spot-description-font-color);font-family:var(--spot-description-font-family);padding:var(--spot-description-padding);line-height:125%;letter-spacing:-.025rem}.spot-content__wrapper{display:flex;flex-direction:column}@media (min-width: 48rem){.spot-content__wrapper.desktop-layout{display:grid;grid-template-columns:1fr 20.3125rem;align-items:start;gap:1rem}.desktop-layout .spot-benefits__list{grid-row:1}.desktop-layout .spot-selection__options{grid-row:2}.desktop-layout .spot-table__container{grid-row:1 / span 2}}@media (max-width: 52.438rem){.spot-selection__recommended-tag{display:inline-block;margin-left:0}}@media (max-width: 47.938rem){.spot-selection__recommended-tag{margin-top:0rem}}@media (max-width: 32.125rem){.spot-selection__recommended-tag{margin-top:.5rem}}@media (max-width: 47.9375rem){.spot-table__container{display:flex;justify-content:center}.spot-selection__recommended-tag{display:inline-block;margin-left:0}.spot-footer__container{flex-direction:column;margin-top:.5rem}.spot-refund__table{width:100%;table-layout:auto}.spot-refund__table th{padding:0rem}}.spot-benefits__list{list-style-type:none;line-height:125%;gap:.5625rem;font-size:var(--spot-bullets-font-size);font-weight:var(--spot-bullets-font-weight);color:var(--spot-bullets-font-color);font-family:var(--spot-bullets-font-family);padding:var(--spot-bullets-padding);margin-block-start:0rem;margin-block-end:0rem}.spot-benefits__list li{margin-bottom:.5rem;display:flex;align-items:flex-start;gap:.5rem}.spot-benefits__list li svg{flex-shrink:0;position:relative;top:.125rem}.spot-table__container{width:100%}.spot-refund__table{max-width:22rem;border-radius:var(--spot-table-border-radius);overflow:hidden;border:.09375rem solid #636569;table-layout:fixed;margin-bottom:1.5rem;margin-top:.25rem;padding:.625rem}.spot-refund__table--dynamic{height:auto!important;min-height:7.5rem}.spot-refund__table td,.spot-refund__table th{padding:.375rem .625rem;text-align:left}.spot-refund__table th{text-align:left;font-size:var(--spot-table-header-font-size);font-weight:var(--spot-table-header-font-weight);color:var(--spot-table-header-font-color);font-family:var(--spot-table-header-font-family);padding:var(--spot-table-header-padding)}.spot-refund__table td{text-align:left;font-size:var(--spot-table-cell-font-size);font-weight:var(--spot-table-cell-font-weight);color:var(--spot-table-cell-font-color);font-family:var(--spot-table-cell-font-family);padding:var(--spot-table-cell-padding)}input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:.75rem;height:.75rem;border:.0625rem solid var(--spot-radio-border);border-radius:var(--spot-radio-border-radius);margin-right:.5rem;position:relative;vertical-align:middle;top:-.0625rem;cursor:pointer}input[type=radio]:checked{background:var(--spot-radio-checked-background);box-shadow:inset 0 0 0 .0625rem #fff}.spot-selection__options{display:flex;flex-direction:column;gap:.5rem}.spot-selection__option{display:block;position:relative;transition:background .2s;cursor:pointer;font-size:var(--spot-radio-text-font-size);font-weight:var(--spot-radio-text-font-weight);color:var(--spot-radio-text-font-color);font-family:var(--spot-radio-text-font-family);padding:var(--spot-radio-text-padding);margin-right:.5rem;gap:.5rem;flex:1 0 0;align-self:stretch}.spot-selection__option.selected{background:var(--spot-radio-selection-background);border-radius:var(--spot-radio-selection-border-radius);padding:var(--spot-radio-selection-padding)}.spot-selection__recommended-tag{background:var(--spot-recommended-tag-background);color:var(--spot-recommended-tag-font-color);font-size:var(--spot-recommended-tag-font-size);font-weight:var(--spot-recommended-tag-font-weight);padding:var(--spot-recommended-tag-padding);border-radius:var(--spot-recommended-tag-border-radius);margin-left:1.5rem;white-space:nowrap}.spot-selection__error{color:var(--spot-selection-error-font-color);font-size:var(--spot-selection-error-font-size);padding:var(--spot-selection-error-padding);display:none}.spot-footer__terms{margin-top:.625rem;margin-right:.25rem;font-size:var(--spot-terms-font-size);font-weight:var(--spot-terms-font-weight);color:var(--spot-terms-font-color);font-family:var(--spot-terms-font-family);padding:var(--spot-terms-padding)}.spot-footer__terms-link{text-decoration:var(--spot-terms-link-text-decoration);font-size:var(--spot-terms-link-font-size);font-weight:var(--spot-terms-link-font-weight);color:var(--spot-terms-link-font-color);font-family:var(--spot-terms-link-font-family);padding:var(--spot-terms-link-padding)}.spot-footer__container{display:flex;justify-content:space-between;align-items:center}.spot-footer__powered-by{margin-top:1.5rem}";
211
+ function H(s) {
212
+ const e = document.createElement("style");
213
+ e.textContent = s, document.head.appendChild(e);
130
214
  }
131
- v(C);
132
- class y {
133
- constructor(t = {}) {
215
+ H(x);
216
+ const q = {
217
+ sandbox: "https://api.sandbox.getspot.com/api/v1/quote",
218
+ production: "https://api.getspot.com/api/v1/quote"
219
+ };
220
+ class M {
221
+ constructor(e = {}) {
134
222
  this.options = {
135
223
  location: "body",
136
224
  showTable: !0,
137
225
  optInSelected: !1,
138
- apiConfig: { endpoint: "", partnerId: "" },
226
+ apiConfig: { environment: "production", partnerId: "" },
139
227
  quoteRequestData: {},
140
228
  callbacks: {},
141
- ...t
229
+ ...e
142
230
  }, this._onResize = this._updateLayout.bind(this), this.root = typeof this.options.location == "string" ? document.querySelector(this.options.location) : this.options.location, this.currentSelection = this.options.optInSelected ? "yes" : null, this._init();
143
231
  }
144
232
  async _init() {
233
+ var e, t, o, n;
145
234
  try {
146
- const t = await f(
147
- this.options.apiConfig.endpoint,
148
- this.options.apiConfig.partnerId,
235
+ _(this.options);
236
+ const {
237
+ environment: r,
238
+ partnerId: a,
239
+ endpoint: p
240
+ } = this.options.apiConfig, l = p || q[r], d = await b(
241
+ l,
242
+ a,
149
243
  this.options.quoteRequestData
150
244
  );
151
- if (t.status !== "QUOTE_AVAILABLE") {
152
- t.status === "NO_MATCHING_QUOTE" && this.options.callbacks.noMatchingQuote({
245
+ if (d.status !== "QUOTE_AVAILABLE") {
246
+ d.status === "NO_MATCHING_QUOTE" && ((e = this.options.callbacks) != null && e.noMatchingQuote) && this.options.callbacks.noMatchingQuote({
153
247
  status: "NO_MATCHING_QUOTE",
154
248
  data: this.options.quoteRequestData
155
249
  });
156
250
  return;
157
251
  }
158
- this.quote = t.data, this._renderWidget(), this.options.optInSelected && this.options.callbacks.onOptIn && this.options.callbacks.onOptIn({
252
+ this.quote = d.data, this._renderWidget(), this.options.optInSelected && ((t = this.options.callbacks) != null && t.onOptIn) && this.options.callbacks.onOptIn({
159
253
  status: "QUOTE_ACCEPTED",
160
254
  spotPrice: this.quote.spotPrice,
161
255
  quoteId: this.quote.id
162
- }), this.options.callbacks.onQuoteRetrieved && this.options.callbacks.onQuoteRetrieved(this.quote);
163
- } catch (t) {
164
- this.options.callbacks.onError({
165
- message: t.message,
166
- status: t.status,
167
- responseBody: t.responseBody
256
+ }), (o = this.options.callbacks) != null && o.onQuoteRetrieved && this.options.callbacks.onQuoteRetrieved(this.quote);
257
+ } catch (r) {
258
+ (n = this.options.callbacks) == null || n.onError({
259
+ message: r.message,
260
+ status: r.status,
261
+ responseBody: r.responseBody
168
262
  });
169
263
  }
170
264
  }
171
265
  _renderWidget() {
172
- this.container = document.createElement("div"), this.container.className = "spot-refund-guarantee", this.root.appendChild(this.container), Object.entries(this.options.theme || {}).forEach(([r, i]) => {
173
- const n = `--${r}`;
174
- this.container.style.setProperty(n, i);
175
- }), h(this.container, this.quote.communication);
176
- const t = document.createElement("div");
177
- t.className = "spot-content__wrapper", this.container.appendChild(t), u(t, this.quote.communication.bulletPoints), this.options.showTable && g(t, this.quote.payoutSchedule);
178
- const o = _(
179
- t,
266
+ this.container = document.createElement("div"), this.container.className = "spot-refund-guarantee", this.root.appendChild(this.container), Object.entries(this.options.theme || {}).forEach(([o, n]) => {
267
+ const r = `--${o}`;
268
+ this.container.style.setProperty(r, n);
269
+ }), y(this.container, this.quote.communication);
270
+ const e = document.createElement("div");
271
+ e.className = "spot-content__wrapper", this.container.appendChild(e), C(e, this.quote.communication.bulletPoints), this.options.showTable && v(e, this.quote.payoutSchedule);
272
+ const t = E(
273
+ e,
180
274
  this.quote.spotPrice,
181
275
  this.options.optInSelected
182
276
  );
183
- t.appendChild(o), b(this.container, this.quote), window.addEventListener("resize", this._onResize), this._updateLayout(), this._setupOptionListeners(o);
277
+ e.appendChild(t), k(this.container, this.quote), window.addEventListener("resize", this._onResize), this._updateLayout(), this._setupOptionListeners(t);
184
278
  }
185
279
  _updateLayout() {
186
- const t = window.matchMedia("(min-width: 768px)").matches;
187
- this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout", t && this.options.showTable);
280
+ const e = window.matchMedia("(min-width: 768px)").matches;
281
+ this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout", e && this.options.showTable);
188
282
  }
189
- _setupOptionListeners(t) {
190
- const o = t.querySelectorAll('input[type="radio"]'), r = t.querySelectorAll(".spot-selection__option");
191
- o.forEach((i) => {
192
- i.addEventListener("change", (n) => {
193
- var l;
194
- const a = n.target.value;
195
- this.hideSelectionError(), this.currentSelection = a, r.forEach((p) => p.classList.remove("selected")), (l = n.target.closest(".spot-selection__option")) == null || l.classList.add("selected"), a === "yes" && this.options.callbacks.onOptIn && this.options.callbacks.onOptIn({
283
+ _setupOptionListeners(e) {
284
+ const t = e.querySelectorAll('input[type="radio"]'), o = e.querySelectorAll(".spot-selection__option");
285
+ t.forEach((n) => {
286
+ n.addEventListener("change", (r) => {
287
+ var p, l, d;
288
+ const a = r.target.value;
289
+ this.hideSelectionError(), this.currentSelection = a, o.forEach((m) => m.classList.remove("selected")), (p = r.target.closest(".spot-selection__option")) == null || p.classList.add("selected"), a === "yes" && ((l = this.options.callbacks) != null && l.onOptIn) && this.options.callbacks.onOptIn({
196
290
  status: "QUOTE_ACCEPTED",
197
291
  spotPrice: this.quote.spotPrice,
198
292
  quoteId: this.quote.id
199
- }), a === "no" && this.options.callbacks.onOptOut && this.options.callbacks.onOptOut({
293
+ }), a === "no" && ((d = this.options.callbacks) != null && d.onOptOut) && this.options.callbacks.onOptOut({
200
294
  status: "QUOTE_DECLINED",
201
295
  quoteId: this.quote.id
202
296
  });
@@ -204,13 +298,13 @@ class y {
204
298
  });
205
299
  }
206
300
  showSelectionError() {
207
- var t;
301
+ var e;
208
302
  if (!this.errorEl) {
209
303
  this.errorEl = document.createElement("div"), this.errorEl.className = "spot-selection__error", this.errorEl.textContent = "Please make a selection";
210
- const o = (t = this.container) == null ? void 0 : t.querySelector(
304
+ const t = (e = this.container) == null ? void 0 : e.querySelector(
211
305
  ".spot-selection__options"
212
306
  );
213
- o && o.insertAdjacentElement("afterend", this.errorEl);
307
+ t && t.insertAdjacentElement("afterend", this.errorEl);
214
308
  }
215
309
  this.errorEl.style.display = "block";
216
310
  }
@@ -219,17 +313,17 @@ class y {
219
313
  }
220
314
  validateSelection() {
221
315
  if (!this.container) return !1;
222
- const t = !!this.container.querySelector(
316
+ const e = !!this.container.querySelector(
223
317
  'input[name="selection"]:checked'
224
318
  );
225
- return t ? this.hideSelectionError() : this.showSelectionError(), t;
319
+ return e ? this.hideSelectionError() : this.showSelectionError(), e;
226
320
  }
227
321
  getSelection() {
228
- var t, o;
322
+ var e, t;
229
323
  return this.currentSelection == null ? null : {
230
324
  selection: this.currentSelection,
231
- quoteId: (t = this.quote) == null ? void 0 : t.id,
232
- spotPrice: (o = this.quote) == null ? void 0 : o.spotPrice,
325
+ quoteId: (e = this.quote) == null ? void 0 : e.id,
326
+ spotPrice: (t = this.quote) == null ? void 0 : t.spotPrice,
233
327
  status: this.currentSelection === "yes" ? "QUOTE_ACCEPTED" : "QUOTE_DECLINED"
234
328
  };
235
329
  }
@@ -238,5 +332,5 @@ class y {
238
332
  }
239
333
  }
240
334
  export {
241
- y as default
335
+ M as default
242
336
  };
package/dist/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
- (function(l,e){typeof exports=="object"&&typeof module<"u"?module.exports=e():typeof define=="function"&&define.amd?define(e):(l=typeof globalThis<"u"?globalThis:l||self,l.SpotWidget=e())})(this,function(){"use strict";async function l(s,t,o){try{const r=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json","X-Spot-Partner-Id":t},body:JSON.stringify(o)}),i=await r.json();if(!r.ok){const n=new Error((i==null?void 0:i.message)||"Failed to fetch quote");throw n.status=r.status,n.responseBody=i,n}return i}catch(r){throw r instanceof Error?r:new Error("Unknown error occurred while fetching quote")}}function e(s,{text:t,className:o,parent:r,innerHTML:i}={}){const n=document.createElement(s);return o&&(n.className=o),t!=null&&(n.textContent=t),i!=null&&(n.innerHTML=i),r&&r.appendChild(n),n}function m(s,{name:t,description:o}){e("div",{className:"spot-header__title",text:t,parent:s}),e("div",{className:"spot-header__description",text:o,parent:s})}function f(s,t=[]){const o=e("ul",{className:"spot-benefits__list",parent:s});t.forEach(r=>{const i=e("li",{parent:o});i.innerHTML=`
1
+ (function(f,m){typeof exports=="object"&&typeof module<"u"?module.exports=m():typeof define=="function"&&define.amd?define(m):(f=typeof globalThis<"u"?globalThis:f||self,f.SpotWidget=m())})(this,function(){"use strict";async function f(s,e,t){try{const o=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json","X-Spot-Partner-Id":e},body:JSON.stringify(t)}),n=await o.json();if(!o.ok){const r=new Error((n==null?void 0:n.message)||"Failed to fetch quote");throw r.status=o.status,r.responseBody=n,r}return n}catch(o){throw o instanceof Error?o:new Error("Unknown error occurred while fetching quote")}}const m={sandbox:"https://api.sandbox.getspot.com/v1/quote",production:"https://api.getspot.com/v1/quote"};function _(s){const{apiConfig:e={},quoteRequestData:t,callbacks:o={},location:n,theme:r}=s,{environment:a="sandbox",partnerId:p,endpoint:l}=e;if(!p||typeof p!="string")throw new Error("Invalid or missing partnerId in apiConfig");if(!(l||m[a]))throw new Error(`Invalid environment in apiConfig: ${a}`);if(!t||typeof t!="object")throw new Error("quoteRequestData must be a non-null object");["startDate","endDate","currencyCode","eventType","productType","productDuration","productPrice","productId","productName"].forEach(c=>{if(!Object.prototype.hasOwnProperty.call(t,c)||t[c]===void 0||t[c]===null)throw new Error(`Missing required quoteRequestData field: '${c}'`)});const h=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;if(!h.test(t.startDate))throw new Error("startDate must be a valid ISO8601 string");if(!h.test(t.endDate))throw new Error("endDate must be a valid ISO8601 string");if(typeof t.currencyCode!="string")throw new Error("currencyCode must be a string");if(!["USD","CAD","AUD"].includes(t.currencyCode))throw new Error(`Invalid currency code: ${t.currencyCode}`);if(typeof t.eventType!="string")throw new Error("eventType must be a string");if(typeof t.productType!="string")throw new Error("productType must be a string");const g=["Pass","Trip","Registration"];if(!g.includes(t.productType))throw new Error(`productType must be one of ${g.join(", ")}`);if(typeof t.productDuration!="string")throw new Error("productDuration must be a string");const b=["Daily","Seasonal","Trip","Event"];if(!b.includes(t.productDuration))throw new Error(`productDuration must be one of ${b.join(", ")}`);if(typeof t.productPrice!="number"||isNaN(t.productPrice))throw new Error("productPrice must be a valid number");if(typeof t.productId!="string")throw new Error("productId must be a string");if(typeof t.productName!="string")throw new Error("productName must be a string");if(["onOptIn","onOptOut","onQuoteRetrieved","onError","noMatchingQuote"].forEach(c=>{const w=o[c];if(w&&typeof w!="function")throw new Error(`Callback '${c}' must be a function.`)}),typeof n=="string"&&!document.querySelector(n))throw new Error(`Invalid location selector: '${n}'`);if(r&&typeof r!="object")throw new Error("Theme must be an object with CSS variables, do not include the '--' prefix")}function i(s,{text:e,className:t,parent:o,innerHTML:n}={}){const r=document.createElement(s);return t&&(r.className=t),e!=null&&(r.textContent=e),n!=null&&(r.innerHTML=n),o&&o.appendChild(r),r}function y(s,{name:e,description:t}){i("div",{className:"spot-header__title",text:e,parent:s}),i("div",{className:"spot-header__description",text:t,parent:s})}function C(s,e=[]){const t=i("ul",{className:"spot-benefits__list",parent:s});e.forEach(o=>{const n=i("li",{parent:t});n.innerHTML=`
2
2
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none">
3
3
  <path d="M11.6666 3.5L5.24998 9.91667L2.33331 7"
4
4
  stroke="#2E2E2E" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
5
- </svg>`,e("span",{text:r,parent:i})})}function h(s,t=[]){const o=e("div",{className:"spot-table__container",parent:s}),r=e("table",{className:"spot-refund__table spot-table--dynamic",parent:o}),i=e("thead",{parent:r}),n=e("tr",{parent:i});e("th",{text:"When you cancel",parent:n}),e("th",{text:"You will receive",parent:n});const a=e("tbody",{parent:r});t.forEach(({text:p,percent:d,amount:v})=>{const c=e("tr",{parent:a});e("td",{text:p,parent:c});const y=d==="Not eligible for refund"?"Not eligible for a refund":`$${v} refund`;e("td",{text:y,parent:c})})}function u(s,t,o){const r=e("div",{className:"spot-selection__options",parent:s}),i=e("label",{className:`spot-selection__option ${o?"selected":""}`,parent:r}),n=e("input",{parent:i});n.type="radio",n.name="selection",n.value="yes",o&&(n.checked=!0),e("strong",{text:`Yes, protect my booking for $${t}`,parent:i}),e("span",{className:"spot-selection__recommended-tag",text:"Recommended",parent:i});const a=e("label",{className:"spot-selection__option",parent:r}),p=e("input",{parent:a});return p.type="radio",p.name="selection",p.value="no",e("span",{text:"No, do not protect my booking",parent:a}),r}function g(s,t){const o=e("div",{className:"spot-footer__container",parent:s}),r=e("div",{className:"spot-footer__terms",parent:o});e("span",{innerHTML:t.communication.legalDisclaimer,parent:r}),e("br",{parent:r}),e("a",{href:t.communication.termsAndConditionsUrl,className:"spot-footer__terms-link",text:"Refund Guarantee Terms and Conditions",parent:r});const i=e("p",{className:"spot-footer__powered-by",parent:o});return i.innerHTML=`
5
+ </svg>`,i("span",{text:o,parent:n})})}function v(s,e=[]){const t=i("div",{className:"spot-table__container",parent:s}),o=i("table",{className:"spot-refund__table spot-table--dynamic",parent:t}),n=i("thead",{parent:o}),r=i("tr",{parent:n});i("th",{text:"When you cancel",parent:r}),i("th",{text:"You will receive",parent:r});const a=i("tbody",{parent:o});e.forEach(({text:p,percent:l,amount:d})=>{const u=i("tr",{parent:a});i("td",{text:p,parent:u});const h=l==="Not eligible for refund"?"Not eligible for a refund":`$${d} refund`;i("td",{text:h,parent:u})})}function E(s,e,t){const o=i("div",{className:"spot-selection__options",parent:s}),n=i("label",{className:`spot-selection__option ${t?"selected":""}`,parent:o}),r=i("input",{parent:n});r.type="radio",r.name="selection",r.value="yes",t&&(r.checked=!0),i("strong",{text:`Yes, protect my booking for $${e}`,parent:n}),i("span",{className:"spot-selection__recommended-tag",text:"Recommended",parent:n});const a=i("label",{className:"spot-selection__option",parent:o}),p=i("input",{parent:a});return p.type="radio",p.name="selection",p.value="no",i("span",{text:"No, do not protect my booking",parent:a}),o}function k(s,e){const t=i("div",{className:"spot-footer__container",parent:s}),o=i("div",{className:"spot-footer__terms",parent:t});i("span",{innerHTML:e.communication.legalDisclaimer,parent:o}),i("br",{parent:o}),i("a",{href:e.communication.termsAndConditionsUrl,className:"spot-footer__terms-link",text:"Refund Guarantee Terms and Conditions",parent:o});const n=i("p",{className:"spot-footer__powered-by",parent:t});return n.innerHTML=`
6
6
  <svg width="145" height="28" viewBox="0 0 145 28" fill="none" xmlns="http://www.w3.org/2000/svg">
7
7
  <rect width="145" height="28"/>
8
8
  <rect x="-655" y="-270" width="819" height="325" rx="10"/>
@@ -17,4 +17,4 @@
17
17
  <rect width="45.405" height="14.8867" fill="white" transform="translate(87 8)"/>
18
18
  </clipPath>
19
19
  </defs>
20
- </svg>`,o}const _=":root{--spot-font-family: Arial;--spot-padding: 1.25rem;--spot-background-color: #ffffff;--spot-font-color: #000000;--spot-border-radius: .5rem;--spot-title-font-size: 1.25rem;--spot-title-font-weight: 700;--spot-title-padding: 0 0 1.25rem 0;--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-padding: 0 .625rem;--spot-radio-border: #000000;--spot-radio-border-radius: .625rem;--spot-radio-checked-background: #000000;--spot-radio-text-font-size: .875rem;--spot-radio-text-font-weight: 400;--spot-radio-text-padding: .625rem;--spot-radio-selection-background: #f4f4f4;--spot-radio-selection-border-radius: .625rem;--spot-radio-selection-padding: .625rem;--spot-recommended-tag-background: #000000;--spot-recommended-tag-font-color: #ffffff;--spot-recommended-tag-font-size: .875rem;--spot-recommended-tag-font-weight: 700;--spot-recommended-tag-padding: .25rem .5rem;--spot-recommended-tag-border-radius: .5rem;--spot-selection-error-font-color: #ff0000;--spot-selection-error-font-size: .875rem;--spot-selection-error-padding: .5rem;--spot-terms-font-size: .75rem;--spot-terms-font-weight: 400;--spot-terms-font-color: #636569;--spot-terms-padding: 0;--spot-terms-link-text-decoration: underline;--spot-terms-link-font-size: .75rem;--spot-terms-link-font-weight: 400;--spot-terms-link-font-color: #636569;--spot-terms-link-padding: 0}.spot-refund-guarantee{font-family:var(--spot-font-family);padding:var(--spot-padding);background-color:var(--spot-background-color);color:var(--spot-font-color);border:.0625rem solid #e0e0e0;border-radius:var(--spot-border-radius);max-width:51rem;margin:1rem}.spot-refund-guarantee *{color:inherit}.spot-header__title{font-size:var(--spot-title-font-size);font-weight:var(--spot-title-font-weight);padding:var(--spot-title-padding);color:var(--spot-title-font-color);font-family:var(--spot-title-font-family);line-height:120%;letter-spacing:-.03125rem}.spot-header__description{font-size:var(--spot-description-font-size);font-weight:var(--spot-description-font-weight);color:var(--spot-description-font-color);font-family:var(--spot-description-font-family);padding:var(--spot-description-padding);line-height:125%;letter-spacing:-.025rem}.spot-content__wrapper{display:flex;flex-direction:column}@media (min-width: 48rem){.spot-content__wrapper.desktop-layout{display:grid;grid-template-columns:1fr 20.3125rem;align-items:start;gap:1rem}.desktop-layout .spot-benefits__list{grid-row:1}.desktop-layout .spot-selection__options{grid-row:2}.desktop-layout .spot-table__container{grid-row:1 / span 2}}@media (max-width: 52.438rem){.spot-selection__recommended-tag{display:inline-block;margin-left:0}}@media (max-width: 47.938rem){.spot-selection__recommended-tag{margin-top:0rem}}@media (max-width: 32.125rem){.spot-selection__recommended-tag{margin-top:.5rem}}@media (max-width: 47.9375rem){.spot-table__container{display:flex;justify-content:center}.spot-selection__recommended-tag{display:inline-block;margin-left:0}.spot-footer__container{flex-direction:column;margin-top:.5rem}.spot-refund__table{width:100%;table-layout:auto}.spot-refund__table th{padding:0rem}}.spot-benefits__list{list-style-type:none;line-height:125%;gap:.5625rem;font-size:var(--spot-bullets-font-size);font-weight:var(--spot-bullets-font-weight);color:var(--spot-bullets-font-color);font-family:var(--spot-bullets-font-family);padding:var(--spot-bullets-padding);margin-block-start:0rem;margin-block-end:0rem}.spot-benefits__list li{margin-bottom:.5rem;display:flex;align-items:flex-start;gap:.5rem}.spot-benefits__list li svg{flex-shrink:0;position:relative;top:.125rem}.spot-table__container{width:100%}.spot-refund__table{max-width:22rem;border-radius:var(--spot-table-border-radius);overflow:hidden;border:.09375rem solid #636569;table-layout:fixed;margin-bottom:1.5rem;margin-top:.25rem;padding:.625rem}.spot-refund__table--dynamic{height:auto!important;min-height:7.5rem}.spot-refund__table td,.spot-refund__table th{padding:.375rem .625rem;text-align:left}.spot-refund__table th{text-align:left;font-size:var(--spot-table-header-font-size);font-weight:var(--spot-table-header-font-weight);color:var(--spot-table-header-font-color);font-family:var(--spot-table-header-font-family);padding:var(--spot-table-header-padding)}.spot-refund__table td{text-align:left;font-size:var(--spot-table-cell-font-size);font-weight:var(--spot-table-cell-font-weight);color:var(--spot-table-cell-font-color);font-family:var(--spot-table-cell-font-family);padding:var(--spot-table-cell-padding)}input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:.75rem;height:.75rem;border:.0625rem solid var(--spot-radio-border);border-radius:var(--spot-radio-border-radius);margin-right:.5rem;position:relative;vertical-align:middle;top:-.0625rem;cursor:pointer}input[type=radio]:checked{background:var(--spot-radio-checked-background);box-shadow:inset 0 0 0 .0625rem #fff}.spot-selection__options{display:flex;flex-direction:column;gap:.5rem}.spot-selection__option{display:block;position:relative;transition:background .2s;cursor:pointer;font-size:var(--spot-radio-text-font-size);font-weight:var(--spot-radio-text-font-weight);color:var(--spot-radio-text-font-color);font-family:var(--spot-radio-text-font-family);padding:var(--spot-radio-text-padding);margin-right:.5rem;gap:.5rem;flex:1 0 0;align-self:stretch}.spot-selection__option.selected{background:var(--spot-radio-selection-background);border-radius:var(--spot-radio-selection-border-radius);padding:var(--spot-radio-selection-padding)}.spot-selection__recommended-tag{background:var(--spot-recommended-tag-background);color:var(--spot-recommended-tag-font-color);font-size:var(--spot-recommended-tag-font-size);font-weight:var(--spot-recommended-tag-font-weight);padding:var(--spot-recommended-tag-padding);border-radius:var(--spot-recommended-tag-border-radius);margin-left:1.5rem;white-space:nowrap}.spot-selection__error{color:var(--spot-selection-error-font-color);font-size:var(--spot-selection-error-font-size);padding:var(--spot-selection-error-padding);display:none}.spot-footer__terms{margin-top:.625rem;margin-right:.25rem;font-size:var(--spot-terms-font-size);font-weight:var(--spot-terms-font-weight);color:var(--spot-terms-font-color);font-family:var(--spot-terms-font-family);padding:var(--spot-terms-padding)}.spot-footer__terms-link{text-decoration:var(--spot-terms-link-text-decoration);font-size:var(--spot-terms-link-font-size);font-weight:var(--spot-terms-link-font-weight);color:var(--spot-terms-link-font-color);font-family:var(--spot-terms-link-font-family);padding:var(--spot-terms-link-padding)}.spot-footer__container{display:flex;justify-content:space-between;align-items:center}.spot-footer__powered-by{margin-top:1.5rem}";function b(s){const t=document.createElement("style");t.textContent=s,document.head.appendChild(t)}b(_);class C{constructor(t={}){this.options={location:"body",showTable:!0,optInSelected:!1,apiConfig:{endpoint:"",partnerId:""},quoteRequestData:{},callbacks:{},...t},this._onResize=this._updateLayout.bind(this),this.root=typeof this.options.location=="string"?document.querySelector(this.options.location):this.options.location,this.currentSelection=this.options.optInSelected?"yes":null,this._init()}async _init(){try{const t=await l(this.options.apiConfig.endpoint,this.options.apiConfig.partnerId,this.options.quoteRequestData);if(t.status!=="QUOTE_AVAILABLE"){t.status==="NO_MATCHING_QUOTE"&&this.options.callbacks.noMatchingQuote({status:"NO_MATCHING_QUOTE",data:this.options.quoteRequestData});return}this.quote=t.data,this._renderWidget(),this.options.optInSelected&&this.options.callbacks.onOptIn&&this.options.callbacks.onOptIn({status:"QUOTE_ACCEPTED",spotPrice:this.quote.spotPrice,quoteId:this.quote.id}),this.options.callbacks.onQuoteRetrieved&&this.options.callbacks.onQuoteRetrieved(this.quote)}catch(t){this.options.callbacks.onError({message:t.message,status:t.status,responseBody:t.responseBody})}}_renderWidget(){this.container=document.createElement("div"),this.container.className="spot-refund-guarantee",this.root.appendChild(this.container),Object.entries(this.options.theme||{}).forEach(([r,i])=>{const n=`--${r}`;this.container.style.setProperty(n,i)}),m(this.container,this.quote.communication);const t=document.createElement("div");t.className="spot-content__wrapper",this.container.appendChild(t),f(t,this.quote.communication.bulletPoints),this.options.showTable&&h(t,this.quote.payoutSchedule);const o=u(t,this.quote.spotPrice,this.options.optInSelected);t.appendChild(o),g(this.container,this.quote),window.addEventListener("resize",this._onResize),this._updateLayout(),this._setupOptionListeners(o)}_updateLayout(){const t=window.matchMedia("(min-width: 768px)").matches;this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout",t&&this.options.showTable)}_setupOptionListeners(t){const o=t.querySelectorAll('input[type="radio"]'),r=t.querySelectorAll(".spot-selection__option");o.forEach(i=>{i.addEventListener("change",n=>{var p;const a=n.target.value;this.hideSelectionError(),this.currentSelection=a,r.forEach(d=>d.classList.remove("selected")),(p=n.target.closest(".spot-selection__option"))==null||p.classList.add("selected"),a==="yes"&&this.options.callbacks.onOptIn&&this.options.callbacks.onOptIn({status:"QUOTE_ACCEPTED",spotPrice:this.quote.spotPrice,quoteId:this.quote.id}),a==="no"&&this.options.callbacks.onOptOut&&this.options.callbacks.onOptOut({status:"QUOTE_DECLINED",quoteId:this.quote.id})})})}showSelectionError(){var t;if(!this.errorEl){this.errorEl=document.createElement("div"),this.errorEl.className="spot-selection__error",this.errorEl.textContent="Please make a selection";const o=(t=this.container)==null?void 0:t.querySelector(".spot-selection__options");o&&o.insertAdjacentElement("afterend",this.errorEl)}this.errorEl.style.display="block"}hideSelectionError(){this.errorEl&&(this.errorEl.style.display="none")}validateSelection(){if(!this.container)return!1;const t=!!this.container.querySelector('input[name="selection"]:checked');return t?this.hideSelectionError():this.showSelectionError(),t}getSelection(){var t,o;return this.currentSelection==null?null:{selection:this.currentSelection,quoteId:(t=this.quote)==null?void 0:t.id,spotPrice:(o=this.quote)==null?void 0:o.spotPrice,status:this.currentSelection==="yes"?"QUOTE_ACCEPTED":"QUOTE_DECLINED"}}destroy(){window.removeEventListener("resize",this._onResize),this.container&&this.container.parentNode&&this.container.parentNode.removeChild(this.container)}}return C});
20
+ </svg>`,t}const x=":root{--spot-font-family: Arial;--spot-padding: 1.25rem;--spot-background-color: #ffffff;--spot-font-color: #000000;--spot-border-radius: .5rem;--spot-title-font-size: 1.25rem;--spot-title-font-weight: 700;--spot-title-padding: 0 0 1.25rem 0;--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-padding: 0 .625rem;--spot-radio-border: #000000;--spot-radio-border-radius: .625rem;--spot-radio-checked-background: #000000;--spot-radio-text-font-size: .875rem;--spot-radio-text-font-weight: 400;--spot-radio-text-padding: .625rem;--spot-radio-selection-background: #f4f4f4;--spot-radio-selection-border-radius: .625rem;--spot-radio-selection-padding: .625rem;--spot-recommended-tag-background: #000000;--spot-recommended-tag-font-color: #ffffff;--spot-recommended-tag-font-size: .875rem;--spot-recommended-tag-font-weight: 700;--spot-recommended-tag-padding: .25rem .5rem;--spot-recommended-tag-border-radius: .5rem;--spot-selection-error-font-color: #ff0000;--spot-selection-error-font-size: .875rem;--spot-selection-error-padding: .5rem;--spot-terms-font-size: .75rem;--spot-terms-font-weight: 400;--spot-terms-font-color: #636569;--spot-terms-padding: 0;--spot-terms-link-text-decoration: underline;--spot-terms-link-font-size: .75rem;--spot-terms-link-font-weight: 400;--spot-terms-link-font-color: #636569;--spot-terms-link-padding: 0}.spot-refund-guarantee{font-family:var(--spot-font-family);padding:var(--spot-padding);background-color:var(--spot-background-color);color:var(--spot-font-color);border:.0625rem solid #e0e0e0;border-radius:var(--spot-border-radius);max-width:51rem;margin:1rem}.spot-refund-guarantee *{color:inherit}.spot-header__title{font-size:var(--spot-title-font-size);font-weight:var(--spot-title-font-weight);padding:var(--spot-title-padding);color:var(--spot-title-font-color);font-family:var(--spot-title-font-family);line-height:120%;letter-spacing:-.03125rem}.spot-header__description{font-size:var(--spot-description-font-size);font-weight:var(--spot-description-font-weight);color:var(--spot-description-font-color);font-family:var(--spot-description-font-family);padding:var(--spot-description-padding);line-height:125%;letter-spacing:-.025rem}.spot-content__wrapper{display:flex;flex-direction:column}@media (min-width: 48rem){.spot-content__wrapper.desktop-layout{display:grid;grid-template-columns:1fr 20.3125rem;align-items:start;gap:1rem}.desktop-layout .spot-benefits__list{grid-row:1}.desktop-layout .spot-selection__options{grid-row:2}.desktop-layout .spot-table__container{grid-row:1 / span 2}}@media (max-width: 52.438rem){.spot-selection__recommended-tag{display:inline-block;margin-left:0}}@media (max-width: 47.938rem){.spot-selection__recommended-tag{margin-top:0rem}}@media (max-width: 32.125rem){.spot-selection__recommended-tag{margin-top:.5rem}}@media (max-width: 47.9375rem){.spot-table__container{display:flex;justify-content:center}.spot-selection__recommended-tag{display:inline-block;margin-left:0}.spot-footer__container{flex-direction:column;margin-top:.5rem}.spot-refund__table{width:100%;table-layout:auto}.spot-refund__table th{padding:0rem}}.spot-benefits__list{list-style-type:none;line-height:125%;gap:.5625rem;font-size:var(--spot-bullets-font-size);font-weight:var(--spot-bullets-font-weight);color:var(--spot-bullets-font-color);font-family:var(--spot-bullets-font-family);padding:var(--spot-bullets-padding);margin-block-start:0rem;margin-block-end:0rem}.spot-benefits__list li{margin-bottom:.5rem;display:flex;align-items:flex-start;gap:.5rem}.spot-benefits__list li svg{flex-shrink:0;position:relative;top:.125rem}.spot-table__container{width:100%}.spot-refund__table{max-width:22rem;border-radius:var(--spot-table-border-radius);overflow:hidden;border:.09375rem solid #636569;table-layout:fixed;margin-bottom:1.5rem;margin-top:.25rem;padding:.625rem}.spot-refund__table--dynamic{height:auto!important;min-height:7.5rem}.spot-refund__table td,.spot-refund__table th{padding:.375rem .625rem;text-align:left}.spot-refund__table th{text-align:left;font-size:var(--spot-table-header-font-size);font-weight:var(--spot-table-header-font-weight);color:var(--spot-table-header-font-color);font-family:var(--spot-table-header-font-family);padding:var(--spot-table-header-padding)}.spot-refund__table td{text-align:left;font-size:var(--spot-table-cell-font-size);font-weight:var(--spot-table-cell-font-weight);color:var(--spot-table-cell-font-color);font-family:var(--spot-table-cell-font-family);padding:var(--spot-table-cell-padding)}input[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;width:.75rem;height:.75rem;border:.0625rem solid var(--spot-radio-border);border-radius:var(--spot-radio-border-radius);margin-right:.5rem;position:relative;vertical-align:middle;top:-.0625rem;cursor:pointer}input[type=radio]:checked{background:var(--spot-radio-checked-background);box-shadow:inset 0 0 0 .0625rem #fff}.spot-selection__options{display:flex;flex-direction:column;gap:.5rem}.spot-selection__option{display:block;position:relative;transition:background .2s;cursor:pointer;font-size:var(--spot-radio-text-font-size);font-weight:var(--spot-radio-text-font-weight);color:var(--spot-radio-text-font-color);font-family:var(--spot-radio-text-font-family);padding:var(--spot-radio-text-padding);margin-right:.5rem;gap:.5rem;flex:1 0 0;align-self:stretch}.spot-selection__option.selected{background:var(--spot-radio-selection-background);border-radius:var(--spot-radio-selection-border-radius);padding:var(--spot-radio-selection-padding)}.spot-selection__recommended-tag{background:var(--spot-recommended-tag-background);color:var(--spot-recommended-tag-font-color);font-size:var(--spot-recommended-tag-font-size);font-weight:var(--spot-recommended-tag-font-weight);padding:var(--spot-recommended-tag-padding);border-radius:var(--spot-recommended-tag-border-radius);margin-left:1.5rem;white-space:nowrap}.spot-selection__error{color:var(--spot-selection-error-font-color);font-size:var(--spot-selection-error-font-size);padding:var(--spot-selection-error-padding);display:none}.spot-footer__terms{margin-top:.625rem;margin-right:.25rem;font-size:var(--spot-terms-font-size);font-weight:var(--spot-terms-font-weight);color:var(--spot-terms-font-color);font-family:var(--spot-terms-font-family);padding:var(--spot-terms-padding)}.spot-footer__terms-link{text-decoration:var(--spot-terms-link-text-decoration);font-size:var(--spot-terms-link-font-size);font-weight:var(--spot-terms-link-font-weight);color:var(--spot-terms-link-font-color);font-family:var(--spot-terms-link-font-family);padding:var(--spot-terms-link-padding)}.spot-footer__container{display:flex;justify-content:space-between;align-items:center}.spot-footer__powered-by{margin-top:1.5rem}";function H(s){const e=document.createElement("style");e.textContent=s,document.head.appendChild(e)}H(x);const q={sandbox:"https://api.sandbox.getspot.com/api/v1/quote",production:"https://api.getspot.com/api/v1/quote"};class T{constructor(e={}){this.options={location:"body",showTable:!0,optInSelected:!1,apiConfig:{environment:"production",partnerId:""},quoteRequestData:{},callbacks:{},...e},this._onResize=this._updateLayout.bind(this),this.root=typeof this.options.location=="string"?document.querySelector(this.options.location):this.options.location,this.currentSelection=this.options.optInSelected?"yes":null,this._init()}async _init(){var e,t,o,n;try{_(this.options);const{environment:r,partnerId:a,endpoint:p}=this.options.apiConfig,l=p||q[r],d=await f(l,a,this.options.quoteRequestData);if(d.status!=="QUOTE_AVAILABLE"){d.status==="NO_MATCHING_QUOTE"&&((e=this.options.callbacks)!=null&&e.noMatchingQuote)&&this.options.callbacks.noMatchingQuote({status:"NO_MATCHING_QUOTE",data:this.options.quoteRequestData});return}this.quote=d.data,this._renderWidget(),this.options.optInSelected&&((t=this.options.callbacks)!=null&&t.onOptIn)&&this.options.callbacks.onOptIn({status:"QUOTE_ACCEPTED",spotPrice:this.quote.spotPrice,quoteId:this.quote.id}),(o=this.options.callbacks)!=null&&o.onQuoteRetrieved&&this.options.callbacks.onQuoteRetrieved(this.quote)}catch(r){(n=this.options.callbacks)==null||n.onError({message:r.message,status:r.status,responseBody:r.responseBody})}}_renderWidget(){this.container=document.createElement("div"),this.container.className="spot-refund-guarantee",this.root.appendChild(this.container),Object.entries(this.options.theme||{}).forEach(([o,n])=>{const r=`--${o}`;this.container.style.setProperty(r,n)}),y(this.container,this.quote.communication);const e=document.createElement("div");e.className="spot-content__wrapper",this.container.appendChild(e),C(e,this.quote.communication.bulletPoints),this.options.showTable&&v(e,this.quote.payoutSchedule);const t=E(e,this.quote.spotPrice,this.options.optInSelected);e.appendChild(t),k(this.container,this.quote),window.addEventListener("resize",this._onResize),this._updateLayout(),this._setupOptionListeners(t)}_updateLayout(){const e=window.matchMedia("(min-width: 768px)").matches;this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout",e&&this.options.showTable)}_setupOptionListeners(e){const t=e.querySelectorAll('input[type="radio"]'),o=e.querySelectorAll(".spot-selection__option");t.forEach(n=>{n.addEventListener("change",r=>{var p,l,d;const a=r.target.value;this.hideSelectionError(),this.currentSelection=a,o.forEach(u=>u.classList.remove("selected")),(p=r.target.closest(".spot-selection__option"))==null||p.classList.add("selected"),a==="yes"&&((l=this.options.callbacks)!=null&&l.onOptIn)&&this.options.callbacks.onOptIn({status:"QUOTE_ACCEPTED",spotPrice:this.quote.spotPrice,quoteId:this.quote.id}),a==="no"&&((d=this.options.callbacks)!=null&&d.onOptOut)&&this.options.callbacks.onOptOut({status:"QUOTE_DECLINED",quoteId:this.quote.id})})})}showSelectionError(){var e;if(!this.errorEl){this.errorEl=document.createElement("div"),this.errorEl.className="spot-selection__error",this.errorEl.textContent="Please make a selection";const t=(e=this.container)==null?void 0:e.querySelector(".spot-selection__options");t&&t.insertAdjacentElement("afterend",this.errorEl)}this.errorEl.style.display="block"}hideSelectionError(){this.errorEl&&(this.errorEl.style.display="none")}validateSelection(){if(!this.container)return!1;const e=!!this.container.querySelector('input[name="selection"]:checked');return e?this.hideSelectionError():this.showSelectionError(),e}getSelection(){var e,t;return this.currentSelection==null?null:{selection:this.currentSelection,quoteId:(e=this.quote)==null?void 0:e.id,spotPrice:(t=this.quote)==null?void 0:t.spotPrice,status:this.currentSelection==="yes"?"QUOTE_ACCEPTED":"QUOTE_DECLINED"}}destroy(){window.removeEventListener("resize",this._onResize),this.container&&this.container.parentNode&&this.container.parentNode.removeChild(this.container)}}return T});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getspot/spot-widget",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { fetchQuote } from "./api.js";
2
+ import { validateOptions } from "./validateOptions.js";
2
3
  import {
3
4
  renderHeader,
4
5
  renderBenefits,
@@ -17,13 +18,18 @@ function injectStyles(css) {
17
18
 
18
19
  injectStyles(styles);
19
20
 
21
+ const apiEndpoint = {
22
+ sandbox: "https://api.sandbox.getspot.com/api/v1/quote",
23
+ production: "https://api.getspot.com/api/v1/quote",
24
+ };
25
+
20
26
  class SpotWidget {
21
27
  constructor(options = {}) {
22
28
  this.options = {
23
29
  location: "body",
24
30
  showTable: true,
25
31
  optInSelected: false,
26
- apiConfig: { endpoint: "", partnerId: "" },
32
+ apiConfig: { environment: "production", partnerId: "" },
27
33
  quoteRequestData: {},
28
34
  callbacks: {},
29
35
  ...options,
@@ -41,14 +47,26 @@ class SpotWidget {
41
47
 
42
48
  async _init() {
43
49
  try {
50
+ validateOptions(this.options);
51
+ const {
52
+ environment,
53
+ partnerId,
54
+ endpoint: customEndpoint,
55
+ } = this.options.apiConfig;
56
+
57
+ const endpoint = customEndpoint || apiEndpoint[environment];
58
+
44
59
  const response = await fetchQuote(
45
- this.options.apiConfig.endpoint,
46
- this.options.apiConfig.partnerId,
60
+ endpoint,
61
+ partnerId,
47
62
  this.options.quoteRequestData
48
63
  );
49
64
 
50
65
  if (response.status !== "QUOTE_AVAILABLE") {
51
- if (response.status === "NO_MATCHING_QUOTE") {
66
+ if (
67
+ response.status === "NO_MATCHING_QUOTE" &&
68
+ this.options.callbacks?.noMatchingQuote
69
+ ) {
52
70
  this.options.callbacks.noMatchingQuote({
53
71
  status: "NO_MATCHING_QUOTE",
54
72
  data: this.options.quoteRequestData,
@@ -60,7 +78,7 @@ class SpotWidget {
60
78
  this.quote = response.data;
61
79
  this._renderWidget();
62
80
 
63
- if (this.options.optInSelected && this.options.callbacks.onOptIn) {
81
+ if (this.options.optInSelected && this.options.callbacks?.onOptIn) {
64
82
  this.options.callbacks.onOptIn({
65
83
  status: "QUOTE_ACCEPTED",
66
84
  spotPrice: this.quote.spotPrice,
@@ -68,11 +86,11 @@ class SpotWidget {
68
86
  });
69
87
  }
70
88
 
71
- if (this.options.callbacks.onQuoteRetrieved) {
89
+ if (this.options.callbacks?.onQuoteRetrieved) {
72
90
  this.options.callbacks.onQuoteRetrieved(this.quote);
73
91
  }
74
92
  } catch (err) {
75
- this.options.callbacks.onError({
93
+ this.options.callbacks?.onError({
76
94
  message: err.message,
77
95
  status: err.status,
78
96
  responseBody: err.responseBody,
@@ -81,12 +99,10 @@ class SpotWidget {
81
99
  }
82
100
 
83
101
  _renderWidget() {
84
- // outer container
85
102
  this.container = document.createElement("div");
86
103
  this.container.className = "spot-refund-guarantee";
87
104
  this.root.appendChild(this.container);
88
105
 
89
- // apply theme
90
106
  Object.entries(this.options.theme || {}).forEach(([k, v]) => {
91
107
  const cssVariable = `--${k}`;
92
108
  this.container.style.setProperty(cssVariable, v);
@@ -134,14 +150,14 @@ class SpotWidget {
134
150
  options.forEach((label) => label.classList.remove("selected"));
135
151
  e.target.closest(".spot-selection__option")?.classList.add("selected");
136
152
 
137
- if (val === "yes" && this.options.callbacks.onOptIn) {
153
+ if (val === "yes" && this.options.callbacks?.onOptIn) {
138
154
  this.options.callbacks.onOptIn({
139
155
  status: "QUOTE_ACCEPTED",
140
156
  spotPrice: this.quote.spotPrice,
141
157
  quoteId: this.quote.id,
142
158
  });
143
159
  }
144
- if (val === "no" && this.options.callbacks.onOptOut) {
160
+ if (val === "no" && this.options.callbacks?.onOptOut) {
145
161
  this.options.callbacks.onOptOut({
146
162
  status: "QUOTE_DECLINED",
147
163
  quoteId: this.quote.id,
@@ -0,0 +1,138 @@
1
+ const apiEndpoint = {
2
+ sandbox: "https://api.sandbox.getspot.com/v1/quote",
3
+ production: "https://api.getspot.com/v1/quote",
4
+ };
5
+
6
+ export function validateOptions(options) {
7
+ const {
8
+ apiConfig = {},
9
+ quoteRequestData,
10
+ callbacks = {},
11
+ location,
12
+ theme,
13
+ } = options;
14
+
15
+ const {
16
+ environment = "sandbox",
17
+ partnerId,
18
+ endpoint: customEndpoint,
19
+ } = apiConfig;
20
+
21
+ if (!partnerId || typeof partnerId !== "string") {
22
+ throw new Error("Invalid or missing partnerId in apiConfig");
23
+ }
24
+
25
+ const endpoint = customEndpoint || apiEndpoint[environment];
26
+
27
+ if (!endpoint) {
28
+ throw new Error(`Invalid environment in apiConfig: ${environment}`);
29
+ }
30
+
31
+ if (!quoteRequestData || typeof quoteRequestData !== "object") {
32
+ throw new Error("quoteRequestData must be a non-null object");
33
+ }
34
+
35
+ const requiredFields = [
36
+ "startDate",
37
+ "endDate",
38
+ "currencyCode",
39
+ "eventType",
40
+ "productType",
41
+ "productDuration",
42
+ "productPrice",
43
+ "productId",
44
+ "productName",
45
+ ];
46
+
47
+ requiredFields.forEach((field) => {
48
+ if (
49
+ !Object.prototype.hasOwnProperty.call(quoteRequestData, field) ||
50
+ quoteRequestData[field] === undefined ||
51
+ quoteRequestData[field] === null
52
+ ) {
53
+ throw new Error(`Missing required quoteRequestData field: '${field}'`);
54
+ }
55
+ });
56
+
57
+ const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
58
+ if (!iso8601Regex.test(quoteRequestData.startDate)) {
59
+ throw new Error("startDate must be a valid ISO8601 string");
60
+ }
61
+ if (!iso8601Regex.test(quoteRequestData.endDate)) {
62
+ throw new Error("endDate must be a valid ISO8601 string");
63
+ }
64
+
65
+ if (typeof quoteRequestData.currencyCode !== "string") {
66
+ throw new Error("currencyCode must be a string");
67
+ }
68
+
69
+ const validCurrencyCodes = ["USD", "CAD", "AUD"];
70
+ if (!validCurrencyCodes.includes(quoteRequestData.currencyCode)) {
71
+ throw new Error(`Invalid currency code: ${quoteRequestData.currencyCode}`);
72
+ }
73
+
74
+ if (typeof quoteRequestData.eventType !== "string") {
75
+ throw new Error("eventType must be a string");
76
+ }
77
+
78
+ if (typeof quoteRequestData.productType !== "string") {
79
+ throw new Error("productType must be a string");
80
+ }
81
+
82
+ const validProductTypes = ["Pass", "Trip", "Registration"];
83
+ if (!validProductTypes.includes(quoteRequestData.productType)) {
84
+ throw new Error(
85
+ `productType must be one of ${validProductTypes.join(", ")}`
86
+ );
87
+ }
88
+
89
+ if (typeof quoteRequestData.productDuration !== "string") {
90
+ throw new Error("productDuration must be a string");
91
+ }
92
+
93
+ const validDurations = ["Daily", "Seasonal", "Trip", "Event"];
94
+ if (!validDurations.includes(quoteRequestData.productDuration)) {
95
+ throw new Error(
96
+ `productDuration must be one of ${validDurations.join(", ")}`
97
+ );
98
+ }
99
+
100
+ if (
101
+ typeof quoteRequestData.productPrice !== "number" ||
102
+ isNaN(quoteRequestData.productPrice)
103
+ ) {
104
+ throw new Error("productPrice must be a valid number");
105
+ }
106
+
107
+ if (typeof quoteRequestData.productId !== "string") {
108
+ throw new Error("productId must be a string");
109
+ }
110
+
111
+ if (typeof quoteRequestData.productName !== "string") {
112
+ throw new Error("productName must be a string");
113
+ }
114
+
115
+ const callbackNames = [
116
+ "onOptIn",
117
+ "onOptOut",
118
+ "onQuoteRetrieved",
119
+ "onError",
120
+ "noMatchingQuote",
121
+ ];
122
+ callbackNames.forEach((cbName) => {
123
+ const cb = callbacks[cbName];
124
+ if (cb && typeof cb !== "function") {
125
+ throw new Error(`Callback '${cbName}' must be a function.`);
126
+ }
127
+ });
128
+
129
+ if (typeof location === "string" && !document.querySelector(location)) {
130
+ throw new Error(`Invalid location selector: '${location}'`);
131
+ }
132
+
133
+ if (theme && typeof theme !== "object") {
134
+ throw new Error(
135
+ "Theme must be an object with CSS variables, do not include the '--' prefix"
136
+ );
137
+ }
138
+ }