@nuskin/cs-product-lib 1.4.2 → 1.4.3

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.4.2",
3
+ "version": "1.4.3",
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": {
@@ -11,7 +11,7 @@ const formatProduct = (product) => ({
11
11
  productID: product?.product_id || ''
12
12
  });
13
13
 
14
- const formatCategory = (parent, language = 'en', homeUrl, allTopCategories) => ({
14
+ const formatCategory = (parent, language = 'en', homeUrl = '', allTopCategories = null) => ({
15
15
  displayName: parent?.display_name || '',
16
16
  categoryIdentifier: parent?.category_id || '',
17
17
  status: parent?.status || 'Inactive',
package/src/formatKit.js CHANGED
@@ -112,7 +112,6 @@ const parseBundleAvailableQuantity = (product) => {
112
112
  )
113
113
  return Math.min(...quantities) || 0
114
114
  } catch {
115
- console.log('some error in availableQuantity')
116
115
  return 0
117
116
  }
118
117
  }
@@ -148,7 +147,6 @@ const getKitOrBundleDetails = (bundle) => {
148
147
  dates = dates.filter(date => date.isValid());
149
148
  latestBackOrderedDate = dates && dates.length > 0 && backOrderedQty !== 0 ? moment.max(dates).format(dateFormat) : null;
150
149
  } catch (error) {
151
- console.error('getKitOrBundleDetails() - back order qty, available qty, max qty, latest back order date or status error', JSON.stringify(error));
152
150
  maxQty = 0;
153
151
  backOrderedQty = 0;
154
152
  availableQty = 0;
package/src/helpers.js CHANGED
@@ -148,70 +148,51 @@ function mapProductImages(parent) {
148
148
  }
149
149
 
150
150
  /* istanbul ignore next */
151
+ // Get the best SKU for this variant if there are multiple SKUs present (more than 1 SKU in the GPID)
152
+ // This is determined by the following priority of status:
153
+ // 1. sellable status
154
+ // 2. the first SKU in the list that isn't discontinued or inactive
155
+ // 3. discontined status
156
+ // 4. inactive status
151
157
  function getBestSkuData(variant) {
152
- if (
153
- variant.sap_data &&
154
- variant.sap_data.sku_data &&
155
- variant.sap_data.sku_data.length > 0
156
- ) {
158
+ if (variant?.sap_data?.sku_data?.length > 0) {
157
159
  if (variant.sap_data.sku_data.length > 1) {
158
- let bestSKUData = variant.sap_data.sku_data.find(
159
- (s) => s.status === 10
160
- )
161
- bestSKUData =
162
- variant.sap_data.sku_data.find(
163
- (s) => (s.status_selections || {}).status === 'sellable'
164
- ) || bestSKUData
160
+ // First: status === 10 or status_selections.status === 'sellable'
161
+ let bestSKUData = variant.sap_data.sku_data.find((s) => s?.status === 10 || s?.status_selections?.status === 'sellable')
162
+ // Second: not status 2, 9 or status_selections 'discontinued', 'inactive'
165
163
  if (!bestSKUData) {
166
- bestSKUData = variant.sap_data.sku_data.find(
167
- (s) => s.status === 9
164
+ bestSKUData = variant.sap_data.sku_data.find((s) =>
165
+ s?.status !== 2 && s?.status !== 9 &&
166
+ s?.status_selections?.status !== 'discontinued' &&
167
+ s?.status_selections?.status !== 'inactive'
168
168
  )
169
- bestSKUData =
170
- variant.sap_data.sku_data.find(
171
- (s) => (s.status_selections || {}).status === 'inactive'
172
- ) || bestSKUData
173
- if (!bestSKUData) {
174
- bestSKUData = variant.sap_data.sku_data.find(
175
- (s) => s.status !== 2
176
- )
177
- bestSKUData =
178
- variant.sap_data.sku_data.find(
179
- (s) =>
180
- (s.status_selections || {}).status !==
181
- 'discontinued'
182
- ) || bestSKUData
183
- }
184
- if (!bestSKUData) {
185
- bestSKUData = variant.sap_data.sku_data[0]
186
- }
187
169
  }
188
- return bestSKUData
189
- }
190
- return variant.sap_data.sku_data[0] || {}
191
- } else if (variant.sap_data && Array.isArray(variant.sap_data.sku)) {
192
- if (variant.sap_data.sku.length > 1) {
193
- let bestSKUData = variant.sap_data.sku.find(
194
- (s) => s.retail_status === 10
195
- )
170
+ // Third: status 2 or status_selections 'discontinued'
196
171
  if (!bestSKUData) {
197
- bestSKUData = variant.sap_data.sku.find(
198
- (s) => s.retail_status === 9
199
- )
200
- if (!bestSKUData) {
201
- bestSKUData = variant.sap_data.sku.find(
202
- (s) => s.retail_status !== 2
203
- )
204
- }
172
+ bestSKUData = variant.sap_data.sku_data.find((s) => s?.status === 2 || s?.status_selections?.status === 'discontinued')
205
173
  }
174
+ // Fourth: status 9 or status_selections 'inactive'
175
+ if (!bestSKUData) {
176
+ bestSKUData = variant.sap_data.sku_data.find((s) => s?.status === 9 || s?.status_selections?.status === 'inactive')
177
+ }
178
+ return bestSKUData || variant.sap_data.sku_data[0]
179
+ }
180
+ return variant?.sap_data?.sku_data[0] || {}
181
+ } else if (variant?.sap_data && Array.isArray(variant.sap_data.sku)) {
182
+ if (variant.sap_data.sku.length > 1) {
183
+ let bestSKUData = variant.sap_data.sku.find((s) => s.retail_status === 10) ||
184
+ variant.sap_data.sku.find((s) => s.retail_status !== 2 && s.retail_status !== 9) ||
185
+ variant.sap_data.sku.find((s) => s.retail_status === 2) ||
186
+ variant.sap_data.sku.find((s) => s.retail_status === 9) ||
187
+ variant.sap_data.sku[0]
206
188
  bestSKUData.status = bestSKUData.retail_status
207
189
  return bestSKUData
208
190
  }
209
191
  if (variant.sap_data.sku[0]) {
210
- variant.sap_data.sku[0].status =
211
- variant.sap_data.sku[0].retail_status
192
+ variant.sap_data.sku[0].status = variant.sap_data.sku[0].retail_status
212
193
  }
213
194
  return variant.sap_data.sku[0] || {}
214
- } else if (variant.sap_data && variant.sap_data.sku) {
195
+ } else if (variant?.sap_data?.sku) {
215
196
  variant.sap_data.sku.status = variant.sap_data.sku.retail_status
216
197
  return variant.sap_data.sku
217
198
  }
@@ -354,7 +335,7 @@ function mapSeoInformation(parent, homeUrl, type) {
354
335
  }/${parent?.localeData?.language}/catalog/${
355
336
  parent?.category_id || parent?.seo_information?.slug
356
337
  }`
357
- seoData.metaTitle = seoData.metaTitle || parent?.display_name || '',
338
+ seoData.metaTitle = seoData.metaTitle || parent?.display_name || ''
358
339
  seoData.metaDescription = seoData.metaDescription || ''
359
340
  break
360
341
  default:
@@ -497,78 +478,91 @@ function mapPoints(parent) {
497
478
  }
498
479
  }
499
480
 
500
- /* istanbul ignore next */
501
- function mapImage(image, useWebp, width, height, quality, transform, useDAT) {
502
- if (Array.isArray(image)) {
503
- image = image[0]
481
+ function getImageAlt(image) {
482
+ return image?.meta?.SEO_Tags || image.description || image.name || ''
483
+ }
484
+
485
+ function buildTransformUrl(baseUrl, useWebp, width, height, quality, transform) {
486
+ let url = `${baseUrl}${useWebp ? '.webp' : ''}`
487
+ if (width || height) {
488
+ const w = width ? `,width:${width}` : ''
489
+ const h = height ? `,height:${height}` : ''
490
+ url += `?quality=${quality}&io=transform:${transform}${w}${h}`
491
+ } else if (quality && quality !== 100) {
492
+ url += `?quality=${quality}`
504
493
  }
505
- useWebp = !!useWebp
506
- quality = quality || 100
507
- transform = transform || 'fit'
508
- useDAT = !!useDAT
509
-
510
- if ((image || {}).type) {
511
- if (image.type === 'VIDEO') {
512
- const derivatives = image.derivatives || {}
513
- if ((image.previewUrls || []).length > 0) {
514
- return {
515
- url: image.previewUrls[0],
516
- alt: image.description || image.name || '',
517
- thumbnail: derivatives.thumbnail
518
- }
519
- }
520
- return {
521
- url: derivatives.webImage,
522
- alt: image.description || image.name || '',
523
- thumbnail: derivatives.thumbnail
524
- }
525
- }
494
+ return url
495
+ }
496
+
497
+ function handleVideoImage(image) {
498
+ const derivatives = image.derivatives || {}
499
+ const url = (image.previewUrls || []).length > 0 ? image.previewUrls[0] : derivatives.webImage
500
+ return {
501
+ url,
502
+ alt: image.description || image.name || '',
503
+ thumbnail: derivatives.thumbnail
526
504
  }
527
- if ((image || {}).derivatives) {
528
- const derivatives = image.derivatives || {}
529
- // isSvg will only work if a public link to the original derivative is authored on the bynder asset.
530
- const isSvg = derivatives.original && derivatives.original.includes('.svg');
531
- let imgURL = derivatives.webImage
532
- let thumbnail = derivatives.thumbnail
533
- if (isSvg) {
534
- imgURL = derivatives.original
535
- } else if (derivatives.transformBaseUrl && useDAT) {
536
- imgURL = thumbnail = `${derivatives.transformBaseUrl}${useWebp ? '.webp' : ''}`
537
- if (width || height) {
538
- imgURL += `?quality=${quality}&io=transform:${transform}${width ? `,width:${width}` : ''}${height ? `,height:${height}` : ''}`
539
- } else if (quality && quality !== 100) {
540
- imgURL += `?quality=${quality}`
541
- }
542
- thumbnail += '?quality=85&io=transform:fill,width:50,height:50'
543
- }
505
+ }
506
+
507
+ function handleDerivativesImage(image, useWebp, width, height, quality, transform, useDAT) {
508
+ const derivatives = image.derivatives || {}
509
+ const isSvg = derivatives.original?.includes('.svg')
510
+ if (isSvg) {
544
511
  return {
545
- url: imgURL,
546
- alt:
547
- (image.meta || {}).SEO_Tags ||
548
- image.description ||
549
- image.name ||
550
- '',
551
- thumbnail: thumbnail
512
+ url: derivatives.original,
513
+ alt: getImageAlt(image),
514
+ thumbnail: derivatives.thumbnail
552
515
  }
553
- } else if ((image || {}).files) {
554
- const isSvg = (image.files.original || {}).url && (image.files.original || {}).url.includes('.svg');
516
+ }
517
+ if (derivatives.transformBaseUrl && useDAT) {
518
+ const imgURL = buildTransformUrl(derivatives.transformBaseUrl, useWebp, width, height, quality, transform)
519
+ const thumbnail = `${derivatives.transformBaseUrl}${useWebp ? '.webp' : ''}?quality=85&io=transform:fill,width:50,height:50`
555
520
  return {
556
- url: isSvg ? (image.files.original || {}).url : (image.files.webImage || {}).url,
557
- alt:
558
- (image.meta || {}).SEO_Tags ||
559
- image.description ||
560
- image.name ||
561
- '',
562
- thumbnail: (image.files.thumbnail || {}).url
521
+ url: imgURL,
522
+ alt: getImageAlt(image),
523
+ thumbnail
563
524
  }
564
525
  }
565
- return (image || {}).url
566
- ? {
567
- url: image.url,
568
- alt: image.description || image.title || '',
569
- thumbnail: image.url
570
- }
571
- : null
526
+ return {
527
+ url: derivatives.webImage,
528
+ alt: getImageAlt(image),
529
+ thumbnail: derivatives.thumbnail
530
+ }
531
+ }
532
+
533
+ function handleFilesImage(image) {
534
+ const isSvg = image?.files?.original?.url?.includes('.svg')
535
+ return {
536
+ url: isSvg ? image.files.original.url : image?.files?.webImage?.url,
537
+ alt: getImageAlt(image),
538
+ thumbnail: image?.files?.thumbnail?.url
539
+ }
540
+ }
541
+
542
+ function mapImage(image, useWebp, width, height, quality, transform, useDAT) {
543
+ if (Array.isArray(image)) {
544
+ image = image[0]
545
+ }
546
+ const params = {
547
+ useWebp: !!useWebp,
548
+ quality: quality || 100,
549
+ transform: transform || 'fit',
550
+ useDAT: !!useDAT
551
+ }
552
+ if (image?.type === 'VIDEO') {
553
+ return handleVideoImage(image)
554
+ }
555
+ if (image?.derivatives) {
556
+ return handleDerivativesImage(image, params.useWebp, width, height, params.quality, params.transform, params.useDAT)
557
+ }
558
+ if (image?.files) {
559
+ return handleFilesImage(image)
560
+ }
561
+ return image?.url ? {
562
+ url: image.url,
563
+ alt: image.description || image.title || '',
564
+ thumbnail: image.url
565
+ } : null
572
566
  }
573
567
 
574
568
  function mapLink(link) {
@@ -604,9 +598,9 @@ function mapProductDetails(productOrVariant) {
604
598
  }
605
599
  const includedItems = productDetails.included_items
606
600
  const highlights = (productDetails.highlights || []).map((highlight) => ({
607
- iconUrl: !isNullOrEmpty(highlight.icon_bynder)
608
- ? (mapImage(highlight.icon_bynder, true, 60, 60, 95, null, productOrVariant?.useDAT) || {}).url
609
- : (highlight.icon || {}).url,
601
+ iconUrl: !isNullOrEmpty(highlight?.icon_bynder)
602
+ ? mapImage(highlight?.icon_bynder, true, 60, 60, 95, null, productOrVariant?.useDAT)?.url
603
+ : highlight?.icon?.url,
610
604
  label: highlight.text
611
605
  }))
612
606
  const originCountry = productDetails.origin_country
@@ -861,77 +855,68 @@ function mapResources(productOrVariant) {
861
855
  }
862
856
  }
863
857
 
858
+ function getNutritionInformationImage(ingredients, useDAT) {
859
+ if (!isNullOrEmpty(ingredients?.nutrition_information_image_bynder)) {
860
+ return mapImage(ingredients.nutrition_information_image_bynder, true, null, 1200, 95, null, useDAT)
861
+ }
862
+ if (ingredients?.nutrition_information_image) {
863
+ return {
864
+ url: ingredients.nutrition_information_image.url,
865
+ alt: ingredients.nutrition_information_image.title
866
+ }
867
+ }
868
+ return null
869
+ }
870
+
864
871
  /* istanbul ignore next */
865
872
  function mapIngredients(ings, legacyIngredients, useDAT) {
866
873
  if (Array.isArray(ings)) {
867
874
  return ings.map((ingredients) => ({
868
- keyIngredients: ((ingredients || {}).key_ingredients || []).map(
875
+ keyIngredients: (ingredients?.key_ingredients || []).map(
869
876
  (keyIngredient) => ({
870
- image: !isNullOrEmpty(keyIngredient.icon_bynder)
877
+ image: !isNullOrEmpty(keyIngredient?.icon_bynder)
871
878
  ? mapImage(keyIngredient.icon_bynder, true, 100, 100, 95, null, useDAT)
872
879
  : {
873
- url: (keyIngredient.icon || {}).url,
874
- alt: (keyIngredient.icon || {}).title
880
+ url: keyIngredient?.icon?.url,
881
+ alt: keyIngredient?.icon?.title
875
882
  },
876
- name: keyIngredient.header,
877
- description: keyIngredient.description
883
+ name: keyIngredient?.header,
884
+ description: keyIngredient?.description
878
885
  })
879
886
  ),
880
- nutritionInformationImage: !isNullOrEmpty(
881
- (ingredients || {}).nutrition_information_image_bynder
882
- )
883
- ? mapImage(ingredients.nutrition_information_image_bynder, true, null, 1200, 95, null, useDAT)
884
- : (ingredients || {}).nutrition_information_image
885
- ? {
886
- url: ingredients.nutrition_information_image.url,
887
- alt: ingredients.nutrition_information_image.title
888
- }
889
- : null,
890
- productName: (ingredients || {}).product_name,
891
- allIngredients: decodeHTML((ingredients || {}).all_ingredients),
892
- otherIngredients: (ingredients || {}).other_ingredients,
893
- activeIngredients: (ingredients || {}).active_ingredients,
894
- inactiveIngredients: (ingredients || {}).inactive_ingredients,
895
- ingredientDisclaimers:
896
- (ingredients || {}).ingredient_disclaimers || [],
897
- markdown: legacyIngredients
898
- ? htmlToMarkdown(legacyIngredients)
899
- : null
887
+ nutritionInformationImage: getNutritionInformationImage(ingredients, useDAT),
888
+ productName: ingredients?.product_name,
889
+ allIngredients: decodeHTML(ingredients?.all_ingredients),
890
+ otherIngredients: ingredients?.other_ingredients,
891
+ activeIngredients: ingredients?.active_ingredients,
892
+ inactiveIngredients: ingredients?.inactive_ingredients,
893
+ ingredientDisclaimers: ingredients?.ingredient_disclaimers || [],
894
+ markdown: legacyIngredients ? htmlToMarkdown(legacyIngredients) : null
900
895
  }))
901
896
  }
902
897
 
903
- const keyIngredients = (ings || {}).key_ingredients || []
898
+ const keyIngredients = ings?.key_ingredients || []
904
899
  if (keyIngredients.length > 0) {
905
900
  return [
906
901
  {
907
902
  keyIngredients: keyIngredients.map((keyIngredient) => ({
908
- image: !isNullOrEmpty(keyIngredient.icon_bynder)
903
+ image: !isNullOrEmpty(keyIngredient?.icon_bynder)
909
904
  ? mapImage(keyIngredient.icon_bynder, true, 100, 100, 95, null, useDAT)
910
905
  : {
911
- url: (keyIngredient.icon || {}).url,
912
- alt: (keyIngredient.icon || {}).title
906
+ url: keyIngredient?.icon?.url,
907
+ alt: keyIngredient?.icon?.title
913
908
  },
914
- name: keyIngredient.header,
915
- description: keyIngredient.description
909
+ name: keyIngredient?.header,
910
+ description: keyIngredient?.description
916
911
  })),
917
- nutritionInformationImage: !isNullOrEmpty(
918
- (ings || {}).nutrition_information_image_bynder
919
- )
920
- ? mapImage(ings.nutrition_information_image_bynder, true, null, 1200, 95, null, useDAT)
921
- : (ings || {}).nutrition_information_image
922
- ? {
923
- url: ings.nutrition_information_image.url,
924
- alt: ings.nutrition_information_image.title
925
- }: null,
926
- allIngredients: decodeHTML((ings || {}).all_ingredients),
927
- otherIngredients: (ings || {}).other_ingredients,
928
- activeIngredients: (ings || {}).active_ingredients,
929
- inactiveIngredients: (ings || {}).inactive_ingredients,
912
+ nutritionInformationImage: getNutritionInformationImage(ings, useDAT),
913
+ allIngredients: decodeHTML(ings?.all_ingredients),
914
+ otherIngredients: ings?.other_ingredients,
915
+ activeIngredients: ings?.active_ingredients,
916
+ inactiveIngredients: ings?.inactive_ingredients,
930
917
  ingredientDisclaimers:
931
- (ings || {}).ingredient_disclaimers || [],
932
- markdown: legacyIngredients
933
- ? htmlToMarkdown(legacyIngredients)
934
- : null
918
+ ings?.ingredient_disclaimers || [],
919
+ markdown: legacyIngredients ? htmlToMarkdown(legacyIngredients) : null
935
920
  }
936
921
  ]
937
922
  }
@@ -993,7 +978,7 @@ function mapActiveEventDisclaimer(activeEventDisclaimer) {
993
978
 
994
979
  function hasRichTextContent(richText) {
995
980
  // when cleared, the rich text json still contains children, this checks for any text or links
996
- return ((richText || {}).children || []).some((line) =>
981
+ return (richText?.children || []).some((line) =>
997
982
  (line.children || []).some(
998
983
  (block) =>
999
984
  (!!block.text &&
@@ -1022,13 +1007,13 @@ function createMarkdownWithModifiers(text, bold, italic) {
1022
1007
 
1023
1008
  function createMarkdownFromRichText(richText) {
1024
1009
  let markdown = ''
1025
- for (const [index, line] of ((richText || {}).children || []).entries()) {
1010
+ for (const [index, line] of (richText?.children || []).entries()) {
1026
1011
  if (index > 0) {
1027
1012
  markdown += '\n'
1028
1013
  }
1029
1014
  for (const block of line.children) {
1030
1015
  if (block.type === 'a') {
1031
- const url = (block.attrs || {}).url || ''
1016
+ const url = block?.attrs?.url || ''
1032
1017
  for (const part of block.children) {
1033
1018
  const linkMarkdown = `[${part.text}](${url})`
1034
1019
  markdown += createMarkdownWithModifiers(
@@ -1111,13 +1096,11 @@ const arrayString = (array) => {
1111
1096
  /* istanbul ignore next */
1112
1097
  function mapWarranty(product) {
1113
1098
  if (
1114
- product &&
1115
- product.warranty &&
1116
- ((product.warranty || [])[0] || {}).derivatives
1099
+ (product?.warranty || [])[0]?.derivatives
1117
1100
  ) {
1118
1101
  const warranty = product.warranty[0]
1119
- const linkText = ((product.translations || {}).warranty || {}).link_text
1120
- const message = ((product.translations || {}).warranty || {}).message
1102
+ const linkText = product?.translations?.warranty?.link_text
1103
+ const message = product?.translations?.warranty?.message
1121
1104
  const translationPresent = `<span>${message}</span> <b>${linkText}</b>`
1122
1105
  const defaultTranslation = '<b>Warranty Info</b>'
1123
1106
  const translation =
@@ -1137,8 +1120,7 @@ function mapWarranty(product) {
1137
1120
 
1138
1121
  function mapUpSellProductIds(product) {
1139
1122
  if (
1140
- product &&
1141
- product.up_sell_products &&
1123
+ product?.up_sell_products &&
1142
1124
  product.up_sell_products.length > 0
1143
1125
  ) {
1144
1126
  return product.up_sell_products.map((prod) => prod.uid)
@@ -1148,8 +1130,7 @@ function mapUpSellProductIds(product) {
1148
1130
 
1149
1131
  function mapCrossProductIds(product) {
1150
1132
  if (
1151
- product &&
1152
- product.cross_sell_products &&
1133
+ product?.cross_sell_products &&
1153
1134
  product.cross_sell_products.length > 0
1154
1135
  ) {
1155
1136
  return product.cross_sell_products.map((prod) => prod.uid)
@@ -1158,7 +1139,7 @@ function mapCrossProductIds(product) {
1158
1139
  }
1159
1140
 
1160
1141
  function mapFaqs(product) {
1161
- if (product && product.faqs && product.faqs.length > 0) {
1142
+ if (product?.faqs && product.faqs.length > 0) {
1162
1143
  let jsonFaqs = JSON.stringify(product.faqs);
1163
1144
  if (jsonFaqs.length < 10000) {
1164
1145
  return product?.faqs?.map((faq) => ({