@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.mjs
CHANGED
|
@@ -120,21 +120,41 @@ async function fetchSearchPanelCopy(_options = {}) {
|
|
|
120
120
|
|
|
121
121
|
// src/copy/loadSearchPanelCopy.ts
|
|
122
122
|
var copyByLanguage = /* @__PURE__ */ new Map();
|
|
123
|
+
var loadedByLanguage = /* @__PURE__ */ new Map();
|
|
124
|
+
function cacheKey(language) {
|
|
125
|
+
return (language ?? "").toLowerCase();
|
|
126
|
+
}
|
|
123
127
|
function loadSearchPanelCopy(language) {
|
|
124
|
-
const key = (language
|
|
128
|
+
const key = cacheKey(language);
|
|
125
129
|
const cached = copyByLanguage.get(key);
|
|
126
130
|
if (cached) return cached;
|
|
127
131
|
const pending = fetchSearchPanelCopy(language ? { } : {});
|
|
128
132
|
copyByLanguage.set(key, pending);
|
|
129
|
-
pending.
|
|
133
|
+
pending.then(
|
|
134
|
+
(copy) => {
|
|
135
|
+
if (copyByLanguage.get(key) === pending) {
|
|
136
|
+
loadedByLanguage.set(key, copy);
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
() => {
|
|
140
|
+
if (copyByLanguage.get(key) === pending) {
|
|
141
|
+
copyByLanguage.delete(key);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
130
145
|
return pending;
|
|
131
146
|
}
|
|
147
|
+
function peekSearchPanelCopy(language) {
|
|
148
|
+
return loadedByLanguage.get(cacheKey(language));
|
|
149
|
+
}
|
|
132
150
|
|
|
133
151
|
// src/hooks/useSearchPanelCopy.ts
|
|
134
152
|
function useSearchPanelCopy({
|
|
135
153
|
language
|
|
136
154
|
}) {
|
|
137
|
-
const [copy, setCopy] = useState(
|
|
155
|
+
const [copy, setCopy] = useState(
|
|
156
|
+
() => peekSearchPanelCopy(language)
|
|
157
|
+
);
|
|
138
158
|
useEffect(() => {
|
|
139
159
|
let isStale = false;
|
|
140
160
|
loadSearchPanelCopy(language).then((next) => {
|
|
@@ -152,21 +172,7 @@ function useSearchPanelCopy({
|
|
|
152
172
|
return copy;
|
|
153
173
|
}
|
|
154
174
|
|
|
155
|
-
// src/services/
|
|
156
|
-
function canUseStorage(storage) {
|
|
157
|
-
return Boolean(storage) && typeof storage.getItem === "function";
|
|
158
|
-
}
|
|
159
|
-
function getSessionStorage(providedStorage) {
|
|
160
|
-
if (providedStorage) return providedStorage;
|
|
161
|
-
if (typeof window !== "undefined") {
|
|
162
|
-
try {
|
|
163
|
-
return window.sessionStorage;
|
|
164
|
-
} catch {
|
|
165
|
-
return null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
return null;
|
|
169
|
-
}
|
|
175
|
+
// src/services/localeParts.ts
|
|
170
176
|
function normalizeLocaleParts({
|
|
171
177
|
country,
|
|
172
178
|
language,
|
|
@@ -181,6 +187,102 @@ function normalizeLocaleParts({
|
|
|
181
187
|
locale: normalizedLocale
|
|
182
188
|
};
|
|
183
189
|
}
|
|
190
|
+
function toCatalogLocale(localeParts) {
|
|
191
|
+
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
192
|
+
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
193
|
+
return `${language}_${country}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/services/catalogRequest.ts
|
|
197
|
+
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
198
|
+
const apiGateway = String(apiGatewayPath ?? "").trim();
|
|
199
|
+
if (!apiGateway) return "";
|
|
200
|
+
return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
|
|
201
|
+
}
|
|
202
|
+
function resolveCatalogApiBaseUrl(marketConfig) {
|
|
203
|
+
return resolveApiGatewayPath(marketConfig?.api_gateway_path);
|
|
204
|
+
}
|
|
205
|
+
function resolveCatalogStoreId(marketConfig) {
|
|
206
|
+
return String(marketConfig?.store_id ?? "").trim();
|
|
207
|
+
}
|
|
208
|
+
function buildCatalogSuggestionsUrl(apiBaseUrl) {
|
|
209
|
+
const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
|
|
210
|
+
if (!normalizedBase) return "";
|
|
211
|
+
const suggestionsPath = normalizedBase.endsWith(
|
|
212
|
+
`/${STOREFRONT_SHOPPING_CONTEXT}`
|
|
213
|
+
) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
|
|
214
|
+
return `${normalizedBase}${suggestionsPath}`;
|
|
215
|
+
}
|
|
216
|
+
function buildSuggestionsRequestUrl({
|
|
217
|
+
suggestionsUrl,
|
|
218
|
+
searchTerm,
|
|
219
|
+
storeId,
|
|
220
|
+
localeParts
|
|
221
|
+
}) {
|
|
222
|
+
const queryParams = new URLSearchParams({
|
|
223
|
+
searchTerm,
|
|
224
|
+
page: String(DEFAULT_SUGGESTION_PAGE),
|
|
225
|
+
size: String(DEFAULT_SUGGESTION_SIZE),
|
|
226
|
+
locale: toCatalogLocale(localeParts),
|
|
227
|
+
storeId
|
|
228
|
+
});
|
|
229
|
+
return `${suggestionsUrl}?${queryParams.toString()}`;
|
|
230
|
+
}
|
|
231
|
+
async function resolveCatalogRequestConfig({
|
|
232
|
+
locationLike,
|
|
233
|
+
country,
|
|
234
|
+
getMarketConfig
|
|
235
|
+
}) {
|
|
236
|
+
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
237
|
+
return {
|
|
238
|
+
suggestionsUrl: buildCatalogSuggestionsUrl(
|
|
239
|
+
resolveCatalogApiBaseUrl(marketConfig)
|
|
240
|
+
),
|
|
241
|
+
storeId: resolveCatalogStoreId(marketConfig)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function resolveCustomerTypeHeaders(getCustomerType) {
|
|
245
|
+
const customerType = getCustomerType?.()?.trim();
|
|
246
|
+
return customerType ? { "X-Customer-Type": customerType } : void 0;
|
|
247
|
+
}
|
|
248
|
+
var NO_RESULTS_ERROR_CODE = "SFOSER002";
|
|
249
|
+
async function isNoResultsResponse(response) {
|
|
250
|
+
if (response.status !== 404) return false;
|
|
251
|
+
try {
|
|
252
|
+
const body = await response.json();
|
|
253
|
+
return body?.code === NO_RESULTS_ERROR_CODE;
|
|
254
|
+
} catch {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function fetchJson(fetchImpl, requestUrl, requestHeaders = {}) {
|
|
259
|
+
const response = await fetchImpl(requestUrl, {
|
|
260
|
+
method: "GET",
|
|
261
|
+
credentials: "include",
|
|
262
|
+
headers: { Accept: "application/json", ...requestHeaders }
|
|
263
|
+
});
|
|
264
|
+
if (!response.ok) {
|
|
265
|
+
if (await isNoResultsResponse(response)) return null;
|
|
266
|
+
throw new Error(`Request failed with status ${response.status}`);
|
|
267
|
+
}
|
|
268
|
+
return response.json();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/services/recentSearches.ts
|
|
272
|
+
function canUseStorage(storage) {
|
|
273
|
+
return Boolean(storage) && typeof storage.getItem === "function";
|
|
274
|
+
}
|
|
275
|
+
function getSessionStorage(providedStorage) {
|
|
276
|
+
if (providedStorage) return providedStorage;
|
|
277
|
+
if (typeof window !== "undefined") {
|
|
278
|
+
try {
|
|
279
|
+
return window.sessionStorage;
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
184
286
|
function getRecentHeaderSearchesStorageKey(localeParts) {
|
|
185
287
|
const { country, language, locale } = normalizeLocaleParts(localeParts);
|
|
186
288
|
return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
|
|
@@ -236,63 +338,8 @@ function clearRecentHeaderSearches(localeParts, storage) {
|
|
|
236
338
|
} catch {
|
|
237
339
|
}
|
|
238
340
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (typeof window !== "undefined" && window.location)
|
|
242
|
-
return window.location;
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
246
|
-
const apiGateway = String(apiGatewayPath ?? "").trim();
|
|
247
|
-
if (!apiGateway) return "";
|
|
248
|
-
return apiGateway.replace(/\{api_name\}/gi, apiName).replace(/\/$/, "");
|
|
249
|
-
}
|
|
250
|
-
function resolveCatalogApiBaseUrl(marketConfig) {
|
|
251
|
-
return resolveApiGatewayPath(marketConfig?.api_gateway_path);
|
|
252
|
-
}
|
|
253
|
-
function resolveCatalogStoreId(marketConfig) {
|
|
254
|
-
return String(marketConfig?.store_id ?? "").trim();
|
|
255
|
-
}
|
|
256
|
-
function buildCatalogSuggestionsUrl(apiBaseUrl) {
|
|
257
|
-
const normalizedBase = String(apiBaseUrl ?? "").trim().replace(/\/$/, "");
|
|
258
|
-
if (!normalizedBase) return "";
|
|
259
|
-
const suggestionsPath = normalizedBase.endsWith(
|
|
260
|
-
`/${STOREFRONT_SHOPPING_CONTEXT}`
|
|
261
|
-
) ? "/catalogs/search/suggestions" : CATALOG_SUGGESTIONS_PATH;
|
|
262
|
-
return `${normalizedBase}${suggestionsPath}`;
|
|
263
|
-
}
|
|
264
|
-
function resolveSuggestionsUrl(suggestionsApi, locationLike) {
|
|
265
|
-
const pathOrUrl = String(suggestionsApi ?? "").trim();
|
|
266
|
-
if (!pathOrUrl) return "";
|
|
267
|
-
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
268
|
-
return pathOrUrl;
|
|
269
|
-
}
|
|
270
|
-
const normalizedPath = pathOrUrl.startsWith("/") ? pathOrUrl : `/${pathOrUrl}`;
|
|
271
|
-
const origin = String(locationLike?.origin ?? "").trim();
|
|
272
|
-
return origin ? `${origin}${normalizedPath}` : normalizedPath;
|
|
273
|
-
}
|
|
274
|
-
async function resolveCatalogRequestConfig({
|
|
275
|
-
suggestionsApi,
|
|
276
|
-
storeIdOption,
|
|
277
|
-
locationLike,
|
|
278
|
-
country,
|
|
279
|
-
getMarketConfig
|
|
280
|
-
}) {
|
|
281
|
-
const explicitStoreId = storeIdOption !== void 0 && storeIdOption !== null && String(storeIdOption).trim() !== "" ? String(storeIdOption).trim() : "";
|
|
282
|
-
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
283
|
-
const configuredApiBase = resolveCatalogApiBaseUrl(marketConfig);
|
|
284
|
-
const configuredStoreId = resolveCatalogStoreId(marketConfig);
|
|
285
|
-
const explicitSuggestionsApi = String(suggestionsApi ?? "").trim();
|
|
286
|
-
return {
|
|
287
|
-
suggestionsUrl: explicitSuggestionsApi ? resolveSuggestionsUrl(explicitSuggestionsApi, locationLike) : buildCatalogSuggestionsUrl(configuredApiBase),
|
|
288
|
-
storeId: explicitStoreId || configuredStoreId
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
function toCatalogLocale(localeParts) {
|
|
292
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
293
|
-
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
294
|
-
return `${language}_${country}`;
|
|
295
|
-
}
|
|
341
|
+
|
|
342
|
+
// src/services/searchPaths.ts
|
|
296
343
|
function buildSearchResultsPath(query, localeParts) {
|
|
297
344
|
const trimmedQuery = String(query ?? "").trim();
|
|
298
345
|
const hyphenatedQuery = trimmedQuery.replace(/\s+/g, "-");
|
|
@@ -362,50 +409,52 @@ function resolveOptionHref(option, localeParts) {
|
|
|
362
409
|
}
|
|
363
410
|
return "";
|
|
364
411
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
412
|
+
|
|
413
|
+
// src/services/suggestionsPayload.ts
|
|
414
|
+
function emptySuggestions() {
|
|
415
|
+
return { suggestions: [], didYouMean: [], totalResultsCount: 0 };
|
|
368
416
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
const body = await response.json();
|
|
374
|
-
return body?.code === NO_RESULTS_ERROR_CODE;
|
|
375
|
-
} catch {
|
|
376
|
-
return false;
|
|
417
|
+
function resolveTotalResultsCount(payload, fallback) {
|
|
418
|
+
if (typeof payload?.totalResultsCount === "number") {
|
|
419
|
+
return payload.totalResultsCount;
|
|
377
420
|
}
|
|
421
|
+
const pageableTotal = Number(
|
|
422
|
+
payload?.pageableInfo?.totalCount ?? Number.NaN
|
|
423
|
+
);
|
|
424
|
+
return Number.isNaN(pageableTotal) ? fallback : pageableTotal;
|
|
378
425
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
426
|
+
function normalizeSuggestionsPayload(payload) {
|
|
427
|
+
const suggestions = Array.isArray(payload?.suggestions) ? payload.suggestions : [];
|
|
428
|
+
const products = Array.isArray(payload?.product) ? payload.product : null;
|
|
429
|
+
const rawDidYouMean = payload?.didYouMean ?? payload?.did_you_mean;
|
|
430
|
+
const didYouMean = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
|
|
431
|
+
return {
|
|
432
|
+
...payload ?? {},
|
|
433
|
+
suggestions,
|
|
434
|
+
didYouMean,
|
|
435
|
+
totalResultsCount: resolveTotalResultsCount(
|
|
436
|
+
payload,
|
|
437
|
+
products?.length ?? suggestions.length
|
|
438
|
+
)
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/services/SearchApiService.ts
|
|
443
|
+
function getBrowserLocation(providedLocation) {
|
|
444
|
+
if (providedLocation) return providedLocation;
|
|
445
|
+
if (typeof window !== "undefined" && window.location)
|
|
446
|
+
return window.location;
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
function resolveFetch(fetchImplOption) {
|
|
450
|
+
if (fetchImplOption !== void 0) return fetchImplOption;
|
|
451
|
+
return typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
401
452
|
}
|
|
402
453
|
function createSearchService(options = {}) {
|
|
403
454
|
const {
|
|
404
455
|
fetchImpl: fetchImplOption,
|
|
405
456
|
location: providedLocation,
|
|
406
457
|
storage: providedStorage,
|
|
407
|
-
suggestionsApi,
|
|
408
|
-
storeId: storeIdOption,
|
|
409
458
|
country,
|
|
410
459
|
language,
|
|
411
460
|
locale,
|
|
@@ -413,7 +462,7 @@ function createSearchService(options = {}) {
|
|
|
413
462
|
getCustomerType,
|
|
414
463
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
|
|
415
464
|
} = options;
|
|
416
|
-
const fetchImpl = fetchImplOption
|
|
465
|
+
const fetchImpl = resolveFetch(fetchImplOption);
|
|
417
466
|
const locationLike = getBrowserLocation(providedLocation);
|
|
418
467
|
const storage = getSessionStorage(providedStorage);
|
|
419
468
|
const localeParts = normalizeLocaleParts({ country, language, locale });
|
|
@@ -421,8 +470,6 @@ function createSearchService(options = {}) {
|
|
|
421
470
|
function loadCatalogRequestConfig() {
|
|
422
471
|
if (!catalogRequestConfigPromise) {
|
|
423
472
|
catalogRequestConfigPromise = resolveCatalogRequestConfig({
|
|
424
|
-
suggestionsApi,
|
|
425
|
-
storeIdOption,
|
|
426
473
|
locationLike,
|
|
427
474
|
country: localeParts.country,
|
|
428
475
|
getMarketConfig
|
|
@@ -441,47 +488,21 @@ function createSearchService(options = {}) {
|
|
|
441
488
|
async fetchSuggestions(searchTerm) {
|
|
442
489
|
const trimmedSearchTerm = String(searchTerm ?? "").trim();
|
|
443
490
|
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
|
|
444
|
-
return
|
|
445
|
-
suggestions: [],
|
|
446
|
-
didYouMean: [],
|
|
447
|
-
totalResultsCount: 0
|
|
448
|
-
};
|
|
491
|
+
return emptySuggestions();
|
|
449
492
|
}
|
|
450
493
|
const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
|
|
451
|
-
if (!suggestionsUrl || !storeId)
|
|
452
|
-
|
|
453
|
-
suggestions: [],
|
|
454
|
-
didYouMean: [],
|
|
455
|
-
totalResultsCount: 0
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
const queryParams = new URLSearchParams({
|
|
459
|
-
searchTerm: trimmedSearchTerm,
|
|
460
|
-
page: String(DEFAULT_SUGGESTION_PAGE),
|
|
461
|
-
size: String(DEFAULT_SUGGESTION_SIZE),
|
|
462
|
-
locale: toCatalogLocale(localeParts),
|
|
463
|
-
storeId: String(storeId)
|
|
464
|
-
});
|
|
465
|
-
const requestUrls = [`${suggestionsUrl}?${queryParams.toString()}`];
|
|
466
|
-
const responsePayload = await fetchJsonFromCandidates(
|
|
494
|
+
if (!suggestionsUrl || !storeId) return emptySuggestions();
|
|
495
|
+
const responsePayload = await fetchJson(
|
|
467
496
|
fetchImpl,
|
|
468
|
-
|
|
497
|
+
buildSuggestionsRequestUrl({
|
|
498
|
+
suggestionsUrl,
|
|
499
|
+
searchTerm: trimmedSearchTerm,
|
|
500
|
+
storeId,
|
|
501
|
+
localeParts
|
|
502
|
+
}),
|
|
469
503
|
resolveCustomerTypeHeaders(getCustomerType)
|
|
470
504
|
);
|
|
471
|
-
|
|
472
|
-
const productField = Array.isArray(responsePayload?.product) ? responsePayload?.product : null;
|
|
473
|
-
const rawDidYouMean = responsePayload?.didYouMean ?? responsePayload?.did_you_mean;
|
|
474
|
-
const didYouMeanField = Array.isArray(rawDidYouMean) ? rawDidYouMean : [];
|
|
475
|
-
const pageableTotal = Number(
|
|
476
|
-
responsePayload?.pageableInfo?.totalCount ?? Number.NaN
|
|
477
|
-
);
|
|
478
|
-
const totalResultsCount = typeof responsePayload?.totalResultsCount === "number" ? responsePayload.totalResultsCount : !Number.isNaN(pageableTotal) ? pageableTotal : productField?.length ?? suggestionsField.length;
|
|
479
|
-
return {
|
|
480
|
-
...responsePayload ?? {},
|
|
481
|
-
suggestions: suggestionsField,
|
|
482
|
-
didYouMean: didYouMeanField,
|
|
483
|
-
totalResultsCount
|
|
484
|
-
};
|
|
505
|
+
return normalizeSuggestionsPayload(responsePayload);
|
|
485
506
|
},
|
|
486
507
|
submitProductSearch(query, option) {
|
|
487
508
|
const trimmedQuery = String(query ?? "").trim();
|
|
@@ -566,7 +587,90 @@ function getProductEnvironment() {
|
|
|
566
587
|
}
|
|
567
588
|
}
|
|
568
589
|
|
|
569
|
-
// src/utils/
|
|
590
|
+
// src/utils/nuiSearchGate.ts
|
|
591
|
+
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
592
|
+
function parseFlagValue(raw) {
|
|
593
|
+
const value = raw?.trim().toLowerCase();
|
|
594
|
+
if (value === "true" || value === "1") return true;
|
|
595
|
+
if (value === "false" || value === "0") return false;
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
function readCookieFlag(cookie) {
|
|
599
|
+
for (const part of cookie.split(";")) {
|
|
600
|
+
const separator = part.indexOf("=");
|
|
601
|
+
if (separator < 0) continue;
|
|
602
|
+
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
603
|
+
try {
|
|
604
|
+
return parseFlagValue(
|
|
605
|
+
decodeURIComponent(part.slice(separator + 1))
|
|
606
|
+
);
|
|
607
|
+
} catch {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
function resolveNuiSearchFlag(env = {}) {
|
|
614
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
615
|
+
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
616
|
+
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
617
|
+
return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
|
|
618
|
+
}
|
|
619
|
+
function isNuiSearchEnabled({
|
|
620
|
+
isAuthenticated,
|
|
621
|
+
flag,
|
|
622
|
+
allowGuests = false
|
|
623
|
+
}) {
|
|
624
|
+
return flag === true && (isAuthenticated === true || allowGuests);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/hooks/useNuiAgentGate.ts
|
|
628
|
+
function useNuiAgentGate({
|
|
629
|
+
hasAgent,
|
|
630
|
+
isOpen,
|
|
631
|
+
isAuthenticated,
|
|
632
|
+
allowGuestAgent
|
|
633
|
+
}) {
|
|
634
|
+
const [flag, setFlag] = useState(void 0);
|
|
635
|
+
useEffect(() => {
|
|
636
|
+
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
637
|
+
}, [isOpen]);
|
|
638
|
+
return hasAgent && isNuiSearchEnabled({
|
|
639
|
+
isAuthenticated,
|
|
640
|
+
flag,
|
|
641
|
+
allowGuests: allowGuestAgent
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function uniqueRecentSearches(queries) {
|
|
645
|
+
const seen = /* @__PURE__ */ new Set();
|
|
646
|
+
return queries.filter((query) => {
|
|
647
|
+
const key = query.trim().toLowerCase();
|
|
648
|
+
if (!key || seen.has(key)) return false;
|
|
649
|
+
seen.add(key);
|
|
650
|
+
return true;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
function useRecentSearches({
|
|
654
|
+
service,
|
|
655
|
+
isOpen,
|
|
656
|
+
refreshKey
|
|
657
|
+
}) {
|
|
658
|
+
const [recentSearches, setRecentSearches] = useState([]);
|
|
659
|
+
useEffect(() => {
|
|
660
|
+
if (isOpen) {
|
|
661
|
+
setRecentSearches(
|
|
662
|
+
uniqueRecentSearches(service.getRecentSearches())
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}, [isOpen, service, refreshKey]);
|
|
666
|
+
const clearRecentSearches = useCallback(() => {
|
|
667
|
+
service.clearRecentSearches();
|
|
668
|
+
setRecentSearches([]);
|
|
669
|
+
}, [service]);
|
|
670
|
+
return { recentSearches, clearRecentSearches };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/utils/catalogStatus.ts
|
|
570
674
|
var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
|
|
571
675
|
"discontinued",
|
|
572
676
|
"preview",
|
|
@@ -614,6 +718,8 @@ function isSuggestionDisabled(suggestionOption) {
|
|
|
614
718
|
!suggestionOption || suggestionOption["out_of_stock"] || suggestionOption["outOfStock"]
|
|
615
719
|
);
|
|
616
720
|
}
|
|
721
|
+
|
|
722
|
+
// src/utils/catalogHelpers.ts
|
|
617
723
|
function getImageFromProperty(properties = {}) {
|
|
618
724
|
let image = String(properties["primaryimage"] ?? "").trim() || String(properties["imageURL"] ?? "").trim();
|
|
619
725
|
if (!image) {
|
|
@@ -851,6 +957,46 @@ function normalizeSuggestionsResponse(response) {
|
|
|
851
957
|
didYouMean: normalizeDidYouMean(r.didYouMean)
|
|
852
958
|
};
|
|
853
959
|
}
|
|
960
|
+
function resolveTotalResultsCount2(response, fallback) {
|
|
961
|
+
if (typeof response.totalResultsCount === "number") {
|
|
962
|
+
return response.totalResultsCount;
|
|
963
|
+
}
|
|
964
|
+
if (typeof response.pageableInfo?.totalCount === "number") {
|
|
965
|
+
return response.pageableInfo.totalCount;
|
|
966
|
+
}
|
|
967
|
+
return fallback;
|
|
968
|
+
}
|
|
969
|
+
var FETCH_FAILED = {
|
|
970
|
+
suggestions: [],
|
|
971
|
+
didYouMean: [],
|
|
972
|
+
hasMoreResults: false,
|
|
973
|
+
emptyFetched: true,
|
|
974
|
+
failed: true
|
|
975
|
+
};
|
|
976
|
+
function toSettledResults(response) {
|
|
977
|
+
const { suggestions: allSuggestions, didYouMean } = normalizeSuggestionsResponse(response);
|
|
978
|
+
if (didYouMean.length > 0) {
|
|
979
|
+
return {
|
|
980
|
+
suggestions: [],
|
|
981
|
+
didYouMean,
|
|
982
|
+
hasMoreResults: false,
|
|
983
|
+
emptyFetched: false,
|
|
984
|
+
failed: false
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
const total = Math.max(
|
|
988
|
+
resolveTotalResultsCount2(response, allSuggestions.length),
|
|
989
|
+
allSuggestions.length
|
|
990
|
+
);
|
|
991
|
+
const suggestions = allSuggestions.slice(0, MAX_VISIBLE_SUGGESTIONS);
|
|
992
|
+
return {
|
|
993
|
+
suggestions,
|
|
994
|
+
didYouMean,
|
|
995
|
+
hasMoreResults: total > MAX_VISIBLE_SUGGESTIONS,
|
|
996
|
+
emptyFetched: suggestions.length === 0,
|
|
997
|
+
failed: false
|
|
998
|
+
};
|
|
999
|
+
}
|
|
854
1000
|
function useSearchSuggestions({
|
|
855
1001
|
service,
|
|
856
1002
|
enabled = true
|
|
@@ -862,7 +1008,6 @@ function useSearchSuggestions({
|
|
|
862
1008
|
const [emptyFetched, setEmptyFetched] = useState(false);
|
|
863
1009
|
const [suggestionFetchFailed, setSuggestionFetchFailed] = useState(false);
|
|
864
1010
|
const [hasMoreResults, setHasMoreResults] = useState(false);
|
|
865
|
-
const [totalResultsCount, setTotalResultsCount] = useState(0);
|
|
866
1011
|
const [settledQuery, setSettledQuery] = useState("");
|
|
867
1012
|
const fetchDebounced = useRef(null);
|
|
868
1013
|
const latestRequestId = useRef(0);
|
|
@@ -872,7 +1017,6 @@ function useSearchSuggestions({
|
|
|
872
1017
|
setEmptyFetched(false);
|
|
873
1018
|
setSuggestionFetchFailed(false);
|
|
874
1019
|
setHasMoreResults(false);
|
|
875
|
-
setTotalResultsCount(0);
|
|
876
1020
|
setSettledQuery("");
|
|
877
1021
|
setLoading(false);
|
|
878
1022
|
}, []);
|
|
@@ -890,6 +1034,14 @@ function useSearchSuggestions({
|
|
|
890
1034
|
resetSuggestionsState();
|
|
891
1035
|
setQuery("");
|
|
892
1036
|
}, [resetSuggestionsState]);
|
|
1037
|
+
const settle = useCallback((results, term) => {
|
|
1038
|
+
setDidYouMean(results.didYouMean);
|
|
1039
|
+
setSuggestions(results.suggestions);
|
|
1040
|
+
setHasMoreResults(results.hasMoreResults);
|
|
1041
|
+
setSuggestionFetchFailed(results.failed);
|
|
1042
|
+
setSettledQuery(term);
|
|
1043
|
+
setEmptyFetched(results.emptyFetched);
|
|
1044
|
+
}, []);
|
|
893
1045
|
const runFetch = useCallback(
|
|
894
1046
|
async (nextQuery) => {
|
|
895
1047
|
const trimmedQuery = nextQuery.trim();
|
|
@@ -901,51 +1053,20 @@ function useSearchSuggestions({
|
|
|
901
1053
|
setLoading(true);
|
|
902
1054
|
setEmptyFetched(false);
|
|
903
1055
|
setSuggestionFetchFailed(false);
|
|
1056
|
+
let results;
|
|
904
1057
|
try {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
const {
|
|
910
|
-
suggestions: allSuggestions,
|
|
911
|
-
didYouMean: suggestedTerms
|
|
912
|
-
} = normalizeSuggestionsResponse(fetchedSuggestions);
|
|
913
|
-
const visibleSuggestions = allSuggestions.slice(
|
|
914
|
-
0,
|
|
915
|
-
MAX_VISIBLE_SUGGESTIONS
|
|
1058
|
+
results = toSettledResults(
|
|
1059
|
+
await service.fetchSuggestions(trimmedQuery)
|
|
916
1060
|
);
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
setDidYouMean(suggestedTerms);
|
|
921
|
-
setSuggestions(hasDidYouMean ? [] : visibleSuggestions);
|
|
922
|
-
setHasMoreResults(
|
|
923
|
-
!hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
|
|
924
|
-
);
|
|
925
|
-
setTotalResultsCount(effectiveTotal);
|
|
926
|
-
setSuggestionFetchFailed(false);
|
|
927
|
-
setSettledQuery(trimmedQuery);
|
|
928
|
-
setEmptyFetched(
|
|
929
|
-
hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
|
|
930
|
-
);
|
|
931
|
-
} catch {
|
|
932
|
-
if (requestId !== latestRequestId.current) {
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
setSuggestions([]);
|
|
936
|
-
setDidYouMean([]);
|
|
937
|
-
setEmptyFetched(true);
|
|
938
|
-
setSuggestionFetchFailed(true);
|
|
939
|
-
setHasMoreResults(false);
|
|
940
|
-
setTotalResultsCount(0);
|
|
941
|
-
setSettledQuery(trimmedQuery);
|
|
942
|
-
} finally {
|
|
943
|
-
if (requestId === latestRequestId.current) {
|
|
944
|
-
setLoading(false);
|
|
945
|
-
}
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
console.warn("[search] Could not load suggestions:", error);
|
|
1063
|
+
results = FETCH_FAILED;
|
|
946
1064
|
}
|
|
1065
|
+
if (requestId !== latestRequestId.current) return;
|
|
1066
|
+
settle(results, trimmedQuery);
|
|
1067
|
+
setLoading(false);
|
|
947
1068
|
},
|
|
948
|
-
[enabled, service, resetSuggestionsState]
|
|
1069
|
+
[enabled, service, resetSuggestionsState, settle]
|
|
949
1070
|
);
|
|
950
1071
|
useEffect(() => {
|
|
951
1072
|
if (!enabled) {
|
|
@@ -969,15 +1090,12 @@ function useSearchSuggestions({
|
|
|
969
1090
|
clearQuery,
|
|
970
1091
|
emptyFetched,
|
|
971
1092
|
hasMoreResults,
|
|
972
|
-
hasQuery: Boolean(query.trim()),
|
|
973
1093
|
didYouMean,
|
|
974
1094
|
loading,
|
|
975
1095
|
query,
|
|
976
1096
|
settledQuery,
|
|
977
|
-
setQuery,
|
|
978
1097
|
suggestionFetchFailed,
|
|
979
1098
|
suggestions,
|
|
980
|
-
totalResultsCount,
|
|
981
1099
|
updateQuery
|
|
982
1100
|
};
|
|
983
1101
|
}
|
|
@@ -1052,43 +1170,6 @@ function trackProductSearch(input) {
|
|
|
1052
1170
|
});
|
|
1053
1171
|
}
|
|
1054
1172
|
|
|
1055
|
-
// src/utils/nuiSearchGate.ts
|
|
1056
|
-
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
1057
|
-
function parseFlagValue(raw) {
|
|
1058
|
-
const value = raw?.trim().toLowerCase();
|
|
1059
|
-
if (value === "true" || value === "1") return true;
|
|
1060
|
-
if (value === "false" || value === "0") return false;
|
|
1061
|
-
return void 0;
|
|
1062
|
-
}
|
|
1063
|
-
function readCookieFlag(cookie) {
|
|
1064
|
-
for (const part of cookie.split(";")) {
|
|
1065
|
-
const separator = part.indexOf("=");
|
|
1066
|
-
if (separator < 0) continue;
|
|
1067
|
-
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
1068
|
-
try {
|
|
1069
|
-
return parseFlagValue(
|
|
1070
|
-
decodeURIComponent(part.slice(separator + 1))
|
|
1071
|
-
);
|
|
1072
|
-
} catch {
|
|
1073
|
-
return void 0;
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
return void 0;
|
|
1077
|
-
}
|
|
1078
|
-
function resolveNuiSearchFlag(env = {}) {
|
|
1079
|
-
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
1080
|
-
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
1081
|
-
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
1082
|
-
return parseFlagValue(new URLSearchParams(search).get(NUI_SEARCH_FLAG)) ?? readCookieFlag(cookie);
|
|
1083
|
-
}
|
|
1084
|
-
function isNuiSearchEnabled({
|
|
1085
|
-
isAuthenticated,
|
|
1086
|
-
flag,
|
|
1087
|
-
allowGuests = false
|
|
1088
|
-
}) {
|
|
1089
|
-
return flag === true && (isAuthenticated === true || allowGuests);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
1173
|
// src/utils/searchRouting.ts
|
|
1093
1174
|
function decideSearchRoute({
|
|
1094
1175
|
enabled,
|
|
@@ -1101,6 +1182,96 @@ function decideSearchRoute({
|
|
|
1101
1182
|
return snapshot.suggestions.length > 0 ? "search" : "handoff";
|
|
1102
1183
|
}
|
|
1103
1184
|
|
|
1185
|
+
// src/hooks/useSubmitRouting.ts
|
|
1186
|
+
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1187
|
+
function useSubmitRouting({
|
|
1188
|
+
enabled,
|
|
1189
|
+
minimumSearchLength,
|
|
1190
|
+
search,
|
|
1191
|
+
gtmContext,
|
|
1192
|
+
pendingTerm,
|
|
1193
|
+
setPendingTerm,
|
|
1194
|
+
onSearch,
|
|
1195
|
+
onHandoff
|
|
1196
|
+
}) {
|
|
1197
|
+
const {
|
|
1198
|
+
didYouMean,
|
|
1199
|
+
loading,
|
|
1200
|
+
settledQuery,
|
|
1201
|
+
suggestionFetchFailed,
|
|
1202
|
+
suggestions
|
|
1203
|
+
} = search;
|
|
1204
|
+
const decide = useCallback(
|
|
1205
|
+
(term) => decideSearchRoute({
|
|
1206
|
+
enabled,
|
|
1207
|
+
snapshot: {
|
|
1208
|
+
query: term,
|
|
1209
|
+
loading,
|
|
1210
|
+
settledQuery,
|
|
1211
|
+
suggestions,
|
|
1212
|
+
suggestionFetchFailed
|
|
1213
|
+
}
|
|
1214
|
+
}),
|
|
1215
|
+
[enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
|
|
1216
|
+
);
|
|
1217
|
+
const act = useCallback(
|
|
1218
|
+
(term, route) => {
|
|
1219
|
+
setPendingTerm(null);
|
|
1220
|
+
if (route === "handoff") {
|
|
1221
|
+
trackChatEntryPoint(gtmContext ?? {});
|
|
1222
|
+
onHandoff({
|
|
1223
|
+
query: term,
|
|
1224
|
+
didYouMean: didYouMean.map((item) => item.value)
|
|
1225
|
+
});
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
onSearch(term);
|
|
1229
|
+
},
|
|
1230
|
+
[didYouMean, gtmContext, onHandoff, onSearch, setPendingTerm]
|
|
1231
|
+
);
|
|
1232
|
+
const submit = useCallback(
|
|
1233
|
+
(value) => {
|
|
1234
|
+
const term = value.trim();
|
|
1235
|
+
if (!term) return;
|
|
1236
|
+
if (term.length < minimumSearchLength) {
|
|
1237
|
+
onSearch(term);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const route = decide(term);
|
|
1241
|
+
if (route === "wait") {
|
|
1242
|
+
setPendingTerm(term);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
act(term, route);
|
|
1246
|
+
},
|
|
1247
|
+
[act, decide, minimumSearchLength, onSearch, setPendingTerm]
|
|
1248
|
+
);
|
|
1249
|
+
const latest = useRef({ act, decide });
|
|
1250
|
+
useEffect(() => {
|
|
1251
|
+
latest.current = { act, decide };
|
|
1252
|
+
});
|
|
1253
|
+
useEffect(() => {
|
|
1254
|
+
if (pendingTerm === null) return void 0;
|
|
1255
|
+
const route = latest.current.decide(pendingTerm);
|
|
1256
|
+
if (route !== "wait") {
|
|
1257
|
+
latest.current.act(pendingTerm, route);
|
|
1258
|
+
return void 0;
|
|
1259
|
+
}
|
|
1260
|
+
const timer = setTimeout(
|
|
1261
|
+
() => latest.current.act(pendingTerm, "search"),
|
|
1262
|
+
SUBMIT_WAIT_TIMEOUT_MS
|
|
1263
|
+
);
|
|
1264
|
+
return () => clearTimeout(timer);
|
|
1265
|
+
}, [
|
|
1266
|
+
pendingTerm,
|
|
1267
|
+
loading,
|
|
1268
|
+
settledQuery,
|
|
1269
|
+
suggestions,
|
|
1270
|
+
suggestionFetchFailed
|
|
1271
|
+
]);
|
|
1272
|
+
return submit;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1104
1275
|
// src/utils/cachedProductClient.ts
|
|
1105
1276
|
function withProductCache(client) {
|
|
1106
1277
|
const lookups = /* @__PURE__ */ new Map();
|
|
@@ -1168,7 +1339,8 @@ var AgentErrorBoundary = class extends Component {
|
|
|
1168
1339
|
static getDerivedStateFromError() {
|
|
1169
1340
|
return { failed: true };
|
|
1170
1341
|
}
|
|
1171
|
-
componentDidCatch(
|
|
1342
|
+
componentDidCatch(error) {
|
|
1343
|
+
console.warn("[search] The agent could not be shown:", error);
|
|
1172
1344
|
this.props.onError();
|
|
1173
1345
|
}
|
|
1174
1346
|
render() {
|
|
@@ -1181,6 +1353,10 @@ function useHandoff({
|
|
|
1181
1353
|
agentContext,
|
|
1182
1354
|
onUnavailable
|
|
1183
1355
|
}) {
|
|
1356
|
+
const onUnavailableRef = useRef(onUnavailable);
|
|
1357
|
+
useEffect(() => {
|
|
1358
|
+
onUnavailableRef.current = onUnavailable;
|
|
1359
|
+
});
|
|
1184
1360
|
useEffect(() => {
|
|
1185
1361
|
let cancelled = false;
|
|
1186
1362
|
let timer;
|
|
@@ -1196,7 +1372,10 @@ function useHandoff({
|
|
|
1196
1372
|
return;
|
|
1197
1373
|
}
|
|
1198
1374
|
if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
|
|
1199
|
-
|
|
1375
|
+
console.warn(
|
|
1376
|
+
`[search] The agent did not start within ${HANDLE_TIMEOUT_MS / 1e3} seconds.`
|
|
1377
|
+
);
|
|
1378
|
+
onUnavailableRef.current();
|
|
1200
1379
|
return;
|
|
1201
1380
|
}
|
|
1202
1381
|
timer = setTimeout(tryOpen, HANDLE_POLL_MS);
|
|
@@ -1238,16 +1417,6 @@ function NuiAgentPanel({
|
|
|
1238
1417
|
}
|
|
1239
1418
|
) });
|
|
1240
1419
|
}
|
|
1241
|
-
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1242
|
-
function uniqueRecentSearches(queries) {
|
|
1243
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1244
|
-
return queries.filter((query) => {
|
|
1245
|
-
const key = query.trim().toLowerCase();
|
|
1246
|
-
if (!key || seen.has(key)) return false;
|
|
1247
|
-
seen.add(key);
|
|
1248
|
-
return true;
|
|
1249
|
-
});
|
|
1250
|
-
}
|
|
1251
1420
|
function CommerceSearchView({
|
|
1252
1421
|
copy,
|
|
1253
1422
|
isOpen,
|
|
@@ -1262,11 +1431,9 @@ function CommerceSearchView({
|
|
|
1262
1431
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
|
|
1263
1432
|
gtmContext
|
|
1264
1433
|
}) {
|
|
1265
|
-
const [flag, setFlag] = useState(void 0);
|
|
1266
1434
|
const [handoff, setHandoff] = useState(null);
|
|
1267
1435
|
const agentQuery = handoff?.query ?? null;
|
|
1268
1436
|
const [pendingTerm, setPendingTerm] = useState(null);
|
|
1269
|
-
const [recentSearches, setRecentSearches] = useState([]);
|
|
1270
1437
|
const search = useSearchSuggestions({
|
|
1271
1438
|
service,
|
|
1272
1439
|
enabled: isOpen && agentQuery === null
|
|
@@ -1278,31 +1445,25 @@ function CommerceSearchView({
|
|
|
1278
1445
|
hasMoreResults,
|
|
1279
1446
|
loading,
|
|
1280
1447
|
query,
|
|
1281
|
-
settledQuery,
|
|
1282
|
-
suggestionFetchFailed,
|
|
1283
1448
|
suggestions,
|
|
1284
1449
|
updateQuery
|
|
1285
1450
|
} = search;
|
|
1286
|
-
const
|
|
1451
|
+
const agentEnabled = useNuiAgentGate({
|
|
1452
|
+
hasAgent: agent !== void 0,
|
|
1453
|
+
isOpen,
|
|
1287
1454
|
isAuthenticated,
|
|
1288
|
-
|
|
1289
|
-
|
|
1455
|
+
allowGuestAgent
|
|
1456
|
+
});
|
|
1457
|
+
const { recentSearches, clearRecentSearches } = useRecentSearches({
|
|
1458
|
+
service,
|
|
1459
|
+
isOpen,
|
|
1460
|
+
refreshKey: agentQuery
|
|
1290
1461
|
});
|
|
1291
1462
|
const items = getVisibleSuggestions({
|
|
1292
1463
|
didYouMean,
|
|
1293
1464
|
suggestions
|
|
1294
1465
|
});
|
|
1295
1466
|
const showDidYouMean = didYouMean.length > 0;
|
|
1296
|
-
useEffect(() => {
|
|
1297
|
-
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
1298
|
-
}, [isOpen]);
|
|
1299
|
-
useEffect(() => {
|
|
1300
|
-
if (isOpen) {
|
|
1301
|
-
setRecentSearches(
|
|
1302
|
-
uniqueRecentSearches(service.getRecentSearches())
|
|
1303
|
-
);
|
|
1304
|
-
}
|
|
1305
|
-
}, [isOpen, service, agentQuery]);
|
|
1306
1467
|
const close = useCallback(() => {
|
|
1307
1468
|
setHandoff(null);
|
|
1308
1469
|
setPendingTerm(null);
|
|
@@ -1341,72 +1502,16 @@ function CommerceSearchView({
|
|
|
1341
1502
|
},
|
|
1342
1503
|
[items, navigateTo, service, trackSemantic]
|
|
1343
1504
|
);
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
loading,
|
|
1350
|
-
settledQuery,
|
|
1351
|
-
suggestions,
|
|
1352
|
-
suggestionFetchFailed
|
|
1353
|
-
}
|
|
1354
|
-
}),
|
|
1355
|
-
[enabled, loading, settledQuery, suggestions, suggestionFetchFailed]
|
|
1356
|
-
);
|
|
1357
|
-
const act = useCallback(
|
|
1358
|
-
(term, route) => {
|
|
1359
|
-
setPendingTerm(null);
|
|
1360
|
-
if (route === "handoff") {
|
|
1361
|
-
trackChatEntryPoint(gtmContext ?? {});
|
|
1362
|
-
setHandoff({
|
|
1363
|
-
query: term,
|
|
1364
|
-
didYouMean: didYouMean.map((item) => item.value)
|
|
1365
|
-
});
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1368
|
-
runSearch(term);
|
|
1369
|
-
},
|
|
1370
|
-
[didYouMean, gtmContext, runSearch]
|
|
1371
|
-
);
|
|
1372
|
-
const handleSubmit = useCallback(
|
|
1373
|
-
(value) => {
|
|
1374
|
-
const term = value.trim();
|
|
1375
|
-
if (!term) return;
|
|
1376
|
-
if (term.length < minimumSearchLength) {
|
|
1377
|
-
runSearch(term);
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
const route = decide(term);
|
|
1381
|
-
if (route === "wait") {
|
|
1382
|
-
setPendingTerm(term);
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
act(term, route);
|
|
1386
|
-
},
|
|
1387
|
-
[act, decide, minimumSearchLength, runSearch]
|
|
1388
|
-
);
|
|
1389
|
-
const latest = useRef({ act, decide });
|
|
1390
|
-
latest.current = { act, decide };
|
|
1391
|
-
useEffect(() => {
|
|
1392
|
-
if (pendingTerm === null) return void 0;
|
|
1393
|
-
const route = latest.current.decide(pendingTerm);
|
|
1394
|
-
if (route !== "wait") {
|
|
1395
|
-
latest.current.act(pendingTerm, route);
|
|
1396
|
-
return void 0;
|
|
1397
|
-
}
|
|
1398
|
-
const timer = setTimeout(
|
|
1399
|
-
() => latest.current.act(pendingTerm, "search"),
|
|
1400
|
-
SUBMIT_WAIT_TIMEOUT_MS
|
|
1401
|
-
);
|
|
1402
|
-
return () => clearTimeout(timer);
|
|
1403
|
-
}, [
|
|
1505
|
+
const handleSubmit = useSubmitRouting({
|
|
1506
|
+
enabled: agentEnabled,
|
|
1507
|
+
minimumSearchLength,
|
|
1508
|
+
search,
|
|
1509
|
+
...gtmContext ? { gtmContext } : {},
|
|
1404
1510
|
pendingTerm,
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
]);
|
|
1511
|
+
setPendingTerm,
|
|
1512
|
+
onSearch: runSearch,
|
|
1513
|
+
onHandoff: setHandoff
|
|
1514
|
+
});
|
|
1410
1515
|
const handleSelect = useCallback(
|
|
1411
1516
|
(option) => {
|
|
1412
1517
|
if (isSuggestionDisabled(
|
|
@@ -1431,10 +1536,11 @@ function CommerceSearchView({
|
|
|
1431
1536
|
)) {
|
|
1432
1537
|
return;
|
|
1433
1538
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1539
|
+
navigateTo(
|
|
1540
|
+
service.activateSuggestionAction(
|
|
1541
|
+
option
|
|
1542
|
+
)
|
|
1436
1543
|
);
|
|
1437
|
-
void Promise.resolve(result).then(navigateTo).catch(() => void 0);
|
|
1438
1544
|
},
|
|
1439
1545
|
[navigateTo, service]
|
|
1440
1546
|
);
|
|
@@ -1449,10 +1555,6 @@ function CommerceSearchView({
|
|
|
1449
1555
|
},
|
|
1450
1556
|
[navigateTo, service, trackSemantic]
|
|
1451
1557
|
);
|
|
1452
|
-
const handleClearRecent = useCallback(() => {
|
|
1453
|
-
service.clearRecentSearches();
|
|
1454
|
-
setRecentSearches([]);
|
|
1455
|
-
}, [service]);
|
|
1456
1558
|
const handleAgentUnavailable = useCallback(() => {
|
|
1457
1559
|
if (agentQuery !== null)
|
|
1458
1560
|
navigateTo(service.submitProductSearch(agentQuery));
|
|
@@ -1501,7 +1603,7 @@ function CommerceSearchView({
|
|
|
1501
1603
|
recentSearches: {
|
|
1502
1604
|
items: recentSearches,
|
|
1503
1605
|
onSelect: handleRecentSelect,
|
|
1504
|
-
onClear:
|
|
1606
|
+
onClear: clearRecentSearches
|
|
1505
1607
|
},
|
|
1506
1608
|
agentPanel
|
|
1507
1609
|
}
|
|
@@ -1527,8 +1629,11 @@ function CommerceSearch({
|
|
|
1527
1629
|
language,
|
|
1528
1630
|
enabled: hasOpened
|
|
1529
1631
|
});
|
|
1530
|
-
const
|
|
1531
|
-
customerTypeRef
|
|
1632
|
+
const customerType = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
|
|
1633
|
+
const customerTypeRef = useRef(customerType);
|
|
1634
|
+
useEffect(() => {
|
|
1635
|
+
customerTypeRef.current = customerType;
|
|
1636
|
+
}, [customerType]);
|
|
1532
1637
|
const service = useMemo(
|
|
1533
1638
|
() => createSearchService({
|
|
1534
1639
|
country,
|