@ordergroove/offers 2.44.0 → 2.44.1-alpha-PR-1167-2.36

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.
Files changed (40) hide show
  1. package/dist/bundle-report.html +42 -39
  2. package/dist/offers.js +69 -69
  3. package/dist/offers.js.map +4 -4
  4. package/package.json +2 -2
  5. package/src/components/FrequencyStatus.js +14 -10
  6. package/src/components/Offer.js +33 -14
  7. package/src/components/OptinButton.js +2 -2
  8. package/src/components/OptinSelect.js +6 -5
  9. package/src/components/OptinStatus.js +16 -9
  10. package/src/components/Price.js +3 -3
  11. package/src/components/SelectFrequency.js +11 -6
  12. package/src/components/TestWizard.js +45 -41
  13. package/src/components/UpsellModal.js +9 -3
  14. package/src/components/__tests__/Offer.spec.js +0 -19
  15. package/src/components/__tests__/OptinStatus.spec.js +17 -4
  16. package/src/core/__tests__/actions.spec.js +47 -1
  17. package/src/core/__tests__/base.spec.js +0 -77
  18. package/src/core/__tests__/experiments.spec.js +0 -3
  19. package/src/core/__tests__/offerRequest.spec.js +2 -1
  20. package/src/core/__tests__/selectors.spec.js +7 -7
  21. package/src/core/actions-preview.js +6 -3
  22. package/src/core/actions.js +22 -13
  23. package/src/core/base.js +0 -23
  24. package/src/core/offerRequest.js +1 -1
  25. package/src/core/{reducer.js → reducer.ts} +30 -10
  26. package/src/core/selectors.ts +215 -0
  27. package/src/core/types/api.ts +71 -0
  28. package/src/core/types/reducer.ts +94 -0
  29. package/src/core/types/utility.ts +1 -0
  30. package/src/core/utils.ts +32 -15
  31. package/src/make-api.js +1 -1
  32. package/src/shopify/__tests__/reducers/config.spec.js +603 -0
  33. package/src/shopify/__tests__/shopifyReducer.spec.js +69 -744
  34. package/src/shopify/__tests__/utils.spec.js +24 -1
  35. package/src/shopify/reducers/config.ts +185 -0
  36. package/src/shopify/shopifyMiddleware.ts +2 -9
  37. package/src/shopify/{shopifyReducer.js → shopifyReducer.ts} +50 -195
  38. package/src/shopify/utils.ts +25 -0
  39. package/src/core/selectors.js +0 -192
  40. package/src/types.ts +0 -16
@@ -1,4 +1,4 @@
1
- import { getPayAsYouGoSellingPlanGroups, money, percentage } from '../utils';
1
+ import { getPayAsYouGoSellingPlanGroups, money, percentage, textToFreq } from '../utils';
2
2
 
3
3
  describe('Shopify Utils', () => {
4
4
  describe('Money', () => {
@@ -26,6 +26,29 @@ describe('Shopify Utils', () => {
26
26
  expect(formattedPrice).toBe('10%');
27
27
  });
28
28
  });
29
+
30
+ describe('textToFreq', () => {
31
+ it('textToFreq should return freq', () => {
32
+ expect(textToFreq('DAY')).toEqual('1_1');
33
+ expect(textToFreq('DAYS')).toEqual('1_1');
34
+ expect(textToFreq('DAYLY')).toEqual('1_1');
35
+ expect(textToFreq('1 DAY')).toEqual('1_1');
36
+ expect(textToFreq('2 DAYS')).toEqual('2_1');
37
+ expect(textToFreq('2 day(s)')).toEqual('2_1');
38
+
39
+ expect(textToFreq('week')).toEqual('1_2');
40
+ expect(textToFreq('weekly')).toEqual('1_2');
41
+ expect(textToFreq('1 week')).toEqual('1_2');
42
+ expect(textToFreq('2 weeks')).toEqual('2_2');
43
+ expect(textToFreq('2 week(s)')).toEqual('2_2');
44
+
45
+ expect(textToFreq('MONTH')).toEqual('1_3');
46
+ expect(textToFreq('MONTHLY')).toEqual('1_3');
47
+ expect(textToFreq('1 month')).toEqual('1_3');
48
+ expect(textToFreq('2 months')).toEqual('2_3');
49
+ expect(textToFreq('2 month(s)')).toEqual('2_3');
50
+ });
51
+ });
29
52
  });
30
53
 
31
54
  describe('selling plan queries', () => {
@@ -0,0 +1,185 @@
1
+ import * as constants from '../../core/constants';
2
+ import { getFirstSellingPlan, isOgFrequency, mapFrequencyToSellingPlan, safeProductId } from '../../core/utils';
3
+ import type {
4
+ ConfigState,
5
+ ReceiveMerchantSettingsPayload,
6
+ ReceiveOfferPayload,
7
+ SetupProductPayload
8
+ } from '../../core/types/reducer';
9
+
10
+ import {
11
+ getPayAsYouGoSellingPlanGroup,
12
+ sellingPlansToEveryPeriod,
13
+ sellingPlansToFrequencies,
14
+ getPrepaidShipments
15
+ } from '../utils';
16
+ import { ShopifySellingPlanGroupsEntity, ShopifyVariantsEntity } from '../types/shopify';
17
+
18
+ const config = (
19
+ state: ConfigState = {
20
+ offerType: 'radio',
21
+ productFrequencies: {}
22
+ },
23
+ action
24
+ ): ConfigState => {
25
+ if (constants.SETUP_PRODUCT === action.type) {
26
+ const {
27
+ payload: { product, currency }
28
+ } = action as { payload: SetupProductPayload };
29
+ let configToAdd: ConfigState = {};
30
+ let productFrequencies = product.variants?.reduce(
31
+ (acc, variant) => reduceSellingPlansToFrequencies(acc, variant, product.selling_plan_groups, state),
32
+ {}
33
+ );
34
+
35
+ configToAdd = {
36
+ ...configToAdd,
37
+
38
+ productFrequencies: {
39
+ ...state.productFrequencies,
40
+ ...productFrequencies
41
+ }
42
+ };
43
+
44
+ // prepaid selling plans
45
+ const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
46
+ if (prepaidSellingPlanGroups.length) {
47
+ configToAdd = {
48
+ ...configToAdd,
49
+ prepaidSellingPlans: { ...state.prepaidSellingPlans, ...getPrepaidSellingPlans(prepaidSellingPlanGroups) }
50
+ };
51
+ }
52
+ return {
53
+ ...state,
54
+ ...configToAdd,
55
+ storeCurrency: currency
56
+ };
57
+ }
58
+
59
+ if (constants.RECEIVE_OFFER === action.type) {
60
+ const {
61
+ payload: { offer: offerEl }
62
+ } = action as { payload: ReceiveOfferPayload };
63
+ const { defaultFrequency, product } = offerEl || {};
64
+ const { prepaidSellingPlans = {} } = state;
65
+
66
+ // productFrequencies does not have entries for the cart ID
67
+ // eligible frequencies apply to cart entries for the product
68
+ const productId = safeProductId(product?.id);
69
+ const currentProductFrequencies = state.productFrequencies[productId];
70
+
71
+ return {
72
+ ...state,
73
+ productFrequencies: {
74
+ ...state.productFrequencies,
75
+ [productId]: {
76
+ ...currentProductFrequencies,
77
+ defaultFrequency: getUpdatedDefaultFrequency(
78
+ productId,
79
+ defaultFrequency,
80
+ prepaidSellingPlans,
81
+ currentProductFrequencies?.frequencies,
82
+ currentProductFrequencies?.frequenciesEveryPeriod
83
+ )
84
+ }
85
+ }
86
+ };
87
+ }
88
+
89
+ if (constants.RECEIVE_MERCHANT_SETTINGS === action.type) {
90
+ return {
91
+ ...state,
92
+ merchantSettings: {
93
+ ...(action.payload as ReceiveMerchantSettingsPayload)
94
+ }
95
+ };
96
+ }
97
+
98
+ return state;
99
+ };
100
+
101
+ function getFrequencies(
102
+ productSellingPlanGroups: ShopifySellingPlanGroupsEntity[],
103
+ state: { defaultFrequency?: string }
104
+ ) {
105
+ const sellingPlanGroup = getPayAsYouGoSellingPlanGroup(productSellingPlanGroups);
106
+ const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
107
+ if (frequencies?.length) {
108
+ const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
109
+ const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
110
+ let defaultFrequency = state?.defaultFrequency;
111
+
112
+ if (defaultFrequency && isOgFrequency(defaultFrequency)) {
113
+ defaultFrequency =
114
+ mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, defaultFrequency) ||
115
+ getFirstSellingPlan(frequencies) ||
116
+ defaultFrequency;
117
+ }
118
+ return {
119
+ frequencies,
120
+ frequenciesEveryPeriod,
121
+ frequenciesText,
122
+ ...(defaultFrequency ? { defaultFrequency } : {})
123
+ };
124
+ }
125
+ return null;
126
+ }
127
+
128
+ function reduceSellingPlansToFrequencies(
129
+ acc: ConfigState['productFrequencies'],
130
+ variant: ShopifyVariantsEntity,
131
+ sellingPlanGroups: ShopifySellingPlanGroupsEntity[],
132
+ state: ConfigState
133
+ ) {
134
+ const sellingPlanGroupIdsForProduct = variant.selling_plan_allocations.map(
135
+ allocation => allocation.selling_plan_group_id
136
+ );
137
+ const applicableSellingPlanGroups = sellingPlanGroups.filter(group =>
138
+ sellingPlanGroupIdsForProduct.includes(group.id)
139
+ );
140
+ const frequencies = getFrequencies(applicableSellingPlanGroups, state.productFrequencies[variant.id]);
141
+ if (frequencies) {
142
+ acc[variant.id] = frequencies;
143
+ }
144
+ return acc;
145
+ }
146
+
147
+ function getUpdatedDefaultFrequency(
148
+ productId: string,
149
+ offerElementDefaultFrequency: string,
150
+ prepaidSellingPlans: ConfigState['prepaidSellingPlans'],
151
+ frequencies: string[] | undefined = [],
152
+ frequenciesEveryPeriod: string[] | undefined = []
153
+ ) {
154
+ // We don't want to be setting the default frequency to a prepaid selling plan
155
+ if (prepaidSellingPlans[productId]?.some(({ sellingPlan }) => sellingPlan === offerElementDefaultFrequency)) {
156
+ return getFirstSellingPlan(frequencies) || offerElementDefaultFrequency;
157
+ }
158
+
159
+ if (!isOgFrequency(offerElementDefaultFrequency)) {
160
+ return offerElementDefaultFrequency;
161
+ }
162
+
163
+ return (
164
+ mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, offerElementDefaultFrequency) ||
165
+ getFirstSellingPlan(frequencies) ||
166
+ offerElementDefaultFrequency
167
+ );
168
+ }
169
+
170
+ function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
171
+ return prepaidSellingPlanGroups.reduce((acc, cur) => {
172
+ const variant = cur.name.split('-')[1];
173
+
174
+ const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
175
+ return {
176
+ numberShipments: getPrepaidShipments(sellingPlanObject),
177
+ sellingPlan: String(sellingPlanObject.id)
178
+ };
179
+ });
180
+
181
+ return { ...acc, [variant]: sellingPlanInfo };
182
+ }, {});
183
+ }
184
+
185
+ export default config;
@@ -18,6 +18,7 @@ import { makeSubscribedSelector } from '../core/selectors';
18
18
  import { getOrCreateHidden, safeProductId } from '../core/utils';
19
19
  import { getTrackingKey } from './shopifyTrackingMiddleware';
20
20
  import { ShopifyCart, ShopifyProductEntity } from './types/shopify';
21
+ import { SetupProductPayload, SetupCartPayload, OfferElement } from '../core/types/reducer';
21
22
 
22
23
  const SHOPIFY_ROOT = window.Shopify?.routes?.root || '/';
23
24
  const CART_PAGE_URL = '/cart';
@@ -25,14 +26,6 @@ const CART_JS_URL = `${SHOPIFY_ROOT}cart.js`;
25
26
  const CART_CHANGE_URL = `${SHOPIFY_ROOT}cart/change.js`;
26
27
  const PRODUCTS_URL = `${SHOPIFY_ROOT}products/`;
27
28
 
28
- type SetupProductPayload = {
29
- product: ShopifyProductEntity;
30
- offer: any;
31
- currency: string;
32
- };
33
-
34
- type SetupCartPayload = ShopifyCart;
35
-
36
29
  /**
37
30
  * List of section DOM elements to update via section-rendering api https://shopify.dev/api/section-rendering
38
31
  */
@@ -344,7 +337,7 @@ export function getSubscribedFrequency(productId, store) {
344
337
  *
345
338
  * @param store
346
339
  */
347
- function synchronizeSellingPlan(store: any, offerElement?: HTMLElement) {
340
+ function synchronizeSellingPlan(store: any, offerElement?: OfferElement) {
348
341
  if (offerElement?.isCart) return; // hidden inputs are used when product page, not cart.
349
342
  if (!offerElement?.shouldEnableOffer) return; // do not set a selling plan if we're hiding the offer
350
343
 
@@ -31,16 +31,26 @@ import {
31
31
  safeProductId,
32
32
  getMatchingProductIfExists
33
33
  } from '../core/utils';
34
-
35
- import { getObjectStructuredProductPlans } from '../core/adapters';
34
+ import type {
35
+ AutoshipEligibleState,
36
+ OfferElement,
37
+ OptedInState,
38
+ OptInItem,
39
+ ReceiveOfferPayload,
40
+ SetupProductPayload
41
+ } from '../core/types/reducer';
36
42
 
37
43
  import { sellingPlanAllocationsReducer, getSellingPlans } from './reducers/productPlans';
44
+ import config from './reducers/config';
38
45
  import { experimentsReducer } from '../core/experiments';
39
46
  import {
40
47
  getPayAsYouGoSellingPlanGroup,
41
48
  getPayAsYouGoSellingPlanGroups,
42
- isProductSpecificFrequencySellingPlanGroup
49
+ sellingPlansToEveryPeriod,
50
+ sellingPlansToFrequencies,
51
+ getPrepaidShipments
43
52
  } from './utils';
53
+ import { EmptyObject } from '../core/types/utility';
44
54
 
45
55
  const overrideLineKey = (state, productId, newValue) => {
46
56
  const keys = Object.keys(state).filter(it => it.startsWith(productId.toString()));
@@ -50,7 +60,11 @@ const overrideLineKey = (state, productId, newValue) => {
50
60
  return state;
51
61
  };
52
62
 
53
- export const getDefaultSellingPlan = (sellingPlans, frequenciesEveryPeriod, defaultFrequency) => {
63
+ export const getDefaultSellingPlan = (
64
+ sellingPlans: string[],
65
+ frequenciesEveryPeriod: string[],
66
+ defaultFrequency: string | undefined
67
+ ) => {
54
68
  if (!defaultFrequency) {
55
69
  return null;
56
70
  }
@@ -72,23 +86,27 @@ export const getDefaultSellingPlan = (sellingPlans, frequenciesEveryPeriod, defa
72
86
  return defaultFrequency;
73
87
  };
74
88
 
75
- export const mapExistingOptinsFromOfferResponse = (state, offerEl) =>
89
+ export const mapExistingOptinsFromOfferResponse = (
90
+ state: OptedInState,
91
+ offerEl: OfferElement | EmptyObject,
92
+ frequencyConfig: ReceiveOfferPayload['frequencyConfig']
93
+ ) =>
76
94
  state.map(it => {
77
95
  if (isOgFrequency(it?.frequency)) {
78
96
  return {
79
97
  ...it,
80
- frequency: hasShopifySellingPlans(offerEl?.config?.frequencies, offerEl?.config?.frequenciesEveryPeriod)
98
+ frequency: hasShopifySellingPlans(frequencyConfig?.frequencies, frequencyConfig?.frequenciesEveryPeriod)
81
99
  ? mapFrequencyToSellingPlan(
82
- offerEl?.config?.frequencies,
83
- offerEl?.config?.frequenciesEveryPeriod,
100
+ frequencyConfig?.frequencies,
101
+ frequencyConfig?.frequenciesEveryPeriod,
84
102
  it.frequency
85
103
  ) ||
86
104
  mapFrequencyToSellingPlan(
87
- offerEl?.config?.frequencies,
88
- offerEl?.config?.frequenciesEveryPeriod,
105
+ frequencyConfig?.frequencies,
106
+ frequencyConfig?.frequenciesEveryPeriod,
89
107
  offerEl?.defaultFrequency
90
108
  ) ||
91
- getFirstSellingPlan(offerEl?.config?.frequencies)
109
+ getFirstSellingPlan(frequencyConfig?.frequencies)
92
110
  : it.frequency
93
111
  };
94
112
  }
@@ -97,14 +115,16 @@ export const mapExistingOptinsFromOfferResponse = (state, offerEl) =>
97
115
  });
98
116
 
99
117
  export const reduceNewOptinsFromOfferResponse = (
100
- { autoship = {}, autoship_by_default = {}, default_frequencies = {}, in_stock = {} },
101
- existingOptins,
102
- offerEl
118
+ { autoship = {}, autoship_by_default = {}, default_frequencies = {}, in_stock = {} }: ReceiveOfferPayload,
119
+ existingOptins: OptedInState,
120
+ offerEl: OfferElement | EmptyObject,
121
+ frequencyConfig: ReceiveOfferPayload['frequencyConfig']
103
122
  ) =>
104
123
  Object.keys(autoship).reduce((acc, id) => {
105
124
  if (!existingOptins.some(it => it.id === id)) {
106
125
  if (!(autoship[id] && autoship_by_default[id] && in_stock[id])) return acc;
107
- const { config: { frequencies: sellingPlans, frequenciesEveryPeriod } = {}, defaultFrequency } = offerEl || {};
126
+ const { frequencies: sellingPlans, frequenciesEveryPeriod } = frequencyConfig;
127
+ const { defaultFrequency } = offerEl || {};
108
128
  const psdf = default_frequencies[id];
109
129
  let frequency;
110
130
 
@@ -133,17 +153,12 @@ const productOrVariantInStockReducer = (acc, cur) => ({
133
153
  [cur.id]: cur.available
134
154
  });
135
155
 
136
- const productTrue = (acc, [id]) => ({ ...acc, [id]: true });
137
-
138
156
  const reduceProductCartLine = (acc, cur) => {
139
157
  const productId = safeProductId(cur.key);
140
158
  return { ...acc, [cur.key]: acc[productId] || null };
141
159
  };
142
160
 
143
- export const autoshipEligible = (state = {}, action) => {
144
- if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
145
- return Object.entries(action.payload).reduce(productTrue, state);
146
- }
161
+ export const autoshipEligible = (state: AutoshipEligibleState = {}, action): AutoshipEligibleState => {
147
162
  if (constants.SETUP_CART === action.type) {
148
163
  const { payload: cart } = action;
149
164
  return cart.items.reduce(reduceProductCartLine, state);
@@ -151,14 +166,14 @@ export const autoshipEligible = (state = {}, action) => {
151
166
  if (constants.SETUP_PRODUCT === action.type) {
152
167
  const {
153
168
  payload: { product }
154
- } = action;
169
+ } = action as { payload: SetupProductPayload };
155
170
  const applicableSellingPlanGroups = getPayAsYouGoSellingPlanGroups(product?.selling_plan_groups);
156
171
 
157
172
  const ogSellingPlanIds = new Set(
158
173
  applicableSellingPlanGroups.flatMap(group => group.selling_plans.map(sellingPlan => sellingPlan.id)) ?? []
159
174
  );
160
175
 
161
- return [product, ...(product?.variants || [])]?.reduce((acc, cur) => {
176
+ return product.variants.reduce((acc, cur) => {
162
177
  const productSellingPlansFromOG =
163
178
  cur?.selling_plan_allocations?.filter(sellingPlan => ogSellingPlanIds.has(sellingPlan.selling_plan_id)) ?? [];
164
179
  // a product is autoship eligible if it has plans from the default or PSFL selling plan group
@@ -178,157 +193,7 @@ export const autoshipEligible = (state = {}, action) => {
178
193
  return state;
179
194
  };
180
195
 
181
- export function textToFreq(text) {
182
- const period = ['day', 'week', 'month'].findIndex(it => text.toLowerCase().includes(it)) + 1;
183
- const every = (text.match(/(\d+)/) || ['', 1])[1];
184
- if (every && period) {
185
- return `${every}_${period}`;
186
- }
187
- return null;
188
- }
189
-
190
- export function sellingPlansToEveryPeriod(sellingPlanGroup) {
191
- return sellingPlanGroup?.selling_plans
192
- ?.map(({ options }) => options || [])
193
- .flat()
194
- .map(({ value }) => textToFreq(value));
195
- }
196
-
197
- export function sellingPlansToText(sellingPlanGroup, frequencies) {
198
- return sellingPlanGroup.options?.[0]?.values || frequencies;
199
- }
200
-
201
- export function sellingPlansToFrequencies(sellingPlanGroup) {
202
- return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
203
- }
204
-
205
- function getPrepaidShipments(sellingPlan) {
206
- const shipments = sellingPlan?.options.find(({ name }) => name === 'Shipment amount')?.value.split(' ')[0];
207
- return shipments ? Number(shipments) : undefined;
208
- }
209
-
210
- function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
211
- return prepaidSellingPlanGroups.reduce((acc, cur) => {
212
- const variant = cur.name.split('-')[1];
213
-
214
- const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
215
- return {
216
- numberShipments: getPrepaidShipments(sellingPlanObject),
217
- sellingPlan: String(sellingPlanObject.id)
218
- };
219
- });
220
-
221
- return { ...acc, [variant]: sellingPlanInfo };
222
- }, {});
223
- }
224
-
225
- export const config = (
226
- state = {
227
- frequencies: [],
228
- offerType: 'radio',
229
- frequenciesEveryPeriod: []
230
- },
231
- action
232
- ) => {
233
- if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
234
- const frequencies = [...new Set(Object.values(action.payload).map(Object.keys).flat())];
235
- return {
236
- ...state,
237
- frequencies
238
- };
239
- }
240
-
241
- if (constants.SETUP_PRODUCT === action.type) {
242
- const {
243
- payload: { product, currency }
244
- } = action;
245
- let configToAdd = {};
246
- // pay as you go selling plans
247
- const sellingPlanGroup = getPayAsYouGoSellingPlanGroup(product?.selling_plan_groups);
248
- const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
249
- if (frequencies?.length) {
250
- const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
251
- const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
252
- let defaultFrequency = state.defaultFrequency;
253
-
254
- if (defaultFrequency && isOgFrequency(defaultFrequency)) {
255
- defaultFrequency =
256
- mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, defaultFrequency) ||
257
- getFirstSellingPlan(frequencies) ||
258
- defaultFrequency;
259
- }
260
- configToAdd = {
261
- frequencies,
262
- frequenciesEveryPeriod,
263
- frequenciesText,
264
- ...(defaultFrequency ? { defaultFrequency } : {}),
265
- hasProductSpecificFrequencies:
266
- state.hasProductSpecificFrequencies || isProductSpecificFrequencySellingPlanGroup(sellingPlanGroup)
267
- };
268
- }
269
-
270
- // prepaid selling plans
271
- const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
272
- if (prepaidSellingPlanGroups.length) {
273
- configToAdd = {
274
- ...configToAdd,
275
- prepaidSellingPlans: { ...state.prepaidSellingPlans, ...getPrepaidSellingPlans(prepaidSellingPlanGroups) }
276
- };
277
- }
278
- return {
279
- ...state,
280
- ...configToAdd,
281
- storeCurrency: currency
282
- };
283
- }
284
-
285
- if (constants.RECEIVE_OFFER === action.type) {
286
- const {
287
- payload: { offer: offerEl }
288
- } = action;
289
- const {
290
- defaultFrequency,
291
- config: { frequencies: sellingPlans, frequenciesEveryPeriod, prepaidSellingPlans = {} } = {},
292
- product
293
- } = offerEl || {};
294
-
295
- // We don't want to be setting the default frequency to a prepaid selling plan
296
- if (prepaidSellingPlans[product?.id]?.some(({ sellingPlan }) => sellingPlan === defaultFrequency)) {
297
- return { ...state, defaultFrequency: getFirstSellingPlan(sellingPlans) || defaultFrequency };
298
- }
299
-
300
- if (!isOgFrequency(defaultFrequency))
301
- return {
302
- ...state,
303
- defaultFrequency: defaultFrequency
304
- };
305
-
306
- return {
307
- ...state,
308
- defaultFrequency:
309
- mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, defaultFrequency) ||
310
- getFirstSellingPlan(sellingPlans) ||
311
- defaultFrequency
312
- };
313
- }
314
-
315
- if (constants.RECEIVE_MERCHANT_SETTINGS === action.type) {
316
- return {
317
- ...state,
318
- merchantSettings: {
319
- ...action.payload
320
- }
321
- };
322
- }
323
-
324
- return state;
325
- };
326
-
327
196
  export const inStock = (state = {}, action) => {
328
- if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
329
- return Object.entries(action.payload).reduce(productTrue, state);
330
- }
331
-
332
197
  if (constants.SETUP_CART === action.type) {
333
198
  const cart = action.payload;
334
199
 
@@ -338,7 +203,9 @@ export const inStock = (state = {}, action) => {
338
203
  if (constants.SETUP_PRODUCT === action.type) {
339
204
  const {
340
205
  payload: { product }
341
- } = action;
206
+ } = action as { payload: SetupProductPayload };
207
+ // it's unclear whether this needs to check the base product object or could only check the variants
208
+ // leaving for now to preserve backwards compatibility
342
209
  return [product, ...(product?.variants ?? [])]?.reduce(productOrVariantInStockReducer, state) || state;
343
210
  }
344
211
  // force offer to refresh when requesting a new one
@@ -354,18 +221,9 @@ export const inStock = (state = {}, action) => {
354
221
 
355
222
  export const offer = (state = {}, _action) => state;
356
223
 
357
- function getFrequencyForPrepaidShipments({ prepaidShipments, offer: offerEl, product }) {
358
- if (prepaidShipments) {
359
- const productId = safeProductId(product.id);
360
- const plan = offerEl?.config.prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
361
- return plan ? plan.sellingPlan : null;
362
- }
363
- return offerEl?.config.frequencies[0];
364
- }
365
-
366
224
  function getOptedInItem(cartItem) {
367
225
  const prepaidShipments = getPrepaidShipments(cartItem.selling_plan_allocation.selling_plan);
368
- const item = {
226
+ const item: OptInItem = {
369
227
  id: cartItem.key,
370
228
  frequency: `${cartItem.selling_plan_allocation.selling_plan.id}`
371
229
  };
@@ -375,7 +233,7 @@ function getOptedInItem(cartItem) {
375
233
  return item;
376
234
  }
377
235
 
378
- export const optedin = (state = [], action) => {
236
+ export const optedin = (state: OptedInState = [], action): OptedInState => {
379
237
  if (constants.SETUP_CART === action.type) {
380
238
  const cart = action.payload;
381
239
  return state
@@ -384,9 +242,10 @@ export const optedin = (state = [], action) => {
384
242
  }
385
243
 
386
244
  if (constants.RECEIVE_OFFER === action.type) {
387
- const { offer: offerEl = {}, ...payload } = action.payload;
388
- const existingOptins = mapExistingOptinsFromOfferResponse(state, offerEl);
389
- const newOptins = reduceNewOptinsFromOfferResponse(payload, existingOptins, offerEl);
245
+ const payload = action.payload as ReceiveOfferPayload;
246
+ const { offer: offerEl = {}, frequencyConfig } = payload;
247
+ const existingOptins = mapExistingOptinsFromOfferResponse(state, offerEl, frequencyConfig);
248
+ const newOptins = reduceNewOptinsFromOfferResponse(payload, existingOptins, offerEl, frequencyConfig);
390
249
 
391
250
  return [...existingOptins, ...newOptins];
392
251
  }
@@ -423,7 +282,7 @@ export const optedin = (state = [], action) => {
423
282
  return rest.concat({
424
283
  ...oldone,
425
284
  ...payload.product,
426
- frequency: getFrequencyForPrepaidShipments(payload)
285
+ frequency: payload.frequency
427
286
  });
428
287
  }
429
288
 
@@ -436,13 +295,12 @@ export const productPlans = (state = {}, action) => {
436
295
  if (constants.SETUP_PRODUCT === action.type) {
437
296
  const {
438
297
  payload: { product, currency }
439
- } = action;
298
+ } = action as { payload: SetupProductPayload };
440
299
 
441
300
  const sellingPlans = getSellingPlans(product);
442
301
 
443
302
  return (
444
- // We consider the product here as well for cases where we don't have any variants
445
- [product, ...(product?.variants ?? [])]?.reduce(
303
+ product.variants.reduce(
446
304
  (acc, cur) => ({
447
305
  ...acc,
448
306
  [cur.id]: cur.selling_plan_allocations?.reduce(
@@ -469,9 +327,6 @@ export const productPlans = (state = {}, action) => {
469
327
  ) || state
470
328
  );
471
329
  }
472
- if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
473
- return getObjectStructuredProductPlans(action.payload);
474
- }
475
330
  return state;
476
331
  };
477
332
 
@@ -52,3 +52,28 @@ export const getPayAsYouGoSellingPlan = (sellingPlans: ShopifySellingPlansEntity
52
52
  const group = getPayAsYouGoSellingPlanGroup(sellingPlans.map(plan => plan.group));
53
53
  return sellingPlans.find(plan => plan.group === group);
54
54
  };
55
+
56
+ export function sellingPlansToFrequencies(sellingPlanGroup: ShopifySellingPlanGroupsEntity) {
57
+ return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
58
+ }
59
+
60
+ export function sellingPlansToEveryPeriod(sellingPlanGroup: ShopifySellingPlanGroupsEntity) {
61
+ return sellingPlanGroup?.selling_plans
62
+ ?.map(({ options }) => options || [])
63
+ .flat()
64
+ .map(({ value }) => textToFreq(value));
65
+ }
66
+
67
+ export function textToFreq(text) {
68
+ const period = ['day', 'week', 'month'].findIndex(it => text.toLowerCase().includes(it)) + 1;
69
+ const every = (text.match(/(\d+)/) || ['', 1])[1];
70
+ if (every && period) {
71
+ return `${every}_${period}`;
72
+ }
73
+ return null;
74
+ }
75
+
76
+ export function getPrepaidShipments(sellingPlan) {
77
+ const shipments = sellingPlan?.options.find(({ name }) => name === 'Shipment amount')?.value.split(' ')[0];
78
+ return shipments ? Number(shipments) : undefined;
79
+ }