@nuskin/react-search-sdk 1.0.0 → 1.0.1-code-cleanup.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/dist/index.js CHANGED
@@ -3,11 +3,11 @@
3
3
  var react = require('react');
4
4
  var configurationSdk = require('@nuskin/configuration-sdk');
5
5
  var reactSearchComponents = require('@nuskin/react-search-components');
6
+ var jsxRuntime = require('react/jsx-runtime');
6
7
  var reactNuiChatSdk = require('@nuskin/react-nui-chat-sdk');
7
8
  require('@nuskin/react-nui-chat-sdk/styles.css');
8
9
  var reactProductSdk = require('@nuskin/react-product-sdk');
9
10
  var styled = require('@emotion/styled');
10
- var jsxRuntime = require('react/jsx-runtime');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
13
 
@@ -127,21 +127,41 @@ async function fetchSearchPanelCopy(_options = {}) {
127
127
 
128
128
  // src/copy/loadSearchPanelCopy.ts
129
129
  var copyByLanguage = /* @__PURE__ */ new Map();
130
+ var loadedByLanguage = /* @__PURE__ */ new Map();
131
+ function cacheKey(language) {
132
+ return (language ?? "").toLowerCase();
133
+ }
130
134
  function loadSearchPanelCopy(language) {
131
- const key = (language ?? "").toLowerCase();
135
+ const key = cacheKey(language);
132
136
  const cached = copyByLanguage.get(key);
133
137
  if (cached) return cached;
134
138
  const pending = fetchSearchPanelCopy(language ? { } : {});
135
139
  copyByLanguage.set(key, pending);
136
- pending.catch(() => copyByLanguage.delete(key));
140
+ pending.then(
141
+ (copy) => {
142
+ if (copyByLanguage.get(key) === pending) {
143
+ loadedByLanguage.set(key, copy);
144
+ }
145
+ },
146
+ () => {
147
+ if (copyByLanguage.get(key) === pending) {
148
+ copyByLanguage.delete(key);
149
+ }
150
+ }
151
+ );
137
152
  return pending;
138
153
  }
154
+ function peekSearchPanelCopy(language) {
155
+ return loadedByLanguage.get(cacheKey(language));
156
+ }
139
157
 
140
158
  // src/hooks/useSearchPanelCopy.ts
141
159
  function useSearchPanelCopy({
142
160
  language
143
161
  }) {
144
- const [copy, setCopy] = react.useState(void 0);
162
+ const [copy, setCopy] = react.useState(
163
+ () => peekSearchPanelCopy(language)
164
+ );
145
165
  react.useEffect(() => {
146
166
  let isStale = false;
147
167
  loadSearchPanelCopy(language).then((next) => {
@@ -159,7 +179,125 @@ function useSearchPanelCopy({
159
179
  return copy;
160
180
  }
161
181
 
162
- // src/services/SearchApiService.ts
182
+ // src/services/localeParts.ts
183
+ var TWO_LETTERS = /^[A-Za-z]{2}$/;
184
+ function marketProblem(country, language) {
185
+ const countryText = String(country ?? "").trim();
186
+ const languageText = String(language ?? "").trim();
187
+ if (TWO_LETTERS.test(countryText) && TWO_LETTERS.test(languageText)) {
188
+ return void 0;
189
+ }
190
+ return `the market is not valid (country ${JSON.stringify(countryText)}, language ${JSON.stringify(languageText)}): each must be two letters, for example US and en`;
191
+ }
192
+ function isValidMarket(country, language) {
193
+ return marketProblem(country, language) === void 0;
194
+ }
195
+ function createLocaleParts({
196
+ country,
197
+ language
198
+ }) {
199
+ const problem = marketProblem(country, language);
200
+ if (problem) throw new Error(problem);
201
+ const normalizedCountry = country.trim().toUpperCase();
202
+ const normalizedLanguage = language.trim().toLowerCase();
203
+ return {
204
+ country: normalizedCountry,
205
+ language: normalizedLanguage,
206
+ locale: `${normalizedCountry}-${normalizedLanguage}`
207
+ };
208
+ }
209
+ function toCatalogLocale({ country, language }) {
210
+ return `${language}_${country}`;
211
+ }
212
+ function toPathPrefix({ country, language }) {
213
+ return `/${country.toLowerCase()}/${language}`;
214
+ }
215
+
216
+ // src/services/catalogRequest.ts
217
+ function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
218
+ const apiGateway = String(apiGatewayPath ?? "").trim();
219
+ if (!apiGateway) return "";
220
+ return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
221
+ }
222
+ function resolveCatalogApiBaseUrl(marketConfig) {
223
+ return resolveApiGatewayPath(marketConfig?.api_gateway_path);
224
+ }
225
+ function resolveCatalogStoreId(marketConfig) {
226
+ return String(marketConfig?.store_id ?? "").trim();
227
+ }
228
+ function buildCatalogSuggestionsUrl(apiBaseUrl) {
229
+ const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
230
+ if (!normalizedBase) return "";
231
+ const suggestionsPath = normalizedBase.endsWith(
232
+ `/${STOREFRONT_SHOPPING_CONTEXT}`
233
+ ) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
234
+ return `${normalizedBase}${suggestionsPath}`;
235
+ }
236
+ function buildSuggestionsRequestUrl({
237
+ suggestionsUrl,
238
+ searchTerm,
239
+ storeId,
240
+ localeParts
241
+ }) {
242
+ const queryParams = new URLSearchParams({
243
+ searchTerm,
244
+ page: String(DEFAULT_SUGGESTION_PAGE),
245
+ size: String(DEFAULT_SUGGESTION_SIZE),
246
+ locale: toCatalogLocale(localeParts),
247
+ storeId
248
+ });
249
+ return `${suggestionsUrl}?${queryParams.toString()}`;
250
+ }
251
+ async function resolveCatalogRequestConfig({
252
+ locationLike,
253
+ country,
254
+ getMarketConfig
255
+ }) {
256
+ const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
257
+ const suggestionsUrl = buildCatalogSuggestionsUrl(
258
+ resolveCatalogApiBaseUrl(marketConfig)
259
+ );
260
+ if (!suggestionsUrl) {
261
+ throw new Error(
262
+ `no suggestions API for market ${country}: the market configuration has no gateway`
263
+ );
264
+ }
265
+ const storeId = resolveCatalogStoreId(marketConfig);
266
+ if (!storeId) {
267
+ throw new Error(
268
+ `no suggestions API for market ${country}: the market configuration has no store`
269
+ );
270
+ }
271
+ return { suggestionsUrl, storeId };
272
+ }
273
+ function resolveCustomerTypeHeaders(getCustomerType) {
274
+ const customerType = getCustomerType?.()?.trim();
275
+ return customerType ? { "X-Customer-Type": customerType } : void 0;
276
+ }
277
+ var NO_RESULTS_ERROR_CODE = "SFOSER002";
278
+ async function isNoResultsResponse(response) {
279
+ if (response.status !== 404) return false;
280
+ try {
281
+ const body = await response.json();
282
+ return body?.code === NO_RESULTS_ERROR_CODE;
283
+ } catch {
284
+ return false;
285
+ }
286
+ }
287
+ async function fetchJson(fetchImpl, requestUrl, requestHeaders = {}) {
288
+ const response = await fetchImpl(requestUrl, {
289
+ method: "GET",
290
+ credentials: "include",
291
+ headers: { Accept: "application/json", ...requestHeaders }
292
+ });
293
+ if (!response.ok) {
294
+ if (await isNoResultsResponse(response)) return null;
295
+ throw new Error(`Request failed with status ${response.status}`);
296
+ }
297
+ return response.json();
298
+ }
299
+
300
+ // src/services/recentSearches.ts
163
301
  function canUseStorage(storage) {
164
302
  return Boolean(storage) && typeof storage.getItem === "function";
165
303
  }
@@ -174,22 +312,8 @@ function getSessionStorage(providedStorage) {
174
312
  }
175
313
  return null;
176
314
  }
177
- function normalizeLocaleParts({
178
- country,
179
- language,
180
- locale
181
- }) {
182
- const normalizedCountry = String(country ?? "").trim().toUpperCase();
183
- const normalizedLanguage = String(language ?? "").trim().toLowerCase();
184
- const normalizedLocale = String(locale ?? "").trim() || "UNKNOWN";
185
- return {
186
- country: normalizedCountry || "UNKNOWN",
187
- language: normalizedLanguage || "unknown",
188
- locale: normalizedLocale
189
- };
190
- }
191
315
  function getRecentHeaderSearchesStorageKey(localeParts) {
192
- const { country, language, locale } = normalizeLocaleParts(localeParts);
316
+ const { country, language, locale } = localeParts;
193
317
  return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
194
318
  }
195
319
  function getRecentHeaderSearches(localeParts, storage) {
@@ -243,63 +367,8 @@ function clearRecentHeaderSearches(localeParts, storage) {
243
367
  } catch {
244
368
  }
245
369
  }
246
- function getBrowserLocation(providedLocation) {
247
- if (providedLocation) return providedLocation;
248
- if (typeof window !== "undefined" && window.location)
249
- return window.location;
250
- return null;
251
- }
252
- function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
253
- const apiGateway = String(apiGatewayPath ?? "").trim();
254
- if (!apiGateway) return "";
255
- return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
256
- }
257
- function resolveCatalogApiBaseUrl(marketConfig) {
258
- return resolveApiGatewayPath(marketConfig?.api_gateway_path);
259
- }
260
- function resolveCatalogStoreId(marketConfig) {
261
- return String(marketConfig?.store_id ?? "").trim();
262
- }
263
- function buildCatalogSuggestionsUrl(apiBaseUrl) {
264
- const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
265
- if (!normalizedBase) return "";
266
- const suggestionsPath = normalizedBase.endsWith(
267
- `/${STOREFRONT_SHOPPING_CONTEXT}`
268
- ) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
269
- return `${normalizedBase}${suggestionsPath}`;
270
- }
271
- function resolveSuggestionsUrl(suggestionsApi, locationLike) {
272
- const pathOrUrl = String(suggestionsApi ?? "").trim();
273
- if (!pathOrUrl) return "";
274
- if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
275
- return pathOrUrl;
276
- }
277
- const normalizedPath = pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`;
278
- const origin = String(locationLike?.origin ?? "").trim();
279
- return origin ? `${origin}${normalizedPath}` : normalizedPath;
280
- }
281
- async function resolveCatalogRequestConfig({
282
- suggestionsApi,
283
- storeIdOption,
284
- locationLike,
285
- country,
286
- getMarketConfig
287
- }) {
288
- const explicitStoreId = storeIdOption !== void 0 && storeIdOption !== null && String(storeIdOption).trim() !== "" ? String(storeIdOption).trim() : "";
289
- const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
290
- const configuredApiBase = resolveCatalogApiBaseUrl(marketConfig);
291
- const configuredStoreId = resolveCatalogStoreId(marketConfig);
292
- const explicitSuggestionsApi = String(suggestionsApi ?? "").trim();
293
- return {
294
- suggestionsUrl: explicitSuggestionsApi ? resolveSuggestionsUrl(explicitSuggestionsApi, locationLike) : buildCatalogSuggestionsUrl(configuredApiBase),
295
- storeId: explicitStoreId || configuredStoreId
296
- };
297
- }
298
- function toCatalogLocale(localeParts) {
299
- const language = String(localeParts.language ?? "en").toLowerCase();
300
- const country = String(localeParts.country ?? "US").toUpperCase();
301
- return `${language}_${country}`;
302
- }
370
+
371
+ // src/services/searchPaths.ts
303
372
  function buildSearchResultsPath(query, localeParts) {
304
373
  const trimmedQuery = String(query ?? "").trim();
305
374
  const hyphenatedQuery = trimmedQuery.replace(/\s+/g, "-");
@@ -307,13 +376,14 @@ function buildSearchResultsPath(query, localeParts) {
307
376
  /%2D/gi,
308
377
  "-"
309
378
  );
310
- const country = localeParts.country.toLowerCase();
311
- const language = localeParts.language.toLowerCase();
312
- return `/${country}/${language}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
379
+ return `${toPathPrefix(localeParts)}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
313
380
  }
381
+ var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
314
382
  function normalizeOptionUrl(rawValue) {
315
383
  if (typeof rawValue !== "string") return "";
316
- return rawValue.trim();
384
+ const value = rawValue.trim();
385
+ if (!value || value.startsWith("/") || SCHEME.test(value)) return value;
386
+ return `/${value}`;
317
387
  }
318
388
  function safeDecodeURIComponent(value) {
319
389
  try {
@@ -343,16 +413,14 @@ function slugifyPathSegment(value) {
343
413
  function buildProductSlugPath(slugValue, localeParts) {
344
414
  const slug = slugifyPathSegment(slugValue);
345
415
  if (!slug) return "";
346
- const country = String(localeParts.country ?? "US").toLowerCase();
347
- const language = String(localeParts.language ?? "en").toLowerCase();
348
- return `/${country}/${language}/product/${slug}`;
416
+ return `${toPathPrefix(localeParts)}/product/${slug}`;
349
417
  }
350
418
  function isNavigableUrl(value) {
351
419
  if (!value) return false;
352
420
  return value.startsWith("/") || /^https?:\/\//i.test(value);
353
421
  }
354
422
  function stripLocalePrefix(path, localeParts) {
355
- const prefix = `/${localeParts.country.toLowerCase()}/${localeParts.language.toLowerCase()}`;
423
+ const prefix = toPathPrefix(localeParts);
356
424
  return path.startsWith(prefix) ? path.slice(prefix.length) || "/" : path;
357
425
  }
358
426
  function resolveOptionHref(option, localeParts) {
@@ -369,67 +437,61 @@ function resolveOptionHref(option, localeParts) {
369
437
  }
370
438
  return "";
371
439
  }
372
- function resolveCustomerTypeHeaders(getCustomerType) {
373
- const customerType = getCustomerType?.()?.trim();
374
- return customerType ? { "X-Customer-Type": customerType } : void 0;
440
+
441
+ // src/services/suggestionsPayload.ts
442
+ function emptySuggestions() {
443
+ return { suggestions: [], didYouMean: [], totalResultsCount: 0 };
375
444
  }
376
- var NO_RESULTS_ERROR_CODE = "SFOSER002";
377
- async function isNoResultsResponse(response) {
378
- if (response.status !== 404) return false;
379
- try {
380
- const body = await response.json();
381
- return body?.code === NO_RESULTS_ERROR_CODE;
382
- } catch {
383
- return false;
445
+ function resolveTotalResultsCount(payload, fallback) {
446
+ if (typeof payload?.totalResultsCount === "number") {
447
+ return payload.totalResultsCount;
384
448
  }
449
+ const pageableTotal = Number(
450
+ payload?.pageableInfo?.totalCount ?? Number.NaN
451
+ );
452
+ return Number.isNaN(pageableTotal) ? fallback : pageableTotal;
385
453
  }
386
- async function fetchJsonFromCandidates(fetchImpl, requestUrls, requestHeaders = {}) {
387
- let lastError;
388
- for (const requestUrl of requestUrls) {
389
- try {
390
- const response = await fetchImpl(requestUrl, {
391
- method: "GET",
392
- credentials: "include",
393
- headers: { Accept: "application/json", ...requestHeaders }
394
- });
395
- if (!response.ok) {
396
- if (await isNoResultsResponse(response)) return null;
397
- lastError = new Error(
398
- `Request failed with status ${response.status}`
399
- );
400
- continue;
401
- }
402
- return await response.json();
403
- } catch (error) {
404
- lastError = error;
405
- }
406
- }
407
- throw lastError ?? new Error("Unable to fetch header search data");
454
+ function normalizeSuggestionsPayload(payload) {
455
+ const suggestions = Array.isArray(payload?.suggestions) ? payload.suggestions : [];
456
+ const products = Array.isArray(payload?.product) ? payload.product : null;
457
+ const rawDidYouMean = payload?.didYouMean ?? payload?.did_you_mean;
458
+ const didYouMean = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
459
+ return {
460
+ ...payload ?? {},
461
+ suggestions,
462
+ didYouMean,
463
+ totalResultsCount: resolveTotalResultsCount(
464
+ payload,
465
+ products?.length ?? suggestions.length
466
+ )
467
+ };
408
468
  }
409
- function createSearchService(options = {}) {
469
+
470
+ // src/services/SearchApiService.ts
471
+ function getBrowserLocation(providedLocation) {
472
+ if (providedLocation) return providedLocation;
473
+ if (typeof window !== "undefined" && window.location)
474
+ return window.location;
475
+ return null;
476
+ }
477
+ function createSearchService(options) {
410
478
  const {
411
479
  fetchImpl: fetchImplOption,
412
480
  location: providedLocation,
413
481
  storage: providedStorage,
414
- suggestionsApi,
415
- storeId: storeIdOption,
416
482
  country,
417
483
  language,
418
- locale,
419
484
  getMarketConfig,
420
485
  getCustomerType,
421
486
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
422
487
  } = options;
423
- const fetchImpl = fetchImplOption !== void 0 ? fetchImplOption : typeof fetch === "function" ? fetch.bind(globalThis) : null;
424
488
  const locationLike = getBrowserLocation(providedLocation);
425
489
  const storage = getSessionStorage(providedStorage);
426
- const localeParts = normalizeLocaleParts({ country, language, locale });
490
+ const localeParts = createLocaleParts({ country, language });
427
491
  let catalogRequestConfigPromise = null;
428
492
  function loadCatalogRequestConfig() {
429
493
  if (!catalogRequestConfigPromise) {
430
494
  catalogRequestConfigPromise = resolveCatalogRequestConfig({
431
- suggestionsApi,
432
- storeIdOption,
433
495
  locationLike,
434
496
  country: localeParts.country,
435
497
  getMarketConfig
@@ -447,48 +509,21 @@ function createSearchService(options = {}) {
447
509
  const service = {
448
510
  async fetchSuggestions(searchTerm) {
449
511
  const trimmedSearchTerm = String(searchTerm ?? "").trim();
450
- if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
451
- return {
452
- suggestions: [],
453
- didYouMean: [],
454
- totalResultsCount: 0
455
- };
512
+ if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength) {
513
+ return emptySuggestions();
456
514
  }
457
515
  const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
458
- if (!suggestionsUrl || !storeId) {
459
- return {
460
- suggestions: [],
461
- didYouMean: [],
462
- totalResultsCount: 0
463
- };
464
- }
465
- const queryParams = new URLSearchParams({
466
- searchTerm: trimmedSearchTerm,
467
- page: String(DEFAULT_SUGGESTION_PAGE),
468
- size: String(DEFAULT_SUGGESTION_SIZE),
469
- locale: toCatalogLocale(localeParts),
470
- storeId: String(storeId)
471
- });
472
- const requestUrls = [`${suggestionsUrl}?${queryParams.toString()}`];
473
- const responsePayload = await fetchJsonFromCandidates(
474
- fetchImpl,
475
- requestUrls,
516
+ const responsePayload = await fetchJson(
517
+ fetchImplOption ?? fetch.bind(globalThis),
518
+ buildSuggestionsRequestUrl({
519
+ suggestionsUrl,
520
+ searchTerm: trimmedSearchTerm,
521
+ storeId,
522
+ localeParts
523
+ }),
476
524
  resolveCustomerTypeHeaders(getCustomerType)
477
525
  );
478
- const suggestionsField = Array.isArray(responsePayload?.suggestions) ? responsePayload?.suggestions : [];
479
- const productField = Array.isArray(responsePayload?.product) ? responsePayload?.product : null;
480
- const rawDidYouMean = responsePayload?.didYouMean ?? responsePayload?.did_you_mean;
481
- const didYouMeanField = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
482
- const pageableTotal = Number(
483
- responsePayload?.pageableInfo?.totalCount ?? Number.NaN
484
- );
485
- const totalResultsCount = typeof responsePayload?.totalResultsCount === "number" ? responsePayload.totalResultsCount : !Number.isNaN(pageableTotal) ? pageableTotal : productField?.length ?? suggestionsField.length;
486
- return {
487
- ...responsePayload ?? {},
488
- suggestions: suggestionsField,
489
- didYouMean: didYouMeanField,
490
- totalResultsCount
491
- };
526
+ return normalizeSuggestionsPayload(responsePayload);
492
527
  },
493
528
  submitProductSearch(query, option) {
494
529
  const trimmedQuery = String(query ?? "").trim();
@@ -533,7 +568,7 @@ function createSearchService(options = {}) {
533
568
 
534
569
  // src/services/marketConfig.ts
535
570
  var fetchMarketConfig = async ({ country }) => {
536
- const countryCode = String(country ?? "").trim().toUpperCase() || "US";
571
+ const countryCode = country.trim().toUpperCase();
537
572
  const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
538
573
  country: countryCode,
539
574
  ...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
@@ -556,14 +591,25 @@ var SEARCH_GTM_USER_STATUS = {
556
591
  var GTM_FALLBACK_NA = "NA";
557
592
 
558
593
  // src/utils/navigationTarget.ts
594
+ var EXTERNAL_URL = /^https?:\/\//i;
595
+ var INTERNAL_PATH = /^\/(?![/\\])[^\t\n\r]*$/;
559
596
  function toNavigationTarget(path) {
560
- return /^https?:\/\//i.test(path) ? { type: "external", url: path } : { type: "internal", path };
597
+ if (EXTERNAL_URL.test(path)) return { type: "external", url: path };
598
+ if (INTERNAL_PATH.test(path)) return { type: "internal", path };
599
+ return void 0;
561
600
  }
562
601
 
563
602
  // src/utils/productEnvironment.ts
564
- var PRODUCT_ENVIRONMENTS = ["dev", "test", "prod"];
603
+ var PRODUCT_ENVIRONMENTS = {
604
+ dev: true,
605
+ test: true,
606
+ prod: true
607
+ };
608
+ function isProductEnvironment(code) {
609
+ return Object.keys(PRODUCT_ENVIRONMENTS).includes(code);
610
+ }
565
611
  function toProductEnvironment(environmentCode) {
566
- return environmentCode && PRODUCT_ENVIRONMENTS.includes(environmentCode) ? environmentCode : void 0;
612
+ return environmentCode !== void 0 && isProductEnvironment(environmentCode) ? environmentCode : void 0;
567
613
  }
568
614
  function getProductEnvironment() {
569
615
  try {
@@ -572,8 +618,132 @@ function getProductEnvironment() {
572
618
  return void 0;
573
619
  }
574
620
  }
621
+ var NOTHING = () => void 0;
622
+ function CommerceSearchUnavailable({
623
+ isOpen,
624
+ onClose,
625
+ country,
626
+ language,
627
+ logo
628
+ }) {
629
+ react.useEffect(() => {
630
+ console.error(
631
+ `[search] Search is unavailable: ${marketProblem(country, language)}`
632
+ );
633
+ }, [country, language]);
634
+ return /* @__PURE__ */ jsxRuntime.jsx(
635
+ reactSearchComponents.SearchPanel,
636
+ {
637
+ isOpen,
638
+ onClose,
639
+ copy: SEARCH_PANEL_COPY_EN,
640
+ logo,
641
+ input: { value: "", onChange: NOTHING, onSubmit: NOTHING },
642
+ suggestions: {
643
+ items: [],
644
+ loading: false,
645
+ showNoResults: false,
646
+ showDidYouMean: false,
647
+ hasMore: false,
648
+ query: "",
649
+ onSelect: NOTHING,
650
+ onRunAction: NOTHING,
651
+ onSeeMore: NOTHING
652
+ },
653
+ recentSearches: {
654
+ items: [],
655
+ onSelect: NOTHING,
656
+ onClear: NOTHING
657
+ },
658
+ agentPanel: /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: "Search is unavailable right now." })
659
+ }
660
+ );
661
+ }
575
662
 
576
- // src/utils/catalogHelpers.ts
663
+ // src/utils/nuiSearchGate.ts
664
+ var NUI_SEARCH_FLAG = "enableNuiSearch";
665
+ function parseFlagValue(raw) {
666
+ const value = raw?.trim().toLowerCase();
667
+ if (value === "true" || value === "1") return true;
668
+ if (value === "false" || value === "0") return false;
669
+ return void 0;
670
+ }
671
+ function readCookieFlag(cookie) {
672
+ for (const part of cookie.split(";")) {
673
+ const separator = part.indexOf("=");
674
+ if (separator < 0) continue;
675
+ if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
676
+ try {
677
+ return parseFlagValue(
678
+ decodeURIComponent(part.slice(separator + 1))
679
+ );
680
+ } catch {
681
+ return void 0;
682
+ }
683
+ }
684
+ return void 0;
685
+ }
686
+ function resolveNuiSearchFlag(env = {}) {
687
+ const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
688
+ const search = env.search ?? (inBrowser ? window.location.search : "");
689
+ const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
690
+ return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
691
+ }
692
+ function isNuiSearchEnabled({
693
+ isAuthenticated,
694
+ flag,
695
+ allowGuests = false
696
+ }) {
697
+ return flag === true && (isAuthenticated === true || allowGuests);
698
+ }
699
+
700
+ // src/hooks/useNuiAgentGate.ts
701
+ function useNuiAgentGate({
702
+ hasAgent,
703
+ isOpen,
704
+ isAuthenticated,
705
+ allowGuestAgent
706
+ }) {
707
+ const [flag, setFlag] = react.useState(void 0);
708
+ react.useEffect(() => {
709
+ if (isOpen) setFlag(resolveNuiSearchFlag());
710
+ }, [isOpen]);
711
+ return hasAgent && isNuiSearchEnabled({
712
+ isAuthenticated,
713
+ flag,
714
+ allowGuests: allowGuestAgent
715
+ });
716
+ }
717
+ function uniqueRecentSearches(queries) {
718
+ const seen = /* @__PURE__ */ new Set();
719
+ return queries.filter((query) => {
720
+ const key = query.trim().toLowerCase();
721
+ if (!key || seen.has(key)) return false;
722
+ seen.add(key);
723
+ return true;
724
+ });
725
+ }
726
+ function useRecentSearches({
727
+ service,
728
+ isOpen,
729
+ refreshKey
730
+ }) {
731
+ const [recentSearches, setRecentSearches] = react.useState([]);
732
+ react.useEffect(() => {
733
+ if (isOpen) {
734
+ setRecentSearches(
735
+ uniqueRecentSearches(service.getRecentSearches())
736
+ );
737
+ }
738
+ }, [isOpen, service, refreshKey]);
739
+ const clearRecentSearches = react.useCallback(() => {
740
+ service.clearRecentSearches();
741
+ setRecentSearches([]);
742
+ }, [service]);
743
+ return { recentSearches, clearRecentSearches };
744
+ }
745
+
746
+ // src/utils/catalogStatus.ts
577
747
  var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
578
748
  "discontinued",
579
749
  "preview",
@@ -621,6 +791,8 @@ function isSuggestionDisabled(suggestionOption) {
621
791
  !suggestionOption || suggestionOption["out_of_stock"] || suggestionOption["outOfStock"]
622
792
  );
623
793
  }
794
+
795
+ // src/utils/catalogHelpers.ts
624
796
  function getImageFromProperty(properties = {}) {
625
797
  let image = String(properties["primaryimage"] ?? "").trim() || String(properties["imageURL"] ?? "").trim();
626
798
  if (!image) {
@@ -858,6 +1030,46 @@ function normalizeSuggestionsResponse(response) {
858
1030
  didYouMean: normalizeDidYouMean(r.didYouMean)
859
1031
  };
860
1032
  }
1033
+ function resolveTotalResultsCount2(response, fallback) {
1034
+ if (typeof response.totalResultsCount === "number") {
1035
+ return response.totalResultsCount;
1036
+ }
1037
+ if (typeof response.pageableInfo?.totalCount === "number") {
1038
+ return response.pageableInfo.totalCount;
1039
+ }
1040
+ return fallback;
1041
+ }
1042
+ var FETCH_FAILED = {
1043
+ suggestions: [],
1044
+ didYouMean: [],
1045
+ hasMoreResults: false,
1046
+ emptyFetched: true,
1047
+ failed: true
1048
+ };
1049
+ function toSettledResults(response) {
1050
+ const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
1051
+ if (didYouMean.length > 0) {
1052
+ return {
1053
+ suggestions: [],
1054
+ didYouMean,
1055
+ hasMoreResults: false,
1056
+ emptyFetched: false,
1057
+ failed: false
1058
+ };
1059
+ }
1060
+ const total = Math.max(
1061
+ resolveTotalResultsCount2(response, allSuggestions.length),
1062
+ allSuggestions.length
1063
+ );
1064
+ const suggestions = allSuggestions.slice(0, MAX_VISIBLE_SUGGESTIONS);
1065
+ return {
1066
+ suggestions,
1067
+ didYouMean,
1068
+ hasMoreResults: total > MAX_VISIBLE_SUGGESTIONS,
1069
+ emptyFetched: suggestions.length === 0,
1070
+ failed: false
1071
+ };
1072
+ }
861
1073
  function useSearchSuggestions({
862
1074
  service,
863
1075
  enabled = true
@@ -869,7 +1081,6 @@ function useSearchSuggestions({
869
1081
  const [emptyFetched, setEmptyFetched] = react.useState(false);
870
1082
  const [suggestionFetchFailed, setSuggestionFetchFailed] = react.useState(false);
871
1083
  const [hasMoreResults, setHasMoreResults] = react.useState(false);
872
- const [totalResultsCount, setTotalResultsCount] = react.useState(0);
873
1084
  const [settledQuery, setSettledQuery] = react.useState("");
874
1085
  const fetchDebounced = react.useRef(null);
875
1086
  const latestRequestId = react.useRef(0);
@@ -879,7 +1090,6 @@ function useSearchSuggestions({
879
1090
  setEmptyFetched(false);
880
1091
  setSuggestionFetchFailed(false);
881
1092
  setHasMoreResults(false);
882
- setTotalResultsCount(0);
883
1093
  setSettledQuery("");
884
1094
  setLoading(false);
885
1095
  }, []);
@@ -897,6 +1107,14 @@ function useSearchSuggestions({
897
1107
  resetSuggestionsState();
898
1108
  setQuery("");
899
1109
  }, [resetSuggestionsState]);
1110
+ const settle = react.useCallback((results, term) => {
1111
+ setDidYouMean(results.didYouMean);
1112
+ setSuggestions(results.suggestions);
1113
+ setHasMoreResults(results.hasMoreResults);
1114
+ setSuggestionFetchFailed(results.failed);
1115
+ setSettledQuery(term);
1116
+ setEmptyFetched(results.emptyFetched);
1117
+ }, []);
900
1118
  const runFetch = react.useCallback(
901
1119
  async (nextQuery) => {
902
1120
  const trimmedQuery = nextQuery.trim();
@@ -908,53 +1126,23 @@ function useSearchSuggestions({
908
1126
  setLoading(true);
909
1127
  setEmptyFetched(false);
910
1128
  setSuggestionFetchFailed(false);
1129
+ let results;
911
1130
  try {
912
- const fetchedSuggestions = await service.fetchSuggestions(trimmedQuery);
913
- if (requestId !== latestRequestId.current) {
914
- return;
915
- }
916
- const {
917
- suggestions: allSuggestions,
918
- didYouMean: suggestedTerms
919
- } = normalizeSuggestionsResponse(fetchedSuggestions);
920
- const visibleSuggestions = allSuggestions.slice(
921
- 0,
922
- MAX_VISIBLE_SUGGESTIONS
923
- );
924
- const hasDidYouMean = suggestedTerms.length > 0;
925
- const normalizedTotal = typeof fetchedSuggestions.totalResultsCount === "number" ? fetchedSuggestions.totalResultsCount : typeof fetchedSuggestions.pageableInfo?.totalCount === "number" ? fetchedSuggestions.pageableInfo.totalCount : allSuggestions.length;
926
- const effectiveTotal = hasDidYouMean ? suggestedTerms.length : Math.max(normalizedTotal, allSuggestions.length);
927
- setDidYouMean(suggestedTerms);
928
- setSuggestions(hasDidYouMean ? [] : visibleSuggestions);
929
- setHasMoreResults(
930
- !hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
931
- );
932
- setTotalResultsCount(effectiveTotal);
933
- setSuggestionFetchFailed(false);
934
- setSettledQuery(trimmedQuery);
935
- setEmptyFetched(
936
- hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
1131
+ results = toSettledResults(
1132
+ await service.fetchSuggestions(trimmedQuery)
937
1133
  );
938
- } catch {
939
- if (requestId !== latestRequestId.current) {
940
- return;
941
- }
942
- setSuggestions([]);
943
- setDidYouMean([]);
944
- setEmptyFetched(true);
945
- setSuggestionFetchFailed(true);
946
- setHasMoreResults(false);
947
- setTotalResultsCount(0);
948
- setSettledQuery(trimmedQuery);
949
- } finally {
950
- if (requestId === latestRequestId.current) {
951
- setLoading(false);
952
- }
1134
+ } catch (error) {
1135
+ console.warn("[search] Could not load suggestions:", error);
1136
+ results = FETCH_FAILED;
953
1137
  }
1138
+ if (requestId !== latestRequestId.current) return;
1139
+ settle(results, trimmedQuery);
1140
+ setLoading(false);
954
1141
  },
955
- [enabled, service, resetSuggestionsState]
1142
+ [enabled, service, resetSuggestionsState, settle]
956
1143
  );
957
1144
  react.useEffect(() => {
1145
+ latestRequestId.current += 1;
958
1146
  if (!enabled) {
959
1147
  resetSuggestionsState();
960
1148
  return void 0;
@@ -962,7 +1150,6 @@ function useSearchSuggestions({
962
1150
  if (fetchDebounced.current) {
963
1151
  clearTimeout(fetchDebounced.current);
964
1152
  }
965
- latestRequestId.current += 1;
966
1153
  fetchDebounced.current = setTimeout(() => {
967
1154
  void runFetch(query);
968
1155
  }, DEBOUNCE_MS);
@@ -976,15 +1163,12 @@ function useSearchSuggestions({
976
1163
  clearQuery,
977
1164
  emptyFetched,
978
1165
  hasMoreResults,
979
- hasQuery: Boolean(query.trim()),
980
1166
  didYouMean,
981
1167
  loading,
982
1168
  query,
983
1169
  settledQuery,
984
- setQuery,
985
1170
  suggestionFetchFailed,
986
1171
  suggestions,
987
- totalResultsCount,
988
1172
  updateQuery
989
1173
  };
990
1174
  }
@@ -1059,43 +1243,6 @@ function trackProductSearch(input) {
1059
1243
  });
1060
1244
  }
1061
1245
 
1062
- // src/utils/nuiSearchGate.ts
1063
- var NUI_SEARCH_FLAG = "enableNuiSearch";
1064
- function parseFlagValue(raw) {
1065
- const value = raw?.trim().toLowerCase();
1066
- if (value === "true" || value === "1") return true;
1067
- if (value === "false" || value === "0") return false;
1068
- return void 0;
1069
- }
1070
- function readCookieFlag(cookie) {
1071
- for (const part of cookie.split(";")) {
1072
- const separator = part.indexOf("=");
1073
- if (separator < 0) continue;
1074
- if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
1075
- try {
1076
- return parseFlagValue(
1077
- decodeURIComponent(part.slice(separator + 1))
1078
- );
1079
- } catch {
1080
- return void 0;
1081
- }
1082
- }
1083
- return void 0;
1084
- }
1085
- function resolveNuiSearchFlag(env = {}) {
1086
- const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
1087
- const search = env.search ?? (inBrowser ? window.location.search : "");
1088
- const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
1089
- return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
1090
- }
1091
- function isNuiSearchEnabled({
1092
- isAuthenticated,
1093
- flag,
1094
- allowGuests = false
1095
- }) {
1096
- return flag === true && (isAuthenticated === true || allowGuests);
1097
- }
1098
-
1099
1246
  // src/utils/searchRouting.ts
1100
1247
  function decideSearchRoute({
1101
1248
  enabled,
@@ -1108,6 +1255,96 @@ function decideSearchRoute({
1108
1255
  return snapshot.suggestions.length > 0 ? "search" : "handoff";
1109
1256
  }
1110
1257
 
1258
+ // src/hooks/useSubmitRouting.ts
1259
+ var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
1260
+ function useSubmitRouting({
1261
+ enabled,
1262
+ minimumSearchLength,
1263
+ search,
1264
+ gtmContext,
1265
+ pendingTerm,
1266
+ setPendingTerm,
1267
+ onSearch,
1268
+ onHandoff
1269
+ }) {
1270
+ const {
1271
+ didYouMean,
1272
+ loading,
1273
+ settledQuery,
1274
+ suggestionFetchFailed,
1275
+ suggestions
1276
+ } = search;
1277
+ const decide = react.useCallback(
1278
+ (term) => decideSearchRoute({
1279
+ enabled,
1280
+ snapshot: {
1281
+ query: term,
1282
+ loading,
1283
+ settledQuery,
1284
+ suggestions,
1285
+ suggestionFetchFailed
1286
+ }
1287
+ }),
1288
+ [enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
1289
+ );
1290
+ const act = react.useCallback(
1291
+ (term, route) => {
1292
+ setPendingTerm(null);
1293
+ if (route === "handoff") {
1294
+ trackChatEntryPoint(gtmContext ?? {});
1295
+ onHandoff({
1296
+ query: term,
1297
+ didYouMean: didYouMean.map((item) => item.value)
1298
+ });
1299
+ return;
1300
+ }
1301
+ onSearch(term);
1302
+ },
1303
+ [didYouMean, gtmContext, onHandoff, onSearch, setPendingTerm]
1304
+ );
1305
+ const submit = react.useCallback(
1306
+ (value) => {
1307
+ const term = value.trim();
1308
+ if (!term) return;
1309
+ if (term.length < minimumSearchLength) {
1310
+ onSearch(term);
1311
+ return;
1312
+ }
1313
+ const route = decide(term);
1314
+ if (route === "wait") {
1315
+ setPendingTerm(term);
1316
+ return;
1317
+ }
1318
+ act(term, route);
1319
+ },
1320
+ [act, decide, minimumSearchLength, onSearch, setPendingTerm]
1321
+ );
1322
+ const latest = react.useRef({ act, decide });
1323
+ react.useEffect(() => {
1324
+ latest.current = { act, decide };
1325
+ });
1326
+ react.useEffect(() => {
1327
+ if (pendingTerm === null) return void 0;
1328
+ const route = latest.current.decide(pendingTerm);
1329
+ if (route !== "wait") {
1330
+ latest.current.act(pendingTerm, route);
1331
+ return void 0;
1332
+ }
1333
+ const timer = setTimeout(
1334
+ () => latest.current.act(pendingTerm, "search"),
1335
+ SUBMIT_WAIT_TIMEOUT_MS
1336
+ );
1337
+ return () => clearTimeout(timer);
1338
+ }, [
1339
+ pendingTerm,
1340
+ loading,
1341
+ settledQuery,
1342
+ suggestions,
1343
+ suggestionFetchFailed
1344
+ ]);
1345
+ return submit;
1346
+ }
1347
+
1111
1348
  // src/utils/cachedProductClient.ts
1112
1349
  function withProductCache(client) {
1113
1350
  const lookups = /* @__PURE__ */ new Map();
@@ -1175,7 +1412,8 @@ var AgentErrorBoundary = class extends react.Component {
1175
1412
  static getDerivedStateFromError() {
1176
1413
  return { failed: true };
1177
1414
  }
1178
- componentDidCatch(_error, _info) {
1415
+ componentDidCatch(error) {
1416
+ console.warn("[search] The agent could not be shown:", error);
1179
1417
  this.props.onError();
1180
1418
  }
1181
1419
  render() {
@@ -1188,12 +1426,16 @@ function useHandoff({
1188
1426
  agentContext,
1189
1427
  onUnavailable
1190
1428
  }) {
1429
+ const onUnavailableRef = react.useRef(onUnavailable);
1430
+ react.useEffect(() => {
1431
+ onUnavailableRef.current = onUnavailable;
1432
+ });
1191
1433
  react.useEffect(() => {
1192
- let cancelled = false;
1434
+ let canceled = false;
1193
1435
  let timer;
1194
1436
  const startedAt = Date.now();
1195
1437
  const tryOpen = () => {
1196
- if (cancelled) return;
1438
+ if (canceled) return;
1197
1439
  const handle = getSalesAIHandle();
1198
1440
  if (handle) {
1199
1441
  handle.open({
@@ -1203,14 +1445,17 @@ function useHandoff({
1203
1445
  return;
1204
1446
  }
1205
1447
  if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
1206
- onUnavailable();
1448
+ console.warn(
1449
+ `[search] The agent did not start within ${HANDLE_TIMEOUT_MS / 1e3} seconds.`
1450
+ );
1451
+ onUnavailableRef.current();
1207
1452
  return;
1208
1453
  }
1209
1454
  timer = setTimeout(tryOpen, HANDLE_POLL_MS);
1210
1455
  };
1211
1456
  tryOpen();
1212
1457
  return () => {
1213
- cancelled = true;
1458
+ canceled = true;
1214
1459
  if (timer !== void 0) clearTimeout(timer);
1215
1460
  };
1216
1461
  }, [query]);
@@ -1245,16 +1490,6 @@ function NuiAgentPanel({
1245
1490
  }
1246
1491
  ) });
1247
1492
  }
1248
- var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
1249
- function uniqueRecentSearches(queries) {
1250
- const seen = /* @__PURE__ */ new Set();
1251
- return queries.filter((query) => {
1252
- const key = query.trim().toLowerCase();
1253
- if (!key || seen.has(key)) return false;
1254
- seen.add(key);
1255
- return true;
1256
- });
1257
- }
1258
1493
  function CommerceSearchView({
1259
1494
  copy,
1260
1495
  isOpen,
@@ -1269,11 +1504,9 @@ function CommerceSearchView({
1269
1504
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
1270
1505
  gtmContext
1271
1506
  }) {
1272
- const [flag, setFlag] = react.useState(void 0);
1273
1507
  const [handoff, setHandoff] = react.useState(null);
1274
1508
  const agentQuery = handoff?.query ?? null;
1275
1509
  const [pendingTerm, setPendingTerm] = react.useState(null);
1276
- const [recentSearches, setRecentSearches] = react.useState([]);
1277
1510
  const search = useSearchSuggestions({
1278
1511
  service,
1279
1512
  enabled: isOpen && agentQuery === null
@@ -1285,31 +1518,25 @@ function CommerceSearchView({
1285
1518
  hasMoreResults,
1286
1519
  loading,
1287
1520
  query,
1288
- settledQuery,
1289
- suggestionFetchFailed,
1290
1521
  suggestions,
1291
1522
  updateQuery
1292
1523
  } = search;
1293
- const enabled = agent !== void 0 && isNuiSearchEnabled({
1524
+ const agentEnabled = useNuiAgentGate({
1525
+ hasAgent: agent !== void 0,
1526
+ isOpen,
1294
1527
  isAuthenticated,
1295
- flag,
1296
- allowGuests: allowGuestAgent
1528
+ allowGuestAgent
1529
+ });
1530
+ const { recentSearches, clearRecentSearches } = useRecentSearches({
1531
+ service,
1532
+ isOpen,
1533
+ refreshKey: agentQuery
1297
1534
  });
1298
1535
  const items = getVisibleSuggestions({
1299
1536
  didYouMean,
1300
1537
  suggestions
1301
1538
  });
1302
1539
  const showDidYouMean = didYouMean.length > 0;
1303
- react.useEffect(() => {
1304
- if (isOpen) setFlag(resolveNuiSearchFlag());
1305
- }, [isOpen]);
1306
- react.useEffect(() => {
1307
- if (isOpen) {
1308
- setRecentSearches(
1309
- uniqueRecentSearches(service.getRecentSearches())
1310
- );
1311
- }
1312
- }, [isOpen, service, agentQuery]);
1313
1540
  const close = react.useCallback(() => {
1314
1541
  setHandoff(null);
1315
1542
  setPendingTerm(null);
@@ -1348,72 +1575,16 @@ function CommerceSearchView({
1348
1575
  },
1349
1576
  [items, navigateTo, service, trackSemantic]
1350
1577
  );
1351
- const decide = react.useCallback(
1352
- (term) => decideSearchRoute({
1353
- enabled,
1354
- snapshot: {
1355
- query: term,
1356
- loading,
1357
- settledQuery,
1358
- suggestions,
1359
- suggestionFetchFailed
1360
- }
1361
- }),
1362
- [enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
1363
- );
1364
- const act = react.useCallback(
1365
- (term, route) => {
1366
- setPendingTerm(null);
1367
- if (route === "handoff") {
1368
- trackChatEntryPoint(gtmContext ?? {});
1369
- setHandoff({
1370
- query: term,
1371
- didYouMean: didYouMean.map((item) => item.value)
1372
- });
1373
- return;
1374
- }
1375
- runSearch(term);
1376
- },
1377
- [didYouMean, gtmContext, runSearch]
1378
- );
1379
- const handleSubmit = react.useCallback(
1380
- (value) => {
1381
- const term = value.trim();
1382
- if (!term) return;
1383
- if (term.length < minimumSearchLength) {
1384
- runSearch(term);
1385
- return;
1386
- }
1387
- const route = decide(term);
1388
- if (route === "wait") {
1389
- setPendingTerm(term);
1390
- return;
1391
- }
1392
- act(term, route);
1393
- },
1394
- [act, decide, minimumSearchLength, runSearch]
1395
- );
1396
- const latest = react.useRef({ act, decide });
1397
- latest.current = { act, decide };
1398
- react.useEffect(() => {
1399
- if (pendingTerm === null) return void 0;
1400
- const route = latest.current.decide(pendingTerm);
1401
- if (route !== "wait") {
1402
- latest.current.act(pendingTerm, route);
1403
- return void 0;
1404
- }
1405
- const timer = setTimeout(
1406
- () => latest.current.act(pendingTerm, "search"),
1407
- SUBMIT_WAIT_TIMEOUT_MS
1408
- );
1409
- return () => clearTimeout(timer);
1410
- }, [
1578
+ const handleSubmit = useSubmitRouting({
1579
+ enabled: agentEnabled,
1580
+ minimumSearchLength,
1581
+ search,
1582
+ ...gtmContext ? { gtmContext } : {},
1411
1583
  pendingTerm,
1412
- loading,
1413
- settledQuery,
1414
- suggestions,
1415
- suggestionFetchFailed
1416
- ]);
1584
+ setPendingTerm,
1585
+ onSearch: runSearch,
1586
+ onHandoff: setHandoff
1587
+ });
1417
1588
  const handleSelect = react.useCallback(
1418
1589
  (option) => {
1419
1590
  if (isSuggestionDisabled(
@@ -1438,10 +1609,11 @@ function CommerceSearchView({
1438
1609
  )) {
1439
1610
  return;
1440
1611
  }
1441
- const result = service.activateSuggestionAction(
1442
- option
1612
+ navigateTo(
1613
+ service.activateSuggestionAction(
1614
+ option
1615
+ )
1443
1616
  );
1444
- void Promise.resolve(result).then(navigateTo).catch(() => void 0);
1445
1617
  },
1446
1618
  [navigateTo, service]
1447
1619
  );
@@ -1456,10 +1628,6 @@ function CommerceSearchView({
1456
1628
  },
1457
1629
  [navigateTo, service, trackSemantic]
1458
1630
  );
1459
- const handleClearRecent = react.useCallback(() => {
1460
- service.clearRecentSearches();
1461
- setRecentSearches([]);
1462
- }, [service]);
1463
1631
  const handleAgentUnavailable = react.useCallback(() => {
1464
1632
  if (agentQuery !== null)
1465
1633
  navigateTo(service.submitProductSearch(agentQuery));
@@ -1508,14 +1676,29 @@ function CommerceSearchView({
1508
1676
  recentSearches: {
1509
1677
  items: recentSearches,
1510
1678
  onSelect: handleRecentSelect,
1511
- onClear: handleClearRecent
1679
+ onClear: clearRecentSearches
1512
1680
  },
1513
1681
  agentPanel
1514
1682
  }
1515
1683
  );
1516
1684
  }
1517
1685
  var GUEST = "guest";
1518
- function CommerceSearch({
1686
+ function CommerceSearch(props) {
1687
+ if (!isValidMarket(props.country, props.language)) {
1688
+ return /* @__PURE__ */ jsxRuntime.jsx(
1689
+ CommerceSearchUnavailable,
1690
+ {
1691
+ isOpen: props.isOpen,
1692
+ onClose: props.onClose,
1693
+ country: props.country,
1694
+ language: props.language,
1695
+ logo: props.logo
1696
+ }
1697
+ );
1698
+ }
1699
+ return /* @__PURE__ */ jsxRuntime.jsx(ValidMarketSearch, { ...props });
1700
+ }
1701
+ function ValidMarketSearch({
1519
1702
  isOpen,
1520
1703
  onClose,
1521
1704
  navigate,
@@ -1534,13 +1717,15 @@ function CommerceSearch({
1534
1717
  language,
1535
1718
  enabled: hasOpened
1536
1719
  });
1537
- const customerTypeRef = react.useRef(GUEST);
1538
- customerTypeRef.current = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
1720
+ const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
1721
+ const customerTypeRef = react.useRef(customerType);
1722
+ react.useEffect(() => {
1723
+ customerTypeRef.current = customerType;
1724
+ }, [customerType]);
1539
1725
  const service = react.useMemo(
1540
1726
  () => createSearchService({
1541
1727
  country,
1542
1728
  language,
1543
- locale: `${country.toUpperCase()}-${language.toLowerCase()}`,
1544
1729
  getMarketConfig: fetchMarketConfig,
1545
1730
  minimumSearchLength,
1546
1731
  getCustomerType: () => customerTypeRef.current
@@ -1548,7 +1733,17 @@ function CommerceSearch({
1548
1733
  [country, language, minimumSearchLength]
1549
1734
  );
1550
1735
  const navigateToPath = react.useCallback(
1551
- (path) => navigate(toNavigationTarget(path)),
1736
+ (path) => {
1737
+ const target = toNavigationTarget(path);
1738
+ if (target) {
1739
+ navigate(target);
1740
+ } else {
1741
+ console.warn(
1742
+ "[search] Not navigating to a path the SDK does not recognize:",
1743
+ path
1744
+ );
1745
+ }
1746
+ },
1552
1747
  [navigate]
1553
1748
  );
1554
1749
  const { isAuthenticated, accountType, userId } = customer;