@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
|
@@ -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,223 @@
|
|
|
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
|
+
ReceiveProductPlansPayload,
|
|
8
|
+
SetupProductPayload
|
|
9
|
+
} from '../../core/types/reducer';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getPayAsYouGoSellingPlanGroup,
|
|
13
|
+
isProductSpecificFrequencySellingPlanGroup,
|
|
14
|
+
sellingPlansToEveryPeriod,
|
|
15
|
+
sellingPlansToFrequencies,
|
|
16
|
+
getPrepaidShipments
|
|
17
|
+
} from '../utils';
|
|
18
|
+
import { ShopifySellingPlanGroupsEntity, ShopifyVariantsEntity } from '../types/shopify';
|
|
19
|
+
|
|
20
|
+
const config = (
|
|
21
|
+
state: ConfigState = {
|
|
22
|
+
frequencies: [],
|
|
23
|
+
offerType: 'radio',
|
|
24
|
+
frequenciesEveryPeriod: [],
|
|
25
|
+
productFrequencies: {}
|
|
26
|
+
},
|
|
27
|
+
action
|
|
28
|
+
): ConfigState => {
|
|
29
|
+
if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
|
|
30
|
+
const frequencies = [
|
|
31
|
+
...new Set(
|
|
32
|
+
Object.values(action.payload as ReceiveProductPlansPayload)
|
|
33
|
+
.map(Object.keys)
|
|
34
|
+
.flat()
|
|
35
|
+
)
|
|
36
|
+
];
|
|
37
|
+
return {
|
|
38
|
+
...state,
|
|
39
|
+
frequencies
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (constants.SETUP_PRODUCT === action.type) {
|
|
44
|
+
const {
|
|
45
|
+
payload: { product, currency }
|
|
46
|
+
} = action as { payload: SetupProductPayload };
|
|
47
|
+
let configToAdd: ConfigState = {};
|
|
48
|
+
|
|
49
|
+
// get the frequency values for the old config structure
|
|
50
|
+
// leaving for now for backwards compatibility, but they should eventually be removed in favor of productFrequencies
|
|
51
|
+
let globalFrequencies = getFrequencies(product.selling_plan_groups, state);
|
|
52
|
+
if (globalFrequencies?.frequencies?.length) {
|
|
53
|
+
configToAdd = {
|
|
54
|
+
...globalFrequencies,
|
|
55
|
+
hasProductSpecificFrequencies:
|
|
56
|
+
state.hasProductSpecificFrequencies ||
|
|
57
|
+
isProductSpecificFrequencySellingPlanGroup(getPayAsYouGoSellingPlanGroup(product.selling_plan_groups))
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let productFrequencies = product.variants?.reduce(
|
|
62
|
+
(acc, variant) => reduceSellingPlansToFrequencies(acc, variant, product.selling_plan_groups, state),
|
|
63
|
+
{}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
configToAdd = {
|
|
67
|
+
...configToAdd,
|
|
68
|
+
|
|
69
|
+
productFrequencies: {
|
|
70
|
+
...state.productFrequencies,
|
|
71
|
+
...productFrequencies
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// prepaid selling plans
|
|
76
|
+
const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
|
|
77
|
+
if (prepaidSellingPlanGroups.length) {
|
|
78
|
+
configToAdd = {
|
|
79
|
+
...configToAdd,
|
|
80
|
+
prepaidSellingPlans: { ...state.prepaidSellingPlans, ...getPrepaidSellingPlans(prepaidSellingPlanGroups) }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
...state,
|
|
85
|
+
...configToAdd,
|
|
86
|
+
storeCurrency: currency
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (constants.RECEIVE_OFFER === action.type) {
|
|
91
|
+
const {
|
|
92
|
+
payload: { offer: offerEl }
|
|
93
|
+
} = action as { payload: ReceiveOfferPayload };
|
|
94
|
+
const { defaultFrequency, product } = offerEl || {};
|
|
95
|
+
const { frequencies: sellingPlans, frequenciesEveryPeriod, prepaidSellingPlans = {} } = state;
|
|
96
|
+
|
|
97
|
+
// productFrequencies does not have entries for the cart ID
|
|
98
|
+
// eligible frequencies apply to cart entries for the product
|
|
99
|
+
const productId = safeProductId(product?.id);
|
|
100
|
+
const currentProductFrequencies = state.productFrequencies[productId];
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
...state,
|
|
104
|
+
// populate this field for backwards compatibility
|
|
105
|
+
// this should eventually be removed in favor of productFrequencies
|
|
106
|
+
defaultFrequency: getUpdatedDefaultFrequency(
|
|
107
|
+
productId,
|
|
108
|
+
defaultFrequency,
|
|
109
|
+
prepaidSellingPlans,
|
|
110
|
+
sellingPlans,
|
|
111
|
+
frequenciesEveryPeriod
|
|
112
|
+
),
|
|
113
|
+
productFrequencies: {
|
|
114
|
+
...state.productFrequencies,
|
|
115
|
+
[productId]: {
|
|
116
|
+
...currentProductFrequencies,
|
|
117
|
+
defaultFrequency: getUpdatedDefaultFrequency(
|
|
118
|
+
productId,
|
|
119
|
+
defaultFrequency,
|
|
120
|
+
prepaidSellingPlans,
|
|
121
|
+
currentProductFrequencies?.frequencies,
|
|
122
|
+
currentProductFrequencies?.frequenciesEveryPeriod
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (constants.RECEIVE_MERCHANT_SETTINGS === action.type) {
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
merchantSettings: {
|
|
133
|
+
...(action.payload as ReceiveMerchantSettingsPayload)
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return state;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
function getFrequencies(
|
|
142
|
+
productSellingPlanGroups: ShopifySellingPlanGroupsEntity[],
|
|
143
|
+
state: { defaultFrequency?: string }
|
|
144
|
+
) {
|
|
145
|
+
const sellingPlanGroup = getPayAsYouGoSellingPlanGroup(productSellingPlanGroups);
|
|
146
|
+
const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
|
|
147
|
+
if (frequencies?.length) {
|
|
148
|
+
const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
|
|
149
|
+
const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
|
|
150
|
+
let defaultFrequency = state?.defaultFrequency;
|
|
151
|
+
|
|
152
|
+
if (defaultFrequency && isOgFrequency(defaultFrequency)) {
|
|
153
|
+
defaultFrequency =
|
|
154
|
+
mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, defaultFrequency) ||
|
|
155
|
+
getFirstSellingPlan(frequencies) ||
|
|
156
|
+
defaultFrequency;
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
frequencies,
|
|
160
|
+
frequenciesEveryPeriod,
|
|
161
|
+
frequenciesText,
|
|
162
|
+
...(defaultFrequency ? { defaultFrequency } : {})
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function reduceSellingPlansToFrequencies(
|
|
169
|
+
acc: ConfigState['productFrequencies'],
|
|
170
|
+
variant: ShopifyVariantsEntity,
|
|
171
|
+
sellingPlanGroups: ShopifySellingPlanGroupsEntity[],
|
|
172
|
+
state: ConfigState
|
|
173
|
+
) {
|
|
174
|
+
const sellingPlanGroupIdsForProduct = variant.selling_plan_allocations.map(all => all.selling_plan_group_id);
|
|
175
|
+
const applicableSellingPlanGroups = sellingPlanGroups.filter(group =>
|
|
176
|
+
sellingPlanGroupIdsForProduct.includes(group.id)
|
|
177
|
+
);
|
|
178
|
+
const frequencies = getFrequencies(applicableSellingPlanGroups, state.productFrequencies[variant.id]);
|
|
179
|
+
if (frequencies) {
|
|
180
|
+
acc[variant.id] = frequencies;
|
|
181
|
+
}
|
|
182
|
+
return acc;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function getUpdatedDefaultFrequency(
|
|
186
|
+
productId: string,
|
|
187
|
+
offerElementDefaultFrequency: string,
|
|
188
|
+
prepaidSellingPlans: ConfigState['prepaidSellingPlans'],
|
|
189
|
+
frequencies: string[] | undefined = [],
|
|
190
|
+
frequenciesEveryPeriod: string[] | undefined = []
|
|
191
|
+
) {
|
|
192
|
+
// We don't want to be setting the default frequency to a prepaid selling plan
|
|
193
|
+
if (prepaidSellingPlans[productId]?.some(({ sellingPlan }) => sellingPlan === offerElementDefaultFrequency)) {
|
|
194
|
+
return getFirstSellingPlan(frequencies) || offerElementDefaultFrequency;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!isOgFrequency(offerElementDefaultFrequency)) {
|
|
198
|
+
return offerElementDefaultFrequency;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
mapFrequencyToSellingPlan(frequencies, frequenciesEveryPeriod, offerElementDefaultFrequency) ||
|
|
203
|
+
getFirstSellingPlan(frequencies) ||
|
|
204
|
+
offerElementDefaultFrequency
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
|
|
209
|
+
return prepaidSellingPlanGroups.reduce((acc, cur) => {
|
|
210
|
+
const variant = cur.name.split('-')[1];
|
|
211
|
+
|
|
212
|
+
const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
|
|
213
|
+
return {
|
|
214
|
+
numberShipments: getPrepaidShipments(sellingPlanObject),
|
|
215
|
+
sellingPlan: String(sellingPlanObject.id)
|
|
216
|
+
};
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return { ...acc, [variant]: sellingPlanInfo };
|
|
220
|
+
}, {});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
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?:
|
|
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,28 @@ import {
|
|
|
31
31
|
safeProductId,
|
|
32
32
|
getMatchingProductIfExists
|
|
33
33
|
} from '../core/utils';
|
|
34
|
+
import type {
|
|
35
|
+
AutoshipEligibleState,
|
|
36
|
+
OfferElement,
|
|
37
|
+
OptedInState,
|
|
38
|
+
OptInItem,
|
|
39
|
+
ReceiveOfferPayload,
|
|
40
|
+
ReceiveProductPlansPayload
|
|
41
|
+
} from '../core/types/reducer';
|
|
34
42
|
|
|
35
43
|
import { getObjectStructuredProductPlans } from '../core/adapters';
|
|
36
44
|
|
|
37
45
|
import { sellingPlanAllocationsReducer, getSellingPlans } from './reducers/productPlans';
|
|
46
|
+
import config from './reducers/config';
|
|
38
47
|
import { experimentsReducer } from '../core/experiments';
|
|
39
48
|
import {
|
|
40
49
|
getPayAsYouGoSellingPlanGroup,
|
|
41
50
|
getPayAsYouGoSellingPlanGroups,
|
|
42
|
-
|
|
51
|
+
sellingPlansToEveryPeriod,
|
|
52
|
+
sellingPlansToFrequencies,
|
|
53
|
+
getPrepaidShipments
|
|
43
54
|
} from './utils';
|
|
55
|
+
import { EmptyObject } from '../core/types/utility';
|
|
44
56
|
|
|
45
57
|
const overrideLineKey = (state, productId, newValue) => {
|
|
46
58
|
const keys = Object.keys(state).filter(it => it.startsWith(productId.toString()));
|
|
@@ -50,7 +62,11 @@ const overrideLineKey = (state, productId, newValue) => {
|
|
|
50
62
|
return state;
|
|
51
63
|
};
|
|
52
64
|
|
|
53
|
-
export const getDefaultSellingPlan = (
|
|
65
|
+
export const getDefaultSellingPlan = (
|
|
66
|
+
sellingPlans: string[],
|
|
67
|
+
frequenciesEveryPeriod: string[],
|
|
68
|
+
defaultFrequency: string | undefined
|
|
69
|
+
) => {
|
|
54
70
|
if (!defaultFrequency) {
|
|
55
71
|
return null;
|
|
56
72
|
}
|
|
@@ -72,23 +88,27 @@ export const getDefaultSellingPlan = (sellingPlans, frequenciesEveryPeriod, defa
|
|
|
72
88
|
return defaultFrequency;
|
|
73
89
|
};
|
|
74
90
|
|
|
75
|
-
export const mapExistingOptinsFromOfferResponse = (
|
|
91
|
+
export const mapExistingOptinsFromOfferResponse = (
|
|
92
|
+
state: OptedInState,
|
|
93
|
+
offerEl: OfferElement | EmptyObject,
|
|
94
|
+
frequencyConfig: ReceiveOfferPayload['frequencyConfig']
|
|
95
|
+
) =>
|
|
76
96
|
state.map(it => {
|
|
77
97
|
if (isOgFrequency(it?.frequency)) {
|
|
78
98
|
return {
|
|
79
99
|
...it,
|
|
80
|
-
frequency: hasShopifySellingPlans(
|
|
100
|
+
frequency: hasShopifySellingPlans(frequencyConfig?.frequencies, frequencyConfig?.frequenciesEveryPeriod)
|
|
81
101
|
? mapFrequencyToSellingPlan(
|
|
82
|
-
|
|
83
|
-
|
|
102
|
+
frequencyConfig?.frequencies,
|
|
103
|
+
frequencyConfig?.frequenciesEveryPeriod,
|
|
84
104
|
it.frequency
|
|
85
105
|
) ||
|
|
86
106
|
mapFrequencyToSellingPlan(
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
frequencyConfig?.frequencies,
|
|
108
|
+
frequencyConfig?.frequenciesEveryPeriod,
|
|
89
109
|
offerEl?.defaultFrequency
|
|
90
110
|
) ||
|
|
91
|
-
getFirstSellingPlan(
|
|
111
|
+
getFirstSellingPlan(frequencyConfig?.frequencies)
|
|
92
112
|
: it.frequency
|
|
93
113
|
};
|
|
94
114
|
}
|
|
@@ -97,14 +117,16 @@ export const mapExistingOptinsFromOfferResponse = (state, offerEl) =>
|
|
|
97
117
|
});
|
|
98
118
|
|
|
99
119
|
export const reduceNewOptinsFromOfferResponse = (
|
|
100
|
-
{ autoship = {}, autoship_by_default = {}, default_frequencies = {}, in_stock = {} },
|
|
101
|
-
existingOptins,
|
|
102
|
-
offerEl
|
|
120
|
+
{ autoship = {}, autoship_by_default = {}, default_frequencies = {}, in_stock = {} }: ReceiveOfferPayload,
|
|
121
|
+
existingOptins: OptedInState,
|
|
122
|
+
offerEl: OfferElement | EmptyObject,
|
|
123
|
+
frequencyConfig: ReceiveOfferPayload['frequencyConfig']
|
|
103
124
|
) =>
|
|
104
125
|
Object.keys(autoship).reduce((acc, id) => {
|
|
105
126
|
if (!existingOptins.some(it => it.id === id)) {
|
|
106
127
|
if (!(autoship[id] && autoship_by_default[id] && in_stock[id])) return acc;
|
|
107
|
-
const {
|
|
128
|
+
const { frequencies: sellingPlans, frequenciesEveryPeriod } = frequencyConfig;
|
|
129
|
+
const { defaultFrequency } = offerEl || {};
|
|
108
130
|
const psdf = default_frequencies[id];
|
|
109
131
|
let frequency;
|
|
110
132
|
|
|
@@ -133,16 +155,16 @@ const productOrVariantInStockReducer = (acc, cur) => ({
|
|
|
133
155
|
[cur.id]: cur.available
|
|
134
156
|
});
|
|
135
157
|
|
|
136
|
-
const productTrue = (acc, [id]) => ({ ...acc, [id]: true });
|
|
158
|
+
const productTrue = (acc: Record<string, boolean>, [id]: [string, string[]]) => ({ ...acc, [id]: true });
|
|
137
159
|
|
|
138
160
|
const reduceProductCartLine = (acc, cur) => {
|
|
139
161
|
const productId = safeProductId(cur.key);
|
|
140
162
|
return { ...acc, [cur.key]: acc[productId] || null };
|
|
141
163
|
};
|
|
142
164
|
|
|
143
|
-
export const autoshipEligible = (state = {}, action) => {
|
|
165
|
+
export const autoshipEligible = (state: AutoshipEligibleState = {}, action): AutoshipEligibleState => {
|
|
144
166
|
if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
|
|
145
|
-
return Object.entries(action.payload).reduce(productTrue, state);
|
|
167
|
+
return Object.entries(action.payload as ReceiveProductPlansPayload).reduce(productTrue, state);
|
|
146
168
|
}
|
|
147
169
|
if (constants.SETUP_CART === action.type) {
|
|
148
170
|
const { payload: cart } = action;
|
|
@@ -178,152 +200,6 @@ export const autoshipEligible = (state = {}, action) => {
|
|
|
178
200
|
return state;
|
|
179
201
|
};
|
|
180
202
|
|
|
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
203
|
export const inStock = (state = {}, action) => {
|
|
328
204
|
if (constants.RECEIVE_PRODUCT_PLANS === action.type) {
|
|
329
205
|
return Object.entries(action.payload).reduce(productTrue, state);
|
|
@@ -354,18 +230,9 @@ export const inStock = (state = {}, action) => {
|
|
|
354
230
|
|
|
355
231
|
export const offer = (state = {}, _action) => state;
|
|
356
232
|
|
|
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
233
|
function getOptedInItem(cartItem) {
|
|
367
234
|
const prepaidShipments = getPrepaidShipments(cartItem.selling_plan_allocation.selling_plan);
|
|
368
|
-
const item = {
|
|
235
|
+
const item: OptInItem = {
|
|
369
236
|
id: cartItem.key,
|
|
370
237
|
frequency: `${cartItem.selling_plan_allocation.selling_plan.id}`
|
|
371
238
|
};
|
|
@@ -375,7 +242,7 @@ function getOptedInItem(cartItem) {
|
|
|
375
242
|
return item;
|
|
376
243
|
}
|
|
377
244
|
|
|
378
|
-
export const optedin = (state = [], action) => {
|
|
245
|
+
export const optedin = (state: OptedInState = [], action): OptedInState => {
|
|
379
246
|
if (constants.SETUP_CART === action.type) {
|
|
380
247
|
const cart = action.payload;
|
|
381
248
|
return state
|
|
@@ -384,9 +251,10 @@ export const optedin = (state = [], action) => {
|
|
|
384
251
|
}
|
|
385
252
|
|
|
386
253
|
if (constants.RECEIVE_OFFER === action.type) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
const
|
|
254
|
+
const payload = action.payload as ReceiveOfferPayload;
|
|
255
|
+
const { offer: offerEl = {}, frequencyConfig } = payload;
|
|
256
|
+
const existingOptins = mapExistingOptinsFromOfferResponse(state, offerEl, frequencyConfig);
|
|
257
|
+
const newOptins = reduceNewOptinsFromOfferResponse(payload, existingOptins, offerEl, frequencyConfig);
|
|
390
258
|
|
|
391
259
|
return [...existingOptins, ...newOptins];
|
|
392
260
|
}
|
|
@@ -423,7 +291,7 @@ export const optedin = (state = [], action) => {
|
|
|
423
291
|
return rest.concat({
|
|
424
292
|
...oldone,
|
|
425
293
|
...payload.product,
|
|
426
|
-
frequency:
|
|
294
|
+
frequency: payload.frequency
|
|
427
295
|
});
|
|
428
296
|
}
|
|
429
297
|
|
package/src/shopify/utils.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/types.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type ExperimentVariant = {
|
|
2
|
-
public_id: string;
|
|
3
|
-
parameters: any;
|
|
4
|
-
weight: number;
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
export type ExperimentConfig = {
|
|
8
|
-
public_id: string;
|
|
9
|
-
variants: ExperimentVariant[];
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export interface MerchantSettings {
|
|
13
|
-
currency_code: string;
|
|
14
|
-
multicurrency_enabled: boolean;
|
|
15
|
-
experiments?: ExperimentConfig;
|
|
16
|
-
}
|