@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.
@@ -38,7 +38,8 @@ async function setupPdp(store, offer) {
38
38
  const handle = guessProductHandle();
39
39
  if (handle) {
40
40
  try {
41
- store.dispatch({ type: SETUP_PRODUCT, payload: await getProduct(handle) });
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 { safeProductId } from '../core/utils';
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 { payload: product } = action;
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 product = action.payload;
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?.selling_plans
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 product = action.payload;
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 { autoship, autoship_by_default, in_stock, offer: offerEl } = action.payload;
199
-
200
- return Object.keys(autoship).reduce(
201
- (acc, id) =>
202
- acc.concat(
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 productIds = action.payload.variants.map(variant => variant.id).map(id => `${id}`);
213
- const sellingPlanGroup = getOGSellingPlanGroup(action.payload);
214
- const frequencies = sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
215
- // if the product is in the list of variants and its frequency isn't a valid selling plan, replace with the first valid selling plan
216
- return state.map(cur => ({
217
- ...cur,
218
- frequency:
219
- productIds.some(id => id === cur.id) && !frequencies.some(freq => freq === cur.frequency)
220
- ? frequencies[0]
221
- : cur.frequency
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 product = action.payload;
364
+ const {
365
+ payload: { product }
366
+ } = action;
232
367
  return (
233
368
  [product, ...product?.variants]?.reduce(
234
369
  (acc, cur) => ({