@ordergroove/offers 2.28.9 → 2.28.11-alpha-PR-703-17.87
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/CHANGELOG.md +11 -0
- package/README.md +1 -0
- package/dist/bundle-report.html +48 -45
- package/dist/examples.js +261 -105
- package/dist/examples.js.map +2 -2
- package/dist/offers.js +69 -35
- package/dist/offers.js.map +3 -3
- package/examples/index.js +213 -217
- package/package.json +2 -2
- package/src/components/Modal.js +3 -3
- package/src/components/PrepaidToggle.js +100 -0
- package/src/components/Select.js +1 -1
- package/src/components/__tests__/PrepaidToggle.spec.js +113 -0
- package/src/core/__tests__/descriptors.spec.js +56 -0
- package/src/core/__tests__/reducer.spec.js +79 -0
- package/src/core/actions.js +5 -0
- package/src/core/constants.js +1 -0
- package/src/core/descriptors.js +7 -1
- package/src/core/reducer.js +19 -12
- package/src/core/selectors.js +13 -0
- package/src/core/utils.ts +16 -0
- package/src/make-api.js +2 -0
- package/src/shopify/__tests__/shopifyReducer.spec.js +113 -0
- package/src/shopify/shopifyMiddleware.ts +3 -0
- package/src/shopify/shopifyReducer.js +58 -6
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { html, render } from 'lit-html';
|
|
2
|
+
import { PrepaidToggle } from '../PrepaidToggle';
|
|
3
|
+
|
|
4
|
+
const TEST_CONTAINER_ID = 'test-container';
|
|
5
|
+
const TAG_NAME_UNDER_TEST = 'og-some-prepaid-toggle';
|
|
6
|
+
customElements.define(TAG_NAME_UNDER_TEST, PrepaidToggle);
|
|
7
|
+
|
|
8
|
+
async function renderTemplate(template) {
|
|
9
|
+
// make sure the element was cleaned up
|
|
10
|
+
expect(document.body.querySelector(TAG_NAME_UNDER_TEST)).toBeFalsy();
|
|
11
|
+
render(template, document.getElementById(TEST_CONTAINER_ID));
|
|
12
|
+
const element = document.querySelector(TAG_NAME_UNDER_TEST);
|
|
13
|
+
await element.updateComplete;
|
|
14
|
+
return element;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getPrepaidToggleTemplate(options = [3, 6]) {
|
|
18
|
+
return html`
|
|
19
|
+
<og-some-prepaid-toggle product="yum product">
|
|
20
|
+
${options.map(
|
|
21
|
+
opt =>
|
|
22
|
+
html`
|
|
23
|
+
<option value=${opt}></option>
|
|
24
|
+
`
|
|
25
|
+
)}
|
|
26
|
+
</og-some-prepaid-toggle>
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function expectPrepaidShipmentsAction(toggle, numShipments) {
|
|
31
|
+
expect(toggle.productChangePrepaidShipments).toHaveBeenCalledWith({ id: 'yum product' }, numShipments, undefined);
|
|
32
|
+
toggle.productChangePrepaidShipments.calls.reset();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toggleCheckbox(checkbox) {
|
|
36
|
+
checkbox.checked = !checkbox.checked;
|
|
37
|
+
checkbox.dispatchEvent(new Event('change'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function selectOption(ogSelect, value) {
|
|
41
|
+
const innerSelect = ogSelect.shadowRoot.querySelector('select');
|
|
42
|
+
innerSelect.value = value;
|
|
43
|
+
innerSelect.dispatchEvent(new Event('change'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe('PrepaidToggle', () => {
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
// create a dedicated container to render the templates into
|
|
49
|
+
// otherwise we get race conditions
|
|
50
|
+
const testContainer = document.createElement('div');
|
|
51
|
+
testContainer.id = TEST_CONTAINER_ID;
|
|
52
|
+
document.body.appendChild(testContainer);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
const testContainer = document.getElementById(TEST_CONTAINER_ID);
|
|
57
|
+
testContainer.remove();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should render with select options', async () => {
|
|
61
|
+
const toggle = await renderTemplate(getPrepaidToggleTemplate());
|
|
62
|
+
|
|
63
|
+
const select = toggle.shadowRoot.querySelector('og-select');
|
|
64
|
+
expect(select).toBeTruthy();
|
|
65
|
+
expect(select.options).toEqual([
|
|
66
|
+
{ value: 3, text: '3 shipments' },
|
|
67
|
+
{ value: 6, text: '6 shipments' }
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should render static text when only one option', async () => {
|
|
72
|
+
const toggle = await renderTemplate(getPrepaidToggleTemplate([3]));
|
|
73
|
+
const select = toggle.shadowRoot.querySelector('og-select');
|
|
74
|
+
expect(select).toBeFalsy();
|
|
75
|
+
const label = toggle.shadowRoot.querySelector('label');
|
|
76
|
+
expect(label.innerText).toBe('Prepay for 3 shipments');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should opt into prepaid', async () => {
|
|
80
|
+
const toggle = await renderTemplate(getPrepaidToggleTemplate());
|
|
81
|
+
toggle.productChangePrepaidShipments = jasmine.createSpy('productChangePrepaidShipments');
|
|
82
|
+
const checkbox = toggle.shadowRoot.querySelector('input');
|
|
83
|
+
|
|
84
|
+
// checking checkbox selects default value
|
|
85
|
+
toggleCheckbox(checkbox);
|
|
86
|
+
expectPrepaidShipmentsAction(toggle, 3);
|
|
87
|
+
|
|
88
|
+
// changing select changes selection
|
|
89
|
+
const select = toggle.shadowRoot.querySelector('og-select');
|
|
90
|
+
selectOption(select, 6);
|
|
91
|
+
expectPrepaidShipmentsAction(toggle, 6);
|
|
92
|
+
|
|
93
|
+
// unchecking checkbox opts out of prepaid
|
|
94
|
+
toggleCheckbox(checkbox);
|
|
95
|
+
expectPrepaidShipmentsAction(toggle, null);
|
|
96
|
+
|
|
97
|
+
// checking checkbox again opts into previously selected value
|
|
98
|
+
toggleCheckbox(checkbox);
|
|
99
|
+
expectPrepaidShipmentsAction(toggle, 6);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should preselect a value if shipments is set', async () => {
|
|
103
|
+
const toggle = await renderTemplate(getPrepaidToggleTemplate());
|
|
104
|
+
toggle.shipments = 6;
|
|
105
|
+
await toggle.updateComplete;
|
|
106
|
+
|
|
107
|
+
const checkbox = toggle.shadowRoot.querySelector('input');
|
|
108
|
+
expect(checkbox.checked).toBe(true);
|
|
109
|
+
|
|
110
|
+
const select = toggle.shadowRoot.querySelector('og-select');
|
|
111
|
+
expect(select.selected).toBe(6);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -228,3 +228,59 @@ describe('upcomingOrderContainsProduct', () => {
|
|
|
228
228
|
).toEqual(false);
|
|
229
229
|
});
|
|
230
230
|
});
|
|
231
|
+
|
|
232
|
+
describe('prepaidEligible', () => {
|
|
233
|
+
it('should return false if no eligibilityGroups at all', () => {
|
|
234
|
+
expect(descriptors.prepaidEligible({}, ownProps)).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should return true if it has the prepaid group', () => {
|
|
238
|
+
expect(
|
|
239
|
+
descriptors.prepaidEligible(
|
|
240
|
+
{
|
|
241
|
+
eligibilityGroups: {
|
|
242
|
+
[productId]: ['prepaid']
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
ownProps
|
|
246
|
+
)
|
|
247
|
+
).toBe(true);
|
|
248
|
+
expect(
|
|
249
|
+
descriptors.prepaidEligible(
|
|
250
|
+
{
|
|
251
|
+
eligibilityGroups: {
|
|
252
|
+
[productId]: []
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
ownProps
|
|
256
|
+
)
|
|
257
|
+
).toBe(false);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
function getPrepaidOptedinState(id, prepaidShipments) {
|
|
262
|
+
const frequency = '1_3';
|
|
263
|
+
return {
|
|
264
|
+
optedin: [
|
|
265
|
+
{
|
|
266
|
+
id,
|
|
267
|
+
frequency,
|
|
268
|
+
prepaidShipments
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
describe('prepaidSubscribed', () => {
|
|
275
|
+
it('returns false when prepaidShipments is not set', () => {
|
|
276
|
+
expect(descriptors.prepaidSubscribed(getPrepaidOptedinState(productId, undefined), ownProps)).toBe(false);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('returns true when prepaidShipments is set', () => {
|
|
280
|
+
expect(descriptors.prepaidSubscribed(getPrepaidOptedinState(productId, 3), ownProps)).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('returns false when prepaidShipments is set for another product', () => {
|
|
284
|
+
expect(descriptors.prepaidSubscribed(getPrepaidOptedinState('otherProduct', 3), ownProps)).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
@@ -60,6 +60,85 @@ describe('reducers', () => {
|
|
|
60
60
|
expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it('should remove existing prepaidShipments given action PRODUCT_CHANGE_FREQUENCY', () => {
|
|
64
|
+
const actual = optedin(
|
|
65
|
+
[
|
|
66
|
+
{
|
|
67
|
+
id: productToTest.id,
|
|
68
|
+
frequency: '1_3',
|
|
69
|
+
prepaidShipments: 2
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
{
|
|
73
|
+
type: constants.PRODUCT_CHANGE_FREQUENCY,
|
|
74
|
+
payload: {
|
|
75
|
+
product: productToTest,
|
|
76
|
+
frequency: '1_2'
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should remove existing prepaidShipments given action OPTIN_PRODUCT', () => {
|
|
84
|
+
const actual = optedin(
|
|
85
|
+
[
|
|
86
|
+
{
|
|
87
|
+
id: productToTest.id,
|
|
88
|
+
frequency: '1_3',
|
|
89
|
+
prepaidShipments: 2
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
{
|
|
93
|
+
type: constants.OPTIN_PRODUCT,
|
|
94
|
+
payload: {
|
|
95
|
+
product: productToTest,
|
|
96
|
+
frequency: '1_2'
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
expect(actual).toEqual([{ id: 'product 3', frequency: '1_2' }]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should set prepaidShipments given action PRODUCT_CHANGE_PREPAID_SHIPMENTS', () => {
|
|
104
|
+
const actual = optedin(
|
|
105
|
+
[
|
|
106
|
+
{
|
|
107
|
+
id: productToTest.id,
|
|
108
|
+
frequency: '1_3'
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
{
|
|
112
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
113
|
+
payload: {
|
|
114
|
+
product: productToTest,
|
|
115
|
+
prepaidShipments: 3
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
expect(actual).toEqual([{ id: 'product 3', frequency: '1_3', prepaidShipments: 3 }]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should remove prepaidShipments given action PRODUCT_CHANGE_PREPAID_SHIPMENTS with null', () => {
|
|
123
|
+
const actual = optedin(
|
|
124
|
+
[
|
|
125
|
+
{
|
|
126
|
+
id: productToTest.id,
|
|
127
|
+
frequency: '1_3',
|
|
128
|
+
prepaidShipments: 3
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
{
|
|
132
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
133
|
+
payload: {
|
|
134
|
+
product: productToTest,
|
|
135
|
+
prepaidShipments: null
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
expect(actual).toEqual([{ id: 'product 3', frequency: '1_3' }]);
|
|
140
|
+
});
|
|
141
|
+
|
|
63
142
|
it('should return empty array given OPTOUT_PRODUCT', () => {
|
|
64
143
|
const actual = optedin([], {
|
|
65
144
|
type: constants.OPTOUT_PRODUCT,
|
package/src/core/actions.js
CHANGED
|
@@ -23,6 +23,11 @@ export const productChangeFrequency = (product, frequency, offer) => ({
|
|
|
23
23
|
payload: { product, frequency, offer }
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
+
export const productChangePrepaidShipments = (product, prepaidShipments, offer) => ({
|
|
27
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
28
|
+
payload: { product, prepaidShipments, offer }
|
|
29
|
+
});
|
|
30
|
+
|
|
26
31
|
export const concludeUpsell = product => ({
|
|
27
32
|
type: constants.CONCLUDE_UPSELL,
|
|
28
33
|
payload: { product }
|
package/src/core/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const OPTIN_PRODUCT = 'OPTIN_PRODUCT';
|
|
2
2
|
export const OPTOUT_PRODUCT = 'OPTOUT_PRODUCT';
|
|
3
3
|
export const PRODUCT_CHANGE_FREQUENCY = 'PRODUCT_CHANGE_FREQUENCY';
|
|
4
|
+
export const PRODUCT_CHANGE_PREPAID_SHIPMENTS = 'PRODUCT_CHANGE_PREPAID_SHIPMENTS';
|
|
4
5
|
export const SET_MERCHANT_ID = 'SET_MERCHANT_ID';
|
|
5
6
|
export const REQUEST_OFFER = 'REQUEST_OFFER';
|
|
6
7
|
export const RECEIVE_OFFER = 'RECEIVE_OFFER';
|
package/src/core/descriptors.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeSubscribedSelector, makeOptedoutSelector } from './selectors';
|
|
1
|
+
import { makeSubscribedSelector, makeOptedoutSelector, makePrepaidSubscribedSelector } from './selectors';
|
|
2
2
|
|
|
3
3
|
export const inStock = (state, ownProps) => (state.inStock || {})[(ownProps.product || {}).id];
|
|
4
4
|
export const eligible = (state, ownProps) => (state.autoshipEligible || {})[(ownProps.product || {}).id] || false;
|
|
@@ -15,8 +15,14 @@ export const hasUpsellGroup = (state, ownProps) => {
|
|
|
15
15
|
return groups === null || !!groups.find(it => it === 'upsell' || it === 'impulse_upsell');
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
export const prepaidEligible = (state, ownProps) => {
|
|
19
|
+
const groups = eligibilityGroups(state, ownProps);
|
|
20
|
+
return groups?.some(it => it === 'prepaid') || false;
|
|
21
|
+
};
|
|
22
|
+
|
|
18
23
|
export const subscribed = (state, ownProps) => makeSubscribedSelector(ownProps.product)(state);
|
|
19
24
|
export const optedout = (state, ownProps) => makeOptedoutSelector(ownProps.product)(state);
|
|
25
|
+
export const prepaidSubscribed = (state, ownProps) => makePrepaidSubscribedSelector(ownProps.product)(state);
|
|
20
26
|
|
|
21
27
|
export const hasUpcomingOrder = state => !!(state.nextUpcomingOrder && state.nextUpcomingOrder.public_id);
|
|
22
28
|
|
package/src/core/reducer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { combineReducers } from 'redux';
|
|
|
3
3
|
import * as constants from './constants';
|
|
4
4
|
import { isSameProduct } from './selectors';
|
|
5
5
|
import { stringifyFrequency } from './api';
|
|
6
|
-
import { safeProductId } from './utils';
|
|
6
|
+
import { safeProductId, getMatchingProductIfExists } from './utils';
|
|
7
7
|
|
|
8
8
|
export const optedin = (state = [], action) => {
|
|
9
9
|
switch (action.type) {
|
|
@@ -12,16 +12,26 @@ export const optedin = (state = [], action) => {
|
|
|
12
12
|
case constants.LOCAL_STORAGE_CHANGE:
|
|
13
13
|
return action.newValue ? action.newValue.optedin : state;
|
|
14
14
|
case constants.OPTIN_PRODUCT:
|
|
15
|
-
case constants.PRODUCT_CHANGE_FREQUENCY:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
20
|
-
return (rest || []).concat({
|
|
15
|
+
case constants.PRODUCT_CHANGE_FREQUENCY: {
|
|
16
|
+
// since prepaid maps to a different set of frequencies, we remove prepaidShipments when frequency is changed
|
|
17
|
+
const [{ prepaidShipments, ...oldone }, rest] = getMatchingProductIfExists(state, action.payload.product);
|
|
18
|
+
return rest.concat({
|
|
21
19
|
...oldone,
|
|
22
20
|
...action.payload.product,
|
|
23
21
|
frequency: action.payload.frequency
|
|
24
22
|
});
|
|
23
|
+
}
|
|
24
|
+
case constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS:
|
|
25
|
+
const { payload } = action;
|
|
26
|
+
const [{ prepaidShipments, ...oldone }, rest] = getMatchingProductIfExists(state, payload.product);
|
|
27
|
+
const newState = {
|
|
28
|
+
...oldone,
|
|
29
|
+
...payload.product
|
|
30
|
+
};
|
|
31
|
+
if (payload.prepaidShipments) {
|
|
32
|
+
newState.prepaidShipments = payload.prepaidShipments;
|
|
33
|
+
}
|
|
34
|
+
return rest.concat(newState);
|
|
25
35
|
case constants.OPTOUT_PRODUCT:
|
|
26
36
|
return state.filter(a => !isSameProduct(action.payload.product, a));
|
|
27
37
|
case constants.PRODUCT_HAS_CHANGED:
|
|
@@ -47,11 +57,8 @@ export const optedout = (state = [], action) => {
|
|
|
47
57
|
case constants.PRODUCT_CHANGE_FREQUENCY:
|
|
48
58
|
return state.filter(a => !isSameProduct(action.payload.product, a));
|
|
49
59
|
case constants.OPTOUT_PRODUCT:
|
|
50
|
-
const [
|
|
51
|
-
|
|
52
|
-
[[], []]
|
|
53
|
-
);
|
|
54
|
-
return (rest || []).concat({
|
|
60
|
+
const [oldone, rest] = getMatchingProductIfExists(state, action.payload.product);
|
|
61
|
+
return rest.concat({
|
|
55
62
|
...oldone,
|
|
56
63
|
...action.payload.product,
|
|
57
64
|
frequency: action.payload.frequency
|
package/src/core/selectors.js
CHANGED
|
@@ -99,6 +99,12 @@ export const makeSubscribedSelector = memoize(
|
|
|
99
99
|
product => JSON.stringify(product)
|
|
100
100
|
);
|
|
101
101
|
|
|
102
|
+
export const makePrepaidSubscribedSelector = memoize(
|
|
103
|
+
product =>
|
|
104
|
+
createSelector(optedinSelector, optedin => optedin.some(b => isSameProduct(product, b) && b.prepaidShipments)),
|
|
105
|
+
product => JSON.stringify(product)
|
|
106
|
+
);
|
|
107
|
+
|
|
102
108
|
/**
|
|
103
109
|
* Creates a function with state arguments that return the true when
|
|
104
110
|
* productId is in the optedout array
|
|
@@ -114,6 +120,13 @@ export const makeProductFrequencySelector = memoize(productId =>
|
|
|
114
120
|
createSelector(makeOptedinSelector(productId), productOptin => (productOptin && productOptin.frequency) || null)
|
|
115
121
|
);
|
|
116
122
|
|
|
123
|
+
export const makeProductPrepaidShipmentsSelector = memoize(productId =>
|
|
124
|
+
createSelector(
|
|
125
|
+
makeOptedinSelector(productId),
|
|
126
|
+
productOptin => (productOptin && productOptin.prepaidShipments) || null
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
|
|
117
130
|
/**
|
|
118
131
|
* Creates a function with state arguments that returns stringified frequency
|
|
119
132
|
* if default frequency exists for the product
|
package/src/core/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import platform from '../platform';
|
|
2
2
|
import { ENV_PROD, ENV_STAGING, STAGING_STATIC_HOST, STATIC_HOST } from './constants';
|
|
3
|
+
import { isSameProduct } from './selectors';
|
|
3
4
|
|
|
4
5
|
export function onReady(fn) {
|
|
5
6
|
if (document.readyState === 'loading') {
|
|
@@ -140,3 +141,18 @@ export function getOrCreateHidden(parent, name, value) {
|
|
|
140
141
|
input.value = value;
|
|
141
142
|
}
|
|
142
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Returns the first matching product if it exists, or an empty object if it doesn't.
|
|
147
|
+
* @param state - the optedin/optedout state array to search
|
|
148
|
+
* @param product - the product to search for
|
|
149
|
+
* @returns {[any, any[]]} - a two item array where the first item is the matching product and the second is the remaining items from the original state.
|
|
150
|
+
*/
|
|
151
|
+
export function getMatchingProductIfExists(state, product) {
|
|
152
|
+
const [[oldone], rest] = state.reduce((acc, val) => acc[isSameProduct(product, val) ? 0 : 1].push(val) && acc, [
|
|
153
|
+
[],
|
|
154
|
+
[]
|
|
155
|
+
]);
|
|
156
|
+
|
|
157
|
+
return [oldone || {}, rest || []];
|
|
158
|
+
}
|
package/src/make-api.js
CHANGED
|
@@ -20,6 +20,7 @@ import { Modal } from './components/Modal';
|
|
|
20
20
|
import { Select } from './components/Select';
|
|
21
21
|
import { Tooltip } from './components/Tooltip';
|
|
22
22
|
import { ConnectedFrequencyStatus } from './components/FrequencyStatus';
|
|
23
|
+
import { ConnectedPrepaidToggle } from './components/PrepaidToggle';
|
|
23
24
|
import * as testMode from './test-mode';
|
|
24
25
|
import { api } from './core/api';
|
|
25
26
|
import { environment, offer } from './core/reducer';
|
|
@@ -51,6 +52,7 @@ export default function makeApi(store) {
|
|
|
51
52
|
customElements.define('og-upsell-modal', ConnectedUpsellModal);
|
|
52
53
|
customElements.define('og-next-upcoming-order', ConnectedNextUpcomingOrder);
|
|
53
54
|
customElements.define('og-price', ConnectedPrice);
|
|
55
|
+
customElements.define('og-prepaid-toggle', ConnectedPrepaidToggle);
|
|
54
56
|
} catch (err) {
|
|
55
57
|
console.info('OG WebComponents already registered, skipping.');
|
|
56
58
|
}
|
|
@@ -344,6 +344,45 @@ describe('config', () => {
|
|
|
344
344
|
);
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
+
it('should set prepaidSellingPlans', () => {
|
|
348
|
+
const sellingPlanGroups = [
|
|
349
|
+
{
|
|
350
|
+
name: 'Prepaid-43017264201944',
|
|
351
|
+
selling_plans: [
|
|
352
|
+
{
|
|
353
|
+
id: 2146042072,
|
|
354
|
+
name: 'Delivered every 2 months, prepaid for 4 shipments',
|
|
355
|
+
options: [
|
|
356
|
+
{ name: 'Delivery every', position: 1, value: 'PREPAID-2 months' },
|
|
357
|
+
{ name: 'Shipment amount', position: 2, value: '4 shipments' }
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
];
|
|
363
|
+
const actual = config(
|
|
364
|
+
{},
|
|
365
|
+
{
|
|
366
|
+
type: constants.SETUP_PRODUCT,
|
|
367
|
+
payload: {
|
|
368
|
+
product: {
|
|
369
|
+
selling_plan_groups: sellingPlanGroups
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
expect(actual).toEqual({
|
|
375
|
+
prepaidSellingPlans: {
|
|
376
|
+
'43017264201944': [
|
|
377
|
+
{
|
|
378
|
+
numberShipments: 4,
|
|
379
|
+
sellingPlan: '2146042072'
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
347
386
|
it('should return unmodified state given unsupported action', () => {
|
|
348
387
|
const actual = config(
|
|
349
388
|
{ 'yum existing key': 'yum existing value' },
|
|
@@ -1156,6 +1195,80 @@ describe('optedin', () => {
|
|
|
1156
1195
|
});
|
|
1157
1196
|
});
|
|
1158
1197
|
|
|
1198
|
+
describe('given action is PRODUCT_CHANGE_PREPAID_SHIPMENTS', () => {
|
|
1199
|
+
function getPayload(prepaidShipments) {
|
|
1200
|
+
return {
|
|
1201
|
+
product: {
|
|
1202
|
+
id: 'yum prepaid id'
|
|
1203
|
+
},
|
|
1204
|
+
prepaidShipments: prepaidShipments,
|
|
1205
|
+
offer: {
|
|
1206
|
+
config: {
|
|
1207
|
+
prepaidSellingPlans: {
|
|
1208
|
+
'yum prepaid id': [
|
|
1209
|
+
{ numberShipments: 3, sellingPlan: 'yum prepaid selling plan id 1' },
|
|
1210
|
+
{
|
|
1211
|
+
numberShipments: 4,
|
|
1212
|
+
sellingPlan: 'yum prepaid selling plan id 2'
|
|
1213
|
+
}
|
|
1214
|
+
]
|
|
1215
|
+
},
|
|
1216
|
+
frequencies: ['yum selling plan id 1']
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
it('sets frequency to the corresponding selling plan', () => {
|
|
1223
|
+
const actual = optedin([], {
|
|
1224
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
1225
|
+
payload: getPayload(4)
|
|
1226
|
+
});
|
|
1227
|
+
expect(actual).toEqual([
|
|
1228
|
+
{
|
|
1229
|
+
id: 'yum prepaid id',
|
|
1230
|
+
frequency: 'yum prepaid selling plan id 2',
|
|
1231
|
+
prepaidShipments: 4
|
|
1232
|
+
}
|
|
1233
|
+
]);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
it('sets frequency to the first frequency when prepaidShipments is null', () => {
|
|
1237
|
+
const actual = optedin([], {
|
|
1238
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
1239
|
+
payload: getPayload(null)
|
|
1240
|
+
});
|
|
1241
|
+
expect(actual).toEqual([
|
|
1242
|
+
{
|
|
1243
|
+
id: 'yum prepaid id',
|
|
1244
|
+
frequency: 'yum selling plan id 1'
|
|
1245
|
+
}
|
|
1246
|
+
]);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
it('updates existing opted in state', () => {
|
|
1250
|
+
const actual = optedin(
|
|
1251
|
+
[
|
|
1252
|
+
{
|
|
1253
|
+
id: 'yum prepaid id',
|
|
1254
|
+
frequency: 'yum selling plan id 1'
|
|
1255
|
+
}
|
|
1256
|
+
],
|
|
1257
|
+
{
|
|
1258
|
+
type: constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
1259
|
+
payload: getPayload(4)
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
expect(actual).toEqual([
|
|
1263
|
+
{
|
|
1264
|
+
id: 'yum prepaid id',
|
|
1265
|
+
frequency: 'yum prepaid selling plan id 2',
|
|
1266
|
+
prepaidShipments: 4
|
|
1267
|
+
}
|
|
1268
|
+
]);
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1159
1272
|
it('should return unmodified state given unsupported action', () => {
|
|
1160
1273
|
const actual = optedin(
|
|
1161
1274
|
{ 'yum existing key': 'yum existing value' },
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
OPTIN_PRODUCT,
|
|
7
7
|
OPTOUT_PRODUCT,
|
|
8
8
|
PRODUCT_CHANGE_FREQUENCY,
|
|
9
|
+
PRODUCT_CHANGE_PREPAID_SHIPMENTS,
|
|
9
10
|
RECEIVE_OFFER,
|
|
10
11
|
REQUEST_OFFER,
|
|
11
12
|
SETUP_CART,
|
|
@@ -340,6 +341,8 @@ export default function shopifyMiddleware(store) {
|
|
|
340
341
|
case OPTOUT_PRODUCT:
|
|
341
342
|
case PRODUCT_CHANGE_FREQUENCY:
|
|
342
343
|
synchronizeCartOptin(action, store);
|
|
344
|
+
// eslint-disable-next-line no-fallthrough
|
|
345
|
+
case PRODUCT_CHANGE_PREPAID_SHIPMENTS:
|
|
343
346
|
case REQUEST_OFFER:
|
|
344
347
|
case RECEIVE_OFFER:
|
|
345
348
|
case SETUP_PRODUCT:
|
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
hasShopifySellingPlans,
|
|
29
29
|
isOgFrequency,
|
|
30
30
|
mapFrequencyToSellingPlan,
|
|
31
|
-
safeProductId
|
|
31
|
+
safeProductId,
|
|
32
|
+
getMatchingProductIfExists
|
|
32
33
|
} from '../core/utils';
|
|
33
34
|
|
|
34
35
|
const money = val => (val === null ? '' : `$${val.toString().replace(/(\d\d)$/, '.$1')}`);
|
|
@@ -210,6 +211,22 @@ export function sellingPlansToFrequencies(sellingPlanGroup) {
|
|
|
210
211
|
return sellingPlanGroup?.selling_plans?.map(({ id }) => `${id}`);
|
|
211
212
|
}
|
|
212
213
|
|
|
214
|
+
function getPrepaidSellingPlans(prepaidSellingPlanGroups) {
|
|
215
|
+
return prepaidSellingPlanGroups.reduce((acc, cur) => {
|
|
216
|
+
const variant = cur.name.split('-')[1];
|
|
217
|
+
|
|
218
|
+
const sellingPlanInfo = cur.selling_plans.map(sellingPlanObject => {
|
|
219
|
+
const shipments = sellingPlanObject?.options.find(({ name }) => name === 'Shipment amount')?.value.split(' ')[0];
|
|
220
|
+
return {
|
|
221
|
+
numberShipments: Number(shipments),
|
|
222
|
+
sellingPlan: String(sellingPlanObject.id)
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return { ...acc, [variant]: sellingPlanInfo };
|
|
227
|
+
}, {});
|
|
228
|
+
}
|
|
229
|
+
|
|
213
230
|
export const config = (
|
|
214
231
|
state = {
|
|
215
232
|
frequencies: [],
|
|
@@ -236,8 +253,10 @@ export const config = (
|
|
|
236
253
|
const {
|
|
237
254
|
payload: { offer: offerEl, product }
|
|
238
255
|
} = action;
|
|
256
|
+
let configToAdd = {};
|
|
257
|
+
// pay as you go selling plans
|
|
239
258
|
const sellingPlanGroup = getOGSellingPlanGroup(product);
|
|
240
|
-
const frequencies = sellingPlanGroup
|
|
259
|
+
const frequencies = sellingPlansToFrequencies(sellingPlanGroup);
|
|
241
260
|
if (frequencies?.length) {
|
|
242
261
|
const frequenciesEveryPeriod = sellingPlansToEveryPeriod(sellingPlanGroup);
|
|
243
262
|
const frequenciesText = sellingPlanGroup.options?.[0]?.values || frequencies;
|
|
@@ -249,15 +268,26 @@ export const config = (
|
|
|
249
268
|
getFirstSellingPlan(frequencies) ||
|
|
250
269
|
defaultFrequency;
|
|
251
270
|
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
...state,
|
|
255
|
-
frequenciesEveryPeriod,
|
|
271
|
+
configToAdd = {
|
|
256
272
|
frequencies,
|
|
273
|
+
frequenciesEveryPeriod,
|
|
257
274
|
frequenciesText,
|
|
258
275
|
...(defaultFrequency ? { defaultFrequency } : {})
|
|
259
276
|
};
|
|
260
277
|
}
|
|
278
|
+
|
|
279
|
+
// prepaid selling plans
|
|
280
|
+
const prepaidSellingPlanGroups = product?.selling_plan_groups.filter(group => /^Prepaid-.*/.test(group.name));
|
|
281
|
+
if (prepaidSellingPlanGroups.length) {
|
|
282
|
+
configToAdd = {
|
|
283
|
+
...configToAdd,
|
|
284
|
+
prepaidSellingPlans: getPrepaidSellingPlans(prepaidSellingPlanGroups)
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
...state,
|
|
289
|
+
...configToAdd
|
|
290
|
+
};
|
|
261
291
|
}
|
|
262
292
|
|
|
263
293
|
if (constants.RECEIVE_OFFER === action.type) {
|
|
@@ -310,6 +340,15 @@ export const inStock = (state = {}, action) => {
|
|
|
310
340
|
|
|
311
341
|
export const offer = (state = {}, action) => state;
|
|
312
342
|
|
|
343
|
+
function getFrequencyForPrepaidShipments({ prepaidShipments, offer: offerEl, product }) {
|
|
344
|
+
if (prepaidShipments) {
|
|
345
|
+
const productId = product.id;
|
|
346
|
+
const plan = offerEl.config.prepaidSellingPlans[productId]?.find(p => p.numberShipments === prepaidShipments);
|
|
347
|
+
return plan ? plan.sellingPlan : null;
|
|
348
|
+
}
|
|
349
|
+
return offerEl.config.frequencies[0];
|
|
350
|
+
}
|
|
351
|
+
|
|
313
352
|
export const optedin = (state = [], action) => {
|
|
314
353
|
if (constants.SETUP_CART === action.type) {
|
|
315
354
|
const cart = action.payload;
|
|
@@ -354,6 +393,19 @@ export const optedin = (state = [], action) => {
|
|
|
354
393
|
});
|
|
355
394
|
}
|
|
356
395
|
|
|
396
|
+
if (constants.PRODUCT_CHANGE_PREPAID_SHIPMENTS === action.type) {
|
|
397
|
+
const { payload } = action;
|
|
398
|
+
// core reducer sets prepaid shipments
|
|
399
|
+
const newState = coreOptedin(state, action);
|
|
400
|
+
// get the new frequency (selling plan) that matches the prepaid shipments
|
|
401
|
+
const [oldone, rest] = getMatchingProductIfExists(newState, payload.product);
|
|
402
|
+
return rest.concat({
|
|
403
|
+
...oldone,
|
|
404
|
+
...payload.product,
|
|
405
|
+
frequency: getFrequencyForPrepaidShipments(payload)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
357
409
|
return coreOptedin(state, action);
|
|
358
410
|
};
|
|
359
411
|
|