@nuskin/react-search-sdk 1.0.0 → 1.0.1-code-cleanup.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +442 -337
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +442 -337
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -127,21 +127,41 @@ async function fetchSearchPanelCopy(_options = {}) {
|
|
|
127
127
|
|
|
128
128
|
// src/copy/loadSearchPanelCopy.ts
|
|
129
129
|
var copyByLanguage = /* @__PURE__ */ new Map();
|
|
130
|
+
var loadedByLanguage = /* @__PURE__ */ new Map();
|
|
131
|
+
function cacheKey(language) {
|
|
132
|
+
return (language ?? "").toLowerCase();
|
|
133
|
+
}
|
|
130
134
|
function loadSearchPanelCopy(language) {
|
|
131
|
-
const key = (language
|
|
135
|
+
const key = cacheKey(language);
|
|
132
136
|
const cached = copyByLanguage.get(key);
|
|
133
137
|
if (cached) return cached;
|
|
134
138
|
const pending = fetchSearchPanelCopy(language ? { } : {});
|
|
135
139
|
copyByLanguage.set(key, pending);
|
|
136
|
-
pending.
|
|
140
|
+
pending.then(
|
|
141
|
+
(copy) => {
|
|
142
|
+
if (copyByLanguage.get(key) === pending) {
|
|
143
|
+
loadedByLanguage.set(key, copy);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
() => {
|
|
147
|
+
if (copyByLanguage.get(key) === pending) {
|
|
148
|
+
copyByLanguage.delete(key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
137
152
|
return pending;
|
|
138
153
|
}
|
|
154
|
+
function peekSearchPanelCopy(language) {
|
|
155
|
+
return loadedByLanguage.get(cacheKey(language));
|
|
156
|
+
}
|
|
139
157
|
|
|
140
158
|
// src/hooks/useSearchPanelCopy.ts
|
|
141
159
|
function useSearchPanelCopy({
|
|
142
160
|
language
|
|
143
161
|
}) {
|
|
144
|
-
const [copy, setCopy] = react.useState(
|
|
162
|
+
const [copy, setCopy] = react.useState(
|
|
163
|
+
() => peekSearchPanelCopy(language)
|
|
164
|
+
);
|
|
145
165
|
react.useEffect(() => {
|
|
146
166
|
let isStale = false;
|
|
147
167
|
loadSearchPanelCopy(language).then((next) => {
|
|
@@ -159,21 +179,7 @@ function useSearchPanelCopy({
|
|
|
159
179
|
return copy;
|
|
160
180
|
}
|
|
161
181
|
|
|
162
|
-
// src/services/
|
|
163
|
-
function canUseStorage(storage) {
|
|
164
|
-
return Boolean(storage) && typeof storage.getItem === "function";
|
|
165
|
-
}
|
|
166
|
-
function getSessionStorage(providedStorage) {
|
|
167
|
-
if (providedStorage) return providedStorage;
|
|
168
|
-
if (typeof window !== "undefined") {
|
|
169
|
-
try {
|
|
170
|
-
return window.sessionStorage;
|
|
171
|
-
} catch {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
182
|
+
// src/services/localeParts.ts
|
|
177
183
|
function normalizeLocaleParts({
|
|
178
184
|
country,
|
|
179
185
|
language,
|
|
@@ -188,6 +194,102 @@ function normalizeLocaleParts({
|
|
|
188
194
|
locale: normalizedLocale
|
|
189
195
|
};
|
|
190
196
|
}
|
|
197
|
+
function toCatalogLocale(localeParts) {
|
|
198
|
+
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
199
|
+
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
200
|
+
return `${language}_${country}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/services/catalogRequest.ts
|
|
204
|
+
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
205
|
+
const apiGateway = String(apiGatewayPath ?? "").trim();
|
|
206
|
+
if (!apiGateway) return "";
|
|
207
|
+
return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
|
|
208
|
+
}
|
|
209
|
+
function resolveCatalogApiBaseUrl(marketConfig) {
|
|
210
|
+
return resolveApiGatewayPath(marketConfig?.api_gateway_path);
|
|
211
|
+
}
|
|
212
|
+
function resolveCatalogStoreId(marketConfig) {
|
|
213
|
+
return String(marketConfig?.store_id ?? "").trim();
|
|
214
|
+
}
|
|
215
|
+
function buildCatalogSuggestionsUrl(apiBaseUrl) {
|
|
216
|
+
const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
|
|
217
|
+
if (!normalizedBase) return "";
|
|
218
|
+
const suggestionsPath = normalizedBase.endsWith(
|
|
219
|
+
`/${STOREFRONT_SHOPPING_CONTEXT}`
|
|
220
|
+
) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
|
|
221
|
+
return `${normalizedBase}${suggestionsPath}`;
|
|
222
|
+
}
|
|
223
|
+
function buildSuggestionsRequestUrl({
|
|
224
|
+
suggestionsUrl,
|
|
225
|
+
searchTerm,
|
|
226
|
+
storeId,
|
|
227
|
+
localeParts
|
|
228
|
+
}) {
|
|
229
|
+
const queryParams = new URLSearchParams({
|
|
230
|
+
searchTerm,
|
|
231
|
+
page: String(DEFAULT_SUGGESTION_PAGE),
|
|
232
|
+
size: String(DEFAULT_SUGGESTION_SIZE),
|
|
233
|
+
locale: toCatalogLocale(localeParts),
|
|
234
|
+
storeId
|
|
235
|
+
});
|
|
236
|
+
return `${suggestionsUrl}?${queryParams.toString()}`;
|
|
237
|
+
}
|
|
238
|
+
async function resolveCatalogRequestConfig({
|
|
239
|
+
locationLike,
|
|
240
|
+
country,
|
|
241
|
+
getMarketConfig
|
|
242
|
+
}) {
|
|
243
|
+
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
244
|
+
return {
|
|
245
|
+
suggestionsUrl: buildCatalogSuggestionsUrl(
|
|
246
|
+
resolveCatalogApiBaseUrl(marketConfig)
|
|
247
|
+
),
|
|
248
|
+
storeId: resolveCatalogStoreId(marketConfig)
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function resolveCustomerTypeHeaders(getCustomerType) {
|
|
252
|
+
const customerType = getCustomerType?.()?.trim();
|
|
253
|
+
return customerType ? { "X-Customer-Type": customerType } : void 0;
|
|
254
|
+
}
|
|
255
|
+
var NO_RESULTS_ERROR_CODE = "SFOSER002";
|
|
256
|
+
async function isNoResultsResponse(response) {
|
|
257
|
+
if (response.status !== 404) return false;
|
|
258
|
+
try {
|
|
259
|
+
const body = await response.json();
|
|
260
|
+
return body?.code === NO_RESULTS_ERROR_CODE;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function fetchJson(fetchImpl, requestUrl, requestHeaders = {}) {
|
|
266
|
+
const response = await fetchImpl(requestUrl, {
|
|
267
|
+
method: "GET",
|
|
268
|
+
credentials: "include",
|
|
269
|
+
headers: { Accept: "application/json", ...requestHeaders }
|
|
270
|
+
});
|
|
271
|
+
if (!response.ok) {
|
|
272
|
+
if (await isNoResultsResponse(response)) return null;
|
|
273
|
+
throw new Error(`Request failed with status ${response.status}`);
|
|
274
|
+
}
|
|
275
|
+
return response.json();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/services/recentSearches.ts
|
|
279
|
+
function canUseStorage(storage) {
|
|
280
|
+
return Boolean(storage) && typeof storage.getItem === "function";
|
|
281
|
+
}
|
|
282
|
+
function getSessionStorage(providedStorage) {
|
|
283
|
+
if (providedStorage) return providedStorage;
|
|
284
|
+
if (typeof window !== "undefined") {
|
|
285
|
+
try {
|
|
286
|
+
return window.sessionStorage;
|
|
287
|
+
} catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
191
293
|
function getRecentHeaderSearchesStorageKey(localeParts) {
|
|
192
294
|
const { country, language, locale } = normalizeLocaleParts(localeParts);
|
|
193
295
|
return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
|
|
@@ -243,63 +345,8 @@ function clearRecentHeaderSearches(localeParts, storage) {
|
|
|
243
345
|
} catch {
|
|
244
346
|
}
|
|
245
347
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
if (typeof window !== "undefined" && window.location)
|
|
249
|
-
return window.location;
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
253
|
-
const apiGateway = String(apiGatewayPath ?? "").trim();
|
|
254
|
-
if (!apiGateway) return "";
|
|
255
|
-
return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
|
|
256
|
-
}
|
|
257
|
-
function resolveCatalogApiBaseUrl(marketConfig) {
|
|
258
|
-
return resolveApiGatewayPath(marketConfig?.api_gateway_path);
|
|
259
|
-
}
|
|
260
|
-
function resolveCatalogStoreId(marketConfig) {
|
|
261
|
-
return String(marketConfig?.store_id ?? "").trim();
|
|
262
|
-
}
|
|
263
|
-
function buildCatalogSuggestionsUrl(apiBaseUrl) {
|
|
264
|
-
const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
|
|
265
|
-
if (!normalizedBase) return "";
|
|
266
|
-
const suggestionsPath = normalizedBase.endsWith(
|
|
267
|
-
`/${STOREFRONT_SHOPPING_CONTEXT}`
|
|
268
|
-
) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
|
|
269
|
-
return `${normalizedBase}${suggestionsPath}`;
|
|
270
|
-
}
|
|
271
|
-
function resolveSuggestionsUrl(suggestionsApi, locationLike) {
|
|
272
|
-
const pathOrUrl = String(suggestionsApi ?? "").trim();
|
|
273
|
-
if (!pathOrUrl) return "";
|
|
274
|
-
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
275
|
-
return pathOrUrl;
|
|
276
|
-
}
|
|
277
|
-
const normalizedPath = pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`;
|
|
278
|
-
const origin = String(locationLike?.origin ?? "").trim();
|
|
279
|
-
return origin ? `${origin}${normalizedPath}` : normalizedPath;
|
|
280
|
-
}
|
|
281
|
-
async function resolveCatalogRequestConfig({
|
|
282
|
-
suggestionsApi,
|
|
283
|
-
storeIdOption,
|
|
284
|
-
locationLike,
|
|
285
|
-
country,
|
|
286
|
-
getMarketConfig
|
|
287
|
-
}) {
|
|
288
|
-
const explicitStoreId = storeIdOption !== void 0 && storeIdOption !== null && String(storeIdOption).trim() !== "" ? String(storeIdOption).trim() : "";
|
|
289
|
-
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
290
|
-
const configuredApiBase = resolveCatalogApiBaseUrl(marketConfig);
|
|
291
|
-
const configuredStoreId = resolveCatalogStoreId(marketConfig);
|
|
292
|
-
const explicitSuggestionsApi = String(suggestionsApi ?? "").trim();
|
|
293
|
-
return {
|
|
294
|
-
suggestionsUrl: explicitSuggestionsApi ? resolveSuggestionsUrl(explicitSuggestionsApi, locationLike) : buildCatalogSuggestionsUrl(configuredApiBase),
|
|
295
|
-
storeId: explicitStoreId || configuredStoreId
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
function toCatalogLocale(localeParts) {
|
|
299
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
300
|
-
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
301
|
-
return `${language}_${country}`;
|
|
302
|
-
}
|
|
348
|
+
|
|
349
|
+
// src/services/searchPaths.ts
|
|
303
350
|
function buildSearchResultsPath(query, localeParts) {
|
|
304
351
|
const trimmedQuery = String(query ?? "").trim();
|
|
305
352
|
const hyphenatedQuery = trimmedQuery.replace(/\s+/g, "-");
|
|
@@ -369,50 +416,52 @@ function resolveOptionHref(option, localeParts) {
|
|
|
369
416
|
}
|
|
370
417
|
return "";
|
|
371
418
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
419
|
+
|
|
420
|
+
// src/services/suggestionsPayload.ts
|
|
421
|
+
function emptySuggestions() {
|
|
422
|
+
return { suggestions: [], didYouMean: [], totalResultsCount: 0 };
|
|
375
423
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
try {
|
|
380
|
-
const body = await response.json();
|
|
381
|
-
return body?.code === NO_RESULTS_ERROR_CODE;
|
|
382
|
-
} catch {
|
|
383
|
-
return false;
|
|
424
|
+
function resolveTotalResultsCount(payload, fallback) {
|
|
425
|
+
if (typeof payload?.totalResultsCount === "number") {
|
|
426
|
+
return payload.totalResultsCount;
|
|
384
427
|
}
|
|
428
|
+
const pageableTotal = Number(
|
|
429
|
+
payload?.pageableInfo?.totalCount ?? Number.NaN
|
|
430
|
+
);
|
|
431
|
+
return Number.isNaN(pageableTotal) ? fallback : pageableTotal;
|
|
385
432
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
433
|
+
function normalizeSuggestionsPayload(payload) {
|
|
434
|
+
const suggestions = Array.isArray(payload?.suggestions) ? payload.suggestions : [];
|
|
435
|
+
const products = Array.isArray(payload?.product) ? payload.product : null;
|
|
436
|
+
const rawDidYouMean = payload?.didYouMean ?? payload?.did_you_mean;
|
|
437
|
+
const didYouMean = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
|
|
438
|
+
return {
|
|
439
|
+
...payload ?? {},
|
|
440
|
+
suggestions,
|
|
441
|
+
didYouMean,
|
|
442
|
+
totalResultsCount: resolveTotalResultsCount(
|
|
443
|
+
payload,
|
|
444
|
+
products?.length ?? suggestions.length
|
|
445
|
+
)
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/services/SearchApiService.ts
|
|
450
|
+
function getBrowserLocation(providedLocation) {
|
|
451
|
+
if (providedLocation) return providedLocation;
|
|
452
|
+
if (typeof window !== "undefined" && window.location)
|
|
453
|
+
return window.location;
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
function resolveFetch(fetchImplOption) {
|
|
457
|
+
if (fetchImplOption !== void 0) return fetchImplOption;
|
|
458
|
+
return typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
408
459
|
}
|
|
409
460
|
function createSearchService(options = {}) {
|
|
410
461
|
const {
|
|
411
462
|
fetchImpl: fetchImplOption,
|
|
412
463
|
location: providedLocation,
|
|
413
464
|
storage: providedStorage,
|
|
414
|
-
suggestionsApi,
|
|
415
|
-
storeId: storeIdOption,
|
|
416
465
|
country,
|
|
417
466
|
language,
|
|
418
467
|
locale,
|
|
@@ -420,7 +469,7 @@ function createSearchService(options = {}) {
|
|
|
420
469
|
getCustomerType,
|
|
421
470
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
|
|
422
471
|
} = options;
|
|
423
|
-
const fetchImpl = fetchImplOption
|
|
472
|
+
const fetchImpl = resolveFetch(fetchImplOption);
|
|
424
473
|
const locationLike = getBrowserLocation(providedLocation);
|
|
425
474
|
const storage = getSessionStorage(providedStorage);
|
|
426
475
|
const localeParts = normalizeLocaleParts({ country, language, locale });
|
|
@@ -428,8 +477,6 @@ function createSearchService(options = {}) {
|
|
|
428
477
|
function loadCatalogRequestConfig() {
|
|
429
478
|
if (!catalogRequestConfigPromise) {
|
|
430
479
|
catalogRequestConfigPromise = resolveCatalogRequestConfig({
|
|
431
|
-
suggestionsApi,
|
|
432
|
-
storeIdOption,
|
|
433
480
|
locationLike,
|
|
434
481
|
country: localeParts.country,
|
|
435
482
|
getMarketConfig
|
|
@@ -448,47 +495,21 @@ function createSearchService(options = {}) {
|
|
|
448
495
|
async fetchSuggestions(searchTerm) {
|
|
449
496
|
const trimmedSearchTerm = String(searchTerm ?? "").trim();
|
|
450
497
|
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
|
|
451
|
-
return
|
|
452
|
-
suggestions: [],
|
|
453
|
-
didYouMean: [],
|
|
454
|
-
totalResultsCount: 0
|
|
455
|
-
};
|
|
498
|
+
return emptySuggestions();
|
|
456
499
|
}
|
|
457
500
|
const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
|
|
458
|
-
if (!suggestionsUrl || !storeId)
|
|
459
|
-
|
|
460
|
-
suggestions: [],
|
|
461
|
-
didYouMean: [],
|
|
462
|
-
totalResultsCount: 0
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
const queryParams = new URLSearchParams({
|
|
466
|
-
searchTerm: trimmedSearchTerm,
|
|
467
|
-
page: String(DEFAULT_SUGGESTION_PAGE),
|
|
468
|
-
size: String(DEFAULT_SUGGESTION_SIZE),
|
|
469
|
-
locale: toCatalogLocale(localeParts),
|
|
470
|
-
storeId: String(storeId)
|
|
471
|
-
});
|
|
472
|
-
const requestUrls = [`${suggestionsUrl}?${queryParams.toString()}`];
|
|
473
|
-
const responsePayload = await fetchJsonFromCandidates(
|
|
501
|
+
if (!suggestionsUrl || !storeId) return emptySuggestions();
|
|
502
|
+
const responsePayload = await fetchJson(
|
|
474
503
|
fetchImpl,
|
|
475
|
-
|
|
504
|
+
buildSuggestionsRequestUrl({
|
|
505
|
+
suggestionsUrl,
|
|
506
|
+
searchTerm: trimmedSearchTerm,
|
|
507
|
+
storeId,
|
|
508
|
+
localeParts
|
|
509
|
+
}),
|
|
476
510
|
resolveCustomerTypeHeaders(getCustomerType)
|
|
477
511
|
);
|
|
478
|
-
|
|
479
|
-
const productField = Array.isArray(responsePayload?.product) ? responsePayload?.product : null;
|
|
480
|
-
const rawDidYouMean = responsePayload?.didYouMean ?? responsePayload?.did_you_mean;
|
|
481
|
-
const didYouMeanField = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
|
|
482
|
-
const pageableTotal = Number(
|
|
483
|
-
responsePayload?.pageableInfo?.totalCount ?? Number.NaN
|
|
484
|
-
);
|
|
485
|
-
const totalResultsCount = typeof responsePayload?.totalResultsCount === "number" ? responsePayload.totalResultsCount : !Number.isNaN(pageableTotal) ? pageableTotal : productField?.length ?? suggestionsField.length;
|
|
486
|
-
return {
|
|
487
|
-
...responsePayload ?? {},
|
|
488
|
-
suggestions: suggestionsField,
|
|
489
|
-
didYouMean: didYouMeanField,
|
|
490
|
-
totalResultsCount
|
|
491
|
-
};
|
|
512
|
+
return normalizeSuggestionsPayload(responsePayload);
|
|
492
513
|
},
|
|
493
514
|
submitProductSearch(query, option) {
|
|
494
515
|
const trimmedQuery = String(query ?? "").trim();
|
|
@@ -573,7 +594,90 @@ function getProductEnvironment() {
|
|
|
573
594
|
}
|
|
574
595
|
}
|
|
575
596
|
|
|
576
|
-
// src/utils/
|
|
597
|
+
// src/utils/nuiSearchGate.ts
|
|
598
|
+
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
599
|
+
function parseFlagValue(raw) {
|
|
600
|
+
const value = raw?.trim().toLowerCase();
|
|
601
|
+
if (value === "true" || value === "1") return true;
|
|
602
|
+
if (value === "false" || value === "0") return false;
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
function readCookieFlag(cookie) {
|
|
606
|
+
for (const part of cookie.split(";")) {
|
|
607
|
+
const separator = part.indexOf("=");
|
|
608
|
+
if (separator < 0) continue;
|
|
609
|
+
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
610
|
+
try {
|
|
611
|
+
return parseFlagValue(
|
|
612
|
+
decodeURIComponent(part.slice(separator + 1))
|
|
613
|
+
);
|
|
614
|
+
} catch {
|
|
615
|
+
return void 0;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return void 0;
|
|
619
|
+
}
|
|
620
|
+
function resolveNuiSearchFlag(env = {}) {
|
|
621
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
622
|
+
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
623
|
+
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
624
|
+
return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
|
|
625
|
+
}
|
|
626
|
+
function isNuiSearchEnabled({
|
|
627
|
+
isAuthenticated,
|
|
628
|
+
flag,
|
|
629
|
+
allowGuests = false
|
|
630
|
+
}) {
|
|
631
|
+
return flag === true && (isAuthenticated === true || allowGuests);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/hooks/useNuiAgentGate.ts
|
|
635
|
+
function useNuiAgentGate({
|
|
636
|
+
hasAgent,
|
|
637
|
+
isOpen,
|
|
638
|
+
isAuthenticated,
|
|
639
|
+
allowGuestAgent
|
|
640
|
+
}) {
|
|
641
|
+
const [flag, setFlag] = react.useState(void 0);
|
|
642
|
+
react.useEffect(() => {
|
|
643
|
+
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
644
|
+
}, [isOpen]);
|
|
645
|
+
return hasAgent && isNuiSearchEnabled({
|
|
646
|
+
isAuthenticated,
|
|
647
|
+
flag,
|
|
648
|
+
allowGuests: allowGuestAgent
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
function uniqueRecentSearches(queries) {
|
|
652
|
+
const seen = /* @__PURE__ */ new Set();
|
|
653
|
+
return queries.filter((query) => {
|
|
654
|
+
const key = query.trim().toLowerCase();
|
|
655
|
+
if (!key || seen.has(key)) return false;
|
|
656
|
+
seen.add(key);
|
|
657
|
+
return true;
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
function useRecentSearches({
|
|
661
|
+
service,
|
|
662
|
+
isOpen,
|
|
663
|
+
refreshKey
|
|
664
|
+
}) {
|
|
665
|
+
const [recentSearches, setRecentSearches] = react.useState([]);
|
|
666
|
+
react.useEffect(() => {
|
|
667
|
+
if (isOpen) {
|
|
668
|
+
setRecentSearches(
|
|
669
|
+
uniqueRecentSearches(service.getRecentSearches())
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
}, [isOpen, service, refreshKey]);
|
|
673
|
+
const clearRecentSearches = react.useCallback(() => {
|
|
674
|
+
service.clearRecentSearches();
|
|
675
|
+
setRecentSearches([]);
|
|
676
|
+
}, [service]);
|
|
677
|
+
return { recentSearches, clearRecentSearches };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// src/utils/catalogStatus.ts
|
|
577
681
|
var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
|
|
578
682
|
"discontinued",
|
|
579
683
|
"preview",
|
|
@@ -621,6 +725,8 @@ function isSuggestionDisabled(suggestionOption) {
|
|
|
621
725
|
!suggestionOption || suggestionOption["out_of_stock"] || suggestionOption["outOfStock"]
|
|
622
726
|
);
|
|
623
727
|
}
|
|
728
|
+
|
|
729
|
+
// src/utils/catalogHelpers.ts
|
|
624
730
|
function getImageFromProperty(properties = {}) {
|
|
625
731
|
let image = String(properties["primaryimage"] ?? "").trim() || String(properties["imageURL"] ?? "").trim();
|
|
626
732
|
if (!image) {
|
|
@@ -858,6 +964,46 @@ function normalizeSuggestionsResponse(response) {
|
|
|
858
964
|
didYouMean: normalizeDidYouMean(r.didYouMean)
|
|
859
965
|
};
|
|
860
966
|
}
|
|
967
|
+
function resolveTotalResultsCount2(response, fallback) {
|
|
968
|
+
if (typeof response.totalResultsCount === "number") {
|
|
969
|
+
return response.totalResultsCount;
|
|
970
|
+
}
|
|
971
|
+
if (typeof response.pageableInfo?.totalCount === "number") {
|
|
972
|
+
return response.pageableInfo.totalCount;
|
|
973
|
+
}
|
|
974
|
+
return fallback;
|
|
975
|
+
}
|
|
976
|
+
var FETCH_FAILED = {
|
|
977
|
+
suggestions: [],
|
|
978
|
+
didYouMean: [],
|
|
979
|
+
hasMoreResults: false,
|
|
980
|
+
emptyFetched: true,
|
|
981
|
+
failed: true
|
|
982
|
+
};
|
|
983
|
+
function toSettledResults(response) {
|
|
984
|
+
const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
|
|
985
|
+
if (didYouMean.length > 0) {
|
|
986
|
+
return {
|
|
987
|
+
suggestions: [],
|
|
988
|
+
didYouMean,
|
|
989
|
+
hasMoreResults: false,
|
|
990
|
+
emptyFetched: false,
|
|
991
|
+
failed: false
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
const total = Math.max(
|
|
995
|
+
resolveTotalResultsCount2(response, allSuggestions.length),
|
|
996
|
+
allSuggestions.length
|
|
997
|
+
);
|
|
998
|
+
const suggestions = allSuggestions.slice(0, MAX_VISIBLE_SUGGESTIONS);
|
|
999
|
+
return {
|
|
1000
|
+
suggestions,
|
|
1001
|
+
didYouMean,
|
|
1002
|
+
hasMoreResults: total > MAX_VISIBLE_SUGGESTIONS,
|
|
1003
|
+
emptyFetched: suggestions.length === 0,
|
|
1004
|
+
failed: false
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
861
1007
|
function useSearchSuggestions({
|
|
862
1008
|
service,
|
|
863
1009
|
enabled = true
|
|
@@ -869,7 +1015,6 @@ function useSearchSuggestions({
|
|
|
869
1015
|
const [emptyFetched, setEmptyFetched] = react.useState(false);
|
|
870
1016
|
const [suggestionFetchFailed, setSuggestionFetchFailed] = react.useState(false);
|
|
871
1017
|
const [hasMoreResults, setHasMoreResults] = react.useState(false);
|
|
872
|
-
const [totalResultsCount, setTotalResultsCount] = react.useState(0);
|
|
873
1018
|
const [settledQuery, setSettledQuery] = react.useState("");
|
|
874
1019
|
const fetchDebounced = react.useRef(null);
|
|
875
1020
|
const latestRequestId = react.useRef(0);
|
|
@@ -879,7 +1024,6 @@ function useSearchSuggestions({
|
|
|
879
1024
|
setEmptyFetched(false);
|
|
880
1025
|
setSuggestionFetchFailed(false);
|
|
881
1026
|
setHasMoreResults(false);
|
|
882
|
-
setTotalResultsCount(0);
|
|
883
1027
|
setSettledQuery("");
|
|
884
1028
|
setLoading(false);
|
|
885
1029
|
}, []);
|
|
@@ -897,6 +1041,14 @@ function useSearchSuggestions({
|
|
|
897
1041
|
resetSuggestionsState();
|
|
898
1042
|
setQuery("");
|
|
899
1043
|
}, [resetSuggestionsState]);
|
|
1044
|
+
const settle = react.useCallback((results, term) => {
|
|
1045
|
+
setDidYouMean(results.didYouMean);
|
|
1046
|
+
setSuggestions(results.suggestions);
|
|
1047
|
+
setHasMoreResults(results.hasMoreResults);
|
|
1048
|
+
setSuggestionFetchFailed(results.failed);
|
|
1049
|
+
setSettledQuery(term);
|
|
1050
|
+
setEmptyFetched(results.emptyFetched);
|
|
1051
|
+
}, []);
|
|
900
1052
|
const runFetch = react.useCallback(
|
|
901
1053
|
async (nextQuery) => {
|
|
902
1054
|
const trimmedQuery = nextQuery.trim();
|
|
@@ -908,51 +1060,20 @@ function useSearchSuggestions({
|
|
|
908
1060
|
setLoading(true);
|
|
909
1061
|
setEmptyFetched(false);
|
|
910
1062
|
setSuggestionFetchFailed(false);
|
|
1063
|
+
let results;
|
|
911
1064
|
try {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return;
|
|
915
|
-
}
|
|
916
|
-
const {
|
|
917
|
-
suggestions: allSuggestions,
|
|
918
|
-
didYouMean: suggestedTerms
|
|
919
|
-
} = normalizeSuggestionsResponse(fetchedSuggestions);
|
|
920
|
-
const visibleSuggestions = allSuggestions.slice(
|
|
921
|
-
0,
|
|
922
|
-
MAX_VISIBLE_SUGGESTIONS
|
|
1065
|
+
results = toSettledResults(
|
|
1066
|
+
await service.fetchSuggestions(trimmedQuery)
|
|
923
1067
|
);
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
setDidYouMean(suggestedTerms);
|
|
928
|
-
setSuggestions(hasDidYouMean ? [] : visibleSuggestions);
|
|
929
|
-
setHasMoreResults(
|
|
930
|
-
!hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
|
|
931
|
-
);
|
|
932
|
-
setTotalResultsCount(effectiveTotal);
|
|
933
|
-
setSuggestionFetchFailed(false);
|
|
934
|
-
setSettledQuery(trimmedQuery);
|
|
935
|
-
setEmptyFetched(
|
|
936
|
-
hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
|
|
937
|
-
);
|
|
938
|
-
} catch {
|
|
939
|
-
if (requestId !== latestRequestId.current) {
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
setSuggestions([]);
|
|
943
|
-
setDidYouMean([]);
|
|
944
|
-
setEmptyFetched(true);
|
|
945
|
-
setSuggestionFetchFailed(true);
|
|
946
|
-
setHasMoreResults(false);
|
|
947
|
-
setTotalResultsCount(0);
|
|
948
|
-
setSettledQuery(trimmedQuery);
|
|
949
|
-
} finally {
|
|
950
|
-
if (requestId === latestRequestId.current) {
|
|
951
|
-
setLoading(false);
|
|
952
|
-
}
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
console.warn("[search] Could not load suggestions:", error);
|
|
1070
|
+
results = FETCH_FAILED;
|
|
953
1071
|
}
|
|
1072
|
+
if (requestId !== latestRequestId.current) return;
|
|
1073
|
+
settle(results, trimmedQuery);
|
|
1074
|
+
setLoading(false);
|
|
954
1075
|
},
|
|
955
|
-
[enabled, service, resetSuggestionsState]
|
|
1076
|
+
[enabled, service, resetSuggestionsState, settle]
|
|
956
1077
|
);
|
|
957
1078
|
react.useEffect(() => {
|
|
958
1079
|
if (!enabled) {
|
|
@@ -976,15 +1097,12 @@ function useSearchSuggestions({
|
|
|
976
1097
|
clearQuery,
|
|
977
1098
|
emptyFetched,
|
|
978
1099
|
hasMoreResults,
|
|
979
|
-
hasQuery: Boolean(query.trim()),
|
|
980
1100
|
didYouMean,
|
|
981
1101
|
loading,
|
|
982
1102
|
query,
|
|
983
1103
|
settledQuery,
|
|
984
|
-
setQuery,
|
|
985
1104
|
suggestionFetchFailed,
|
|
986
1105
|
suggestions,
|
|
987
|
-
totalResultsCount,
|
|
988
1106
|
updateQuery
|
|
989
1107
|
};
|
|
990
1108
|
}
|
|
@@ -1059,43 +1177,6 @@ function trackProductSearch(input) {
|
|
|
1059
1177
|
});
|
|
1060
1178
|
}
|
|
1061
1179
|
|
|
1062
|
-
// src/utils/nuiSearchGate.ts
|
|
1063
|
-
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
1064
|
-
function parseFlagValue(raw) {
|
|
1065
|
-
const value = raw?.trim().toLowerCase();
|
|
1066
|
-
if (value === "true" || value === "1") return true;
|
|
1067
|
-
if (value === "false" || value === "0") return false;
|
|
1068
|
-
return void 0;
|
|
1069
|
-
}
|
|
1070
|
-
function readCookieFlag(cookie) {
|
|
1071
|
-
for (const part of cookie.split(";")) {
|
|
1072
|
-
const separator = part.indexOf("=");
|
|
1073
|
-
if (separator < 0) continue;
|
|
1074
|
-
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
1075
|
-
try {
|
|
1076
|
-
return parseFlagValue(
|
|
1077
|
-
decodeURIComponent(part.slice(separator + 1))
|
|
1078
|
-
);
|
|
1079
|
-
} catch {
|
|
1080
|
-
return void 0;
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
return void 0;
|
|
1084
|
-
}
|
|
1085
|
-
function resolveNuiSearchFlag(env = {}) {
|
|
1086
|
-
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
1087
|
-
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
1088
|
-
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
1089
|
-
return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
|
|
1090
|
-
}
|
|
1091
|
-
function isNuiSearchEnabled({
|
|
1092
|
-
isAuthenticated,
|
|
1093
|
-
flag,
|
|
1094
|
-
allowGuests = false
|
|
1095
|
-
}) {
|
|
1096
|
-
return flag === true && (isAuthenticated === true || allowGuests);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
1180
|
// src/utils/searchRouting.ts
|
|
1100
1181
|
function decideSearchRoute({
|
|
1101
1182
|
enabled,
|
|
@@ -1108,6 +1189,96 @@ function decideSearchRoute({
|
|
|
1108
1189
|
return snapshot.suggestions.length > 0 ? "search" : "handoff";
|
|
1109
1190
|
}
|
|
1110
1191
|
|
|
1192
|
+
// src/hooks/useSubmitRouting.ts
|
|
1193
|
+
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1194
|
+
function useSubmitRouting({
|
|
1195
|
+
enabled,
|
|
1196
|
+
minimumSearchLength,
|
|
1197
|
+
search,
|
|
1198
|
+
gtmContext,
|
|
1199
|
+
pendingTerm,
|
|
1200
|
+
setPendingTerm,
|
|
1201
|
+
onSearch,
|
|
1202
|
+
onHandoff
|
|
1203
|
+
}) {
|
|
1204
|
+
const {
|
|
1205
|
+
didYouMean,
|
|
1206
|
+
loading,
|
|
1207
|
+
settledQuery,
|
|
1208
|
+
suggestionFetchFailed,
|
|
1209
|
+
suggestions
|
|
1210
|
+
} = search;
|
|
1211
|
+
const decide = react.useCallback(
|
|
1212
|
+
(term) => decideSearchRoute({
|
|
1213
|
+
enabled,
|
|
1214
|
+
snapshot: {
|
|
1215
|
+
query: term,
|
|
1216
|
+
loading,
|
|
1217
|
+
settledQuery,
|
|
1218
|
+
suggestions,
|
|
1219
|
+
suggestionFetchFailed
|
|
1220
|
+
}
|
|
1221
|
+
}),
|
|
1222
|
+
[enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
|
|
1223
|
+
);
|
|
1224
|
+
const act = react.useCallback(
|
|
1225
|
+
(term, route) => {
|
|
1226
|
+
setPendingTerm(null);
|
|
1227
|
+
if (route === "handoff") {
|
|
1228
|
+
trackChatEntryPoint(gtmContext ?? {});
|
|
1229
|
+
onHandoff({
|
|
1230
|
+
query: term,
|
|
1231
|
+
didYouMean: didYouMean.map((item) => item.value)
|
|
1232
|
+
});
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
onSearch(term);
|
|
1236
|
+
},
|
|
1237
|
+
[didYouMean, gtmContext, onHandoff, onSearch, setPendingTerm]
|
|
1238
|
+
);
|
|
1239
|
+
const submit = react.useCallback(
|
|
1240
|
+
(value) => {
|
|
1241
|
+
const term = value.trim();
|
|
1242
|
+
if (!term) return;
|
|
1243
|
+
if (term.length < minimumSearchLength) {
|
|
1244
|
+
onSearch(term);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
const route = decide(term);
|
|
1248
|
+
if (route === "wait") {
|
|
1249
|
+
setPendingTerm(term);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
act(term, route);
|
|
1253
|
+
},
|
|
1254
|
+
[act, decide, minimumSearchLength, onSearch, setPendingTerm]
|
|
1255
|
+
);
|
|
1256
|
+
const latest = react.useRef({ act, decide });
|
|
1257
|
+
react.useEffect(() => {
|
|
1258
|
+
latest.current = { act, decide };
|
|
1259
|
+
});
|
|
1260
|
+
react.useEffect(() => {
|
|
1261
|
+
if (pendingTerm === null) return void 0;
|
|
1262
|
+
const route = latest.current.decide(pendingTerm);
|
|
1263
|
+
if (route !== "wait") {
|
|
1264
|
+
latest.current.act(pendingTerm, route);
|
|
1265
|
+
return void 0;
|
|
1266
|
+
}
|
|
1267
|
+
const timer = setTimeout(
|
|
1268
|
+
() => latest.current.act(pendingTerm, "search"),
|
|
1269
|
+
SUBMIT_WAIT_TIMEOUT_MS
|
|
1270
|
+
);
|
|
1271
|
+
return () => clearTimeout(timer);
|
|
1272
|
+
}, [
|
|
1273
|
+
pendingTerm,
|
|
1274
|
+
loading,
|
|
1275
|
+
settledQuery,
|
|
1276
|
+
suggestions,
|
|
1277
|
+
suggestionFetchFailed
|
|
1278
|
+
]);
|
|
1279
|
+
return submit;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1111
1282
|
// src/utils/cachedProductClient.ts
|
|
1112
1283
|
function withProductCache(client) {
|
|
1113
1284
|
const lookups = /* @__PURE__ */ new Map();
|
|
@@ -1175,7 +1346,8 @@ var AgentErrorBoundary = class extends react.Component {
|
|
|
1175
1346
|
static getDerivedStateFromError() {
|
|
1176
1347
|
return { failed: true };
|
|
1177
1348
|
}
|
|
1178
|
-
componentDidCatch(
|
|
1349
|
+
componentDidCatch(error) {
|
|
1350
|
+
console.warn("[search] The agent could not be shown:", error);
|
|
1179
1351
|
this.props.onError();
|
|
1180
1352
|
}
|
|
1181
1353
|
render() {
|
|
@@ -1188,6 +1360,10 @@ function useHandoff({
|
|
|
1188
1360
|
agentContext,
|
|
1189
1361
|
onUnavailable
|
|
1190
1362
|
}) {
|
|
1363
|
+
const onUnavailableRef = react.useRef(onUnavailable);
|
|
1364
|
+
react.useEffect(() => {
|
|
1365
|
+
onUnavailableRef.current = onUnavailable;
|
|
1366
|
+
});
|
|
1191
1367
|
react.useEffect(() => {
|
|
1192
1368
|
let cancelled = false;
|
|
1193
1369
|
let timer;
|
|
@@ -1203,7 +1379,10 @@ function useHandoff({
|
|
|
1203
1379
|
return;
|
|
1204
1380
|
}
|
|
1205
1381
|
if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
|
|
1206
|
-
|
|
1382
|
+
console.warn(
|
|
1383
|
+
`[search] The agent did not start within ${HANDLE_TIMEOUT_MS / 1e3} seconds.`
|
|
1384
|
+
);
|
|
1385
|
+
onUnavailableRef.current();
|
|
1207
1386
|
return;
|
|
1208
1387
|
}
|
|
1209
1388
|
timer = setTimeout(tryOpen, HANDLE_POLL_MS);
|
|
@@ -1245,16 +1424,6 @@ function NuiAgentPanel({
|
|
|
1245
1424
|
}
|
|
1246
1425
|
) });
|
|
1247
1426
|
}
|
|
1248
|
-
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1249
|
-
function uniqueRecentSearches(queries) {
|
|
1250
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1251
|
-
return queries.filter((query) => {
|
|
1252
|
-
const key = query.trim().toLowerCase();
|
|
1253
|
-
if (!key || seen.has(key)) return false;
|
|
1254
|
-
seen.add(key);
|
|
1255
|
-
return true;
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
1427
|
function CommerceSearchView({
|
|
1259
1428
|
copy,
|
|
1260
1429
|
isOpen,
|
|
@@ -1269,11 +1438,9 @@ function CommerceSearchView({
|
|
|
1269
1438
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
|
|
1270
1439
|
gtmContext
|
|
1271
1440
|
}) {
|
|
1272
|
-
const [flag, setFlag] = react.useState(void 0);
|
|
1273
1441
|
const [handoff, setHandoff] = react.useState(null);
|
|
1274
1442
|
const agentQuery = handoff?.query ?? null;
|
|
1275
1443
|
const [pendingTerm, setPendingTerm] = react.useState(null);
|
|
1276
|
-
const [recentSearches, setRecentSearches] = react.useState([]);
|
|
1277
1444
|
const search = useSearchSuggestions({
|
|
1278
1445
|
service,
|
|
1279
1446
|
enabled: isOpen && agentQuery === null
|
|
@@ -1285,31 +1452,25 @@ function CommerceSearchView({
|
|
|
1285
1452
|
hasMoreResults,
|
|
1286
1453
|
loading,
|
|
1287
1454
|
query,
|
|
1288
|
-
settledQuery,
|
|
1289
|
-
suggestionFetchFailed,
|
|
1290
1455
|
suggestions,
|
|
1291
1456
|
updateQuery
|
|
1292
1457
|
} = search;
|
|
1293
|
-
const
|
|
1458
|
+
const agentEnabled = useNuiAgentGate({
|
|
1459
|
+
hasAgent: agent !== void 0,
|
|
1460
|
+
isOpen,
|
|
1294
1461
|
isAuthenticated,
|
|
1295
|
-
|
|
1296
|
-
|
|
1462
|
+
allowGuestAgent
|
|
1463
|
+
});
|
|
1464
|
+
const { recentSearches, clearRecentSearches } = useRecentSearches({
|
|
1465
|
+
service,
|
|
1466
|
+
isOpen,
|
|
1467
|
+
refreshKey: agentQuery
|
|
1297
1468
|
});
|
|
1298
1469
|
const items = getVisibleSuggestions({
|
|
1299
1470
|
didYouMean,
|
|
1300
1471
|
suggestions
|
|
1301
1472
|
});
|
|
1302
1473
|
const showDidYouMean = didYouMean.length > 0;
|
|
1303
|
-
react.useEffect(() => {
|
|
1304
|
-
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
1305
|
-
}, [isOpen]);
|
|
1306
|
-
react.useEffect(() => {
|
|
1307
|
-
if (isOpen) {
|
|
1308
|
-
setRecentSearches(
|
|
1309
|
-
uniqueRecentSearches(service.getRecentSearches())
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
}, [isOpen, service, agentQuery]);
|
|
1313
1474
|
const close = react.useCallback(() => {
|
|
1314
1475
|
setHandoff(null);
|
|
1315
1476
|
setPendingTerm(null);
|
|
@@ -1348,72 +1509,16 @@ function CommerceSearchView({
|
|
|
1348
1509
|
},
|
|
1349
1510
|
[items, navigateTo, service, trackSemantic]
|
|
1350
1511
|
);
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
loading,
|
|
1357
|
-
settledQuery,
|
|
1358
|
-
suggestions,
|
|
1359
|
-
suggestionFetchFailed
|
|
1360
|
-
}
|
|
1361
|
-
}),
|
|
1362
|
-
[enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
|
|
1363
|
-
);
|
|
1364
|
-
const act = react.useCallback(
|
|
1365
|
-
(term, route) => {
|
|
1366
|
-
setPendingTerm(null);
|
|
1367
|
-
if (route === "handoff") {
|
|
1368
|
-
trackChatEntryPoint(gtmContext ?? {});
|
|
1369
|
-
setHandoff({
|
|
1370
|
-
query: term,
|
|
1371
|
-
didYouMean: didYouMean.map((item) => item.value)
|
|
1372
|
-
});
|
|
1373
|
-
return;
|
|
1374
|
-
}
|
|
1375
|
-
runSearch(term);
|
|
1376
|
-
},
|
|
1377
|
-
[didYouMean, gtmContext, runSearch]
|
|
1378
|
-
);
|
|
1379
|
-
const handleSubmit = react.useCallback(
|
|
1380
|
-
(value) => {
|
|
1381
|
-
const term = value.trim();
|
|
1382
|
-
if (!term) return;
|
|
1383
|
-
if (term.length < minimumSearchLength) {
|
|
1384
|
-
runSearch(term);
|
|
1385
|
-
return;
|
|
1386
|
-
}
|
|
1387
|
-
const route = decide(term);
|
|
1388
|
-
if (route === "wait") {
|
|
1389
|
-
setPendingTerm(term);
|
|
1390
|
-
return;
|
|
1391
|
-
}
|
|
1392
|
-
act(term, route);
|
|
1393
|
-
},
|
|
1394
|
-
[act, decide, minimumSearchLength, runSearch]
|
|
1395
|
-
);
|
|
1396
|
-
const latest = react.useRef({ act, decide });
|
|
1397
|
-
latest.current = { act, decide };
|
|
1398
|
-
react.useEffect(() => {
|
|
1399
|
-
if (pendingTerm === null) return void 0;
|
|
1400
|
-
const route = latest.current.decide(pendingTerm);
|
|
1401
|
-
if (route !== "wait") {
|
|
1402
|
-
latest.current.act(pendingTerm, route);
|
|
1403
|
-
return void 0;
|
|
1404
|
-
}
|
|
1405
|
-
const timer = setTimeout(
|
|
1406
|
-
() => latest.current.act(pendingTerm, "search"),
|
|
1407
|
-
SUBMIT_WAIT_TIMEOUT_MS
|
|
1408
|
-
);
|
|
1409
|
-
return () => clearTimeout(timer);
|
|
1410
|
-
}, [
|
|
1512
|
+
const handleSubmit = useSubmitRouting({
|
|
1513
|
+
enabled: agentEnabled,
|
|
1514
|
+
minimumSearchLength,
|
|
1515
|
+
search,
|
|
1516
|
+
...gtmContext ? { gtmContext } : {},
|
|
1411
1517
|
pendingTerm,
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
]);
|
|
1518
|
+
setPendingTerm,
|
|
1519
|
+
onSearch: runSearch,
|
|
1520
|
+
onHandoff: setHandoff
|
|
1521
|
+
});
|
|
1417
1522
|
const handleSelect = react.useCallback(
|
|
1418
1523
|
(option) => {
|
|
1419
1524
|
if (isSuggestionDisabled(
|
|
@@ -1438,10 +1543,11 @@ function CommerceSearchView({
|
|
|
1438
1543
|
)) {
|
|
1439
1544
|
return;
|
|
1440
1545
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1546
|
+
navigateTo(
|
|
1547
|
+
service.activateSuggestionAction(
|
|
1548
|
+
option
|
|
1549
|
+
)
|
|
1443
1550
|
);
|
|
1444
|
-
void Promise.resolve(result).then(navigateTo).catch(() => void 0);
|
|
1445
1551
|
},
|
|
1446
1552
|
[navigateTo, service]
|
|
1447
1553
|
);
|
|
@@ -1456,10 +1562,6 @@ function CommerceSearchView({
|
|
|
1456
1562
|
},
|
|
1457
1563
|
[navigateTo, service, trackSemantic]
|
|
1458
1564
|
);
|
|
1459
|
-
const handleClearRecent = react.useCallback(() => {
|
|
1460
|
-
service.clearRecentSearches();
|
|
1461
|
-
setRecentSearches([]);
|
|
1462
|
-
}, [service]);
|
|
1463
1565
|
const handleAgentUnavailable = react.useCallback(() => {
|
|
1464
1566
|
if (agentQuery !== null)
|
|
1465
1567
|
navigateTo(service.submitProductSearch(agentQuery));
|
|
@@ -1508,7 +1610,7 @@ function CommerceSearchView({
|
|
|
1508
1610
|
recentSearches: {
|
|
1509
1611
|
items: recentSearches,
|
|
1510
1612
|
onSelect: handleRecentSelect,
|
|
1511
|
-
onClear:
|
|
1613
|
+
onClear: clearRecentSearches
|
|
1512
1614
|
},
|
|
1513
1615
|
agentPanel
|
|
1514
1616
|
}
|
|
@@ -1534,8 +1636,11 @@ function CommerceSearch({
|
|
|
1534
1636
|
language,
|
|
1535
1637
|
enabled: hasOpened
|
|
1536
1638
|
});
|
|
1537
|
-
const
|
|
1538
|
-
customerTypeRef
|
|
1639
|
+
const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
|
|
1640
|
+
const customerTypeRef = react.useRef(customerType);
|
|
1641
|
+
react.useEffect(() => {
|
|
1642
|
+
customerTypeRef.current = customerType;
|
|
1643
|
+
}, [customerType]);
|
|
1539
1644
|
const service = react.useMemo(
|
|
1540
1645
|
() => createSearchService({
|
|
1541
1646
|
country,
|