notify-me-wl 1.4.4 → 2.0.0
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 +15 -1
- package/build/notify-me-wl.js +667 -0
- package/build/notify-me-wl.min.js +1 -0
- package/package.json +17 -3
- package/CLAUDE.md +0 -199
- package/index.html +0 -34
- package/index.js +0 -693
package/index.js
DELETED
|
@@ -1,693 +0,0 @@
|
|
|
1
|
-
// Access token for the API
|
|
2
|
-
const ACCESS_TOKEN =
|
|
3
|
-
'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJRWlJkS3JabXJmMEk3WkhXRUtqNWRLTEhQanFubWJFeV9iNmpSbHdya1drIn0.eyJleHAiOjE3MjMwMzI3NzIsImlhdCI6MTcyMzAyOTE3MiwianRpIjoiNDJiNGYwM2QtMDNiNS00NmZlLTk5YmItZDQ2NTdhNjk5NGNiIiwiaXNzIjoiaHR0cHM6Ly9hdXRoLmF1LWF3cy50aGV3aXNobGlzdC5pby9hdXRoL3JlYWxtcy90d2NNYWluIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjI2YTIyNTAyLWRhNzQtNDhkMC1iZWFiLTgzY2E0YTlmMDdlOSIsInR5cCI6IkJlYXJlciIsImF6cCI6InR3Yy1wb3MtY2xpZW50Iiwic2Vzc2lvbl9zdGF0ZSI6IjQxYWQwMDc4LWVmYTItNGVjMi1hM2I2LTIzYjBkNDM3YjEzOCIsImFjciI6IjEiLCJhbGxvd2VkLW9yaWdpbnMiOlsiaHR0cHM6Ly9sb2NhbGhvc3QiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInR3Yy1wb3MtdXNlciIsIm9mZmxpbmVfYWNjZXNzIiwidHdjLXN0b3JlLW93bmVyIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6InRlbmFudGlkIHN0b3JlIHByb2ZpbGUgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInRlbmFudGlkIjoidmlrdG9yaWEtd29vZHMiLCJuYW1lIjoiTWF0dCBIYW1wc2hpcmUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJtYXR0QHRoZXdpc2hsaXN0LmlvIiwic3RvcmUiOiIyMDUiLCJnaXZlbl9uYW1lIjoiTWF0dCIsImZhbWlseV9uYW1lIjoiSGFtcHNoaXJlIiwiZW1haWwiOiJtYXR0QHRoZXdpc2hsaXN0LmlvIn0.iZkMgwH74njjXUWvImkJomHYr91Lr8ZGrZGwslEGcV3vbNuoNc5CocvNWW476o-LoSh-LsKf-MLiYN1XvOuPDF3fGoGCEbMh6_M0RJcrhVWogkj81fx4ukvDPCFIjgoDCV9WIuehV9dsSWa7E0irZeE6MUVhLwRIaTzKtxgzUUKrAqBtI_HKpyo8TUGQBiYlrc85QFUyuoKbKg-QaRn_SObRLDB8ooIBJvIlgklXQt1ZYBM2HUOc5L1bAQwfzcrWEvl6eYiQHXCSPqS0rPGoaGC6v5ydBo9VMxtVHGladDHLrO3Gt2BnIGMBoYrKTAmt7j0KABwPyB3CmAIwj_pOBQ'; // Replace `ACCESS_TOKEN` with your actual access token to authenticate API requests.
|
|
4
|
-
const TENANT_ID = 'victoria-woods'; // Replace `TENANT_ID` with your actual tenant ID.
|
|
5
|
-
|
|
6
|
-
// Session-cached access token obtained from the Shopify App Proxy (proxy auth mode).
|
|
7
|
-
let cachedProxyToken = null;
|
|
8
|
-
|
|
9
|
-
// Fetch a tenant-scoped access token from the Shopify App Proxy.
|
|
10
|
-
// The path is relative/same-origin, so Shopify's App Proxy layer appends and
|
|
11
|
-
// signs `shop`, `logged_in_customer_id`, `timestamp`, and `signature`.
|
|
12
|
-
// Returns the access token string, or null on failure (e.g. customer not logged in).
|
|
13
|
-
async function getProxyAccessToken() {
|
|
14
|
-
if (cachedProxyToken) return cachedProxyToken;
|
|
15
|
-
try {
|
|
16
|
-
const response = await fetch('/apps/twc-sdk/auth/token', {
|
|
17
|
-
method: 'GET',
|
|
18
|
-
headers: { Accept: 'application/json' },
|
|
19
|
-
});
|
|
20
|
-
const body = await response.json().catch(() => null);
|
|
21
|
-
if (
|
|
22
|
-
response.ok &&
|
|
23
|
-
body &&
|
|
24
|
-
body.success &&
|
|
25
|
-
body.data &&
|
|
26
|
-
body.data.access_token
|
|
27
|
-
) {
|
|
28
|
-
cachedProxyToken = body.data.access_token;
|
|
29
|
-
return cachedProxyToken;
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Error fetching proxy access token:', error);
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const COUNTRY_CODES = [
|
|
39
|
-
{ code: 'AF', name: 'Afghanistan' }, { code: 'AX', name: 'Åland Islands' },
|
|
40
|
-
{ code: 'AL', name: 'Albania' }, { code: 'DZ', name: 'Algeria' },
|
|
41
|
-
{ code: 'AS', name: 'American Samoa' }, { code: 'AD', name: 'Andorra' },
|
|
42
|
-
{ code: 'AO', name: 'Angola' }, { code: 'AI', name: 'Anguilla' },
|
|
43
|
-
{ code: 'AG', name: 'Antigua and Barbuda' }, { code: 'AR', name: 'Argentina' },
|
|
44
|
-
{ code: 'AM', name: 'Armenia' }, { code: 'AW', name: 'Aruba' },
|
|
45
|
-
{ code: 'AU', name: 'Australia' }, { code: 'AT', name: 'Austria' },
|
|
46
|
-
{ code: 'AZ', name: 'Azerbaijan' }, { code: 'BS', name: 'Bahamas' },
|
|
47
|
-
{ code: 'BH', name: 'Bahrain' }, { code: 'BD', name: 'Bangladesh' },
|
|
48
|
-
{ code: 'BB', name: 'Barbados' }, { code: 'BY', name: 'Belarus' },
|
|
49
|
-
{ code: 'BE', name: 'Belgium' }, { code: 'BZ', name: 'Belize' },
|
|
50
|
-
{ code: 'BJ', name: 'Benin' }, { code: 'BM', name: 'Bermuda' },
|
|
51
|
-
{ code: 'BT', name: 'Bhutan' }, { code: 'BO', name: 'Bolivia' },
|
|
52
|
-
{ code: 'BA', name: 'Bosnia and Herzegovina' }, { code: 'BW', name: 'Botswana' },
|
|
53
|
-
{ code: 'BR', name: 'Brazil' }, { code: 'BN', name: 'Brunei' },
|
|
54
|
-
{ code: 'BG', name: 'Bulgaria' }, { code: 'BF', name: 'Burkina Faso' },
|
|
55
|
-
{ code: 'BI', name: 'Burundi' }, { code: 'CV', name: 'Cabo Verde' },
|
|
56
|
-
{ code: 'KH', name: 'Cambodia' }, { code: 'CM', name: 'Cameroon' },
|
|
57
|
-
{ code: 'CA', name: 'Canada' }, { code: 'KY', name: 'Cayman Islands' },
|
|
58
|
-
{ code: 'CF', name: 'Central African Republic' }, { code: 'TD', name: 'Chad' },
|
|
59
|
-
{ code: 'CL', name: 'Chile' }, { code: 'CN', name: 'China' },
|
|
60
|
-
{ code: 'CO', name: 'Colombia' }, { code: 'KM', name: 'Comoros' },
|
|
61
|
-
{ code: 'CG', name: 'Congo' }, { code: 'CD', name: 'Congo, DR' },
|
|
62
|
-
{ code: 'CK', name: 'Cook Islands' }, { code: 'CR', name: 'Costa Rica' },
|
|
63
|
-
{ code: 'HR', name: 'Croatia' }, { code: 'CU', name: 'Cuba' },
|
|
64
|
-
{ code: 'CW', name: 'Curaçao' }, { code: 'CY', name: 'Cyprus' },
|
|
65
|
-
{ code: 'CZ', name: 'Czech Republic' }, { code: 'DK', name: 'Denmark' },
|
|
66
|
-
{ code: 'DJ', name: 'Djibouti' }, { code: 'DM', name: 'Dominica' },
|
|
67
|
-
{ code: 'DO', name: 'Dominican Republic' }, { code: 'EC', name: 'Ecuador' },
|
|
68
|
-
{ code: 'EG', name: 'Egypt' }, { code: 'SV', name: 'El Salvador' },
|
|
69
|
-
{ code: 'GQ', name: 'Equatorial Guinea' }, { code: 'ER', name: 'Eritrea' },
|
|
70
|
-
{ code: 'EE', name: 'Estonia' }, { code: 'SZ', name: 'Eswatini' },
|
|
71
|
-
{ code: 'ET', name: 'Ethiopia' }, { code: 'FO', name: 'Faroe Islands' },
|
|
72
|
-
{ code: 'FJ', name: 'Fiji' }, { code: 'FI', name: 'Finland' },
|
|
73
|
-
{ code: 'FR', name: 'France' }, { code: 'GF', name: 'French Guiana' },
|
|
74
|
-
{ code: 'PF', name: 'French Polynesia' }, { code: 'GA', name: 'Gabon' },
|
|
75
|
-
{ code: 'GM', name: 'Gambia' }, { code: 'GE', name: 'Georgia' },
|
|
76
|
-
{ code: 'DE', name: 'Germany' }, { code: 'GH', name: 'Ghana' },
|
|
77
|
-
{ code: 'GI', name: 'Gibraltar' }, { code: 'GR', name: 'Greece' },
|
|
78
|
-
{ code: 'GL', name: 'Greenland' }, { code: 'GD', name: 'Grenada' },
|
|
79
|
-
{ code: 'GP', name: 'Guadeloupe' }, { code: 'GU', name: 'Guam' },
|
|
80
|
-
{ code: 'GT', name: 'Guatemala' }, { code: 'GN', name: 'Guinea' },
|
|
81
|
-
{ code: 'GW', name: 'Guinea-Bissau' }, { code: 'GY', name: 'Guyana' },
|
|
82
|
-
{ code: 'HT', name: 'Haiti' }, { code: 'HN', name: 'Honduras' },
|
|
83
|
-
{ code: 'HK', name: 'Hong Kong' }, { code: 'HU', name: 'Hungary' },
|
|
84
|
-
{ code: 'IS', name: 'Iceland' }, { code: 'IN', name: 'India' },
|
|
85
|
-
{ code: 'ID', name: 'Indonesia' }, { code: 'IR', name: 'Iran' },
|
|
86
|
-
{ code: 'IQ', name: 'Iraq' }, { code: 'IE', name: 'Ireland' },
|
|
87
|
-
{ code: 'IM', name: 'Isle of Man' }, { code: 'IL', name: 'Israel' },
|
|
88
|
-
{ code: 'IT', name: 'Italy' }, { code: 'JM', name: 'Jamaica' },
|
|
89
|
-
{ code: 'JP', name: 'Japan' }, { code: 'JE', name: 'Jersey' },
|
|
90
|
-
{ code: 'JO', name: 'Jordan' }, { code: 'KZ', name: 'Kazakhstan' },
|
|
91
|
-
{ code: 'KE', name: 'Kenya' }, { code: 'KI', name: 'Kiribati' },
|
|
92
|
-
{ code: 'KR', name: 'Korea, South' }, { code: 'XK', name: 'Kosovo' },
|
|
93
|
-
{ code: 'KW', name: 'Kuwait' }, { code: 'KG', name: 'Kyrgyzstan' },
|
|
94
|
-
{ code: 'LA', name: 'Laos' }, { code: 'LV', name: 'Latvia' },
|
|
95
|
-
{ code: 'LB', name: 'Lebanon' }, { code: 'LS', name: 'Lesotho' },
|
|
96
|
-
{ code: 'LR', name: 'Liberia' }, { code: 'LY', name: 'Libya' },
|
|
97
|
-
{ code: 'LI', name: 'Liechtenstein' }, { code: 'LT', name: 'Lithuania' },
|
|
98
|
-
{ code: 'LU', name: 'Luxembourg' }, { code: 'MO', name: 'Macao' },
|
|
99
|
-
{ code: 'MG', name: 'Madagascar' }, { code: 'MW', name: 'Malawi' },
|
|
100
|
-
{ code: 'MY', name: 'Malaysia' }, { code: 'MV', name: 'Maldives' },
|
|
101
|
-
{ code: 'ML', name: 'Mali' }, { code: 'MT', name: 'Malta' },
|
|
102
|
-
{ code: 'MH', name: 'Marshall Islands' }, { code: 'MQ', name: 'Martinique' },
|
|
103
|
-
{ code: 'MR', name: 'Mauritania' }, { code: 'MU', name: 'Mauritius' },
|
|
104
|
-
{ code: 'MX', name: 'Mexico' }, { code: 'FM', name: 'Micronesia' },
|
|
105
|
-
{ code: 'MD', name: 'Moldova' }, { code: 'MC', name: 'Monaco' },
|
|
106
|
-
{ code: 'MN', name: 'Mongolia' }, { code: 'ME', name: 'Montenegro' },
|
|
107
|
-
{ code: 'MS', name: 'Montserrat' }, { code: 'MA', name: 'Morocco' },
|
|
108
|
-
{ code: 'MZ', name: 'Mozambique' }, { code: 'MM', name: 'Myanmar' },
|
|
109
|
-
{ code: 'NA', name: 'Namibia' }, { code: 'NR', name: 'Nauru' },
|
|
110
|
-
{ code: 'NP', name: 'Nepal' }, { code: 'NL', name: 'Netherlands' },
|
|
111
|
-
{ code: 'NC', name: 'New Caledonia' }, { code: 'NZ', name: 'New Zealand' },
|
|
112
|
-
{ code: 'NI', name: 'Nicaragua' }, { code: 'NE', name: 'Niger' },
|
|
113
|
-
{ code: 'NG', name: 'Nigeria' }, { code: 'NU', name: 'Niue' },
|
|
114
|
-
{ code: 'MK', name: 'North Macedonia' }, { code: 'NO', name: 'Norway' },
|
|
115
|
-
{ code: 'OM', name: 'Oman' }, { code: 'PK', name: 'Pakistan' },
|
|
116
|
-
{ code: 'PW', name: 'Palau' }, { code: 'PS', name: 'Palestine' },
|
|
117
|
-
{ code: 'PA', name: 'Panama' }, { code: 'PG', name: 'Papua New Guinea' },
|
|
118
|
-
{ code: 'PY', name: 'Paraguay' }, { code: 'PE', name: 'Peru' },
|
|
119
|
-
{ code: 'PH', name: 'Philippines' }, { code: 'PL', name: 'Poland' },
|
|
120
|
-
{ code: 'PT', name: 'Portugal' }, { code: 'PR', name: 'Puerto Rico' },
|
|
121
|
-
{ code: 'QA', name: 'Qatar' }, { code: 'RE', name: 'Réunion' },
|
|
122
|
-
{ code: 'RO', name: 'Romania' },
|
|
123
|
-
{ code: 'RW', name: 'Rwanda' }, { code: 'KN', name: 'Saint Kitts and Nevis' },
|
|
124
|
-
{ code: 'LC', name: 'Saint Lucia' }, { code: 'VC', name: 'Saint Vincent and the Grenadines' },
|
|
125
|
-
{ code: 'WS', name: 'Samoa' }, { code: 'SM', name: 'San Marino' },
|
|
126
|
-
{ code: 'SA', name: 'Saudi Arabia' }, { code: 'SN', name: 'Senegal' },
|
|
127
|
-
{ code: 'RS', name: 'Serbia' }, { code: 'SC', name: 'Seychelles' },
|
|
128
|
-
{ code: 'SL', name: 'Sierra Leone' }, { code: 'SG', name: 'Singapore' },
|
|
129
|
-
{ code: 'SK', name: 'Slovakia' }, { code: 'SI', name: 'Slovenia' },
|
|
130
|
-
{ code: 'SB', name: 'Solomon Islands' }, { code: 'SO', name: 'Somalia' },
|
|
131
|
-
{ code: 'ZA', name: 'South Africa' }, { code: 'SS', name: 'South Sudan' },
|
|
132
|
-
{ code: 'ES', name: 'Spain' }, { code: 'LK', name: 'Sri Lanka' },
|
|
133
|
-
{ code: 'SD', name: 'Sudan' }, { code: 'SR', name: 'Suriname' },
|
|
134
|
-
{ code: 'SE', name: 'Sweden' }, { code: 'CH', name: 'Switzerland' },
|
|
135
|
-
{ code: 'SY', name: 'Syria' }, { code: 'TW', name: 'Taiwan' },
|
|
136
|
-
{ code: 'TJ', name: 'Tajikistan' }, { code: 'TZ', name: 'Tanzania' },
|
|
137
|
-
{ code: 'TH', name: 'Thailand' }, { code: 'TL', name: 'Timor-Leste' },
|
|
138
|
-
{ code: 'TG', name: 'Togo' }, { code: 'TO', name: 'Tonga' },
|
|
139
|
-
{ code: 'TT', name: 'Trinidad and Tobago' }, { code: 'TN', name: 'Tunisia' },
|
|
140
|
-
{ code: 'TR', name: 'Turkey' }, { code: 'TM', name: 'Turkmenistan' },
|
|
141
|
-
{ code: 'TC', name: 'Turks and Caicos Islands' }, { code: 'TV', name: 'Tuvalu' },
|
|
142
|
-
{ code: 'UG', name: 'Uganda' }, { code: 'UA', name: 'Ukraine' },
|
|
143
|
-
{ code: 'AE', name: 'United Arab Emirates' }, { code: 'GB', name: 'United Kingdom' },
|
|
144
|
-
{ code: 'US', name: 'United States' }, { code: 'UY', name: 'Uruguay' },
|
|
145
|
-
{ code: 'UZ', name: 'Uzbekistan' }, { code: 'VU', name: 'Vanuatu' },
|
|
146
|
-
{ code: 'VE', name: 'Venezuela' }, { code: 'VN', name: 'Vietnam' },
|
|
147
|
-
{ code: 'VG', name: 'Virgin Islands, British' }, { code: 'VI', name: 'Virgin Islands, U.S.' },
|
|
148
|
-
{ code: 'YE', name: 'Yemen' }, { code: 'ZM', name: 'Zambia' },
|
|
149
|
-
{ code: 'ZW', name: 'Zimbabwe' },
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
// Detect country/province/market context using priority fallback chain
|
|
153
|
-
function detectCountryContext() {
|
|
154
|
-
const ctx = { countryCode: null, provinceCode: null, marketId: null };
|
|
155
|
-
|
|
156
|
-
// Priority 1: Shopify localization/country context
|
|
157
|
-
const shopify = window.Shopify;
|
|
158
|
-
if (shopify) {
|
|
159
|
-
if (typeof shopify.country === 'string') ctx.countryCode = shopify.country;
|
|
160
|
-
const mkt = shopify.markets?.currentMarket;
|
|
161
|
-
if (mkt) {
|
|
162
|
-
if (mkt.countryCode) ctx.countryCode = mkt.countryCode;
|
|
163
|
-
if (mkt.id != null) ctx.marketId = String(mkt.id);
|
|
164
|
-
}
|
|
165
|
-
if (!ctx.countryCode && typeof shopify.locale === 'string') {
|
|
166
|
-
const parts = shopify.locale.split('-');
|
|
167
|
-
if (parts.length > 1) ctx.countryCode = parts[parts.length - 1].toUpperCase();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Priority 2: Existing customer shipping country
|
|
172
|
-
if (!ctx.countryCode || !ctx.provinceCode) {
|
|
173
|
-
const addr = window.customer?.default_address;
|
|
174
|
-
if (addr) {
|
|
175
|
-
if (!ctx.countryCode && addr.country_code) ctx.countryCode = addr.country_code;
|
|
176
|
-
if (!ctx.provinceCode && addr.province_code) ctx.provinceCode = addr.province_code;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Priority 3: Browser locale fallback
|
|
181
|
-
if (!ctx.countryCode) {
|
|
182
|
-
const parts = (navigator.language || '').split('-');
|
|
183
|
-
if (parts.length > 1) ctx.countryCode = parts[parts.length - 1].toUpperCase();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return ctx;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
document.addEventListener('DOMContentLoaded', function () {
|
|
190
|
-
const wrapper = document.getElementById('notification-widget');
|
|
191
|
-
if (!wrapper) return;
|
|
192
|
-
|
|
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.
|
|
196
|
-
const styles = `
|
|
197
|
-
.notification-btn {
|
|
198
|
-
display: inline-block;
|
|
199
|
-
width: 100%;
|
|
200
|
-
padding: 12px 24px;
|
|
201
|
-
background: #fff;
|
|
202
|
-
border: 1px solid #111827;
|
|
203
|
-
border-radius: 8px;
|
|
204
|
-
color: #111827;
|
|
205
|
-
font: inherit;
|
|
206
|
-
text-align: center;
|
|
207
|
-
text-transform: uppercase;
|
|
208
|
-
cursor: pointer;
|
|
209
|
-
}
|
|
210
|
-
.notification-btn:hover {
|
|
211
|
-
background: #111827;
|
|
212
|
-
color: #fff;
|
|
213
|
-
}
|
|
214
|
-
#twc-nm-overlay {
|
|
215
|
-
position: fixed;
|
|
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);
|
|
223
|
-
opacity: 0;
|
|
224
|
-
visibility: hidden;
|
|
225
|
-
transition: opacity 200ms ease;
|
|
226
|
-
}
|
|
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 {
|
|
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);
|
|
251
|
-
}
|
|
252
|
-
#twc-nm-overlay .twc-nm-close {
|
|
253
|
-
position: absolute;
|
|
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;
|
|
268
|
-
cursor: pointer;
|
|
269
|
-
}
|
|
270
|
-
#twc-nm-overlay .twc-nm-close:hover {
|
|
271
|
-
background: #f3f4f6;
|
|
272
|
-
color: #111827;
|
|
273
|
-
}
|
|
274
|
-
#twc-nm-overlay .twc-nm-title {
|
|
275
|
-
margin: 0 32px 4px 0;
|
|
276
|
-
font-size: 18px;
|
|
277
|
-
font-weight: 600;
|
|
278
|
-
}
|
|
279
|
-
#twc-nm-overlay .twc-nm-text {
|
|
280
|
-
margin: 0 0 16px;
|
|
281
|
-
font-size: 14px;
|
|
282
|
-
line-height: 1.5;
|
|
283
|
-
color: #6b7280;
|
|
284
|
-
}
|
|
285
|
-
#twc-nm-overlay .twc-nm-form {
|
|
286
|
-
display: flex;
|
|
287
|
-
flex-direction: column;
|
|
288
|
-
gap: 14px;
|
|
289
|
-
}
|
|
290
|
-
#twc-nm-overlay .twc-nm-field {
|
|
291
|
-
display: flex;
|
|
292
|
-
flex-direction: column;
|
|
293
|
-
gap: 6px;
|
|
294
|
-
}
|
|
295
|
-
#twc-nm-overlay .twc-nm-label {
|
|
296
|
-
font-size: 13px;
|
|
297
|
-
font-weight: 500;
|
|
298
|
-
color: #374151;
|
|
299
|
-
}
|
|
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;
|
|
310
|
-
}
|
|
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);
|
|
316
|
-
}
|
|
317
|
-
#twc-nm-overlay .twc-nm-select {
|
|
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 {
|
|
360
|
-
width: 100%;
|
|
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;
|
|
369
|
-
cursor: pointer;
|
|
370
|
-
transition: background 150ms ease;
|
|
371
|
-
}
|
|
372
|
-
#twc-nm-overlay .twc-nm-submit:hover {
|
|
373
|
-
background: #374151;
|
|
374
|
-
}
|
|
375
|
-
#twc-nm-overlay .twc-nm-submit:disabled {
|
|
376
|
-
opacity: 0.6;
|
|
377
|
-
cursor: default;
|
|
378
|
-
}
|
|
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
|
-
}
|
|
387
|
-
}
|
|
388
|
-
`;
|
|
389
|
-
|
|
390
|
-
const styleSheet = document.createElement('style');
|
|
391
|
-
styleSheet.type = 'text/css';
|
|
392
|
-
styleSheet.innerText = styles;
|
|
393
|
-
document.head.appendChild(styleSheet);
|
|
394
|
-
|
|
395
|
-
// Create and append the overlay for the popup
|
|
396
|
-
const overlay = document.createElement('div');
|
|
397
|
-
overlay.innerHTML = `
|
|
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>
|
|
408
|
-
</select>
|
|
409
|
-
</div>
|
|
410
|
-
</form>
|
|
411
|
-
</div>
|
|
412
|
-
</div>
|
|
413
|
-
`;
|
|
414
|
-
|
|
415
|
-
wrapper.appendChild(overlay);
|
|
416
|
-
|
|
417
|
-
const overlayEl = document.getElementById('twc-nm-overlay');
|
|
418
|
-
const popupClose = document.getElementById('popup-close');
|
|
419
|
-
const popupOpenButton = document.getElementById('popup-open');
|
|
420
|
-
const popupTitle = document.getElementById('popup-title');
|
|
421
|
-
const popupText = document.getElementById('popup-text');
|
|
422
|
-
const sizeSelect = document.querySelector("select[name='select-size']");
|
|
423
|
-
const form = document.getElementById('popup-form');
|
|
424
|
-
|
|
425
|
-
if (
|
|
426
|
-
!overlayEl ||
|
|
427
|
-
!popupClose ||
|
|
428
|
-
!popupOpenButton ||
|
|
429
|
-
!popupTitle ||
|
|
430
|
-
!sizeSelect
|
|
431
|
-
)
|
|
432
|
-
return;
|
|
433
|
-
|
|
434
|
-
// Parse fields and type to be included in the form from data attribute
|
|
435
|
-
const fields = JSON.parse(
|
|
436
|
-
popupOpenButton.getAttribute('data-fields') || '["email"]',
|
|
437
|
-
);
|
|
438
|
-
const type = popupOpenButton.getAttribute('data-type') || 'notify-me';
|
|
439
|
-
// Auth mode: 'token' (default, hardcoded ACCESS_TOKEN) or 'proxy' (Shopify App Proxy).
|
|
440
|
-
const authMode = popupOpenButton.getAttribute('data-auth') || 'token';
|
|
441
|
-
// Tenant for X-Twc-Tenant header; used in both auth modes.
|
|
442
|
-
const tenant = popupOpenButton.getAttribute('data-tenant') || TENANT_ID;
|
|
443
|
-
const typeConfig = {
|
|
444
|
-
'notify-me': {
|
|
445
|
-
text: 'Register to receive a notification as soon as this item is back in stock',
|
|
446
|
-
buttonText: 'Notify Me',
|
|
447
|
-
},
|
|
448
|
-
'coming-soon': {
|
|
449
|
-
text: 'Register your interest to hear more about this item',
|
|
450
|
-
buttonText: 'Register Interest',
|
|
451
|
-
},
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
// Get product data from Shopify's global variable
|
|
455
|
-
const productData = window?.currentProduct;
|
|
456
|
-
|
|
457
|
-
const countryCtx = detectCountryContext();
|
|
458
|
-
const resolvedMarketId =
|
|
459
|
-
popupOpenButton.getAttribute('data-market-id') || countryCtx.marketId;
|
|
460
|
-
|
|
461
|
-
// Map for form fields
|
|
462
|
-
popupText.innerText = typeConfig[type].text;
|
|
463
|
-
|
|
464
|
-
const countryOptions = COUNTRY_CODES.map(
|
|
465
|
-
(c) =>
|
|
466
|
-
`<option value="${c.code}"${c.code === countryCtx.countryCode ? ' selected' : ''}>${c.name}</option>`,
|
|
467
|
-
).join('');
|
|
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
|
-
|
|
473
|
-
const fieldMap = {
|
|
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
|
-
),
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
// Add fields to the form based on the parsed fields
|
|
507
|
-
fields.forEach((name) => {
|
|
508
|
-
if (fieldMap[name]) {
|
|
509
|
-
form.insertAdjacentHTML('beforeend', fieldMap[name]);
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
form.insertAdjacentHTML(
|
|
514
|
-
'beforeend',
|
|
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>`,
|
|
519
|
-
);
|
|
520
|
-
form.insertAdjacentHTML(
|
|
521
|
-
'beforeend',
|
|
522
|
-
`<div id="popup-status" class="twc-nm-status" role="status" aria-live="polite" hidden></div>`,
|
|
523
|
-
);
|
|
524
|
-
form.insertAdjacentHTML(
|
|
525
|
-
'beforeend',
|
|
526
|
-
`<button type="submit" class="twc-nm-submit">${typeConfig[type].buttonText}</button>`,
|
|
527
|
-
);
|
|
528
|
-
|
|
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
|
-
|
|
568
|
-
function showPopup() {
|
|
569
|
-
openPopup();
|
|
570
|
-
|
|
571
|
-
if (!productData) {
|
|
572
|
-
form.hidden = true;
|
|
573
|
-
setStatus('error', 'Product information is unavailable. Please refresh the page.');
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
|
|
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();
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
popupOpenButton.addEventListener('click', showPopup);
|
|
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
|
-
});
|
|
599
|
-
|
|
600
|
-
// Form submission handler
|
|
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
|
-
}
|
|
611
|
-
|
|
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
|
-
}
|
|
633
|
-
|
|
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
|
-
}
|
|
648
|
-
|
|
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;
|
|
664
|
-
}
|
|
665
|
-
authToken = `Bearer ${proxyToken}`;
|
|
666
|
-
}
|
|
667
|
-
|
|
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);
|
|
682
|
-
} else {
|
|
683
|
-
setStatus('error', 'Something went wrong. Please try again.');
|
|
684
|
-
}
|
|
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
|
-
});
|
|
693
|
-
});
|