@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.
- package/dist/bundle-report.html +42 -39
- package/dist/offers.js +69 -69
- package/dist/offers.js.map +4 -4
- package/package.json +2 -2
- package/src/components/FrequencyStatus.js +14 -10
- package/src/components/Offer.js +33 -14
- package/src/components/OptinButton.js +2 -2
- package/src/components/OptinSelect.js +6 -5
- package/src/components/OptinStatus.js +16 -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__/experiments.spec.js +0 -3
- 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.ts +215 -0
- package/src/core/types/api.ts +71 -0
- package/src/core/types/reducer.ts +94 -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 +603 -0
- package/src/shopify/__tests__/shopifyReducer.spec.js +69 -744
- package/src/shopify/__tests__/utils.spec.js +24 -1
- package/src/shopify/reducers/config.ts +185 -0
- package/src/shopify/shopifyMiddleware.ts +2 -9
- package/src/shopify/{shopifyReducer.js → shopifyReducer.ts} +50 -195
- package/src/shopify/utils.ts +25 -0
- package/src/core/selectors.js +0 -192
- 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?:
|
|
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
|
-
|
|
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
|
-
|
|
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 = (
|
|
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 = (
|
|
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(
|
|
98
|
+
frequency: hasShopifySellingPlans(frequencyConfig?.frequencies, frequencyConfig?.frequenciesEveryPeriod)
|
|
81
99
|
? mapFrequencyToSellingPlan(
|
|
82
|
-
|
|
83
|
-
|
|
100
|
+
frequencyConfig?.frequencies,
|
|
101
|
+
frequencyConfig?.frequenciesEveryPeriod,
|
|
84
102
|
it.frequency
|
|
85
103
|
) ||
|
|
86
104
|
mapFrequencyToSellingPlan(
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
frequencyConfig?.frequencies,
|
|
106
|
+
frequencyConfig?.frequenciesEveryPeriod,
|
|
89
107
|
offerEl?.defaultFrequency
|
|
90
108
|
) ||
|
|
91
|
-
getFirstSellingPlan(
|
|
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 {
|
|
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
|
|
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
|
|
388
|
-
const
|
|
389
|
-
const
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|