@ordergroove/offers 2.26.2 → 2.26.3-alpha-PR-593-11.14
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/build.js +3 -1
- package/dist/bundle-report.html +174 -103
- package/dist/offers.js +63 -74
- package/dist/offers.js.map +3 -3
- package/examples/cart.js +105 -0
- package/examples/index.html +2 -2
- package/examples/products/cheap-watch.js +183 -0
- package/examples/shopify-cart.html +26 -0
- package/examples/shopify-pdp.html +34 -0
- package/karma.conf.js +2 -1
- package/package.json +4 -4
- package/src/__tests__/offers.spec.js +32 -2
- package/src/components/FrequencyStatus.js +14 -11
- package/src/components/Offer.js +11 -7
- package/src/components/OptinButton.js +1 -1
- package/src/components/OptinSelect.js +2 -2
- package/src/components/OptinToggle.js +2 -2
- package/src/components/OptoutButton.js +1 -1
- package/src/components/Price.js +3 -3
- package/src/components/Select.js +3 -13
- package/src/components/SelectFrequency.js +23 -5
- package/src/components/__tests__/OG.fspec.js +24 -0
- package/src/components/__tests__/Offer.spec.js +4 -4
- package/src/components/__tests__/OptinButton.spec.js +2 -2
- package/src/components/__tests__/OptinToggle.spec.js +2 -2
- package/src/components/__tests__/OptoutButton.spec.js +1 -1
- package/src/components/__tests__/SelectFrequency.fspec.js +1 -0
- package/src/components/__tests__/SelectFrequency.spec.js +1 -1
- package/src/components/__tests__/TestWizard.spec.js +2 -2
- package/src/core/__tests__/actions.spec.js +6 -6
- package/src/core/actions.js +10 -10
- package/src/core/constants.js +2 -0
- package/src/core/reducer.js +15 -14
- package/src/core/resolveProperties.js +2 -7
- package/src/core/selectors.js +1 -1
- package/src/core/store.js +6 -5
- package/src/index.js +44 -206
- package/src/make-api.js +187 -0
- package/src/shopify/__tests__/shopifyReducer.spec.js +477 -0
- package/src/shopify/getProduct.ts +6 -0
- package/src/shopify/guessProductHandle.ts +26 -0
- package/src/shopify/shopifyMiddleware.ts +137 -0
- package/src/shopify/shopifyReducer.js +199 -0
- package/tsconfig.json +35 -0
- package/examples/5starnutrition-main.js +0 -3
- package/examples/single-offer.html +0 -9
- package/src/init-test.js +0 -3
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import * as constants from '../../core/constants';
|
|
2
|
+
import * as coreReducers from '../../core/reducer';
|
|
3
|
+
import {
|
|
4
|
+
autoshipEligible,
|
|
5
|
+
config,
|
|
6
|
+
inStock,
|
|
7
|
+
offer,
|
|
8
|
+
offerId,
|
|
9
|
+
optedin,
|
|
10
|
+
productOffer,
|
|
11
|
+
productPlans
|
|
12
|
+
} from '../shopifyReducer';
|
|
13
|
+
|
|
14
|
+
describe('autoshipEligible', () => {
|
|
15
|
+
it('should return true for each id given action RECEIVE_PRODUCT_PLANS', () => {
|
|
16
|
+
const actual = autoshipEligible(
|
|
17
|
+
{},
|
|
18
|
+
{
|
|
19
|
+
type: constants.RECEIVE_PRODUCT_PLANS,
|
|
20
|
+
payload: {
|
|
21
|
+
'yum product id 1': {},
|
|
22
|
+
'yum product id 2': {}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
expect(actual).toEqual({ 'yum product id 1': true, 'yum product id 2': true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should return true for each key in items given action SETUP_CART', () => {
|
|
30
|
+
const actual = autoshipEligible(
|
|
31
|
+
{},
|
|
32
|
+
{
|
|
33
|
+
type: constants.SETUP_CART,
|
|
34
|
+
payload: {
|
|
35
|
+
items: [
|
|
36
|
+
{
|
|
37
|
+
key: 'yum key 1'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
key: 'yum key 2'
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
expect(actual).toEqual({ 'yum key 1': true, 'yum key 2': true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should return true for each product and variant given action SETUP_PRODUCT and products have selling plan allocations', () => {
|
|
50
|
+
const actual = autoshipEligible(
|
|
51
|
+
{},
|
|
52
|
+
{
|
|
53
|
+
type: constants.SETUP_PRODUCT,
|
|
54
|
+
payload: {
|
|
55
|
+
id: 'yum product id',
|
|
56
|
+
selling_plan_allocations: [{ 'yum key': 'yum value' }],
|
|
57
|
+
variants: [
|
|
58
|
+
{
|
|
59
|
+
id: 'yum variant id',
|
|
60
|
+
selling_plan_allocations: [{ 'yum key': 'yum value' }]
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(actual).toEqual({ 'yum product id': true, 'yum variant id': true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should return false for each product and variant given action SETUP_PRODUCT and products have no selling plan allocations', () => {
|
|
71
|
+
const actual = autoshipEligible(
|
|
72
|
+
{},
|
|
73
|
+
{
|
|
74
|
+
type: constants.SETUP_PRODUCT,
|
|
75
|
+
payload: {
|
|
76
|
+
id: 'yum product id',
|
|
77
|
+
selling_plan_allocations: [],
|
|
78
|
+
variants: [
|
|
79
|
+
{
|
|
80
|
+
id: 'yum variant id',
|
|
81
|
+
selling_plan_allocations: []
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(actual).toEqual({ 'yum product id': false, 'yum variant id': false });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return unmodified state given unsupported action', () => {
|
|
92
|
+
const actual = autoshipEligible(
|
|
93
|
+
{ 'yum existing key': 'yum existing value' },
|
|
94
|
+
{
|
|
95
|
+
type: 'yum unsupported action',
|
|
96
|
+
payload: {}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('config', () => {
|
|
105
|
+
it('should return unique frequencies given action RECEIVE_PRODUCT_PLANS', () => {
|
|
106
|
+
const actual = config(
|
|
107
|
+
{},
|
|
108
|
+
{
|
|
109
|
+
type: constants.RECEIVE_PRODUCT_PLANS,
|
|
110
|
+
payload: {
|
|
111
|
+
'product plan id': { '1_1': {}, '3_1': {} }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
expect(actual).toEqual(
|
|
117
|
+
jasmine.objectContaining({
|
|
118
|
+
frequencies: ['1_1', '3_1']
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should return first selling plan id as default frequency given action SETUP_PRODUCT', () => {
|
|
124
|
+
const actual = config(
|
|
125
|
+
{},
|
|
126
|
+
{
|
|
127
|
+
type: constants.SETUP_PRODUCT,
|
|
128
|
+
payload: {
|
|
129
|
+
selling_plan_groups: [
|
|
130
|
+
{
|
|
131
|
+
selling_plans: [
|
|
132
|
+
{
|
|
133
|
+
id: 'yum selling plan id 1'
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: 'yum selling plan id 2'
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(actual).toEqual(
|
|
146
|
+
jasmine.objectContaining({
|
|
147
|
+
defaultFrequency: 'yum selling plan id 1'
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should return selling plan ids as frequencies given action SETUP_PRODUCT', () => {
|
|
153
|
+
const actual = config(
|
|
154
|
+
{},
|
|
155
|
+
{
|
|
156
|
+
type: constants.SETUP_PRODUCT,
|
|
157
|
+
payload: {
|
|
158
|
+
selling_plan_groups: [
|
|
159
|
+
{
|
|
160
|
+
selling_plans: [
|
|
161
|
+
{
|
|
162
|
+
id: 'yum selling plan id 1'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'yum selling plan id 2'
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(actual).toEqual(
|
|
175
|
+
jasmine.objectContaining({
|
|
176
|
+
frequencies: ['yum selling plan id 1', 'yum selling plan id 2']
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should return values of first selling plan group as frequencies text given action SETUP_PRODUCT', () => {
|
|
182
|
+
const actual = config(
|
|
183
|
+
{},
|
|
184
|
+
{
|
|
185
|
+
type: constants.SETUP_PRODUCT,
|
|
186
|
+
payload: {
|
|
187
|
+
selling_plan_groups: [
|
|
188
|
+
{
|
|
189
|
+
options: [{ values: 'yum values' }],
|
|
190
|
+
selling_plans: [
|
|
191
|
+
{
|
|
192
|
+
id: 'yum selling plan id'
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(actual).toEqual(
|
|
202
|
+
jasmine.objectContaining({
|
|
203
|
+
frequenciesText: 'yum values'
|
|
204
|
+
})
|
|
205
|
+
);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should return unmodified state given unsupported action', () => {
|
|
209
|
+
const actual = config(
|
|
210
|
+
{ 'yum existing key': 'yum existing value' },
|
|
211
|
+
{
|
|
212
|
+
type: 'yum unsupported action',
|
|
213
|
+
payload: {}
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('inStock', () => {
|
|
222
|
+
it('should return true for each id given action RECEIVE_PRODUCT_PLANS', () => {
|
|
223
|
+
const actual = inStock(
|
|
224
|
+
{},
|
|
225
|
+
{
|
|
226
|
+
type: constants.RECEIVE_PRODUCT_PLANS,
|
|
227
|
+
payload: {
|
|
228
|
+
'yum product id 1': {},
|
|
229
|
+
'yum product id 2': {}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
expect(actual).toEqual({ 'yum product id 1': true, 'yum product id 2': true });
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should return true item key given action SETUP_CART', () => {
|
|
237
|
+
const actual = inStock(
|
|
238
|
+
{},
|
|
239
|
+
{
|
|
240
|
+
type: constants.SETUP_CART,
|
|
241
|
+
payload: {
|
|
242
|
+
items: [{ key: 'yum item key 1' }, { key: 'yum item key 2' }]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
expect(actual).toEqual({ 'yum item key 1': true, 'yum item key 2': true });
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return true for each available product and variant given action SETUP_PRODUCT', () => {
|
|
250
|
+
const actual = inStock(
|
|
251
|
+
{},
|
|
252
|
+
{
|
|
253
|
+
type: constants.SETUP_PRODUCT,
|
|
254
|
+
payload: {
|
|
255
|
+
id: 'yum product id',
|
|
256
|
+
available: true,
|
|
257
|
+
variants: [
|
|
258
|
+
{
|
|
259
|
+
id: 'yum variant id',
|
|
260
|
+
available: true
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
expect(actual).toEqual({
|
|
268
|
+
'yum product id': true,
|
|
269
|
+
'yum variant id': true
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should return false for each unavailable product and variant given action SETUP_PRODUCT', () => {
|
|
274
|
+
const actual = inStock(
|
|
275
|
+
{},
|
|
276
|
+
{
|
|
277
|
+
type: constants.SETUP_PRODUCT,
|
|
278
|
+
payload: {
|
|
279
|
+
id: 'yum product id',
|
|
280
|
+
available: false,
|
|
281
|
+
variants: [
|
|
282
|
+
{
|
|
283
|
+
id: 'yum variant id',
|
|
284
|
+
available: false
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(actual).toEqual({
|
|
292
|
+
'yum product id': false,
|
|
293
|
+
'yum variant id': false
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should return unmodified state given unsupported action', () => {
|
|
298
|
+
const actual = inStock(
|
|
299
|
+
{ 'yum existing key': 'yum existing value' },
|
|
300
|
+
{
|
|
301
|
+
type: 'yum unsupported action',
|
|
302
|
+
payload: {}
|
|
303
|
+
}
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
describe('offer', () => {
|
|
311
|
+
it('should return unmodified state', () => {
|
|
312
|
+
const actual = offer({ 'yum existing key': 'yum existing value' });
|
|
313
|
+
|
|
314
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('offerId', () => {
|
|
319
|
+
it('should return shopify constant', () => {
|
|
320
|
+
const actual = offerId();
|
|
321
|
+
|
|
322
|
+
expect(actual).toEqual('native-shopify-offer');
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('optedin', () => {
|
|
327
|
+
it('should return optins given action SETUP_CART', () => {
|
|
328
|
+
const actual = optedin(
|
|
329
|
+
{},
|
|
330
|
+
{
|
|
331
|
+
type: constants.SETUP_CART,
|
|
332
|
+
payload: {
|
|
333
|
+
items: [
|
|
334
|
+
{
|
|
335
|
+
key: 'yum item key 1',
|
|
336
|
+
selling_plan_allocation: {
|
|
337
|
+
selling_plan: {
|
|
338
|
+
id: 'yum selling plan id 1'
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
key: 'yum item key 2',
|
|
344
|
+
selling_plan_allocation: {
|
|
345
|
+
selling_plan: {
|
|
346
|
+
id: 'yum selling plan id 2'
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
]
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
expect(actual).toEqual([
|
|
356
|
+
{
|
|
357
|
+
id: 'yum item key 1',
|
|
358
|
+
frequency: 'yum selling plan id 1'
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
id: 'yum item key 2',
|
|
362
|
+
frequency: 'yum selling plan id 2'
|
|
363
|
+
}
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should return unmodified state given unsupported action', () => {
|
|
368
|
+
const actual = optedin(
|
|
369
|
+
{ 'yum existing key': 'yum existing value' },
|
|
370
|
+
{
|
|
371
|
+
type: 'yum unsupported action',
|
|
372
|
+
payload: {}
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('productOffer', () => {
|
|
381
|
+
it('should return unmodified state', () => {
|
|
382
|
+
const actual = productOffer({ 'yum existing key': 'yum existing value' });
|
|
383
|
+
|
|
384
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('productPlans', () => {
|
|
389
|
+
it('should product plans with formatted discounts given action SETUP_PRODUCT', () => {
|
|
390
|
+
const actual = productPlans(
|
|
391
|
+
{},
|
|
392
|
+
{
|
|
393
|
+
type: constants.SETUP_PRODUCT,
|
|
394
|
+
payload: {
|
|
395
|
+
id: 'yum product id',
|
|
396
|
+
selling_plan_allocations: [
|
|
397
|
+
{
|
|
398
|
+
selling_plan_id: 'yum selling plan id 1',
|
|
399
|
+
compare_at_price: 100,
|
|
400
|
+
price: 50,
|
|
401
|
+
price_adjustments: [
|
|
402
|
+
{
|
|
403
|
+
value: 50,
|
|
404
|
+
value_type: 'percentage'
|
|
405
|
+
}
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
],
|
|
409
|
+
variants: [
|
|
410
|
+
{
|
|
411
|
+
id: 'yum variant id 1',
|
|
412
|
+
selling_plan_allocations: [
|
|
413
|
+
{
|
|
414
|
+
selling_plan_id: 'yum selling plan id 2',
|
|
415
|
+
compare_at_price: 50,
|
|
416
|
+
price: 25,
|
|
417
|
+
price_adjustments: [
|
|
418
|
+
{
|
|
419
|
+
value: 25
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: 'yum variant id 2',
|
|
427
|
+
selling_plan_allocations: [
|
|
428
|
+
{
|
|
429
|
+
selling_plan_id: 'yum selling plan id 3',
|
|
430
|
+
compare_at_price: 10,
|
|
431
|
+
price: 8,
|
|
432
|
+
price_adjustments: []
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
expect(actual).toEqual({
|
|
442
|
+
'yum product id': { 'yum selling plan id 1': ['$1.00', '50%', '$.50'] },
|
|
443
|
+
'yum variant id 1': {
|
|
444
|
+
'yum selling plan id 2': ['$.50', '$.25', '$.25']
|
|
445
|
+
},
|
|
446
|
+
'yum variant id 2': { 'yum selling plan id 3': ['$.10', '%', '$8'] }
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should return payload given action RECEIVE_PRODUCT_PLANS', () => {
|
|
451
|
+
const actual = productPlans(
|
|
452
|
+
{},
|
|
453
|
+
{
|
|
454
|
+
type: constants.RECEIVE_PRODUCT_PLANS,
|
|
455
|
+
payload: {
|
|
456
|
+
'yum key': 'yum value'
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
expect(actual).toEqual({
|
|
462
|
+
'yum key': 'yum value'
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('should return unmodified state given unsupported action', () => {
|
|
467
|
+
const actual = productPlans(
|
|
468
|
+
{ 'yum existing key': 'yum existing value' },
|
|
469
|
+
{
|
|
470
|
+
type: 'yum unsupported action',
|
|
471
|
+
payload: {}
|
|
472
|
+
}
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
expect(actual).toEqual({ 'yum existing key': 'yum existing value' });
|
|
476
|
+
});
|
|
477
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attemps to guess the product handle o
|
|
3
|
+
* @returns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function guessProductHandle(): String {
|
|
7
|
+
return (
|
|
8
|
+
[
|
|
9
|
+
() =>
|
|
10
|
+
// Use the oembed to get the product handle
|
|
11
|
+
(document.querySelector('[href$=".oembed"]')?.getAttribute('href')?.match(/\/([^\/]+)\.oembed$/) || [])[1],
|
|
12
|
+
|
|
13
|
+
() =>
|
|
14
|
+
// Use the open graph og:type==product and og:url to get the product handle
|
|
15
|
+
((document.querySelector('meta[property="og:type"][content="product"]') &&
|
|
16
|
+
document.querySelector('meta[property="og:url"][content]')?.getAttribute('content')?.match(/\/([^\/]+)$/)) ||
|
|
17
|
+
[])[1],
|
|
18
|
+
|
|
19
|
+
() =>
|
|
20
|
+
// use any json in the markup
|
|
21
|
+
[...document.querySelectorAll('[type$=json]')].map(it => JSON.parse(it.textContent)).find(it => it.handle && it.price)?.handle
|
|
22
|
+
]
|
|
23
|
+
// returns the first truthy and prevent call next functions
|
|
24
|
+
.reduce((acc, cur) => acc || cur(), '')
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OPTIN_PRODUCT,
|
|
3
|
+
OPTOUT_PRODUCT,
|
|
4
|
+
PRODUCT_CHANGE_FREQUENCY,
|
|
5
|
+
REQUEST_OFFER,
|
|
6
|
+
SETUP_CART,
|
|
7
|
+
SETUP_PRODUCT
|
|
8
|
+
} from '../core/constants';
|
|
9
|
+
import { guessProductHandle } from './guessProductHandle';
|
|
10
|
+
import { getProduct } from './getProduct';
|
|
11
|
+
import { makeSubscribedSelector } from '../core/selectors';
|
|
12
|
+
|
|
13
|
+
async function setupPdp(store) {
|
|
14
|
+
const handle = guessProductHandle();
|
|
15
|
+
if (handle) {
|
|
16
|
+
try {
|
|
17
|
+
store.dispatch({ type: SETUP_PRODUCT, payload: await getProduct(handle) });
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.warn('OG: Unable to fetch product details for PDP', err);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const getCart = async () => await (await fetch(`${window.Shopify?.routes.root}cart.js`)).json();
|
|
25
|
+
|
|
26
|
+
async function setupCart(store) {
|
|
27
|
+
const cart = await getCart();
|
|
28
|
+
store.dispatch({ type: SETUP_CART, payload: cart });
|
|
29
|
+
const { items } = cart;
|
|
30
|
+
const products = await Promise.all(Array.from(new Set(items.map(({ handle }) => handle))).map(getProduct));
|
|
31
|
+
products.forEach(product => store.dispatch({ type: SETUP_PRODUCT, payload: product }));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function synchronizeCartOptin(action: any, store: any) {
|
|
35
|
+
const offerElement = action.payload.offer;
|
|
36
|
+
const selling_plan = action.payload.frequency || null;
|
|
37
|
+
|
|
38
|
+
if (offerElement?.isCart) {
|
|
39
|
+
const closestSection = offerElement.closest('.shopify-section');
|
|
40
|
+
const closestSectionId = closestSection && (closestSection.id.match(/^shopify-section-(.+)/) || [])[1];
|
|
41
|
+
|
|
42
|
+
const key = action.payload.product.id; // shopify cart.item.key
|
|
43
|
+
const cart = await getCart();
|
|
44
|
+
const item = cart.items.find(it => it.key === key); // cart.items[offerIx];
|
|
45
|
+
|
|
46
|
+
const res = await fetch('/cart/change.js', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
credentials: 'same-origin',
|
|
49
|
+
headers: { 'Content-Type': 'application/json' },
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
id: key,
|
|
52
|
+
quantity: item.quantity,
|
|
53
|
+
properties: {
|
|
54
|
+
...item.properties
|
|
55
|
+
},
|
|
56
|
+
selling_plan: selling_plan || null,
|
|
57
|
+
sections: closestSectionId ? [closestSectionId] : undefined
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
if (res.status !== 200) {
|
|
61
|
+
throw new Error('Cart not updated');
|
|
62
|
+
}
|
|
63
|
+
const { sections } = (await res.json()) || {};
|
|
64
|
+
|
|
65
|
+
if (sections?.length) {
|
|
66
|
+
const [section] = Object.values(sections);
|
|
67
|
+
|
|
68
|
+
const el = new DOMParser()
|
|
69
|
+
.parseFromString(section?.toString() || '', 'text/html')
|
|
70
|
+
.getElementById('shopify-section-' + closestSectionId);
|
|
71
|
+
closestSection.innerHTML = el.innerHTML;
|
|
72
|
+
} else {
|
|
73
|
+
window.location.reload();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('update cart');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* // update <input type="hidden" name="selling_plan"/> if available
|
|
82
|
+
*
|
|
83
|
+
* @param store
|
|
84
|
+
*/
|
|
85
|
+
function synchronizeSellingPlan(store: any) {
|
|
86
|
+
[...document.querySelectorAll('[name=id]')].forEach(productIdInput => {
|
|
87
|
+
const productId = productIdInput.value;
|
|
88
|
+
let sellingPlanInput = productIdInput.form.selling_plan;
|
|
89
|
+
if (!sellingPlanInput) {
|
|
90
|
+
sellingPlanInput = document.createElement('input');
|
|
91
|
+
sellingPlanInput.type = 'hidden';
|
|
92
|
+
sellingPlanInput.name = 'selling_plan';
|
|
93
|
+
productIdInput.form.appendChild(sellingPlanInput);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const subscribedSelector = makeSubscribedSelector({ id: productId });
|
|
97
|
+
const sellingPlanId = subscribedSelector(store.getState())?.frequency;
|
|
98
|
+
|
|
99
|
+
sellingPlanInput.value = sellingPlanId;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default function shopifyMiddleware(store) {
|
|
104
|
+
return next => action => {
|
|
105
|
+
/**
|
|
106
|
+
* This redux middleware will perform Shopify specific side-effects such as change
|
|
107
|
+
* the product selling plan when offer is cart
|
|
108
|
+
*/
|
|
109
|
+
switch (action.type) {
|
|
110
|
+
case OPTIN_PRODUCT:
|
|
111
|
+
case OPTOUT_PRODUCT:
|
|
112
|
+
case PRODUCT_CHANGE_FREQUENCY:
|
|
113
|
+
break;
|
|
114
|
+
case REQUEST_OFFER:
|
|
115
|
+
if (action.payload.offer?.isCart) {
|
|
116
|
+
setupCart(store);
|
|
117
|
+
} else {
|
|
118
|
+
setupPdp(store);
|
|
119
|
+
}
|
|
120
|
+
default:
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
next(action);
|
|
124
|
+
|
|
125
|
+
switch (action.type) {
|
|
126
|
+
case OPTIN_PRODUCT:
|
|
127
|
+
case OPTOUT_PRODUCT:
|
|
128
|
+
case PRODUCT_CHANGE_FREQUENCY:
|
|
129
|
+
synchronizeCartOptin(action, store);
|
|
130
|
+
case REQUEST_OFFER:
|
|
131
|
+
case SETUP_PRODUCT:
|
|
132
|
+
synchronizeSellingPlan(store);
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|