@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.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  import { useState, useEffect, useRef, useMemo, useCallback, 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;
@@ -120,21 +120,41 @@ async function fetchSearchPanelCopy(_options = {}) {
120
120
 
121
121
  // src/copy/loadSearchPanelCopy.ts
122
122
  var copyByLanguage = /* @__PURE__ */ new Map();
123
+ var loadedByLanguage = /* @__PURE__ */ new Map();
124
+ function cacheKey(language) {
125
+ return (language ?? "").toLowerCase();
126
+ }
123
127
  function loadSearchPanelCopy(language) {
124
- const key = (language ?? "").toLowerCase();
128
+ const key = cacheKey(language);
125
129
  const cached = copyByLanguage.get(key);
126
130
  if (cached) return cached;
127
131
  const pending = fetchSearchPanelCopy(language ? { } : {});
128
132
  copyByLanguage.set(key, pending);
129
- pending.catch(() => copyByLanguage.delete(key));
133
+ pending.then(
134
+ (copy) => {
135
+ if (copyByLanguage.get(key) === pending) {
136
+ loadedByLanguage.set(key, copy);
137
+ }
138
+ },
139
+ () => {
140
+ if (copyByLanguage.get(key) === pending) {
141
+ copyByLanguage.delete(key);
142
+ }
143
+ }
144
+ );
130
145
  return pending;
131
146
  }
147
+ function peekSearchPanelCopy(language) {
148
+ return loadedByLanguage.get(cacheKey(language));
149
+ }
132
150
 
133
151
  // src/hooks/useSearchPanelCopy.ts
134
152
  function useSearchPanelCopy({
135
153
  language
136
154
  }) {
137
- const [copy, setCopy] = useState(void 0);
155
+ const [copy, setCopy] = useState(
156
+ () => peekSearchPanelCopy(language)
157
+ );
138
158
  useEffect(() => {
139
159
  let isStale = false;
140
160
  loadSearchPanelCopy(language).then((next) => {
@@ -152,7 +172,125 @@ function useSearchPanelCopy({
152
172
  return copy;
153
173
  }
154
174
 
155
- // src/services/SearchApiService.ts
175
+ // src/services/localeParts.ts
176
+ var TWO_LETTERS = /^[A-Za-z]{2}$/;
177
+ function marketProblem(country, language) {
178
+ const countryText = String(country ?? "").trim();
179
+ const languageText = String(language ?? "").trim();
180
+ if (TWO_LETTERS.test(countryText) && TWO_LETTERS.test(languageText)) {
181
+ return void 0;
182
+ }
183
+ return `the market is not valid (country ${JSON.stringify(countryText)}, language ${JSON.stringify(languageText)}): each must be two letters, for example US and en`;
184
+ }
185
+ function isValidMarket(country, language) {
186
+ return marketProblem(country, language) === void 0;
187
+ }
188
+ function createLocaleParts({
189
+ country,
190
+ language
191
+ }) {
192
+ const problem = marketProblem(country, language);
193
+ if (problem) throw new Error(problem);
194
+ const normalizedCountry = country.trim().toUpperCase();
195
+ const normalizedLanguage = language.trim().toLowerCase();
196
+ return {
197
+ country: normalizedCountry,
198
+ language: normalizedLanguage,
199
+ locale: `${normalizedCountry}-${normalizedLanguage}`
200
+ };
201
+ }
202
+ function toCatalogLocale({ country, language }) {
203
+ return `${language}_${country}`;
204
+ }
205
+ function toPathPrefix({ country, language }) {
206
+ return `/${country.toLowerCase()}/${language}`;
207
+ }
208
+
209
+ // src/services/catalogRequest.ts
210
+ function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
211
+ const apiGateway = String(apiGatewayPath ?? "").trim();
212
+ if (!apiGateway) return "";
213
+ return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
214
+ }
215
+ function resolveCatalogApiBaseUrl(marketConfig) {
216
+ return resolveApiGatewayPath(marketConfig?.api_gateway_path);
217
+ }
218
+ function resolveCatalogStoreId(marketConfig) {
219
+ return String(marketConfig?.store_id ?? "").trim();
220
+ }
221
+ function buildCatalogSuggestionsUrl(apiBaseUrl) {
222
+ const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
223
+ if (!normalizedBase) return "";
224
+ const suggestionsPath = normalizedBase.endsWith(
225
+ `/${STOREFRONT_SHOPPING_CONTEXT}`
226
+ ) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
227
+ return `${normalizedBase}${suggestionsPath}`;
228
+ }
229
+ function buildSuggestionsRequestUrl({
230
+ suggestionsUrl,
231
+ searchTerm,
232
+ storeId,
233
+ localeParts
234
+ }) {
235
+ const queryParams = new URLSearchParams({
236
+ searchTerm,
237
+ page: String(DEFAULT_SUGGESTION_PAGE),
238
+ size: String(DEFAULT_SUGGESTION_SIZE),
239
+ locale: toCatalogLocale(localeParts),
240
+ storeId
241
+ });
242
+ return `${suggestionsUrl}?${queryParams.toString()}`;
243
+ }
244
+ async function resolveCatalogRequestConfig({
245
+ locationLike,
246
+ country,
247
+ getMarketConfig
248
+ }) {
249
+ const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
250
+ const suggestionsUrl = buildCatalogSuggestionsUrl(
251
+ resolveCatalogApiBaseUrl(marketConfig)
252
+ );
253
+ if (!suggestionsUrl) {
254
+ throw new Error(
255
+ `no suggestions API for market ${country}: the market configuration has no gateway`
256
+ );
257
+ }
258
+ const storeId = resolveCatalogStoreId(marketConfig);
259
+ if (!storeId) {
260
+ throw new Error(
261
+ `no suggestions API for market ${country}: the market configuration has no store`
262
+ );
263
+ }
264
+ return { suggestionsUrl, storeId };
265
+ }
266
+ function resolveCustomerTypeHeaders(getCustomerType) {
267
+ const customerType = getCustomerType?.()?.trim();
268
+ return customerType ? { "X-Customer-Type": customerType } : void 0;
269
+ }
270
+ var NO_RESULTS_ERROR_CODE = "SFOSER002";
271
+ async function isNoResultsResponse(response) {
272
+ if (response.status !== 404) return false;
273
+ try {
274
+ const body = await response.json();
275
+ return body?.code === NO_RESULTS_ERROR_CODE;
276
+ } catch {
277
+ return false;
278
+ }
279
+ }
280
+ async function fetchJson(fetchImpl, requestUrl, requestHeaders = {}) {
281
+ const response = await fetchImpl(requestUrl, {
282
+ method: "GET",
283
+ credentials: "include",
284
+ headers: { Accept: "application/json", ...requestHeaders }
285
+ });
286
+ if (!response.ok) {
287
+ if (await isNoResultsResponse(response)) return null;
288
+ throw new Error(`Request failed with status ${response.status}`);
289
+ }
290
+ return response.json();
291
+ }
292
+
293
+ // src/services/recentSearches.ts
156
294
  function canUseStorage(storage) {
157
295
  return Boolean(storage) && typeof storage.getItem === "function";
158
296
  }
@@ -167,22 +305,8 @@ function getSessionStorage(providedStorage) {
167
305
  }
168
306
  return null;
169
307
  }
170
- function normalizeLocaleParts({
171
- country,
172
- language,
173
- locale
174
- }) {
175
- const normalizedCountry = String(country ?? "").trim().toUpperCase();
176
- const normalizedLanguage = String(language ?? "").trim().toLowerCase();
177
- const normalizedLocale = String(locale ?? "").trim() || "UNKNOWN";
178
- return {
179
- country: normalizedCountry || "UNKNOWN",
180
- language: normalizedLanguage || "unknown",
181
- locale: normalizedLocale
182
- };
183
- }
184
308
  function getRecentHeaderSearchesStorageKey(localeParts) {
185
- const { country, language, locale } = normalizeLocaleParts(localeParts);
309
+ const { country, language, locale } = localeParts;
186
310
  return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
187
311
  }
188
312
  function getRecentHeaderSearches(localeParts, storage) {
@@ -236,63 +360,8 @@ function clearRecentHeaderSearches(localeParts, storage) {
236
360
  } catch {
237
361
  }
238
362
  }
239
- function getBrowserLocation(providedLocation) {
240
- if (providedLocation) return providedLocation;
241
- if (typeof window !== "undefined" && window.location)
242
- return window.location;
243
- return null;
244
- }
245
- function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
246
- const apiGateway = String(apiGatewayPath ?? "").trim();
247
- if (!apiGateway) return "";
248
- return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
249
- }
250
- function resolveCatalogApiBaseUrl(marketConfig) {
251
- return resolveApiGatewayPath(marketConfig?.api_gateway_path);
252
- }
253
- function resolveCatalogStoreId(marketConfig) {
254
- return String(marketConfig?.store_id ?? "").trim();
255
- }
256
- function buildCatalogSuggestionsUrl(apiBaseUrl) {
257
- const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
258
- if (!normalizedBase) return "";
259
- const suggestionsPath = normalizedBase.endsWith(
260
- `/${STOREFRONT_SHOPPING_CONTEXT}`
261
- ) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
262
- return `${normalizedBase}${suggestionsPath}`;
263
- }
264
- function resolveSuggestionsUrl(suggestionsApi, locationLike) {
265
- const pathOrUrl = String(suggestionsApi ?? "").trim();
266
- if (!pathOrUrl) return "";
267
- if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
268
- return pathOrUrl;
269
- }
270
- const normalizedPath = pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`;
271
- const origin = String(locationLike?.origin ?? "").trim();
272
- return origin ? `${origin}${normalizedPath}` : normalizedPath;
273
- }
274
- async function resolveCatalogRequestConfig({
275
- suggestionsApi,
276
- storeIdOption,
277
- locationLike,
278
- country,
279
- getMarketConfig
280
- }) {
281
- const explicitStoreId = storeIdOption !== void 0 && storeIdOption !== null && String(storeIdOption).trim() !== "" ? String(storeIdOption).trim() : "";
282
- const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
283
- const configuredApiBase = resolveCatalogApiBaseUrl(marketConfig);
284
- const configuredStoreId = resolveCatalogStoreId(marketConfig);
285
- const explicitSuggestionsApi = String(suggestionsApi ?? "").trim();
286
- return {
287
- suggestionsUrl: explicitSuggestionsApi ? resolveSuggestionsUrl(explicitSuggestionsApi, locationLike) : buildCatalogSuggestionsUrl(configuredApiBase),
288
- storeId: explicitStoreId || configuredStoreId
289
- };
290
- }
291
- function toCatalogLocale(localeParts) {
292
- const language = String(localeParts.language ?? "en").toLowerCase();
293
- const country = String(localeParts.country ?? "US").toUpperCase();
294
- return `${language}_${country}`;
295
- }
363
+
364
+ // src/services/searchPaths.ts
296
365
  function buildSearchResultsPath(query, localeParts) {
297
366
  const trimmedQuery = String(query ?? "").trim();
298
367
  const hyphenatedQuery = trimmedQuery.replace(/\s+/g, "-");
@@ -300,13 +369,14 @@ function buildSearchResultsPath(query, localeParts) {
300
369
  /%2D/gi,
301
370
  "-"
302
371
  );
303
- const country = localeParts.country.toLowerCase();
304
- const language = localeParts.language.toLowerCase();
305
- return `/${country}/${language}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
372
+ return `${toPathPrefix(localeParts)}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
306
373
  }
374
+ var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
307
375
  function normalizeOptionUrl(rawValue) {
308
376
  if (typeof rawValue !== "string") return "";
309
- return rawValue.trim();
377
+ const value = rawValue.trim();
378
+ if (!value || value.startsWith("/") || SCHEME.test(value)) return value;
379
+ return `/${value}`;
310
380
  }
311
381
  function safeDecodeURIComponent(value) {
312
382
  try {
@@ -336,16 +406,14 @@ function slugifyPathSegment(value) {
336
406
  function buildProductSlugPath(slugValue, localeParts) {
337
407
  const slug = slugifyPathSegment(slugValue);
338
408
  if (!slug) return "";
339
- const country = String(localeParts.country ?? "US").toLowerCase();
340
- const language = String(localeParts.language ?? "en").toLowerCase();
341
- return `/${country}/${language}/product/${slug}`;
409
+ return `${toPathPrefix(localeParts)}/product/${slug}`;
342
410
  }
343
411
  function isNavigableUrl(value) {
344
412
  if (!value) return false;
345
413
  return value.startsWith("/") || /^https?:\/\//i.test(value);
346
414
  }
347
415
  function stripLocalePrefix(path, localeParts) {
348
- const prefix = `/${localeParts.country.toLowerCase()}/${localeParts.language.toLowerCase()}`;
416
+ const prefix = toPathPrefix(localeParts);
349
417
  return path.startsWith(prefix) ? path.slice(prefix.length) || "/" : path;
350
418
  }
351
419
  function resolveOptionHref(option, localeParts) {
@@ -362,67 +430,61 @@ function resolveOptionHref(option, localeParts) {
362
430
  }
363
431
  return "";
364
432
  }
365
- function resolveCustomerTypeHeaders(getCustomerType) {
366
- const customerType = getCustomerType?.()?.trim();
367
- return customerType ? { "X-Customer-Type": customerType } : void 0;
433
+
434
+ // src/services/suggestionsPayload.ts
435
+ function emptySuggestions() {
436
+ return { suggestions: [], didYouMean: [], totalResultsCount: 0 };
368
437
  }
369
- var NO_RESULTS_ERROR_CODE = "SFOSER002";
370
- async function isNoResultsResponse(response) {
371
- if (response.status !== 404) return false;
372
- try {
373
- const body = await response.json();
374
- return body?.code === NO_RESULTS_ERROR_CODE;
375
- } catch {
376
- return false;
438
+ function resolveTotalResultsCount(payload, fallback) {
439
+ if (typeof payload?.totalResultsCount === "number") {
440
+ return payload.totalResultsCount;
377
441
  }
442
+ const pageableTotal = Number(
443
+ payload?.pageableInfo?.totalCount ?? Number.NaN
444
+ );
445
+ return Number.isNaN(pageableTotal) ? fallback : pageableTotal;
378
446
  }
379
- async function fetchJsonFromCandidates(fetchImpl, requestUrls, requestHeaders = {}) {
380
- let lastError;
381
- for (const requestUrl of requestUrls) {
382
- try {
383
- const response = await fetchImpl(requestUrl, {
384
- method: "GET",
385
- credentials: "include",
386
- headers: { Accept: "application/json", ...requestHeaders }
387
- });
388
- if (!response.ok) {
389
- if (await isNoResultsResponse(response)) return null;
390
- lastError = new Error(
391
- `Request failed with status ${response.status}`
392
- );
393
- continue;
394
- }
395
- return await response.json();
396
- } catch (error) {
397
- lastError = error;
398
- }
399
- }
400
- throw lastError ?? new Error("Unable to fetch header search data");
447
+ function normalizeSuggestionsPayload(payload) {
448
+ const suggestions = Array.isArray(payload?.suggestions) ? payload.suggestions : [];
449
+ const products = Array.isArray(payload?.product) ? payload.product : null;
450
+ const rawDidYouMean = payload?.didYouMean ?? payload?.did_you_mean;
451
+ const didYouMean = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
452
+ return {
453
+ ...payload ?? {},
454
+ suggestions,
455
+ didYouMean,
456
+ totalResultsCount: resolveTotalResultsCount(
457
+ payload,
458
+ products?.length ?? suggestions.length
459
+ )
460
+ };
401
461
  }
402
- function createSearchService(options = {}) {
462
+
463
+ // src/services/SearchApiService.ts
464
+ function getBrowserLocation(providedLocation) {
465
+ if (providedLocation) return providedLocation;
466
+ if (typeof window !== "undefined" && window.location)
467
+ return window.location;
468
+ return null;
469
+ }
470
+ function createSearchService(options) {
403
471
  const {
404
472
  fetchImpl: fetchImplOption,
405
473
  location: providedLocation,
406
474
  storage: providedStorage,
407
- suggestionsApi,
408
- storeId: storeIdOption,
409
475
  country,
410
476
  language,
411
- locale,
412
477
  getMarketConfig,
413
478
  getCustomerType,
414
479
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
415
480
  } = options;
416
- const fetchImpl = fetchImplOption !== void 0 ? fetchImplOption : typeof fetch === "function" ? fetch.bind(globalThis) : null;
417
481
  const locationLike = getBrowserLocation(providedLocation);
418
482
  const storage = getSessionStorage(providedStorage);
419
- const localeParts = normalizeLocaleParts({ country, language, locale });
483
+ const localeParts = createLocaleParts({ country, language });
420
484
  let catalogRequestConfigPromise = null;
421
485
  function loadCatalogRequestConfig() {
422
486
  if (!catalogRequestConfigPromise) {
423
487
  catalogRequestConfigPromise = resolveCatalogRequestConfig({
424
- suggestionsApi,
425
- storeIdOption,
426
488
  locationLike,
427
489
  country: localeParts.country,
428
490
  getMarketConfig
@@ -440,48 +502,21 @@ function createSearchService(options = {}) {
440
502
  const service = {
441
503
  async fetchSuggestions(searchTerm) {
442
504
  const trimmedSearchTerm = String(searchTerm ?? "").trim();
443
- if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
444
- return {
445
- suggestions: [],
446
- didYouMean: [],
447
- totalResultsCount: 0
448
- };
505
+ if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength) {
506
+ return emptySuggestions();
449
507
  }
450
508
  const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
451
- if (!suggestionsUrl || !storeId) {
452
- return {
453
- suggestions: [],
454
- didYouMean: [],
455
- totalResultsCount: 0
456
- };
457
- }
458
- const queryParams = new URLSearchParams({
459
- searchTerm: trimmedSearchTerm,
460
- page: String(DEFAULT_SUGGESTION_PAGE),
461
- size: String(DEFAULT_SUGGESTION_SIZE),
462
- locale: toCatalogLocale(localeParts),
463
- storeId: String(storeId)
464
- });
465
- const requestUrls = [`${suggestionsUrl}?${queryParams.toString()}`];
466
- const responsePayload = await fetchJsonFromCandidates(
467
- fetchImpl,
468
- requestUrls,
509
+ const responsePayload = await fetchJson(
510
+ fetchImplOption ?? fetch.bind(globalThis),
511
+ buildSuggestionsRequestUrl({
512
+ suggestionsUrl,
513
+ searchTerm: trimmedSearchTerm,
514
+ storeId,
515
+ localeParts
516
+ }),
469
517
  resolveCustomerTypeHeaders(getCustomerType)
470
518
  );
471
- const suggestionsField = Array.isArray(responsePayload?.suggestions) ? responsePayload?.suggestions : [];
472
- const productField = Array.isArray(responsePayload?.product) ? responsePayload?.product : null;
473
- const rawDidYouMean = responsePayload?.didYouMean ?? responsePayload?.did_you_mean;
474
- const didYouMeanField = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
475
- const pageableTotal = Number(
476
- responsePayload?.pageableInfo?.totalCount ?? Number.NaN
477
- );
478
- const totalResultsCount = typeof responsePayload?.totalResultsCount === "number" ? responsePayload.totalResultsCount : !Number.isNaN(pageableTotal) ? pageableTotal : productField?.length ?? suggestionsField.length;
479
- return {
480
- ...responsePayload ?? {},
481
- suggestions: suggestionsField,
482
- didYouMean: didYouMeanField,
483
- totalResultsCount
484
- };
519
+ return normalizeSuggestionsPayload(responsePayload);
485
520
  },
486
521
  submitProductSearch(query, option) {
487
522
  const trimmedQuery = String(query ?? "").trim();
@@ -526,7 +561,7 @@ function createSearchService(options = {}) {
526
561
 
527
562
  // src/services/marketConfig.ts
528
563
  var fetchMarketConfig = async ({ country }) => {
529
- const countryCode = String(country ?? "").trim().toUpperCase() || "US";
564
+ const countryCode = country.trim().toUpperCase();
530
565
  const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
531
566
  country: countryCode,
532
567
  ...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
@@ -549,14 +584,25 @@ var SEARCH_GTM_USER_STATUS = {
549
584
  var GTM_FALLBACK_NA = "NA";
550
585
 
551
586
  // src/utils/navigationTarget.ts
587
+ var EXTERNAL_URL = /^https?:\/\//i;
588
+ var INTERNAL_PATH = /^\/(?![/\\])[^\t\n\r]*$/;
552
589
  function toNavigationTarget(path) {
553
- return /^https?:\/\//i.test(path) ? { type: "external", url: path } : { type: "internal", path };
590
+ if (EXTERNAL_URL.test(path)) return { type: "external", url: path };
591
+ if (INTERNAL_PATH.test(path)) return { type: "internal", path };
592
+ return void 0;
554
593
  }
555
594
 
556
595
  // src/utils/productEnvironment.ts
557
- var PRODUCT_ENVIRONMENTS = ["dev", "test", "prod"];
596
+ var PRODUCT_ENVIRONMENTS = {
597
+ dev: true,
598
+ test: true,
599
+ prod: true
600
+ };
601
+ function isProductEnvironment(code) {
602
+ return Object.keys(PRODUCT_ENVIRONMENTS).includes(code);
603
+ }
558
604
  function toProductEnvironment(environmentCode) {
559
- return environmentCode && PRODUCT_ENVIRONMENTS.includes(environmentCode) ? environmentCode : void 0;
605
+ return environmentCode !== void 0 && isProductEnvironment(environmentCode) ? environmentCode : void 0;
560
606
  }
561
607
  function getProductEnvironment() {
562
608
  try {
@@ -565,8 +611,132 @@ function getProductEnvironment() {
565
611
  return void 0;
566
612
  }
567
613
  }
614
+ var NOTHING = () => void 0;
615
+ function CommerceSearchUnavailable({
616
+ isOpen,
617
+ onClose,
618
+ country,
619
+ language,
620
+ logo
621
+ }) {
622
+ useEffect(() => {
623
+ console.error(
624
+ `[search] Search is unavailable: ${marketProblem(country, language)}`
625
+ );
626
+ }, [country, language]);
627
+ return /* @__PURE__ */ jsx(
628
+ SearchPanel,
629
+ {
630
+ isOpen,
631
+ onClose,
632
+ copy: SEARCH_PANEL_COPY_EN,
633
+ logo,
634
+ input: { value: "", onChange: NOTHING, onSubmit: NOTHING },
635
+ suggestions: {
636
+ items: [],
637
+ loading: false,
638
+ showNoResults: false,
639
+ showDidYouMean: false,
640
+ hasMore: false,
641
+ query: "",
642
+ onSelect: NOTHING,
643
+ onRunAction: NOTHING,
644
+ onSeeMore: NOTHING
645
+ },
646
+ recentSearches: {
647
+ items: [],
648
+ onSelect: NOTHING,
649
+ onClear: NOTHING
650
+ },
651
+ agentPanel: /* @__PURE__ */ jsx("p", { role: "alert", children: "Search is unavailable right now." })
652
+ }
653
+ );
654
+ }
568
655
 
569
- // src/utils/catalogHelpers.ts
656
+ // src/utils/nuiSearchGate.ts
657
+ var NUI_SEARCH_FLAG = "enableNuiSearch";
658
+ function parseFlagValue(raw) {
659
+ const value = raw?.trim().toLowerCase();
660
+ if (value === "true" || value === "1") return true;
661
+ if (value === "false" || value === "0") return false;
662
+ return void 0;
663
+ }
664
+ function readCookieFlag(cookie) {
665
+ for (const part of cookie.split(";")) {
666
+ const separator = part.indexOf("=");
667
+ if (separator < 0) continue;
668
+ if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
669
+ try {
670
+ return parseFlagValue(
671
+ decodeURIComponent(part.slice(separator + 1))
672
+ );
673
+ } catch {
674
+ return void 0;
675
+ }
676
+ }
677
+ return void 0;
678
+ }
679
+ function resolveNuiSearchFlag(env = {}) {
680
+ const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
681
+ const search = env.search ?? (inBrowser ? window.location.search : "");
682
+ const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
683
+ return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
684
+ }
685
+ function isNuiSearchEnabled({
686
+ isAuthenticated,
687
+ flag,
688
+ allowGuests = false
689
+ }) {
690
+ return flag === true && (isAuthenticated === true || allowGuests);
691
+ }
692
+
693
+ // src/hooks/useNuiAgentGate.ts
694
+ function useNuiAgentGate({
695
+ hasAgent,
696
+ isOpen,
697
+ isAuthenticated,
698
+ allowGuestAgent
699
+ }) {
700
+ const [flag, setFlag] = useState(void 0);
701
+ useEffect(() => {
702
+ if (isOpen) setFlag(resolveNuiSearchFlag());
703
+ }, [isOpen]);
704
+ return hasAgent && isNuiSearchEnabled({
705
+ isAuthenticated,
706
+ flag,
707
+ allowGuests: allowGuestAgent
708
+ });
709
+ }
710
+ function uniqueRecentSearches(queries) {
711
+ const seen = /* @__PURE__ */ new Set();
712
+ return queries.filter((query) => {
713
+ const key = query.trim().toLowerCase();
714
+ if (!key || seen.has(key)) return false;
715
+ seen.add(key);
716
+ return true;
717
+ });
718
+ }
719
+ function useRecentSearches({
720
+ service,
721
+ isOpen,
722
+ refreshKey
723
+ }) {
724
+ const [recentSearches, setRecentSearches] = useState([]);
725
+ useEffect(() => {
726
+ if (isOpen) {
727
+ setRecentSearches(
728
+ uniqueRecentSearches(service.getRecentSearches())
729
+ );
730
+ }
731
+ }, [isOpen, service, refreshKey]);
732
+ const clearRecentSearches = useCallback(() => {
733
+ service.clearRecentSearches();
734
+ setRecentSearches([]);
735
+ }, [service]);
736
+ return { recentSearches, clearRecentSearches };
737
+ }
738
+
739
+ // src/utils/catalogStatus.ts
570
740
  var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
571
741
  "discontinued",
572
742
  "preview",
@@ -614,6 +784,8 @@ function isSuggestionDisabled(suggestionOption) {
614
784
  !suggestionOption || suggestionOption["out_of_stock"] || suggestionOption["outOfStock"]
615
785
  );
616
786
  }
787
+
788
+ // src/utils/catalogHelpers.ts
617
789
  function getImageFromProperty(properties = {}) {
618
790
  let image = String(properties["primaryimage"] ?? "").trim() || String(properties["imageURL"] ?? "").trim();
619
791
  if (!image) {
@@ -851,6 +1023,46 @@ function normalizeSuggestionsResponse(response) {
851
1023
  didYouMean: normalizeDidYouMean(r.didYouMean)
852
1024
  };
853
1025
  }
1026
+ function resolveTotalResultsCount2(response, fallback) {
1027
+ if (typeof response.totalResultsCount === "number") {
1028
+ return response.totalResultsCount;
1029
+ }
1030
+ if (typeof response.pageableInfo?.totalCount === "number") {
1031
+ return response.pageableInfo.totalCount;
1032
+ }
1033
+ return fallback;
1034
+ }
1035
+ var FETCH_FAILED = {
1036
+ suggestions: [],
1037
+ didYouMean: [],
1038
+ hasMoreResults: false,
1039
+ emptyFetched: true,
1040
+ failed: true
1041
+ };
1042
+ function toSettledResults(response) {
1043
+ const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
1044
+ if (didYouMean.length > 0) {
1045
+ return {
1046
+ suggestions: [],
1047
+ didYouMean,
1048
+ hasMoreResults: false,
1049
+ emptyFetched: false,
1050
+ failed: false
1051
+ };
1052
+ }
1053
+ const total = Math.max(
1054
+ resolveTotalResultsCount2(response, allSuggestions.length),
1055
+ allSuggestions.length
1056
+ );
1057
+ const suggestions = allSuggestions.slice(0, MAX_VISIBLE_SUGGESTIONS);
1058
+ return {
1059
+ suggestions,
1060
+ didYouMean,
1061
+ hasMoreResults: total > MAX_VISIBLE_SUGGESTIONS,
1062
+ emptyFetched: suggestions.length === 0,
1063
+ failed: false
1064
+ };
1065
+ }
854
1066
  function useSearchSuggestions({
855
1067
  service,
856
1068
  enabled = true
@@ -862,7 +1074,6 @@ function useSearchSuggestions({
862
1074
  const [emptyFetched, setEmptyFetched] = useState(false);
863
1075
  const [suggestionFetchFailed, setSuggestionFetchFailed] = useState(false);
864
1076
  const [hasMoreResults, setHasMoreResults] = useState(false);
865
- const [totalResultsCount, setTotalResultsCount] = useState(0);
866
1077
  const [settledQuery, setSettledQuery] = useState("");
867
1078
  const fetchDebounced = useRef(null);
868
1079
  const latestRequestId = useRef(0);
@@ -872,7 +1083,6 @@ function useSearchSuggestions({
872
1083
  setEmptyFetched(false);
873
1084
  setSuggestionFetchFailed(false);
874
1085
  setHasMoreResults(false);
875
- setTotalResultsCount(0);
876
1086
  setSettledQuery("");
877
1087
  setLoading(false);
878
1088
  }, []);
@@ -890,6 +1100,14 @@ function useSearchSuggestions({
890
1100
  resetSuggestionsState();
891
1101
  setQuery("");
892
1102
  }, [resetSuggestionsState]);
1103
+ const settle = useCallback((results, term) => {
1104
+ setDidYouMean(results.didYouMean);
1105
+ setSuggestions(results.suggestions);
1106
+ setHasMoreResults(results.hasMoreResults);
1107
+ setSuggestionFetchFailed(results.failed);
1108
+ setSettledQuery(term);
1109
+ setEmptyFetched(results.emptyFetched);
1110
+ }, []);
893
1111
  const runFetch = useCallback(
894
1112
  async (nextQuery) => {
895
1113
  const trimmedQuery = nextQuery.trim();
@@ -901,53 +1119,23 @@ function useSearchSuggestions({
901
1119
  setLoading(true);
902
1120
  setEmptyFetched(false);
903
1121
  setSuggestionFetchFailed(false);
1122
+ let results;
904
1123
  try {
905
- const fetchedSuggestions = await service.fetchSuggestions(trimmedQuery);
906
- if (requestId !== latestRequestId.current) {
907
- return;
908
- }
909
- const {
910
- suggestions: allSuggestions,
911
- didYouMean: suggestedTerms
912
- } = normalizeSuggestionsResponse(fetchedSuggestions);
913
- const visibleSuggestions = allSuggestions.slice(
914
- 0,
915
- MAX_VISIBLE_SUGGESTIONS
916
- );
917
- const hasDidYouMean = suggestedTerms.length > 0;
918
- const normalizedTotal = typeof fetchedSuggestions.totalResultsCount === "number" ? fetchedSuggestions.totalResultsCount : typeof fetchedSuggestions.pageableInfo?.totalCount === "number" ? fetchedSuggestions.pageableInfo.totalCount : allSuggestions.length;
919
- const effectiveTotal = hasDidYouMean ? suggestedTerms.length : Math.max(normalizedTotal, allSuggestions.length);
920
- setDidYouMean(suggestedTerms);
921
- setSuggestions(hasDidYouMean ? [] : visibleSuggestions);
922
- setHasMoreResults(
923
- !hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
924
- );
925
- setTotalResultsCount(effectiveTotal);
926
- setSuggestionFetchFailed(false);
927
- setSettledQuery(trimmedQuery);
928
- setEmptyFetched(
929
- hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
1124
+ results = toSettledResults(
1125
+ await service.fetchSuggestions(trimmedQuery)
930
1126
  );
931
- } catch {
932
- if (requestId !== latestRequestId.current) {
933
- return;
934
- }
935
- setSuggestions([]);
936
- setDidYouMean([]);
937
- setEmptyFetched(true);
938
- setSuggestionFetchFailed(true);
939
- setHasMoreResults(false);
940
- setTotalResultsCount(0);
941
- setSettledQuery(trimmedQuery);
942
- } finally {
943
- if (requestId === latestRequestId.current) {
944
- setLoading(false);
945
- }
1127
+ } catch (error) {
1128
+ console.warn("[search] Could not load suggestions:", error);
1129
+ results = FETCH_FAILED;
946
1130
  }
1131
+ if (requestId !== latestRequestId.current) return;
1132
+ settle(results, trimmedQuery);
1133
+ setLoading(false);
947
1134
  },
948
- [enabled, service, resetSuggestionsState]
1135
+ [enabled, service, resetSuggestionsState, settle]
949
1136
  );
950
1137
  useEffect(() => {
1138
+ latestRequestId.current += 1;
951
1139
  if (!enabled) {
952
1140
  resetSuggestionsState();
953
1141
  return void 0;
@@ -955,7 +1143,6 @@ function useSearchSuggestions({
955
1143
  if (fetchDebounced.current) {
956
1144
  clearTimeout(fetchDebounced.current);
957
1145
  }
958
- latestRequestId.current += 1;
959
1146
  fetchDebounced.current = setTimeout(() => {
960
1147
  void runFetch(query);
961
1148
  }, DEBOUNCE_MS);
@@ -969,15 +1156,12 @@ function useSearchSuggestions({
969
1156
  clearQuery,
970
1157
  emptyFetched,
971
1158
  hasMoreResults,
972
- hasQuery: Boolean(query.trim()),
973
1159
  didYouMean,
974
1160
  loading,
975
1161
  query,
976
1162
  settledQuery,
977
- setQuery,
978
1163
  suggestionFetchFailed,
979
1164
  suggestions,
980
- totalResultsCount,
981
1165
  updateQuery
982
1166
  };
983
1167
  }
@@ -1052,43 +1236,6 @@ function trackProductSearch(input) {
1052
1236
  });
1053
1237
  }
1054
1238
 
1055
- // src/utils/nuiSearchGate.ts
1056
- var NUI_SEARCH_FLAG = "enableNuiSearch";
1057
- function parseFlagValue(raw) {
1058
- const value = raw?.trim().toLowerCase();
1059
- if (value === "true" || value === "1") return true;
1060
- if (value === "false" || value === "0") return false;
1061
- return void 0;
1062
- }
1063
- function readCookieFlag(cookie) {
1064
- for (const part of cookie.split(";")) {
1065
- const separator = part.indexOf("=");
1066
- if (separator < 0) continue;
1067
- if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
1068
- try {
1069
- return parseFlagValue(
1070
- decodeURIComponent(part.slice(separator + 1))
1071
- );
1072
- } catch {
1073
- return void 0;
1074
- }
1075
- }
1076
- return void 0;
1077
- }
1078
- function resolveNuiSearchFlag(env = {}) {
1079
- const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
1080
- const search = env.search ?? (inBrowser ? window.location.search : "");
1081
- const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
1082
- return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
1083
- }
1084
- function isNuiSearchEnabled({
1085
- isAuthenticated,
1086
- flag,
1087
- allowGuests = false
1088
- }) {
1089
- return flag === true && (isAuthenticated === true || allowGuests);
1090
- }
1091
-
1092
1239
  // src/utils/searchRouting.ts
1093
1240
  function decideSearchRoute({
1094
1241
  enabled,
@@ -1101,6 +1248,96 @@ function decideSearchRoute({
1101
1248
  return snapshot.suggestions.length > 0 ? "search" : "handoff";
1102
1249
  }
1103
1250
 
1251
+ // src/hooks/useSubmitRouting.ts
1252
+ var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
1253
+ function useSubmitRouting({
1254
+ enabled,
1255
+ minimumSearchLength,
1256
+ search,
1257
+ gtmContext,
1258
+ pendingTerm,
1259
+ setPendingTerm,
1260
+ onSearch,
1261
+ onHandoff
1262
+ }) {
1263
+ const {
1264
+ didYouMean,
1265
+ loading,
1266
+ settledQuery,
1267
+ suggestionFetchFailed,
1268
+ suggestions
1269
+ } = search;
1270
+ const decide = useCallback(
1271
+ (term) => decideSearchRoute({
1272
+ enabled,
1273
+ snapshot: {
1274
+ query: term,
1275
+ loading,
1276
+ settledQuery,
1277
+ suggestions,
1278
+ suggestionFetchFailed
1279
+ }
1280
+ }),
1281
+ [enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
1282
+ );
1283
+ const act = useCallback(
1284
+ (term, route) => {
1285
+ setPendingTerm(null);
1286
+ if (route === "handoff") {
1287
+ trackChatEntryPoint(gtmContext ?? {});
1288
+ onHandoff({
1289
+ query: term,
1290
+ didYouMean: didYouMean.map((item) => item.value)
1291
+ });
1292
+ return;
1293
+ }
1294
+ onSearch(term);
1295
+ },
1296
+ [didYouMean, gtmContext, onHandoff, onSearch, setPendingTerm]
1297
+ );
1298
+ const submit = useCallback(
1299
+ (value) => {
1300
+ const term = value.trim();
1301
+ if (!term) return;
1302
+ if (term.length < minimumSearchLength) {
1303
+ onSearch(term);
1304
+ return;
1305
+ }
1306
+ const route = decide(term);
1307
+ if (route === "wait") {
1308
+ setPendingTerm(term);
1309
+ return;
1310
+ }
1311
+ act(term, route);
1312
+ },
1313
+ [act, decide, minimumSearchLength, onSearch, setPendingTerm]
1314
+ );
1315
+ const latest = useRef({ act, decide });
1316
+ useEffect(() => {
1317
+ latest.current = { act, decide };
1318
+ });
1319
+ useEffect(() => {
1320
+ if (pendingTerm === null) return void 0;
1321
+ const route = latest.current.decide(pendingTerm);
1322
+ if (route !== "wait") {
1323
+ latest.current.act(pendingTerm, route);
1324
+ return void 0;
1325
+ }
1326
+ const timer = setTimeout(
1327
+ () => latest.current.act(pendingTerm, "search"),
1328
+ SUBMIT_WAIT_TIMEOUT_MS
1329
+ );
1330
+ return () => clearTimeout(timer);
1331
+ }, [
1332
+ pendingTerm,
1333
+ loading,
1334
+ settledQuery,
1335
+ suggestions,
1336
+ suggestionFetchFailed
1337
+ ]);
1338
+ return submit;
1339
+ }
1340
+
1104
1341
  // src/utils/cachedProductClient.ts
1105
1342
  function withProductCache(client) {
1106
1343
  const lookups = /* @__PURE__ */ new Map();
@@ -1168,7 +1405,8 @@ var AgentErrorBoundary = class extends Component {
1168
1405
  static getDerivedStateFromError() {
1169
1406
  return { failed: true };
1170
1407
  }
1171
- componentDidCatch(_error, _info) {
1408
+ componentDidCatch(error) {
1409
+ console.warn("[search] The agent could not be shown:", error);
1172
1410
  this.props.onError();
1173
1411
  }
1174
1412
  render() {
@@ -1181,12 +1419,16 @@ function useHandoff({
1181
1419
  agentContext,
1182
1420
  onUnavailable
1183
1421
  }) {
1422
+ const onUnavailableRef = useRef(onUnavailable);
1423
+ useEffect(() => {
1424
+ onUnavailableRef.current = onUnavailable;
1425
+ });
1184
1426
  useEffect(() => {
1185
- let cancelled = false;
1427
+ let canceled = false;
1186
1428
  let timer;
1187
1429
  const startedAt = Date.now();
1188
1430
  const tryOpen = () => {
1189
- if (cancelled) return;
1431
+ if (canceled) return;
1190
1432
  const handle = getSalesAIHandle();
1191
1433
  if (handle) {
1192
1434
  handle.open({
@@ -1196,14 +1438,17 @@ function useHandoff({
1196
1438
  return;
1197
1439
  }
1198
1440
  if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
1199
- onUnavailable();
1441
+ console.warn(
1442
+ `[search] The agent did not start within ${HANDLE_TIMEOUT_MS / 1e3} seconds.`
1443
+ );
1444
+ onUnavailableRef.current();
1200
1445
  return;
1201
1446
  }
1202
1447
  timer = setTimeout(tryOpen, HANDLE_POLL_MS);
1203
1448
  };
1204
1449
  tryOpen();
1205
1450
  return () => {
1206
- cancelled = true;
1451
+ canceled = true;
1207
1452
  if (timer !== void 0) clearTimeout(timer);
1208
1453
  };
1209
1454
  }, [query]);
@@ -1238,16 +1483,6 @@ function NuiAgentPanel({
1238
1483
  }
1239
1484
  ) });
1240
1485
  }
1241
- var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
1242
- function uniqueRecentSearches(queries) {
1243
- const seen = /* @__PURE__ */ new Set();
1244
- return queries.filter((query) => {
1245
- const key = query.trim().toLowerCase();
1246
- if (!key || seen.has(key)) return false;
1247
- seen.add(key);
1248
- return true;
1249
- });
1250
- }
1251
1486
  function CommerceSearchView({
1252
1487
  copy,
1253
1488
  isOpen,
@@ -1262,11 +1497,9 @@ function CommerceSearchView({
1262
1497
  minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
1263
1498
  gtmContext
1264
1499
  }) {
1265
- const [flag, setFlag] = useState(void 0);
1266
1500
  const [handoff, setHandoff] = useState(null);
1267
1501
  const agentQuery = handoff?.query ?? null;
1268
1502
  const [pendingTerm, setPendingTerm] = useState(null);
1269
- const [recentSearches, setRecentSearches] = useState([]);
1270
1503
  const search = useSearchSuggestions({
1271
1504
  service,
1272
1505
  enabled: isOpen && agentQuery === null
@@ -1278,31 +1511,25 @@ function CommerceSearchView({
1278
1511
  hasMoreResults,
1279
1512
  loading,
1280
1513
  query,
1281
- settledQuery,
1282
- suggestionFetchFailed,
1283
1514
  suggestions,
1284
1515
  updateQuery
1285
1516
  } = search;
1286
- const enabled = agent !== void 0 && isNuiSearchEnabled({
1517
+ const agentEnabled = useNuiAgentGate({
1518
+ hasAgent: agent !== void 0,
1519
+ isOpen,
1287
1520
  isAuthenticated,
1288
- flag,
1289
- allowGuests: allowGuestAgent
1521
+ allowGuestAgent
1522
+ });
1523
+ const { recentSearches, clearRecentSearches } = useRecentSearches({
1524
+ service,
1525
+ isOpen,
1526
+ refreshKey: agentQuery
1290
1527
  });
1291
1528
  const items = getVisibleSuggestions({
1292
1529
  didYouMean,
1293
1530
  suggestions
1294
1531
  });
1295
1532
  const showDidYouMean = didYouMean.length > 0;
1296
- useEffect(() => {
1297
- if (isOpen) setFlag(resolveNuiSearchFlag());
1298
- }, [isOpen]);
1299
- useEffect(() => {
1300
- if (isOpen) {
1301
- setRecentSearches(
1302
- uniqueRecentSearches(service.getRecentSearches())
1303
- );
1304
- }
1305
- }, [isOpen, service, agentQuery]);
1306
1533
  const close = useCallback(() => {
1307
1534
  setHandoff(null);
1308
1535
  setPendingTerm(null);
@@ -1341,72 +1568,16 @@ function CommerceSearchView({
1341
1568
  },
1342
1569
  [items, navigateTo, service, trackSemantic]
1343
1570
  );
1344
- const decide = useCallback(
1345
- (term) => decideSearchRoute({
1346
- enabled,
1347
- snapshot: {
1348
- query: term,
1349
- loading,
1350
- settledQuery,
1351
- suggestions,
1352
- suggestionFetchFailed
1353
- }
1354
- }),
1355
- [enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
1356
- );
1357
- const act = useCallback(
1358
- (term, route) => {
1359
- setPendingTerm(null);
1360
- if (route === "handoff") {
1361
- trackChatEntryPoint(gtmContext ?? {});
1362
- setHandoff({
1363
- query: term,
1364
- didYouMean: didYouMean.map((item) => item.value)
1365
- });
1366
- return;
1367
- }
1368
- runSearch(term);
1369
- },
1370
- [didYouMean, gtmContext, runSearch]
1371
- );
1372
- const handleSubmit = useCallback(
1373
- (value) => {
1374
- const term = value.trim();
1375
- if (!term) return;
1376
- if (term.length < minimumSearchLength) {
1377
- runSearch(term);
1378
- return;
1379
- }
1380
- const route = decide(term);
1381
- if (route === "wait") {
1382
- setPendingTerm(term);
1383
- return;
1384
- }
1385
- act(term, route);
1386
- },
1387
- [act, decide, minimumSearchLength, runSearch]
1388
- );
1389
- const latest = useRef({ act, decide });
1390
- latest.current = { act, decide };
1391
- useEffect(() => {
1392
- if (pendingTerm === null) return void 0;
1393
- const route = latest.current.decide(pendingTerm);
1394
- if (route !== "wait") {
1395
- latest.current.act(pendingTerm, route);
1396
- return void 0;
1397
- }
1398
- const timer = setTimeout(
1399
- () => latest.current.act(pendingTerm, "search"),
1400
- SUBMIT_WAIT_TIMEOUT_MS
1401
- );
1402
- return () => clearTimeout(timer);
1403
- }, [
1571
+ const handleSubmit = useSubmitRouting({
1572
+ enabled: agentEnabled,
1573
+ minimumSearchLength,
1574
+ search,
1575
+ ...gtmContext ? { gtmContext } : {},
1404
1576
  pendingTerm,
1405
- loading,
1406
- settledQuery,
1407
- suggestions,
1408
- suggestionFetchFailed
1409
- ]);
1577
+ setPendingTerm,
1578
+ onSearch: runSearch,
1579
+ onHandoff: setHandoff
1580
+ });
1410
1581
  const handleSelect = useCallback(
1411
1582
  (option) => {
1412
1583
  if (isSuggestionDisabled(
@@ -1431,10 +1602,11 @@ function CommerceSearchView({
1431
1602
  )) {
1432
1603
  return;
1433
1604
  }
1434
- const result = service.activateSuggestionAction(
1435
- option
1605
+ navigateTo(
1606
+ service.activateSuggestionAction(
1607
+ option
1608
+ )
1436
1609
  );
1437
- void Promise.resolve(result).then(navigateTo).catch(() => void 0);
1438
1610
  },
1439
1611
  [navigateTo, service]
1440
1612
  );
@@ -1449,10 +1621,6 @@ function CommerceSearchView({
1449
1621
  },
1450
1622
  [navigateTo, service, trackSemantic]
1451
1623
  );
1452
- const handleClearRecent = useCallback(() => {
1453
- service.clearRecentSearches();
1454
- setRecentSearches([]);
1455
- }, [service]);
1456
1624
  const handleAgentUnavailable = useCallback(() => {
1457
1625
  if (agentQuery !== null)
1458
1626
  navigateTo(service.submitProductSearch(agentQuery));
@@ -1501,14 +1669,29 @@ function CommerceSearchView({
1501
1669
  recentSearches: {
1502
1670
  items: recentSearches,
1503
1671
  onSelect: handleRecentSelect,
1504
- onClear: handleClearRecent
1672
+ onClear: clearRecentSearches
1505
1673
  },
1506
1674
  agentPanel
1507
1675
  }
1508
1676
  );
1509
1677
  }
1510
1678
  var GUEST = "guest";
1511
- function CommerceSearch({
1679
+ function CommerceSearch(props) {
1680
+ if (!isValidMarket(props.country, props.language)) {
1681
+ return /* @__PURE__ */ jsx(
1682
+ CommerceSearchUnavailable,
1683
+ {
1684
+ isOpen: props.isOpen,
1685
+ onClose: props.onClose,
1686
+ country: props.country,
1687
+ language: props.language,
1688
+ logo: props.logo
1689
+ }
1690
+ );
1691
+ }
1692
+ return /* @__PURE__ */ jsx(ValidMarketSearch, { ...props });
1693
+ }
1694
+ function ValidMarketSearch({
1512
1695
  isOpen,
1513
1696
  onClose,
1514
1697
  navigate,
@@ -1527,13 +1710,15 @@ function CommerceSearch({
1527
1710
  language,
1528
1711
  enabled: hasOpened
1529
1712
  });
1530
- const customerTypeRef = useRef(GUEST);
1531
- customerTypeRef.current = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
1713
+ const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
1714
+ const customerTypeRef = useRef(customerType);
1715
+ useEffect(() => {
1716
+ customerTypeRef.current = customerType;
1717
+ }, [customerType]);
1532
1718
  const service = useMemo(
1533
1719
  () => createSearchService({
1534
1720
  country,
1535
1721
  language,
1536
- locale: `${country.toUpperCase()}-${language.toLowerCase()}`,
1537
1722
  getMarketConfig: fetchMarketConfig,
1538
1723
  minimumSearchLength,
1539
1724
  getCustomerType: () => customerTypeRef.current
@@ -1541,7 +1726,17 @@ function CommerceSearch({
1541
1726
  [country, language, minimumSearchLength]
1542
1727
  );
1543
1728
  const navigateToPath = useCallback(
1544
- (path) => navigate(toNavigationTarget(path)),
1729
+ (path) => {
1730
+ const target = toNavigationTarget(path);
1731
+ if (target) {
1732
+ navigate(target);
1733
+ } else {
1734
+ console.warn(
1735
+ "[search] Not navigating to a path the SDK does not recognize:",
1736
+ path
1737
+ );
1738
+ }
1739
+ },
1545
1740
  [navigate]
1546
1741
  );
1547
1742
  const { isAuthenticated, accountType, userId } = customer;