@getspot/spot-widget 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,13 @@
1
1
 
2
- > @getspot/spot-widget@0.1.0 build /builds/getspot/spot-widget/packages/core
2
+ > @getspot/spot-widget@0.1.3 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.98 kB │ gzip: 6.30 kB
12
- dist/index.es.js 21.41 kB │ gzip: 6.46 kB
13
- ✓ built in 139ms
11
+ dist/index.umd.js 21.85 kB │ gzip: 7.00 kB
12
+ dist/index.es.js 23.88 kB │ gzip: 7.23 kB
13
+ ✓ built in 145ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @getspot/spot-widget
2
2
 
3
+ ## 0.1.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 4fe833f: add validation
8
+
9
+ ## 0.1.2
10
+
11
+ ### Patch Changes
12
+
13
+ - 5d7ddb4: add configuration validation
14
+
15
+ ## 0.1.1
16
+
17
+ ### Patch Changes
18
+
19
+ - 6fb40ff: theming changes and cleanup
20
+
3
21
  ## 0.1.0
4
22
 
5
23
  ### Minor Changes
package/dist/index.es.js CHANGED
@@ -1,112 +1,196 @@
1
- async function m(n, t, e) {
1
+ async function b(s, e, t) {
2
2
  try {
3
- const o = await fetch(n, {
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(e)
10
- });
11
- console.log("res: ", o);
12
- const i = await o.json();
13
- return console.log("body: ", i), o.ok ? i : { status: "ERROR", responseBody: i };
9
+ body: JSON.stringify(t)
10
+ }), r = await o.json();
11
+ if (!o.ok) {
12
+ const n = new Error((r == null ? void 0 : r.message) || "Failed to fetch quote");
13
+ throw n.status = o.status, n.responseBody = r, n;
14
+ }
15
+ return r;
14
16
  } catch (o) {
15
- throw console.log("what"), console.error("Failed to fetch quote:", { payload: e, error: o }), new Error(
16
- o instanceof Error ? o.message : "Unknown error occurred while fetching quote"
17
- );
17
+ throw o instanceof Error ? o : new Error("Unknown error occurred while fetching quote");
18
18
  }
19
19
  }
20
- function r(n, { text: t, className: e, parent: o, innerHTML: i } = {}) {
21
- const s = document.createElement(n);
22
- return e && (s.className = e), t != null && (s.textContent = t), i != null && (s.innerHTML = i), o && o.appendChild(s), s;
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: r,
30
+ theme: n
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((d) => {
53
+ if (!Object.prototype.hasOwnProperty.call(t, d) || t[d] === void 0 || t[d] === null)
54
+ throw new Error(`Missing required quoteRequestData field: '${d}'`);
55
+ });
56
+ const c = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;
57
+ if (!c.test(t.startDate))
58
+ throw new Error("startDate must be a valid ISO8601 string");
59
+ if (!c.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((d) => {
94
+ const g = o[d];
95
+ if (g && typeof g != "function")
96
+ throw new Error(`Callback '${d}' must be a function.`);
97
+ }), typeof r == "string" && !document.querySelector(r))
98
+ throw new Error(`Invalid location selector: '${r}'`);
99
+ if (n && typeof n != "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: r } = {}) {
105
+ const n = document.createElement(s);
106
+ return t && (n.className = t), e != null && (n.textContent = e), r != null && (n.innerHTML = r), o && o.appendChild(n), n;
23
107
  }
24
- function h(n, { name: t, description: e }) {
25
- r("div", {
108
+ function y(s, { name: e, description: t }) {
109
+ i("div", {
26
110
  className: "spot-header__title",
27
- text: t,
28
- parent: n
29
- }), r("div", {
30
- className: "spot-header__description",
31
111
  text: e,
32
- parent: n
112
+ parent: s
113
+ }), i("div", {
114
+ className: "spot-header__description",
115
+ text: t,
116
+ parent: s
33
117
  });
34
118
  }
35
- function u(n, t = []) {
36
- const e = r("ul", {
119
+ function C(s, e = []) {
120
+ const t = i("ul", {
37
121
  className: "spot-benefits__list",
38
- parent: n
122
+ parent: s
39
123
  });
40
- t.forEach((o) => {
41
- const i = r("li", { parent: e });
42
- i.innerHTML = `
124
+ e.forEach((o) => {
125
+ const r = i("li", { parent: t });
126
+ r.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>`, r("span", { text: o, parent: i });
130
+ </svg>`, i("span", { text: o, parent: r });
47
131
  });
48
132
  }
49
- function g(n, t = []) {
50
- const e = r("div", {
133
+ function v(s, e = []) {
134
+ const t = i("div", {
51
135
  className: "spot-table__container",
52
- parent: n
53
- }), o = r("table", {
136
+ parent: s
137
+ }), o = i("table", {
54
138
  className: "spot-refund__table spot-table--dynamic",
55
- parent: e
56
- }), i = r("thead", { parent: o }), s = r("tr", { parent: i });
57
- r("th", { text: "When you cancel", parent: s }), r("th", { text: "You will receive", parent: s });
58
- const a = r("tbody", { parent: o });
59
- t.forEach(({ text: l, percent: p, amount: c }) => {
60
- const d = r("tr", { parent: a });
61
- r("td", { text: l, parent: d });
62
- const f = p === "Not eligible for refund" ? "Not eligible for a refund" : `$${c} refund`;
63
- r("td", { text: f, parent: d });
139
+ parent: t
140
+ }), r = i("thead", { parent: o }), n = i("tr", { parent: r });
141
+ i("th", { text: "When you cancel", parent: n }), i("th", { text: "You will receive", parent: n });
142
+ const a = i("tbody", { parent: o });
143
+ e.forEach(({ text: p, percent: l, amount: f }) => {
144
+ const m = i("tr", { parent: a });
145
+ i("td", { text: p, parent: m });
146
+ const c = l === "Not eligible for refund" ? "Not eligible for a refund" : `$${f} refund`;
147
+ i("td", { text: c, parent: m });
64
148
  });
65
149
  }
66
- function _(n, t, e) {
67
- const o = r("div", {
150
+ function E(s, e, t) {
151
+ const o = i("div", {
68
152
  className: "spot-selection__options",
69
- parent: n
70
- }), i = r("label", {
71
- className: `spot-selection__option ${e ? "selected" : ""}`,
153
+ parent: s
154
+ }), r = i("label", {
155
+ className: `spot-selection__option ${t ? "selected" : ""}`,
72
156
  parent: o
73
- }), s = r("input", { parent: i });
74
- s.type = "radio", s.name = "selection", s.value = "yes", e && (s.checked = !0), r("strong", {
75
- text: `Yes, protect my booking for $${t}`,
76
- parent: i
77
- }), r("span", {
157
+ }), n = i("input", { parent: r });
158
+ n.type = "radio", n.name = "selection", n.value = "yes", t && (n.checked = !0), i("strong", {
159
+ text: `Yes, protect my booking for $${e}`,
160
+ parent: r
161
+ }), i("span", {
78
162
  className: "spot-selection__recommended-tag",
79
163
  text: "Recommended",
80
- parent: i
164
+ parent: r
81
165
  });
82
- const a = r("label", {
166
+ const a = i("label", {
83
167
  className: "spot-selection__option",
84
168
  parent: o
85
- }), l = r("input", { parent: a });
86
- return l.type = "radio", l.name = "selection", l.value = "no", r("span", { text: "No, do not protect my booking", parent: a }), 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(n, t) {
89
- const e = r("div", {
172
+ function k(s, e) {
173
+ const t = i("div", {
90
174
  className: "spot-footer__container",
91
- parent: n
92
- }), o = r("div", {
175
+ parent: s
176
+ }), o = i("div", {
93
177
  className: "spot-footer__terms",
94
- parent: e
178
+ parent: t
95
179
  });
96
- r("span", {
97
- innerHTML: t.communication.legalDisclaimer,
180
+ i("span", {
181
+ innerHTML: e.communication.legalDisclaimer,
98
182
  parent: o
99
- }), r("br", { parent: o }), r("a", {
100
- href: t.communication.termsAndConditionsUrl,
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
187
  parent: o
104
188
  });
105
- const i = r("p", {
189
+ const r = i("p", {
106
190
  className: "spot-footer__powered-by",
107
- parent: e
191
+ parent: t
108
192
  });
109
- return i.innerHTML = `
193
+ return r.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,70 +205,87 @@ function b(n, t) {
121
205
  <rect width="45.405" height="14.8867" fill="white" transform="translate(87 8)"/>
122
206
  </clipPath>
123
207
  </defs>
124
- </svg>`, e;
208
+ </svg>`, t;
125
209
  }
126
- const v = ":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-title-font-color: var(--spot-font-color);--spot-title-font-family: var(--spot-font-family);--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-description-font-color: var(--spot-font-color);--spot-description-font-family: var(--spot-font-family);--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-font-color: var(--spot-font-color);--spot-bullets-font-family: var(--spot-font-family);--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-font-color: var(--spot-font-color);--spot-table-header-font-family: var(--spot-font-family);--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-font-color: var(--spot-font-color);--spot-table-cell-font-family: var(--spot-font-family);--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-font-color: var(--spot-font-color);--spot-radio-text-font-family: var(--spot-font-family);--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-font-family: var(--spot-font-family);--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-font-family: var(--spot-font-family);--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 *{font-family:inherit;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 y(n) {
128
- const t = document.createElement("style");
129
- t.textContent = n, 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
- y(v);
132
- class C {
133
- constructor(t = {}) {
215
+ H(x);
216
+ const q = {
217
+ sandbox: "https://api.sandbox.getspot.com/v1/quote",
218
+ production: "https://api.getspot.com/v1/quote"
219
+ };
220
+ class S {
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() {
145
233
  try {
146
- const t = await m(
147
- this.options.apiConfig.endpoint,
148
- this.options.apiConfig.partnerId,
234
+ _(this.options);
235
+ const {
236
+ environment: e,
237
+ partnerId: t,
238
+ endpoint: o
239
+ } = this.options.apiConfig, r = o || q[e], n = await b(
240
+ r,
241
+ t,
149
242
  this.options.quoteRequestData
150
243
  );
151
- if (t.status !== "QUOTE_AVAILABLE") {
152
- t.status === "NO_MATCHING_QUOTE" && this._emit("noMatchingQuote");
244
+ if (n.status !== "QUOTE_AVAILABLE") {
245
+ n.status === "NO_MATCHING_QUOTE" && this.options.callbacks.noMatchingQuote({
246
+ status: "NO_MATCHING_QUOTE",
247
+ data: this.options.quoteRequestData
248
+ });
153
249
  return;
154
250
  }
155
- this.quote = t.data, this._renderWidget(), this.options.optInSelected && this.options.callbacks.onOptIn && this.options.callbacks.onOptIn({
251
+ this.quote = n.data, this._renderWidget(), this.options.optInSelected && this.options.callbacks.onOptIn && this.options.callbacks.onOptIn({
156
252
  status: "QUOTE_ACCEPTED",
157
253
  spotPrice: this.quote.spotPrice,
158
254
  quoteId: this.quote.id
159
255
  }), this.options.callbacks.onQuoteRetrieved && this.options.callbacks.onQuoteRetrieved(this.quote);
160
- } catch (t) {
161
- this._emit("error", t);
256
+ } catch (e) {
257
+ this.options.callbacks.onError({
258
+ message: e.message,
259
+ status: e.status,
260
+ responseBody: e.responseBody
261
+ });
162
262
  }
163
263
  }
164
264
  _renderWidget() {
165
- this.container = document.createElement("div"), this.container.className = "spot-refund-guarantee", this.root.appendChild(this.container), Object.entries(this.options.theme || {}).forEach(
166
- ([o, i]) => this.container.style.setProperty(o, i)
167
- ), h(this.container, this.quote.communication);
168
- const t = document.createElement("div");
169
- t.className = "spot-content__wrapper", this.container.appendChild(t), u(t, this.quote.communication.bulletPoints), this.options.showTable && g(t, this.quote.payoutSchedule);
170
- const e = _(
171
- t,
265
+ this.container = document.createElement("div"), this.container.className = "spot-refund-guarantee", this.root.appendChild(this.container), Object.entries(this.options.theme || {}).forEach(([o, r]) => {
266
+ const n = `--${o}`;
267
+ this.container.style.setProperty(n, r);
268
+ }), y(this.container, this.quote.communication);
269
+ const e = document.createElement("div");
270
+ e.className = "spot-content__wrapper", this.container.appendChild(e), C(e, this.quote.communication.bulletPoints), this.options.showTable && v(e, this.quote.payoutSchedule);
271
+ const t = E(
272
+ e,
172
273
  this.quote.spotPrice,
173
274
  this.options.optInSelected
174
275
  );
175
- t.appendChild(e), b(this.container, this.quote), window.addEventListener("resize", this._onResize), this._updateLayout(), this._setupOptionListeners(e);
276
+ e.appendChild(t), k(this.container, this.quote), window.addEventListener("resize", this._onResize), this._updateLayout(), this._setupOptionListeners(t);
176
277
  }
177
278
  _updateLayout() {
178
- const t = window.matchMedia("(min-width: 768px)").matches;
179
- this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout", t && this.options.showTable);
279
+ const e = window.matchMedia("(min-width: 768px)").matches;
280
+ this.container.querySelector(".spot-content__wrapper").classList.toggle("desktop-layout", e && this.options.showTable);
180
281
  }
181
- _setupOptionListeners(t) {
182
- const e = t.querySelectorAll('input[type="radio"]'), o = t.querySelectorAll(".spot-selection__option");
183
- e.forEach((i) => {
184
- i.addEventListener("change", (s) => {
185
- var l;
186
- const a = s.target.value;
187
- this.hideSelectionError(), this.currentSelection = a, o.forEach((p) => p.classList.remove("selected")), (l = s.target.closest(".spot-selection__option")) == null || l.classList.add("selected"), a === "yes" && this.options.callbacks.onOptIn && this.options.callbacks.onOptIn({
282
+ _setupOptionListeners(e) {
283
+ const t = e.querySelectorAll('input[type="radio"]'), o = e.querySelectorAll(".spot-selection__option");
284
+ t.forEach((r) => {
285
+ r.addEventListener("change", (n) => {
286
+ var p;
287
+ const a = n.target.value;
288
+ this.hideSelectionError(), this.currentSelection = a, o.forEach((l) => l.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({
188
289
  status: "QUOTE_ACCEPTED",
189
290
  spotPrice: this.quote.spotPrice,
190
291
  quoteId: this.quote.id
@@ -196,13 +297,13 @@ class C {
196
297
  });
197
298
  }
198
299
  showSelectionError() {
199
- var t;
300
+ var e;
200
301
  if (!this.errorEl) {
201
302
  this.errorEl = document.createElement("div"), this.errorEl.className = "spot-selection__error", this.errorEl.textContent = "Please make a selection";
202
- const e = (t = this.container) == null ? void 0 : t.querySelector(
303
+ const t = (e = this.container) == null ? void 0 : e.querySelector(
203
304
  ".spot-selection__options"
204
305
  );
205
- e && e.insertAdjacentElement("afterend", this.errorEl);
306
+ t && t.insertAdjacentElement("afterend", this.errorEl);
206
307
  }
207
308
  this.errorEl.style.display = "block";
208
309
  }
@@ -211,28 +312,24 @@ class C {
211
312
  }
212
313
  validateSelection() {
213
314
  if (!this.container) return !1;
214
- const t = !!this.container.querySelector(
315
+ const e = !!this.container.querySelector(
215
316
  'input[name="selection"]:checked'
216
317
  );
217
- return t ? this.hideSelectionError() : this.showSelectionError(), t;
318
+ return e ? this.hideSelectionError() : this.showSelectionError(), e;
218
319
  }
219
320
  getSelection() {
220
- var t, e;
221
- return {
321
+ var e, t;
322
+ return this.currentSelection == null ? null : {
222
323
  selection: this.currentSelection,
223
- quoteId: (t = this.quote) == null ? void 0 : t.id,
224
- spotPrice: (e = this.quote) == null ? void 0 : e.spotPrice,
324
+ quoteId: (e = this.quote) == null ? void 0 : e.id,
325
+ spotPrice: (t = this.quote) == null ? void 0 : t.spotPrice,
225
326
  status: this.currentSelection === "yes" ? "QUOTE_ACCEPTED" : "QUOTE_DECLINED"
226
327
  };
227
328
  }
228
- _emit(t, e) {
229
- const o = this.options.callbacks[t];
230
- o && o(e);
231
- }
232
329
  destroy() {
233
330
  window.removeEventListener("resize", this._onResize), this.container && this.container.parentNode && this.container.parentNode.removeChild(this.container);
234
331
  }
235
332
  }
236
333
  export {
237
- C as default
334
+ S as default
238
335
  };
package/dist/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
- (function(p,e){typeof exports=="object"&&typeof module<"u"?module.exports=e():typeof define=="function"&&define.amd?define(e):(p=typeof globalThis<"u"?globalThis:p||self,p.SpotWidget=e())})(this,function(){"use strict";async function p(n,t,o){try{const r=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json","X-Spot-Partner-Id":t},body:JSON.stringify(o)});console.log("res: ",r);const i=await r.json();return console.log("body: ",i),r.ok?i:{status:"ERROR",responseBody:i}}catch(r){throw console.log("what"),console.error("Failed to fetch quote:",{payload:o,error:r}),new Error(r instanceof Error?r.message:"Unknown error occurred while fetching quote")}}function e(n,{text:t,className:o,parent:r,innerHTML:i}={}){const s=document.createElement(n);return o&&(s.className=o),t!=null&&(s.textContent=t),i!=null&&(s.innerHTML=i),r&&r.appendChild(s),s}function f(n,{name:t,description:o}){e("div",{className:"spot-header__title",text:t,parent:n}),e("div",{className:"spot-header__description",text:o,parent:n})}function m(n,t=[]){const o=e("ul",{className:"spot-benefits__list",parent:n});t.forEach(r=>{const i=e("li",{parent:o});i.innerHTML=`
1
+ (function(l,c){typeof exports=="object"&&typeof module<"u"?module.exports=c():typeof define=="function"&&define.amd?define(c):(l=typeof globalThis<"u"?globalThis:l||self,l.SpotWidget=c())})(this,function(){"use strict";async function l(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 i=new Error((n==null?void 0:n.message)||"Failed to fetch quote");throw i.status=o.status,i.responseBody=n,i}return n}catch(o){throw o instanceof Error?o:new Error("Unknown error occurred while fetching quote")}}const c={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:i}=s,{environment:a="sandbox",partnerId:p,endpoint:f}=e;if(!p||typeof p!="string")throw new Error("Invalid or missing partnerId in apiConfig");if(!(f||c[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(d=>{if(!Object.prototype.hasOwnProperty.call(t,d)||t[d]===void 0||t[d]===null)throw new Error(`Missing required quoteRequestData field: '${d}'`)});const m=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z$/;if(!m.test(t.startDate))throw new Error("startDate must be a valid ISO8601 string");if(!m.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(d=>{const w=o[d];if(w&&typeof w!="function")throw new Error(`Callback '${d}' must be a function.`)}),typeof n=="string"&&!document.querySelector(n))throw new Error(`Invalid location selector: '${n}'`);if(i&&typeof i!="object")throw new Error("Theme must be an object with CSS variables, do not include the '--' prefix")}function r(s,{text:e,className:t,parent:o,innerHTML:n}={}){const i=document.createElement(s);return t&&(i.className=t),e!=null&&(i.textContent=e),n!=null&&(i.innerHTML=n),o&&o.appendChild(i),i}function y(s,{name:e,description:t}){r("div",{className:"spot-header__title",text:e,parent:s}),r("div",{className:"spot-header__description",text:t,parent:s})}function C(s,e=[]){const t=r("ul",{className:"spot-benefits__list",parent:s});e.forEach(o=>{const n=r("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(n,t=[]){const o=e("div",{className:"spot-table__container",parent:n}),r=e("table",{className:"spot-refund__table spot-table--dynamic",parent:o}),i=e("thead",{parent:r}),s=e("tr",{parent:i});e("th",{text:"When you cancel",parent:s}),e("th",{text:"You will receive",parent:s});const a=e("tbody",{parent:r});t.forEach(({text:l,percent:d,amount:y})=>{const c=e("tr",{parent:a});e("td",{text:l,parent:c});const C=d==="Not eligible for refund"?"Not eligible for a refund":`$${y} refund`;e("td",{text:C,parent:c})})}function u(n,t,o){const r=e("div",{className:"spot-selection__options",parent:n}),i=e("label",{className:`spot-selection__option ${o?"selected":""}`,parent:r}),s=e("input",{parent:i});s.type="radio",s.name="selection",s.value="yes",o&&(s.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}),l=e("input",{parent:a});return l.type="radio",l.name="selection",l.value="no",e("span",{text:"No, do not protect my booking",parent:a}),r}function g(n,t){const o=e("div",{className:"spot-footer__container",parent:n}),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>`,r("span",{text:o,parent:n})})}function v(s,e=[]){const t=r("div",{className:"spot-table__container",parent:s}),o=r("table",{className:"spot-refund__table spot-table--dynamic",parent:t}),n=r("thead",{parent:o}),i=r("tr",{parent:n});r("th",{text:"When you cancel",parent:i}),r("th",{text:"You will receive",parent:i});const a=r("tbody",{parent:o});e.forEach(({text:p,percent:f,amount:h})=>{const u=r("tr",{parent:a});r("td",{text:p,parent:u});const m=f==="Not eligible for refund"?"Not eligible for a refund":`$${h} refund`;r("td",{text:m,parent:u})})}function E(s,e,t){const o=r("div",{className:"spot-selection__options",parent:s}),n=r("label",{className:`spot-selection__option ${t?"selected":""}`,parent:o}),i=r("input",{parent:n});i.type="radio",i.name="selection",i.value="yes",t&&(i.checked=!0),r("strong",{text:`Yes, protect my booking for $${e}`,parent:n}),r("span",{className:"spot-selection__recommended-tag",text:"Recommended",parent:n});const a=r("label",{className:"spot-selection__option",parent:o}),p=r("input",{parent:a});return p.type="radio",p.name="selection",p.value="no",r("span",{text:"No, do not protect my booking",parent:a}),o}function k(s,e){const t=r("div",{className:"spot-footer__container",parent:s}),o=r("div",{className:"spot-footer__terms",parent:t});r("span",{innerHTML:e.communication.legalDisclaimer,parent:o}),r("br",{parent:o}),r("a",{href:e.communication.termsAndConditionsUrl,className:"spot-footer__terms-link",text:"Refund Guarantee Terms and Conditions",parent:o});const n=r("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 b=":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-title-font-color: var(--spot-font-color);--spot-title-font-family: var(--spot-font-family);--spot-description-font-size: .875rem;--spot-description-font-weight: 400;--spot-description-padding: 0 0 .5rem 0;--spot-description-font-color: var(--spot-font-color);--spot-description-font-family: var(--spot-font-family);--spot-bullets-font-size: .875rem;--spot-bullets-font-weight: 400;--spot-bullets-font-color: var(--spot-font-color);--spot-bullets-font-family: var(--spot-font-family);--spot-bullets-padding: .3125rem;--spot-table-border-radius: .625rem;--spot-table-header-font-size: .875rem;--spot-table-header-font-weight: 700;--spot-table-header-font-color: var(--spot-font-color);--spot-table-header-font-family: var(--spot-font-family);--spot-table-header-padding: 0 .5rem .625rem;--spot-table-cell-font-size: .815rem;--spot-table-cell-font-weight: 400;--spot-table-cell-font-color: var(--spot-font-color);--spot-table-cell-font-family: var(--spot-font-family);--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-font-color: var(--spot-font-color);--spot-radio-text-font-family: var(--spot-font-family);--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-font-family: var(--spot-font-family);--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-font-family: var(--spot-font-family);--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 *{font-family:inherit;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 _(n){const t=document.createElement("style");t.textContent=n,document.head.appendChild(t)}_(b);class v{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 p(this.options.apiConfig.endpoint,this.options.apiConfig.partnerId,this.options.quoteRequestData);if(t.status!=="QUOTE_AVAILABLE"){t.status==="NO_MATCHING_QUOTE"&&this._emit("noMatchingQuote");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._emit("error",t)}}_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])=>this.container.style.setProperty(r,i)),f(this.container,this.quote.communication);const t=document.createElement("div");t.className="spot-content__wrapper",this.container.appendChild(t),m(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",s=>{var l;const a=s.target.value;this.hideSelectionError(),this.currentSelection=a,r.forEach(d=>d.classList.remove("selected")),(l=s.target.closest(".spot-selection__option"))==null||l.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{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"}}_emit(t,o){const r=this.options.callbacks[t];r&&r(o)}destroy(){window.removeEventListener("resize",this._onResize),this.container&&this.container.parentNode&&this.container.parentNode.removeChild(this.container)}}return v});
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/v1/quote",production:"https://api.getspot.com/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(){try{_(this.options);const{environment:e,partnerId:t,endpoint:o}=this.options.apiConfig,n=o||q[e],i=await l(n,t,this.options.quoteRequestData);if(i.status!=="QUOTE_AVAILABLE"){i.status==="NO_MATCHING_QUOTE"&&this.options.callbacks.noMatchingQuote({status:"NO_MATCHING_QUOTE",data:this.options.quoteRequestData});return}this.quote=i.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(e){this.options.callbacks.onError({message:e.message,status:e.status,responseBody:e.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 i=`--${o}`;this.container.style.setProperty(i,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",i=>{var p;const a=i.target.value;this.hideSelectionError(),this.currentSelection=a,o.forEach(f=>f.classList.remove("selected")),(p=i.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 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.0",
3
+ "version": "0.1.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/api.js CHANGED
@@ -15,23 +15,18 @@ export async function fetchQuote(endpoint, partnerId, payload) {
15
15
  },
16
16
  body: JSON.stringify(payload),
17
17
  });
18
- console.log("res: ", res);
19
-
20
18
  const body = await res.json();
21
- console.log("body: ", body);
22
-
23
19
  if (!res.ok) {
24
- return { status: "ERROR", responseBody: body };
20
+ const error = new Error(body?.message || "Failed to fetch quote");
21
+ error.status = res.status;
22
+ error.responseBody = body;
23
+ throw error;
25
24
  }
26
25
 
27
26
  return body;
28
27
  } catch (err) {
29
- console.log("what");
30
- console.error("Failed to fetch quote:", { payload, error: err });
31
- throw new Error(
32
- err instanceof Error
33
- ? err.message
34
- : "Unknown error occurred while fetching quote"
35
- );
28
+ throw err instanceof Error
29
+ ? err
30
+ : new Error("Unknown error occurred while fetching quote");
36
31
  }
37
32
  }
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/v1/quote",
23
+ production: "https://api.getspot.com/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,15 +47,27 @@ 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
66
  if (response.status === "NO_MATCHING_QUOTE") {
52
- this._emit("noMatchingQuote");
67
+ this.options.callbacks.noMatchingQuote({
68
+ status: "NO_MATCHING_QUOTE",
69
+ data: this.options.quoteRequestData,
70
+ });
53
71
  }
54
72
  return;
55
73
  }
@@ -69,20 +87,23 @@ class SpotWidget {
69
87
  this.options.callbacks.onQuoteRetrieved(this.quote);
70
88
  }
71
89
  } catch (err) {
72
- this._emit("error", err);
90
+ this.options.callbacks.onError({
91
+ message: err.message,
92
+ status: err.status,
93
+ responseBody: err.responseBody,
94
+ });
73
95
  }
74
96
  }
75
97
 
76
98
  _renderWidget() {
77
- // outer container
78
99
  this.container = document.createElement("div");
79
100
  this.container.className = "spot-refund-guarantee";
80
101
  this.root.appendChild(this.container);
81
102
 
82
- // apply theme
83
- Object.entries(this.options.theme || {}).forEach(([k, v]) =>
84
- this.container.style.setProperty(k, v)
85
- );
103
+ Object.entries(this.options.theme || {}).forEach(([k, v]) => {
104
+ const cssVariable = `--${k}`;
105
+ this.container.style.setProperty(cssVariable, v);
106
+ });
86
107
 
87
108
  renderHeader(this.container, this.quote.communication);
88
109
  const cw = document.createElement("div");
@@ -182,6 +203,8 @@ class SpotWidget {
182
203
  }
183
204
 
184
205
  getSelection() {
206
+ if (this.currentSelection == null) return null;
207
+
185
208
  return {
186
209
  selection: this.currentSelection,
187
210
  quoteId: this.quote?.id,
@@ -190,12 +213,6 @@ class SpotWidget {
190
213
  this.currentSelection === "yes" ? "QUOTE_ACCEPTED" : "QUOTE_DECLINED",
191
214
  };
192
215
  }
193
- _emit(event, data) {
194
- const callback = this.options.callbacks[event];
195
- if (callback) {
196
- callback(data);
197
- }
198
- }
199
216
 
200
217
  destroy() {
201
218
  window.removeEventListener("resize", this._onResize);
package/src/styles.css CHANGED
@@ -8,33 +8,23 @@
8
8
  --spot-title-font-size: 1.25rem;
9
9
  --spot-title-font-weight: 700;
10
10
  --spot-title-padding: 0 0 1.25rem 0;
11
- --spot-title-font-color: var(--spot-font-color);
12
- --spot-title-font-family: var(--spot-font-family);
13
11
 
14
12
  --spot-description-font-size: 0.875rem;
15
13
  --spot-description-font-weight: 400;
16
14
  --spot-description-padding: 0 0 0.5rem 0;
17
- --spot-description-font-color: var(--spot-font-color);
18
- --spot-description-font-family: var(--spot-font-family);
19
15
 
20
16
  --spot-bullets-font-size: 0.875rem;
21
17
  --spot-bullets-font-weight: 400;
22
- --spot-bullets-font-color: var(--spot-font-color);
23
- --spot-bullets-font-family: var(--spot-font-family);
24
18
  --spot-bullets-padding: 0.3125rem;
25
19
 
26
20
  --spot-table-border-radius: 0.625rem;
27
21
 
28
22
  --spot-table-header-font-size: 0.875rem;
29
23
  --spot-table-header-font-weight: 700;
30
- --spot-table-header-font-color: var(--spot-font-color);
31
- --spot-table-header-font-family: var(--spot-font-family);
32
24
  --spot-table-header-padding: 0 0.5rem 0.625rem;
33
25
 
34
26
  --spot-table-cell-font-size: 0.815rem;
35
27
  --spot-table-cell-font-weight: 400;
36
- --spot-table-cell-font-color: var(--spot-font-color);
37
- --spot-table-cell-font-family: var(--spot-font-family);
38
28
  --spot-table-cell-padding: 0 0.625rem;
39
29
 
40
30
  --spot-radio-border: #000000;
@@ -43,8 +33,6 @@
43
33
 
44
34
  --spot-radio-text-font-size: 0.875rem;
45
35
  --spot-radio-text-font-weight: 400;
46
- --spot-radio-text-font-color: var(--spot-font-color);
47
- --spot-radio-text-font-family: var(--spot-font-family);
48
36
  --spot-radio-text-padding: 0.625rem;
49
37
 
50
38
  --spot-radio-selection-background: #f4f4f4;
@@ -65,14 +53,12 @@
65
53
  --spot-terms-font-size: 0.75rem;
66
54
  --spot-terms-font-weight: 400;
67
55
  --spot-terms-font-color: #636569;
68
- --spot-terms-font-family: var(--spot-font-family);
69
56
  --spot-terms-padding: 0;
70
57
 
71
58
  --spot-terms-link-text-decoration: underline;
72
59
  --spot-terms-link-font-size: 0.75rem;
73
60
  --spot-terms-link-font-weight: 400;
74
61
  --spot-terms-link-font-color: #636569;
75
- --spot-terms-link-font-family: var(--spot-font-family);
76
62
  --spot-terms-link-padding: 0;
77
63
  }
78
64
 
@@ -88,7 +74,6 @@
88
74
  }
89
75
 
90
76
  .spot-refund-guarantee * {
91
- font-family: inherit;
92
77
  color: inherit;
93
78
  }
94
79
 
@@ -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
+ }