@nuskin/cs-product-lib 1.0.1 → 1.0.2-cx12-6001.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/cs-product-lib",
3
- "version": "1.0.1",
3
+ "version": "1.0.2-cx12-6001.2",
4
4
  "description": "Library for pulling and parsing product content (and applying business logic) retrieved from Contentstack",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/csProduct.js CHANGED
@@ -1,416 +1,422 @@
1
- 'use strict'
2
-
3
- const helpers = require('./helpers')
4
- const { getProductStatus } = require('./productStatus')
5
- const { deliveryAxios } = require('./axiosClient')
6
- const { formatTranslations } = require('./formatTranslations')
7
- const { formatVariant } = require('./formatVariant')
8
- const { formatProduct } = require('./formatProduct')
9
- const { transformBundle } = require('./transformBundle')
10
-
11
- const validateConfig = (props) => {
12
- if (!props) {
13
- throw new Error(
14
- `This will not work without a valid configuration {apiKey, environment, deliveryToken, homeUrl, awsBase, clientId}.`
15
- )
16
- }
17
-
18
- const requiredProps =[
19
- "apiKey",
20
- "environment",
21
- "deliveryToken",
22
- "homeUrl",
23
- "awsBase",
24
- "clientId"
25
- ]
26
-
27
- requiredProps.forEach(prop => {
28
- if (!props[prop]) {
29
- throw new Error(`Missing required property ${prop} in config.`)
30
- }
31
- })
32
- }
33
-
34
- /* istanbul ignore next */
35
- const csProduct = (props) => {
36
- const DEV_ENVIRONMENT = 'public-dev'
37
- const VARIANTS = 'variants'
38
- const KEY_INGREDIENTS = 'ingredients.key_ingredients'
39
- const PRODUCT_HIGHLIGHTS = 'product_details.highlights'
40
- const VARIANT_HIGHLIGHTS = 'variants.product_details.highlights'
41
- const VARIANT_KEY_INGREDIENTS =
42
- 'variants.ingredients_override.key_ingredients'
43
- const PRODUCT_FAQS = 'faqs'
44
- const VARIANT_FAQS = 'variants.faqs'
45
- const PRODUCT_RESOURCES = 'resources'
46
- const VARIANT_RESOURCES = 'variants.resources'
47
- const PRODUCT_CATEGORY = 'categories'
48
- const PRODUCT_SUBCATEGORIES = 'categories.subcategories'
49
- const DEFAULT_LOCALE = 'en'
50
- const PRODUCT_TYPE = 'product'
51
- const INCLUDE_FALLBACK = '&include_fallback=true'
52
- const REQUIRED = 'required_products.product.'
53
- const OPTIONAL = 'optional_products.product.'
54
- const BUNDLE_TYPE = 'bundle'
55
-
56
- // eslint-disable-next-line max-len
57
- const productIncludes = `&include[]=${VARIANTS}&include[]=${PRODUCT_FAQS}&include[]=${KEY_INGREDIENTS}&include[]=${PRODUCT_HIGHLIGHTS}&include[]=${PRODUCT_RESOURCES}&include[]=${VARIANT_KEY_INGREDIENTS}&include[]=${VARIANT_HIGHLIGHTS}&include[]=${VARIANT_FAQS}&include[]=${VARIANT_RESOURCES}&include[]=${PRODUCT_CATEGORY}&include[]=${PRODUCT_SUBCATEGORIES}`
58
- const requiredIncludes = `&include[]=${REQUIRED}${VARIANTS}&include[]=${REQUIRED}${PRODUCT_FAQS}&include[]=${REQUIRED}${KEY_INGREDIENTS}&include[]=${REQUIRED}${PRODUCT_HIGHLIGHTS}&include[]=${REQUIRED}${PRODUCT_RESOURCES}&include[]=${REQUIRED}${VARIANT_KEY_INGREDIENTS}&include[]=${REQUIRED}${VARIANT_HIGHLIGHTS}&include[]=${REQUIRED}${VARIANT_FAQS}&include[]=${REQUIRED}${VARIANT_RESOURCES}&include[]=${REQUIRED}${PRODUCT_CATEGORY}&include[]=${REQUIRED}${PRODUCT_SUBCATEGORIES}`
59
- const optionalIncludes = `&include[]=${OPTIONAL}${VARIANTS}&include[]=${OPTIONAL}${PRODUCT_FAQS}&include[]=${OPTIONAL}${KEY_INGREDIENTS}&include[]=${OPTIONAL}${PRODUCT_HIGHLIGHTS}&include[]=${OPTIONAL}${PRODUCT_RESOURCES}&include[]=${OPTIONAL}${VARIANT_KEY_INGREDIENTS}&include[]=${OPTIONAL}${VARIANT_HIGHLIGHTS}&include[]=${OPTIONAL}${VARIANT_FAQS}&include[]=${OPTIONAL}${VARIANT_RESOURCES}&include[]=${OPTIONAL}${PRODUCT_CATEGORY}&include[]=${OPTIONAL}${PRODUCT_SUBCATEGORIES}`
60
-
61
- validateConfig(props)
62
- const { apiKey, environment, deliveryToken, ...config } = props
63
- const axios = deliveryAxios({ apiKey, deliveryToken})
64
-
65
- const asObject = () => {
66
- return props
67
- }
68
-
69
- const removeInvalidVariants = async (products, locale, translations) => {
70
- const validSkus = []
71
- for (const product of products) {
72
- for (const variant of product?.variants || []) {
73
- const bestSkuData = helpers?.getBestSkuData(variant) || []
74
- if (bestSkuData && Object.keys(bestSkuData)?.length > 0) {
75
- validSkus.push(bestSkuData.sku)
76
- }
77
- }
78
- }
79
- if (environment === 'public-dev' && validSkus?.length === 0) return
80
-
81
- const localeData = helpers?.parseLocale(locale)
82
- locale = helpers?.reorderLocale(locale)
83
- let status = {}
84
- let marketData = {}
85
- if (validSkus?.length > 0) {
86
- status = await getProductStatus(validSkus, locale, config)
87
- const localeSplit = locale.split('_')
88
- let market =
89
- localeSplit?.length > 1 ? localeSplit[1].toUpperCase() : 'XX'
90
- if (market === 'XX') {
91
- const split2 = locale.split('-')
92
- market = split2.length > 1 ? split2[0].toUpperCase() : ''
93
- }
94
- marketData = await getEntriesByQuery({
95
- contentType: 'market',
96
- query: `{"iso_code_2": "${market}"}`,
97
- locale: 'en'
98
- })
99
- if (marketData?.length > 0) {
100
- marketData = marketData[0]
101
- }
102
- }
103
- for (const product of products) {
104
- const validVariants = []
105
- for (const variant of product?.variants || []) {
106
- const bestSkuData = helpers?.getBestSkuData(variant)
107
- if (bestSkuData.sku) {
108
- const varStatus = status[bestSkuData.sku]
109
- if (varStatus && varStatus.status.retail) {
110
- variant.sap_data.sku_data = [bestSkuData]
111
- validVariants.push({
112
- ...variant,
113
- status: varStatus,
114
- marketData
115
- })
116
- }
117
- }
118
- }
119
- product.variants = validVariants
120
- product.translations = Array.isArray(translations)
121
- ? translations[0]
122
- : translations
123
- product.localeData = localeData
124
- }
125
- }
126
-
127
- const parseSharedStrings = async (translations) => {
128
- if (helpers?.isIterable(translations)) {
129
- for (const translation of translations) {
130
- translation.common_strings = translation?.common_strings
131
- ?.map((obj) => {
132
- const commonStrings = { type: obj.key }
133
- // eslint-disable-next-line no-sequences
134
- commonStrings.strings = (
135
- (obj || {}).strings || []
136
- ).reduce(
137
- (result, { key, value }) => (
138
- (result[key] = value), result
139
- ),
140
- {}
141
- )
142
- return commonStrings
143
- })
144
- .reduce((result, { type, strings }) => {
145
- result[type] = strings
146
- return result
147
- }, {})
148
- }
149
- }
150
- }
151
-
152
- const formatProductWithSchima = (products = []) => {
153
- return products?.map((product) => {
154
- const formatedProduct = formatProduct(product, config)
155
- formatedProduct.variants = formatedProduct?.variants?.map(
156
- (variant) => formatVariant(variant)
157
- )
158
- return formatedProduct
159
- })
160
- }
161
-
162
- const formatBundleWithSchima = (bundles = []) => {
163
- return bundles?.map((bundle) => {
164
- const formatedBundle = transformBundle(bundle, config)
165
-
166
- formatedBundle.requiredProducts = formatedBundle?.requiredProducts?.map(
167
- ({product, quantity}) => {
168
- const [formatedProduct] = formatProductWithSchima([product])
169
- return {product : formatedProduct, quantity}
170
- }
171
- )
172
-
173
- formatedBundle.optionalProducts = formatedBundle?.optionalProducts?.map(
174
- ({product, quantity}) => {
175
- const [formatedProduct] = formatProductWithSchima([product])
176
- return {product : formatedProduct, quantity}
177
- }
178
- )
179
-
180
- return formatedBundle
181
- })
182
- }
183
-
184
- // includes is an array of all the referenced types in the contentType you are requesting that should be resolved
185
- // and returned (Example: ['common_strings'])
186
- const getEntriesByQuery = async ({
187
- contentType = PRODUCT_TYPE,
188
- query = '',
189
- locale = DEFAULT_LOCALE,
190
- includes,
191
- start = 0,
192
- limit = 50,
193
- onlyEntries = true
194
- }) => {
195
- // eslint-disable-next-line max-len
196
- let url = `content_types/${contentType}/entries?environment=${environment}&locale=${locale}&include_count=true&limit=${limit}&query=${query}`
197
- if (contentType === PRODUCT_TYPE) {
198
- url += productIncludes
199
- }
200
- if (contentType === BUNDLE_TYPE) {
201
- url += requiredIncludes
202
- url += optionalIncludes
203
- }
204
- if (includes) {
205
- for (const include of includes) {
206
- url += `&include[]=${include}`
207
- }
208
- }
209
- if (environment === DEV_ENVIRONMENT) {
210
- url += INCLUDE_FALLBACK
211
- }
212
- if (start > 0) {
213
- url += `&skip=${start}`
214
- }
215
- try {
216
- const res = await axios(url)
217
-
218
- if (onlyEntries)
219
- return res?.data?.entries ? res.data.entries : res;
220
- else
221
- return res?.data?.entries ? res.data : res;
222
- } catch (error) {
223
- return error
224
- }
225
- }
226
-
227
- const queryProducts = async (query, locale) => {
228
- const products = await getEntriesByQuery({ query, locale })
229
- return products
230
- }
231
-
232
- const getBundle = async (id, locale) => {
233
- const query = `{ "$or": [{ "bundle_id": { "$in": ["${id}"]}}, { "uid": { "$in": ["${id}"] }}] }`
234
- const bundles = await getEntriesByQuery({
235
- query,
236
- locale,
237
- contentType: BUNDLE_TYPE
238
- })
239
- const translations = await getTranslations(locale)
240
-
241
- for (let b = 0; b < bundles.length; b++) {
242
- for (
243
- let i = 0;
244
- i < (bundles[b].required_products || []).length;
245
- i++
246
- ) {
247
- await removeInvalidVariants(
248
- bundles[b].required_products[i].product,
249
- locale,
250
- translations
251
- )
252
- }
253
- for (
254
- let i = 0;
255
- i < (bundles[b].optional_products || []).length;
256
- i++
257
- ) {
258
- await removeInvalidVariants(
259
- bundles[b].optional_products[i].product,
260
- locale,
261
- translations
262
- )
263
- }
264
- }
265
- return formatBundleWithSchima(bundles)?.[0]
266
- }
267
- const getBundles = async (ids, locale) => {
268
- const query = `{ "$or": [{ "bundle_id": { "$in": ${helpers?.arrayString(ids)} }}, { "uid": { "$in": ${helpers?.arrayString(ids)} }}] }`
269
- const bundles = await getEntriesByQuery({
270
- query,
271
- locale,
272
- contentType: BUNDLE_TYPE
273
- })
274
- const translations = await getTranslations(locale)
275
-
276
- for (let b = 0; b < bundles.length; b++) {
277
- for (
278
- let i = 0;
279
- i < (bundles[b].required_products || []).length;
280
- i++
281
- ) {
282
- await removeInvalidVariants(
283
- bundles[b].required_products[i].product,
284
- locale,
285
- translations
286
- )
287
- }
288
- for (
289
- let i = 0;
290
- i < (bundles[b].optional_products || []).length;
291
- i++
292
- ) {
293
- await removeInvalidVariants(
294
- bundles[b].optional_products[i].product,
295
- locale,
296
- translations
297
- )
298
- }
299
- }
300
- return formatBundleWithSchima(bundles)
301
- }
302
-
303
- const getProductsBySku = async (skus, locale) => {
304
- if (!Array.isArray(skus)) {
305
- skus = [skus]
306
- }
307
- const query = `{"variants": {"$in_query":{"sap_data.skus": {"$in": ${helpers?.arrayString(
308
- skus
309
- )}}}}}`
310
-
311
- let products = await queryProducts(query, locale)
312
- const translations = await getTranslations(locale)
313
- // await parseSharedStrings(translations);
314
- await removeInvalidVariants(products, locale, translations)
315
- return formatProductWithSchima(products)
316
- }
317
-
318
- const getProductsByGlobalId = async (ids, locale) => {
319
- if (!Array.isArray(ids)) {
320
- ids = [ids]
321
- }
322
- const query = `{"variants": {"$in_query":{"sap_data.global_id": {"$in": ${helpers?.arrayString(
323
- [ids]
324
- )}}}}}`
325
-
326
- let products = await queryProducts(query, locale)
327
- const translations = await getTranslations(locale)
328
- // await parseSharedStrings(translations);
329
- await removeInvalidVariants(products, locale, translations)
330
- return formatProductWithSchima(products)
331
- }
332
-
333
- const getProductsBySlug = async (slugs, locale) => {
334
- if (!Array.isArray(slugs)) {
335
- slugs = [slugs]
336
- }
337
- const query = `{"seo_information.slug": {"$in": ${helpers?.arrayString(
338
- slugs
339
- )}}}`
340
-
341
- const products = await queryProducts(query, locale)
342
- const translations = await getTranslations(locale)
343
- // await parseSharedStrings(translations);
344
- await removeInvalidVariants(products, locale, translations)
345
- return formatProductWithSchima(products)
346
- }
347
-
348
- const getProductsByUID = async (ids, locale) => {
349
- if (!Array.isArray(ids)) {
350
- ids = [ids]
351
- }
352
- const query = `{"uid": {"$in": ${helpers?.arrayString(ids)}}}`
353
- let products = await queryProducts(query, locale)
354
- const translations = await getTranslations(locale)
355
- // await parseSharedStrings(translations);
356
- await removeInvalidVariants(products, locale, translations)
357
- return formatProductWithSchima(products)
358
- }
359
-
360
- const getTranslations = async (locale) => {
361
- const translations = await getEntriesByQuery({
362
- contentType: 'strings_product_detail_page',
363
- locale,
364
- includes: ['common_strings', 'subscriptions.modal']
365
- })
366
- await parseSharedStrings(translations)
367
- return translations
368
- }
369
-
370
- const getProductTranslations = async (locale) => {
371
- const translations = await getTranslations(locale)
372
- const formatedTranslations = translations.map((translate) =>
373
- formatTranslations(translate)
374
- )
375
- return formatedTranslations
376
- }
377
-
378
- const publishedProducts = async ({ locale, skip = 0, limit = 50 } = {}) => {
379
- let products = await getEntriesByQuery({
380
- locale,
381
- start: skip,
382
- limit,
383
- onlyEntries: false
384
- })
385
- const count = products?.count || 0
386
- products = products?.entries || []
387
- const translations = await getTranslations(locale)
388
- await removeInvalidVariants(products, locale, translations)
389
- const transformProduct = formatProductWithSchima(products)
390
- return {
391
- products: transformProduct,
392
- validCount: products.length,
393
- totalCount: count,
394
- skip
395
- }
396
- }
397
-
398
- return {
399
- asObject,
400
- getEntriesByQuery,
401
- getProductsBySku,
402
- getProductsByGlobalId,
403
- getProductsBySlug,
404
- getProductsByUID,
405
- getTranslations,
406
- publishedProducts,
407
- formatTranslations,
408
- getProductTranslations,
409
- getBundle,
410
- getBundles
411
- }
412
- }
413
-
414
- module.exports = {
415
- csProduct
416
- }
1
+ 'use strict'
2
+
3
+ const helpers = require('./helpers')
4
+ const { getProductStatus } = require('./productStatus')
5
+ const { deliveryAxios } = require('./axiosClient')
6
+ const { formatTranslations } = require('./formatTranslations')
7
+ const { formatVariant } = require('./formatVariant')
8
+ const { formatProduct } = require('./formatProduct')
9
+ const { transformBundle } = require('./transformBundle')
10
+
11
+ const validateConfig = (props) => {
12
+ if (!props) {
13
+ throw new Error(
14
+ `This will not work without a valid configuration {apiKey, environment, deliveryToken, homeUrl, awsBase, clientId}.`
15
+ )
16
+ }
17
+
18
+ const requiredProps =[
19
+ "apiKey",
20
+ "environment",
21
+ "deliveryToken",
22
+ "homeUrl",
23
+ "awsBase",
24
+ "clientId"
25
+ ]
26
+
27
+ requiredProps.forEach(prop => {
28
+ if (!props[prop]) {
29
+ throw new Error(`Missing required property ${prop} in config.`)
30
+ }
31
+ })
32
+ }
33
+
34
+ /* istanbul ignore next */
35
+ const csProduct = (props) => {
36
+ const DEV_ENVIRONMENT = 'public-dev'
37
+ const VARIANTS = 'variants'
38
+ const KEY_INGREDIENTS = 'ingredients.key_ingredients'
39
+ const PRODUCT_HIGHLIGHTS = 'product_details.highlights'
40
+ const VARIANT_HIGHLIGHTS = 'variants.product_details.highlights'
41
+ const VARIANT_KEY_INGREDIENTS =
42
+ 'variants.ingredients_override.key_ingredients'
43
+ const PRODUCT_FAQS = 'faqs'
44
+ const VARIANT_FAQS = 'variants.faqs'
45
+ const PRODUCT_RESOURCES = 'resources'
46
+ const VARIANT_RESOURCES = 'variants.resources'
47
+ const PRODUCT_CATEGORY = 'categories'
48
+ const PRODUCT_SUBCATEGORIES = 'categories.subcategories'
49
+ const PRODUCT_BRAND_STYLE = 'features.brand_style';
50
+ const DEFAULT_LOCALE = 'en'
51
+ const PRODUCT_TYPE = 'product'
52
+ const INCLUDE_FALLBACK = '&include_fallback=true'
53
+ const REQUIRED = 'required_products.product.'
54
+ const OPTIONAL = 'optional_products.product.'
55
+ const BUNDLE_TYPE = 'bundle'
56
+
57
+ // eslint-disable-next-line max-len
58
+ const productIncludes = `&include[]=${VARIANTS}&include[]=${PRODUCT_FAQS}&include[]=${KEY_INGREDIENTS}&include[]=${PRODUCT_HIGHLIGHTS}&include[]=${PRODUCT_RESOURCES}&include[]=${VARIANT_KEY_INGREDIENTS}&include[]=${VARIANT_HIGHLIGHTS}&include[]=${VARIANT_FAQS}&include[]=${VARIANT_RESOURCES}&include[]=${PRODUCT_CATEGORY}&include[]=${PRODUCT_SUBCATEGORIES}&include[]=${PRODUCT_BRAND_STYLE}`
59
+ const requiredIncludes = `&include[]=${REQUIRED}${VARIANTS}&include[]=${REQUIRED}${PRODUCT_FAQS}&include[]=${REQUIRED}${KEY_INGREDIENTS}&include[]=${REQUIRED}${PRODUCT_HIGHLIGHTS}&include[]=${REQUIRED}${PRODUCT_RESOURCES}&include[]=${REQUIRED}${VARIANT_KEY_INGREDIENTS}&include[]=${REQUIRED}${VARIANT_HIGHLIGHTS}&include[]=${REQUIRED}${VARIANT_FAQS}&include[]=${REQUIRED}${VARIANT_RESOURCES}&include[]=${REQUIRED}${PRODUCT_CATEGORY}&include[]=${REQUIRED}${PRODUCT_SUBCATEGORIES}`
60
+ const optionalIncludes = `&include[]=${OPTIONAL}${VARIANTS}&include[]=${OPTIONAL}${PRODUCT_FAQS}&include[]=${OPTIONAL}${KEY_INGREDIENTS}&include[]=${OPTIONAL}${PRODUCT_HIGHLIGHTS}&include[]=${OPTIONAL}${PRODUCT_RESOURCES}&include[]=${OPTIONAL}${VARIANT_KEY_INGREDIENTS}&include[]=${OPTIONAL}${VARIANT_HIGHLIGHTS}&include[]=${OPTIONAL}${VARIANT_FAQS}&include[]=${OPTIONAL}${VARIANT_RESOURCES}&include[]=${OPTIONAL}${PRODUCT_CATEGORY}&include[]=${OPTIONAL}${PRODUCT_SUBCATEGORIES}`
61
+
62
+ validateConfig(props)
63
+ const { apiKey, environment, deliveryToken, ...config } = props
64
+ const axios = deliveryAxios({ apiKey, deliveryToken})
65
+
66
+ const asObject = () => {
67
+ return props
68
+ }
69
+
70
+ const removeInvalidVariants = async (products, locale, translations) => {
71
+ const validSkus = []
72
+ for (const product of products) {
73
+ for (const variant of product?.variants || []) {
74
+ const bestSkuData = helpers?.getBestSkuData(variant) || []
75
+ if (bestSkuData && Object.keys(bestSkuData)?.length > 0) {
76
+ validSkus.push(bestSkuData.sku)
77
+ }
78
+ }
79
+ }
80
+ if (environment === 'public-dev' && validSkus?.length === 0) return
81
+
82
+ const localeData = helpers?.parseLocale(locale)
83
+ locale = helpers?.reorderLocale(locale)
84
+ let status = {}
85
+ let marketData = {}
86
+ if (validSkus?.length > 0) {
87
+ status = await getProductStatus(validSkus, locale, config)
88
+ const localeSplit = locale.split('_')
89
+ let market =
90
+ localeSplit?.length > 1 ? localeSplit[1].toUpperCase() : 'XX'
91
+ if (market === 'XX') {
92
+ const split2 = locale.split('-')
93
+ market = split2.length > 1 ? split2[0].toUpperCase() : ''
94
+ }
95
+ marketData = await getEntriesByQuery({
96
+ contentType: 'market',
97
+ query: `{"iso_code_2": "${market}"}`,
98
+ locale: 'en'
99
+ })
100
+ if (marketData?.length > 0) {
101
+ marketData = marketData[0]
102
+ }
103
+ }
104
+ for (const product of products) {
105
+ const validVariants = []
106
+ for (const variant of product?.variants || []) {
107
+ const bestSkuData = helpers?.getBestSkuData(variant)
108
+ if (bestSkuData.sku) {
109
+ const varStatus = status[bestSkuData.sku]
110
+ if (varStatus && varStatus.status.retail) {
111
+ variant.sap_data.sku_data = [bestSkuData]
112
+ validVariants.push({
113
+ ...variant,
114
+ status: varStatus,
115
+ marketData
116
+ })
117
+ }
118
+ }
119
+ }
120
+ product.variants = validVariants
121
+ product.translations = Array.isArray(translations)
122
+ ? translations[0]
123
+ : translations
124
+ product.localeData = localeData
125
+ }
126
+ }
127
+
128
+ const parseSharedStrings = async (translations) => {
129
+ if (helpers?.isIterable(translations)) {
130
+ for (const translation of translations) {
131
+ translation.common_strings = translation?.common_strings
132
+ ?.map((obj) => {
133
+ const commonStrings = { type: obj.key }
134
+ // eslint-disable-next-line no-sequences
135
+ commonStrings.strings = (
136
+ (obj || {}).strings || []
137
+ ).reduce(
138
+ (result, { key, value }) => (
139
+ (result[key] = value), result
140
+ ),
141
+ {}
142
+ )
143
+ return commonStrings
144
+ })
145
+ .reduce((result, { type, strings }) => {
146
+ result[type] = strings
147
+ return result
148
+ }, {})
149
+ translation.subscriptions = helpers.mapSubscriptions(translation?.subscriptions)
150
+ }
151
+ }
152
+ }
153
+
154
+ const formatProductWithSchima = (products = []) => {
155
+ return products?.map((product) => {
156
+ const formatedProduct = formatProduct(product, config)
157
+ formatedProduct.variants = formatedProduct?.variants?.map(
158
+ (variant) => formatVariant(variant)
159
+ )
160
+ return formatedProduct
161
+ })
162
+ }
163
+
164
+ const formatBundleWithSchima = (bundles = []) => {
165
+ return bundles?.map((bundle) => {
166
+ const formatedBundle = transformBundle(bundle, config)
167
+
168
+ formatedBundle.requiredProducts = formatedBundle?.requiredProducts?.map(
169
+ ({product, quantity}) => {
170
+ const [formatedProduct] = formatProductWithSchima([product])
171
+ return {product : formatedProduct, quantity}
172
+ }
173
+ )
174
+
175
+ formatedBundle.optionalProducts = formatedBundle?.optionalProducts?.map(
176
+ ({product, quantity}) => {
177
+ const [formatedProduct] = formatProductWithSchima([product])
178
+ return {product : formatedProduct, quantity}
179
+ }
180
+ )
181
+
182
+ return formatedBundle
183
+ })
184
+ }
185
+
186
+ // includes is an array of all the referenced types in the contentType you are requesting that should be resolved
187
+ // and returned (Example: ['common_strings'])
188
+ const getEntriesByQuery = async ({
189
+ contentType = PRODUCT_TYPE,
190
+ query = '',
191
+ locale = DEFAULT_LOCALE,
192
+ includes,
193
+ start = 0,
194
+ limit = 50,
195
+ onlyEntries = true
196
+ }) => {
197
+ // eslint-disable-next-line max-len
198
+ let url = `content_types/${contentType}/entries?environment=${environment}&locale=${locale}&include_count=true&limit=${limit}&query=${query}`
199
+ if (contentType === PRODUCT_TYPE) {
200
+ url += productIncludes
201
+ }
202
+ if (contentType === BUNDLE_TYPE) {
203
+ url += requiredIncludes
204
+ url += optionalIncludes
205
+ }
206
+ if (includes) {
207
+ for (const include of includes) {
208
+ url += `&include[]=${include}`
209
+ }
210
+ }
211
+ if (environment === DEV_ENVIRONMENT) {
212
+ url += INCLUDE_FALLBACK
213
+ }
214
+ if (start > 0) {
215
+ url += `&skip=${start}`
216
+ }
217
+ try {
218
+ const res = await axios(url)
219
+
220
+ if (onlyEntries)
221
+ return res?.data?.entries ? res.data.entries : res;
222
+ else
223
+ return res?.data?.entries ? res.data : res;
224
+ } catch (error) {
225
+ return error
226
+ }
227
+ }
228
+
229
+ const queryProducts = async (query, locale) => {
230
+ const products = await getEntriesByQuery({ query, locale })
231
+ return products
232
+ }
233
+
234
+ const getBundle = async (id, locale) => {
235
+ const query = `{ "$or": [{ "bundle_id": { "$in": ["${id}"]}}, { "uid": { "$in": ["${id}"] }}] }`
236
+ const bundles = await getEntriesByQuery({
237
+ query,
238
+ locale,
239
+ contentType: BUNDLE_TYPE
240
+ })
241
+ const translations = await getTranslations(locale)
242
+
243
+ for (let b = 0; b < bundles.length; b++) {
244
+ for (
245
+ let i = 0;
246
+ i < (bundles[b].required_products || []).length;
247
+ i++
248
+ ) {
249
+ await removeInvalidVariants(
250
+ bundles[b].required_products[i].product,
251
+ locale,
252
+ translations
253
+ )
254
+ }
255
+ for (
256
+ let i = 0;
257
+ i < (bundles[b].optional_products || []).length;
258
+ i++
259
+ ) {
260
+ await removeInvalidVariants(
261
+ bundles[b].optional_products[i].product,
262
+ locale,
263
+ translations
264
+ )
265
+ }
266
+ }
267
+ return formatBundleWithSchima(bundles)?.[0]
268
+ }
269
+ const getBundles = async (ids, locale) => {
270
+ const query = `{ "$or": [{ "bundle_id": { "$in": ${helpers?.arrayString(ids)} }}, { "uid": { "$in": ${helpers?.arrayString(ids)} }}] }`
271
+ const bundles = await getEntriesByQuery({
272
+ query,
273
+ locale,
274
+ contentType: BUNDLE_TYPE
275
+ })
276
+ const translations = await getTranslations(locale)
277
+
278
+ for (let b = 0; b < bundles.length; b++) {
279
+ for (
280
+ let i = 0;
281
+ i < (bundles[b].required_products || []).length;
282
+ i++
283
+ ) {
284
+ await removeInvalidVariants(
285
+ bundles[b].required_products[i].product,
286
+ locale,
287
+ translations
288
+ )
289
+ }
290
+ for (
291
+ let i = 0;
292
+ i < (bundles[b].optional_products || []).length;
293
+ i++
294
+ ) {
295
+ await removeInvalidVariants(
296
+ bundles[b].optional_products[i].product,
297
+ locale,
298
+ translations
299
+ )
300
+ }
301
+ }
302
+ return formatBundleWithSchima(bundles)
303
+ }
304
+
305
+ const getProductsBySku = async (skus, locale) => {
306
+ if (!Array.isArray(skus)) {
307
+ skus = [skus]
308
+ }
309
+ const query = `{"variants": {"$in_query":{"sap_data.skus": {"$in": ${helpers?.arrayString(
310
+ skus
311
+ )}}}}}`
312
+
313
+ let products = await queryProducts(query, locale)
314
+ const translations = await getTranslations(locale)
315
+ // await parseSharedStrings(translations);
316
+ await removeInvalidVariants(products, locale, translations)
317
+ for (const product of products) {
318
+ product.skus = skus
319
+ product.locale = locale
320
+ }
321
+ return formatProductWithSchima(products)
322
+ }
323
+
324
+ const getProductsByGlobalId = async (ids, locale) => {
325
+ if (!Array.isArray(ids)) {
326
+ ids = [ids]
327
+ }
328
+ const query = `{"variants": {"$in_query":{"sap_data.global_id": {"$in": ${helpers?.arrayString(
329
+ [ids]
330
+ )}}}}}`
331
+
332
+ let products = await queryProducts(query, locale)
333
+ const translations = await getTranslations(locale)
334
+ // await parseSharedStrings(translations);
335
+ await removeInvalidVariants(products, locale, translations)
336
+ return formatProductWithSchima(products)
337
+ }
338
+
339
+ const getProductsBySlug = async (slugs, locale) => {
340
+ if (!Array.isArray(slugs)) {
341
+ slugs = [slugs]
342
+ }
343
+ const query = `{"seo_information.slug": {"$in": ${helpers?.arrayString(
344
+ slugs
345
+ )}}}`
346
+
347
+ const products = await queryProducts(query, locale)
348
+ const translations = await getTranslations(locale)
349
+ // await parseSharedStrings(translations);
350
+ await removeInvalidVariants(products, locale, translations)
351
+ return formatProductWithSchima(products)
352
+ }
353
+
354
+ const getProductsByUID = async (ids, locale) => {
355
+ if (!Array.isArray(ids)) {
356
+ ids = [ids]
357
+ }
358
+ const query = `{"uid": {"$in": ${helpers?.arrayString(ids)}}}`
359
+ let products = await queryProducts(query, locale)
360
+ const translations = await getTranslations(locale)
361
+ // await parseSharedStrings(translations);
362
+ await removeInvalidVariants(products, locale, translations)
363
+ return formatProductWithSchima(products)
364
+ }
365
+
366
+ const getTranslations = async (locale) => {
367
+ const translations = await getEntriesByQuery({
368
+ contentType: 'strings_product_detail_page',
369
+ locale,
370
+ includes: ['common_strings', 'subscriptions.modal']
371
+ })
372
+ await parseSharedStrings(translations)
373
+ return translations
374
+ }
375
+
376
+ const getProductTranslations = async (locale) => {
377
+ const translations = await getTranslations(locale)
378
+ const formatedTranslations = translations.map((translate) =>
379
+ formatTranslations(translate)
380
+ )
381
+ return formatedTranslations
382
+ }
383
+
384
+ const publishedProducts = async ({ locale, skip = 0, limit = 50 } = {}) => {
385
+ let products = await getEntriesByQuery({
386
+ locale,
387
+ start: skip,
388
+ limit,
389
+ onlyEntries: false
390
+ })
391
+ const count = products?.count || 0
392
+ products = products?.entries || []
393
+ const translations = await getTranslations(locale)
394
+ await removeInvalidVariants(products, locale, translations)
395
+ const transformProduct = formatProductWithSchima(products)
396
+ return {
397
+ products: transformProduct,
398
+ validCount: products.length,
399
+ totalCount: count,
400
+ skip
401
+ }
402
+ }
403
+
404
+ return {
405
+ asObject,
406
+ getEntriesByQuery,
407
+ getProductsBySku,
408
+ getProductsByGlobalId,
409
+ getProductsBySlug,
410
+ getProductsByUID,
411
+ getTranslations,
412
+ publishedProducts,
413
+ formatTranslations,
414
+ getProductTranslations,
415
+ getBundle,
416
+ getBundles
417
+ }
418
+ }
419
+
420
+ module.exports = {
421
+ csProduct
422
+ }
@@ -28,6 +28,7 @@ const formatVariant = (parent) => ({
28
28
  availableQuantity: _.get(parent, 'defaults.status.availableQuantity', 0),
29
29
  maxQuantity: _.get(parent, 'defaults.status.maxQuantity', 0),
30
30
  status: {
31
+ status: _.get(parent, 'defaults.status.status.retail', null),
31
32
  retail: _.get(parent, 'defaults.status.status.retail', null),
32
33
  wholesale: _.get(parent, 'defaults.status.status.wholesale', null),
33
34
  isBackordered: _.get(parent, 'defaults.status.backordered', null),
package/src/helpers.js CHANGED
@@ -10,6 +10,37 @@ const templateSettings = require('lodash/templateSettings')
10
10
  const _ = require('lodash')
11
11
  const { isNullOrEmpty } = require('@nuskin/ns-common-lib')
12
12
 
13
+ // CONSTS for helpers
14
+ const TEXT_COLOR = 'textColor';
15
+ const BACKGROUND_COLOR = 'backgroundColor';
16
+
17
+ /* istanbul ignore next */
18
+ function mapBrandStyle(feature, type) {
19
+ // if brand style background color is authored, use it, and make text black.
20
+ // otherwise make text white, and use feature.background_color if it exists or default the background color to be #4f4d4d if not
21
+ const isBrandStyle = feature.brand_style && Array.isArray(feature.brand_style) && feature.brand_style.length > 0;
22
+ // eslint-disable-next-line no-nested-ternary
23
+ const backgroundColor = isBrandStyle ? feature.brand_style[0].background_color : feature.background_color.length > 1 ? feature.background_color : '#4f4d4d';
24
+ const textColor = isBrandStyle && feature.brand_style[0].background_color.length > 0 ? '#000000' : '#ffffff';
25
+
26
+ switch (type) {
27
+ case TEXT_COLOR:
28
+ if (!textColor.includes('#')) {
29
+ return `#${textColor}`
30
+ }
31
+ return textColor;
32
+ case BACKGROUND_COLOR:
33
+ if (!backgroundColor.includes('#')) {
34
+ return `#${backgroundColor}`
35
+ }
36
+ return backgroundColor;
37
+ default:
38
+ break;
39
+ }
40
+ return null; // error, need to specify type i.e. text or background color etc
41
+ }
42
+
43
+
13
44
  /* istanbul ignore next */
14
45
  const parseLocale = (locale) => {
15
46
  const localeSplit = locale.split('-')
@@ -522,7 +553,8 @@ function mapFeatures(productOrVariant) {
522
553
  image: mapImage(featureImage(feature)),
523
554
  subtitle: feature.header,
524
555
  features: feature.features,
525
- backgroundColor: feature.background_color
556
+ backgroundColor: mapBrandStyle(feature, BACKGROUND_COLOR),
557
+ textColor: mapBrandStyle(feature, TEXT_COLOR)
526
558
  }))
527
559
  }
528
560
 
@@ -612,7 +644,9 @@ function mapBenefitsAndFeatures(productOrVariant) {
612
644
  return {
613
645
  image: mapImage(benefitsMedia.image),
614
646
  youTubeVideoId: benefitsMedia.youTubeVideoId,
615
- benefits: benefitsToMarkdown(legacyBenefits, jsonBenefits)
647
+ benefits: benefitsToMarkdown(legacyBenefits, jsonBenefits),
648
+ backgroundColor: productOrVariant?.background_color,
649
+ textColor: productOrVariant?.text_color
616
650
  }
617
651
  }
618
652
 
@@ -1047,6 +1081,7 @@ function mapCrossProductIds(product) {
1047
1081
  }
1048
1082
 
1049
1083
  module.exports = {
1084
+ mapBrandStyle,
1050
1085
  mapWarranty,
1051
1086
  mapUpSellProductIds,
1052
1087
  mapCrossProductIds,