@ordergroove/offers 2.44.0 → 2.44.1-alpha-PR-1166-3.30
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/dist/bundle-report.html +52 -49
- package/dist/offers.js +38 -38
- package/dist/offers.js.map +4 -4
- package/package.json +2 -2
- package/src/components/FrequencyStatus.js +13 -10
- package/src/components/Offer.js +33 -14
- package/src/components/OptinButton.js +2 -2
- package/src/components/OptinSelect.js +5 -5
- package/src/components/OptinStatus.js +15 -9
- package/src/components/Price.js +3 -3
- package/src/components/SelectFrequency.js +11 -6
- package/src/components/TestWizard.js +45 -41
- package/src/components/UpsellModal.js +9 -3
- package/src/components/__tests__/Offer.spec.js +0 -19
- package/src/components/__tests__/OptinStatus.spec.js +17 -4
- package/src/core/__tests__/actions.spec.js +47 -1
- package/src/core/__tests__/base.spec.js +0 -77
- package/src/core/__tests__/offerRequest.spec.js +2 -1
- package/src/core/__tests__/selectors.spec.js +7 -7
- package/src/core/actions-preview.js +6 -3
- package/src/core/actions.js +22 -13
- package/src/core/base.js +0 -23
- package/src/core/offerRequest.js +1 -1
- package/src/core/{reducer.js → reducer.ts} +30 -10
- package/src/core/{selectors.js → selectors.ts} +73 -57
- package/src/core/types/api.ts +71 -0
- package/src/core/types/reducer.ts +95 -0
- package/src/core/types/utility.ts +1 -0
- package/src/core/utils.ts +32 -15
- package/src/make-api.js +1 -1
- package/src/shopify/__tests__/reducers/config.spec.js +497 -0
- package/src/shopify/__tests__/shopifyReducer.spec.js +65 -610
- package/src/shopify/__tests__/utils.spec.js +24 -1
- package/src/shopify/reducers/config.ts +223 -0
- package/src/shopify/shopifyMiddleware.ts +2 -9
- package/src/shopify/{shopifyReducer.js → shopifyReducer.ts} +45 -177
- package/src/shopify/utils.ts +25 -0
- package/src/types.ts +0 -16
|
@@ -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({
|
package/src/core/actions.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
188
|
-
|
|
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
|
-
} =
|
|
244
|
+
} = state;
|
|
237
245
|
|
|
238
246
|
if (!auth) return dispatch(unauthorized('No auth set.'));
|
|
239
247
|
|
|
240
|
-
const
|
|
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)
|
package/src/core/offerRequest.js
CHANGED
|
@@ -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
|
-
|
|
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 = (
|
|
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 =
|
|
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 = (
|
|
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
|
|
@@ -2,11 +2,17 @@ import { createSelector } from 'reselect';
|
|
|
2
2
|
import memoize from 'lodash.memoize';
|
|
3
3
|
import { stringifyFrequency } from './api';
|
|
4
4
|
import platform from '../platform';
|
|
5
|
-
import { mapFrequencyToSellingPlan, safeProductId } from '
|
|
5
|
+
import { mapFrequencyToSellingPlan, safeProductId } from './utils';
|
|
6
|
+
import { OfferElement, State } from './types/reducer';
|
|
6
7
|
|
|
7
8
|
memoize.Cache = Map;
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
type BaseProduct = {
|
|
11
|
+
id: string;
|
|
12
|
+
components?: string[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function arraysEqual<T>(a: T[], b: T[]) {
|
|
10
16
|
if (a === b) return true;
|
|
11
17
|
if (a === null || b === null) return false;
|
|
12
18
|
if (a.length !== b.length) return false;
|
|
@@ -22,14 +28,14 @@ function arraysEqual(a, b) {
|
|
|
22
28
|
return true;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
function resolveFrequency(sellingPlans, frequenciesEveryPeriod, frequency) {
|
|
31
|
+
function resolveFrequency(sellingPlans: string[], frequenciesEveryPeriod: string[], frequency) {
|
|
26
32
|
const ogFrequency = stringifyFrequency(frequency);
|
|
27
33
|
if (!platform.shopify_selling_plans) return ogFrequency;
|
|
28
34
|
return mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, ogFrequency);
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
export const isSameProduct = (a, b) => {
|
|
32
|
-
if (a === b) return true;
|
|
37
|
+
export const isSameProduct = <T extends BaseProduct, S extends BaseProduct>(a: T, b: S) => {
|
|
38
|
+
if ((a as BaseProduct) === b) return true;
|
|
33
39
|
if (typeof a === 'object' && typeof b === 'object' && a && b) {
|
|
34
40
|
if (a.id === b.id) {
|
|
35
41
|
if (!(Array.isArray(a.components) && Array.isArray(b.components))) {
|
|
@@ -47,28 +53,23 @@ export const isSameProduct = (a, b) => {
|
|
|
47
53
|
* Returns a list of opted in products id from the state
|
|
48
54
|
* @param {object} state
|
|
49
55
|
*/
|
|
50
|
-
export const optedinSelector = state => state.optedin || [];
|
|
56
|
+
export const optedinSelector = (state: State) => state.optedin || [];
|
|
51
57
|
|
|
52
|
-
|
|
58
|
+
const optedoutSelector = (state: State) => state.optedout || [];
|
|
53
59
|
|
|
54
|
-
export const autoshipSelector = state => state.autoshipByDefault || {};
|
|
60
|
+
export const autoshipSelector = (state: State) => state.autoshipByDefault || {};
|
|
55
61
|
|
|
56
|
-
|
|
62
|
+
const defaultFrequenciesSelector = (state: State) => state.defaultFrequencies || {};
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
export const frequenciesEveryPeriodSelector = state => state?.config?.frequenciesEveryPeriod || [];
|
|
61
|
-
|
|
62
|
-
export const prepaidSellingPlansSelector = state => state?.config?.prepaidSellingPlans || [];
|
|
63
|
-
export const prepaidShipmentsSelectedSelector = state => state?.prepaidShipmentsSelected || {};
|
|
64
|
+
const prepaidSellingPlansSelector = (state: State) => state?.config?.prepaidSellingPlans || [];
|
|
65
|
+
const prepaidShipmentsSelectedSelector = (state: State) => state?.prepaidShipmentsSelected || {};
|
|
64
66
|
|
|
65
67
|
/**
|
|
66
68
|
* Creates a function with state arguments that return the true when
|
|
67
69
|
* productId is in the optedin array or not in optedout or autoship by default
|
|
68
|
-
* @param {String} productId
|
|
69
70
|
*/
|
|
70
71
|
export const makeOptedinSelector = memoize(
|
|
71
|
-
product =>
|
|
72
|
+
(product: BaseProduct) =>
|
|
72
73
|
createSelector(optedinSelector, optedoutSelector, autoshipSelector, (optedin, optedout, autoshipByDefault) => {
|
|
73
74
|
const entry = optedin.find(b => isSameProduct(product, b));
|
|
74
75
|
if (entry) {
|
|
@@ -87,10 +88,9 @@ export const makeOptedinSelector = memoize(
|
|
|
87
88
|
/**
|
|
88
89
|
* Creates a function with state arguments that return the true when
|
|
89
90
|
* productId is in the optedin array
|
|
90
|
-
* @param {String} productId
|
|
91
91
|
*/
|
|
92
92
|
export const makeSubscribedSelector = memoize(
|
|
93
|
-
product =>
|
|
93
|
+
(product: BaseProduct) =>
|
|
94
94
|
createSelector(optedinSelector, optedin => {
|
|
95
95
|
const entry = optedin.find(b => isSameProduct(product, b));
|
|
96
96
|
if (entry) {
|
|
@@ -102,13 +102,13 @@ export const makeSubscribedSelector = memoize(
|
|
|
102
102
|
);
|
|
103
103
|
|
|
104
104
|
export const makePrepaidSubscribedSelector = memoize(
|
|
105
|
-
product =>
|
|
105
|
+
(product: BaseProduct) =>
|
|
106
106
|
createSelector(optedinSelector, optedin => optedin.some(b => isSameProduct(product, b) && b.prepaidShipments)),
|
|
107
107
|
product => JSON.stringify(product)
|
|
108
108
|
);
|
|
109
109
|
|
|
110
110
|
export const makePrepaidShipmentsSelectedSelector = memoize(
|
|
111
|
-
product =>
|
|
111
|
+
(product: BaseProduct) =>
|
|
112
112
|
createSelector(
|
|
113
113
|
prepaidShipmentsSelectedSelector,
|
|
114
114
|
prepaidShipmentsSelected => prepaidShipmentsSelected[product.id] || null
|
|
@@ -119,23 +119,28 @@ export const makePrepaidShipmentsSelectedSelector = memoize(
|
|
|
119
119
|
/**
|
|
120
120
|
* Creates a function with state arguments that return the true when
|
|
121
121
|
* productId is in the optedout array
|
|
122
|
-
* @param {String} productId
|
|
123
122
|
*/
|
|
124
|
-
export const makeOptedoutSelector = memoize(
|
|
125
|
-
createSelector(optedoutSelector, optedout => optedout.find(b => isSameProduct(
|
|
123
|
+
export const makeOptedoutSelector = memoize((product: BaseProduct) =>
|
|
124
|
+
createSelector(optedoutSelector, optedout => optedout.find(b => isSameProduct(product, b)))
|
|
126
125
|
);
|
|
127
126
|
|
|
128
|
-
export const frequencySelector = state => state.frequency;
|
|
127
|
+
export const frequencySelector = (state: State) => state.frequency;
|
|
129
128
|
|
|
130
|
-
export const
|
|
131
|
-
createSelector(
|
|
129
|
+
export const makeProductFrequencyOptedInSelector = memoize((product: BaseProduct) =>
|
|
130
|
+
createSelector(
|
|
131
|
+
makeOptedinSelector(product),
|
|
132
|
+
productOptin => (productOptin && 'frequency' in productOptin && productOptin.frequency) || null
|
|
133
|
+
)
|
|
132
134
|
);
|
|
133
135
|
|
|
134
|
-
export const makeProductPrepaidShipmentsOptedInSelector = memoize(product =>
|
|
135
|
-
createSelector(
|
|
136
|
+
export const makeProductPrepaidShipmentsOptedInSelector = memoize((product: BaseProduct) =>
|
|
137
|
+
createSelector(
|
|
138
|
+
makeOptedinSelector(product),
|
|
139
|
+
productOptin => (productOptin && 'prepaidShipments' in productOptin && productOptin.prepaidShipments) || null
|
|
140
|
+
)
|
|
136
141
|
);
|
|
137
142
|
|
|
138
|
-
export const makeProductPrepaidShipmentOptionsSelector = memoize(productId =>
|
|
143
|
+
export const makeProductPrepaidShipmentOptionsSelector = memoize((productId: string) =>
|
|
139
144
|
createSelector(prepaidSellingPlansSelector, prepaidSellingPlans => {
|
|
140
145
|
const shipmentsList =
|
|
141
146
|
prepaidSellingPlans[safeProductId(productId)]?.map(({ numberShipments }) => numberShipments) || [];
|
|
@@ -144,49 +149,60 @@ export const makeProductPrepaidShipmentOptionsSelector = memoize(productId =>
|
|
|
144
149
|
);
|
|
145
150
|
|
|
146
151
|
/**
|
|
147
|
-
*
|
|
148
|
-
* if default frequency exists for the product
|
|
149
|
-
* @param {String} productId
|
|
152
|
+
* If the product has a product-specific default frequency, return that frequency
|
|
150
153
|
*/
|
|
151
|
-
export const
|
|
154
|
+
export const makeProductSpecificDefaultFrequencySelector = memoize((productId: string) =>
|
|
152
155
|
createSelector(
|
|
153
156
|
defaultFrequenciesSelector,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
(defaultFrequencies, sellingPlans, frequenciesEveryPeriod) =>
|
|
157
|
+
makeProductFrequenciesSelector(productId),
|
|
158
|
+
(defaultFrequencies, { frequencies: sellingPlans = [], frequenciesEveryPeriod = [] }) =>
|
|
157
159
|
(defaultFrequencies[safeProductId(productId)] &&
|
|
158
160
|
resolveFrequency(sellingPlans, frequenciesEveryPeriod, defaultFrequencies[safeProductId(productId)])) ||
|
|
159
161
|
null
|
|
160
162
|
)
|
|
161
163
|
);
|
|
162
164
|
|
|
165
|
+
export const makeProductFrequencyOptionsSelector = (productId: string) =>
|
|
166
|
+
createSelector(makeProductFrequenciesSelector(productId), productFrequencies => productFrequencies.frequencies);
|
|
167
|
+
|
|
168
|
+
export const makeProductDefaultFrequencySelector = (productId: string) =>
|
|
169
|
+
createSelector(makeProductFrequenciesSelector(productId), productFrequencies => productFrequencies.defaultFrequency);
|
|
170
|
+
|
|
171
|
+
export const makeProductFrequenciesSelector = (productId: string) =>
|
|
172
|
+
createSelector(
|
|
173
|
+
(state: State) => state?.config?.productFrequencies || {},
|
|
174
|
+
productFrequencies => {
|
|
175
|
+
return productFrequencies[safeProductId(productId)] || {};
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
export const makeFrequencyForPrepaidShipmentsSelector = (product: BaseProduct, prepaidShipments: number) =>
|
|
180
|
+
createSelector(
|
|
181
|
+
prepaidSellingPlansSelector,
|
|
182
|
+
makeProductFrequenciesSelector(product.id),
|
|
183
|
+
(prepaidSellingPlans, { frequencies }) => {
|
|
184
|
+
if (prepaidShipments) {
|
|
185
|
+
const productId = safeProductId(product.id);
|
|
186
|
+
const plan = prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
|
|
187
|
+
return plan ? plan.sellingPlan : null;
|
|
188
|
+
}
|
|
189
|
+
return frequencies[0];
|
|
190
|
+
}
|
|
191
|
+
);
|
|
192
|
+
|
|
163
193
|
/**
|
|
164
194
|
* Convert a string from camel case to kebab case.
|
|
165
|
-
* @param {String} string
|
|
166
|
-
* @returns {String}
|
|
167
195
|
*/
|
|
168
|
-
export const kebabCase = string => {
|
|
196
|
+
export const kebabCase = (string: string) => {
|
|
169
197
|
return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
|
|
170
198
|
};
|
|
171
199
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
* @param {HTMLElement} element
|
|
177
|
-
* @param {String} key
|
|
178
|
-
* @returns {Object}
|
|
179
|
-
*/
|
|
180
|
-
export const configSelector = (state, element, key, defaultValue) => ({
|
|
181
|
-
[key]:
|
|
182
|
-
(state.config && state.config[key]) ||
|
|
183
|
-
(element && element.hasAttribute && element.hasAttribute(kebabCase(key)) && element[key]) ||
|
|
184
|
-
(element.offer && typeof (element.offer[key] !== 'undefined') && element.offer[key]) ||
|
|
185
|
-
defaultValue
|
|
186
|
-
});
|
|
200
|
+
export const getFallbackValue = (element: HTMLElement & { offer: OfferElement }, key: string, defaultValue?) =>
|
|
201
|
+
(element && element.hasAttribute && element.hasAttribute(kebabCase(key)) && element[key]) ||
|
|
202
|
+
(element.offer && typeof (element.offer[key] !== 'undefined') && element.offer[key]) ||
|
|
203
|
+
defaultValue;
|
|
187
204
|
|
|
188
205
|
/**
|
|
189
206
|
* Returns a list of opted in products id from the state
|
|
190
|
-
* @param {object} state
|
|
191
207
|
*/
|
|
192
|
-
export const templatesSelector = state => ({ templates: state.templates || [] });
|
|
208
|
+
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,95 @@
|
|
|
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
|
+
hasProductSpecificFrequencies: boolean;
|
|
48
|
+
prepaidSellingPlans: Record<string, { numberShipments: number; sellingPlan: string }[]>;
|
|
49
|
+
storeCurrency: string;
|
|
50
|
+
productFrequencies: Record<string, ProductFrequencyConfig>;
|
|
51
|
+
}>;
|
|
52
|
+
|
|
53
|
+
type ProductFrequencyConfig = {
|
|
54
|
+
frequencies?: string[];
|
|
55
|
+
frequenciesEveryPeriod?: string[];
|
|
56
|
+
frequenciesText?: string[];
|
|
57
|
+
defaultFrequency?: string;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type AutoshipEligibleState = Record<string, boolean>;
|
|
61
|
+
|
|
62
|
+
export type PrepaidShipmentsSelectedState = Record<string, number>;
|
|
63
|
+
|
|
64
|
+
export type OptInItem = { id: string; frequency: string; prepaidShipments?: number; components?: string[] };
|
|
65
|
+
|
|
66
|
+
export type OptedInState = OptInItem[];
|
|
67
|
+
|
|
68
|
+
export type OptedOutState = { id: string }[];
|
|
69
|
+
|
|
70
|
+
export type AutoshipByDefaultState = Record<string, boolean>;
|
|
71
|
+
|
|
72
|
+
// payload types
|
|
73
|
+
|
|
74
|
+
export type ReceiveOfferPayload = OfferResponse & {
|
|
75
|
+
offer: OfferElement;
|
|
76
|
+
frequencyConfig: {
|
|
77
|
+
frequencies?: string[];
|
|
78
|
+
frequenciesEveryPeriod?: string[];
|
|
79
|
+
frequenciesText?: string[];
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export type OfferElement = InstanceType<typeof Offer> & { config: ConfigState };
|
|
84
|
+
|
|
85
|
+
export type ReceiveProductPlansPayload = Record<string, string[]>;
|
|
86
|
+
|
|
87
|
+
export type SetupProductPayload = {
|
|
88
|
+
product: ShopifyProductEntity;
|
|
89
|
+
offer: OfferElement;
|
|
90
|
+
currency: string;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type SetupCartPayload = ShopifyCart;
|
|
94
|
+
|
|
95
|
+
export type ReceiveMerchantSettingsPayload = MerchantSettings;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type EmptyObject = Record<string, never>;
|