@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
@@ -3,7 +3,7 @@ import {
3
3
  makeOptedinSelector,
4
4
  isSameProduct,
5
5
  templatesSelector,
6
- makeProductDefaultFrequencySelector,
6
+ makeProductSpecificDefaultFrequencySelector,
7
7
  makeProductPrepaidShipmentOptionsSelector
8
8
  } from '../selectors';
9
9
  import { stringifyFrequency } from '../api';
@@ -61,25 +61,25 @@ describe('templatesSelector', () => {
61
61
  });
62
62
  });
63
63
 
64
- describe('makeProductDefaultFrequencySelector', () => {
64
+ describe('makeProductSpecificDefaultFrequencySelector', () => {
65
65
  it('should return memoized function', () => {
66
- const firsCall = makeProductDefaultFrequencySelector(123);
66
+ const firsCall = makeProductSpecificDefaultFrequencySelector(123);
67
67
  expect(firsCall).toEqual(jasmine.any(Function));
68
- expect(firsCall).toBe(makeProductDefaultFrequencySelector(123));
68
+ expect(firsCall).toBe(makeProductSpecificDefaultFrequencySelector(123));
69
69
  });
70
70
 
71
71
  it('should return null by default', () => {
72
- const selectProductDefaultFrequency = makeProductDefaultFrequencySelector(123);
72
+ const selectProductDefaultFrequency = makeProductSpecificDefaultFrequencySelector(123);
73
73
  expect(selectProductDefaultFrequency({})).toEqual(null);
74
74
  });
75
75
 
76
76
  it('should select the product default frequency if there is one', () => {
77
- const selectProductDefaultFrequency = makeProductDefaultFrequencySelector(123);
77
+ const selectProductDefaultFrequency = makeProductSpecificDefaultFrequencySelector(123);
78
78
  expect(selectProductDefaultFrequency({ defaultFrequencies: { 123: '2_w' } })).toEqual('2_w');
79
79
  });
80
80
 
81
81
  it('should select the product default frequency if there is one (object frequency)', () => {
82
- const selectProductDefaultFrequency = makeProductDefaultFrequencySelector(123);
82
+ const selectProductDefaultFrequency = makeProductSpecificDefaultFrequencySelector(123);
83
83
  const frequency = { every: '2', period: 'w' };
84
84
  expect(selectProductDefaultFrequency({ defaultFrequencies: { 123: frequency } })).toEqual(
85
85
  stringifyFrequency(frequency)
@@ -58,7 +58,8 @@ export const setPreviewStandardOffer = (isPreview, productId, offer) =>
58
58
  }
59
59
  }
60
60
  },
61
- offer
61
+ offer,
62
+ productId
62
63
  )
63
64
  );
64
65
  };
@@ -94,7 +95,8 @@ export const setPreviewUpsellOffer = (isPreview, productId, offer) =>
94
95
  autoship_by_default: { [productId]: false },
95
96
  modifiers: {}
96
97
  },
97
- offer
98
+ offer,
99
+ productId
98
100
  )
99
101
  );
100
102
  await dispatch(
@@ -194,7 +196,8 @@ export const setPreviewPrepaid = (isPreview, productId, offer) =>
194
196
  }
195
197
  }
196
198
  },
197
- offer
199
+ offer,
200
+ productId
198
201
  )
199
202
  );
200
203
  await dispatch({
@@ -1,7 +1,8 @@
1
1
  import { resolveAuth } from '@ordergroove/auth';
2
2
  import * as constants from './constants';
3
3
  import { api } from './api';
4
- import { safeOgFrequency, safeProductId } from './utils';
4
+ import { safeOgFrequency } from './utils';
5
+ import { makeFrequencyForPrepaidShipmentsSelector, makeProductFrequenciesSelector } from './selectors';
5
6
 
6
7
  export const optinProduct = (product, frequency, offer) => ({
7
8
  type: constants.OPTIN_PRODUCT,
@@ -23,10 +24,14 @@ export const productChangeFrequency = (product, frequency, offer) => ({
23
24
  payload: { product, frequency, offer }
24
25
  });
25
26
 
26
- export const productChangePrepaidShipments = (product, prepaidShipments, offer) => ({
27
- type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
28
- payload: { product, prepaidShipments, offer }
29
- });
27
+ export const productChangePrepaidShipments = (product, prepaidShipments, offer) => (dispatch, getState) => {
28
+ // this is a thunk so that we access the state for the selector
29
+ const frequency = makeFrequencyForPrepaidShipmentsSelector(product, prepaidShipments)(getState());
30
+ dispatch({
31
+ type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
32
+ payload: { product, prepaidShipments, offer, frequency }
33
+ });
34
+ };
30
35
 
31
36
  export const cartProductKeyHasChanged = (oldCartProductKey, newCartProductKey) => ({
32
37
  type: constants.CART_PRODUCT_KEY_HAS_CHANGED,
@@ -183,10 +188,13 @@ export const requestSessionId = () => (dispatch, getState) => {
183
188
  return sessionId;
184
189
  };
185
190
 
186
- export const receiveOffer = (response, offer) => ({
187
- type: constants.RECEIVE_OFFER,
188
- payload: { ...response, offer }
189
- });
191
+ export const receiveOffer = (response, offer, productId) => (dispatch, getState) => {
192
+ const frequencyConfig = makeProductFrequenciesSelector(productId)(getState());
193
+ dispatch({
194
+ type: constants.RECEIVE_OFFER,
195
+ payload: { ...response, offer, frequencyConfig }
196
+ });
197
+ };
190
198
 
191
199
  export const fetchResponseError = err => ({
192
200
  type: constants.FETCH_RESPONSE_ERROR,
@@ -226,18 +234,19 @@ export const receiveConvertOneTime = (response, product) => ({
226
234
 
227
235
  export const createIu = (product, order, quantity, subscribed = false, initialFrequency = null) =>
228
236
  function createIuThunk(dispatch, getState) {
237
+ const state = getState();
229
238
  const {
230
239
  auth,
231
- config,
232
240
  environment: { legoUrl },
233
241
  previewUpsellOffer,
234
242
  offerId: offer,
235
243
  sessionId
236
- } = getState();
244
+ } = state;
237
245
 
238
246
  if (!auth) return dispatch(unauthorized('No auth set.'));
239
247
 
240
- const frequency = safeOgFrequency(initialFrequency, config);
248
+ const { frequencies, frequenciesEveryPeriod } = makeProductFrequenciesSelector(product.id)(state);
249
+ const frequency = safeOgFrequency(initialFrequency, frequencies, frequenciesEveryPeriod);
241
250
 
242
251
  const requestAction = requestCreateOneTime(product, order, quantity, offer);
243
252
 
@@ -301,7 +310,7 @@ export const setProductToSubscribe = (product, productToSubscribe) => ({
301
310
  });
302
311
 
303
312
  /**
304
- * @param {import('../types').MerchantSettings} settings
313
+ * @param {import('../core/types/api').MerchantSettings} settings
305
314
  */
306
315
  export const receiveMerchantSettings = settings => ({
307
316
  type: constants.RECEIVE_MERCHANT_SETTINGS,
package/src/core/base.js CHANGED
@@ -1,30 +1,7 @@
1
1
  import { LitElement } from 'lit-element';
2
- import { kebabCase } from './selectors';
3
2
 
4
3
  export const withTemplate = Base =>
5
4
  class extends Base {
6
- /**
7
- *
8
- * @param {*} key html attribute key name
9
- * @param {*} optional configKey key name in configuration object
10
- */
11
- getOption(key, configKey = key) {
12
- const attrName = kebabCase(key);
13
- if (this.hasAttribute(attrName)) {
14
- const attr = this.getAttribute(attrName);
15
- if (attr.toString().toLowerCase() === 'true') return true;
16
- if (attr.toString().toLowerCase() === 'false') return false;
17
- return attr;
18
- }
19
-
20
- if (this.template && this.template.config && typeof this.template.config[configKey] !== 'undefined')
21
- return this.template.config[configKey];
22
-
23
- if (this.config && typeof this.config[configKey] !== 'undefined') return this.config[configKey];
24
-
25
- return undefined;
26
- }
27
-
28
5
  applyTemplate(template) {
29
6
  this.template = template;
30
7
  // update innerHTML if change (performance)
@@ -24,7 +24,7 @@ export function offerRequestMiddleware(store) {
24
24
  action.payload.searchParams
25
25
  )
26
26
  .then(
27
- response => store.dispatch(receiveOffer(response, action.payload.offer)),
27
+ response => store.dispatch(receiveOffer(response, action.payload.offer, productId)),
28
28
  err => store.dispatch(fetchResponseError(err))
29
29
  )
30
30
  .finally(() => store.dispatch(fetchDone(action)));
@@ -6,7 +6,21 @@ import { getObjectStructuredProductPlans } from './adapters';
6
6
  import { safeProductId, getMatchingProductIfExists } from './utils';
7
7
  import { experimentsReducer } from './experiments';
8
8
 
9
- export const optedin = (state = [], action) => {
9
+ import {
10
+ AutoshipByDefaultState,
11
+ AutoshipEligibleState,
12
+ ConfigState,
13
+ IncentiveObject,
14
+ IncentivesState,
15
+ NextUpcomingOrderState,
16
+ OptedInState,
17
+ OptedOutState,
18
+ PrepaidShipmentsSelectedState,
19
+ ReceiveOfferPayload
20
+ } from './types/reducer';
21
+ import { EmptyObject } from './types/utility';
22
+
23
+ export const optedin = (state: OptedInState = [], action): OptedInState => {
10
24
  switch (action.type) {
11
25
  case constants.LOCAL_STORAGE_CLEAR:
12
26
  return [];
@@ -49,7 +63,7 @@ export const optedin = (state = [], action) => {
49
63
  }
50
64
  };
51
65
 
52
- export const optedout = (state = [], action) => {
66
+ export const optedout = (state: OptedOutState = [], action): OptedOutState => {
53
67
  switch (action.type) {
54
68
  case constants.LOCAL_STORAGE_CLEAR:
55
69
  return [];
@@ -77,7 +91,7 @@ export const optedout = (state = [], action) => {
77
91
  }
78
92
  };
79
93
 
80
- export const nextUpcomingOrder = (state = {}, { type, payload }) => {
94
+ export const nextUpcomingOrder = (state: NextUpcomingOrderState = {}, { type, payload }): NextUpcomingOrderState => {
81
95
  switch (type) {
82
96
  case constants.RECEIVE_ORDERS:
83
97
  return payload && payload.count > 0
@@ -107,7 +121,7 @@ export const nextUpcomingOrder = (state = {}, { type, payload }) => {
107
121
  }
108
122
  };
109
123
 
110
- export const autoshipEligible = (state = {}, action) => {
124
+ export const autoshipEligible = (state: AutoshipEligibleState = {}, action): AutoshipEligibleState => {
111
125
  switch (action.type) {
112
126
  case constants.RECEIVE_OFFER:
113
127
  return {
@@ -153,7 +167,10 @@ const mapIncentive = (incentive, incentiveDisplay) => {
153
167
  }));
154
168
  };
155
169
 
156
- export const incentives = (state = {}, action) => {
170
+ export const incentives = (
171
+ state: IncentivesState = {},
172
+ action: { type: string; payload: ReceiveOfferPayload }
173
+ ): IncentivesState => {
157
174
  switch (action.type) {
158
175
  case constants.RECEIVE_OFFER:
159
176
  return {
@@ -164,7 +181,7 @@ export const incentives = (state = {}, action) => {
164
181
  [uniqueProductId]: Object.entries(action.payload.incentives)
165
182
  .filter(([productId]) => productId === uniqueProductId)
166
183
  .reduce(
167
- (incentiveObj, [, { initial, ongoing }]) => ({
184
+ (incentiveObj: IncentiveObject | EmptyObject, [, { initial, ongoing }]) => ({
168
185
  ...incentiveObj,
169
186
  initial: [
170
187
  ...(incentiveObj.initial || []),
@@ -392,11 +409,11 @@ export const locale = (
392
409
  };
393
410
 
394
411
  export const config = (
395
- state = {
412
+ state: ConfigState = {
396
413
  offerType: 'radio'
397
414
  },
398
415
  action
399
- ) => {
416
+ ): ConfigState => {
400
417
  switch (action.type) {
401
418
  case constants.SET_CONFIG:
402
419
  return {
@@ -447,7 +464,7 @@ export const previewPrepaidOffer = (state = false, action) => {
447
464
  }
448
465
  };
449
466
 
450
- export const autoshipByDefault = (state = [], action) => {
467
+ export const autoshipByDefault = (state: AutoshipByDefaultState = {}, action): AutoshipByDefaultState => {
451
468
  switch (action.type) {
452
469
  case constants.RECEIVE_OFFER:
453
470
  return {
@@ -492,7 +509,10 @@ export const productPlans = (state = {}, action) => {
492
509
  }
493
510
  };
494
511
 
495
- export const prepaidShipmentsSelected = (state = {}, action) => {
512
+ export const prepaidShipmentsSelected = (
513
+ state: PrepaidShipmentsSelectedState = {},
514
+ action
515
+ ): PrepaidShipmentsSelectedState => {
496
516
  switch (action.type) {
497
517
  // Given that, in the cart, products will have a composed id (<productId>:<cartId>) and that every time
498
518
  // a product changes in the cart we need to sync these changes back with the eComm platform, this operation
@@ -0,0 +1,215 @@
1
+ import { createSelector } from 'reselect';
2
+ import memoize from 'lodash.memoize';
3
+ import { stringifyFrequency } from './api';
4
+ import platform from '../platform';
5
+ import { mapFrequencyToSellingPlan, safeProductId } from './utils';
6
+ import { OfferElement, State } from './types/reducer';
7
+
8
+ memoize.Cache = Map;
9
+
10
+ type BaseProduct = {
11
+ id: string;
12
+ components?: string[];
13
+ };
14
+
15
+ function arraysEqual<T>(a: T[], b: T[]) {
16
+ if (a === b) return true;
17
+ if (a === null || b === null) return false;
18
+ if (a.length !== b.length) return false;
19
+
20
+ // If you don't care about the order of the elements inside
21
+ // the array, you should sort both arrays here.
22
+ // Please note that calling sort on an array will modify that array.
23
+ // you might want to clone your array first.
24
+
25
+ for (let i = 0; i < a.length; ++i) {
26
+ if (a[i] !== b[i]) return false;
27
+ }
28
+ return true;
29
+ }
30
+
31
+ function resolveFrequency(sellingPlans: string[], frequenciesEveryPeriod: string[], frequency) {
32
+ const ogFrequency = stringifyFrequency(frequency);
33
+ if (!platform.shopify_selling_plans) return ogFrequency;
34
+ return mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, ogFrequency);
35
+ }
36
+
37
+ export const isSameProduct = <T extends BaseProduct, S extends BaseProduct>(a: T, b: S) => {
38
+ if ((a as BaseProduct) === b) return true;
39
+ if (typeof a === 'object' && typeof b === 'object' && a && b) {
40
+ if (a.id === b.id) {
41
+ if (!(Array.isArray(a.components) && Array.isArray(b.components))) {
42
+ return true;
43
+ }
44
+ if (arraysEqual((a.components || []).sort(), (b.components || []).sort())) {
45
+ return true;
46
+ }
47
+ }
48
+ }
49
+ return false;
50
+ };
51
+
52
+ /**
53
+ * Returns a list of opted in products id from the state
54
+ * @param {object} state
55
+ */
56
+ export const optedinSelector = (state: State) => state.optedin || [];
57
+
58
+ const optedoutSelector = (state: State) => state.optedout || [];
59
+
60
+ export const autoshipSelector = (state: State) => state.autoshipByDefault || {};
61
+
62
+ const defaultFrequenciesSelector = (state: State) => state.defaultFrequencies || {};
63
+
64
+ const prepaidSellingPlansSelector = (state: State) => state?.config?.prepaidSellingPlans || [];
65
+ const prepaidShipmentsSelectedSelector = (state: State) => state?.prepaidShipmentsSelected || {};
66
+
67
+ /**
68
+ * Creates a function with state arguments that return the true when
69
+ * productId is in the optedin array or not in optedout or autoship by default
70
+ */
71
+ export const makeOptedinSelector = memoize(
72
+ (product: BaseProduct) =>
73
+ createSelector(optedinSelector, optedoutSelector, autoshipSelector, (optedin, optedout, autoshipByDefault) => {
74
+ const entry = optedin.find(b => isSameProduct(product, b));
75
+ if (entry) {
76
+ return entry;
77
+ }
78
+ if (optedout.find(b => isSameProduct(product, b))) {
79
+ return false;
80
+ }
81
+ if (product && autoshipByDefault[product.id]) {
82
+ return { id: product.id };
83
+ }
84
+ return false;
85
+ }),
86
+ product => JSON.stringify(product)
87
+ );
88
+ /**
89
+ * Creates a function with state arguments that return the true when
90
+ * productId is in the optedin array
91
+ */
92
+ export const makeSubscribedSelector = memoize(
93
+ (product: BaseProduct) =>
94
+ createSelector(optedinSelector, optedin => {
95
+ const entry = optedin.find(b => isSameProduct(product, b));
96
+ if (entry) {
97
+ return entry;
98
+ }
99
+ return false;
100
+ }),
101
+ product => JSON.stringify(product)
102
+ );
103
+
104
+ export const makePrepaidSubscribedSelector = memoize(
105
+ (product: BaseProduct) =>
106
+ createSelector(optedinSelector, optedin => optedin.some(b => isSameProduct(product, b) && b.prepaidShipments)),
107
+ product => JSON.stringify(product)
108
+ );
109
+
110
+ export const makePrepaidShipmentsSelectedSelector = memoize(
111
+ (product: BaseProduct) =>
112
+ createSelector(
113
+ prepaidShipmentsSelectedSelector,
114
+ prepaidShipmentsSelected => prepaidShipmentsSelected[product.id] || null
115
+ ),
116
+ product => JSON.stringify(product)
117
+ );
118
+
119
+ /**
120
+ * Creates a function with state arguments that return the true when
121
+ * productId is in the optedout array
122
+ */
123
+ export const makeOptedoutSelector = memoize((product: BaseProduct) =>
124
+ createSelector(optedoutSelector, optedout => optedout.find(b => isSameProduct(product, b)))
125
+ );
126
+
127
+ export const makeProductFrequencyOptedInSelector = memoize(
128
+ (product: BaseProduct) =>
129
+ createSelector(
130
+ makeOptedinSelector(product),
131
+ productOptin => (productOptin && 'frequency' in productOptin && productOptin.frequency) || null
132
+ ),
133
+ product => JSON.stringify(product)
134
+ );
135
+
136
+ export const makeProductPrepaidShipmentsOptedInSelector = memoize(
137
+ (product: BaseProduct) =>
138
+ createSelector(
139
+ makeOptedinSelector(product),
140
+ productOptin => (productOptin && 'prepaidShipments' in productOptin && productOptin.prepaidShipments) || null
141
+ ),
142
+ product => JSON.stringify(product)
143
+ );
144
+
145
+ export const makeProductPrepaidShipmentOptionsSelector = memoize((productId: string) =>
146
+ createSelector(prepaidSellingPlansSelector, prepaidSellingPlans => {
147
+ const shipmentsList =
148
+ prepaidSellingPlans[safeProductId(productId)]?.map(({ numberShipments }) => numberShipments) || [];
149
+ return shipmentsList.sort((a, b) => a - b);
150
+ })
151
+ );
152
+
153
+ /**
154
+ * If the product has a product-specific default frequency, return that frequency
155
+ */
156
+ export const makeProductSpecificDefaultFrequencySelector = memoize((productId: string) =>
157
+ createSelector(
158
+ defaultFrequenciesSelector,
159
+ makeProductFrequenciesSelector(productId),
160
+ (defaultFrequencies, { frequencies: sellingPlans = [], frequenciesEveryPeriod = [] }) =>
161
+ (defaultFrequencies[safeProductId(productId)] &&
162
+ resolveFrequency(sellingPlans, frequenciesEveryPeriod, defaultFrequencies[safeProductId(productId)])) ||
163
+ null
164
+ )
165
+ );
166
+
167
+ export const makeProductFrequencyOptionsSelector = memoize((productId: string) =>
168
+ createSelector(makeProductFrequenciesSelector(productId), productFrequencies => productFrequencies.frequencies)
169
+ );
170
+
171
+ export const makeProductDefaultFrequencySelector = memoize((productId: string) =>
172
+ createSelector(makeProductFrequenciesSelector(productId), productFrequencies => productFrequencies.defaultFrequency)
173
+ );
174
+
175
+ export const makeProductFrequenciesSelector = memoize((productId: string) =>
176
+ createSelector(
177
+ (state: State) => state?.config?.productFrequencies || {},
178
+ productFrequencies => {
179
+ return productFrequencies[safeProductId(productId)] || {};
180
+ }
181
+ )
182
+ );
183
+
184
+ // this selector is only called when an action is dispatched, so we don't need to memoize
185
+ // other selectors are called whenever the Redux state is updated
186
+ export const makeFrequencyForPrepaidShipmentsSelector = (product: BaseProduct, prepaidShipments: number) =>
187
+ createSelector(
188
+ prepaidSellingPlansSelector,
189
+ makeProductFrequenciesSelector(product.id),
190
+ (prepaidSellingPlans, { frequencies }) => {
191
+ if (prepaidShipments) {
192
+ const productId = safeProductId(product.id);
193
+ const plan = prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
194
+ return plan ? plan.sellingPlan : null;
195
+ }
196
+ return frequencies[0];
197
+ }
198
+ );
199
+
200
+ /**
201
+ * Convert a string from camel case to kebab case.
202
+ */
203
+ export const kebabCase = (string: string) => {
204
+ return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
205
+ };
206
+
207
+ export const getFallbackValue = (element: HTMLElement & { offer: OfferElement }, key: string, defaultValue?) =>
208
+ (element && element.hasAttribute && element.hasAttribute(kebabCase(key)) && element[key]) ||
209
+ (element.offer && typeof (element.offer[key] !== 'undefined') && element.offer[key]) ||
210
+ defaultValue;
211
+
212
+ /**
213
+ * Returns a list of opted in products id from the state
214
+ */
215
+ export const templatesSelector = (state: State) => ({ templates: state.templates || [] });
@@ -0,0 +1,71 @@
1
+ export type Order = {
2
+ merchant: string;
3
+ customer: string;
4
+ payment: string;
5
+ shipping_address: string;
6
+ public_id: string;
7
+ sub_total: string;
8
+ tax_total: string;
9
+ shipping_total: string;
10
+ discount_total: string;
11
+ total: string;
12
+ created: string;
13
+ updated: string;
14
+ place: string;
15
+ cancelled: string | null;
16
+ tries: number;
17
+ generic_error_count: number;
18
+ status: number;
19
+ type: number;
20
+ order_merchant_id: string | null;
21
+ rejected_message: string | null;
22
+ extra_data: unknown;
23
+ locked: boolean;
24
+ oos_free_shipping: boolean;
25
+ currency_code: string;
26
+ };
27
+
28
+ export type Incentive = {
29
+ object: string;
30
+ field: string;
31
+ type: string;
32
+ value: number;
33
+ };
34
+
35
+ type ExperimentVariant = {
36
+ public_id: string;
37
+ parameters: any;
38
+ weight: number;
39
+ };
40
+
41
+ type ExperimentConfig = {
42
+ public_id: string;
43
+ variants: ExperimentVariant[];
44
+ };
45
+
46
+ export interface MerchantSettings {
47
+ currency_code: string;
48
+ multicurrency_enabled: boolean;
49
+ experiments?: ExperimentConfig;
50
+ }
51
+
52
+ export type OfferResponse = {
53
+ result: string;
54
+ module_view: {
55
+ regular: string;
56
+ };
57
+ modifiers: Record<string, { coupon_code: string }>;
58
+ autoship: Record<string, boolean>;
59
+ in_stock: Record<string, boolean>;
60
+ autoship_by_default: Record<string, boolean>;
61
+ default_frequencies: Record<string, { every: number; every_period: number }>;
62
+ eligibility_groups: Record<string, string[]>;
63
+ incentives: Record<
64
+ string,
65
+ {
66
+ ongoing: string[];
67
+ initial: string[];
68
+ }
69
+ >;
70
+ incentives_display: Record<string, Incentive>;
71
+ };
@@ -0,0 +1,94 @@
1
+ import { Order, Incentive as ApiIncentive, MerchantSettings, OfferResponse } from './api';
2
+ import { type Offer } from '../../components/Offer';
3
+ import { ShopifyCart, ShopifyProductEntity } from '../../shopify/types/shopify';
4
+ import reducer from '../reducer';
5
+
6
+ export type State = ReturnType<typeof reducer>;
7
+
8
+ // state types
9
+
10
+ export type NextUpcomingOrderState = Partial<
11
+ Order & {
12
+ place: Date;
13
+ products: string[];
14
+ }
15
+ >;
16
+
17
+ export type IncentivesState = Record<string, IncentiveObject>;
18
+
19
+ type Incentive = ApiIncentive & {
20
+ id: string;
21
+ };
22
+
23
+ export type IncentiveObject = {
24
+ initial: Incentive[];
25
+ ongoing: Incentive[];
26
+ };
27
+
28
+ export type ConfigState = Partial<{
29
+ /**
30
+ * @deprecated use productFrequencies instead
31
+ */
32
+ frequencies: string[];
33
+ offerType: string;
34
+ /**
35
+ * @deprecated use productFrequencies instead
36
+ */
37
+ frequenciesEveryPeriod: string[];
38
+ merchantSettings: MerchantSettings;
39
+ /**
40
+ * @deprecated use productFrequencies instead
41
+ */
42
+ defaultFrequency: string;
43
+ /**
44
+ * @deprecated use productFrequencies instead
45
+ */
46
+ frequenciesText: string[];
47
+ prepaidSellingPlans: Record<string, { numberShipments: number; sellingPlan: string }[]>;
48
+ storeCurrency: string;
49
+ productFrequencies: Record<string, ProductFrequencyConfig>;
50
+ }>;
51
+
52
+ type ProductFrequencyConfig = {
53
+ frequencies?: string[];
54
+ frequenciesEveryPeriod?: string[];
55
+ frequenciesText?: string[];
56
+ defaultFrequency?: string;
57
+ };
58
+
59
+ export type AutoshipEligibleState = Record<string, boolean>;
60
+
61
+ export type PrepaidShipmentsSelectedState = Record<string, number>;
62
+
63
+ export type OptInItem = { id: string; frequency: string; prepaidShipments?: number; components?: string[] };
64
+
65
+ export type OptedInState = OptInItem[];
66
+
67
+ export type OptedOutState = { id: string }[];
68
+
69
+ export type AutoshipByDefaultState = Record<string, boolean>;
70
+
71
+ // payload types
72
+
73
+ export type ReceiveOfferPayload = OfferResponse & {
74
+ offer: OfferElement;
75
+ frequencyConfig: {
76
+ frequencies?: string[];
77
+ frequenciesEveryPeriod?: string[];
78
+ frequenciesText?: string[];
79
+ };
80
+ };
81
+
82
+ export type OfferElement = InstanceType<typeof Offer> & { config: ConfigState };
83
+
84
+ export type ReceiveProductPlansPayload = Record<string, string[]>;
85
+
86
+ export type SetupProductPayload = {
87
+ product: ShopifyProductEntity;
88
+ offer: OfferElement;
89
+ currency: string;
90
+ };
91
+
92
+ export type SetupCartPayload = ShopifyCart;
93
+
94
+ export type ReceiveMerchantSettingsPayload = MerchantSettings;
@@ -0,0 +1 @@
1
+ export type EmptyObject = Record<string, never>;