@nuskin/product-components 3.20.0 → 3.20.1-it-17535.1

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/.releaserc CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "branches": [
3
- "master"
3
+ "master", {"name":"IT-17535", "channel":"prerelease", "prerelease":"it-17535"}
4
4
  ],
5
5
  "plugins": [
6
6
  "@semantic-release/release-notes-generator",
@@ -426,6 +426,17 @@ export default {
426
426
  const storeId = this.storeId || queryStoreId;
427
427
  const offerId = this.offerId || queryOfferId;
428
428
 
429
+ // Log initialization check
430
+ console.log("[NsProductOffer] checkToInit called:", {
431
+ timestamp: new Date().toISOString(),
432
+ appServiceLoading: this.$NsProductAppService.loading,
433
+ hasTranslations: !isNullOrEmpty(this.translations),
434
+ storeId: storeId,
435
+ offerId: offerId,
436
+ market: this.market,
437
+ language: this.language
438
+ });
439
+
429
440
  // app service needs to be loaded, market and language need to be different, and translations are required
430
441
  if (
431
442
  !this.$NsProductAppService.loading &&
@@ -437,6 +448,14 @@ export default {
437
448
  (!!offerId && offerId !== this.localOfferId)) &&
438
449
  !isNullOrEmpty(this.translations)
439
450
  ) {
451
+ console.log("[NsProductOffer] Initializing offer:", {
452
+ timestamp: new Date().toISOString(),
453
+ market: this.runConfig.country,
454
+ language: this.runConfig.language,
455
+ storeId: storeId,
456
+ offerId: offerId
457
+ });
458
+
440
459
  this.market = this.runConfig.country;
441
460
  this.language = this.runConfig.language;
442
461
 
@@ -454,10 +473,25 @@ export default {
454
473
 
455
474
  await waitForConfig();
456
475
  await this.init();
476
+ } else {
477
+ // Log why initialization was skipped
478
+ console.log("[NsProductOffer] Init skipped:", {
479
+ timestamp: new Date().toISOString(),
480
+ appServiceLoading: this.$NsProductAppService.loading,
481
+ marketMatch: this.market === this.runConfig.country,
482
+ languageMatch: this.language === this.runConfig.language,
483
+ hasTranslations: !isNullOrEmpty(this.translations)
484
+ });
457
485
  }
458
486
  },
459
487
 
460
488
  async init() {
489
+ console.log("[NsProductOffer] init() started:", {
490
+ timestamp: new Date().toISOString(),
491
+ localStoreId: this.localStoreId,
492
+ localOfferId: this.localOfferId
493
+ });
494
+
461
495
  this.storeOwner = StoreFrontSponsorStorageService.getStoreFrontSponsor();
462
496
 
463
497
  const shoppingContext = ShoppingContext.getShoppingContext();
@@ -524,6 +558,7 @@ export default {
524
558
 
525
559
  this.offerLoading = false;
526
560
  } else {
561
+ console.log("[NsProductOffer] Offer not found - no offer ID provided");
527
562
  this.offerNotFound = true;
528
563
  this.showOfferSavings = false;
529
564
  this.offerLoading = false;
@@ -531,12 +566,19 @@ export default {
531
566
  },
532
567
 
533
568
  async getOffer() {
569
+ console.log("[NsProductOffer] getOffer() called:", {
570
+ timestamp: new Date().toISOString(),
571
+ storeId: this.localStoreId,
572
+ offerId: this.localOfferId
573
+ });
574
+
534
575
  const offer = await PersonalOfferService.getOfferV2(
535
576
  this.localStoreId,
536
577
  this.localOfferId
537
578
  );
538
579
 
539
580
  if (isNullOrEmpty(offer)) {
581
+ console.log("[NsProductOffer] Offer not found in API response");
540
582
  this.offerNotFound = true;
541
583
  return;
542
584
  }
@@ -750,6 +792,20 @@ export default {
750
792
 
751
793
  checkProductsHaveData: debounce(function(dataAvailable) {
752
794
  const products = Object.values(this.products);
795
+ const productsWithData = products.filter(p => !isNullOrEmpty(p.data));
796
+ const productsWithAvailability = products.filter(
797
+ p => !isNullOrEmpty(p.availability)
798
+ );
799
+
800
+ console.log("[NsProductOffer] checkProductsHaveData:", {
801
+ timestamp: new Date().toISOString(),
802
+ totalProducts: products.length,
803
+ productsWithData: productsWithData.length,
804
+ productsWithAvailability: productsWithAvailability.length,
805
+ dataAvailable: dataAvailable,
806
+ productSkus: products.map(p => p.sku)
807
+ });
808
+
753
809
  if (products.every(product => !isNullOrEmpty(product.availability))) {
754
810
  this.someProductsValid = products.some(
755
811
  product =>
@@ -762,8 +818,13 @@ export default {
762
818
  dataAvailable &&
763
819
  products.every(product => isNullOrEmpty(product.data))
764
820
  ) {
821
+ console.log(
822
+ "[NsProductOffer] Offer not found - all products have empty data"
823
+ );
765
824
  this.offerNotFound = true;
766
825
  }
826
+ } else {
827
+ console.log("[NsProductOffer] Some products missing availability data");
767
828
  }
768
829
  }, 250),
769
830
 
package/docs/CHANGELOG.md CHANGED
@@ -1 +1 @@
1
- # [3.20.0](https://code.tls.nuskin.io/ns-am/ux/product-components/compare/v3.19.1...v3.20.0) (2026-02-17)
1
+ ## [3.20.1-it-17535.1](https://code.tls.nuskin.io/ns-am/ux/product-components/compare/v3.20.0...v3.20.1-it-17535.1) (2026-03-20)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/product-components",
3
- "version": "3.20.0",
3
+ "version": "3.20.1-it-17535.1",
4
4
  "description": "Nu Skin Product Components",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -13,6 +13,82 @@ import debounce from "lodash/debounce";
13
13
  const fallbackImagePath =
14
14
  "etc/designs/hello/packaged/img/product-basic__fallback-image.svg";
15
15
 
16
+ /**
17
+ * Utility to detect browser type
18
+ */
19
+ const getBrowserInfo = () => {
20
+ // Check if running in browser environment
21
+ if (typeof navigator === "undefined" || !navigator.userAgent) {
22
+ return {
23
+ userAgent: "",
24
+ isInstagram: false,
25
+ isFacebook: false,
26
+ isTwitter: false,
27
+ isLinkedIn: false,
28
+ isTikTok: false,
29
+ isInAppBrowser: false
30
+ };
31
+ }
32
+
33
+ const ua = navigator.userAgent || "";
34
+ return {
35
+ userAgent: ua,
36
+ isInstagram: ua.includes("Instagram"),
37
+ isFacebook:
38
+ ua.includes("FBAN") || ua.includes("FBAV") || ua.includes("FB_IAB"),
39
+ isTwitter: ua.includes("Twitter"),
40
+ isLinkedIn: ua.includes("LinkedInApp"),
41
+ isTikTok: ua.includes("TikTok"),
42
+ isInAppBrowser:
43
+ ua.includes("Instagram") ||
44
+ ua.includes("FBAN") ||
45
+ ua.includes("FBAV") ||
46
+ ua.includes("FB_IAB") ||
47
+ ua.includes("Twitter") ||
48
+ ua.includes("LinkedInApp")
49
+ };
50
+ };
51
+
52
+ /**
53
+ * Log to console and send to analytics/monitoring
54
+ */
55
+ const logProductComponentEvent = (eventName, data) => {
56
+ // Check if running in browser environment
57
+ if (typeof window === "undefined" || typeof document === "undefined") {
58
+ return null;
59
+ }
60
+
61
+ const browserInfo = getBrowserInfo();
62
+ const logData = {
63
+ timestamp: new Date().toISOString(),
64
+ event: eventName,
65
+ component: "NsProductAppService",
66
+ url: window.location?.href || "",
67
+ referrer: document.referrer || "",
68
+ visibilityState: document.visibilityState || "visible",
69
+ ...browserInfo,
70
+ ...data
71
+ };
72
+
73
+ // Log to console for debugging
74
+ console.log(`[ProductComponents] ${eventName}:`, logData);
75
+
76
+ // Send to analytics if available (customize based on your analytics setup)
77
+ if (window.dataLayer) {
78
+ window.dataLayer.push({
79
+ event: "product_component_event",
80
+ ...logData
81
+ });
82
+ }
83
+
84
+ // Send to custom logging endpoint if available
85
+ if (window.nsLogger && typeof window.nsLogger.log === "function") {
86
+ window.nsLogger.log(logData);
87
+ }
88
+
89
+ return logData;
90
+ };
91
+
16
92
  let NsProductAppService = Vue.prototype.$NsProductAppService;
17
93
  if (!NsProductAppService) {
18
94
  const newEl = document.createElement("div");
@@ -25,7 +101,8 @@ if (!NsProductAppService) {
25
101
  return {
26
102
  runConfig: {},
27
103
  runConfigInterval: null,
28
- runConfigTries: 3,
104
+ runConfigTries: 10,
105
+ initialRunConfigTries: 10,
29
106
  locale: "",
30
107
  marketConfig: {},
31
108
  awsUrl: "",
@@ -34,7 +111,12 @@ if (!NsProductAppService) {
34
111
  showWholeSalePricing: false,
35
112
  loadingConfig: true,
36
113
  translations: {},
37
- loadingTranslations: true
114
+ loadingTranslations: true,
115
+ configLoadedSuccessfully: false,
116
+ initStartTime: Date.now(),
117
+ configLoadAttempts: 0,
118
+ visibilityChangeTriggered: false,
119
+ pageShowTriggered: false
38
120
  };
39
121
  },
40
122
  computed: {
@@ -43,21 +125,139 @@ if (!NsProductAppService) {
43
125
  }
44
126
  },
45
127
  async mounted() {
128
+ // Log initial state using modern Performance API
129
+ let pageLoadTime = 0;
130
+ if (typeof performance !== "undefined") {
131
+ // Use modern performance.now() which gives time since page load
132
+ pageLoadTime = Math.round(performance.now());
133
+ }
134
+
135
+ logProductComponentEvent("service_mounted", {
136
+ initialVisibilityState:
137
+ typeof document !== "undefined"
138
+ ? document.visibilityState
139
+ : "visible",
140
+ pageLoadTime
141
+ });
142
+
46
143
  await this.setConfig();
47
- this.runConfigInterval = setInterval(async () => {
48
- if (this.runConfigTries <= 0) {
49
- clearInterval(this.runConfigInterval);
50
- this.runConfigTries = 0;
51
- } else {
52
- await this.checkConfig();
53
- this.runConfigTries--;
144
+ this.startConfigRetryInterval();
145
+
146
+ // Handle Instagram and other in-app browsers that load pages in hidden state
147
+ // Re-initialize when page becomes visible
148
+ this.handleVisibilityChange = async () => {
149
+ const previousState = this.visibilityChangeTriggered;
150
+ this.visibilityChangeTriggered = true;
151
+
152
+ const visibilityState =
153
+ typeof document !== "undefined" && document.visibilityState
154
+ ? document.visibilityState
155
+ : "visible";
156
+
157
+ logProductComponentEvent("visibility_changed", {
158
+ visibilityState,
159
+ configLoadedSuccessfully: this.configLoadedSuccessfully,
160
+ firstVisibilityChange: !previousState,
161
+ retryAttemptsRemaining: this.runConfigTries
162
+ });
163
+
164
+ if (visibilityState === "visible" && !this.configLoadedSuccessfully) {
165
+ logProductComponentEvent("visibility_retry_triggered", {
166
+ reason: "page_became_visible",
167
+ attemptsUsed: this.initialRunConfigTries - this.runConfigTries
168
+ });
169
+
170
+ await this.setConfig();
171
+ if (!this.configLoadedSuccessfully) {
172
+ this.startConfigRetryInterval();
173
+ }
54
174
  }
55
- }, 350);
175
+ };
176
+
177
+ if (typeof document !== "undefined") {
178
+ document.addEventListener(
179
+ "visibilitychange",
180
+ this.handleVisibilityChange
181
+ );
182
+ }
183
+
184
+ // Handle page restoration from bfcache (back/forward cache)
185
+ this.handlePageShow = async event => {
186
+ this.pageShowTriggered = true;
187
+
188
+ logProductComponentEvent("page_show_event", {
189
+ persisted: event.persisted,
190
+ configLoadedSuccessfully: this.configLoadedSuccessfully
191
+ });
192
+
193
+ if (event.persisted && !this.configLoadedSuccessfully) {
194
+ logProductComponentEvent("pageshow_retry_triggered", {
195
+ reason: "bfcache_restore"
196
+ });
197
+
198
+ await this.setConfig();
199
+ if (!this.configLoadedSuccessfully) {
200
+ this.startConfigRetryInterval();
201
+ }
202
+ }
203
+ };
204
+
205
+ if (typeof window !== "undefined") {
206
+ window.addEventListener("pageshow", this.handlePageShow);
207
+ }
56
208
  },
57
209
  beforeDestroy() {
58
- clearInterval(this.runConfigInterval);
210
+ this.clearConfigRetryInterval();
211
+ if (this.handleVisibilityChange && typeof document !== "undefined") {
212
+ document.removeEventListener(
213
+ "visibilitychange",
214
+ this.handleVisibilityChange
215
+ );
216
+ }
217
+ if (this.handlePageShow && typeof window !== "undefined") {
218
+ window.removeEventListener("pageshow", this.handlePageShow);
219
+ }
59
220
  },
60
221
  methods: {
222
+ /**
223
+ * Start the configuration retry interval
224
+ */
225
+ startConfigRetryInterval() {
226
+ this.clearConfigRetryInterval();
227
+
228
+ logProductComponentEvent("retry_interval_started", {
229
+ retriesRemaining: this.runConfigTries,
230
+ configLoadedSuccessfully: this.configLoadedSuccessfully
231
+ });
232
+
233
+ this.runConfigInterval = setInterval(async () => {
234
+ if (this.runConfigTries <= 0 || this.configLoadedSuccessfully) {
235
+ if (this.runConfigTries <= 0 && !this.configLoadedSuccessfully) {
236
+ logProductComponentEvent("config_load_failed", {
237
+ reason: "max_retries_exceeded",
238
+ totalAttempts: this.configLoadAttempts,
239
+ timeSinceInit: Date.now() - this.initStartTime,
240
+ finalVisibilityState: document.visibilityState
241
+ });
242
+ }
243
+ this.clearConfigRetryInterval();
244
+ } else {
245
+ await this.checkConfig();
246
+ this.runConfigTries--;
247
+ }
248
+ }, 350);
249
+ },
250
+
251
+ /**
252
+ * Clear the configuration retry interval
253
+ */
254
+ clearConfigRetryInterval() {
255
+ if (this.runConfigInterval) {
256
+ clearInterval(this.runConfigInterval);
257
+ this.runConfigInterval = null;
258
+ }
259
+ },
260
+
61
261
  /**
62
262
  * Gets the full content path for an image or content src
63
263
  */
@@ -85,10 +285,22 @@ if (!NsProductAppService) {
85
285
  */
86
286
  async setConfig(runConfig) {
87
287
  this.loadingConfig = true;
288
+ this.configLoadAttempts++;
289
+
290
+ const attemptStartTime = Date.now();
88
291
 
89
292
  runConfig = runConfig || RunConfigService.getRunConfig() || {};
90
293
  if (!runConfig.language || !runConfig.country) {
91
294
  // the runconfig isn't ready yet
295
+ logProductComponentEvent("config_not_ready", {
296
+ attempt: this.configLoadAttempts,
297
+ hasLanguage: !!runConfig.language,
298
+ hasCountry: !!runConfig.country,
299
+ runConfigKeys: Object.keys(runConfig),
300
+ timeSinceInit: Date.now() - this.initStartTime
301
+ });
302
+
303
+ this.loadingConfig = false;
92
304
  return;
93
305
  }
94
306
 
@@ -115,6 +327,26 @@ if (!NsProductAppService) {
115
327
  };
116
328
 
117
329
  this.loadingConfig = false;
330
+ this.configLoadedSuccessfully = true;
331
+
332
+ const loadTime = Date.now() - attemptStartTime;
333
+ const totalTime = Date.now() - this.initStartTime;
334
+
335
+ // Log successful configuration
336
+ logProductComponentEvent("config_loaded_successfully", {
337
+ attempt: this.configLoadAttempts,
338
+ loadTime: loadTime,
339
+ totalTime: totalTime,
340
+ retriesUsed: this.initialRunConfigTries - this.runConfigTries,
341
+ locale: this.locale,
342
+ hasMarketConfig: !!this.marketConfig,
343
+ visibilityChangeWasNeeded: this.visibilityChangeTriggered,
344
+ pageShowWasNeeded: this.pageShowTriggered
345
+ });
346
+
347
+ // Stop retry interval once config is successfully loaded
348
+ this.clearConfigRetryInterval();
349
+
118
350
  this.$emit("config-changed", configData);
119
351
  },
120
352