notify-me-wl 1.4.3 → 1.4.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.
- package/README.md +11 -8
- package/index.html +18 -5
- package/index.js +369 -229
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
This JavaScript widget allows users to register their interest in a product that is either "coming soon" or "out of stock" (aka "notify me"). Depending on the `data-type` attribute provided, the widget will display appropriate messages and functionality.
|
|
4
4
|
|
|
5
5
|
#### Features:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
6
|
+
- Clean, centered modal that inherits the host store's font and stays out of the theme's way (all styles are namespaced under `#twc-nm-overlay`).
|
|
7
|
+
- Dynamically display a labeled form to collect user information (email, mobile, first/last name, country, province).
|
|
8
|
+
- Adjust form copy and button text based on the widget type (`notify-me` or `coming-soon`).
|
|
9
|
+
- Inline success/error feedback (no browser `alert`s); the submit button shows a "Sending…" loading state.
|
|
10
|
+
- Closes on the × button, clicking the backdrop, or pressing `Esc`; locks background scroll while open and respects `prefers-reduced-motion`.
|
|
11
|
+
- Optional email/SMS marketing opt-ins.
|
|
12
|
+
- Two auth modes: bundled server token (default) or Shopify App Proxy — see [Configuration](#configuration).
|
|
10
13
|
|
|
11
14
|
### Installation
|
|
12
15
|
|
|
@@ -27,7 +30,7 @@ Add a button to your HTML where you want the widget to appear. The button should
|
|
|
27
30
|
|
|
28
31
|
### Configuration
|
|
29
32
|
|
|
30
|
-
- `data-fields`: A JSON array specifying the fields to include in the form. Possible values are `"mobile"`, `"firstName"`, and `"
|
|
33
|
+
- `data-fields`: A JSON array specifying the optional fields to include in the form. Possible values are `"email"`, `"mobile"`, `"firstName"`, `"lastName"`, `"countryCode"`, and `"provinceCode"`. Defaults to `["email"]`. (A required Size selector is always shown and populated from the product's variants.)
|
|
31
34
|
- `data-type`: Specifies the type of the widget. Possible values are `"notify-me"` and `"coming-soon"`.
|
|
32
35
|
- `data-auth`: Auth mode. `"token"` (default) uses the bundled server-issued access token. `"proxy"` uses the Shopify App Proxy at `/apps/twc-sdk/auth/token` to obtain a tenant-scoped token (requires the customer to be logged in to the storefront).
|
|
33
36
|
- `data-tenant`: The TWC tenant sent as the `X-Twc-Tenant` header. Used in both auth modes. Falls back to the bundled `TENANT_ID` if omitted.
|
|
@@ -52,10 +55,10 @@ Because the proxy only issues a token for a logged-in customer, if the token can
|
|
|
52
55
|
### How It Works
|
|
53
56
|
|
|
54
57
|
1. **Initialization**: The script listens for the `DOMContentLoaded` event to initialize the widget.
|
|
55
|
-
2. **Styles Injection**: It injects
|
|
58
|
+
2. **Styles Injection**: It injects the modal/form styles, all namespaced under `#twc-nm-overlay` so they don't collide with the host theme.
|
|
56
59
|
3. **Modal Creation**: Creates the modal structure and appends it to the `notification-widget` div.
|
|
57
|
-
4. **Dynamic Content**: Based on the `data-type`, it sets the appropriate
|
|
58
|
-
5. **Form Submission**:
|
|
60
|
+
4. **Dynamic Content**: Based on the `data-type`, it sets the appropriate copy and button text, and builds the fields listed in `data-fields` plus the always-present Size selector.
|
|
61
|
+
5. **Form Submission**: On submit, the button enters a "Sending…" state, the configured auth token is resolved, and the form data is POSTed to the API. The result is shown inline (success message then auto-close, or an error message); the popup is never closed on failure.
|
|
59
62
|
|
|
60
63
|
### API Request Example
|
|
61
64
|
|
package/index.html
CHANGED
|
@@ -6,16 +6,29 @@
|
|
|
6
6
|
<title>Notify me</title>
|
|
7
7
|
<style>
|
|
8
8
|
body {
|
|
9
|
-
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
10
10
|
margin: 100px auto;
|
|
11
|
-
width:
|
|
11
|
+
max-width: 320px;
|
|
12
12
|
}
|
|
13
13
|
</style>
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
16
16
|
<div id="notification-widget">
|
|
17
|
-
<button id="popup-open" class="notification-btn" type="button" data-type="notify-me" data-fields='["firstName", "lastName"]'>Notify Me - Join Waitlist</button>
|
|
18
|
-
|
|
17
|
+
<button id="popup-open" class="notification-btn" type="button" data-type="notify-me" data-fields='["email", "firstName", "lastName"]'>Notify Me - Join Waitlist</button>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Mock product so the demo popup can populate (Shopify provides this on the storefront). -->
|
|
21
|
+
<script>
|
|
22
|
+
window.currentProduct = {
|
|
23
|
+
id: 123456789,
|
|
24
|
+
title: 'Sample Product',
|
|
25
|
+
variants: [
|
|
26
|
+
{ id: 1, title: 'Small' },
|
|
27
|
+
{ id: 2, title: 'Medium' },
|
|
28
|
+
{ id: 3, title: 'Large' },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
</script>
|
|
32
|
+
<script src="index.js"></script>
|
|
19
33
|
</body>
|
|
20
|
-
<script src="/index.js"></script>
|
|
21
34
|
</html>
|
package/index.js
CHANGED
|
@@ -190,130 +190,200 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
190
190
|
const wrapper = document.getElementById('notification-widget');
|
|
191
191
|
if (!wrapper) return;
|
|
192
192
|
|
|
193
|
-
// Define and apply styles
|
|
193
|
+
// Define and apply styles. Everything is namespaced under #twc-nm-overlay and
|
|
194
|
+
// uses `font: inherit` so the widget blends into the host store's theme
|
|
195
|
+
// without colliding with (or being broken by) the store's own CSS.
|
|
194
196
|
const styles = `
|
|
195
197
|
.notification-btn {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
color: black;
|
|
198
|
+
display: inline-block;
|
|
199
|
+
width: 100%;
|
|
199
200
|
padding: 12px 24px;
|
|
201
|
+
background: #fff;
|
|
202
|
+
border: 1px solid #111827;
|
|
203
|
+
border-radius: 8px;
|
|
204
|
+
color: #111827;
|
|
205
|
+
font: inherit;
|
|
200
206
|
text-align: center;
|
|
201
|
-
width: 100%;
|
|
202
|
-
cursor: pointer;
|
|
203
207
|
text-transform: uppercase;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
}
|
|
210
|
+
.notification-btn:hover {
|
|
211
|
+
background: #111827;
|
|
212
|
+
color: #fff;
|
|
204
213
|
}
|
|
205
|
-
#
|
|
214
|
+
#twc-nm-overlay {
|
|
206
215
|
position: fixed;
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
visibility: hidden;
|
|
216
|
+
inset: 0;
|
|
217
|
+
z-index: 99999;
|
|
218
|
+
display: flex;
|
|
219
|
+
align-items: center;
|
|
220
|
+
justify-content: center;
|
|
221
|
+
padding: 16px;
|
|
222
|
+
background: rgba(17, 17, 17, 0.55);
|
|
215
223
|
opacity: 0;
|
|
224
|
+
visibility: hidden;
|
|
225
|
+
transition: opacity 200ms ease;
|
|
216
226
|
}
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
227
|
+
#twc-nm-overlay.is-open {
|
|
228
|
+
opacity: 1;
|
|
229
|
+
visibility: visible;
|
|
230
|
+
}
|
|
231
|
+
#twc-nm-overlay * {
|
|
232
|
+
box-sizing: border-box;
|
|
233
|
+
}
|
|
234
|
+
#twc-nm-overlay .twc-nm-card {
|
|
224
235
|
position: relative;
|
|
236
|
+
width: 100%;
|
|
237
|
+
max-width: 400px;
|
|
238
|
+
max-height: calc(100vh - 32px);
|
|
239
|
+
overflow-y: auto;
|
|
240
|
+
padding: 24px;
|
|
241
|
+
background: #fff;
|
|
242
|
+
color: #111827;
|
|
243
|
+
border-radius: 12px;
|
|
244
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);
|
|
245
|
+
font: inherit;
|
|
246
|
+
transform: translateY(8px);
|
|
247
|
+
transition: transform 200ms ease;
|
|
248
|
+
}
|
|
249
|
+
#twc-nm-overlay.is-open .twc-nm-card {
|
|
250
|
+
transform: translateY(0);
|
|
225
251
|
}
|
|
226
|
-
#
|
|
252
|
+
#twc-nm-overlay .twc-nm-close {
|
|
227
253
|
position: absolute;
|
|
228
|
-
top:
|
|
229
|
-
right:
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
254
|
+
top: 12px;
|
|
255
|
+
right: 12px;
|
|
256
|
+
width: 32px;
|
|
257
|
+
height: 32px;
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
padding: 0;
|
|
262
|
+
border: none;
|
|
263
|
+
background: transparent;
|
|
264
|
+
border-radius: 6px;
|
|
265
|
+
font-size: 24px;
|
|
266
|
+
line-height: 1;
|
|
267
|
+
color: #6b7280;
|
|
235
268
|
cursor: pointer;
|
|
236
269
|
}
|
|
237
|
-
#
|
|
238
|
-
|
|
270
|
+
#twc-nm-overlay .twc-nm-close:hover {
|
|
271
|
+
background: #f3f4f6;
|
|
272
|
+
color: #111827;
|
|
239
273
|
}
|
|
240
|
-
#
|
|
241
|
-
margin: 0;
|
|
242
|
-
font-size:
|
|
243
|
-
|
|
244
|
-
text-transform: uppercase;
|
|
274
|
+
#twc-nm-overlay .twc-nm-title {
|
|
275
|
+
margin: 0 32px 4px 0;
|
|
276
|
+
font-size: 18px;
|
|
277
|
+
font-weight: 600;
|
|
245
278
|
}
|
|
246
|
-
#
|
|
247
|
-
margin:
|
|
279
|
+
#twc-nm-overlay .twc-nm-text {
|
|
280
|
+
margin: 0 0 16px;
|
|
248
281
|
font-size: 14px;
|
|
282
|
+
line-height: 1.5;
|
|
283
|
+
color: #6b7280;
|
|
249
284
|
}
|
|
250
|
-
#
|
|
285
|
+
#twc-nm-overlay .twc-nm-form {
|
|
251
286
|
display: flex;
|
|
252
287
|
flex-direction: column;
|
|
253
|
-
gap:
|
|
288
|
+
gap: 14px;
|
|
254
289
|
}
|
|
255
|
-
#
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
font-size: 1.15rem;
|
|
260
|
-
max-width: 100%;
|
|
290
|
+
#twc-nm-overlay .twc-nm-field {
|
|
291
|
+
display: flex;
|
|
292
|
+
flex-direction: column;
|
|
293
|
+
gap: 6px;
|
|
261
294
|
}
|
|
262
|
-
#
|
|
263
|
-
font-size:
|
|
295
|
+
#twc-nm-overlay .twc-nm-label {
|
|
296
|
+
font-size: 13px;
|
|
297
|
+
font-weight: 500;
|
|
298
|
+
color: #374151;
|
|
264
299
|
}
|
|
265
|
-
#
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
300
|
+
#twc-nm-overlay .twc-nm-input,
|
|
301
|
+
#twc-nm-overlay .twc-nm-select {
|
|
302
|
+
width: 100%;
|
|
303
|
+
padding: 10px 12px;
|
|
304
|
+
font-size: 14px;
|
|
305
|
+
font-family: inherit;
|
|
306
|
+
color: #111827;
|
|
307
|
+
background: #fff;
|
|
308
|
+
border: 1px solid #e5e7eb;
|
|
309
|
+
border-radius: 8px;
|
|
274
310
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
.checkbox-item input[type="checkbox"] {
|
|
281
|
-
width: auto;
|
|
282
|
-
margin: 0;
|
|
283
|
-
}
|
|
284
|
-
.custom-select {
|
|
285
|
-
position: relative;
|
|
311
|
+
#twc-nm-overlay .twc-nm-input:focus,
|
|
312
|
+
#twc-nm-overlay .twc-nm-select:focus {
|
|
313
|
+
outline: none;
|
|
314
|
+
border-color: #111827;
|
|
315
|
+
box-shadow: 0 0 0 3px rgba(17, 24, 39, 0.08);
|
|
286
316
|
}
|
|
287
|
-
.
|
|
317
|
+
#twc-nm-overlay .twc-nm-select {
|
|
288
318
|
appearance: none;
|
|
319
|
+
-webkit-appearance: none;
|
|
320
|
+
padding-right: 36px;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
|
323
|
+
background-repeat: no-repeat;
|
|
324
|
+
background-position: right 12px center;
|
|
325
|
+
}
|
|
326
|
+
#twc-nm-overlay .twc-nm-checks {
|
|
327
|
+
display: flex;
|
|
328
|
+
flex-direction: column;
|
|
329
|
+
gap: 10px;
|
|
330
|
+
}
|
|
331
|
+
#twc-nm-overlay .twc-nm-check {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
gap: 8px;
|
|
335
|
+
font-size: 13px;
|
|
336
|
+
color: #374151;
|
|
337
|
+
cursor: pointer;
|
|
338
|
+
}
|
|
339
|
+
#twc-nm-overlay .twc-nm-check input {
|
|
340
|
+
width: 16px;
|
|
341
|
+
height: 16px;
|
|
342
|
+
margin: 0;
|
|
343
|
+
accent-color: #111827;
|
|
344
|
+
}
|
|
345
|
+
#twc-nm-overlay .twc-nm-status {
|
|
346
|
+
font-size: 13px;
|
|
347
|
+
line-height: 1.5;
|
|
348
|
+
padding: 10px 12px;
|
|
349
|
+
border-radius: 8px;
|
|
350
|
+
}
|
|
351
|
+
#twc-nm-overlay .twc-nm-status--error {
|
|
352
|
+
color: #b91c1c;
|
|
353
|
+
background: #fef2f2;
|
|
354
|
+
}
|
|
355
|
+
#twc-nm-overlay .twc-nm-status--success {
|
|
356
|
+
color: #15803d;
|
|
357
|
+
background: #f0fdf4;
|
|
358
|
+
}
|
|
359
|
+
#twc-nm-overlay .twc-nm-submit {
|
|
289
360
|
width: 100%;
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
361
|
+
padding: 12px 16px;
|
|
362
|
+
font-size: 14px;
|
|
363
|
+
font-weight: 600;
|
|
364
|
+
font-family: inherit;
|
|
365
|
+
color: #fff;
|
|
366
|
+
background: #111827;
|
|
367
|
+
border: none;
|
|
368
|
+
border-radius: 8px;
|
|
296
369
|
cursor: pointer;
|
|
370
|
+
transition: background 150ms ease;
|
|
297
371
|
}
|
|
298
|
-
.
|
|
299
|
-
|
|
300
|
-
--size: 0.3rem;
|
|
301
|
-
content: "";
|
|
302
|
-
position: absolute;
|
|
303
|
-
right: 1rem;
|
|
304
|
-
pointer-events: none;
|
|
372
|
+
#twc-nm-overlay .twc-nm-submit:hover {
|
|
373
|
+
background: #374151;
|
|
305
374
|
}
|
|
306
|
-
.
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
border-bottom: var(--size) solid black;
|
|
310
|
-
top: 40%;
|
|
375
|
+
#twc-nm-overlay .twc-nm-submit:disabled {
|
|
376
|
+
opacity: 0.6;
|
|
377
|
+
cursor: default;
|
|
311
378
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
379
|
+
@media (prefers-reduced-motion: reduce) {
|
|
380
|
+
#twc-nm-overlay,
|
|
381
|
+
#twc-nm-overlay .twc-nm-card {
|
|
382
|
+
transition: none;
|
|
383
|
+
}
|
|
384
|
+
#twc-nm-overlay .twc-nm-card {
|
|
385
|
+
transform: none;
|
|
386
|
+
}
|
|
317
387
|
}
|
|
318
388
|
`;
|
|
319
389
|
|
|
@@ -325,18 +395,18 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
325
395
|
// Create and append the overlay for the popup
|
|
326
396
|
const overlay = document.createElement('div');
|
|
327
397
|
overlay.innerHTML = `
|
|
328
|
-
<div id="
|
|
329
|
-
<div
|
|
330
|
-
<
|
|
331
|
-
<
|
|
332
|
-
<
|
|
333
|
-
<
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<select name="select-size" required>
|
|
398
|
+
<div id="twc-nm-overlay" role="dialog" aria-modal="true" aria-labelledby="popup-title">
|
|
399
|
+
<div class="twc-nm-card">
|
|
400
|
+
<button type="button" id="popup-close" class="twc-nm-close" aria-label="Close">×</button>
|
|
401
|
+
<h2 id="popup-title" class="twc-nm-title"></h2>
|
|
402
|
+
<p id="popup-text" class="twc-nm-text"></p>
|
|
403
|
+
<form id="popup-form" class="twc-nm-form">
|
|
404
|
+
<div class="twc-nm-field">
|
|
405
|
+
<label class="twc-nm-label" for="twc-nm-size">Size</label>
|
|
406
|
+
<select class="twc-nm-select" id="twc-nm-size" name="select-size" required>
|
|
407
|
+
<option value="" disabled selected>Select a size</option>
|
|
337
408
|
</select>
|
|
338
409
|
</div>
|
|
339
|
-
<input name="email" placeholder="Email" type="email" required />
|
|
340
410
|
</form>
|
|
341
411
|
</div>
|
|
342
412
|
</div>
|
|
@@ -344,17 +414,16 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
344
414
|
|
|
345
415
|
wrapper.appendChild(overlay);
|
|
346
416
|
|
|
347
|
-
const
|
|
417
|
+
const overlayEl = document.getElementById('twc-nm-overlay');
|
|
348
418
|
const popupClose = document.getElementById('popup-close');
|
|
349
419
|
const popupOpenButton = document.getElementById('popup-open');
|
|
350
420
|
const popupTitle = document.getElementById('popup-title');
|
|
351
421
|
const popupText = document.getElementById('popup-text');
|
|
352
|
-
const loginMsg = document.getElementById('popup-login-msg');
|
|
353
422
|
const sizeSelect = document.querySelector("select[name='select-size']");
|
|
354
|
-
const form =
|
|
423
|
+
const form = document.getElementById('popup-form');
|
|
355
424
|
|
|
356
425
|
if (
|
|
357
|
-
!
|
|
426
|
+
!overlayEl ||
|
|
358
427
|
!popupClose ||
|
|
359
428
|
!popupOpenButton ||
|
|
360
429
|
!popupTitle ||
|
|
@@ -374,7 +443,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
374
443
|
const typeConfig = {
|
|
375
444
|
'notify-me': {
|
|
376
445
|
text: 'Register to receive a notification as soon as this item is back in stock',
|
|
377
|
-
buttonText: 'Notify
|
|
446
|
+
buttonText: 'Notify Me',
|
|
378
447
|
},
|
|
379
448
|
'coming-soon': {
|
|
380
449
|
text: 'Register your interest to hear more about this item',
|
|
@@ -397,157 +466,228 @@ document.addEventListener('DOMContentLoaded', function () {
|
|
|
397
466
|
`<option value="${c.code}"${c.code === countryCtx.countryCode ? ' selected' : ''}>${c.name}</option>`,
|
|
398
467
|
).join('');
|
|
399
468
|
|
|
469
|
+
// Wrap an input/select in a labeled field row.
|
|
470
|
+
const field = (id, label, control) =>
|
|
471
|
+
`<div class="twc-nm-field"><label class="twc-nm-label" for="${id}">${label}</label>${control}</div>`;
|
|
472
|
+
|
|
400
473
|
const fieldMap = {
|
|
401
|
-
email:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
474
|
+
email: field(
|
|
475
|
+
'twc-nm-email',
|
|
476
|
+
'Email',
|
|
477
|
+
`<input class="twc-nm-input" id="twc-nm-email" name="email" type="email" placeholder="you@example.com" required />`,
|
|
478
|
+
),
|
|
479
|
+
mobile: field(
|
|
480
|
+
'twc-nm-mobile',
|
|
481
|
+
'Mobile',
|
|
482
|
+
`<input class="twc-nm-input" id="twc-nm-mobile" name="mobile" type="tel" placeholder="Mobile number" required />`,
|
|
483
|
+
),
|
|
484
|
+
firstName: field(
|
|
485
|
+
'twc-nm-firstname',
|
|
486
|
+
'First name',
|
|
487
|
+
`<input class="twc-nm-input" id="twc-nm-firstname" name="firstName" type="text" placeholder="First name" required />`,
|
|
488
|
+
),
|
|
489
|
+
lastName: field(
|
|
490
|
+
'twc-nm-lastname',
|
|
491
|
+
'Last name',
|
|
492
|
+
`<input class="twc-nm-input" id="twc-nm-lastname" name="lastName" type="text" placeholder="Last name" required />`,
|
|
493
|
+
),
|
|
494
|
+
countryCode: field(
|
|
495
|
+
'twc-nm-country',
|
|
496
|
+
'Country',
|
|
497
|
+
`<select class="twc-nm-select" id="twc-nm-country" name="countryCode"><option value="">Select country</option>${countryOptions}</select>`,
|
|
498
|
+
),
|
|
499
|
+
provinceCode: field(
|
|
500
|
+
'twc-nm-province',
|
|
501
|
+
'State / Province',
|
|
502
|
+
`<input class="twc-nm-input" id="twc-nm-province" name="provinceCode" type="text" placeholder="State / province code" value="${countryCtx.provinceCode || ''}" />`,
|
|
503
|
+
),
|
|
407
504
|
};
|
|
408
505
|
|
|
409
506
|
// Add fields to the form based on the parsed fields
|
|
410
|
-
fields.forEach((
|
|
411
|
-
if (fieldMap[
|
|
412
|
-
form.insertAdjacentHTML('beforeend', fieldMap[
|
|
507
|
+
fields.forEach((name) => {
|
|
508
|
+
if (fieldMap[name]) {
|
|
509
|
+
form.insertAdjacentHTML('beforeend', fieldMap[name]);
|
|
413
510
|
}
|
|
414
511
|
});
|
|
415
512
|
|
|
416
|
-
form.insertAdjacentHTML(
|
|
417
|
-
const checkboxWrapper = document.getElementById('checkbox-wrapper');
|
|
418
|
-
checkboxWrapper.insertAdjacentHTML(
|
|
513
|
+
form.insertAdjacentHTML(
|
|
419
514
|
'beforeend',
|
|
420
|
-
`<div class="
|
|
515
|
+
`<div class="twc-nm-checks">
|
|
516
|
+
<label class="twc-nm-check"><input type="checkbox" name="mailList" /> Email me store updates</label>
|
|
517
|
+
<label class="twc-nm-check"><input type="checkbox" name="mailListSms" /> Text me store updates</label>
|
|
518
|
+
</div>`,
|
|
421
519
|
);
|
|
422
|
-
|
|
520
|
+
form.insertAdjacentHTML(
|
|
423
521
|
'beforeend',
|
|
424
|
-
`<div
|
|
522
|
+
`<div id="popup-status" class="twc-nm-status" role="status" aria-live="polite" hidden></div>`,
|
|
425
523
|
);
|
|
426
524
|
form.insertAdjacentHTML(
|
|
427
525
|
'beforeend',
|
|
428
|
-
`<button type="submit">${typeConfig[type].buttonText}</button>`,
|
|
526
|
+
`<button type="submit" class="twc-nm-submit">${typeConfig[type].buttonText}</button>`,
|
|
429
527
|
);
|
|
430
528
|
|
|
431
|
-
|
|
529
|
+
const popupStatus = document.getElementById('popup-status');
|
|
530
|
+
const submitBtn = form.querySelector('.twc-nm-submit');
|
|
531
|
+
|
|
532
|
+
// Inline status messaging (replaces alert()).
|
|
533
|
+
function setStatus(kind, message) {
|
|
534
|
+
popupStatus.textContent = message;
|
|
535
|
+
popupStatus.className = `twc-nm-status twc-nm-status--${kind}`;
|
|
536
|
+
popupStatus.hidden = false;
|
|
537
|
+
}
|
|
538
|
+
function clearStatus() {
|
|
539
|
+
popupStatus.hidden = true;
|
|
540
|
+
popupStatus.textContent = '';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Open / close the popup.
|
|
544
|
+
let variantsPopulated = false;
|
|
545
|
+
let lastFocused = null;
|
|
546
|
+
|
|
547
|
+
function onKeydown(event) {
|
|
548
|
+
if (event.key === 'Escape') closePopup();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function openPopup() {
|
|
552
|
+
lastFocused = document.activeElement;
|
|
553
|
+
overlayEl.classList.add('is-open');
|
|
554
|
+
document.body.style.overflow = 'hidden';
|
|
555
|
+
document.addEventListener('keydown', onKeydown);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function closePopup() {
|
|
559
|
+
overlayEl.classList.remove('is-open');
|
|
560
|
+
document.body.style.overflow = '';
|
|
561
|
+
document.removeEventListener('keydown', onKeydown);
|
|
562
|
+
clearStatus();
|
|
563
|
+
if (lastFocused && typeof lastFocused.focus === 'function') {
|
|
564
|
+
lastFocused.focus();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
432
568
|
function showPopup() {
|
|
569
|
+
openPopup();
|
|
570
|
+
|
|
433
571
|
if (!productData) {
|
|
434
|
-
|
|
572
|
+
form.hidden = true;
|
|
573
|
+
setStatus('error', 'Product information is unavailable. Please refresh the page.');
|
|
435
574
|
return;
|
|
436
575
|
}
|
|
437
|
-
// Set the popup title and populate the size dropdown
|
|
438
|
-
popupTitle.innerHTML = productData.title;
|
|
439
|
-
productData.variants.forEach((variant) => {
|
|
440
|
-
const option = document.createElement('option');
|
|
441
|
-
option.value = variant.title;
|
|
442
|
-
option.textContent = variant.title;
|
|
443
|
-
sizeSelect.appendChild(option);
|
|
444
|
-
});
|
|
445
|
-
popupBody.style.visibility = 'visible';
|
|
446
|
-
popupBody.style.opacity = 1;
|
|
447
|
-
}
|
|
448
576
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
577
|
+
popupTitle.textContent = productData.title;
|
|
578
|
+
|
|
579
|
+
if (!variantsPopulated) {
|
|
580
|
+
(productData.variants || []).forEach((variant) => {
|
|
581
|
+
const option = document.createElement('option');
|
|
582
|
+
option.value = variant.title;
|
|
583
|
+
option.textContent = variant.title;
|
|
584
|
+
sizeSelect.appendChild(option);
|
|
585
|
+
});
|
|
586
|
+
variantsPopulated = true;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
const firstControl = form.querySelector('select, input');
|
|
590
|
+
if (firstControl) firstControl.focus();
|
|
452
591
|
}
|
|
453
592
|
|
|
454
593
|
popupOpenButton.addEventListener('click', showPopup);
|
|
455
|
-
popupClose.addEventListener('click',
|
|
594
|
+
popupClose.addEventListener('click', closePopup);
|
|
595
|
+
// Close when clicking the backdrop (but not the card).
|
|
596
|
+
overlayEl.addEventListener('click', function (event) {
|
|
597
|
+
if (event.target === overlayEl) closePopup();
|
|
598
|
+
});
|
|
456
599
|
|
|
457
600
|
// Form submission handler
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const formData = {
|
|
469
|
-
variantRef: selectedVariant.id,
|
|
470
|
-
email: form.querySelector("input[name='email']").value,
|
|
471
|
-
subscribe: form.querySelector("input[name='mailList']").checked,
|
|
472
|
-
subscribeSms: form.querySelector("input[name='mailListSms']").checked,
|
|
473
|
-
};
|
|
474
|
-
|
|
475
|
-
if (type === 'coming-soon') {
|
|
476
|
-
formData.comingSoon = true;
|
|
477
|
-
// formData.productRef = productData.id;
|
|
478
|
-
} else {
|
|
479
|
-
formData.notifyMe = true;
|
|
480
|
-
}
|
|
481
|
-
if (fields.includes('firstName')) {
|
|
482
|
-
formData.firstName = form.querySelector(
|
|
483
|
-
"input[name='firstName']",
|
|
484
|
-
).value;
|
|
485
|
-
}
|
|
486
|
-
if (fields.includes('lastName')) {
|
|
487
|
-
formData.lastName = form.querySelector(
|
|
488
|
-
"input[name='lastName']",
|
|
489
|
-
).value;
|
|
490
|
-
}
|
|
491
|
-
if (fields.includes('mobile')) {
|
|
492
|
-
formData.mobile = form.querySelector("input[name='mobile']").value;
|
|
493
|
-
// formData.phone = form.querySelector("input[name='mobile']").value;
|
|
494
|
-
}
|
|
601
|
+
form.addEventListener('submit', async function (event) {
|
|
602
|
+
event.preventDefault();
|
|
603
|
+
|
|
604
|
+
const selectedVariant = (productData?.variants || []).find(
|
|
605
|
+
(variant) => variant.title === sizeSelect.value,
|
|
606
|
+
);
|
|
607
|
+
if (!selectedVariant) {
|
|
608
|
+
setStatus('error', 'Please select a size.');
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
495
611
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
612
|
+
const formData = {
|
|
613
|
+
variantRef: selectedVariant.id,
|
|
614
|
+
email: form.querySelector("input[name='email']").value,
|
|
615
|
+
subscribe: form.querySelector("input[name='mailList']").checked,
|
|
616
|
+
subscribeSms: form.querySelector("input[name='mailListSms']").checked,
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
if (type === 'coming-soon') {
|
|
620
|
+
formData.comingSoon = true;
|
|
621
|
+
} else {
|
|
622
|
+
formData.notifyMe = true;
|
|
623
|
+
}
|
|
624
|
+
if (fields.includes('firstName')) {
|
|
625
|
+
formData.firstName = form.querySelector("input[name='firstName']").value;
|
|
626
|
+
}
|
|
627
|
+
if (fields.includes('lastName')) {
|
|
628
|
+
formData.lastName = form.querySelector("input[name='lastName']").value;
|
|
629
|
+
}
|
|
630
|
+
if (fields.includes('mobile')) {
|
|
631
|
+
formData.mobile = form.querySelector("input[name='mobile']").value;
|
|
632
|
+
}
|
|
503
633
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
634
|
+
// Country / province / market — manual fields take precedence over auto-detected values
|
|
635
|
+
if (fields.includes('countryCode')) {
|
|
636
|
+
const selected = form.querySelector("select[name='countryCode']").value;
|
|
637
|
+
if (selected) formData.countryCode = selected;
|
|
638
|
+
} else if (countryCtx.countryCode) {
|
|
639
|
+
formData.countryCode = countryCtx.countryCode;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (fields.includes('provinceCode')) {
|
|
643
|
+
const val = form.querySelector("input[name='provinceCode']").value;
|
|
644
|
+
if (val) formData.provinceCode = val;
|
|
645
|
+
} else if (countryCtx.provinceCode) {
|
|
646
|
+
formData.provinceCode = countryCtx.provinceCode;
|
|
647
|
+
}
|
|
510
648
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
649
|
+
if (resolvedMarketId) formData.marketId = resolvedMarketId;
|
|
650
|
+
|
|
651
|
+
clearStatus();
|
|
652
|
+
const originalButtonText = submitBtn.textContent;
|
|
653
|
+
submitBtn.disabled = true;
|
|
654
|
+
submitBtn.textContent = 'Sending…';
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
// Resolve the Authorization token based on auth mode.
|
|
658
|
+
let authToken = ACCESS_TOKEN;
|
|
659
|
+
if (authMode === 'proxy') {
|
|
660
|
+
const proxyToken = await getProxyAccessToken();
|
|
661
|
+
if (!proxyToken) {
|
|
662
|
+
setStatus('error', 'Please log in to your account to continue.');
|
|
663
|
+
return;
|
|
523
664
|
}
|
|
665
|
+
authToken = `Bearer ${proxyToken}`;
|
|
666
|
+
}
|
|
524
667
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
hidePopup();
|
|
540
|
-
} else {
|
|
541
|
-
alert('Form submission failed');
|
|
542
|
-
}
|
|
543
|
-
})
|
|
544
|
-
.catch((error) => {
|
|
545
|
-
console.error('Error submitting form:', error);
|
|
546
|
-
alert('Form submission failed');
|
|
547
|
-
});
|
|
668
|
+
const url = `https://api.au-sandbox.thewishlist.io/services/wsservice/api/wishlist/items/customerInterest`;
|
|
669
|
+
const response = await fetch(url, {
|
|
670
|
+
method: 'POST',
|
|
671
|
+
headers: {
|
|
672
|
+
'Content-Type': 'application/json',
|
|
673
|
+
Authorization: authToken,
|
|
674
|
+
'X-Twc-Tenant': tenant,
|
|
675
|
+
},
|
|
676
|
+
body: JSON.stringify(formData),
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (response.ok) {
|
|
680
|
+
setStatus('success', "You're on the list. We'll be in touch.");
|
|
681
|
+
setTimeout(closePopup, 1500);
|
|
548
682
|
} else {
|
|
549
|
-
|
|
683
|
+
setStatus('error', 'Something went wrong. Please try again.');
|
|
550
684
|
}
|
|
551
|
-
})
|
|
552
|
-
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error('Error submitting form:', error);
|
|
687
|
+
setStatus('error', 'Something went wrong. Please try again.');
|
|
688
|
+
} finally {
|
|
689
|
+
submitBtn.disabled = false;
|
|
690
|
+
submitBtn.textContent = originalButtonText;
|
|
691
|
+
}
|
|
692
|
+
});
|
|
553
693
|
});
|