@shopgate/pwa-tracking 7.30.0-alpha.7 → 7.30.0-alpha.8
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/helpers/index.js +412 -15
- package/package.json +5 -5
- package/selectors/cart.js +30 -5
- package/selectors/category.js +25 -2
- package/selectors/favorites.js +7 -2
- package/selectors/index.js +29 -3
- package/selectors/page.js +46 -8
- package/selectors/product.js +30 -4
- package/selectors/search.js +34 -4
- package/selectors/user.js +31 -2
- package/spec.js +6 -1
- package/streams/app.js +9 -3
- package/streams/cart.js +55 -12
- package/streams/category.js +72 -11
- package/streams/checkout.js +26 -5
- package/streams/page.js +49 -7
- package/streams/pages.js +33 -6
- package/streams/product.js +33 -7
- package/streams/scanner.js +43 -2
- package/streams/search.js +44 -7
- package/streams/user.js +13 -4
- package/subscriptions/cart.js +23 -3
- package/subscriptions/checkout.js +71 -3
- package/subscriptions/deeplinkPush.js +70 -5
- package/subscriptions/favorites.js +39 -6
- package/subscriptions/pages.js +28 -3
- package/subscriptions/product.js +40 -4
- package/subscriptions/scanner.js +94 -3
- package/subscriptions/search.js +17 -2
- package/subscriptions/setup.js +113 -10
- package/subscriptions/user.js +27 -3
package/helpers/index.js
CHANGED
|
@@ -1,55 +1,452 @@
|
|
|
1
|
-
|
|
1
|
+
import "core-js/modules/web.url.js";
|
|
2
|
+
import "core-js/modules/web.url.to-json.js";
|
|
3
|
+
import "core-js/modules/web.url-search-params.js";
|
|
4
|
+
import get from 'lodash/get';
|
|
5
|
+
import find from 'lodash/find';
|
|
6
|
+
import queryString from 'query-string';
|
|
7
|
+
import { logger } from '@shopgate/pwa-core/helpers';
|
|
8
|
+
import { QR_CODE_TYPE_HOMEPAGE, QR_CODE_TYPE_PRODUCT, QR_CODE_TYPE_PRODUCT_WITH_COUPON, QR_CODE_TYPE_COUPON, QR_CODE_TYPE_CATEGORY, QR_CODE_TYPE_SEARCH, QR_CODE_TYPE_PAGE, SCANNER_FORMATS_BARCODE, SCANNER_FORMATS_QR_CODE } from '@shopgate/pwa-common-commerce/scanner/constants';
|
|
9
|
+
import { parse2dsQrCode } from '@shopgate/pwa-common-commerce/scanner/helpers';
|
|
10
|
+
import core from '@shopgate/tracking-core/core/Core';
|
|
11
|
+
import appConfig, { shopNumber } from '@shopgate/pwa-common/helpers/config';
|
|
12
|
+
import { i18n } from '@shopgate/engage/core';
|
|
13
|
+
|
|
14
|
+
/**
|
|
2
15
|
* Converts a price to a formatted string.
|
|
3
16
|
* @param {number} price The original price.
|
|
4
17
|
* @return {string|*} The converted price or the original value, if the price was not convertible.
|
|
5
|
-
*/
|
|
18
|
+
*/
|
|
19
|
+
export const convertPriceToString = price => {
|
|
20
|
+
if (typeof price === 'number') {
|
|
21
|
+
return price.toFixed(2);
|
|
22
|
+
}
|
|
23
|
+
return price;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
6
27
|
* Re-format a given product from the store.
|
|
7
28
|
* @param {Object} productData The product data from the store
|
|
8
29
|
* @returns {Object|null} The formatted product.
|
|
9
|
-
*/
|
|
30
|
+
*/
|
|
31
|
+
export const formatProductData = productData => {
|
|
32
|
+
if (!productData) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const {
|
|
36
|
+
id,
|
|
37
|
+
name,
|
|
38
|
+
price,
|
|
39
|
+
manufacturer,
|
|
40
|
+
tags = [],
|
|
41
|
+
identifiers = {}
|
|
42
|
+
} = productData;
|
|
43
|
+
const uid = appConfig.tracking.useSkuAsProductId && identifiers.sku ? identifiers.sku : id;
|
|
44
|
+
return {
|
|
45
|
+
name,
|
|
46
|
+
manufacturer,
|
|
47
|
+
tags,
|
|
48
|
+
uid,
|
|
49
|
+
amount: {
|
|
50
|
+
net: convertPriceToString(price.unitPriceNet),
|
|
51
|
+
gross: convertPriceToString(price.unitPriceWithTax),
|
|
52
|
+
striked: convertPriceToString(price.unitPriceStriked),
|
|
53
|
+
currency: price.currency
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
10
59
|
* Reformat product data for addToCart from the store to the format our core expects.
|
|
11
60
|
* @param {Object} product Product from the store
|
|
12
61
|
* @param {Object} quantity Quantity of the product
|
|
13
62
|
* @return {Object}
|
|
14
|
-
*/
|
|
63
|
+
*/
|
|
64
|
+
export const formatAddToCartProductData = ({
|
|
65
|
+
product,
|
|
66
|
+
quantity
|
|
67
|
+
}) => ({
|
|
68
|
+
...formatProductData(product),
|
|
69
|
+
quantity
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
15
73
|
* Reformat product data from the store to the format our core expects.
|
|
16
74
|
* @param {Object} product Product from the store
|
|
17
75
|
* @param {Object} quantity Quantity of the product
|
|
18
76
|
* @return {Object}
|
|
19
|
-
*/
|
|
77
|
+
*/
|
|
78
|
+
export const formatCartProductData = ({
|
|
79
|
+
product,
|
|
80
|
+
quantity
|
|
81
|
+
}) => ({
|
|
82
|
+
uid: product.id,
|
|
83
|
+
name: product.name,
|
|
84
|
+
amount: {
|
|
85
|
+
gross: convertPriceToString(product.price.unit)
|
|
86
|
+
},
|
|
87
|
+
quantity
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/**
|
|
20
91
|
* Reformat order data from web checkout to the format our core expects.
|
|
21
92
|
* @param {Object} passedOrder Information about the order.
|
|
22
93
|
* @return {Object}
|
|
23
|
-
*/
|
|
24
|
-
|
|
94
|
+
*/
|
|
95
|
+
export const formatPurchaseData = passedOrder => {
|
|
96
|
+
// Return the passedOrder if the format is already correct
|
|
97
|
+
if (!passedOrder.totals && passedOrder.amount) {
|
|
98
|
+
return {
|
|
99
|
+
order: passedOrder
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const defaults = {
|
|
103
|
+
totals: [],
|
|
104
|
+
products: [],
|
|
105
|
+
number: '',
|
|
106
|
+
currency: ''
|
|
107
|
+
};
|
|
108
|
+
const order = {
|
|
109
|
+
...defaults,
|
|
110
|
+
...passedOrder
|
|
111
|
+
};
|
|
112
|
+
const {
|
|
113
|
+
amount: grandTotal = 0
|
|
114
|
+
} = find(order.totals, {
|
|
115
|
+
type: 'grandTotal'
|
|
116
|
+
}) || {};
|
|
117
|
+
const {
|
|
118
|
+
amount: shipping = 0
|
|
119
|
+
} = find(order.totals, {
|
|
120
|
+
type: 'shipping'
|
|
121
|
+
}) || {};
|
|
122
|
+
const {
|
|
123
|
+
amount: tax = 0
|
|
124
|
+
} = find(order.totals, {
|
|
125
|
+
type: 'tax'
|
|
126
|
+
}) || {};
|
|
127
|
+
const grandTotalNet = grandTotal - tax;
|
|
128
|
+
const products = order.products.map(product => ({
|
|
129
|
+
uid: product.id || '',
|
|
130
|
+
productNumber: product.id || '',
|
|
131
|
+
name: product.name || '',
|
|
132
|
+
quantity: product.quantity || 1,
|
|
133
|
+
amount: {
|
|
134
|
+
currency: order.currency,
|
|
135
|
+
gross: convertPriceToString(get(product, 'price.withTax', 0)),
|
|
136
|
+
net: convertPriceToString(get(product, 'price.net', 0))
|
|
137
|
+
}
|
|
138
|
+
}));
|
|
139
|
+
return {
|
|
140
|
+
shop: {
|
|
141
|
+
name: ''
|
|
142
|
+
},
|
|
143
|
+
order: {
|
|
144
|
+
number: order.number,
|
|
145
|
+
amount: {
|
|
146
|
+
currency: order.currency,
|
|
147
|
+
gross: convertPriceToString(grandTotal),
|
|
148
|
+
net: convertPriceToString(grandTotalNet),
|
|
149
|
+
tax: convertPriceToString(tax)
|
|
150
|
+
},
|
|
151
|
+
shipping: {
|
|
152
|
+
amount: {
|
|
153
|
+
gross: convertPriceToString(shipping),
|
|
154
|
+
net: convertPriceToString(shipping)
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
products,
|
|
158
|
+
shippingAddress: {
|
|
159
|
+
city: '',
|
|
160
|
+
country: ''
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
25
167
|
* Reformat order data from native checkout to the format our core expects.
|
|
26
168
|
* @param {Object} order Information about the order.
|
|
27
169
|
* @return {Object}
|
|
28
|
-
*/
|
|
170
|
+
*/
|
|
171
|
+
export const formatNativeCheckoutPurchaseData = (order = {}) => {
|
|
172
|
+
const {
|
|
173
|
+
orderNumber,
|
|
174
|
+
total,
|
|
175
|
+
taxAmount,
|
|
176
|
+
shippingTotal,
|
|
177
|
+
currencyCode,
|
|
178
|
+
lineItems = []
|
|
179
|
+
} = order;
|
|
180
|
+
const products = lineItems.map(({
|
|
181
|
+
quantity,
|
|
182
|
+
currencyCode: itemCurrencyCode,
|
|
183
|
+
taxAmount: itemTaxAmount,
|
|
184
|
+
price,
|
|
185
|
+
product = {}
|
|
186
|
+
}) => ({
|
|
187
|
+
uid: product?.code || '',
|
|
188
|
+
productNumber: product?.code || '',
|
|
189
|
+
name: product?.name || '',
|
|
190
|
+
quantity: quantity || 1,
|
|
191
|
+
amount: {
|
|
192
|
+
currency: itemCurrencyCode,
|
|
193
|
+
gross: convertPriceToString(price),
|
|
194
|
+
net: convertPriceToString(price - itemTaxAmount)
|
|
195
|
+
}
|
|
196
|
+
}));
|
|
197
|
+
return {
|
|
198
|
+
shop: {
|
|
199
|
+
name: ''
|
|
200
|
+
},
|
|
201
|
+
order: {
|
|
202
|
+
number: orderNumber,
|
|
203
|
+
amount: {
|
|
204
|
+
currency: currencyCode,
|
|
205
|
+
gross: convertPriceToString(total),
|
|
206
|
+
net: convertPriceToString(total - taxAmount),
|
|
207
|
+
tax: convertPriceToString(taxAmount)
|
|
208
|
+
},
|
|
209
|
+
shipping: {
|
|
210
|
+
amount: {
|
|
211
|
+
gross: convertPriceToString(shippingTotal),
|
|
212
|
+
net: convertPriceToString(shippingTotal)
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
products,
|
|
216
|
+
shippingAddress: {
|
|
217
|
+
city: '',
|
|
218
|
+
country: ''
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
29
225
|
* Creates data for the scanner tracking events.
|
|
30
226
|
* @param {Object} params params
|
|
31
227
|
* @return {Object}
|
|
32
|
-
*/
|
|
228
|
+
*/
|
|
229
|
+
export const createScannerEventData = ({
|
|
230
|
+
event,
|
|
231
|
+
format,
|
|
232
|
+
payload,
|
|
233
|
+
userInteraction
|
|
234
|
+
}) => {
|
|
235
|
+
let eventLabel = [];
|
|
236
|
+
if (payload) {
|
|
237
|
+
eventLabel = [format];
|
|
238
|
+
if (SCANNER_FORMATS_QR_CODE.includes(format)) {
|
|
239
|
+
const parsedPayload = parse2dsQrCode(payload);
|
|
240
|
+
if (parsedPayload) {
|
|
241
|
+
const {
|
|
242
|
+
type,
|
|
243
|
+
data
|
|
244
|
+
} = parsedPayload;
|
|
245
|
+
switch (type) {
|
|
246
|
+
case QR_CODE_TYPE_HOMEPAGE:
|
|
247
|
+
eventLabel.push('main');
|
|
248
|
+
break;
|
|
249
|
+
case QR_CODE_TYPE_PRODUCT:
|
|
250
|
+
eventLabel.push('product');
|
|
251
|
+
eventLabel.push(data.productId);
|
|
252
|
+
break;
|
|
253
|
+
case QR_CODE_TYPE_PRODUCT_WITH_COUPON:
|
|
254
|
+
eventLabel.push('productcoupon');
|
|
255
|
+
eventLabel.push(`${data.productId}_${data.couponCode}`);
|
|
256
|
+
break;
|
|
257
|
+
case QR_CODE_TYPE_COUPON:
|
|
258
|
+
eventLabel.push('coupon');
|
|
259
|
+
eventLabel.push(data.couponCode);
|
|
260
|
+
break;
|
|
261
|
+
case QR_CODE_TYPE_CATEGORY:
|
|
262
|
+
eventLabel.push('category');
|
|
263
|
+
eventLabel.push(data.categoryId);
|
|
264
|
+
break;
|
|
265
|
+
case QR_CODE_TYPE_SEARCH:
|
|
266
|
+
eventLabel.push('search');
|
|
267
|
+
eventLabel.push(data.searchPhrase);
|
|
268
|
+
break;
|
|
269
|
+
case QR_CODE_TYPE_PAGE:
|
|
270
|
+
eventLabel.push('page');
|
|
271
|
+
eventLabel.push(data.pageId);
|
|
272
|
+
break;
|
|
273
|
+
default:
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} else if (SCANNER_FORMATS_BARCODE.includes(format)) {
|
|
278
|
+
if (payload) {
|
|
279
|
+
eventLabel.push(payload);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
eventLabel = eventLabel.join(' - ');
|
|
284
|
+
return {
|
|
285
|
+
eventAction: event,
|
|
286
|
+
...(eventLabel && {
|
|
287
|
+
eventLabel
|
|
288
|
+
}),
|
|
289
|
+
...(typeof userInteraction === 'boolean' && {
|
|
290
|
+
userInteraction
|
|
291
|
+
})
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/**
|
|
33
296
|
* Creates data for the scanner utm url.
|
|
34
297
|
* @param {Object} params params
|
|
35
298
|
* @return {Object}
|
|
36
|
-
*/
|
|
37
|
-
|
|
299
|
+
*/
|
|
300
|
+
export const buildScannerUtmUrl = ({
|
|
301
|
+
scannerRoute,
|
|
302
|
+
format,
|
|
303
|
+
payload,
|
|
304
|
+
referer
|
|
305
|
+
}) => {
|
|
306
|
+
const source = 'shopgate';
|
|
307
|
+
let medium = 'scanner';
|
|
308
|
+
let campaign = `${shopNumber}Scanner`;
|
|
309
|
+
let term = '';
|
|
310
|
+
if (SCANNER_FORMATS_BARCODE.includes(format)) {
|
|
311
|
+
medium = 'barcode_scanner';
|
|
312
|
+
campaign = `${shopNumber}BarcodeScan`;
|
|
313
|
+
term = payload;
|
|
314
|
+
} else if (SCANNER_FORMATS_QR_CODE.includes(format)) {
|
|
315
|
+
medium = 'qrcode_scanner';
|
|
316
|
+
campaign = `${shopNumber}QRScan`;
|
|
317
|
+
const {
|
|
318
|
+
type,
|
|
319
|
+
data
|
|
320
|
+
} = parse2dsQrCode(payload) || {};
|
|
321
|
+
if (type === QR_CODE_TYPE_SEARCH) {
|
|
322
|
+
term = data.searchPhrase;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
let parsedPayload;
|
|
326
|
+
let utmContent;
|
|
327
|
+
try {
|
|
328
|
+
parsedPayload = queryString.parseUrl(payload);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
// noting to do here
|
|
331
|
+
}
|
|
332
|
+
if (parsedPayload && parsedPayload.query) {
|
|
333
|
+
if (parsedPayload.query.utm_content) {
|
|
334
|
+
utmContent = parsedPayload.query.utm_content;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const {
|
|
338
|
+
location
|
|
339
|
+
} = scannerRoute;
|
|
340
|
+
const newPath = new URL(location, 'http://scanner.com');
|
|
341
|
+
/* eslint-disable camelcase */
|
|
342
|
+
const utms = {
|
|
343
|
+
utm_source: source,
|
|
344
|
+
utm_medium: medium,
|
|
345
|
+
utm_campaign: campaign,
|
|
346
|
+
utm_term: term,
|
|
347
|
+
utm_content: utmContent || referer
|
|
348
|
+
};
|
|
349
|
+
/* eslint-enable camelcase */
|
|
350
|
+
|
|
351
|
+
Object.keys(utms).forEach(utm => {
|
|
352
|
+
if (!newPath.searchParams.has(utm) && utms[utm]) {
|
|
353
|
+
newPath.searchParams.set(utm, utms[utm]);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
return `${newPath.pathname}${newPath.search}`;
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
38
360
|
* Creates tracking data for a category.
|
|
39
361
|
* @param {Object} category The category data from the store.
|
|
40
362
|
* @returns {Object|null}
|
|
41
|
-
*/
|
|
363
|
+
*/
|
|
364
|
+
export const createCategoryData = category => {
|
|
365
|
+
if (!category) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
const {
|
|
369
|
+
name,
|
|
370
|
+
id: uid,
|
|
371
|
+
path
|
|
372
|
+
} = category;
|
|
373
|
+
return {
|
|
374
|
+
uid,
|
|
375
|
+
name,
|
|
376
|
+
path
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
42
381
|
* Creates tracking data for the root category.
|
|
43
382
|
* @param {Object} rootCategory The category data from the store.
|
|
44
383
|
* @return {Object|null}
|
|
45
|
-
*/
|
|
384
|
+
*/
|
|
385
|
+
export const createRootCategoryData = rootCategory => {
|
|
386
|
+
if (!rootCategory) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
uid: null,
|
|
391
|
+
name: i18n.text('titles.categories'),
|
|
392
|
+
path: null
|
|
393
|
+
};
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
46
397
|
* Creates tracking data for the page view event.
|
|
47
398
|
* @param {Object} data The input data.
|
|
48
399
|
* @return {Object}
|
|
49
|
-
*/
|
|
400
|
+
*/
|
|
401
|
+
export const createPageviewData = ({
|
|
402
|
+
page = null,
|
|
403
|
+
cart = null,
|
|
404
|
+
favorites = null,
|
|
405
|
+
search = null,
|
|
406
|
+
category = null,
|
|
407
|
+
product = null,
|
|
408
|
+
pageConfig
|
|
409
|
+
}) => {
|
|
410
|
+
let title = '';
|
|
411
|
+
if (pageConfig) {
|
|
412
|
+
({
|
|
413
|
+
title
|
|
414
|
+
} = pageConfig);
|
|
415
|
+
} else if (category && category.name) {
|
|
416
|
+
title = category.name;
|
|
417
|
+
} else if (product && product.name) {
|
|
418
|
+
title = product.name;
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
page: page ? {
|
|
422
|
+
...page,
|
|
423
|
+
title
|
|
424
|
+
} : null,
|
|
425
|
+
cart,
|
|
426
|
+
favouriteList: {
|
|
427
|
+
products: favorites
|
|
428
|
+
},
|
|
429
|
+
search,
|
|
430
|
+
category,
|
|
431
|
+
product
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
50
435
|
* Helper to pass the redux state to the tracking core
|
|
51
436
|
* @param {string} eventName The name of the event.
|
|
52
437
|
* @param {Object} data The tracking data of the event.
|
|
53
438
|
* @param {Object} state The current redux state.
|
|
54
439
|
* @return {Core|boolean}
|
|
55
|
-
*/
|
|
440
|
+
*/
|
|
441
|
+
export const track = (eventName, data, state) => {
|
|
442
|
+
if (typeof core.track[eventName] !== 'function') {
|
|
443
|
+
logger.warn('Unknown tracking event:', eventName);
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
core.track[eventName](data, undefined, undefined, state);
|
|
448
|
+
} catch (e) {
|
|
449
|
+
logger.error(e);
|
|
450
|
+
}
|
|
451
|
+
return core;
|
|
452
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopgate/pwa-tracking",
|
|
3
|
-
"version": "7.30.0-alpha.
|
|
3
|
+
"version": "7.30.0-alpha.8",
|
|
4
4
|
"description": "Tracking library for the Shopgate Connect PWA.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Shopgate <support@shopgate.com>",
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
"connect"
|
|
16
16
|
],
|
|
17
17
|
"devDependencies": {
|
|
18
|
-
"@shopgate/pwa-common": "7.30.0-alpha.
|
|
19
|
-
"@shopgate/pwa-common-commerce": "7.30.0-alpha.
|
|
20
|
-
"@shopgate/pwa-core": "7.30.0-alpha.
|
|
21
|
-
"@shopgate/tracking-core": "7.30.0-alpha.
|
|
18
|
+
"@shopgate/pwa-common": "7.30.0-alpha.8",
|
|
19
|
+
"@shopgate/pwa-common-commerce": "7.30.0-alpha.8",
|
|
20
|
+
"@shopgate/pwa-core": "7.30.0-alpha.8",
|
|
21
|
+
"@shopgate/tracking-core": "7.30.0-alpha.8",
|
|
22
22
|
"reselect": "^4.1.8",
|
|
23
23
|
"rxjs": "~5.5.12"
|
|
24
24
|
}
|
package/selectors/cart.js
CHANGED
|
@@ -1,15 +1,40 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { getProduct } from '@shopgate/pwa-common-commerce/product/selectors/product';
|
|
3
|
+
import { getSubTotal, getCurrency, getCartProducts, getDiscountsAmount } from '@shopgate/pwa-common-commerce/cart/selectors/index';
|
|
4
|
+
import { convertPriceToString, formatCartProductData, formatAddToCartProductData } from "../helpers";
|
|
5
|
+
|
|
6
|
+
/**
|
|
2
7
|
* Selects the products from the cart and reformat them.
|
|
3
8
|
* @param {Object} state The current state.
|
|
4
9
|
* @return {Array} The reformatted products.
|
|
5
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
const getProducts = createSelector(getCartProducts, products => products.map(formatCartProductData));
|
|
12
|
+
|
|
13
|
+
/**
|
|
6
14
|
* Selects products by ID and reformat them.
|
|
7
15
|
* @param {Object} state The current state.
|
|
8
16
|
* @param {Array} products Array of products.
|
|
9
17
|
* @returns {Array} Formatted products.
|
|
10
|
-
*/
|
|
18
|
+
*/
|
|
19
|
+
export const getAddToCartProducts = (state, products) => products.map(product => ({
|
|
20
|
+
product: getProduct(state, {
|
|
21
|
+
productId: product.productId
|
|
22
|
+
}),
|
|
23
|
+
quantity: product.quantity
|
|
24
|
+
})).map(formatAddToCartProductData);
|
|
25
|
+
|
|
26
|
+
/**
|
|
11
27
|
* Selects the cart information.
|
|
12
28
|
* @param {Object} state The current state.
|
|
13
29
|
* @returns {Object} The cart information.
|
|
14
|
-
*/
|
|
15
|
-
|
|
30
|
+
*/
|
|
31
|
+
export default createSelector(getSubTotal, getDiscountsAmount, getCurrency, getProducts, (subTotal, discounts, currency, products) => ({
|
|
32
|
+
amount: {
|
|
33
|
+
gross: convertPriceToString(subTotal - discounts),
|
|
34
|
+
// TODO: Correct net prices are not possible atm.
|
|
35
|
+
net: convertPriceToString(subTotal - discounts),
|
|
36
|
+
currency
|
|
37
|
+
},
|
|
38
|
+
products,
|
|
39
|
+
productsCount: products.length
|
|
40
|
+
}));
|
package/selectors/category.js
CHANGED
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { hex2bin } from '@shopgate/engage/core/helpers';
|
|
3
|
+
import { makeGetRouteParam, makeGetRoutePattern } from '@shopgate/engage/core/selectors';
|
|
4
|
+
import { CATEGORY_PATTERN, ROOT_CATEGORY_PATTERN } from '@shopgate/engage/category/constants';
|
|
5
|
+
import { getRootCategories, getCategory } from '@shopgate/engage/category/selectors';
|
|
6
|
+
import { createCategoryData, createRootCategoryData } from "../helpers";
|
|
7
|
+
|
|
8
|
+
/**
|
|
2
9
|
* Creates a selector that retrieves a formatted category for the current route.
|
|
3
10
|
* @returns {Function}
|
|
4
|
-
*/
|
|
11
|
+
*/
|
|
12
|
+
export const makeGetRouteCategory = () => {
|
|
13
|
+
const getRoutePattern = makeGetRoutePattern();
|
|
14
|
+
const getCategoryIdRouteParam = makeGetRouteParam('categoryId');
|
|
15
|
+
return createSelector(state => state, getRoutePattern, getCategoryIdRouteParam, getRootCategories, (state, pattern, categoryId, rootCategories) => {
|
|
16
|
+
const decodedCategoryId = categoryId ? hex2bin(categoryId) : null;
|
|
17
|
+
if (pattern === ROOT_CATEGORY_PATTERN) {
|
|
18
|
+
return createRootCategoryData(rootCategories);
|
|
19
|
+
}
|
|
20
|
+
if (pattern === CATEGORY_PATTERN && decodedCategoryId) {
|
|
21
|
+
return createCategoryData(getCategory(state, {
|
|
22
|
+
categoryId: decodedCategoryId
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
});
|
|
27
|
+
};
|
package/selectors/favorites.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { getFavorites } from '@shopgate/pwa-common-commerce/favorites/selectors';
|
|
3
|
+
import { formatProductData } from "../helpers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
2
6
|
* Selects the favorites information.
|
|
3
7
|
* @param {Object} state The current state.
|
|
4
8
|
* @returns {Object} The favorites information.
|
|
5
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
export default createSelector(getFavorites, favorites => favorites.map(product => formatProductData(product)));
|
package/selectors/index.js
CHANGED
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { createPageviewData } from "../helpers";
|
|
3
|
+
import getPage, { makeGetRoutePageConfig } from "./page";
|
|
4
|
+
import getCart from "./cart";
|
|
5
|
+
import getSearch from "./search";
|
|
6
|
+
import getFavorites from "./favorites";
|
|
7
|
+
import { makeGetRouteCategory } from "./category";
|
|
8
|
+
import { makeGetRouteProduct } from "./product";
|
|
9
|
+
|
|
10
|
+
/**
|
|
2
11
|
* Creates a selector that retrieves tracking data for the current route.
|
|
3
12
|
* @returns {Function}
|
|
4
|
-
*/
|
|
13
|
+
*/
|
|
14
|
+
export const makeGetTrackingData = () => {
|
|
15
|
+
const getRouteCategory = makeGetRouteCategory();
|
|
16
|
+
const getRouteProduct = makeGetRouteProduct();
|
|
17
|
+
const getRoutePageConfig = makeGetRoutePageConfig();
|
|
18
|
+
|
|
19
|
+
/**
|
|
5
20
|
* Selects the combined tracking information.
|
|
6
21
|
* @param {Object} state The current state.
|
|
7
22
|
* @returns {Object} The tracking data.
|
|
8
|
-
*/
|
|
23
|
+
*/
|
|
24
|
+
return createSelector(getPage, getCart, getFavorites, getSearch, getRouteCategory, getRouteProduct, getRoutePageConfig, (state, props = {}) => props.pattern, (page, cart, favorites, search, category, product, pageConfig, pattern) => createPageviewData({
|
|
25
|
+
page,
|
|
26
|
+
cart,
|
|
27
|
+
favorites,
|
|
28
|
+
search,
|
|
29
|
+
category,
|
|
30
|
+
product,
|
|
31
|
+
pageConfig,
|
|
32
|
+
pattern
|
|
33
|
+
}));
|
|
34
|
+
};
|