@ordergroove/offers 2.27.23 → 2.28.0
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 +21 -0
- package/README.md +24 -1
- package/dist/bundle-report.html +40 -37
- package/dist/offers.js +54 -54
- package/dist/offers.js.map +3 -3
- package/karma-shopify.conf.js +79 -0
- package/karma.conf.js +1 -1
- package/package.json +5 -3
- package/src/components/Offer.js +1 -0
- package/src/components/OptinButton.js +25 -5
- package/src/components/OptinSelect.js +2 -4
- package/src/components/OptinStatus.js +5 -1
- package/src/components/OptinToggle.js +9 -2
- package/src/components/__tests__/OG.fspec.js +84 -2
- package/src/core/__tests__/utils.spec.js +26 -0
- package/src/core/selectors.js +18 -1
- package/src/core/store.js +3 -2
- package/src/core/utils.ts +45 -6
- package/src/core/waitUntilOffersReady.js +66 -0
- package/src/index.js +6 -2
- package/src/init-shopify-tests.js +7 -0
- package/src/make-api.js +5 -15
- package/src/shopify/__tests__/shopifyReducer.spec.js +915 -122
- package/src/shopify/shopifyMiddleware.ts +4 -3
- package/src/shopify/shopifyReducer.js +169 -34
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Karma configuration
|
|
2
|
+
// Generated on Mon Apr 01 2019 14:32:09 GMT-0400 (EDT)
|
|
3
|
+
|
|
4
|
+
module.exports = function(config) {
|
|
5
|
+
config.set({
|
|
6
|
+
// frameworks to use
|
|
7
|
+
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
|
8
|
+
frameworks: ['jasmine'],
|
|
9
|
+
|
|
10
|
+
// list of files / patterns to load in the browser
|
|
11
|
+
files: [
|
|
12
|
+
{
|
|
13
|
+
pattern: 'src/init-shopify-tests.js',
|
|
14
|
+
type: 'js',
|
|
15
|
+
included: true,
|
|
16
|
+
served: true
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
pattern: 'src/shopify/**/*.spec.js',
|
|
20
|
+
type: 'js',
|
|
21
|
+
included: true,
|
|
22
|
+
served: true
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
// list of files / patterns to exclude
|
|
27
|
+
exclude: [],
|
|
28
|
+
|
|
29
|
+
// preprocess matching files before serving them to the browser
|
|
30
|
+
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
|
31
|
+
preprocessors: {
|
|
32
|
+
'**/*.js': ['esbuild']
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
esbuild: {
|
|
36
|
+
// Replace some global variables
|
|
37
|
+
define: {
|
|
38
|
+
// COVERAGE: coverage,
|
|
39
|
+
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || ''),
|
|
40
|
+
ENABLE_PERFORMANCE: true
|
|
41
|
+
},
|
|
42
|
+
// plugins: [createEsbuildPlugin()],
|
|
43
|
+
|
|
44
|
+
// Karma-esbuild specific options
|
|
45
|
+
singleBundle: false // Merge all test files into one bundle(default: true)
|
|
46
|
+
// singleBundle: true // Merge all test files into one bundle(default: true)
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// test results reporter to use
|
|
50
|
+
// possible values: 'dots', 'progress'
|
|
51
|
+
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
|
52
|
+
reporters: ['spec'],
|
|
53
|
+
|
|
54
|
+
// web server port
|
|
55
|
+
port: 9876,
|
|
56
|
+
|
|
57
|
+
// enable / disable colors in the output (reporters and logs)
|
|
58
|
+
colors: true,
|
|
59
|
+
|
|
60
|
+
// level of logging
|
|
61
|
+
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
|
62
|
+
logLevel: config.LOG_DEBUG,
|
|
63
|
+
|
|
64
|
+
// enable / disable watching file and executing tests whenever any file changes
|
|
65
|
+
autoWatch: true,
|
|
66
|
+
|
|
67
|
+
// start these browsers
|
|
68
|
+
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
|
69
|
+
browsers: ['ChromeHeadless'],
|
|
70
|
+
|
|
71
|
+
// Continuous Integration mode
|
|
72
|
+
// if true, Karma captures browsers, runs the tests and exits
|
|
73
|
+
singleRun: false,
|
|
74
|
+
|
|
75
|
+
// Concurrency level
|
|
76
|
+
// how many browser should be started simultaneous
|
|
77
|
+
concurrency: Infinity
|
|
78
|
+
});
|
|
79
|
+
};
|
package/karma.conf.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ordergroove/offers",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.28.0",
|
|
4
4
|
"description": "offer state component",
|
|
5
5
|
"author": "Eugenio Lattanzio <eugenio63@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/ordergroove/plush-toys#readme",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"bundlesize": "../../node_modules/.bin/bundlesize",
|
|
18
18
|
"lint": "../../node_modules/.bin/eslint --ignore-path ../../.gitignore ./src",
|
|
19
19
|
"prepublishOnly": "npm run -s build:prod && npm run -s bundlesize",
|
|
20
|
-
"test": "../../node_modules/.bin/karma start --single-run --log-level error --reporters progress && npm run build && ../../node_modules/.bin/karma start --single-run --log-level error --reporters progress karma-functional.conf.js",
|
|
20
|
+
"test": "../../node_modules/.bin/karma start --single-run --log-level error --reporters progress && npm run test:shopify && npm run build && ../../node_modules/.bin/karma start --single-run --log-level error --reporters progress karma-functional.conf.js",
|
|
21
|
+
"test:shopify": "../../node_modules/.bin/karma start karma-shopify.conf.js --single-run --log-level error --reporters progress",
|
|
22
|
+
"test:watch:shopify": "../../node_modules/.bin/karma start karma-shopify.conf.js",
|
|
21
23
|
"test:watch": "../../node_modules/.bin/karma start",
|
|
22
24
|
"test:watch:functional": "npm run build && ../../node_modules/.bin/karma start karma-functional.conf.js",
|
|
23
25
|
"test:functional": "npm run test:watch:functional -- --single-run --log-level error --reporters progress",
|
|
@@ -45,5 +47,5 @@
|
|
|
45
47
|
"devDependencies": {
|
|
46
48
|
"@ordergroove/offers-templates": "^0.4.16"
|
|
47
49
|
},
|
|
48
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "b662bba3339b36f954a57b56a350c3c4a76080c7"
|
|
49
51
|
}
|
package/src/components/Offer.js
CHANGED
|
@@ -4,6 +4,8 @@ import { OptinStatus, mapStateToProps } from './OptinStatus';
|
|
|
4
4
|
import { connect } from '../core/connect';
|
|
5
5
|
import { defaultFrequency } from '../core/props';
|
|
6
6
|
import { resolveProduct } from '../core/resolveProperties';
|
|
7
|
+
import { frequencyToSellingPlan } from '../core/utils';
|
|
8
|
+
import platform from '../platform';
|
|
7
9
|
|
|
8
10
|
export class OptinButton extends OptinStatus {
|
|
9
11
|
static get properties() {
|
|
@@ -15,12 +17,30 @@ export class OptinButton extends OptinStatus {
|
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
updated(changed) {
|
|
21
|
+
if (changed.has('subscribed')) {
|
|
22
|
+
this.frequencyMatch = this.frequency === this.optinFrequency;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get optinFrequency() {
|
|
27
|
+
let freq;
|
|
28
|
+
|
|
29
|
+
// use the attribute since this.defaultFrequency comes from mapStateToProps and contains more logic
|
|
30
|
+
if (this.hasAttribute('default-frequency')) {
|
|
31
|
+
freq = this.getAttribute('default-frequency');
|
|
32
|
+
} else {
|
|
33
|
+
freq = this.offer ? this.offer.defaultFrequency : this.defaultFrequency;
|
|
34
|
+
}
|
|
35
|
+
if (platform.shopify_selling_plans && this.store) {
|
|
36
|
+
freq = frequencyToSellingPlan(freq, this.store.getState().config);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return freq;
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
handleClick(ev) {
|
|
19
|
-
this.optinProduct(
|
|
20
|
-
resolveProduct(this),
|
|
21
|
-
this.offer ? this.offer.defaultFrequency : this.defaultFrequency,
|
|
22
|
-
this.offer
|
|
23
|
-
);
|
|
43
|
+
this.optinProduct(resolveProduct(this), this.optinFrequency, this.offer);
|
|
24
44
|
ev.preventDefault();
|
|
25
45
|
}
|
|
26
46
|
|
|
@@ -35,10 +35,8 @@ export class OptinSelect extends withChildOptions(OptinStatus) {
|
|
|
35
35
|
if (!this.subscribed) {
|
|
36
36
|
return 'optedOut';
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
return this.defaultFrequency;
|
|
38
|
+
|
|
39
|
+
return this.frequency || this.productDefaultFrequency || this.defaultFrequency;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
onOptinChange(value) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { html, css } from 'lit-element';
|
|
2
2
|
import {
|
|
3
3
|
makeOptedinSelector,
|
|
4
|
+
makeProductDefaultFrequencySelector,
|
|
4
5
|
makeProductFrequencySelector,
|
|
5
6
|
configSelector,
|
|
6
7
|
templatesSelector
|
|
@@ -14,7 +15,9 @@ export class OptinStatus extends withProduct(TemplateElement) {
|
|
|
14
15
|
static get properties() {
|
|
15
16
|
return {
|
|
16
17
|
subscribed,
|
|
17
|
-
frequencyMatch: { type: Boolean, reflect: true, attribute: 'frequency-match' }
|
|
18
|
+
frequencyMatch: { type: Boolean, reflect: true, attribute: 'frequency-match' },
|
|
19
|
+
productDefaultFrequency: { type: String },
|
|
20
|
+
defaultFrequency: { type: String }
|
|
18
21
|
};
|
|
19
22
|
}
|
|
20
23
|
|
|
@@ -121,6 +124,7 @@ export class OptinStatus extends withProduct(TemplateElement) {
|
|
|
121
124
|
export const mapStateToProps = (state, ownProps = {}) => ({
|
|
122
125
|
subscribed: makeOptedinSelector(ownProps.product)(state),
|
|
123
126
|
frequency: makeProductFrequencySelector(ownProps.product)(state),
|
|
127
|
+
productDefaultFrequency: makeProductDefaultFrequencySelector((ownProps.product || {}).id)(state),
|
|
124
128
|
...configSelector(state, ownProps, 'defaultFrequency'),
|
|
125
129
|
...templatesSelector(state, ownProps)
|
|
126
130
|
});
|
|
@@ -49,8 +49,15 @@ export class OptinToggle extends OptinStatus {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
handleClick(ev) {
|
|
52
|
-
if (this.subscribed)
|
|
53
|
-
|
|
52
|
+
if (this.subscribed) {
|
|
53
|
+
this.optoutProduct(this.product, this.offer);
|
|
54
|
+
} else {
|
|
55
|
+
this.optinProduct(
|
|
56
|
+
this.product,
|
|
57
|
+
this.frequency || this.productDefaultFrequency || this.defaultFrequency,
|
|
58
|
+
this.offer
|
|
59
|
+
);
|
|
60
|
+
}
|
|
54
61
|
ev.preventDefault();
|
|
55
62
|
}
|
|
56
63
|
|
|
@@ -19,7 +19,7 @@ const waitUpdate = async offer => {
|
|
|
19
19
|
await offer.updateComplete;
|
|
20
20
|
await new Promise(r => setTimeout(r, 10));
|
|
21
21
|
};
|
|
22
|
-
const mockOfferResponse = (productId, inStock = true, autoship = true) => {
|
|
22
|
+
const mockOfferResponse = (productId, inStock = true, autoship = true, defaultFrequency) => {
|
|
23
23
|
return Promise.resolve({
|
|
24
24
|
json() {
|
|
25
25
|
return Promise.resolve({
|
|
@@ -33,7 +33,12 @@ const mockOfferResponse = (productId, inStock = true, autoship = true) => {
|
|
|
33
33
|
incentives_display: {},
|
|
34
34
|
incentives: {
|
|
35
35
|
[productId]: { initial: [], ongoing: [] }
|
|
36
|
-
}
|
|
36
|
+
},
|
|
37
|
+
...(defaultFrequency && {
|
|
38
|
+
default_frequencies: {
|
|
39
|
+
[productId]: defaultFrequency
|
|
40
|
+
}
|
|
41
|
+
})
|
|
37
42
|
});
|
|
38
43
|
}
|
|
39
44
|
});
|
|
@@ -210,4 +215,81 @@ describe('Offer', function() {
|
|
|
210
215
|
expect(htmlSelectElement.innerText).toEqual('Buy one time\n2 weeks (recomended)\n1 month');
|
|
211
216
|
});
|
|
212
217
|
});
|
|
218
|
+
|
|
219
|
+
describe('optin default frequency tests', function() {
|
|
220
|
+
let element;
|
|
221
|
+
beforeEach(async () => {
|
|
222
|
+
og.offers.clear();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('it should opt in with default frequency', async () => {
|
|
226
|
+
fetch.and.returnValue(mockOfferResponse(productIdInStock));
|
|
227
|
+
document.body.innerHTML = `
|
|
228
|
+
<og-offer product="${productIdInStock}">
|
|
229
|
+
<og-optout-button></og-optout-button>
|
|
230
|
+
<og-optin-button></og-optin-button>
|
|
231
|
+
<og-select-frequency default-text=" (recomended)">
|
|
232
|
+
<option value="optedOut">Buy one time</option>
|
|
233
|
+
<option value="2_2" selected="selected">2 weeks</option>
|
|
234
|
+
<option value="1_3">1 month </option>
|
|
235
|
+
</og-select-frequency>
|
|
236
|
+
</og-offer>
|
|
237
|
+
`;
|
|
238
|
+
element = document.querySelector('og-offer');
|
|
239
|
+
await new Promise(r => setTimeout(r, 10));
|
|
240
|
+
|
|
241
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
242
|
+
|
|
243
|
+
expect(og.offers.store.getState().optedin).toEqual([]);
|
|
244
|
+
|
|
245
|
+
document.querySelector('og-optin-button').click();
|
|
246
|
+
await element.updateComplete;
|
|
247
|
+
expect(og.offers.store.getState().optedin).toEqual([{ id: productIdInStock, frequency: '2_2' }]);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('it should opt in with PSDF', async () => {
|
|
251
|
+
fetch.and.returnValue(mockOfferResponse(productIdInStock, true, true, { every: '5', period: '1' }));
|
|
252
|
+
|
|
253
|
+
document.body.innerHTML = `
|
|
254
|
+
<og-offer product="${productIdInStock}">
|
|
255
|
+
<og-optout-button></og-optout-button>
|
|
256
|
+
<og-optin-button></og-optin-button>
|
|
257
|
+
<og-select-frequency default-text=" (recomended)">
|
|
258
|
+
<option value="optedOut">Buy one time</option>
|
|
259
|
+
<option value="2_2" selected="selected">2 weeks</option>
|
|
260
|
+
<option value="1_3">1 month </option>
|
|
261
|
+
<option value="5_1">5 days </option>
|
|
262
|
+
</og-select-frequency>
|
|
263
|
+
</og-offer>
|
|
264
|
+
`;
|
|
265
|
+
element = document.querySelector('og-offer');
|
|
266
|
+
await new Promise(r => setTimeout(r, 10));
|
|
267
|
+
|
|
268
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
269
|
+
|
|
270
|
+
expect(og.offers.store.getState().optedin).toEqual([]);
|
|
271
|
+
|
|
272
|
+
document.querySelector('og-optin-button').click();
|
|
273
|
+
await element.updateComplete;
|
|
274
|
+
expect(og.offers.store.getState().optedin).toEqual([{ id: productIdInStock, frequency: '5_1' }]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('buttons only template should use attr frequency as first option', async () => {
|
|
278
|
+
fetch.and.returnValue(mockOfferResponse(productIdInStock, true, true, { every: '5', period: '1' }));
|
|
279
|
+
document.body.innerHTML = `
|
|
280
|
+
<og-offer product="${productIdInStock}" default-frequency="6_1">
|
|
281
|
+
<og-optin-button default-frequency="6_1">Six days</og-optin-button>
|
|
282
|
+
<og-optin-button default-frequency="13_1">thirteen days</og-optin-button>
|
|
283
|
+
<og-optin-button default-frequency="3_2">Three weeks</og-optin-button>
|
|
284
|
+
</og-offer>
|
|
285
|
+
`;
|
|
286
|
+
element = document.querySelector('og-offer');
|
|
287
|
+
await new Promise(r => setTimeout(r, 10));
|
|
288
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
289
|
+
expect(og.offers.store.getState().optedin).toEqual([]);
|
|
290
|
+
document.querySelector('[default-frequency="13_1"]').click();
|
|
291
|
+
await element.updateComplete;
|
|
292
|
+
expect(og.offers.store.getState().optedin).toEqual([{ id: productIdInStock, frequency: '13_1' }]);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
213
295
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { frequencyToSellingPlan } from '../utils';
|
|
2
|
+
|
|
3
|
+
describe('frequencyToSellingPlan', () => {
|
|
4
|
+
it('should return a selling plan from config', () => {
|
|
5
|
+
expect(
|
|
6
|
+
frequencyToSellingPlan('1_2', {
|
|
7
|
+
frequenciesEveryPeriod: ['1_1', '1_2', '1_3'],
|
|
8
|
+
frequencies: [123, 456, 789]
|
|
9
|
+
})
|
|
10
|
+
).toEqual(456);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return same input if it does not looks like a frequency ', () => {
|
|
14
|
+
expect(frequencyToSellingPlan(123, {})).toEqual(123);
|
|
15
|
+
expect(frequencyToSellingPlan('345', {})).toEqual('345');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return original frequency if no frequency match', () => {
|
|
19
|
+
expect(
|
|
20
|
+
frequencyToSellingPlan('1_2', {
|
|
21
|
+
frequenciesEveryPeriod: ['1_1', '1_3'],
|
|
22
|
+
frequencies: [123, 789]
|
|
23
|
+
})
|
|
24
|
+
).toEqual('1_2');
|
|
25
|
+
});
|
|
26
|
+
});
|
package/src/core/selectors.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createSelector } from 'reselect';
|
|
2
2
|
import memoize from 'lodash.memoize';
|
|
3
3
|
import { stringifyFrequency } from './api';
|
|
4
|
+
import platform from '../platform';
|
|
5
|
+
import { mapFrequencyToSellingPlan, safeProductId } from '../core/utils';
|
|
4
6
|
|
|
5
7
|
memoize.Cache = Map;
|
|
6
8
|
|
|
@@ -21,6 +23,12 @@ function arraysEqual(a, b) {
|
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
function resolveFrequency(sellingPlans, frequenciesEveryPeriod, frequency) {
|
|
27
|
+
const ogFrequency = stringifyFrequency(frequency);
|
|
28
|
+
if (!platform.shopify_selling_plans) return ogFrequency;
|
|
29
|
+
return mapFrequencyToSellingPlan(sellingPlans, frequenciesEveryPeriod, ogFrequency);
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
export const isSameProduct = (a, b) => {
|
|
25
33
|
if (a === b) return true;
|
|
26
34
|
if (typeof a === 'object' && typeof b === 'object' && a && b) {
|
|
@@ -48,6 +56,10 @@ export const autoshipSelector = state => state.autoshipByDefault || {};
|
|
|
48
56
|
|
|
49
57
|
export const defaultFrequenciesSelector = state => state.defaultFrequencies || {};
|
|
50
58
|
|
|
59
|
+
export const sellingPlansSelector = state => state?.config?.frequencies || [];
|
|
60
|
+
|
|
61
|
+
export const frequenciesEveryPeriodSelector = state => state?.config?.frequenciesEveryPeriod || [];
|
|
62
|
+
|
|
51
63
|
/**
|
|
52
64
|
* Creates a function with state arguments that return the true when
|
|
53
65
|
* productId is in the optedin array or not in optedout or autoship by default
|
|
@@ -110,7 +122,12 @@ export const makeProductFrequencySelector = memoize(productId =>
|
|
|
110
122
|
export const makeProductDefaultFrequencySelector = memoize(productId =>
|
|
111
123
|
createSelector(
|
|
112
124
|
defaultFrequenciesSelector,
|
|
113
|
-
|
|
125
|
+
sellingPlansSelector,
|
|
126
|
+
frequenciesEveryPeriodSelector,
|
|
127
|
+
(defaultFrequencies, sellingPlans, frequenciesEveryPeriod) =>
|
|
128
|
+
(defaultFrequencies[safeProductId(productId)] &&
|
|
129
|
+
resolveFrequency(sellingPlans, frequenciesEveryPeriod, defaultFrequencies[safeProductId(productId)])) ||
|
|
130
|
+
null
|
|
114
131
|
)
|
|
115
132
|
);
|
|
116
133
|
|
package/src/core/store.js
CHANGED
|
@@ -3,6 +3,7 @@ import thunk from 'redux-thunk';
|
|
|
3
3
|
|
|
4
4
|
import { loadState } from './localStorage';
|
|
5
5
|
import { dispatchMiddleware, localStorageMiddleware, offerEvents } from './middleware';
|
|
6
|
+
import { waitUntilOffersReady } from './waitUntilOffersReady';
|
|
6
7
|
|
|
7
8
|
export function makeStore(reducer, ...extraMiddlewares) {
|
|
8
9
|
if (window.og && window.og.store) return window.og.store;
|
|
@@ -14,11 +15,11 @@ export function makeStore(reducer, ...extraMiddlewares) {
|
|
|
14
15
|
typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
|
15
16
|
? // eslint-disable-next-line no-underscore-dangle
|
|
16
17
|
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
|
17
|
-
|
|
18
|
+
name: 'Ordergroove Offers'
|
|
18
19
|
})
|
|
19
20
|
: compose;
|
|
20
21
|
|
|
21
|
-
const middlewares = [thunk, dispatchMiddleware, offerEvents];
|
|
22
|
+
const middlewares = [waitUntilOffersReady, thunk, dispatchMiddleware, offerEvents];
|
|
22
23
|
|
|
23
24
|
let initial = {};
|
|
24
25
|
|
package/src/core/utils.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function resolveEnvAndMerchant() {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export const safeProductId = product => {
|
|
39
|
-
if (!product
|
|
39
|
+
if (!product) return '';
|
|
40
40
|
let productId = `${product.id || product}`;
|
|
41
41
|
if (platform?.shopify_selling_plans) {
|
|
42
42
|
// we can't avoid make offer request since we need to know the upsell group and autoship by default
|
|
@@ -49,20 +49,29 @@ export const safeProductId = product => {
|
|
|
49
49
|
/**
|
|
50
50
|
* Returns the OG frequency if platform is running on selling plans
|
|
51
51
|
* @param initialFrequency
|
|
52
|
-
* @param config
|
|
52
|
+
* @param config
|
|
53
53
|
*/
|
|
54
|
-
|
|
54
|
+
export const safeOgFrequency = (initialFrequency, config) => {
|
|
55
55
|
if (platform.shopify_selling_plans) {
|
|
56
56
|
const ix = config.frequencies?.indexOf(initialFrequency);
|
|
57
57
|
if (ix >= 0 && config.frequenciesEveryPeriod[ix]) {
|
|
58
58
|
return config.frequenciesEveryPeriod[ix];
|
|
59
|
-
} else {
|
|
60
|
-
throw `OG Can't convert selling_plan_id ${initialFrequency} to OG frequency every period`;
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
return initialFrequency;
|
|
64
62
|
};
|
|
65
63
|
|
|
64
|
+
export const frequencyToSellingPlan = (ogFrequency, config) => {
|
|
65
|
+
// og frequency contains underscore
|
|
66
|
+
if (!`${ogFrequency}`.includes('_')) return ogFrequency;
|
|
67
|
+
const ix = config.frequenciesEveryPeriod?.indexOf(ogFrequency);
|
|
68
|
+
if (ix >= 0 && config.frequenciesEveryPeriod[ix]) {
|
|
69
|
+
return config.frequencies[ix];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return ogFrequency;
|
|
73
|
+
};
|
|
74
|
+
|
|
66
75
|
/**
|
|
67
76
|
* Attempts to auto initialize the offer library reading the merchantId and env from
|
|
68
77
|
* integration script i.e. <script src="http://static.ordergroove...."/>.
|
|
@@ -70,9 +79,19 @@ export const safeProductId = product => {
|
|
|
70
79
|
*/
|
|
71
80
|
export function autoInitializeOffers(offers) {
|
|
72
81
|
if (offers.isReady()) return;
|
|
82
|
+
|
|
83
|
+
console.info('OG offers are auto initializing');
|
|
84
|
+
|
|
73
85
|
const [merchantId, env] = resolveEnvAndMerchant();
|
|
74
86
|
if (!env && !merchantId) return;
|
|
75
|
-
|
|
87
|
+
const script = document.createElement('script');
|
|
88
|
+
script.onload = () => console.info('OG pull initialization chunk for merchant', merchantId, env);
|
|
89
|
+
script.onerror = () => offers.initialize(merchantId, env);
|
|
90
|
+
script.src = `${window.location.protocol}//${
|
|
91
|
+
env === ENV_PROD ? STATIC_HOST : STAGING_STATIC_HOST
|
|
92
|
+
}/${merchantId}/main.js?initOnly=true`;
|
|
93
|
+
|
|
94
|
+
document.head.appendChild(script);
|
|
76
95
|
}
|
|
77
96
|
|
|
78
97
|
export const clearCookie = cookieId => {
|
|
@@ -84,3 +103,23 @@ export function getCookieValue(cookieId) {
|
|
|
84
103
|
const cookie = document.cookie.match(`(^|;) ?${cookieId}=([^;]*)(;|$)`);
|
|
85
104
|
return cookie ? cookie[2] : null;
|
|
86
105
|
}
|
|
106
|
+
export const isOgFrequency = frequency => !!(frequency && frequency?.includes('_'));
|
|
107
|
+
|
|
108
|
+
export const getFirstSellingPlan = (frequencies = []) => frequencies?.[0] || null;
|
|
109
|
+
|
|
110
|
+
export const hasShopifySellingPlans = (sellingPlans = [], frequenciesEveryPeriod = []) =>
|
|
111
|
+
!!(platform?.shopify_selling_plans && sellingPlans.length && frequenciesEveryPeriod.length);
|
|
112
|
+
|
|
113
|
+
export const mapFrequencyToSellingPlan = (sellingPlans, frequenciesEveryPeriod, frequency) => {
|
|
114
|
+
if (sellingPlans.length !== frequenciesEveryPeriod.length) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const index = frequenciesEveryPeriod.findIndex(it => it === frequency);
|
|
119
|
+
|
|
120
|
+
if (index >= 0) {
|
|
121
|
+
return sellingPlans[index];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createSessionId, fetchAuth } from './actions';
|
|
2
|
+
import {
|
|
3
|
+
CREATED_SESSION_ID,
|
|
4
|
+
SET_ENVIRONMENT_DEV,
|
|
5
|
+
SET_ENVIRONMENT_PROD,
|
|
6
|
+
SET_ENVIRONMENT_STAGING,
|
|
7
|
+
SET_MERCHANT_ID
|
|
8
|
+
} from './constants';
|
|
9
|
+
import { listenLocalStorageChanges } from './localStorage';
|
|
10
|
+
|
|
11
|
+
const waitFor = () => {
|
|
12
|
+
let resolve, reject;
|
|
13
|
+
return [
|
|
14
|
+
new Promise((yes, no) => {
|
|
15
|
+
resolve = yes;
|
|
16
|
+
reject = no;
|
|
17
|
+
}),
|
|
18
|
+
resolve,
|
|
19
|
+
reject
|
|
20
|
+
];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Middleware that awaits offers being initialized and resumes the regular actions after.
|
|
24
|
+
* @param {*} store
|
|
25
|
+
* @returns
|
|
26
|
+
*/
|
|
27
|
+
export function waitUntilOffersReady(store) {
|
|
28
|
+
const [waitForSetEnv, resolveEnv] = waitFor();
|
|
29
|
+
const [waitForMerchantId, resolveMerchantId] = waitFor();
|
|
30
|
+
const [waitForSessionId, resolveSessionId] = waitFor();
|
|
31
|
+
|
|
32
|
+
waitForMerchantId.then(merchantId => {
|
|
33
|
+
const { sessionId } = store.getState();
|
|
34
|
+
if (!sessionId || (merchantId && !sessionId.startsWith(merchantId))) {
|
|
35
|
+
store.dispatch(createSessionId(merchantId));
|
|
36
|
+
} else {
|
|
37
|
+
resolveSessionId(sessionId);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const waitForReady = Promise.all([waitForMerchantId, waitForSetEnv, waitForSessionId]);
|
|
42
|
+
|
|
43
|
+
waitForReady.then(() => {
|
|
44
|
+
window.addEventListener('storage', listenLocalStorageChanges(store));
|
|
45
|
+
if (!store.getState().auth?.ts) {
|
|
46
|
+
store.dispatch(fetchAuth());
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return next => async action => {
|
|
51
|
+
if (
|
|
52
|
+
SET_ENVIRONMENT_DEV === action.type ||
|
|
53
|
+
SET_ENVIRONMENT_STAGING === action.type ||
|
|
54
|
+
SET_ENVIRONMENT_PROD === action.type
|
|
55
|
+
) {
|
|
56
|
+
resolveEnv(action.payload);
|
|
57
|
+
} else if (SET_MERCHANT_ID === action.type) {
|
|
58
|
+
resolveMerchantId(action.payload);
|
|
59
|
+
} else if (CREATED_SESSION_ID === action.type) {
|
|
60
|
+
resolveSessionId(action.payload);
|
|
61
|
+
} else {
|
|
62
|
+
await waitForReady;
|
|
63
|
+
}
|
|
64
|
+
next(action);
|
|
65
|
+
};
|
|
66
|
+
}
|
package/src/index.js
CHANGED
|
@@ -34,15 +34,19 @@ export const setTemplates = offers.setTemplates;
|
|
|
34
34
|
export const setupCart = offers.setupCart;
|
|
35
35
|
export const setupProduct = offers.setupProduct;
|
|
36
36
|
export const setupProducts = offers.setupProducts;
|
|
37
|
+
export const autoInit = () => autoInitializeOffers(offers);
|
|
38
|
+
|
|
37
39
|
export { platform };
|
|
38
40
|
export default offers.initialize;
|
|
39
|
-
|
|
40
41
|
/*
|
|
41
42
|
* Attempts to auto initialize the offer library reading the merchantId and env from
|
|
42
43
|
* integration script i.e. <script src="http://static.ordergroove...."/>.
|
|
43
44
|
* Useful when local develop using http redirects
|
|
44
45
|
*/
|
|
45
|
-
|
|
46
|
+
if (process.env.NODE_ENV === 'development') {
|
|
47
|
+
console.info('%c Ordergroove Offers DEV MODE ', 'background: #222; color: #bada55');
|
|
48
|
+
onReady(autoInit);
|
|
49
|
+
}
|
|
46
50
|
|
|
47
51
|
if (platform?.shopify_selling_plans) {
|
|
48
52
|
onReady(() => authorizeShopifyCustomer(offers));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { STATIC_HOST } from './core/constants';
|
|
2
|
+
|
|
3
|
+
const mainJs = document.createElement('script');
|
|
4
|
+
mainJs.setAttribute('id', `mock-main-js`);
|
|
5
|
+
mainJs.setAttribute('src', `http://${STATIC_HOST}`);
|
|
6
|
+
mainJs.dataset.shopifySellingPlans = true;
|
|
7
|
+
document.head.appendChild(mainJs);
|
package/src/make-api.js
CHANGED
|
@@ -156,7 +156,11 @@ export default function makeApi(store) {
|
|
|
156
156
|
*/
|
|
157
157
|
initialize(merchantId, env, authUrl) {
|
|
158
158
|
// settings resolves once, before anything.
|
|
159
|
-
if (
|
|
159
|
+
if (isReady) {
|
|
160
|
+
console.warn('og.offers has been initialized already. Skipping.');
|
|
161
|
+
} else {
|
|
162
|
+
offers.resolveSettings(merchantId, env, window.og_settings, store);
|
|
163
|
+
}
|
|
160
164
|
|
|
161
165
|
const state = store.getState();
|
|
162
166
|
// dont re-trigger actions if value is the same. allowing set new authurl only
|
|
@@ -165,20 +169,6 @@ export default function makeApi(store) {
|
|
|
165
169
|
// allow set new authUrl
|
|
166
170
|
if (authUrl) offers.setAuthUrl(authUrl);
|
|
167
171
|
|
|
168
|
-
if (isReady) {
|
|
169
|
-
console.warn('og.offers has been initialized already. Skipping.');
|
|
170
|
-
} else {
|
|
171
|
-
window.addEventListener('storage', listenLocalStorageChanges(store));
|
|
172
|
-
store.dispatch(actions.requestSessionId());
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (arguments.length === 3) {
|
|
176
|
-
// fetch auth is not there already
|
|
177
|
-
if (!store.getState().auth?.ts) {
|
|
178
|
-
store.dispatch(actions.fetchAuth());
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
172
|
isReady = true;
|
|
183
173
|
|
|
184
174
|
return this;
|