@nuskin/react-search-sdk 1.0.1-code-cleanup.1 → 1.1.0-pa-1255.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/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { useState, useEffect, useRef, useMemo, useCallback, Component } from 'react';
1
+ import { useRef, useEffect, useCallback, useMemo, useState, Component } from 'react';
2
2
  import configurationSdk from '@nuskin/configuration-sdk';
3
3
  import { SearchPanel } from '@nuskin/react-search-components';
4
+ import { jsx } from 'react/jsx-runtime';
4
5
  import { SalesAIChatWidget } from '@nuskin/react-nui-chat-sdk';
5
6
  import '@nuskin/react-nui-chat-sdk/styles.css';
6
7
  import { createProductClient, ProductProvider, useProductById, ProductCard } from '@nuskin/react-product-sdk';
7
8
  import styled from '@emotion/styled';
8
- import { jsx } from 'react/jsx-runtime';
9
9
 
10
10
  // src/components/CommerceSearch/CommerceSearch.tsx
11
11
  var { getConfiguration, getEnvironmentCode } = configurationSdk;
@@ -16,36 +16,53 @@ var CATALOG_SUGGESTIONS_PATH = "/storefront/catalogs/search/suggestions";
16
16
  var RECENT_SEARCHES_PREFIX = "nuskin:header:recentSearches";
17
17
  var RECENT_SEARCHES_DEFAULT_LIMIT = 10;
18
18
  var DEFAULT_SUGGESTION_PAGE = 1;
19
- var DEFAULT_SUGGESTION_SIZE = 6;
20
19
  var DEFAULT_SEARCH_PATH = "/search";
21
20
  var DEBOUNCE_MS = 220;
22
21
  var MINIMUM_SEARCH_CHAR_LENGTH = 2;
23
- var MAX_VISIBLE_SUGGESTIONS = 6;
22
+ var DEFAULT_MAX_SUGGESTIONS = 6;
23
+ var MAX_SUGGESTIONS_CEILING = 20;
24
24
  var EQUINOX_MARKETS_MAP = "Equinox_Markets";
25
- var DEFAULT_EQUINOX_CLIENT_ID = "63056848d7532f9923f3d08cb48c423b";
25
+ var COMMERCE_SEARCH_MAP = "Commerce_Search";
26
26
 
27
27
  // src/config/commerceSearchConfig.ts
28
28
  var COMMERCE_SEARCH_CONFIG_DEFAULTS = Object.freeze({
29
- minimumSearchLength: MINIMUM_SEARCH_CHAR_LENGTH
29
+ minimumSearchLength: MINIMUM_SEARCH_CHAR_LENGTH,
30
+ maxSuggestions: DEFAULT_MAX_SUGGESTIONS,
31
+ agentEnabledFor: Object.freeze({
32
+ guest: false,
33
+ brandAffiliate: false,
34
+ preferredCustomer: false,
35
+ retailCustomer: false
36
+ })
30
37
  });
31
- var TEST_ENVIRONMENT_VALUES = {
32
- agent: {
33
- appKey: "pk_commerce_product_advisor_624aea4305b09240e8c8f41d0f6527b2",
34
- apiBaseUrl: "https://nui.tst.nuskin.ai/"
35
- },
36
- // Open to visitors who are not signed in, so the handoff can be tried where signing in is not
37
- // possible (for example local development). Sign-in stays required everywhere else.
38
- allowGuestAgent: true
38
+ var FIELDS = {
39
+ minimumLength: "commerceSearchMinimumLength",
40
+ maxSuggestions: "commerceSearchMaxSuggestions",
41
+ agentAppKey: "commerceSearchAgentAppKey",
42
+ agentApiBaseUrl: "commerceSearchAgentApiBaseUrl",
43
+ enabledGuest: "commerceSearchAgentEnabledGuest",
44
+ enabledBrandAffiliate: "commerceSearchAgentEnabledBrandAffiliate",
45
+ enabledPreferredCustomer: "commerceSearchAgentEnabledPreferredCustomer",
46
+ enabledRetailCustomer: "commerceSearchAgentEnabledRetailCustomer"
39
47
  };
40
48
  function nonEmptyString(value) {
41
49
  return typeof value === "string" ? value.trim() : "";
42
50
  }
51
+ function wholeNumber(value) {
52
+ const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
53
+ return typeof number === "number" && Number.isInteger(number) ? number : void 0;
54
+ }
55
+ function positiveWholeNumber(value) {
56
+ const number = wholeNumber(value);
57
+ return number !== void 0 && number >= 1 ? number : void 0;
58
+ }
43
59
  function normalizeCommerceSearchConfig(raw, { language } = {}) {
44
- const minimumSearchLength = Number(raw?.minimumSearchLength);
45
- const appKey = nonEmptyString(raw?.agent?.appKey);
46
- const apiBaseUrl = nonEmptyString(raw?.agent?.apiBaseUrl);
60
+ const appKey = nonEmptyString(raw?.[FIELDS.agentAppKey]);
61
+ const apiBaseUrl = nonEmptyString(raw?.[FIELDS.agentApiBaseUrl]);
62
+ const maxSuggestions = positiveWholeNumber(raw?.[FIELDS.maxSuggestions]);
47
63
  return {
48
- minimumSearchLength: Number.isInteger(minimumSearchLength) && minimumSearchLength >= 1 ? minimumSearchLength : COMMERCE_SEARCH_CONFIG_DEFAULTS.minimumSearchLength,
64
+ minimumSearchLength: positiveWholeNumber(raw?.[FIELDS.minimumLength]) ?? COMMERCE_SEARCH_CONFIG_DEFAULTS.minimumSearchLength,
65
+ maxSuggestions: maxSuggestions === void 0 ? COMMERCE_SEARCH_CONFIG_DEFAULTS.maxSuggestions : Math.min(maxSuggestions, MAX_SUGGESTIONS_CEILING),
49
66
  ...appKey && apiBaseUrl ? {
50
67
  agent: {
51
68
  appKey,
@@ -53,13 +70,36 @@ function normalizeCommerceSearchConfig(raw, { language } = {}) {
53
70
  ...language ? { language } : {}
54
71
  }
55
72
  } : {},
56
- ...raw?.allowGuestAgent === true ? { allowGuestAgent: true } : {}
73
+ agentEnabledFor: {
74
+ guest: raw?.[FIELDS.enabledGuest] === true,
75
+ brandAffiliate: raw?.[FIELDS.enabledBrandAffiliate] === true,
76
+ preferredCustomer: raw?.[FIELDS.enabledPreferredCustomer] === true,
77
+ retailCustomer: raw?.[FIELDS.enabledRetailCustomer] === true
78
+ }
57
79
  };
58
80
  }
59
81
  async function fetchCommerceSearchConfig({
60
- language
61
- } = {}) {
62
- const raw = getEnvironmentCode() === "test" ? TEST_ENVIRONMENT_VALUES : {};
82
+ country,
83
+ language,
84
+ clientId
85
+ }) {
86
+ const configuration = await getConfiguration([COMMERCE_SEARCH_MAP], {
87
+ country: country.trim().toUpperCase(),
88
+ ...clientId ? { clientId } : {}
89
+ });
90
+ const raw = configuration?.[COMMERCE_SEARCH_MAP];
91
+ if (!raw || Object.keys(raw).length === 0) {
92
+ throw new Error(
93
+ `the configuration service returned no ${COMMERCE_SEARCH_MAP} for market ${country}`
94
+ );
95
+ }
96
+ const hasKey = nonEmptyString(raw[FIELDS.agentAppKey]) !== "";
97
+ const hasUrl = nonEmptyString(raw[FIELDS.agentApiBaseUrl]) !== "";
98
+ if (hasKey !== hasUrl) {
99
+ console.warn(
100
+ `[search] The agent is half configured: ${FIELDS.agentAppKey} and ${FIELDS.agentApiBaseUrl} must both be set, or both empty. Using plain search.`
101
+ );
102
+ }
63
103
  return normalizeCommerceSearchConfig(raw, language ? { language } : {});
64
104
  }
65
105
 
@@ -67,30 +107,39 @@ async function fetchCommerceSearchConfig({
67
107
  function useCommerceSearchConfig({
68
108
  country,
69
109
  language,
70
- enabled
110
+ clientId,
111
+ isOpen
71
112
  }) {
72
- const [config, setConfig] = useState(
73
- COMMERCE_SEARCH_CONFIG_DEFAULTS
74
- );
113
+ const market = `${country}|${language}|${clientId ?? ""}`;
114
+ const [loaded, setLoaded] = useState(null);
115
+ const currentMarket = useRef(market);
75
116
  useEffect(() => {
76
- if (!enabled) return void 0;
77
- let isStale = false;
117
+ currentMarket.current = market;
118
+ }, [market]);
119
+ const reading = useRef(null);
120
+ useEffect(() => {
121
+ if (!isOpen || loaded?.market === market || reading.current === market) {
122
+ return;
123
+ }
124
+ reading.current = market;
78
125
  fetchCommerceSearchConfig({
79
- ...country ? { country } : {},
80
- ...language ? { language } : {}
81
- }).then((next) => {
82
- if (!isStale) setConfig(next);
126
+ country,
127
+ language,
128
+ ...clientId ? { clientId } : {}
129
+ }).then((config) => {
130
+ if (currentMarket.current === market) {
131
+ setLoaded({ market, config });
132
+ }
83
133
  }).catch((error) => {
84
134
  console.warn(
85
135
  "[search] Could not load the product search configuration:",
86
136
  error
87
137
  );
138
+ }).finally(() => {
139
+ if (reading.current === market) reading.current = null;
88
140
  });
89
- return () => {
90
- isStale = true;
91
- };
92
- }, [enabled, country, language]);
93
- return config;
141
+ }, [isOpen, market, loaded, country, language, clientId]);
142
+ return loaded?.market === market ? loaded.config : COMMERCE_SEARCH_CONFIG_DEFAULTS;
94
143
  }
95
144
 
96
145
  // src/copy/searchPanelCopy.ts
@@ -173,25 +222,38 @@ function useSearchPanelCopy({
173
222
  }
174
223
 
175
224
  // src/services/localeParts.ts
176
- function normalizeLocaleParts({
225
+ var TWO_LETTERS = /^[A-Za-z]{2}$/;
226
+ function marketProblem(country, language) {
227
+ const countryText = String(country ?? "").trim();
228
+ const languageText = String(language ?? "").trim();
229
+ if (TWO_LETTERS.test(countryText) && TWO_LETTERS.test(languageText)) {
230
+ return void 0;
231
+ }
232
+ return `the market is not valid (country ${JSON.stringify(countryText)}, language ${JSON.stringify(languageText)}): each must be two letters, for example US and en`;
233
+ }
234
+ function isValidMarket(country, language) {
235
+ return marketProblem(country, language) === void 0;
236
+ }
237
+ function createLocaleParts({
177
238
  country,
178
- language,
179
- locale
239
+ language
180
240
  }) {
181
- const normalizedCountry = String(country ?? "").trim().toUpperCase();
182
- const normalizedLanguage = String(language ?? "").trim().toLowerCase();
183
- const normalizedLocale = String(locale ?? "").trim() || "UNKNOWN";
241
+ const problem = marketProblem(country, language);
242
+ if (problem) throw new Error(problem);
243
+ const normalizedCountry = country.trim().toUpperCase();
244
+ const normalizedLanguage = language.trim().toLowerCase();
184
245
  return {
185
- country: normalizedCountry || "UNKNOWN",
186
- language: normalizedLanguage || "unknown",
187
- locale: normalizedLocale
246
+ country: normalizedCountry,
247
+ language: normalizedLanguage,
248
+ locale: `${normalizedCountry}-${normalizedLanguage}`
188
249
  };
189
250
  }
190
- function toCatalogLocale(localeParts) {
191
- const language = String(localeParts.language ?? "en").toLowerCase();
192
- const country = String(localeParts.country ?? "US").toUpperCase();
251
+ function toCatalogLocale({ country, language }) {
193
252
  return `${language}_${country}`;
194
253
  }
254
+ function toPathPrefix({ country, language }) {
255
+ return `/${country.toLowerCase()}/${language}`;
256
+ }
195
257
 
196
258
  // src/services/catalogRequest.ts
197
259
  function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
@@ -216,13 +278,14 @@ function buildCatalogSuggestionsUrl(apiBaseUrl) {
216
278
  function buildSuggestionsRequestUrl({
217
279
  suggestionsUrl,
218
280
  searchTerm,
281
+ size,
219
282
  storeId,
220
283
  localeParts
221
284
  }) {
222
285
  const queryParams = new URLSearchParams({
223
286
  searchTerm,
224
287
  page: String(DEFAULT_SUGGESTION_PAGE),
225
- size: String(DEFAULT_SUGGESTION_SIZE),
288
+ size: String(size),
226
289
  locale: toCatalogLocale(localeParts),
227
290
  storeId
228
291
  });
@@ -234,12 +297,21 @@ async function resolveCatalogRequestConfig({
234
297
  getMarketConfig
235
298
  }) {
236
299
  const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
237
- return {
238
- suggestionsUrl: buildCatalogSuggestionsUrl(
239
- resolveCatalogApiBaseUrl(marketConfig)
240
- ),
241
- storeId: resolveCatalogStoreId(marketConfig)
242
- };
300
+ const suggestionsUrl = buildCatalogSuggestionsUrl(
301
+ resolveCatalogApiBaseUrl(marketConfig)
302
+ );
303
+ if (!suggestionsUrl) {
304
+ throw new Error(
305
+ `no suggestions API for market ${country}: the market configuration has no gateway`
306
+ );
307
+ }
308
+ const storeId = resolveCatalogStoreId(marketConfig);
309
+ if (!storeId) {
310
+ throw new Error(
311
+ `no suggestions API for market ${country}: the market configuration has no store`
312
+ );
313
+ }
314
+ return { suggestionsUrl, storeId };
243
315
  }
244
316
  function resolveCustomerTypeHeaders(getCustomerType) {
245
317
  const customerType = getCustomerType?.()?.trim();
@@ -284,7 +356,7 @@ function getSessionStorage(providedStorage) {
284
356
  return null;
285
357
  }
286
358
  function getRecentHeaderSearchesStorageKey(localeParts) {
287
- const { country, language, locale } = normalizeLocaleParts(localeParts);
359
+ const { country, language, locale } = localeParts;
288
360
  return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
289
361
  }
290
362
  function getRecentHeaderSearches(localeParts, storage) {
@@ -347,13 +419,14 @@ function buildSearchResultsPath(query, localeParts) {
347
419
  /%2D/gi,
348
420
  "-"
349
421
  );
350
- const country = localeParts.country.toLowerCase();
351
- const language = localeParts.language.toLowerCase();
352
- return `/${country}/${language}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
422
+ return `${toPathPrefix(localeParts)}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
353
423
  }
424
+ var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
354
425
  function normalizeOptionUrl(rawValue) {
355
426
  if (typeof rawValue !== "string") return "";
356
- return rawValue.trim();
427
+ const value = rawValue.trim();
428
+ if (!value || value.startsWith("/") || SCHEME.test(value)) return value;
429
+ return `/${value}`;
357
430
  }
358
431
  function safeDecodeURIComponent(value) {
359
432
  try {
@@ -383,16 +456,14 @@ function slugifyPathSegment(value) {
383
456
  function buildProductSlugPath(slugValue, localeParts) {
384
457
  const slug = slugifyPathSegment(slugValue);
385
458
  if (!slug) return "";
386
- const country = String(localeParts.country ?? "US").toLowerCase();
387
- const language = String(localeParts.language ?? "en").toLowerCase();
388
- return `/${country}/${language}/product/${slug}`;
459
+ return `${toPathPrefix(localeParts)}/product/${slug}`;
389
460
  }
390
461
  function isNavigableUrl(value) {
391
462
  if (!value) return false;
392
463
  return value.startsWith("/") || /^https?:\/\//i.test(value);
393
464
  }
394
465
  function stripLocalePrefix(path, localeParts) {
395
- const prefix = `/${localeParts.country.toLowerCase()}/${localeParts.language.toLowerCase()}`;
466
+ const prefix = toPathPrefix(localeParts);
396
467
  return path.startsWith(prefix) ? path.slice(prefix.length) || "/" : path;
397
468
  }
398
469
  function resolveOptionHref(option, localeParts) {
@@ -446,26 +517,21 @@ function getBrowserLocation(providedLocation) {
446
517
  return window.location;
447
518
  return null;
448
519
  }
449
- function resolveFetch(fetchImplOption) {
450
- if (fetchImplOption !== void 0) return fetchImplOption;
451
- return typeof fetch === "function" ? fetch.bind(globalThis) : null;
452
- }
453
- function createSearchService(options = {}) {
520
+ function createSearchService(options) {
454
521
  const {
455
522
  fetchImpl: fetchImplOption,
456
523
  location: providedLocation,
457
524
  storage: providedStorage,
458
525
  country,
459
526
  language,
460
- locale,
461
527
  getMarketConfig,
462
528
  getCustomerType,
463
- minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
529
+ minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
530
+ maxSuggestions = DEFAULT_MAX_SUGGESTIONS
464
531
  } = options;
465
- const fetchImpl = resolveFetch(fetchImplOption);
466
532
  const locationLike = getBrowserLocation(providedLocation);
467
533
  const storage = getSessionStorage(providedStorage);
468
- const localeParts = normalizeLocaleParts({ country, language, locale });
534
+ const localeParts = createLocaleParts({ country, language });
469
535
  let catalogRequestConfigPromise = null;
470
536
  function loadCatalogRequestConfig() {
471
537
  if (!catalogRequestConfigPromise) {
@@ -487,16 +553,16 @@ function createSearchService(options = {}) {
487
553
  const service = {
488
554
  async fetchSuggestions(searchTerm) {
489
555
  const trimmedSearchTerm = String(searchTerm ?? "").trim();
490
- if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
556
+ if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength) {
491
557
  return emptySuggestions();
492
558
  }
493
559
  const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
494
- if (!suggestionsUrl || !storeId) return emptySuggestions();
495
560
  const responsePayload = await fetchJson(
496
- fetchImpl,
561
+ fetchImplOption ?? fetch.bind(globalThis),
497
562
  buildSuggestionsRequestUrl({
498
563
  suggestionsUrl,
499
564
  searchTerm: trimmedSearchTerm,
565
+ size: maxSuggestions,
500
566
  storeId,
501
567
  localeParts
502
568
  }),
@@ -546,12 +612,15 @@ function createSearchService(options = {}) {
546
612
  }
547
613
 
548
614
  // src/services/marketConfig.ts
549
- var fetchMarketConfig = async ({ country }) => {
550
- const countryCode = String(country ?? "").trim().toUpperCase() || "US";
615
+ var fetchMarketConfig = async ({
616
+ country,
617
+ clientId
618
+ }) => {
619
+ const countryCode = country.trim().toUpperCase();
551
620
  const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
552
621
  country: countryCode,
553
622
  ...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
554
- clientId: DEFAULT_EQUINOX_CLIENT_ID,
623
+ ...clientId ? { clientId } : {},
555
624
  shoppingContext: STOREFRONT_SHOPPING_CONTEXT
556
625
  });
557
626
  return configuration?.[EQUINOX_MARKETS_MAP] ?? null;
@@ -570,14 +639,25 @@ var SEARCH_GTM_USER_STATUS = {
570
639
  var GTM_FALLBACK_NA = "NA";
571
640
 
572
641
  // src/utils/navigationTarget.ts
642
+ var EXTERNAL_URL = /^https?:\/\//i;
643
+ var INTERNAL_PATH = /^\/(?![/\\])[^\t\n\r]*$/;
573
644
  function toNavigationTarget(path) {
574
- return /^https?:\/\//i.test(path) ? { type: "external", url: path } : { type: "internal", path };
645
+ if (EXTERNAL_URL.test(path)) return { type: "external", url: path };
646
+ if (INTERNAL_PATH.test(path)) return { type: "internal", path };
647
+ return void 0;
575
648
  }
576
649
 
577
650
  // src/utils/productEnvironment.ts
578
- var PRODUCT_ENVIRONMENTS = ["dev", "test", "prod"];
651
+ var PRODUCT_ENVIRONMENTS = {
652
+ dev: true,
653
+ test: true,
654
+ prod: true
655
+ };
656
+ function isProductEnvironment(code) {
657
+ return Object.keys(PRODUCT_ENVIRONMENTS).includes(code);
658
+ }
579
659
  function toProductEnvironment(environmentCode) {
580
- return environmentCode && PRODUCT_ENVIRONMENTS.includes(environmentCode) ? environmentCode : void 0;
660
+ return environmentCode !== void 0 && isProductEnvironment(environmentCode) ? environmentCode : void 0;
581
661
  }
582
662
  function getProductEnvironment() {
583
663
  try {
@@ -586,6 +666,47 @@ function getProductEnvironment() {
586
666
  return void 0;
587
667
  }
588
668
  }
669
+ var NOTHING = () => void 0;
670
+ function CommerceSearchUnavailable({
671
+ isOpen,
672
+ onClose,
673
+ country,
674
+ language,
675
+ logo
676
+ }) {
677
+ useEffect(() => {
678
+ console.error(
679
+ `[search] Search is unavailable: ${marketProblem(country, language)}`
680
+ );
681
+ }, [country, language]);
682
+ return /* @__PURE__ */ jsx(
683
+ SearchPanel,
684
+ {
685
+ isOpen,
686
+ onClose,
687
+ copy: SEARCH_PANEL_COPY_EN,
688
+ logo,
689
+ input: { value: "", onChange: NOTHING, onSubmit: NOTHING },
690
+ suggestions: {
691
+ items: [],
692
+ loading: false,
693
+ showNoResults: false,
694
+ showDidYouMean: false,
695
+ hasMore: false,
696
+ query: "",
697
+ onSelect: NOTHING,
698
+ onRunAction: NOTHING,
699
+ onSeeMore: NOTHING
700
+ },
701
+ recentSearches: {
702
+ items: [],
703
+ onSelect: NOTHING,
704
+ onClear: NOTHING
705
+ },
706
+ agentPanel: /* @__PURE__ */ jsx("p", { role: "alert", children: "Search is unavailable right now." })
707
+ }
708
+ );
709
+ }
589
710
 
590
711
  // src/utils/nuiSearchGate.ts
591
712
  var NUI_SEARCH_FLAG = "enableNuiSearch";
@@ -617,29 +738,23 @@ function resolveNuiSearchFlag(env = {}) {
617
738
  return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
618
739
  }
619
740
  function isNuiSearchEnabled({
620
- isAuthenticated,
621
741
  flag,
622
- allowGuests = false
742
+ enabledForCustomer
623
743
  }) {
624
- return flag === true && (isAuthenticated === true || allowGuests);
744
+ return flag === true && enabledForCustomer;
625
745
  }
626
746
 
627
747
  // src/hooks/useNuiAgentGate.ts
628
748
  function useNuiAgentGate({
629
749
  hasAgent,
630
750
  isOpen,
631
- isAuthenticated,
632
- allowGuestAgent
751
+ enabledForCustomer
633
752
  }) {
634
753
  const [flag, setFlag] = useState(void 0);
635
754
  useEffect(() => {
636
755
  if (isOpen) setFlag(resolveNuiSearchFlag());
637
756
  }, [isOpen]);
638
- return hasAgent && isNuiSearchEnabled({
639
- isAuthenticated,
640
- flag,
641
- allowGuests: allowGuestAgent
642
- });
757
+ return hasAgent && isNuiSearchEnabled({ flag, enabledForCustomer });
643
758
  }
644
759
  function uniqueRecentSearches(queries) {
645
760
  const seen = /* @__PURE__ */ new Set();
@@ -973,7 +1088,7 @@ var FETCH_FAILED = {
973
1088
  emptyFetched: true,
974
1089
  failed: true
975
1090
  };
976
- function toSettledResults(response) {
1091
+ function toSettledResults(response, maxSuggestions) {
977
1092
  const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
978
1093
  if (didYouMean.length > 0) {
979
1094
  return {
@@ -988,18 +1103,19 @@ function toSettledResults(response) {
988
1103
  resolveTotalResultsCount2(response, allSuggestions.length),
989
1104
  allSuggestions.length
990
1105
  );
991
- const suggestions = allSuggestions.slice(0, MAX_VISIBLE_SUGGESTIONS);
1106
+ const suggestions = allSuggestions.slice(0, maxSuggestions);
992
1107
  return {
993
1108
  suggestions,
994
1109
  didYouMean,
995
- hasMoreResults: total > MAX_VISIBLE_SUGGESTIONS,
1110
+ hasMoreResults: total > maxSuggestions,
996
1111
  emptyFetched: suggestions.length === 0,
997
1112
  failed: false
998
1113
  };
999
1114
  }
1000
1115
  function useSearchSuggestions({
1001
1116
  service,
1002
- enabled = true
1117
+ enabled = true,
1118
+ maxSuggestions = DEFAULT_MAX_SUGGESTIONS
1003
1119
  }) {
1004
1120
  const [query, setQuery] = useState("");
1005
1121
  const [suggestions, setSuggestions] = useState([]);
@@ -1056,7 +1172,8 @@ function useSearchSuggestions({
1056
1172
  let results;
1057
1173
  try {
1058
1174
  results = toSettledResults(
1059
- await service.fetchSuggestions(trimmedQuery)
1175
+ await service.fetchSuggestions(trimmedQuery),
1176
+ maxSuggestions
1060
1177
  );
1061
1178
  } catch (error) {
1062
1179
  console.warn("[search] Could not load suggestions:", error);
@@ -1066,9 +1183,10 @@ function useSearchSuggestions({
1066
1183
  settle(results, trimmedQuery);
1067
1184
  setLoading(false);
1068
1185
  },
1069
- [enabled, service, resetSuggestionsState, settle]
1186
+ [enabled, maxSuggestions, service, resetSuggestionsState, settle]
1070
1187
  );
1071
1188
  useEffect(() => {
1189
+ latestRequestId.current += 1;
1072
1190
  if (!enabled) {
1073
1191
  resetSuggestionsState();
1074
1192
  return void 0;
@@ -1076,7 +1194,6 @@ function useSearchSuggestions({
1076
1194
  if (fetchDebounced.current) {
1077
1195
  clearTimeout(fetchDebounced.current);
1078
1196
  }
1079
- latestRequestId.current += 1;
1080
1197
  fetchDebounced.current = setTimeout(() => {
1081
1198
  void runFetch(query);
1082
1199
  }, DEBOUNCE_MS);
@@ -1358,11 +1475,11 @@ function useHandoff({
1358
1475
  onUnavailableRef.current = onUnavailable;
1359
1476
  });
1360
1477
  useEffect(() => {
1361
- let cancelled = false;
1478
+ let canceled = false;
1362
1479
  let timer;
1363
1480
  const startedAt = Date.now();
1364
1481
  const tryOpen = () => {
1365
- if (cancelled) return;
1482
+ if (canceled) return;
1366
1483
  const handle = getSalesAIHandle();
1367
1484
  if (handle) {
1368
1485
  handle.open({
@@ -1382,7 +1499,7 @@ function useHandoff({
1382
1499
  };
1383
1500
  tryOpen();
1384
1501
  return () => {
1385
- cancelled = true;
1502
+ canceled = true;
1386
1503
  if (timer !== void 0) clearTimeout(timer);
1387
1504
  };
1388
1505
  }, [query]);
@@ -1423,12 +1540,12 @@ function CommerceSearchView({
1423
1540
  onClose,
1424
1541
  service,
1425
1542
  navigate,
1426
- isAuthenticated,
1427
1543
  agent,
1428
- allowGuestAgent = false,
1544
+ agentEnabledForCustomer,
1429
1545
  agentProducts,
1430
1546
  logo,
1431
1547
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
1548
+ maxSuggestions = DEFAULT_MAX_SUGGESTIONS,
1432
1549
  gtmContext
1433
1550
  }) {
1434
1551
  const [handoff, setHandoff] = useState(null);
@@ -1436,6 +1553,7 @@ function CommerceSearchView({
1436
1553
  const [pendingTerm, setPendingTerm] = useState(null);
1437
1554
  const search = useSearchSuggestions({
1438
1555
  service,
1556
+ maxSuggestions,
1439
1557
  enabled: isOpen && agentQuery === null
1440
1558
  });
1441
1559
  const {
@@ -1451,8 +1569,7 @@ function CommerceSearchView({
1451
1569
  const agentEnabled = useNuiAgentGate({
1452
1570
  hasAgent: agent !== void 0,
1453
1571
  isOpen,
1454
- isAuthenticated,
1455
- allowGuestAgent
1572
+ enabledForCustomer: agentEnabledForCustomer
1456
1573
  });
1457
1574
  const { recentSearches, clearRecentSearches } = useRecentSearches({
1458
1575
  service,
@@ -1610,43 +1727,89 @@ function CommerceSearchView({
1610
1727
  );
1611
1728
  }
1612
1729
  var GUEST = "guest";
1613
- function CommerceSearch({
1730
+ var AGENT_SWITCH_FOR = {
1731
+ guest: "guest",
1732
+ "Brand Affiliate": "brandAffiliate",
1733
+ "Retail Customer": "retailCustomer",
1734
+ "Preferred Customer/Member": "preferredCustomer"
1735
+ };
1736
+ var AGENT_SWITCHES = new Map(
1737
+ Object.entries(AGENT_SWITCH_FOR)
1738
+ );
1739
+ function CommerceSearch(props) {
1740
+ if (!isValidMarket(props.country, props.language)) {
1741
+ return /* @__PURE__ */ jsx(
1742
+ CommerceSearchUnavailable,
1743
+ {
1744
+ isOpen: props.isOpen,
1745
+ onClose: props.onClose,
1746
+ country: props.country,
1747
+ language: props.language,
1748
+ logo: props.logo
1749
+ }
1750
+ );
1751
+ }
1752
+ return /* @__PURE__ */ jsx(ValidMarketSearch, { ...props });
1753
+ }
1754
+ function ValidMarketSearch({
1614
1755
  isOpen,
1615
1756
  onClose,
1616
1757
  navigate,
1617
1758
  customer,
1618
1759
  country,
1619
1760
  language,
1761
+ clientId,
1620
1762
  logo
1621
1763
  }) {
1622
- const [hasOpened, setHasOpened] = useState(false);
1623
- useEffect(() => {
1624
- if (isOpen) setHasOpened(true);
1625
- }, [isOpen]);
1626
1764
  const copy = useSearchPanelCopy({ language });
1627
- const { minimumSearchLength, agent, allowGuestAgent } = useCommerceSearchConfig({
1765
+ const { minimumSearchLength, maxSuggestions, agent, agentEnabledFor } = useCommerceSearchConfig({
1628
1766
  country,
1629
1767
  language,
1630
- enabled: hasOpened
1768
+ ...clientId ? { clientId } : {},
1769
+ isOpen
1631
1770
  });
1632
1771
  const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
1633
1772
  const customerTypeRef = useRef(customerType);
1634
1773
  useEffect(() => {
1635
1774
  customerTypeRef.current = customerType;
1636
1775
  }, [customerType]);
1776
+ const agentEnabledForCustomer = agentEnabledFor[AGENT_SWITCHES.get(customerType) ?? "guest"];
1777
+ const getMarketConfig = useCallback(
1778
+ (context) => fetchMarketConfig({
1779
+ ...context,
1780
+ ...clientId ? { clientId } : {}
1781
+ }),
1782
+ [clientId]
1783
+ );
1637
1784
  const service = useMemo(
1638
1785
  () => createSearchService({
1639
1786
  country,
1640
1787
  language,
1641
- locale: `${country.toUpperCase()}-${language.toLowerCase()}`,
1642
- getMarketConfig: fetchMarketConfig,
1788
+ getMarketConfig,
1643
1789
  minimumSearchLength,
1790
+ maxSuggestions,
1644
1791
  getCustomerType: () => customerTypeRef.current
1645
1792
  }),
1646
- [country, language, minimumSearchLength]
1793
+ [
1794
+ country,
1795
+ language,
1796
+ getMarketConfig,
1797
+ minimumSearchLength,
1798
+ maxSuggestions
1799
+ ]
1647
1800
  );
1648
1801
  const navigateToPath = useCallback(
1649
- (path) => navigate(toNavigationTarget(path)),
1802
+ (path) => {
1803
+ const target = toNavigationTarget(path);
1804
+ if (target) {
1805
+ navigate(target);
1806
+ } else {
1807
+ console.warn(
1808
+ "[search] Not navigating to a path the SDK does not recognize:",
1809
+ path
1810
+ );
1811
+ }
1812
+ },
1650
1813
  [navigate]
1651
1814
  );
1652
1815
  const { isAuthenticated, accountType, userId } = customer;
@@ -1676,13 +1839,13 @@ function CommerceSearch({
1676
1839
  onClose,
1677
1840
  service,
1678
1841
  navigate: navigateToPath,
1679
- isAuthenticated,
1680
1842
  ...agent ? { agent } : {},
1681
- ...allowGuestAgent ? { allowGuestAgent } : {},
1843
+ agentEnabledForCustomer,
1682
1844
  ...agentProducts ? { agentProducts } : {},
1683
1845
  copy,
1684
1846
  logo,
1685
1847
  minimumSearchLength,
1848
+ maxSuggestions,
1686
1849
  gtmContext
1687
1850
  }
1688
1851
  );