@nuskin/react-search-sdk 1.0.0-trying-again.2 → 1.0.0-trying-again.4

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/dist/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
- import { lazy, useState, useEffect, useRef, useMemo, useCallback, Suspense, Component } from 'react';
1
+ import { lazy, useState, useEffect, useRef, useMemo, useCallback, Component, Suspense } from 'react';
2
2
  import configurationSdk from '@nuskin/configuration-sdk';
3
3
  import { SearchModal } from '@nuskin/react-search-components';
4
+ import { createProductClient, ProductProvider, useProductById, ProductCard } from '@nuskin/react-product-sdk';
5
+ import styled from '@emotion/styled';
4
6
  import { jsx } from 'react/jsx-runtime';
5
7
 
6
8
  // src/components/CommerceSearch/CommerceSearch.tsx
@@ -28,7 +30,10 @@ var TEST_ENVIRONMENT_VALUES = {
28
30
  agent: {
29
31
  appKey: "pk_commerce_product_advisor_624aea4305b09240e8c8f41d0f6527b2",
30
32
  apiBaseUrl: "https://nui.tst.nuskin.ai/"
31
- }
33
+ },
34
+ // Open to visitors who are not signed in, so the handoff can be tried where signing in is not
35
+ // possible (for example local development). Sign-in stays required everywhere else.
36
+ allowGuestAgent: true
32
37
  };
33
38
  function nonEmptyString(value) {
34
39
  return typeof value === "string" ? value.trim() : "";
@@ -45,7 +50,8 @@ function normalizeCommerceSearchConfig(raw, { language } = {}) {
45
50
  apiBaseUrl,
46
51
  ...language ? { language } : {}
47
52
  }
48
- } : {}
53
+ } : {},
54
+ ...raw?.allowGuestAgent === true ? { allowGuestAgent: true } : {}
49
55
  };
50
56
  }
51
57
  async function fetchCommerceSearchConfig({
@@ -299,6 +305,16 @@ function resolveCustomerTypeHeaders(getCustomerType) {
299
305
  const customerType = getCustomerType?.()?.trim();
300
306
  return customerType ? { "X-Customer-Type": customerType } : void 0;
301
307
  }
308
+ var NO_RESULTS_ERROR_CODE = "SFOSER002";
309
+ async function isNoResultsResponse(response) {
310
+ if (response.status !== 404) return false;
311
+ try {
312
+ const body = await response.json();
313
+ return body?.code === NO_RESULTS_ERROR_CODE;
314
+ } catch {
315
+ return false;
316
+ }
317
+ }
302
318
  async function fetchJsonFromCandidates(fetchImpl, requestUrls, requestHeaders = {}) {
303
319
  let lastError;
304
320
  for (const requestUrl of requestUrls) {
@@ -309,6 +325,7 @@ async function fetchJsonFromCandidates(fetchImpl, requestUrls, requestHeaders =
309
325
  headers: { Accept: "application/json", ...requestHeaders }
310
326
  });
311
327
  if (!response.ok) {
328
+ if (await isNoResultsResponse(response)) return null;
312
329
  lastError = new Error(
313
330
  `Request failed with status ${response.status}`
314
331
  );
@@ -348,6 +365,13 @@ function createSearchService(options = {}) {
348
365
  locationLike,
349
366
  country: localeParts.country,
350
367
  getMarketConfig
368
+ }).catch((error) => {
369
+ catalogRequestConfigPromise = null;
370
+ console.warn(
371
+ "[search] Could not load the market configuration for the suggestions:",
372
+ error
373
+ );
374
+ throw error;
351
375
  });
352
376
  }
353
377
  return catalogRequestConfigPromise;
@@ -468,6 +492,19 @@ function toNavigationTarget(path) {
468
492
  return /^https?:\/\//i.test(path) ? { type: "external", url: path } : { type: "internal", path };
469
493
  }
470
494
 
495
+ // src/utils/productEnvironment.ts
496
+ var PRODUCT_ENVIRONMENTS = ["dev", "test", "prod"];
497
+ function toProductEnvironment(environmentCode) {
498
+ return environmentCode && PRODUCT_ENVIRONMENTS.includes(environmentCode) ? environmentCode : void 0;
499
+ }
500
+ function getProductEnvironment() {
501
+ try {
502
+ return toProductEnvironment(getEnvironmentCode());
503
+ } catch {
504
+ return void 0;
505
+ }
506
+ }
507
+
471
508
  // src/utils/catalogHelpers.ts
472
509
  var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
473
510
  "discontinued",
@@ -997,9 +1034,10 @@ function resolveNuiSearchFlag(env = {}) {
997
1034
  }
998
1035
  function isNuiSearchEnabled({
999
1036
  isAuthenticated,
1000
- flag
1037
+ flag,
1038
+ allowGuests = false
1001
1039
  }) {
1002
- return isAuthenticated === true && flag === true;
1040
+ return flag === true && (isAuthenticated === true || allowGuests);
1003
1041
  }
1004
1042
 
1005
1043
  // src/utils/searchRouting.ts
@@ -1013,15 +1051,69 @@ function decideSearchRoute({
1013
1051
  if (snapshot.suggestionFetchFailed) return "search";
1014
1052
  return snapshot.suggestions.length > 0 ? "search" : "handoff";
1015
1053
  }
1054
+
1055
+ // src/utils/cachedProductClient.ts
1056
+ function withProductCache(client) {
1057
+ const lookups = /* @__PURE__ */ new Map();
1058
+ return {
1059
+ getProductById(productId) {
1060
+ let lookup = lookups.get(productId);
1061
+ if (!lookup) {
1062
+ lookup = client.getProductById(productId);
1063
+ lookups.set(productId, lookup);
1064
+ }
1065
+ return lookup;
1066
+ },
1067
+ getProductsByIds: (productIds) => client.getProductsByIds(productIds)
1068
+ };
1069
+ }
1070
+ var AgentProductSlot = styled.div`
1071
+ && {
1072
+ flex: 0 0 240px;
1073
+ overflow: hidden;
1074
+ width: 240px;
1075
+ }
1076
+ `;
1077
+ var SalesAIChatWidget = lazy(
1078
+ () => Promise.all([
1079
+ import('@nu-skin-enterprises/chat-widget'),
1080
+ import('@nu-skin-enterprises/chat-widget/styles.css')
1081
+ ]).then(([mod]) => ({ default: mod.SalesAIChatWidget }))
1082
+ );
1016
1083
  var HANDLE_POLL_MS = 50;
1017
1084
  var HANDLE_TIMEOUT_MS = 15e3;
1018
- var SalesAIChatWidget = lazy(async () => {
1019
- const [widget] = await Promise.all([
1020
- import('@nuskin/react-nui-chat-sdk'),
1021
- import('@nuskin/react-nui-chat-sdk/styles.css')
1022
- ]);
1023
- return { default: widget.SalesAIChatWidget };
1024
- });
1085
+ function AgentProductCard({ sku }) {
1086
+ const { product, isLoading, error } = useProductById(sku);
1087
+ if (!isLoading && (error || !product?.seoInformation?.canonicalURL)) {
1088
+ return null;
1089
+ }
1090
+ return /* @__PURE__ */ jsx(AgentProductSlot, { children: /* @__PURE__ */ jsx(ProductCard, { productId: sku }) });
1091
+ }
1092
+ var renderAgentProduct = (product) => /* @__PURE__ */ jsx(AgentProductCard, { sku: product.sku });
1093
+ function AgentWidget({
1094
+ widgetProps,
1095
+ products
1096
+ }) {
1097
+ const environment = products?.environment;
1098
+ const market = products?.market;
1099
+ const language = products?.language;
1100
+ const client = useMemo(
1101
+ () => environment && market && language ? withProductCache(
1102
+ createProductClient({ environment, market, language })
1103
+ ) : void 0,
1104
+ [environment, market, language]
1105
+ );
1106
+ if (!client) {
1107
+ return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(SalesAIChatWidget, { ...widgetProps }) });
1108
+ }
1109
+ return /* @__PURE__ */ jsx(ProductProvider, { client, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
1110
+ SalesAIChatWidget,
1111
+ {
1112
+ renderProduct: renderAgentProduct,
1113
+ ...widgetProps
1114
+ }
1115
+ ) }) });
1116
+ }
1025
1117
  function getSalesAIHandle() {
1026
1118
  return window.SalesAI;
1027
1119
  }
@@ -1077,6 +1169,7 @@ function NuiAgentPanel({
1077
1169
  agent,
1078
1170
  query,
1079
1171
  handoffContext,
1172
+ products,
1080
1173
  onUnavailable
1081
1174
  }) {
1082
1175
  useHandoff({
@@ -1085,19 +1178,22 @@ function NuiAgentPanel({
1085
1178
  agentContext: agent.context,
1086
1179
  onUnavailable
1087
1180
  });
1088
- return /* @__PURE__ */ jsx(AgentErrorBoundary, { onError: onUnavailable, children: /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(
1089
- SalesAIChatWidget,
1181
+ const widgetProps = {
1182
+ appKey: agent.appKey,
1183
+ apiBaseUrl: agent.apiBaseUrl,
1184
+ theme: agent.theme ?? "nui-light",
1185
+ defaultOpen: true,
1186
+ hideFab: true,
1187
+ placement: "host",
1188
+ ...agent.language === void 0 ? {} : { language: agent.language }
1189
+ };
1190
+ return /* @__PURE__ */ jsx(AgentErrorBoundary, { onError: onUnavailable, children: /* @__PURE__ */ jsx(
1191
+ AgentWidget,
1090
1192
  {
1091
- appKey: agent.appKey,
1092
- apiBaseUrl: agent.apiBaseUrl,
1093
- theme: agent.theme ?? "nui-light",
1094
- defaultOpen: true,
1095
- hideFab: true,
1096
- placement: "host",
1097
- ...agent.language === void 0 ? {} : { language: agent.language },
1098
- ...agent.renderProduct === void 0 ? {} : { renderProduct: agent.renderProduct }
1193
+ widgetProps,
1194
+ ...products ? { products } : {}
1099
1195
  }
1100
- ) }) });
1196
+ ) });
1101
1197
  }
1102
1198
  var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
1103
1199
  function uniqueRecentSearches(queries) {
@@ -1116,6 +1212,8 @@ function CommerceSearchView({
1116
1212
  navigate,
1117
1213
  isAuthenticated,
1118
1214
  agent,
1215
+ allowGuestAgent = false,
1216
+ agentProducts,
1119
1217
  logo,
1120
1218
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
1121
1219
  gtmContext
@@ -1141,7 +1239,11 @@ function CommerceSearchView({
1141
1239
  suggestions,
1142
1240
  updateQuery
1143
1241
  } = search;
1144
- const enabled = agent !== void 0 && isNuiSearchEnabled({ isAuthenticated, flag });
1242
+ const enabled = agent !== void 0 && isNuiSearchEnabled({
1243
+ isAuthenticated,
1244
+ flag,
1245
+ allowGuests: allowGuestAgent
1246
+ });
1145
1247
  const items = getVisibleSuggestions({
1146
1248
  didYouMean,
1147
1249
  suggestions
@@ -1311,6 +1413,22 @@ function CommerceSearchView({
1311
1413
  if (agentQuery !== null)
1312
1414
  navigateTo(service.submitProductSearch(agentQuery));
1313
1415
  }, [agentQuery, navigateTo, service]);
1416
+ const agentProductsProps = agentProducts ? { products: agentProducts } : {};
1417
+ const agentPanel = handoff === null || agent === void 0 ? void 0 : /* @__PURE__ */ jsx(
1418
+ NuiAgentPanel,
1419
+ {
1420
+ agent,
1421
+ query: handoff.query,
1422
+ handoffContext: {
1423
+ source: "storefront_search",
1424
+ trigger: "zero_results",
1425
+ query: handoff.query,
1426
+ did_you_mean: handoff.didYouMean
1427
+ },
1428
+ ...agentProductsProps,
1429
+ onUnavailable: handleAgentUnavailable
1430
+ }
1431
+ );
1314
1432
  return /* @__PURE__ */ jsx(
1315
1433
  SearchModal,
1316
1434
  {
@@ -1338,20 +1456,7 @@ function CommerceSearchView({
1338
1456
  onSelect: handleRecentSelect,
1339
1457
  onClear: handleClearRecent
1340
1458
  },
1341
- agentPanel: handoff === null || agent === void 0 ? void 0 : /* @__PURE__ */ jsx(
1342
- NuiAgentPanel,
1343
- {
1344
- agent,
1345
- query: handoff.query,
1346
- handoffContext: {
1347
- source: "storefront_search",
1348
- trigger: "zero_results",
1349
- query: handoff.query,
1350
- did_you_mean: handoff.didYouMean
1351
- },
1352
- onUnavailable: handleAgentUnavailable
1353
- }
1354
- )
1459
+ agentPanel
1355
1460
  }
1356
1461
  );
1357
1462
  }
@@ -1369,7 +1474,7 @@ function CommerceSearch({
1369
1474
  useEffect(() => {
1370
1475
  if (isOpen) setHasOpened(true);
1371
1476
  }, [isOpen]);
1372
- const { minimumSearchLength, agent } = useCommerceSearchConfig({
1477
+ const { minimumSearchLength, agent, allowGuestAgent } = useCommerceSearchConfig({
1373
1478
  country,
1374
1479
  language,
1375
1480
  enabled: hasOpened
@@ -1402,6 +1507,14 @@ function CommerceSearch({
1402
1507
  }),
1403
1508
  [country, language, isAuthenticated, accountType, userId]
1404
1509
  );
1510
+ const agentProducts = useMemo(() => {
1511
+ const environment = getProductEnvironment();
1512
+ return environment ? {
1513
+ environment,
1514
+ market: country.toUpperCase(),
1515
+ language: language.toLowerCase()
1516
+ } : void 0;
1517
+ }, [country, language]);
1405
1518
  return /* @__PURE__ */ jsx(
1406
1519
  CommerceSearchView,
1407
1520
  {
@@ -1411,6 +1524,8 @@ function CommerceSearch({
1411
1524
  navigate: navigateToPath,
1412
1525
  isAuthenticated,
1413
1526
  ...agent ? { agent } : {},
1527
+ ...allowGuestAgent ? { allowGuestAgent } : {},
1528
+ ...agentProducts ? { agentProducts } : {},
1414
1529
  logo,
1415
1530
  minimumSearchLength,
1416
1531
  gtmContext