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