@magic-spells/gift-with-purchase 0.2.0 → 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 +177 -40
- package/dist/{gift-with-purchase.cjs.css → gift-with-purchase.css} +12 -6
- package/dist/gift-with-purchase.esm.js +353 -242
- package/dist/gift-with-purchase.min.css +2 -2
- package/dist/gift-with-purchase.min.js +1 -2
- package/package.json +19 -26
- package/dist/gift-with-purchase.cjs.css.map +0 -1
- package/dist/gift-with-purchase.cjs.js +0 -339
- package/dist/gift-with-purchase.cjs.js.map +0 -1
- package/dist/gift-with-purchase.esm.css +0 -43
- package/dist/gift-with-purchase.esm.css.map +0 -1
- package/dist/gift-with-purchase.esm.js.map +0 -1
- package/dist/gift-with-purchase.min.css.map +0 -1
- package/dist/gift-with-purchase.min.js.map +0 -1
- package/dist/gift-with-purchase.scss +0 -70
- package/src/gift-with-purchase.js +0 -338
- package/src/gift-with-purchase.scss +0 -70
|
@@ -1,297 +1,408 @@
|
|
|
1
|
+
//#region src/gift-with-purchase.js
|
|
1
2
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
// private fields
|
|
3
|
+
* Gift With Purchase Component - automatically adds/removes gift when cart threshold is met
|
|
4
|
+
* Emits gwp:added/gwp:removed/gwp:error events and broadcasts cart updates
|
|
5
|
+
*/
|
|
6
|
+
var GiftWithPurchase = class extends HTMLElement {
|
|
7
7
|
#threshold = 0;
|
|
8
8
|
#currentAmount = 0;
|
|
9
9
|
#variantId = null;
|
|
10
10
|
#isActive = false;
|
|
11
11
|
#isAdded = false;
|
|
12
12
|
#promoEnded = false;
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
|
|
13
|
+
#productAvailable = true;
|
|
14
|
+
#isDisabled = false;
|
|
15
|
+
#cartPanel = null;
|
|
16
|
+
#handlers = {};
|
|
17
|
+
#debounceTimer = null;
|
|
18
|
+
#attachRetryTimer = null;
|
|
19
|
+
#isMutating = false;
|
|
20
|
+
#missedUpdate = false;
|
|
21
|
+
#messageAbove = null;
|
|
22
|
+
#messageBelow = null;
|
|
23
|
+
#moneyFormat = null;
|
|
19
24
|
static get observedAttributes() {
|
|
20
|
-
return [
|
|
25
|
+
return [
|
|
26
|
+
"threshold",
|
|
27
|
+
"current",
|
|
28
|
+
"variant-id",
|
|
29
|
+
"promo-ended",
|
|
30
|
+
"product-available",
|
|
31
|
+
"message-above",
|
|
32
|
+
"message-below",
|
|
33
|
+
"money-format"
|
|
34
|
+
];
|
|
21
35
|
}
|
|
22
|
-
|
|
23
36
|
constructor() {
|
|
24
37
|
super();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
const _ = this;
|
|
39
|
+
_.#threshold = parseFloat(_.getAttribute("threshold")) || 0;
|
|
40
|
+
_.#currentAmount = parseFloat(_.getAttribute("current")) || 0;
|
|
41
|
+
_.#variantId = _.getAttribute("variant-id");
|
|
42
|
+
_.#promoEnded = _.hasAttribute("promo-ended");
|
|
43
|
+
_.#productAvailable = _.#parseBooleanValue(_.getAttribute("product-available"), true);
|
|
44
|
+
_.#messageAbove = _.getAttribute("message-above");
|
|
45
|
+
_.#messageBelow = _.getAttribute("message-below");
|
|
46
|
+
_.#moneyFormat = _.getAttribute("money-format");
|
|
47
|
+
_.#handlers = { cartDataChange: _.#handleCartDataChange.bind(_) };
|
|
48
|
+
}
|
|
35
49
|
connectedCallback() {
|
|
36
|
-
this
|
|
37
|
-
|
|
50
|
+
const _ = this;
|
|
51
|
+
_.#calculateInitialState();
|
|
52
|
+
_.#render();
|
|
53
|
+
_.#updateVisualState();
|
|
54
|
+
_.#attachListeners();
|
|
55
|
+
if (_.#isDisabled && (!_.#cartPanel || _.#cartPanel.hasAttribute("manual"))) _.#updateState(null);
|
|
56
|
+
}
|
|
57
|
+
#calculateInitialState() {
|
|
58
|
+
const _ = this;
|
|
59
|
+
const convertedThreshold = _.#getConvertedThreshold();
|
|
60
|
+
_.#isDisabled = _.#promoEnded || !_.#productAvailable;
|
|
61
|
+
_.#isActive = _.#currentAmount >= convertedThreshold && !_.#isDisabled;
|
|
38
62
|
}
|
|
39
|
-
|
|
40
63
|
disconnectedCallback() {
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
64
|
+
const _ = this;
|
|
65
|
+
if (_.#debounceTimer) clearTimeout(_.#debounceTimer);
|
|
66
|
+
if (_.#attachRetryTimer) clearTimeout(_.#attachRetryTimer);
|
|
67
|
+
if (_.#cartPanel) _.#cartPanel.removeEventListener("cart-panel:data-changed", _.#handlers.cartDataChange);
|
|
68
|
+
}
|
|
69
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
70
|
+
const _ = this;
|
|
71
|
+
if (oldValue === newValue) return;
|
|
72
|
+
switch (name) {
|
|
73
|
+
case "threshold":
|
|
74
|
+
_.#threshold = parseFloat(newValue) || 0;
|
|
75
|
+
break;
|
|
76
|
+
case "current":
|
|
77
|
+
_.#currentAmount = parseFloat(newValue) || 0;
|
|
78
|
+
break;
|
|
79
|
+
case "variant-id":
|
|
80
|
+
_.#variantId = newValue;
|
|
81
|
+
break;
|
|
82
|
+
case "promo-ended":
|
|
83
|
+
_.#promoEnded = newValue !== null;
|
|
84
|
+
break;
|
|
85
|
+
case "product-available":
|
|
86
|
+
_.#productAvailable = _.#parseBooleanValue(newValue, true);
|
|
87
|
+
break;
|
|
88
|
+
case "message-above":
|
|
89
|
+
_.#messageAbove = newValue;
|
|
90
|
+
break;
|
|
91
|
+
case "message-below":
|
|
92
|
+
_.#messageBelow = newValue;
|
|
93
|
+
break;
|
|
94
|
+
case "money-format": _.#moneyFormat = newValue;
|
|
95
|
+
}
|
|
96
|
+
if (!_.isConnected) return;
|
|
97
|
+
const wasDisabled = _.#isDisabled;
|
|
98
|
+
_.#calculateInitialState();
|
|
99
|
+
_.#updateVisualState();
|
|
100
|
+
_.#updateMessages();
|
|
101
|
+
if (_.#isDisabled && !wasDisabled) _.#updateState(null);
|
|
102
|
+
}
|
|
49
103
|
#render() {
|
|
50
|
-
this.classList.add(
|
|
104
|
+
this.classList.add("gift-with-purchase");
|
|
51
105
|
this.#renderMessages();
|
|
52
106
|
}
|
|
53
|
-
|
|
54
107
|
#renderMessages() {
|
|
55
|
-
// Look for existing message element with data-content-gwp-message
|
|
56
108
|
this.#updateMessages();
|
|
57
109
|
}
|
|
58
|
-
|
|
110
|
+
#formatMoney(amount) {
|
|
111
|
+
if (!this.#moneyFormat) return amount.toFixed(2).replace(/\.00$/, "");
|
|
112
|
+
const amountFixed = amount.toFixed(2);
|
|
113
|
+
const amountNoDecimals = Math.round(amount).toString();
|
|
114
|
+
const amountWithComma = amountFixed.replace(".", ",");
|
|
115
|
+
const amountNoDecimalsWithComma = amountNoDecimals.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
116
|
+
return this.#moneyFormat.replace(/\{\{\s*amount_no_decimals_with_comma_separator\s*\}\}/g, amountNoDecimalsWithComma).replace(/\{\{\s*amount_with_comma_separator\s*\}\}/g, amountWithComma).replace(/\{\{\s*amount_no_decimals\s*\}\}/g, amountNoDecimals).replace(/\{\{\s*amount\s*\}\}/g, amountFixed);
|
|
117
|
+
}
|
|
118
|
+
#getConvertedThreshold() {
|
|
119
|
+
const rate = parseFloat(window.Shopify?.currency?.rate) || 1;
|
|
120
|
+
return this.#threshold * rate;
|
|
121
|
+
}
|
|
122
|
+
#parseBooleanValue(value, defaultValue = true) {
|
|
123
|
+
if (value === null || typeof value === "undefined") return defaultValue;
|
|
124
|
+
if (value === "") return true;
|
|
125
|
+
const normalized = String(value).trim().toLowerCase();
|
|
126
|
+
if (normalized === "false" || normalized === "0") return false;
|
|
127
|
+
if (normalized === "true" || normalized === "1") return true;
|
|
128
|
+
return defaultValue;
|
|
129
|
+
}
|
|
59
130
|
#updateMessages() {
|
|
60
|
-
const
|
|
131
|
+
const _ = this;
|
|
132
|
+
const messageEl = _.querySelector("[data-content-gwp-message]");
|
|
61
133
|
if (!messageEl) return;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// set message to above threshold message
|
|
69
|
-
message = this.#messageAbove;
|
|
70
|
-
} else if (!this.#isActive && this.#messageBelow) {
|
|
71
|
-
// set message to below threshold message
|
|
72
|
-
const remaining = this.#threshold - this.#currentAmount;
|
|
73
|
-
const formattedAmount = remaining.toFixed(2).replace(/\.00$/, '');
|
|
74
|
-
message = this.#messageBelow
|
|
75
|
-
.replace(/\{\s*amount\s*\}/g, formattedAmount)
|
|
76
|
-
.replace(/\{amount\}/g, formattedAmount);
|
|
134
|
+
let message = "";
|
|
135
|
+
if (_.#isActive && _.#messageAbove) message = _.#messageAbove;
|
|
136
|
+
else if (!_.#isActive && _.#messageBelow) {
|
|
137
|
+
const remaining = _.#getConvertedThreshold() - _.#currentAmount;
|
|
138
|
+
const formattedAmount = _.#formatMoney(remaining);
|
|
139
|
+
message = _.#messageBelow.replace(/\[\s*amount\s*\]/g, formattedAmount).replace(/\[amount\]/g, formattedAmount);
|
|
77
140
|
}
|
|
78
|
-
|
|
79
141
|
messageEl.textContent = message;
|
|
80
|
-
messageEl.style.display = message ?
|
|
142
|
+
messageEl.style.display = message ? "block" : "none";
|
|
81
143
|
}
|
|
82
|
-
|
|
83
144
|
#attachListeners() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}, 100);
|
|
107
|
-
}
|
|
145
|
+
const _ = this;
|
|
146
|
+
_.#cartPanel = _.closest("cart-panel");
|
|
147
|
+
if (_.#cartPanel) _.#cartPanel.addEventListener("cart-panel:data-changed", _.#handlers.cartDataChange);
|
|
148
|
+
else _.#attachRetryTimer = setTimeout(() => {
|
|
149
|
+
_.#attachRetryTimer = null;
|
|
150
|
+
_.#cartPanel = _.closest("cart-panel");
|
|
151
|
+
if (_.#cartPanel) _.#cartPanel.addEventListener("cart-panel:data-changed", _.#handlers.cartDataChange);
|
|
152
|
+
else console.error("GWP - cart-panel still not found after delay");
|
|
153
|
+
}, 100);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Ask the parent cart-panel to re-fetch and re-render the cart.
|
|
157
|
+
* refreshCart() is the public API across every published cart-panel (0.3.x - 2.x);
|
|
158
|
+
* getCartAndRefresh() is only tried as a fallback for custom panels written against
|
|
159
|
+
* the name earlier versions of this component mistakenly called. Missing methods are
|
|
160
|
+
* ignored rather than thrown so a successful add/remove never reports a spurious error.
|
|
161
|
+
*/
|
|
162
|
+
#refreshCartPanel() {
|
|
163
|
+
const panel = this.#cartPanel;
|
|
164
|
+
if (!panel) return;
|
|
165
|
+
if (typeof panel.refreshCart === "function") panel.refreshCart();
|
|
166
|
+
else if (typeof panel.getCartAndRefresh === "function") panel.getCartAndRefresh();
|
|
108
167
|
}
|
|
109
|
-
|
|
110
168
|
#handleCartDataChange(event) {
|
|
169
|
+
const _ = this;
|
|
111
170
|
const cart = event.detail;
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
171
|
+
if (!cart || typeof cart.calculated_subtotal === "undefined") return;
|
|
172
|
+
if (_.#debounceTimer) clearTimeout(_.#debounceTimer);
|
|
173
|
+
_.#debounceTimer = setTimeout(() => {
|
|
174
|
+
_.#debounceTimer = null;
|
|
175
|
+
if (_.#isMutating) {
|
|
176
|
+
_.#missedUpdate = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
_.#currentAmount = parseFloat(cart.calculated_subtotal / 100) || 0;
|
|
180
|
+
_.#checkGiftInCart(cart);
|
|
181
|
+
_.#updateState(cart);
|
|
122
182
|
}, 300);
|
|
123
183
|
}
|
|
124
|
-
|
|
125
|
-
// checks to see if the gift is already in the cart
|
|
126
184
|
#checkGiftInCart(cart) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
const _ = this;
|
|
186
|
+
const giftLines = _.#getGiftLines(cart);
|
|
187
|
+
_.#isAdded = giftLines.length > 0;
|
|
188
|
+
const duplicate = giftLines.find((item) => item.quantity > 1);
|
|
189
|
+
if (duplicate) _.#trimGiftQuantity(duplicate);
|
|
190
|
+
}
|
|
191
|
+
async #trimGiftQuantity(item) {
|
|
192
|
+
const _ = this;
|
|
193
|
+
_.#isMutating = true;
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetch("/cart/change.js", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
credentials: "same-origin",
|
|
198
|
+
headers: {
|
|
199
|
+
"Content-Type": "application/json",
|
|
200
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
201
|
+
},
|
|
202
|
+
body: JSON.stringify({
|
|
203
|
+
id: item.key,
|
|
204
|
+
quantity: 1
|
|
205
|
+
})
|
|
206
|
+
});
|
|
207
|
+
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
208
|
+
_.#refreshCartPanel();
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error("giftwithpurchase: quantity trim error", err);
|
|
211
|
+
_.dispatchEvent(new CustomEvent("gwp:error", {
|
|
212
|
+
detail: {
|
|
213
|
+
action: "trim",
|
|
214
|
+
error: err.message
|
|
215
|
+
},
|
|
216
|
+
bubbles: true
|
|
217
|
+
}));
|
|
218
|
+
} finally {
|
|
219
|
+
_.#isMutating = false;
|
|
220
|
+
_.#discardStaleCart();
|
|
130
221
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
this.#removeGiftFromCart(cart);
|
|
222
|
+
}
|
|
223
|
+
#getGiftLines(cart) {
|
|
224
|
+
const _ = this;
|
|
225
|
+
if (!cart?.items || !_.#variantId) return [];
|
|
226
|
+
return cart.items.filter((item) => {
|
|
227
|
+
if (item.properties?._gwp_item !== "true") return false;
|
|
228
|
+
return item.variant_id?.toString() === _.#variantId.toString();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Cart snapshots held across a mutation predate it - drop them and ask for fresh truth.
|
|
233
|
+
*/
|
|
234
|
+
#discardStaleCart() {
|
|
235
|
+
const _ = this;
|
|
236
|
+
if (_.#debounceTimer) {
|
|
237
|
+
clearTimeout(_.#debounceTimer);
|
|
238
|
+
_.#debounceTimer = null;
|
|
239
|
+
_.#missedUpdate = true;
|
|
150
240
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
241
|
+
if (!_.#missedUpdate) return;
|
|
242
|
+
_.#missedUpdate = false;
|
|
243
|
+
if (_.#cartPanel) _.#refreshCartPanel();
|
|
244
|
+
else _.#updateState(null);
|
|
245
|
+
}
|
|
246
|
+
#updateState(cart) {
|
|
247
|
+
const _ = this;
|
|
248
|
+
if (_.#isMutating) {
|
|
249
|
+
_.#missedUpdate = true;
|
|
250
|
+
return;
|
|
156
251
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
252
|
+
const convertedThreshold = _.#getConvertedThreshold();
|
|
253
|
+
_.#isDisabled = _.#promoEnded || !_.#productAvailable;
|
|
254
|
+
_.#isActive = _.#currentAmount >= convertedThreshold && !_.#isDisabled;
|
|
255
|
+
if (_.#isDisabled) _.#removeGiftFromCart(cart);
|
|
256
|
+
else if (_.#isActive && !_.#isAdded && _.#variantId) _.#addGiftToCart();
|
|
257
|
+
else if (!_.#isActive && _.#isAdded && _.#variantId) _.#removeGiftFromCart(cart);
|
|
258
|
+
_.#updateVisualState();
|
|
259
|
+
_.#updateMessages();
|
|
160
260
|
}
|
|
161
|
-
|
|
162
261
|
#updateVisualState() {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
262
|
+
const _ = this;
|
|
263
|
+
if (_.#promoEnded) {
|
|
264
|
+
_.setAttribute("state", "ended");
|
|
265
|
+
_.style.display = "none";
|
|
166
266
|
return;
|
|
167
267
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
} else if (this.#isActive) {
|
|
173
|
-
this.setAttribute('state', 'active');
|
|
268
|
+
if (!_.#productAvailable) {
|
|
269
|
+
_.setAttribute("state", "disabled");
|
|
270
|
+
_.style.display = "none";
|
|
271
|
+
return;
|
|
174
272
|
}
|
|
175
|
-
|
|
273
|
+
_.style.display = "";
|
|
274
|
+
if (_.#isAdded) _.setAttribute("state", "added");
|
|
275
|
+
else if (_.#isActive) _.setAttribute("state", "active");
|
|
276
|
+
else _.setAttribute("state", "inactive");
|
|
176
277
|
}
|
|
177
|
-
|
|
178
278
|
async #addGiftToCart() {
|
|
279
|
+
const _ = this;
|
|
280
|
+
_.#isMutating = true;
|
|
179
281
|
try {
|
|
180
|
-
const res = await fetch(
|
|
181
|
-
method:
|
|
182
|
-
credentials:
|
|
282
|
+
const res = await fetch("/cart/add.js", {
|
|
283
|
+
method: "POST",
|
|
284
|
+
credentials: "same-origin",
|
|
183
285
|
headers: {
|
|
184
|
-
|
|
185
|
-
|
|
286
|
+
"Content-Type": "application/json",
|
|
287
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
186
288
|
},
|
|
187
|
-
body: JSON.stringify({
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
],
|
|
199
|
-
}),
|
|
289
|
+
body: JSON.stringify({ items: [{
|
|
290
|
+
id: _.#variantId,
|
|
291
|
+
quantity: 1,
|
|
292
|
+
properties: {
|
|
293
|
+
_gwp_item: "true",
|
|
294
|
+
_hide_in_cart: "true",
|
|
295
|
+
_ignore_price_in_subtotal: "true"
|
|
296
|
+
}
|
|
297
|
+
}] })
|
|
200
298
|
});
|
|
201
299
|
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
202
300
|
await res.json();
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
);
|
|
301
|
+
_.#isAdded = true;
|
|
302
|
+
_.dispatchEvent(new CustomEvent("gwp:added", {
|
|
303
|
+
detail: { variantId: _.#variantId },
|
|
304
|
+
bubbles: true
|
|
305
|
+
}));
|
|
306
|
+
_.#refreshCartPanel();
|
|
210
307
|
} catch (err) {
|
|
211
|
-
console.error(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
308
|
+
console.error("giftwithpurchase: add error", err);
|
|
309
|
+
_.dispatchEvent(new CustomEvent("gwp:error", {
|
|
310
|
+
detail: {
|
|
311
|
+
action: "add",
|
|
312
|
+
error: err.message
|
|
313
|
+
},
|
|
314
|
+
bubbles: true
|
|
315
|
+
}));
|
|
316
|
+
} finally {
|
|
317
|
+
_.#isMutating = false;
|
|
318
|
+
_.#discardStaleCart();
|
|
218
319
|
}
|
|
219
320
|
}
|
|
220
|
-
|
|
221
321
|
async #removeGiftFromCart(cart) {
|
|
322
|
+
const _ = this;
|
|
323
|
+
_.#isMutating = true;
|
|
222
324
|
try {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
lineItem.variant_id.toString() === this.#variantId.toString() &&
|
|
227
|
-
lineItem.properties?._gwp_item === 'true'
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// exit if no items in the cart
|
|
325
|
+
if (!cart?.items) cart = await _.#fetchCart();
|
|
326
|
+
if (!cart?.items) return;
|
|
327
|
+
const giftLines = _.#getGiftLines(cart);
|
|
231
328
|
if (!giftLines.length) {
|
|
232
|
-
|
|
329
|
+
_.#isAdded = false;
|
|
233
330
|
return;
|
|
234
331
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
332
|
+
await _.#removeAllGiftItems(giftLines);
|
|
333
|
+
} finally {
|
|
334
|
+
_.#isMutating = false;
|
|
335
|
+
_.#discardStaleCart();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async #fetchCart() {
|
|
339
|
+
const _ = this;
|
|
340
|
+
try {
|
|
341
|
+
let cart;
|
|
342
|
+
if (typeof _.#cartPanel?.getCart === "function") cart = await _.#cartPanel.getCart();
|
|
343
|
+
else {
|
|
344
|
+
const res = await fetch("/cart.js", { credentials: "same-origin" });
|
|
345
|
+
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
346
|
+
cart = await res.json();
|
|
347
|
+
}
|
|
348
|
+
return cart?.error ? null : cart;
|
|
238
349
|
} catch (err) {
|
|
239
|
-
console.error(
|
|
240
|
-
|
|
241
|
-
new CustomEvent('gwp:error', {
|
|
242
|
-
detail: { action: 'remove', error: err.message },
|
|
243
|
-
bubbles: true,
|
|
244
|
-
})
|
|
245
|
-
);
|
|
350
|
+
console.error("giftwithpurchase: cart fetch error", err);
|
|
351
|
+
return null;
|
|
246
352
|
}
|
|
247
353
|
}
|
|
248
|
-
|
|
249
354
|
async #removeAllGiftItems(giftLines) {
|
|
355
|
+
const _ = this;
|
|
250
356
|
try {
|
|
251
|
-
await Promise.all(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
357
|
+
await Promise.all(giftLines.map(async (item) => {
|
|
358
|
+
const res = await fetch("/cart/change.js", {
|
|
359
|
+
method: "POST",
|
|
360
|
+
credentials: "same-origin",
|
|
361
|
+
headers: {
|
|
362
|
+
"Content-Type": "application/json",
|
|
363
|
+
"X-Requested-With": "XMLHttpRequest"
|
|
364
|
+
},
|
|
365
|
+
body: JSON.stringify({
|
|
366
|
+
id: item.key,
|
|
367
|
+
quantity: 0
|
|
261
368
|
})
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
369
|
+
});
|
|
370
|
+
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
371
|
+
}));
|
|
372
|
+
_.#isAdded = false;
|
|
373
|
+
_.dispatchEvent(new CustomEvent("gwp:removed", {
|
|
374
|
+
detail: { variantId: _.#variantId },
|
|
375
|
+
bubbles: true
|
|
376
|
+
}));
|
|
377
|
+
_.#refreshCartPanel();
|
|
272
378
|
} catch (err) {
|
|
273
|
-
console.error(
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
379
|
+
console.error("giftwithpurchase: bulk remove error", err);
|
|
380
|
+
_.dispatchEvent(new CustomEvent("gwp:error", {
|
|
381
|
+
detail: {
|
|
382
|
+
action: "remove",
|
|
383
|
+
error: err.message
|
|
384
|
+
},
|
|
385
|
+
bubbles: true
|
|
386
|
+
}));
|
|
280
387
|
}
|
|
281
388
|
}
|
|
282
|
-
|
|
283
389
|
getState() {
|
|
390
|
+
const _ = this;
|
|
391
|
+
const convertedThreshold = _.#getConvertedThreshold();
|
|
284
392
|
return {
|
|
285
|
-
currentAmount:
|
|
286
|
-
threshold:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
393
|
+
currentAmount: _.#currentAmount,
|
|
394
|
+
threshold: _.#threshold,
|
|
395
|
+
convertedThreshold,
|
|
396
|
+
variantId: _.#variantId,
|
|
397
|
+
isActive: _.#isActive,
|
|
398
|
+
isAdded: _.#isAdded,
|
|
399
|
+
promoEnded: _.#promoEnded,
|
|
400
|
+
productAvailable: _.#productAvailable,
|
|
401
|
+
isDisabled: _.#isDisabled,
|
|
402
|
+
remainingAmount: Math.max(0, convertedThreshold - _.#currentAmount),
|
|
403
|
+
currencyRate: parseFloat(window.Shopify?.currency?.rate) || 1
|
|
292
404
|
};
|
|
293
405
|
}
|
|
294
|
-
|
|
295
406
|
get currentAmount() {
|
|
296
407
|
return this.#currentAmount;
|
|
297
408
|
}
|
|
@@ -310,28 +421,28 @@ class GiftWithPurchase extends HTMLElement {
|
|
|
310
421
|
get promoEnded() {
|
|
311
422
|
return this.#promoEnded;
|
|
312
423
|
}
|
|
313
|
-
|
|
314
|
-
|
|
424
|
+
get productAvailable() {
|
|
425
|
+
return this.#productAvailable;
|
|
426
|
+
}
|
|
427
|
+
get isDisabled() {
|
|
428
|
+
return this.#isDisabled;
|
|
429
|
+
}
|
|
315
430
|
setCurrentAmount(amount) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
431
|
+
const _ = this;
|
|
432
|
+
_.#currentAmount = parseFloat(amount) || 0;
|
|
433
|
+
_.#updateState(null);
|
|
434
|
+
_.#updateMessages();
|
|
319
435
|
}
|
|
320
|
-
|
|
321
436
|
setThreshold(threshold) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
437
|
+
const _ = this;
|
|
438
|
+
_.#threshold = parseFloat(threshold) || 0;
|
|
439
|
+
_.#updateState(null);
|
|
440
|
+
_.#updateMessages();
|
|
325
441
|
}
|
|
326
|
-
|
|
327
442
|
setVariantId(variantId) {
|
|
328
443
|
this.#variantId = variantId;
|
|
329
444
|
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
customElements.define('gift-with-purchase', GiftWithPurchase);
|
|
334
|
-
}
|
|
335
|
-
|
|
445
|
+
};
|
|
446
|
+
if (!customElements.get("gift-with-purchase")) customElements.define("gift-with-purchase", GiftWithPurchase);
|
|
447
|
+
//#endregion
|
|
336
448
|
export { GiftWithPurchase };
|
|
337
|
-
//# sourceMappingURL=gift-with-purchase.esm.js.map
|