@ordergroove/offers 2.27.23 → 2.28.1
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/CHANGELOG.md +29 -0
- package/README.md +24 -1
- package/dist/bundle-report.html +40 -37
- package/dist/offers.js +54 -54
- package/dist/offers.js.map +3 -3
- package/karma-shopify.conf.js +79 -0
- package/karma.conf.js +1 -1
- package/package.json +6 -4
- package/src/components/Offer.js +1 -0
- package/src/components/OptinButton.js +25 -5
- package/src/components/OptinSelect.js +2 -4
- package/src/components/OptinStatus.js +5 -1
- package/src/components/OptinToggle.js +9 -2
- package/src/components/__tests__/OG.fspec.js +84 -2
- package/src/core/__tests__/utils.spec.js +26 -0
- package/src/core/selectors.js +18 -1
- package/src/core/store.js +3 -2
- package/src/core/utils.ts +45 -6
- package/src/core/waitUntilOffersReady.js +66 -0
- package/src/index.js +6 -2
- package/src/init-shopify-tests.js +7 -0
- package/src/make-api.js +5 -15
- package/src/shopify/__tests__/shopifyReducer.spec.js +915 -122
- package/src/shopify/shopifyMiddleware.ts +4 -3
- package/src/shopify/shopifyReducer.js +169 -34
|
@@ -38,7 +38,8 @@ async function setupPdp(store, offer) {
|
|
|
38
38
|
const handle = guessProductHandle();
|
|
39
39
|
if (handle) {
|
|
40
40
|
try {
|
|
41
|
-
|
|
41
|
+
const product = await getProduct(handle);
|
|
42
|
+
store.dispatch({ type: SETUP_PRODUCT, payload: { product, offer } });
|
|
42
43
|
} catch (err) {
|
|
43
44
|
console.warn('OG: Unable to fetch product details for PDP', err);
|
|
44
45
|
}
|
|
@@ -126,7 +127,7 @@ async function setupCart(store, offer) {
|
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
const products = await Promise.all(Array.from(new Set(items.map(({ handle }) => handle))).map(getProduct));
|
|
129
|
-
products.forEach(product => store.dispatch({ type: SETUP_PRODUCT, payload: product }));
|
|
130
|
+
products.forEach(product => store.dispatch({ type: SETUP_PRODUCT, payload: { product, offer } }));
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
/**
|
|
@@ -339,7 +340,7 @@ export function getOrCreateHidden(parent, name, value) {
|
|
|
339
340
|
* @param store
|
|
340
341
|
*/
|
|
341
342
|
function synchronizeSellingPlan(store: any, offerElement?: HTMLElement) {
|
|
342
|
-
[...document.querySelectorAll('[name=id]')].forEach((productIdInput: HTMLInputElement) => {
|
|
343
|
+
[...document.querySelectorAll('form[action$="/cart/add"] [name=id]')].forEach((productIdInput: HTMLInputElement) => {
|
|
343
344
|
const productId = productIdInput.value;
|
|
344
345
|
|
|
345
346
|
const subscribedSelector = makeSubscribedSelector({ id: productId });
|
|
@@ -23,7 +23,13 @@ import baseReducer, {
|
|
|
23
23
|
sessionId,
|
|
24
24
|
templates
|
|
25
25
|
} from '../core/reducer';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
getFirstSellingPlan,
|
|
28
|
+
hasShopifySellingPlans,
|
|
29
|
+
isOgFrequency,
|
|
30
|
+
mapFrequencyToSellingPlan,
|
|
31
|
+
safeProductId
|
|
32
|
+
} from '../core/utils';
|
|
27
33
|
|
|
28
34
|
const money = val => (val === null ? '' : `$${val.toString().replace(/(\d\d)$/, '.$1')}`);
|
|
29
35
|
|
|
@@ -54,6 +60,84 @@ const overrideLineKey = (state, productId, newValue) => {
|
|
|
54
60
|
return state;
|
|
55
61
|
};
|
|
56
62
|
|
|
63
|
+
export const getDefaultSellingPlan = (sellingPlans, frequenciesEveryPeriod, defaultFrequency) => {
|
|
64
|
+
if (!defaultFrequency) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!isOgFrequency(defaultFrequency)) {
|
|
69
|
+
return defaultFrequency;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (hasShopifySellingPlans(sellingPlans, frequenciesEveryPeriod)) {
|
|
73
|
+
const sellingPlan = mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, defaultFrequency);
|
|
74
|
+
|
|
75
|
+
if (sellingPlan) {
|
|
76
|
+
return sellingPlan;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return getFirstSellingPlan(sellingPlans);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return defaultFrequency;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const mapExistingOptinsFromOfferResponse = (state, offerEl) =>
|
|
86
|
+
state.map(it => {
|
|
87
|
+
if (isOgFrequency(it?.frequency)) {
|
|
88
|
+
return {
|
|
89
|
+
...it,
|
|
90
|
+
frequency: hasShopifySellingPlans(offerEl?.config?.frequencies, offerEl?.config?.frequenciesEveryPeriod)
|
|
91
|
+
? mapFrequencyToSellingPlan(
|
|
92
|
+
offerEl?.config?.frequencies,
|
|
93
|
+
offerEl?.config?.frequenciesEveryPeriod,
|
|
94
|
+
it.frequency
|
|
95
|
+
) ||
|
|
96
|
+
mapFrequencyToSellingPlan(
|
|
97
|
+
offerEl?.config?.frequencies,
|
|
98
|
+
offerEl?.config?.frequenciesEveryPeriod,
|
|
99
|
+
offerEl.defaultFrequency
|
|
100
|
+
) ||
|
|
101
|
+
getFirstSellingPlan(offerEl?.config?.frequencies)
|
|
102
|
+
: it.frequency
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return it;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
export const reduceNewOptinsFromOfferResponse = (
|
|
110
|
+
{ autoship = {}, autoship_by_default = {}, default_frequencies = {}, in_stock = {} },
|
|
111
|
+
existingOptins,
|
|
112
|
+
offerEl
|
|
113
|
+
) =>
|
|
114
|
+
Object.keys(autoship).reduce((acc, id) => {
|
|
115
|
+
if (!existingOptins.some(it => it.id === id)) {
|
|
116
|
+
if (!(autoship[id] && autoship_by_default[id] && in_stock[id])) return acc;
|
|
117
|
+
const { config: { frequencies: sellingPlans, frequenciesEveryPeriod } = {}, defaultFrequency } = offerEl;
|
|
118
|
+
const psdf = default_frequencies[id];
|
|
119
|
+
let frequency;
|
|
120
|
+
|
|
121
|
+
if (default_frequencies[id] && hasShopifySellingPlans(sellingPlans, frequenciesEveryPeriod)) {
|
|
122
|
+
frequency =
|
|
123
|
+
mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, `${psdf.every}_${psdf.every_period}`) ||
|
|
124
|
+
getDefaultSellingPlan(sellingPlans, frequenciesEveryPeriod, defaultFrequency) ||
|
|
125
|
+
getFirstSellingPlan(sellingPlans);
|
|
126
|
+
} else if (default_frequencies[id]) {
|
|
127
|
+
frequency = `${psdf.every}_${psdf.every_period}`;
|
|
128
|
+
} else {
|
|
129
|
+
frequency = getDefaultSellingPlan(sellingPlans, frequenciesEveryPeriod, defaultFrequency) || '_'; // Placeholder to be backfilled in SETUP_PRODUCT reducer
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return acc.concat({
|
|
133
|
+
id,
|
|
134
|
+
frequency
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return acc;
|
|
139
|
+
}, []);
|
|
140
|
+
|
|
57
141
|
const getOGSellingPlanGroup = product => {
|
|
58
142
|
const sellingPlanGroup = product?.selling_plan_groups.find(group => {
|
|
59
143
|
return group.name === 'Subscribe and Save';
|
|
@@ -88,7 +172,9 @@ export const autoshipEligible = (state = {}, action) => {
|
|
|
88
172
|
return cart.items.reduce(reduceProductCartLine, state);
|
|
89
173
|
}
|
|
90
174
|
if (constants.SETUP_PRODUCT === action.type) {
|
|
91
|
-
const {
|
|
175
|
+
const {
|
|
176
|
+
payload: { product }
|
|
177
|
+
} = action;
|
|
92
178
|
return [product, ...(product?.variants || [])]?.reduce(
|
|
93
179
|
(acc, cur) => ({
|
|
94
180
|
...overrideLineKey(acc, cur.id, cur.selling_plan_allocations?.length > 0),
|
|
@@ -109,6 +195,21 @@ export function textToFreq(text) {
|
|
|
109
195
|
return null;
|
|
110
196
|
}
|
|
111
197
|
|
|
198
|
+
export function sellingPlansToEveryPeriod(sellingPlanGroup) {
|
|
199
|
+
return sellingPlanGroup?.selling_plans
|
|
200
|
+
?.map(({ options }) => options || [])
|
|
201
|
+
.flat()
|
|
202
|
+
.map(({ value }) => textToFreq(value));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function sellingPlansToText(sellingPlanGroup, frequencies) {
|
|
206
|
+
return sellingPlanGroup.options?.[0]?.values || frequencies;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function sellingPlansToFrequencies(sellingPlanGroup) {
|
|
210
|
+
return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
112
213
|
export const config = (
|
|
113
214
|
state = {
|
|
114
215
|
frequencies: [],
|
|
@@ -132,25 +233,54 @@ export const config = (
|
|
|
132
233
|
}
|
|
133
234
|
|
|
134
235
|
if (constants.SETUP_PRODUCT === action.type) {
|
|
135
|
-
const
|
|
236
|
+
const {
|
|
237
|
+
payload: { offer: offerEl, product }
|
|
238
|
+
} = action;
|
|
136
239
|
const sellingPlanGroup = getOGSellingPlanGroup(product);
|
|
137
240
|
const frequencies = sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
|
|
138
241
|
if (frequencies?.length) {
|
|
139
|
-
const frequenciesEveryPeriod = sellingPlanGroup
|
|
140
|
-
?.map(({ options }) => options || [])
|
|
141
|
-
.flat()
|
|
142
|
-
.map(({ value }) => textToFreq(value));
|
|
242
|
+
const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
|
|
143
243
|
const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
|
|
244
|
+
let defaultFrequency = state.defaultFrequency;
|
|
245
|
+
|
|
246
|
+
if (defaultFrequency && isOgFrequency(defaultFrequency)) {
|
|
247
|
+
defaultFrequency =
|
|
248
|
+
mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, defaultFrequency) ||
|
|
249
|
+
getFirstSellingPlan(frequencies) ||
|
|
250
|
+
defaultFrequency;
|
|
251
|
+
}
|
|
252
|
+
|
|
144
253
|
return {
|
|
145
254
|
...state,
|
|
146
|
-
defaultFrequency: frequencies[0],
|
|
147
255
|
frequenciesEveryPeriod,
|
|
148
256
|
frequencies,
|
|
149
|
-
frequenciesText
|
|
257
|
+
frequenciesText,
|
|
258
|
+
...(defaultFrequency ? { defaultFrequency } : {})
|
|
150
259
|
};
|
|
151
260
|
}
|
|
152
261
|
}
|
|
153
262
|
|
|
263
|
+
if (constants.RECEIVE_OFFER === action.type) {
|
|
264
|
+
const {
|
|
265
|
+
payload: { offer: offerEl }
|
|
266
|
+
} = action;
|
|
267
|
+
const { defaultFrequency, config: { frequencies: sellingPlans, frequenciesEveryPeriod } = {} } = offerEl;
|
|
268
|
+
|
|
269
|
+
if (!isOgFrequency(defaultFrequency))
|
|
270
|
+
return {
|
|
271
|
+
...state,
|
|
272
|
+
defaultFrequency: defaultFrequency
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
...state,
|
|
277
|
+
defaultFrequency:
|
|
278
|
+
mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, defaultFrequency) ||
|
|
279
|
+
getFirstSellingPlan(sellingPlans) ||
|
|
280
|
+
defaultFrequency
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
154
284
|
return state;
|
|
155
285
|
};
|
|
156
286
|
|
|
@@ -166,8 +296,9 @@ export const inStock = (state = {}, action) => {
|
|
|
166
296
|
}
|
|
167
297
|
|
|
168
298
|
if (constants.SETUP_PRODUCT === action.type) {
|
|
169
|
-
const
|
|
170
|
-
|
|
299
|
+
const {
|
|
300
|
+
payload: { product }
|
|
301
|
+
} = action;
|
|
171
302
|
return [product, ...product?.variants]?.reduce(productOrVariantInStockReducer, state) || state;
|
|
172
303
|
}
|
|
173
304
|
// force offer to refresh when requesting a new one
|
|
@@ -194,33 +325,35 @@ export const optedin = (state = [], action) => {
|
|
|
194
325
|
)
|
|
195
326
|
);
|
|
196
327
|
}
|
|
328
|
+
|
|
197
329
|
if (constants.RECEIVE_OFFER === action.type) {
|
|
198
|
-
const {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
!acc.some(it => it.id === id) && autoship[id] && autoship_by_default[id] && in_stock[id]
|
|
204
|
-
? { id, frequency: offerEl.defaultFrequency }
|
|
205
|
-
: []
|
|
206
|
-
),
|
|
207
|
-
state
|
|
208
|
-
);
|
|
330
|
+
const { offer: offerEl = {}, ...payload } = action.payload;
|
|
331
|
+
const existingOptins = mapExistingOptinsFromOfferResponse(state, offerEl);
|
|
332
|
+
const newOptins = reduceNewOptinsFromOfferResponse(payload, existingOptins, offerEl);
|
|
333
|
+
|
|
334
|
+
return [...existingOptins, ...newOptins];
|
|
209
335
|
}
|
|
210
336
|
|
|
211
337
|
if (constants.SETUP_PRODUCT === action.type) {
|
|
212
|
-
const
|
|
213
|
-
const sellingPlanGroup = getOGSellingPlanGroup(
|
|
214
|
-
const frequencies = sellingPlanGroup
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
frequency
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
:
|
|
222
|
-
|
|
338
|
+
const { product } = action.payload;
|
|
339
|
+
const sellingPlanGroup = getOGSellingPlanGroup(product);
|
|
340
|
+
const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
|
|
341
|
+
const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
|
|
342
|
+
|
|
343
|
+
return state.map(curr => {
|
|
344
|
+
if (isOgFrequency(curr.frequency)) {
|
|
345
|
+
return {
|
|
346
|
+
...curr,
|
|
347
|
+
frequency:
|
|
348
|
+
mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, curr.frequency) ||
|
|
349
|
+
getFirstSellingPlan(frequencies)
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return curr;
|
|
354
|
+
});
|
|
223
355
|
}
|
|
356
|
+
|
|
224
357
|
return coreOptedin(state, action);
|
|
225
358
|
};
|
|
226
359
|
|
|
@@ -228,7 +361,9 @@ export const productOffer = (state = {}, action) => state;
|
|
|
228
361
|
|
|
229
362
|
export const productPlans = (state = {}, action) => {
|
|
230
363
|
if (constants.SETUP_PRODUCT === action.type) {
|
|
231
|
-
const
|
|
364
|
+
const {
|
|
365
|
+
payload: { product }
|
|
366
|
+
} = action;
|
|
232
367
|
return (
|
|
233
368
|
[product, ...product?.variants]?.reduce(
|
|
234
369
|
(acc, cur) => ({
|