@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/README.md +3 -3
- package/dist/index.d.mts +15 -8
- package/dist/index.d.ts +15 -8
- package/dist/index.js +282 -119
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +283 -120
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
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
|
|
|
@@ -23,36 +23,53 @@ var CATALOG_SUGGESTIONS_PATH = "/storefront/catalogs/search/suggestions";
|
|
|
23
23
|
var RECENT_SEARCHES_PREFIX = "nuskin:header:recentSearches";
|
|
24
24
|
var RECENT_SEARCHES_DEFAULT_LIMIT = 10;
|
|
25
25
|
var DEFAULT_SUGGESTION_PAGE = 1;
|
|
26
|
-
var DEFAULT_SUGGESTION_SIZE = 6;
|
|
27
26
|
var DEFAULT_SEARCH_PATH = "/search";
|
|
28
27
|
var DEBOUNCE_MS = 220;
|
|
29
28
|
var MINIMUM_SEARCH_CHAR_LENGTH = 2;
|
|
30
|
-
var
|
|
29
|
+
var DEFAULT_MAX_SUGGESTIONS = 6;
|
|
30
|
+
var MAX_SUGGESTIONS_CEILING = 20;
|
|
31
31
|
var EQUINOX_MARKETS_MAP = "Equinox_Markets";
|
|
32
|
-
var
|
|
32
|
+
var COMMERCE_SEARCH_MAP = "Commerce_Search";
|
|
33
33
|
|
|
34
34
|
// src/config/commerceSearchConfig.ts
|
|
35
35
|
var COMMERCE_SEARCH_CONFIG_DEFAULTS = Object.freeze({
|
|
36
|
-
minimumSearchLength: MINIMUM_SEARCH_CHAR_LENGTH
|
|
36
|
+
minimumSearchLength: MINIMUM_SEARCH_CHAR_LENGTH,
|
|
37
|
+
maxSuggestions: DEFAULT_MAX_SUGGESTIONS,
|
|
38
|
+
agentEnabledFor: Object.freeze({
|
|
39
|
+
guest: false,
|
|
40
|
+
brandAffiliate: false,
|
|
41
|
+
preferredCustomer: false,
|
|
42
|
+
retailCustomer: false
|
|
43
|
+
})
|
|
37
44
|
});
|
|
38
|
-
var
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
var FIELDS = {
|
|
46
|
+
minimumLength: "commerceSearchMinimumLength",
|
|
47
|
+
maxSuggestions: "commerceSearchMaxSuggestions",
|
|
48
|
+
agentAppKey: "commerceSearchAgentAppKey",
|
|
49
|
+
agentApiBaseUrl: "commerceSearchAgentApiBaseUrl",
|
|
50
|
+
enabledGuest: "commerceSearchAgentEnabledGuest",
|
|
51
|
+
enabledBrandAffiliate: "commerceSearchAgentEnabledBrandAffiliate",
|
|
52
|
+
enabledPreferredCustomer: "commerceSearchAgentEnabledPreferredCustomer",
|
|
53
|
+
enabledRetailCustomer: "commerceSearchAgentEnabledRetailCustomer"
|
|
46
54
|
};
|
|
47
55
|
function nonEmptyString(value) {
|
|
48
56
|
return typeof value === "string" ? value.trim() : "";
|
|
49
57
|
}
|
|
58
|
+
function wholeNumber(value) {
|
|
59
|
+
const number = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
|
|
60
|
+
return typeof number === "number" && Number.isInteger(number) ? number : void 0;
|
|
61
|
+
}
|
|
62
|
+
function positiveWholeNumber(value) {
|
|
63
|
+
const number = wholeNumber(value);
|
|
64
|
+
return number !== void 0 && number >= 1 ? number : void 0;
|
|
65
|
+
}
|
|
50
66
|
function normalizeCommerceSearchConfig(raw, { language } = {}) {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
67
|
+
const appKey = nonEmptyString(raw?.[FIELDS.agentAppKey]);
|
|
68
|
+
const apiBaseUrl = nonEmptyString(raw?.[FIELDS.agentApiBaseUrl]);
|
|
69
|
+
const maxSuggestions = positiveWholeNumber(raw?.[FIELDS.maxSuggestions]);
|
|
54
70
|
return {
|
|
55
|
-
minimumSearchLength:
|
|
71
|
+
minimumSearchLength: positiveWholeNumber(raw?.[FIELDS.minimumLength]) ?? COMMERCE_SEARCH_CONFIG_DEFAULTS.minimumSearchLength,
|
|
72
|
+
maxSuggestions: maxSuggestions === void 0 ? COMMERCE_SEARCH_CONFIG_DEFAULTS.maxSuggestions : Math.min(maxSuggestions, MAX_SUGGESTIONS_CEILING),
|
|
56
73
|
...appKey && apiBaseUrl ? {
|
|
57
74
|
agent: {
|
|
58
75
|
appKey,
|
|
@@ -60,13 +77,36 @@ function normalizeCommerceSearchConfig(raw, { language } = {}) {
|
|
|
60
77
|
...language ? { language } : {}
|
|
61
78
|
}
|
|
62
79
|
} : {},
|
|
63
|
-
|
|
80
|
+
agentEnabledFor: {
|
|
81
|
+
guest: raw?.[FIELDS.enabledGuest] === true,
|
|
82
|
+
brandAffiliate: raw?.[FIELDS.enabledBrandAffiliate] === true,
|
|
83
|
+
preferredCustomer: raw?.[FIELDS.enabledPreferredCustomer] === true,
|
|
84
|
+
retailCustomer: raw?.[FIELDS.enabledRetailCustomer] === true
|
|
85
|
+
}
|
|
64
86
|
};
|
|
65
87
|
}
|
|
66
88
|
async function fetchCommerceSearchConfig({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
country,
|
|
90
|
+
language,
|
|
91
|
+
clientId
|
|
92
|
+
}) {
|
|
93
|
+
const configuration = await getConfiguration([COMMERCE_SEARCH_MAP], {
|
|
94
|
+
country: country.trim().toUpperCase(),
|
|
95
|
+
...clientId ? { clientId } : {}
|
|
96
|
+
});
|
|
97
|
+
const raw = configuration?.[COMMERCE_SEARCH_MAP];
|
|
98
|
+
if (!raw || Object.keys(raw).length === 0) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`the configuration service returned no ${COMMERCE_SEARCH_MAP} for market ${country}`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const hasKey = nonEmptyString(raw[FIELDS.agentAppKey]) !== "";
|
|
104
|
+
const hasUrl = nonEmptyString(raw[FIELDS.agentApiBaseUrl]) !== "";
|
|
105
|
+
if (hasKey !== hasUrl) {
|
|
106
|
+
console.warn(
|
|
107
|
+
`[search] The agent is half configured: ${FIELDS.agentAppKey} and ${FIELDS.agentApiBaseUrl} must both be set, or both empty. Using plain search.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
70
110
|
return normalizeCommerceSearchConfig(raw, language ? { language } : {});
|
|
71
111
|
}
|
|
72
112
|
|
|
@@ -74,30 +114,39 @@ async function fetchCommerceSearchConfig({
|
|
|
74
114
|
function useCommerceSearchConfig({
|
|
75
115
|
country,
|
|
76
116
|
language,
|
|
77
|
-
|
|
117
|
+
clientId,
|
|
118
|
+
isOpen
|
|
78
119
|
}) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
);
|
|
120
|
+
const market = `${country}|${language}|${clientId ?? ""}`;
|
|
121
|
+
const [loaded, setLoaded] = react.useState(null);
|
|
122
|
+
const currentMarket = react.useRef(market);
|
|
82
123
|
react.useEffect(() => {
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
currentMarket.current = market;
|
|
125
|
+
}, [market]);
|
|
126
|
+
const reading = react.useRef(null);
|
|
127
|
+
react.useEffect(() => {
|
|
128
|
+
if (!isOpen || loaded?.market === market || reading.current === market) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
reading.current = market;
|
|
85
132
|
fetchCommerceSearchConfig({
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
133
|
+
country,
|
|
134
|
+
language,
|
|
135
|
+
...clientId ? { clientId } : {}
|
|
136
|
+
}).then((config) => {
|
|
137
|
+
if (currentMarket.current === market) {
|
|
138
|
+
setLoaded({ market, config });
|
|
139
|
+
}
|
|
90
140
|
}).catch((error) => {
|
|
91
141
|
console.warn(
|
|
92
142
|
"[search] Could not load the product search configuration:",
|
|
93
143
|
error
|
|
94
144
|
);
|
|
145
|
+
}).finally(() => {
|
|
146
|
+
if (reading.current === market) reading.current = null;
|
|
95
147
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
};
|
|
99
|
-
}, [enabled, country, language]);
|
|
100
|
-
return config;
|
|
148
|
+
}, [isOpen, market, loaded, country, language, clientId]);
|
|
149
|
+
return loaded?.market === market ? loaded.config : COMMERCE_SEARCH_CONFIG_DEFAULTS;
|
|
101
150
|
}
|
|
102
151
|
|
|
103
152
|
// src/copy/searchPanelCopy.ts
|
|
@@ -180,25 +229,38 @@ function useSearchPanelCopy({
|
|
|
180
229
|
}
|
|
181
230
|
|
|
182
231
|
// src/services/localeParts.ts
|
|
183
|
-
|
|
232
|
+
var TWO_LETTERS = /^[A-Za-z]{2}$/;
|
|
233
|
+
function marketProblem(country, language) {
|
|
234
|
+
const countryText = String(country ?? "").trim();
|
|
235
|
+
const languageText = String(language ?? "").trim();
|
|
236
|
+
if (TWO_LETTERS.test(countryText) && TWO_LETTERS.test(languageText)) {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
return `the market is not valid (country ${JSON.stringify(countryText)}, language ${JSON.stringify(languageText)}): each must be two letters, for example US and en`;
|
|
240
|
+
}
|
|
241
|
+
function isValidMarket(country, language) {
|
|
242
|
+
return marketProblem(country, language) === void 0;
|
|
243
|
+
}
|
|
244
|
+
function createLocaleParts({
|
|
184
245
|
country,
|
|
185
|
-
language
|
|
186
|
-
locale
|
|
246
|
+
language
|
|
187
247
|
}) {
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const
|
|
248
|
+
const problem = marketProblem(country, language);
|
|
249
|
+
if (problem) throw new Error(problem);
|
|
250
|
+
const normalizedCountry = country.trim().toUpperCase();
|
|
251
|
+
const normalizedLanguage = language.trim().toLowerCase();
|
|
191
252
|
return {
|
|
192
|
-
country: normalizedCountry
|
|
193
|
-
language: normalizedLanguage
|
|
194
|
-
locale:
|
|
253
|
+
country: normalizedCountry,
|
|
254
|
+
language: normalizedLanguage,
|
|
255
|
+
locale: `${normalizedCountry}-${normalizedLanguage}`
|
|
195
256
|
};
|
|
196
257
|
}
|
|
197
|
-
function toCatalogLocale(
|
|
198
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
199
|
-
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
258
|
+
function toCatalogLocale({ country, language }) {
|
|
200
259
|
return `${language}_${country}`;
|
|
201
260
|
}
|
|
261
|
+
function toPathPrefix({ country, language }) {
|
|
262
|
+
return `/${country.toLowerCase()}/${language}`;
|
|
263
|
+
}
|
|
202
264
|
|
|
203
265
|
// src/services/catalogRequest.ts
|
|
204
266
|
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
@@ -223,13 +285,14 @@ function buildCatalogSuggestionsUrl(apiBaseUrl) {
|
|
|
223
285
|
function buildSuggestionsRequestUrl({
|
|
224
286
|
suggestionsUrl,
|
|
225
287
|
searchTerm,
|
|
288
|
+
size,
|
|
226
289
|
storeId,
|
|
227
290
|
localeParts
|
|
228
291
|
}) {
|
|
229
292
|
const queryParams = new URLSearchParams({
|
|
230
293
|
searchTerm,
|
|
231
294
|
page: String(DEFAULT_SUGGESTION_PAGE),
|
|
232
|
-
size: String(
|
|
295
|
+
size: String(size),
|
|
233
296
|
locale: toCatalogLocale(localeParts),
|
|
234
297
|
storeId
|
|
235
298
|
});
|
|
@@ -241,12 +304,21 @@ async function resolveCatalogRequestConfig({
|
|
|
241
304
|
getMarketConfig
|
|
242
305
|
}) {
|
|
243
306
|
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
307
|
+
const suggestionsUrl = buildCatalogSuggestionsUrl(
|
|
308
|
+
resolveCatalogApiBaseUrl(marketConfig)
|
|
309
|
+
);
|
|
310
|
+
if (!suggestionsUrl) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`no suggestions API for market ${country}: the market configuration has no gateway`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
const storeId = resolveCatalogStoreId(marketConfig);
|
|
316
|
+
if (!storeId) {
|
|
317
|
+
throw new Error(
|
|
318
|
+
`no suggestions API for market ${country}: the market configuration has no store`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
return { suggestionsUrl, storeId };
|
|
250
322
|
}
|
|
251
323
|
function resolveCustomerTypeHeaders(getCustomerType) {
|
|
252
324
|
const customerType = getCustomerType?.()?.trim();
|
|
@@ -291,7 +363,7 @@ function getSessionStorage(providedStorage) {
|
|
|
291
363
|
return null;
|
|
292
364
|
}
|
|
293
365
|
function getRecentHeaderSearchesStorageKey(localeParts) {
|
|
294
|
-
const { country, language, locale } =
|
|
366
|
+
const { country, language, locale } = localeParts;
|
|
295
367
|
return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
|
|
296
368
|
}
|
|
297
369
|
function getRecentHeaderSearches(localeParts, storage) {
|
|
@@ -354,13 +426,14 @@ function buildSearchResultsPath(query, localeParts) {
|
|
|
354
426
|
/%2D/gi,
|
|
355
427
|
"-"
|
|
356
428
|
);
|
|
357
|
-
|
|
358
|
-
const language = localeParts.language.toLowerCase();
|
|
359
|
-
return `/${country}/${language}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
|
|
429
|
+
return `${toPathPrefix(localeParts)}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
|
|
360
430
|
}
|
|
431
|
+
var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
361
432
|
function normalizeOptionUrl(rawValue) {
|
|
362
433
|
if (typeof rawValue !== "string") return "";
|
|
363
|
-
|
|
434
|
+
const value = rawValue.trim();
|
|
435
|
+
if (!value || value.startsWith("/") || SCHEME.test(value)) return value;
|
|
436
|
+
return `/${value}`;
|
|
364
437
|
}
|
|
365
438
|
function safeDecodeURIComponent(value) {
|
|
366
439
|
try {
|
|
@@ -390,16 +463,14 @@ function slugifyPathSegment(value) {
|
|
|
390
463
|
function buildProductSlugPath(slugValue, localeParts) {
|
|
391
464
|
const slug = slugifyPathSegment(slugValue);
|
|
392
465
|
if (!slug) return "";
|
|
393
|
-
|
|
394
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
395
|
-
return `/${country}/${language}/product/${slug}`;
|
|
466
|
+
return `${toPathPrefix(localeParts)}/product/${slug}`;
|
|
396
467
|
}
|
|
397
468
|
function isNavigableUrl(value) {
|
|
398
469
|
if (!value) return false;
|
|
399
470
|
return value.startsWith("/") || /^https?:\/\//i.test(value);
|
|
400
471
|
}
|
|
401
472
|
function stripLocalePrefix(path, localeParts) {
|
|
402
|
-
const prefix =
|
|
473
|
+
const prefix = toPathPrefix(localeParts);
|
|
403
474
|
return path.startsWith(prefix) ? path.slice(prefix.length) || "/" : path;
|
|
404
475
|
}
|
|
405
476
|
function resolveOptionHref(option, localeParts) {
|
|
@@ -453,26 +524,21 @@ function getBrowserLocation(providedLocation) {
|
|
|
453
524
|
return window.location;
|
|
454
525
|
return null;
|
|
455
526
|
}
|
|
456
|
-
function
|
|
457
|
-
if (fetchImplOption !== void 0) return fetchImplOption;
|
|
458
|
-
return typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
459
|
-
}
|
|
460
|
-
function createSearchService(options = {}) {
|
|
527
|
+
function createSearchService(options) {
|
|
461
528
|
const {
|
|
462
529
|
fetchImpl: fetchImplOption,
|
|
463
530
|
location: providedLocation,
|
|
464
531
|
storage: providedStorage,
|
|
465
532
|
country,
|
|
466
533
|
language,
|
|
467
|
-
locale,
|
|
468
534
|
getMarketConfig,
|
|
469
535
|
getCustomerType,
|
|
470
|
-
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
|
|
536
|
+
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
|
|
537
|
+
maxSuggestions = DEFAULT_MAX_SUGGESTIONS
|
|
471
538
|
} = options;
|
|
472
|
-
const fetchImpl = resolveFetch(fetchImplOption);
|
|
473
539
|
const locationLike = getBrowserLocation(providedLocation);
|
|
474
540
|
const storage = getSessionStorage(providedStorage);
|
|
475
|
-
const localeParts =
|
|
541
|
+
const localeParts = createLocaleParts({ country, language });
|
|
476
542
|
let catalogRequestConfigPromise = null;
|
|
477
543
|
function loadCatalogRequestConfig() {
|
|
478
544
|
if (!catalogRequestConfigPromise) {
|
|
@@ -494,16 +560,16 @@ function createSearchService(options = {}) {
|
|
|
494
560
|
const service = {
|
|
495
561
|
async fetchSuggestions(searchTerm) {
|
|
496
562
|
const trimmedSearchTerm = String(searchTerm ?? "").trim();
|
|
497
|
-
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength
|
|
563
|
+
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength) {
|
|
498
564
|
return emptySuggestions();
|
|
499
565
|
}
|
|
500
566
|
const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
|
|
501
|
-
if (!suggestionsUrl || !storeId) return emptySuggestions();
|
|
502
567
|
const responsePayload = await fetchJson(
|
|
503
|
-
|
|
568
|
+
fetchImplOption ?? fetch.bind(globalThis),
|
|
504
569
|
buildSuggestionsRequestUrl({
|
|
505
570
|
suggestionsUrl,
|
|
506
571
|
searchTerm: trimmedSearchTerm,
|
|
572
|
+
size: maxSuggestions,
|
|
507
573
|
storeId,
|
|
508
574
|
localeParts
|
|
509
575
|
}),
|
|
@@ -553,12 +619,15 @@ function createSearchService(options = {}) {
|
|
|
553
619
|
}
|
|
554
620
|
|
|
555
621
|
// src/services/marketConfig.ts
|
|
556
|
-
var fetchMarketConfig = async ({
|
|
557
|
-
|
|
622
|
+
var fetchMarketConfig = async ({
|
|
623
|
+
country,
|
|
624
|
+
clientId
|
|
625
|
+
}) => {
|
|
626
|
+
const countryCode = country.trim().toUpperCase();
|
|
558
627
|
const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
|
|
559
628
|
country: countryCode,
|
|
560
629
|
...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
|
|
561
|
-
clientId:
|
|
630
|
+
...clientId ? { clientId } : {},
|
|
562
631
|
shoppingContext: STOREFRONT_SHOPPING_CONTEXT
|
|
563
632
|
});
|
|
564
633
|
return configuration?.[EQUINOX_MARKETS_MAP] ?? null;
|
|
@@ -577,14 +646,25 @@ var SEARCH_GTM_USER_STATUS = {
|
|
|
577
646
|
var GTM_FALLBACK_NA = "NA";
|
|
578
647
|
|
|
579
648
|
// src/utils/navigationTarget.ts
|
|
649
|
+
var EXTERNAL_URL = /^https?:\/\//i;
|
|
650
|
+
var INTERNAL_PATH = /^\/(?![/\\])[^\t\n\r]*$/;
|
|
580
651
|
function toNavigationTarget(path) {
|
|
581
|
-
|
|
652
|
+
if (EXTERNAL_URL.test(path)) return { type: "external", url: path };
|
|
653
|
+
if (INTERNAL_PATH.test(path)) return { type: "internal", path };
|
|
654
|
+
return void 0;
|
|
582
655
|
}
|
|
583
656
|
|
|
584
657
|
// src/utils/productEnvironment.ts
|
|
585
|
-
var PRODUCT_ENVIRONMENTS =
|
|
658
|
+
var PRODUCT_ENVIRONMENTS = {
|
|
659
|
+
dev: true,
|
|
660
|
+
test: true,
|
|
661
|
+
prod: true
|
|
662
|
+
};
|
|
663
|
+
function isProductEnvironment(code) {
|
|
664
|
+
return Object.keys(PRODUCT_ENVIRONMENTS).includes(code);
|
|
665
|
+
}
|
|
586
666
|
function toProductEnvironment(environmentCode) {
|
|
587
|
-
return environmentCode &&
|
|
667
|
+
return environmentCode !== void 0 && isProductEnvironment(environmentCode) ? environmentCode : void 0;
|
|
588
668
|
}
|
|
589
669
|
function getProductEnvironment() {
|
|
590
670
|
try {
|
|
@@ -593,6 +673,47 @@ function getProductEnvironment() {
|
|
|
593
673
|
return void 0;
|
|
594
674
|
}
|
|
595
675
|
}
|
|
676
|
+
var NOTHING = () => void 0;
|
|
677
|
+
function CommerceSearchUnavailable({
|
|
678
|
+
isOpen,
|
|
679
|
+
onClose,
|
|
680
|
+
country,
|
|
681
|
+
language,
|
|
682
|
+
logo
|
|
683
|
+
}) {
|
|
684
|
+
react.useEffect(() => {
|
|
685
|
+
console.error(
|
|
686
|
+
`[search] Search is unavailable: ${marketProblem(country, language)}`
|
|
687
|
+
);
|
|
688
|
+
}, [country, language]);
|
|
689
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
690
|
+
reactSearchComponents.SearchPanel,
|
|
691
|
+
{
|
|
692
|
+
isOpen,
|
|
693
|
+
onClose,
|
|
694
|
+
copy: SEARCH_PANEL_COPY_EN,
|
|
695
|
+
logo,
|
|
696
|
+
input: { value: "", onChange: NOTHING, onSubmit: NOTHING },
|
|
697
|
+
suggestions: {
|
|
698
|
+
items: [],
|
|
699
|
+
loading: false,
|
|
700
|
+
showNoResults: false,
|
|
701
|
+
showDidYouMean: false,
|
|
702
|
+
hasMore: false,
|
|
703
|
+
query: "",
|
|
704
|
+
onSelect: NOTHING,
|
|
705
|
+
onRunAction: NOTHING,
|
|
706
|
+
onSeeMore: NOTHING
|
|
707
|
+
},
|
|
708
|
+
recentSearches: {
|
|
709
|
+
items: [],
|
|
710
|
+
onSelect: NOTHING,
|
|
711
|
+
onClear: NOTHING
|
|
712
|
+
},
|
|
713
|
+
agentPanel: /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: "Search is unavailable right now." })
|
|
714
|
+
}
|
|
715
|
+
);
|
|
716
|
+
}
|
|
596
717
|
|
|
597
718
|
// src/utils/nuiSearchGate.ts
|
|
598
719
|
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
@@ -624,29 +745,23 @@ function resolveNuiSearchFlag(env = {}) {
|
|
|
624
745
|
return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
|
|
625
746
|
}
|
|
626
747
|
function isNuiSearchEnabled({
|
|
627
|
-
isAuthenticated,
|
|
628
748
|
flag,
|
|
629
|
-
|
|
749
|
+
enabledForCustomer
|
|
630
750
|
}) {
|
|
631
|
-
return flag === true &&
|
|
751
|
+
return flag === true && enabledForCustomer;
|
|
632
752
|
}
|
|
633
753
|
|
|
634
754
|
// src/hooks/useNuiAgentGate.ts
|
|
635
755
|
function useNuiAgentGate({
|
|
636
756
|
hasAgent,
|
|
637
757
|
isOpen,
|
|
638
|
-
|
|
639
|
-
allowGuestAgent
|
|
758
|
+
enabledForCustomer
|
|
640
759
|
}) {
|
|
641
760
|
const [flag, setFlag] = react.useState(void 0);
|
|
642
761
|
react.useEffect(() => {
|
|
643
762
|
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
644
763
|
}, [isOpen]);
|
|
645
|
-
return hasAgent && isNuiSearchEnabled({
|
|
646
|
-
isAuthenticated,
|
|
647
|
-
flag,
|
|
648
|
-
allowGuests: allowGuestAgent
|
|
649
|
-
});
|
|
764
|
+
return hasAgent && isNuiSearchEnabled({ flag, enabledForCustomer });
|
|
650
765
|
}
|
|
651
766
|
function uniqueRecentSearches(queries) {
|
|
652
767
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -980,7 +1095,7 @@ var FETCH_FAILED = {
|
|
|
980
1095
|
emptyFetched: true,
|
|
981
1096
|
failed: true
|
|
982
1097
|
};
|
|
983
|
-
function toSettledResults(response) {
|
|
1098
|
+
function toSettledResults(response, maxSuggestions) {
|
|
984
1099
|
const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
|
|
985
1100
|
if (didYouMean.length > 0) {
|
|
986
1101
|
return {
|
|
@@ -995,18 +1110,19 @@ function toSettledResults(response) {
|
|
|
995
1110
|
resolveTotalResultsCount2(response, allSuggestions.length),
|
|
996
1111
|
allSuggestions.length
|
|
997
1112
|
);
|
|
998
|
-
const suggestions = allSuggestions.slice(0,
|
|
1113
|
+
const suggestions = allSuggestions.slice(0, maxSuggestions);
|
|
999
1114
|
return {
|
|
1000
1115
|
suggestions,
|
|
1001
1116
|
didYouMean,
|
|
1002
|
-
hasMoreResults: total >
|
|
1117
|
+
hasMoreResults: total > maxSuggestions,
|
|
1003
1118
|
emptyFetched: suggestions.length === 0,
|
|
1004
1119
|
failed: false
|
|
1005
1120
|
};
|
|
1006
1121
|
}
|
|
1007
1122
|
function useSearchSuggestions({
|
|
1008
1123
|
service,
|
|
1009
|
-
enabled = true
|
|
1124
|
+
enabled = true,
|
|
1125
|
+
maxSuggestions = DEFAULT_MAX_SUGGESTIONS
|
|
1010
1126
|
}) {
|
|
1011
1127
|
const [query, setQuery] = react.useState("");
|
|
1012
1128
|
const [suggestions, setSuggestions] = react.useState([]);
|
|
@@ -1063,7 +1179,8 @@ function useSearchSuggestions({
|
|
|
1063
1179
|
let results;
|
|
1064
1180
|
try {
|
|
1065
1181
|
results = toSettledResults(
|
|
1066
|
-
await service.fetchSuggestions(trimmedQuery)
|
|
1182
|
+
await service.fetchSuggestions(trimmedQuery),
|
|
1183
|
+
maxSuggestions
|
|
1067
1184
|
);
|
|
1068
1185
|
} catch (error) {
|
|
1069
1186
|
console.warn("[search] Could not load suggestions:", error);
|
|
@@ -1073,9 +1190,10 @@ function useSearchSuggestions({
|
|
|
1073
1190
|
settle(results, trimmedQuery);
|
|
1074
1191
|
setLoading(false);
|
|
1075
1192
|
},
|
|
1076
|
-
[enabled, service, resetSuggestionsState, settle]
|
|
1193
|
+
[enabled, maxSuggestions, service, resetSuggestionsState, settle]
|
|
1077
1194
|
);
|
|
1078
1195
|
react.useEffect(() => {
|
|
1196
|
+
latestRequestId.current += 1;
|
|
1079
1197
|
if (!enabled) {
|
|
1080
1198
|
resetSuggestionsState();
|
|
1081
1199
|
return void 0;
|
|
@@ -1083,7 +1201,6 @@ function useSearchSuggestions({
|
|
|
1083
1201
|
if (fetchDebounced.current) {
|
|
1084
1202
|
clearTimeout(fetchDebounced.current);
|
|
1085
1203
|
}
|
|
1086
|
-
latestRequestId.current += 1;
|
|
1087
1204
|
fetchDebounced.current = setTimeout(() => {
|
|
1088
1205
|
void runFetch(query);
|
|
1089
1206
|
}, DEBOUNCE_MS);
|
|
@@ -1365,11 +1482,11 @@ function useHandoff({
|
|
|
1365
1482
|
onUnavailableRef.current = onUnavailable;
|
|
1366
1483
|
});
|
|
1367
1484
|
react.useEffect(() => {
|
|
1368
|
-
let
|
|
1485
|
+
let canceled = false;
|
|
1369
1486
|
let timer;
|
|
1370
1487
|
const startedAt = Date.now();
|
|
1371
1488
|
const tryOpen = () => {
|
|
1372
|
-
if (
|
|
1489
|
+
if (canceled) return;
|
|
1373
1490
|
const handle = getSalesAIHandle();
|
|
1374
1491
|
if (handle) {
|
|
1375
1492
|
handle.open({
|
|
@@ -1389,7 +1506,7 @@ function useHandoff({
|
|
|
1389
1506
|
};
|
|
1390
1507
|
tryOpen();
|
|
1391
1508
|
return () => {
|
|
1392
|
-
|
|
1509
|
+
canceled = true;
|
|
1393
1510
|
if (timer !== void 0) clearTimeout(timer);
|
|
1394
1511
|
};
|
|
1395
1512
|
}, [query]);
|
|
@@ -1430,12 +1547,12 @@ function CommerceSearchView({
|
|
|
1430
1547
|
onClose,
|
|
1431
1548
|
service,
|
|
1432
1549
|
navigate,
|
|
1433
|
-
isAuthenticated,
|
|
1434
1550
|
agent,
|
|
1435
|
-
|
|
1551
|
+
agentEnabledForCustomer,
|
|
1436
1552
|
agentProducts,
|
|
1437
1553
|
logo,
|
|
1438
1554
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
|
|
1555
|
+
maxSuggestions = DEFAULT_MAX_SUGGESTIONS,
|
|
1439
1556
|
gtmContext
|
|
1440
1557
|
}) {
|
|
1441
1558
|
const [handoff, setHandoff] = react.useState(null);
|
|
@@ -1443,6 +1560,7 @@ function CommerceSearchView({
|
|
|
1443
1560
|
const [pendingTerm, setPendingTerm] = react.useState(null);
|
|
1444
1561
|
const search = useSearchSuggestions({
|
|
1445
1562
|
service,
|
|
1563
|
+
maxSuggestions,
|
|
1446
1564
|
enabled: isOpen && agentQuery === null
|
|
1447
1565
|
});
|
|
1448
1566
|
const {
|
|
@@ -1458,8 +1576,7 @@ function CommerceSearchView({
|
|
|
1458
1576
|
const agentEnabled = useNuiAgentGate({
|
|
1459
1577
|
hasAgent: agent !== void 0,
|
|
1460
1578
|
isOpen,
|
|
1461
|
-
|
|
1462
|
-
allowGuestAgent
|
|
1579
|
+
enabledForCustomer: agentEnabledForCustomer
|
|
1463
1580
|
});
|
|
1464
1581
|
const { recentSearches, clearRecentSearches } = useRecentSearches({
|
|
1465
1582
|
service,
|
|
@@ -1617,43 +1734,89 @@ function CommerceSearchView({
|
|
|
1617
1734
|
);
|
|
1618
1735
|
}
|
|
1619
1736
|
var GUEST = "guest";
|
|
1620
|
-
|
|
1737
|
+
var AGENT_SWITCH_FOR = {
|
|
1738
|
+
guest: "guest",
|
|
1739
|
+
"Brand Affiliate": "brandAffiliate",
|
|
1740
|
+
"Retail Customer": "retailCustomer",
|
|
1741
|
+
"Preferred Customer/Member": "preferredCustomer"
|
|
1742
|
+
};
|
|
1743
|
+
var AGENT_SWITCHES = new Map(
|
|
1744
|
+
Object.entries(AGENT_SWITCH_FOR)
|
|
1745
|
+
);
|
|
1746
|
+
function CommerceSearch(props) {
|
|
1747
|
+
if (!isValidMarket(props.country, props.language)) {
|
|
1748
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1749
|
+
CommerceSearchUnavailable,
|
|
1750
|
+
{
|
|
1751
|
+
isOpen: props.isOpen,
|
|
1752
|
+
onClose: props.onClose,
|
|
1753
|
+
country: props.country,
|
|
1754
|
+
language: props.language,
|
|
1755
|
+
logo: props.logo
|
|
1756
|
+
}
|
|
1757
|
+
);
|
|
1758
|
+
}
|
|
1759
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ValidMarketSearch, { ...props });
|
|
1760
|
+
}
|
|
1761
|
+
function ValidMarketSearch({
|
|
1621
1762
|
isOpen,
|
|
1622
1763
|
onClose,
|
|
1623
1764
|
navigate,
|
|
1624
1765
|
customer,
|
|
1625
1766
|
country,
|
|
1626
1767
|
language,
|
|
1768
|
+
clientId,
|
|
1627
1769
|
logo
|
|
1628
1770
|
}) {
|
|
1629
|
-
const [hasOpened, setHasOpened] = react.useState(false);
|
|
1630
|
-
react.useEffect(() => {
|
|
1631
|
-
if (isOpen) setHasOpened(true);
|
|
1632
|
-
}, [isOpen]);
|
|
1633
1771
|
const copy = useSearchPanelCopy({ language });
|
|
1634
|
-
const { minimumSearchLength, agent,
|
|
1772
|
+
const { minimumSearchLength, maxSuggestions, agent, agentEnabledFor } = useCommerceSearchConfig({
|
|
1635
1773
|
country,
|
|
1636
1774
|
language,
|
|
1637
|
-
|
|
1775
|
+
...clientId ? { clientId } : {},
|
|
1776
|
+
isOpen
|
|
1638
1777
|
});
|
|
1639
1778
|
const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
|
|
1640
1779
|
const customerTypeRef = react.useRef(customerType);
|
|
1641
1780
|
react.useEffect(() => {
|
|
1642
1781
|
customerTypeRef.current = customerType;
|
|
1643
1782
|
}, [customerType]);
|
|
1783
|
+
const agentEnabledForCustomer = agentEnabledFor[AGENT_SWITCHES.get(customerType) ?? "guest"];
|
|
1784
|
+
const getMarketConfig = react.useCallback(
|
|
1785
|
+
(context) => fetchMarketConfig({
|
|
1786
|
+
...context,
|
|
1787
|
+
...clientId ? { clientId } : {}
|
|
1788
|
+
}),
|
|
1789
|
+
[clientId]
|
|
1790
|
+
);
|
|
1644
1791
|
const service = react.useMemo(
|
|
1645
1792
|
() => createSearchService({
|
|
1646
1793
|
country,
|
|
1647
1794
|
language,
|
|
1648
|
-
|
|
1649
|
-
getMarketConfig: fetchMarketConfig,
|
|
1795
|
+
getMarketConfig,
|
|
1650
1796
|
minimumSearchLength,
|
|
1797
|
+
maxSuggestions,
|
|
1651
1798
|
getCustomerType: () => customerTypeRef.current
|
|
1652
1799
|
}),
|
|
1653
|
-
[
|
|
1800
|
+
[
|
|
1801
|
+
country,
|
|
1802
|
+
language,
|
|
1803
|
+
getMarketConfig,
|
|
1804
|
+
minimumSearchLength,
|
|
1805
|
+
maxSuggestions
|
|
1806
|
+
]
|
|
1654
1807
|
);
|
|
1655
1808
|
const navigateToPath = react.useCallback(
|
|
1656
|
-
(path) =>
|
|
1809
|
+
(path) => {
|
|
1810
|
+
const target = toNavigationTarget(path);
|
|
1811
|
+
if (target) {
|
|
1812
|
+
navigate(target);
|
|
1813
|
+
} else {
|
|
1814
|
+
console.warn(
|
|
1815
|
+
"[search] Not navigating to a path the SDK does not recognize:",
|
|
1816
|
+
path
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1657
1820
|
[navigate]
|
|
1658
1821
|
);
|
|
1659
1822
|
const { isAuthenticated, accountType, userId } = customer;
|
|
@@ -1683,13 +1846,13 @@ function CommerceSearch({
|
|
|
1683
1846
|
onClose,
|
|
1684
1847
|
service,
|
|
1685
1848
|
navigate: navigateToPath,
|
|
1686
|
-
isAuthenticated,
|
|
1687
1849
|
...agent ? { agent } : {},
|
|
1688
|
-
|
|
1850
|
+
agentEnabledForCustomer,
|
|
1689
1851
|
...agentProducts ? { agentProducts } : {},
|
|
1690
1852
|
copy,
|
|
1691
1853
|
logo,
|
|
1692
1854
|
minimumSearchLength,
|
|
1855
|
+
maxSuggestions,
|
|
1693
1856
|
gtmContext
|
|
1694
1857
|
}
|
|
1695
1858
|
);
|