@nuskin/react-search-sdk 1.0.0-comx-2233.4 → 1.0.0-trying-again.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -8
- package/dist/index.d.mts +45 -632
- package/dist/index.d.ts +45 -632
- package/dist/index.js +605 -1149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +589 -1094
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -3
package/dist/index.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var configurationSdk = require('@nuskin/configuration-sdk');
|
|
5
|
+
var reactSearchComponents = require('@nuskin/react-search-components');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
7
|
|
|
5
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
9
|
|
|
7
|
-
var
|
|
10
|
+
var configurationSdk__default = /*#__PURE__*/_interopDefault(configurationSdk);
|
|
11
|
+
|
|
12
|
+
// src/components/CommerceSearch/CommerceSearch.tsx
|
|
13
|
+
var { getConfiguration, getEnvironmentCode } = configurationSdk__default.default;
|
|
8
14
|
|
|
9
15
|
// src/constants.ts
|
|
10
16
|
var STOREFRONT_SHOPPING_CONTEXT = "storefront";
|
|
@@ -15,16 +21,77 @@ var DEFAULT_SUGGESTION_PAGE = 1;
|
|
|
15
21
|
var DEFAULT_SUGGESTION_SIZE = 6;
|
|
16
22
|
var DEFAULT_SEARCH_PATH = "/search";
|
|
17
23
|
var DEBOUNCE_MS = 220;
|
|
24
|
+
var MINIMUM_SEARCH_CHAR_LENGTH = 2;
|
|
18
25
|
var MAX_VISIBLE_SUGGESTIONS = 6;
|
|
19
|
-
var
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
var
|
|
26
|
+
var EQUINOX_MARKETS_MAP = "Equinox_Markets";
|
|
27
|
+
var DEFAULT_EQUINOX_CLIENT_ID = "63056848d7532f9923f3d08cb48c423b";
|
|
28
|
+
|
|
29
|
+
// src/config/commerceSearchConfig.ts
|
|
30
|
+
var COMMERCE_SEARCH_CONFIG_DEFAULTS = Object.freeze({
|
|
31
|
+
minimumSearchLength: MINIMUM_SEARCH_CHAR_LENGTH
|
|
32
|
+
});
|
|
33
|
+
var TEST_ENVIRONMENT_VALUES = {
|
|
34
|
+
agent: {
|
|
35
|
+
appKey: "pk_commerce_product_advisor_624aea4305b09240e8c8f41d0f6527b2",
|
|
36
|
+
apiBaseUrl: "https://nui.tst.nuskin.ai/"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
function nonEmptyString(value) {
|
|
40
|
+
return typeof value === "string" ? value.trim() : "";
|
|
41
|
+
}
|
|
42
|
+
function normalizeCommerceSearchConfig(raw, { language } = {}) {
|
|
43
|
+
const minimumSearchLength = Number(raw?.minimumSearchLength);
|
|
44
|
+
const appKey = nonEmptyString(raw?.agent?.appKey);
|
|
45
|
+
const apiBaseUrl = nonEmptyString(raw?.agent?.apiBaseUrl);
|
|
46
|
+
return {
|
|
47
|
+
minimumSearchLength: Number.isInteger(minimumSearchLength) && minimumSearchLength >= 1 ? minimumSearchLength : COMMERCE_SEARCH_CONFIG_DEFAULTS.minimumSearchLength,
|
|
48
|
+
...appKey && apiBaseUrl ? {
|
|
49
|
+
agent: {
|
|
50
|
+
appKey,
|
|
51
|
+
apiBaseUrl,
|
|
52
|
+
...language ? { language } : {}
|
|
53
|
+
}
|
|
54
|
+
} : {}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async function fetchCommerceSearchConfig({
|
|
58
|
+
language
|
|
59
|
+
} = {}) {
|
|
60
|
+
const raw = getEnvironmentCode() === "test" ? TEST_ENVIRONMENT_VALUES : {};
|
|
61
|
+
return normalizeCommerceSearchConfig(raw, language ? { language } : {});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/hooks/useCommerceSearchConfig.ts
|
|
65
|
+
function useCommerceSearchConfig({
|
|
66
|
+
country,
|
|
67
|
+
language,
|
|
68
|
+
enabled
|
|
69
|
+
}) {
|
|
70
|
+
const [config, setConfig] = react.useState(
|
|
71
|
+
COMMERCE_SEARCH_CONFIG_DEFAULTS
|
|
72
|
+
);
|
|
73
|
+
react.useEffect(() => {
|
|
74
|
+
if (!enabled) return void 0;
|
|
75
|
+
let isStale = false;
|
|
76
|
+
fetchCommerceSearchConfig({
|
|
77
|
+
...country ? { country } : {},
|
|
78
|
+
...language ? { language } : {}
|
|
79
|
+
}).then((next) => {
|
|
80
|
+
if (!isStale) setConfig(next);
|
|
81
|
+
}).catch((error) => {
|
|
82
|
+
console.warn(
|
|
83
|
+
"[search] Could not load the product search configuration:",
|
|
84
|
+
error
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
return () => {
|
|
88
|
+
isStale = true;
|
|
89
|
+
};
|
|
90
|
+
}, [enabled, country, language]);
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
24
93
|
|
|
25
94
|
// src/services/SearchApiService.ts
|
|
26
|
-
var DEFAULT_SEARCH_PLACEHOLDER = TRIGGER_PLACEHOLDER;
|
|
27
|
-
var DEFAULT_INPUT_ARIA_LABEL = SEARCH_PLACEHOLDER;
|
|
28
95
|
function canUseStorage(storage) {
|
|
29
96
|
return Boolean(storage) && typeof storage.getItem === "function";
|
|
30
97
|
}
|
|
@@ -43,7 +110,7 @@ function normalizeLocaleParts({
|
|
|
43
110
|
country,
|
|
44
111
|
language,
|
|
45
112
|
locale
|
|
46
|
-
}
|
|
113
|
+
}) {
|
|
47
114
|
const normalizedCountry = String(country ?? "").trim().toUpperCase();
|
|
48
115
|
const normalizedLanguage = String(language ?? "").trim().toLowerCase();
|
|
49
116
|
const normalizedLocale = String(locale ?? "").trim() || "UNKNOWN";
|
|
@@ -234,14 +301,18 @@ function resolveOptionHref(option, localeParts) {
|
|
|
234
301
|
}
|
|
235
302
|
return "";
|
|
236
303
|
}
|
|
237
|
-
|
|
304
|
+
function resolveCustomerTypeHeaders(getCustomerType) {
|
|
305
|
+
const customerType = getCustomerType?.()?.trim();
|
|
306
|
+
return customerType ? { "X-Customer-Type": customerType } : void 0;
|
|
307
|
+
}
|
|
308
|
+
async function fetchJsonFromCandidates(fetchImpl, requestUrls, requestHeaders = {}) {
|
|
238
309
|
let lastError;
|
|
239
310
|
for (const requestUrl of requestUrls) {
|
|
240
311
|
try {
|
|
241
312
|
const response = await fetchImpl(requestUrl, {
|
|
242
313
|
method: "GET",
|
|
243
314
|
credentials: "include",
|
|
244
|
-
headers: { Accept: "application/json" }
|
|
315
|
+
headers: { Accept: "application/json", ...requestHeaders }
|
|
245
316
|
});
|
|
246
317
|
if (!response.ok) {
|
|
247
318
|
lastError = new Error(
|
|
@@ -266,9 +337,9 @@ function createSearchService(options = {}) {
|
|
|
266
337
|
country,
|
|
267
338
|
language,
|
|
268
339
|
locale,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
340
|
+
getMarketConfig,
|
|
341
|
+
getCustomerType,
|
|
342
|
+
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
|
|
272
343
|
} = options;
|
|
273
344
|
const fetchImpl = fetchImplOption !== void 0 ? fetchImplOption : typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
274
345
|
const locationLike = getBrowserLocation(providedLocation);
|
|
@@ -288,15 +359,9 @@ function createSearchService(options = {}) {
|
|
|
288
359
|
return catalogRequestConfigPromise;
|
|
289
360
|
}
|
|
290
361
|
const service = {
|
|
291
|
-
async getSearchChromeCopy() {
|
|
292
|
-
return {
|
|
293
|
-
placeholder: searchPlaceholder || DEFAULT_SEARCH_PLACEHOLDER,
|
|
294
|
-
inputAriaLabel: inputAriaLabel || DEFAULT_INPUT_ARIA_LABEL
|
|
295
|
-
};
|
|
296
|
-
},
|
|
297
362
|
async fetchSuggestions(searchTerm) {
|
|
298
363
|
const trimmedSearchTerm = String(searchTerm ?? "").trim();
|
|
299
|
-
if (!trimmedSearchTerm || !fetchImpl) {
|
|
364
|
+
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength || !fetchImpl) {
|
|
300
365
|
return {
|
|
301
366
|
suggestions: [],
|
|
302
367
|
didYouMean: [],
|
|
@@ -321,7 +386,8 @@ function createSearchService(options = {}) {
|
|
|
321
386
|
const requestUrls = [`${suggestionsUrl}?${queryParams.toString()}`];
|
|
322
387
|
const responsePayload = await fetchJsonFromCandidates(
|
|
323
388
|
fetchImpl,
|
|
324
|
-
requestUrls
|
|
389
|
+
requestUrls,
|
|
390
|
+
resolveCustomerTypeHeaders(getCustomerType)
|
|
325
391
|
);
|
|
326
392
|
const suggestionsField = Array.isArray(responsePayload?.suggestions) ? responsePayload?.suggestions : [];
|
|
327
393
|
const productField = Array.isArray(responsePayload?.product) ? responsePayload?.product : null;
|
|
@@ -353,33 +419,6 @@ function createSearchService(options = {}) {
|
|
|
353
419
|
return stripLocalePrefix(targetPath, localeParts);
|
|
354
420
|
},
|
|
355
421
|
activateSuggestionAction(option) {
|
|
356
|
-
if (options.onSuggestionAction) {
|
|
357
|
-
const result = options.onSuggestionAction(option);
|
|
358
|
-
if (result instanceof Promise) {
|
|
359
|
-
result.then((handled) => {
|
|
360
|
-
if (!handled) {
|
|
361
|
-
const optionHref2 = resolveOptionHref(
|
|
362
|
-
option,
|
|
363
|
-
localeParts
|
|
364
|
-
);
|
|
365
|
-
if (!optionHref2) {
|
|
366
|
-
const optionQuery2 = option?.value ?? option?.label ?? option?.identifier ?? "";
|
|
367
|
-
service.submitProductSearch(
|
|
368
|
-
String(optionQuery2)
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}).catch((error) => {
|
|
373
|
-
console.error(
|
|
374
|
-
"Error in onSuggestionAction:",
|
|
375
|
-
error
|
|
376
|
-
);
|
|
377
|
-
});
|
|
378
|
-
return "action-pending";
|
|
379
|
-
} else if (result === true) {
|
|
380
|
-
return "action-handled";
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
422
|
const optionHref = resolveOptionHref(option, localeParts);
|
|
384
423
|
if (optionHref) {
|
|
385
424
|
return stripLocalePrefix(optionHref, localeParts);
|
|
@@ -406,547 +445,33 @@ function createSearchService(options = {}) {
|
|
|
406
445
|
return service;
|
|
407
446
|
}
|
|
408
447
|
|
|
409
|
-
//
|
|
410
|
-
var
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
448
|
+
// src/services/marketConfig.ts
|
|
449
|
+
var fetchMarketConfig = async ({ country }) => {
|
|
450
|
+
const countryCode = String(country ?? "").trim().toUpperCase() || "US";
|
|
451
|
+
const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
|
|
452
|
+
country: countryCode,
|
|
453
|
+
...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
|
|
454
|
+
clientId: DEFAULT_EQUINOX_CLIENT_ID,
|
|
455
|
+
shoppingContext: STOREFRONT_SHOPPING_CONTEXT
|
|
456
|
+
});
|
|
457
|
+
return configuration?.[EQUINOX_MARKETS_MAP] ?? null;
|
|
419
458
|
};
|
|
420
459
|
|
|
421
|
-
//
|
|
422
|
-
var
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
var
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config;
|
|
433
|
-
const pendingFragments = [];
|
|
434
|
-
let pendingFragmentsLength = 0;
|
|
435
|
-
let isFirstChunk = true;
|
|
436
|
-
let id;
|
|
437
|
-
let data = "";
|
|
438
|
-
let dataLines = 0;
|
|
439
|
-
let eventType;
|
|
440
|
-
let terminated = false;
|
|
441
|
-
let skippingLine = false;
|
|
442
|
-
let skipNextLineFeed = false;
|
|
443
|
-
function feed(chunk) {
|
|
444
|
-
if (terminated) {
|
|
445
|
-
throw new Error("Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.");
|
|
446
|
-
}
|
|
447
|
-
if (isFirstChunk) {
|
|
448
|
-
isFirstChunk = false;
|
|
449
|
-
if (chunk.charCodeAt(0) === 65279) {
|
|
450
|
-
chunk = chunk.slice(1);
|
|
451
|
-
} else if (chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191) {
|
|
452
|
-
chunk = chunk.slice(3);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (skippingLine || skipNextLineFeed) {
|
|
456
|
-
chunk = resumeAfterSkip(chunk);
|
|
457
|
-
if (!chunk) {
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
if (!pendingFragments.length) {
|
|
462
|
-
const trailing = processLines(chunk);
|
|
463
|
-
if (trailing !== "") {
|
|
464
|
-
storeTrailing(trailing);
|
|
465
|
-
}
|
|
466
|
-
checkBufferSize();
|
|
467
|
-
return;
|
|
468
|
-
}
|
|
469
|
-
if (chunk.indexOf("\n") === -1 && chunk.indexOf("\r") === -1) {
|
|
470
|
-
if (pendingFragmentsLength < MAX_FIELD_PREFIX_LENGTH) {
|
|
471
|
-
const head = pendingFragments.join("") + chunk.slice(0, MAX_FIELD_PREFIX_LENGTH - pendingFragmentsLength);
|
|
472
|
-
if (!shouldBufferTrailing(head)) {
|
|
473
|
-
pendingFragments.length = 0;
|
|
474
|
-
pendingFragmentsLength = 0;
|
|
475
|
-
skippingLine = true;
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
pendingFragments.push(chunk);
|
|
480
|
-
pendingFragmentsLength += chunk.length;
|
|
481
|
-
checkBufferSize();
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
pendingFragments.push(chunk);
|
|
485
|
-
const input = pendingFragments.join("");
|
|
486
|
-
pendingFragments.length = 0;
|
|
487
|
-
pendingFragmentsLength = 0;
|
|
488
|
-
storeTrailing(processLines(input));
|
|
489
|
-
checkBufferSize();
|
|
490
|
-
}
|
|
491
|
-
function resumeAfterSkip(chunk) {
|
|
492
|
-
if (chunk.length === 0) {
|
|
493
|
-
return chunk;
|
|
494
|
-
}
|
|
495
|
-
if (skipNextLineFeed) {
|
|
496
|
-
skipNextLineFeed = false;
|
|
497
|
-
return chunk.charCodeAt(0) === LF ? chunk.slice(1) : chunk;
|
|
498
|
-
}
|
|
499
|
-
const crIndex = chunk.indexOf("\r");
|
|
500
|
-
const lfIndex = chunk.indexOf("\n");
|
|
501
|
-
const lineEnd = crIndex === -1 ? lfIndex : lfIndex === -1 ? crIndex : crIndex < lfIndex ? crIndex : lfIndex;
|
|
502
|
-
if (lineEnd === -1) {
|
|
503
|
-
return "";
|
|
504
|
-
}
|
|
505
|
-
if (lineEnd === chunk.length - 1 && chunk.charCodeAt(lineEnd) === CR) {
|
|
506
|
-
skippingLine = false;
|
|
507
|
-
skipNextLineFeed = true;
|
|
508
|
-
return "";
|
|
509
|
-
}
|
|
510
|
-
skippingLine = false;
|
|
511
|
-
return chunk.slice(lineEnd + (chunk.charCodeAt(lineEnd) === CR && chunk.charCodeAt(lineEnd + 1) === LF ? 2 : 1));
|
|
512
|
-
}
|
|
513
|
-
function storeTrailing(trailing) {
|
|
514
|
-
if (!trailing)
|
|
515
|
-
return;
|
|
516
|
-
if (trailing.charCodeAt(trailing.length - 1) === CR) {
|
|
517
|
-
parseLine(trailing, 0, trailing.length - 1);
|
|
518
|
-
skipNextLineFeed = true;
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
if (shouldBufferTrailing(trailing)) {
|
|
522
|
-
pendingFragments.push(trailing);
|
|
523
|
-
pendingFragmentsLength = trailing.length;
|
|
524
|
-
return;
|
|
525
|
-
}
|
|
526
|
-
skippingLine = true;
|
|
527
|
-
}
|
|
528
|
-
function shouldBufferTrailing(trailing) {
|
|
529
|
-
const firstCharCode = trailing.charCodeAt(0);
|
|
530
|
-
return firstCharCode === 58 && !!onComment || firstCharCode === 100 && isPotentialField(trailing, "data") || firstCharCode === 101 && isPotentialField(trailing, "event") || firstCharCode === 105 && isPotentialField(trailing, "id") || firstCharCode === 114 && isPotentialField(trailing, "retry");
|
|
531
|
-
}
|
|
532
|
-
function checkBufferSize() {
|
|
533
|
-
if (maxBufferSize === void 0)
|
|
534
|
-
return;
|
|
535
|
-
if (pendingFragmentsLength + data.length <= maxBufferSize)
|
|
536
|
-
return;
|
|
537
|
-
terminated = true;
|
|
538
|
-
pendingFragments.length = 0;
|
|
539
|
-
pendingFragmentsLength = 0;
|
|
540
|
-
id = void 0;
|
|
541
|
-
data = "";
|
|
542
|
-
dataLines = 0;
|
|
543
|
-
eventType = void 0;
|
|
544
|
-
skippingLine = false;
|
|
545
|
-
skipNextLineFeed = false;
|
|
546
|
-
onError(new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
|
|
547
|
-
type: "max-buffer-size-exceeded"
|
|
548
|
-
}));
|
|
549
|
-
}
|
|
550
|
-
function processLines(chunk) {
|
|
551
|
-
let searchIndex = 0;
|
|
552
|
-
if (chunk.indexOf("\r") === -1) {
|
|
553
|
-
let lfIndex = chunk.indexOf("\n", searchIndex);
|
|
554
|
-
while (lfIndex !== -1) {
|
|
555
|
-
if (searchIndex === lfIndex) {
|
|
556
|
-
if (dataLines > 0) {
|
|
557
|
-
onEvent({ id, event: eventType, data });
|
|
558
|
-
}
|
|
559
|
-
id = void 0;
|
|
560
|
-
data = "";
|
|
561
|
-
dataLines = 0;
|
|
562
|
-
eventType = void 0;
|
|
563
|
-
searchIndex = lfIndex + 1;
|
|
564
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
const firstCharCode = chunk.charCodeAt(searchIndex);
|
|
568
|
-
if (isDataPrefix(chunk, searchIndex, firstCharCode)) {
|
|
569
|
-
const valueStart = chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5;
|
|
570
|
-
const value = chunk.slice(valueStart, lfIndex);
|
|
571
|
-
if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {
|
|
572
|
-
onEvent({ id, event: eventType, data: value });
|
|
573
|
-
id = void 0;
|
|
574
|
-
data = "";
|
|
575
|
-
eventType = void 0;
|
|
576
|
-
searchIndex = lfIndex + 2;
|
|
577
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
data = dataLines === 0 ? value : `${data}
|
|
581
|
-
${value}`;
|
|
582
|
-
dataLines++;
|
|
583
|
-
} else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {
|
|
584
|
-
eventType = chunk.slice(chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6, lfIndex) || void 0;
|
|
585
|
-
} else {
|
|
586
|
-
parseLine(chunk, searchIndex, lfIndex);
|
|
587
|
-
}
|
|
588
|
-
searchIndex = lfIndex + 1;
|
|
589
|
-
lfIndex = chunk.indexOf("\n", searchIndex);
|
|
590
|
-
}
|
|
591
|
-
return chunk.slice(searchIndex);
|
|
592
|
-
}
|
|
593
|
-
while (searchIndex < chunk.length) {
|
|
594
|
-
const crIndex = chunk.indexOf("\r", searchIndex);
|
|
595
|
-
const lfIndex = chunk.indexOf("\n", searchIndex);
|
|
596
|
-
let lineEnd = -1;
|
|
597
|
-
if (crIndex !== -1 && lfIndex !== -1) {
|
|
598
|
-
lineEnd = crIndex < lfIndex ? crIndex : lfIndex;
|
|
599
|
-
} else if (crIndex !== -1) {
|
|
600
|
-
if (crIndex === chunk.length - 1) {
|
|
601
|
-
lineEnd = -1;
|
|
602
|
-
} else {
|
|
603
|
-
lineEnd = crIndex;
|
|
604
|
-
}
|
|
605
|
-
} else if (lfIndex !== -1) {
|
|
606
|
-
lineEnd = lfIndex;
|
|
607
|
-
}
|
|
608
|
-
if (lineEnd === -1) {
|
|
609
|
-
break;
|
|
610
|
-
}
|
|
611
|
-
parseLine(chunk, searchIndex, lineEnd);
|
|
612
|
-
searchIndex = lineEnd + 1;
|
|
613
|
-
if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {
|
|
614
|
-
searchIndex++;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return chunk.slice(searchIndex);
|
|
618
|
-
}
|
|
619
|
-
function parseLine(chunk, start, end) {
|
|
620
|
-
if (start === end) {
|
|
621
|
-
dispatchEvent();
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const firstCharCode = chunk.charCodeAt(start);
|
|
625
|
-
if (isDataPrefix(chunk, start, firstCharCode)) {
|
|
626
|
-
const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5;
|
|
627
|
-
const value2 = chunk.slice(valueStart, end);
|
|
628
|
-
data = dataLines === 0 ? value2 : `${data}
|
|
629
|
-
${value2}`;
|
|
630
|
-
dataLines++;
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
if (isEventPrefix(chunk, start, firstCharCode)) {
|
|
634
|
-
eventType = chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || void 0;
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (firstCharCode === 105 && chunk.charCodeAt(start + 1) === 100 && chunk.charCodeAt(start + 2) === 58) {
|
|
638
|
-
const value2 = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end);
|
|
639
|
-
if (!value2.includes("\0"))
|
|
640
|
-
id = value2;
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (firstCharCode === 58) {
|
|
644
|
-
if (onComment) {
|
|
645
|
-
const line2 = chunk.slice(start, end);
|
|
646
|
-
onComment(line2.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1));
|
|
647
|
-
}
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const line = chunk.slice(start, end);
|
|
651
|
-
const fieldSeparatorIndex = line.indexOf(":");
|
|
652
|
-
if (fieldSeparatorIndex === -1) {
|
|
653
|
-
processField(line, "", line);
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const field = line.slice(0, fieldSeparatorIndex);
|
|
657
|
-
const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1;
|
|
658
|
-
const value = line.slice(fieldSeparatorIndex + offset);
|
|
659
|
-
processField(field, value, line);
|
|
660
|
-
}
|
|
661
|
-
function processField(field, value, line) {
|
|
662
|
-
switch (field) {
|
|
663
|
-
case "event":
|
|
664
|
-
eventType = value || void 0;
|
|
665
|
-
break;
|
|
666
|
-
case "data":
|
|
667
|
-
data = dataLines === 0 ? value : `${data}
|
|
668
|
-
${value}`;
|
|
669
|
-
dataLines++;
|
|
670
|
-
break;
|
|
671
|
-
case "id":
|
|
672
|
-
if (!value.includes("\0"))
|
|
673
|
-
id = value;
|
|
674
|
-
break;
|
|
675
|
-
case "retry":
|
|
676
|
-
if (/^\d+$/.test(value)) {
|
|
677
|
-
onRetry(parseInt(value, 10));
|
|
678
|
-
} else {
|
|
679
|
-
onError(new ParseError(`Invalid \`retry\` value: "${value}"`, {
|
|
680
|
-
type: "invalid-retry",
|
|
681
|
-
value,
|
|
682
|
-
line
|
|
683
|
-
}));
|
|
684
|
-
}
|
|
685
|
-
break;
|
|
686
|
-
default:
|
|
687
|
-
onError(new ParseError(`Unknown field "${field.length > 20 ? `${field.slice(0, 20)}\u2026` : field}"`, { type: "unknown-field", field, value, line }));
|
|
688
|
-
break;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
function dispatchEvent() {
|
|
692
|
-
if (dataLines > 0) {
|
|
693
|
-
onEvent({
|
|
694
|
-
id,
|
|
695
|
-
event: eventType,
|
|
696
|
-
data
|
|
697
|
-
});
|
|
698
|
-
}
|
|
699
|
-
id = void 0;
|
|
700
|
-
data = "";
|
|
701
|
-
dataLines = 0;
|
|
702
|
-
eventType = void 0;
|
|
703
|
-
}
|
|
704
|
-
function reset(options = {}) {
|
|
705
|
-
if (options.consume && pendingFragments.length > 0) {
|
|
706
|
-
const incompleteLine = pendingFragments.join("");
|
|
707
|
-
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
708
|
-
}
|
|
709
|
-
isFirstChunk = true;
|
|
710
|
-
id = void 0;
|
|
711
|
-
data = "";
|
|
712
|
-
dataLines = 0;
|
|
713
|
-
eventType = void 0;
|
|
714
|
-
pendingFragments.length = 0;
|
|
715
|
-
pendingFragmentsLength = 0;
|
|
716
|
-
terminated = false;
|
|
717
|
-
skippingLine = false;
|
|
718
|
-
skipNextLineFeed = false;
|
|
719
|
-
}
|
|
720
|
-
return { feed, reset };
|
|
721
|
-
}
|
|
722
|
-
function isDataPrefix(chunk, i, firstCharCode) {
|
|
723
|
-
return firstCharCode === 100 && chunk.charCodeAt(i + 1) === 97 && chunk.charCodeAt(i + 2) === 116 && chunk.charCodeAt(i + 3) === 97 && chunk.charCodeAt(i + 4) === 58;
|
|
724
|
-
}
|
|
725
|
-
function isEventPrefix(chunk, i, firstCharCode) {
|
|
726
|
-
return firstCharCode === 101 && chunk.charCodeAt(i + 1) === 118 && chunk.charCodeAt(i + 2) === 101 && chunk.charCodeAt(i + 3) === 110 && chunk.charCodeAt(i + 4) === 116 && chunk.charCodeAt(i + 5) === 58;
|
|
727
|
-
}
|
|
728
|
-
function isPotentialField(line, field) {
|
|
729
|
-
let i = 1;
|
|
730
|
-
while (i < line.length && i < field.length) {
|
|
731
|
-
if (line.charCodeAt(i) !== field.charCodeAt(i)) {
|
|
732
|
-
return false;
|
|
733
|
-
}
|
|
734
|
-
i++;
|
|
735
|
-
}
|
|
736
|
-
return line.length <= field.length || line.charCodeAt(field.length) === 58;
|
|
737
|
-
}
|
|
460
|
+
// src/utils/gtmConstants.ts
|
|
461
|
+
var SEARCH_GTM_EVENTS = {
|
|
462
|
+
CHAT_ENTRY_POINT: "Begin_chat",
|
|
463
|
+
PRODUCT_SEARCH: "ProductSearch"};
|
|
464
|
+
var SEARCH_GTM_SEARCH_TYPES = {
|
|
465
|
+
SEMANTIC: "semantic search"};
|
|
466
|
+
var SEARCH_GTM_USER_STATUS = {
|
|
467
|
+
GUEST: "guest",
|
|
468
|
+
LOGGED_IN: "logged_in"
|
|
469
|
+
};
|
|
470
|
+
var GTM_FALLBACK_NA = "NA";
|
|
738
471
|
|
|
739
|
-
// src/
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
function findProductsBlockStart(content, openingFenceIndex) {
|
|
743
|
-
let blockStart = openingFenceIndex + PRODUCTS_OPENING_FENCE.length;
|
|
744
|
-
while (blockStart < content.length && (content[blockStart] === " " || content[blockStart] === " " || content[blockStart] === "\r")) {
|
|
745
|
-
blockStart += 1;
|
|
746
|
-
}
|
|
747
|
-
return content[blockStart] === "\n" ? blockStart + 1 : null;
|
|
748
|
-
}
|
|
749
|
-
function isStandaloneClosingFence(content, fenceEnd) {
|
|
750
|
-
const lineEnd = content.indexOf("\n", fenceEnd);
|
|
751
|
-
const suffixEnd = lineEnd === -1 ? content.length : lineEnd;
|
|
752
|
-
return content.slice(fenceEnd, suffixEnd).trim() === "";
|
|
753
|
-
}
|
|
754
|
-
function findClosingFence(content, fromIndex) {
|
|
755
|
-
let candidate = content.indexOf(MARKDOWN_CLOSING_FENCE, fromIndex);
|
|
756
|
-
while (candidate !== -1) {
|
|
757
|
-
const fenceEnd = candidate + MARKDOWN_CLOSING_FENCE.length;
|
|
758
|
-
if (isStandaloneClosingFence(content, fenceEnd)) {
|
|
759
|
-
return candidate;
|
|
760
|
-
}
|
|
761
|
-
candidate = content.indexOf(MARKDOWN_CLOSING_FENCE, fenceEnd);
|
|
762
|
-
}
|
|
763
|
-
return -1;
|
|
764
|
-
}
|
|
765
|
-
function isRecord(value) {
|
|
766
|
-
return value !== null && typeof value === "object";
|
|
767
|
-
}
|
|
768
|
-
function getRenderProductListItems(event) {
|
|
769
|
-
if (event["type"] !== "render" || event["pattern"] !== "product_list") {
|
|
770
|
-
return [];
|
|
771
|
-
}
|
|
772
|
-
const data = event["data"];
|
|
773
|
-
if (!isRecord(data) || !Array.isArray(data["items"])) {
|
|
774
|
-
return [];
|
|
775
|
-
}
|
|
776
|
-
return data["items"].filter(isRecord);
|
|
777
|
-
}
|
|
778
|
-
function extractProductsFromMarkdown(content) {
|
|
779
|
-
const products = [];
|
|
780
|
-
const summaryParts = [];
|
|
781
|
-
let summaryCursor = 0;
|
|
782
|
-
let searchCursor = 0;
|
|
783
|
-
while (searchCursor < content.length) {
|
|
784
|
-
const start = content.indexOf(PRODUCTS_OPENING_FENCE, searchCursor);
|
|
785
|
-
if (start === -1) break;
|
|
786
|
-
const blockStart = findProductsBlockStart(content, start);
|
|
787
|
-
if (blockStart === null) {
|
|
788
|
-
searchCursor = start + PRODUCTS_OPENING_FENCE.length;
|
|
789
|
-
continue;
|
|
790
|
-
}
|
|
791
|
-
const nextOpeningFence = content.indexOf(
|
|
792
|
-
PRODUCTS_OPENING_FENCE,
|
|
793
|
-
blockStart
|
|
794
|
-
);
|
|
795
|
-
const end = findClosingFence(content, blockStart);
|
|
796
|
-
if (end === -1) break;
|
|
797
|
-
if (nextOpeningFence !== -1 && nextOpeningFence < end) {
|
|
798
|
-
searchCursor = nextOpeningFence;
|
|
799
|
-
continue;
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
const parsed = JSON.parse(content.slice(blockStart, end));
|
|
803
|
-
if (Array.isArray(parsed)) {
|
|
804
|
-
products.push(...parsed);
|
|
805
|
-
}
|
|
806
|
-
} catch (e) {
|
|
807
|
-
console.error("Failed to parse products block:", e);
|
|
808
|
-
}
|
|
809
|
-
summaryParts.push(content.slice(summaryCursor, start));
|
|
810
|
-
summaryCursor = end + MARKDOWN_CLOSING_FENCE.length;
|
|
811
|
-
searchCursor = summaryCursor;
|
|
812
|
-
}
|
|
813
|
-
summaryParts.push(content.slice(summaryCursor));
|
|
814
|
-
return { summary: summaryParts.join("").trim(), products };
|
|
815
|
-
}
|
|
816
|
-
function createChatService(options) {
|
|
817
|
-
const {
|
|
818
|
-
conversationsUrl = DEFAULT_AI_CHAT_CONVERSATIONS_URL,
|
|
819
|
-
messagesUrl = DEFAULT_AI_CHAT_MESSAGES_URL,
|
|
820
|
-
salesAiAppKey,
|
|
821
|
-
salesAiParentOrigin,
|
|
822
|
-
market,
|
|
823
|
-
language,
|
|
824
|
-
fetchImpl: fetchImplOption
|
|
825
|
-
} = options;
|
|
826
|
-
let fetchImpl;
|
|
827
|
-
if (fetchImplOption !== void 0) {
|
|
828
|
-
fetchImpl = fetchImplOption;
|
|
829
|
-
} else if (typeof fetch === "function") {
|
|
830
|
-
fetchImpl = fetch.bind(globalThis);
|
|
831
|
-
} else {
|
|
832
|
-
fetchImpl = () => {
|
|
833
|
-
throw new Error("ChatService: no fetch implementation available");
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
function buildHeaders(extra, includeJsonContentType = true) {
|
|
837
|
-
const headers = {
|
|
838
|
-
...extra
|
|
839
|
-
};
|
|
840
|
-
if (includeJsonContentType) {
|
|
841
|
-
headers["Content-Type"] = "application/json";
|
|
842
|
-
}
|
|
843
|
-
const accessToken = typeof window !== "undefined" ? window.localStorage?.getItem("accessToken") : null;
|
|
844
|
-
if (accessToken) {
|
|
845
|
-
headers["Authorization"] = `Bearer ${accessToken}`;
|
|
846
|
-
}
|
|
847
|
-
if (salesAiAppKey) {
|
|
848
|
-
headers["X-Sales-AI-App-Key"] = salesAiAppKey;
|
|
849
|
-
}
|
|
850
|
-
if (salesAiParentOrigin) {
|
|
851
|
-
headers["X-Sales-AI-Parent-Origin"] = salesAiParentOrigin;
|
|
852
|
-
}
|
|
853
|
-
return headers;
|
|
854
|
-
}
|
|
855
|
-
return {
|
|
856
|
-
async createConversation() {
|
|
857
|
-
const res = await fetchImpl(conversationsUrl, {
|
|
858
|
-
method: "POST",
|
|
859
|
-
headers: buildHeaders(void 0, false)
|
|
860
|
-
});
|
|
861
|
-
if (!res.ok) {
|
|
862
|
-
throw new Error(
|
|
863
|
-
`ChatService.createConversation: ${res.status} ${res.statusText}`
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
|
-
const data = await res.json();
|
|
867
|
-
const id = data["id"] ?? data["conversation_id"];
|
|
868
|
-
if (typeof id !== "number") {
|
|
869
|
-
throw new TypeError(
|
|
870
|
-
"ChatService.createConversation: response missing numeric id"
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
return id;
|
|
874
|
-
},
|
|
875
|
-
async sendMessage(conversationId, content, onChunk) {
|
|
876
|
-
const requestBody = {
|
|
877
|
-
conversation_id: conversationId,
|
|
878
|
-
content
|
|
879
|
-
};
|
|
880
|
-
if (market || language) {
|
|
881
|
-
requestBody.scope_context = {
|
|
882
|
-
market: market ?? "US",
|
|
883
|
-
language: language ?? "en"
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
requestBody.client_capabilities = {
|
|
887
|
-
patterns: {
|
|
888
|
-
product_list: [1],
|
|
889
|
-
follow_up: [1]
|
|
890
|
-
}
|
|
891
|
-
};
|
|
892
|
-
const res = await fetchImpl(messagesUrl, {
|
|
893
|
-
method: "POST",
|
|
894
|
-
headers: buildHeaders({ Accept: "text/event-stream" }),
|
|
895
|
-
body: JSON.stringify(requestBody)
|
|
896
|
-
});
|
|
897
|
-
if (!res.ok) {
|
|
898
|
-
throw new Error(
|
|
899
|
-
`ChatService.sendMessage: ${res.status} ${res.statusText}`
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
let accumulated = "";
|
|
903
|
-
const renderedProducts = [];
|
|
904
|
-
const parser = createParser({
|
|
905
|
-
onEvent(event) {
|
|
906
|
-
const data = event.data;
|
|
907
|
-
if (!data || data === "[DONE]") return;
|
|
908
|
-
try {
|
|
909
|
-
const parsed = JSON.parse(data);
|
|
910
|
-
const parsedType = parsed["type"];
|
|
911
|
-
const type = typeof parsedType === "string" ? parsedType : "";
|
|
912
|
-
renderedProducts.push(
|
|
913
|
-
...getRenderProductListItems(parsed)
|
|
914
|
-
);
|
|
915
|
-
if (type === "content" && typeof parsed["text"] === "string") {
|
|
916
|
-
const text = parsed["text"];
|
|
917
|
-
accumulated += text;
|
|
918
|
-
onChunk?.(type, text);
|
|
919
|
-
} else if (type === "status" && typeof parsed["message"] === "string") {
|
|
920
|
-
const message = parsed["message"];
|
|
921
|
-
onChunk?.(type, message);
|
|
922
|
-
}
|
|
923
|
-
} catch {
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
});
|
|
927
|
-
const body = res.body;
|
|
928
|
-
if (body && typeof body.getReader === "function") {
|
|
929
|
-
const reader = body.getReader();
|
|
930
|
-
const decoder = new TextDecoder();
|
|
931
|
-
while (true) {
|
|
932
|
-
const { value, done } = await reader.read();
|
|
933
|
-
if (done) break;
|
|
934
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
935
|
-
}
|
|
936
|
-
parser.feed(decoder.decode());
|
|
937
|
-
} else {
|
|
938
|
-
const rawBody = await res.text();
|
|
939
|
-
parser.feed(rawBody);
|
|
940
|
-
}
|
|
941
|
-
const { summary, products } = extractProductsFromMarkdown(accumulated);
|
|
942
|
-
const finalProducts = renderedProducts.length > 0 ? renderedProducts : products;
|
|
943
|
-
return {
|
|
944
|
-
summary,
|
|
945
|
-
products: finalProducts,
|
|
946
|
-
rawContent: accumulated
|
|
947
|
-
};
|
|
948
|
-
}
|
|
949
|
-
};
|
|
472
|
+
// src/utils/navigationTarget.ts
|
|
473
|
+
function toNavigationTarget(path) {
|
|
474
|
+
return /^https?:\/\//i.test(path) ? { type: "external", url: path } : { type: "internal", path };
|
|
950
475
|
}
|
|
951
476
|
|
|
952
477
|
// src/utils/catalogHelpers.ts
|
|
@@ -964,9 +489,6 @@ var CART_DISABLED_PRODUCT_STATUSES = /* @__PURE__ */ new Set([
|
|
|
964
489
|
function normalizeProductStatusValue(value) {
|
|
965
490
|
return String(value ?? "").trim().toLowerCase().replace(/\s+/g, "");
|
|
966
491
|
}
|
|
967
|
-
function isSellableProductStatus(productStatus) {
|
|
968
|
-
return normalizeProductStatusValue(productStatus) === "sellable";
|
|
969
|
-
}
|
|
970
492
|
function resolveSkuStatusValues(suggestionOption) {
|
|
971
493
|
return [
|
|
972
494
|
suggestionOption["productStatus"],
|
|
@@ -1211,14 +733,6 @@ function getVisibleSuggestions(opts) {
|
|
|
1211
733
|
}
|
|
1212
734
|
return opts.suggestions;
|
|
1213
735
|
}
|
|
1214
|
-
function getSeeMoreResultsPayload(query, totalResultsCount, suggestions) {
|
|
1215
|
-
return {
|
|
1216
|
-
query,
|
|
1217
|
-
source: "autocomplete-footer",
|
|
1218
|
-
totalResultsCount,
|
|
1219
|
-
visibleResultsCount: suggestions.length
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
736
|
|
|
1223
737
|
// src/hooks/useSearchSuggestions.ts
|
|
1224
738
|
function normalizeSuggestionsResponse(response) {
|
|
@@ -1249,26 +763,28 @@ function useSearchSuggestions({
|
|
|
1249
763
|
service,
|
|
1250
764
|
enabled = true
|
|
1251
765
|
}) {
|
|
1252
|
-
const [query, setQuery] =
|
|
1253
|
-
const [suggestions, setSuggestions] =
|
|
1254
|
-
const [didYouMean, setDidYouMean] =
|
|
1255
|
-
const [loading, setLoading] =
|
|
1256
|
-
const [emptyFetched, setEmptyFetched] =
|
|
1257
|
-
const [suggestionFetchFailed, setSuggestionFetchFailed] =
|
|
1258
|
-
const [hasMoreResults, setHasMoreResults] =
|
|
1259
|
-
const [totalResultsCount, setTotalResultsCount] =
|
|
1260
|
-
const
|
|
1261
|
-
const
|
|
1262
|
-
const
|
|
766
|
+
const [query, setQuery] = react.useState("");
|
|
767
|
+
const [suggestions, setSuggestions] = react.useState([]);
|
|
768
|
+
const [didYouMean, setDidYouMean] = react.useState([]);
|
|
769
|
+
const [loading, setLoading] = react.useState(false);
|
|
770
|
+
const [emptyFetched, setEmptyFetched] = react.useState(false);
|
|
771
|
+
const [suggestionFetchFailed, setSuggestionFetchFailed] = react.useState(false);
|
|
772
|
+
const [hasMoreResults, setHasMoreResults] = react.useState(false);
|
|
773
|
+
const [totalResultsCount, setTotalResultsCount] = react.useState(0);
|
|
774
|
+
const [settledQuery, setSettledQuery] = react.useState("");
|
|
775
|
+
const fetchDebounced = react.useRef(null);
|
|
776
|
+
const latestRequestId = react.useRef(0);
|
|
777
|
+
const resetSuggestionsState = react.useCallback(() => {
|
|
1263
778
|
setSuggestions([]);
|
|
1264
779
|
setDidYouMean([]);
|
|
1265
780
|
setEmptyFetched(false);
|
|
1266
781
|
setSuggestionFetchFailed(false);
|
|
1267
782
|
setHasMoreResults(false);
|
|
1268
783
|
setTotalResultsCount(0);
|
|
784
|
+
setSettledQuery("");
|
|
1269
785
|
setLoading(false);
|
|
1270
786
|
}, []);
|
|
1271
|
-
const updateQuery =
|
|
787
|
+
const updateQuery = react.useCallback(
|
|
1272
788
|
(nextQuery) => {
|
|
1273
789
|
const hasNextQuery = Boolean(nextQuery.trim());
|
|
1274
790
|
setEmptyFetched(false);
|
|
@@ -1278,11 +794,11 @@ function useSearchSuggestions({
|
|
|
1278
794
|
},
|
|
1279
795
|
[enabled]
|
|
1280
796
|
);
|
|
1281
|
-
const clearQuery =
|
|
797
|
+
const clearQuery = react.useCallback(() => {
|
|
1282
798
|
resetSuggestionsState();
|
|
1283
799
|
setQuery("");
|
|
1284
800
|
}, [resetSuggestionsState]);
|
|
1285
|
-
const runFetch =
|
|
801
|
+
const runFetch = react.useCallback(
|
|
1286
802
|
async (nextQuery) => {
|
|
1287
803
|
const trimmedQuery = nextQuery.trim();
|
|
1288
804
|
const requestId = latestRequestId.current;
|
|
@@ -1316,6 +832,7 @@ function useSearchSuggestions({
|
|
|
1316
832
|
);
|
|
1317
833
|
setTotalResultsCount(effectiveTotal);
|
|
1318
834
|
setSuggestionFetchFailed(false);
|
|
835
|
+
setSettledQuery(trimmedQuery);
|
|
1319
836
|
setEmptyFetched(
|
|
1320
837
|
hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
|
|
1321
838
|
);
|
|
@@ -1329,6 +846,7 @@ function useSearchSuggestions({
|
|
|
1329
846
|
setSuggestionFetchFailed(true);
|
|
1330
847
|
setHasMoreResults(false);
|
|
1331
848
|
setTotalResultsCount(0);
|
|
849
|
+
setSettledQuery(trimmedQuery);
|
|
1332
850
|
} finally {
|
|
1333
851
|
if (requestId === latestRequestId.current) {
|
|
1334
852
|
setLoading(false);
|
|
@@ -1337,7 +855,7 @@ function useSearchSuggestions({
|
|
|
1337
855
|
},
|
|
1338
856
|
[enabled, service, resetSuggestionsState]
|
|
1339
857
|
);
|
|
1340
|
-
|
|
858
|
+
react.useEffect(() => {
|
|
1341
859
|
if (!enabled) {
|
|
1342
860
|
resetSuggestionsState();
|
|
1343
861
|
return void 0;
|
|
@@ -1363,6 +881,7 @@ function useSearchSuggestions({
|
|
|
1363
881
|
didYouMean,
|
|
1364
882
|
loading,
|
|
1365
883
|
query,
|
|
884
|
+
settledQuery,
|
|
1366
885
|
setQuery,
|
|
1367
886
|
suggestionFetchFailed,
|
|
1368
887
|
suggestions,
|
|
@@ -1371,382 +890,6 @@ function useSearchSuggestions({
|
|
|
1371
890
|
};
|
|
1372
891
|
}
|
|
1373
892
|
|
|
1374
|
-
// node_modules/zustand/esm/vanilla.mjs
|
|
1375
|
-
var createStoreImpl = (createState) => {
|
|
1376
|
-
let state;
|
|
1377
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
1378
|
-
const setState = (partial, replace) => {
|
|
1379
|
-
const nextState = typeof partial === "function" ? partial(state) : partial;
|
|
1380
|
-
if (!Object.is(nextState, state)) {
|
|
1381
|
-
const previousState = state;
|
|
1382
|
-
state = (replace != null ? replace : typeof nextState !== "object" || nextState === null) ? nextState : Object.assign({}, state, nextState);
|
|
1383
|
-
listeners.forEach((listener) => listener(state, previousState));
|
|
1384
|
-
}
|
|
1385
|
-
};
|
|
1386
|
-
const getState = () => state;
|
|
1387
|
-
const getInitialState = () => initialState;
|
|
1388
|
-
const subscribe = (listener) => {
|
|
1389
|
-
listeners.add(listener);
|
|
1390
|
-
return () => listeners.delete(listener);
|
|
1391
|
-
};
|
|
1392
|
-
const api = { setState, getState, getInitialState, subscribe };
|
|
1393
|
-
const initialState = state = createState(setState, getState, api);
|
|
1394
|
-
return api;
|
|
1395
|
-
};
|
|
1396
|
-
var createStore = (createState) => createState ? createStoreImpl(createState) : createStoreImpl;
|
|
1397
|
-
var identity = (arg) => arg;
|
|
1398
|
-
function useStore(api, selector = identity) {
|
|
1399
|
-
const slice = React__default.default.useSyncExternalStore(
|
|
1400
|
-
api.subscribe,
|
|
1401
|
-
() => selector(api.getState()),
|
|
1402
|
-
() => selector(api.getInitialState())
|
|
1403
|
-
);
|
|
1404
|
-
React__default.default.useDebugValue(slice);
|
|
1405
|
-
return slice;
|
|
1406
|
-
}
|
|
1407
|
-
var createImpl = (createState) => {
|
|
1408
|
-
const api = createStore(createState);
|
|
1409
|
-
const useBoundStore = (selector) => useStore(api, selector);
|
|
1410
|
-
Object.assign(useBoundStore, api);
|
|
1411
|
-
return useBoundStore;
|
|
1412
|
-
};
|
|
1413
|
-
var create = (createState) => createImpl;
|
|
1414
|
-
|
|
1415
|
-
// node_modules/zustand/esm/middleware.mjs
|
|
1416
|
-
function createJSONStorage(getStorage, options) {
|
|
1417
|
-
let storage;
|
|
1418
|
-
try {
|
|
1419
|
-
storage = getStorage();
|
|
1420
|
-
} catch (e) {
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
const persistStorage = {
|
|
1424
|
-
getItem: (name) => {
|
|
1425
|
-
var _a;
|
|
1426
|
-
const parse = (str2) => {
|
|
1427
|
-
if (str2 === null) {
|
|
1428
|
-
return null;
|
|
1429
|
-
}
|
|
1430
|
-
return JSON.parse(str2, void 0 );
|
|
1431
|
-
};
|
|
1432
|
-
const str = (_a = storage.getItem(name)) != null ? _a : null;
|
|
1433
|
-
if (str instanceof Promise) {
|
|
1434
|
-
return str.then(parse);
|
|
1435
|
-
}
|
|
1436
|
-
return parse(str);
|
|
1437
|
-
},
|
|
1438
|
-
setItem: (name, newValue) => storage.setItem(
|
|
1439
|
-
name,
|
|
1440
|
-
JSON.stringify(newValue, void 0 )
|
|
1441
|
-
),
|
|
1442
|
-
removeItem: (name) => storage.removeItem(name)
|
|
1443
|
-
};
|
|
1444
|
-
return persistStorage;
|
|
1445
|
-
}
|
|
1446
|
-
var toThenable = (fn) => (input) => {
|
|
1447
|
-
try {
|
|
1448
|
-
const result = fn(input);
|
|
1449
|
-
if (result instanceof Promise) {
|
|
1450
|
-
return result;
|
|
1451
|
-
}
|
|
1452
|
-
return {
|
|
1453
|
-
then(onFulfilled) {
|
|
1454
|
-
return toThenable(onFulfilled)(result);
|
|
1455
|
-
},
|
|
1456
|
-
catch(_onRejected) {
|
|
1457
|
-
return this;
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
} catch (e) {
|
|
1461
|
-
return {
|
|
1462
|
-
then(_onFulfilled) {
|
|
1463
|
-
return this;
|
|
1464
|
-
},
|
|
1465
|
-
catch(onRejected) {
|
|
1466
|
-
return toThenable(onRejected)(e);
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
};
|
|
1471
|
-
var persistImpl = (config, baseOptions) => (set, get, api) => {
|
|
1472
|
-
let options = {
|
|
1473
|
-
storage: createJSONStorage(() => localStorage),
|
|
1474
|
-
partialize: (state) => state,
|
|
1475
|
-
version: 0,
|
|
1476
|
-
merge: (persistedState, currentState) => ({
|
|
1477
|
-
...currentState,
|
|
1478
|
-
...persistedState
|
|
1479
|
-
}),
|
|
1480
|
-
...baseOptions
|
|
1481
|
-
};
|
|
1482
|
-
let hasHydrated = false;
|
|
1483
|
-
const hydrationListeners = /* @__PURE__ */ new Set();
|
|
1484
|
-
const finishHydrationListeners = /* @__PURE__ */ new Set();
|
|
1485
|
-
let storage = options.storage;
|
|
1486
|
-
if (!storage) {
|
|
1487
|
-
return config(
|
|
1488
|
-
(...args) => {
|
|
1489
|
-
console.warn(
|
|
1490
|
-
`[zustand persist middleware] Unable to update item '${options.name}', the given storage is currently unavailable.`
|
|
1491
|
-
);
|
|
1492
|
-
set(...args);
|
|
1493
|
-
},
|
|
1494
|
-
get,
|
|
1495
|
-
api
|
|
1496
|
-
);
|
|
1497
|
-
}
|
|
1498
|
-
const setItem = () => {
|
|
1499
|
-
const state = options.partialize({ ...get() });
|
|
1500
|
-
return storage.setItem(options.name, {
|
|
1501
|
-
state,
|
|
1502
|
-
version: options.version
|
|
1503
|
-
});
|
|
1504
|
-
};
|
|
1505
|
-
const savedSetState = api.setState;
|
|
1506
|
-
api.setState = (state, replace) => {
|
|
1507
|
-
savedSetState(state, replace);
|
|
1508
|
-
void setItem();
|
|
1509
|
-
};
|
|
1510
|
-
const configResult = config(
|
|
1511
|
-
(...args) => {
|
|
1512
|
-
set(...args);
|
|
1513
|
-
void setItem();
|
|
1514
|
-
},
|
|
1515
|
-
get,
|
|
1516
|
-
api
|
|
1517
|
-
);
|
|
1518
|
-
api.getInitialState = () => configResult;
|
|
1519
|
-
let stateFromStorage;
|
|
1520
|
-
const hydrate = () => {
|
|
1521
|
-
var _a, _b;
|
|
1522
|
-
if (!storage) return;
|
|
1523
|
-
hasHydrated = false;
|
|
1524
|
-
hydrationListeners.forEach((cb) => {
|
|
1525
|
-
var _a2;
|
|
1526
|
-
return cb((_a2 = get()) != null ? _a2 : configResult);
|
|
1527
|
-
});
|
|
1528
|
-
const postRehydrationCallback = ((_b = options.onRehydrateStorage) == null ? void 0 : _b.call(options, (_a = get()) != null ? _a : configResult)) || void 0;
|
|
1529
|
-
return toThenable(storage.getItem.bind(storage))(options.name).then((deserializedStorageValue) => {
|
|
1530
|
-
if (deserializedStorageValue) {
|
|
1531
|
-
if (typeof deserializedStorageValue.version === "number" && deserializedStorageValue.version !== options.version) {
|
|
1532
|
-
if (options.migrate) {
|
|
1533
|
-
return [
|
|
1534
|
-
true,
|
|
1535
|
-
options.migrate(
|
|
1536
|
-
deserializedStorageValue.state,
|
|
1537
|
-
deserializedStorageValue.version
|
|
1538
|
-
)
|
|
1539
|
-
];
|
|
1540
|
-
}
|
|
1541
|
-
console.error(
|
|
1542
|
-
`State loaded from storage couldn't be migrated since no migrate function was provided`
|
|
1543
|
-
);
|
|
1544
|
-
} else {
|
|
1545
|
-
return [false, deserializedStorageValue.state];
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
return [false, void 0];
|
|
1549
|
-
}).then((migrationResult) => {
|
|
1550
|
-
var _a2;
|
|
1551
|
-
const [migrated, migratedState] = migrationResult;
|
|
1552
|
-
stateFromStorage = options.merge(
|
|
1553
|
-
migratedState,
|
|
1554
|
-
(_a2 = get()) != null ? _a2 : configResult
|
|
1555
|
-
);
|
|
1556
|
-
set(stateFromStorage, true);
|
|
1557
|
-
if (migrated) {
|
|
1558
|
-
return setItem();
|
|
1559
|
-
}
|
|
1560
|
-
}).then(() => {
|
|
1561
|
-
postRehydrationCallback == null ? void 0 : postRehydrationCallback(stateFromStorage, void 0);
|
|
1562
|
-
stateFromStorage = get();
|
|
1563
|
-
hasHydrated = true;
|
|
1564
|
-
finishHydrationListeners.forEach((cb) => cb(stateFromStorage));
|
|
1565
|
-
}).catch((e) => {
|
|
1566
|
-
postRehydrationCallback == null ? void 0 : postRehydrationCallback(void 0, e);
|
|
1567
|
-
});
|
|
1568
|
-
};
|
|
1569
|
-
api.persist = {
|
|
1570
|
-
setOptions: (newOptions) => {
|
|
1571
|
-
options = {
|
|
1572
|
-
...options,
|
|
1573
|
-
...newOptions
|
|
1574
|
-
};
|
|
1575
|
-
if (newOptions.storage) {
|
|
1576
|
-
storage = newOptions.storage;
|
|
1577
|
-
}
|
|
1578
|
-
},
|
|
1579
|
-
clearStorage: () => {
|
|
1580
|
-
storage == null ? void 0 : storage.removeItem(options.name);
|
|
1581
|
-
},
|
|
1582
|
-
getOptions: () => options,
|
|
1583
|
-
rehydrate: () => hydrate(),
|
|
1584
|
-
hasHydrated: () => hasHydrated,
|
|
1585
|
-
onHydrate: (cb) => {
|
|
1586
|
-
hydrationListeners.add(cb);
|
|
1587
|
-
return () => {
|
|
1588
|
-
hydrationListeners.delete(cb);
|
|
1589
|
-
};
|
|
1590
|
-
},
|
|
1591
|
-
onFinishHydration: (cb) => {
|
|
1592
|
-
finishHydrationListeners.add(cb);
|
|
1593
|
-
return () => {
|
|
1594
|
-
finishHydrationListeners.delete(cb);
|
|
1595
|
-
};
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
if (!options.skipHydration) {
|
|
1599
|
-
hydrate();
|
|
1600
|
-
}
|
|
1601
|
-
return stateFromStorage || configResult;
|
|
1602
|
-
};
|
|
1603
|
-
var persist = persistImpl;
|
|
1604
|
-
|
|
1605
|
-
// src/store/searchHistoryStore.ts
|
|
1606
|
-
var MAX_HISTORY = 10;
|
|
1607
|
-
var useSearchHistoryStore = create()(
|
|
1608
|
-
persist(
|
|
1609
|
-
(set) => ({
|
|
1610
|
-
entries: [],
|
|
1611
|
-
addEntry: (entry) => {
|
|
1612
|
-
const trimmed = entry.query.trim();
|
|
1613
|
-
if (!trimmed) return;
|
|
1614
|
-
set((state) => {
|
|
1615
|
-
const deduped = state.entries.filter(
|
|
1616
|
-
(e) => e.query.toLowerCase() !== trimmed.toLowerCase()
|
|
1617
|
-
);
|
|
1618
|
-
const newEntry = {
|
|
1619
|
-
...entry,
|
|
1620
|
-
query: trimmed,
|
|
1621
|
-
timestamp: Date.now()
|
|
1622
|
-
};
|
|
1623
|
-
return {
|
|
1624
|
-
entries: [newEntry, ...deduped].slice(0, MAX_HISTORY)
|
|
1625
|
-
};
|
|
1626
|
-
});
|
|
1627
|
-
},
|
|
1628
|
-
addQuery: (query) => {
|
|
1629
|
-
const trimmed = query.trim();
|
|
1630
|
-
if (!trimmed) return;
|
|
1631
|
-
set((state) => {
|
|
1632
|
-
const deduped = state.entries.filter(
|
|
1633
|
-
(e) => e.query.toLowerCase() !== trimmed.toLowerCase()
|
|
1634
|
-
);
|
|
1635
|
-
return {
|
|
1636
|
-
entries: [
|
|
1637
|
-
{
|
|
1638
|
-
query: trimmed,
|
|
1639
|
-
isAI: false,
|
|
1640
|
-
timestamp: Date.now()
|
|
1641
|
-
},
|
|
1642
|
-
...deduped
|
|
1643
|
-
].slice(0, MAX_HISTORY)
|
|
1644
|
-
};
|
|
1645
|
-
});
|
|
1646
|
-
},
|
|
1647
|
-
removeEntry: (query) => set((state) => ({
|
|
1648
|
-
entries: state.entries.filter((e) => e.query !== query)
|
|
1649
|
-
})),
|
|
1650
|
-
clearAll: () => set({ entries: [] })
|
|
1651
|
-
}),
|
|
1652
|
-
{
|
|
1653
|
-
name: "ai_search_recent_queries",
|
|
1654
|
-
version: 1,
|
|
1655
|
-
storage: createJSONStorage(
|
|
1656
|
-
() => typeof window !== "undefined" ? window.sessionStorage : {}
|
|
1657
|
-
),
|
|
1658
|
-
// Migrate from v0 (queries: string[]) to v1 (entries: SearchHistoryEntry[])
|
|
1659
|
-
migrate(persistedState, version) {
|
|
1660
|
-
if (version === 0) {
|
|
1661
|
-
const old = persistedState;
|
|
1662
|
-
if (Array.isArray(old?.queries)) {
|
|
1663
|
-
return {
|
|
1664
|
-
entries: old.queries.map((q) => ({
|
|
1665
|
-
query: q,
|
|
1666
|
-
isAI: false,
|
|
1667
|
-
timestamp: Date.now()
|
|
1668
|
-
}))
|
|
1669
|
-
};
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
return persistedState;
|
|
1673
|
-
}
|
|
1674
|
-
}
|
|
1675
|
-
)
|
|
1676
|
-
);
|
|
1677
|
-
|
|
1678
|
-
// src/hooks/useSearchHistory.ts
|
|
1679
|
-
function useSearchHistory() {
|
|
1680
|
-
const entries = useSearchHistoryStore((s) => s.entries);
|
|
1681
|
-
const addEntry = useSearchHistoryStore((s) => s.addEntry);
|
|
1682
|
-
const addQuery = useSearchHistoryStore((s) => s.addQuery);
|
|
1683
|
-
const removeEntry = useSearchHistoryStore((s) => s.removeEntry);
|
|
1684
|
-
const clearAll = useSearchHistoryStore((s) => s.clearAll);
|
|
1685
|
-
return {
|
|
1686
|
-
entries,
|
|
1687
|
-
queries: entries.map((e) => e.query),
|
|
1688
|
-
addEntry,
|
|
1689
|
-
addQuery,
|
|
1690
|
-
removeEntry,
|
|
1691
|
-
clearAll
|
|
1692
|
-
};
|
|
1693
|
-
}
|
|
1694
|
-
function getSpeechRecognition() {
|
|
1695
|
-
if (typeof window === "undefined") return null;
|
|
1696
|
-
const w = window;
|
|
1697
|
-
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
1698
|
-
}
|
|
1699
|
-
function useVoiceSearch({
|
|
1700
|
-
lang,
|
|
1701
|
-
onInterimResult,
|
|
1702
|
-
onResult,
|
|
1703
|
-
onError
|
|
1704
|
-
}) {
|
|
1705
|
-
const [listening, setListening] = React.useState(false);
|
|
1706
|
-
const recognitionRef = React.useRef(null);
|
|
1707
|
-
const supported = getSpeechRecognition() !== null;
|
|
1708
|
-
const stop = React.useCallback(() => {
|
|
1709
|
-
recognitionRef.current?.stop();
|
|
1710
|
-
recognitionRef.current = null;
|
|
1711
|
-
setListening(false);
|
|
1712
|
-
}, []);
|
|
1713
|
-
const start = React.useCallback(() => {
|
|
1714
|
-
const SR = getSpeechRecognition();
|
|
1715
|
-
if (!SR) return;
|
|
1716
|
-
stop();
|
|
1717
|
-
const rec = new SR();
|
|
1718
|
-
rec.lang = lang ?? navigator.language ?? "en-US";
|
|
1719
|
-
rec.interimResults = true;
|
|
1720
|
-
rec.maxAlternatives = 1;
|
|
1721
|
-
rec.onresult = (event) => {
|
|
1722
|
-
let finalTranscript = "";
|
|
1723
|
-
let interimTranscript = "";
|
|
1724
|
-
const startIndex = event.resultIndex ?? 0;
|
|
1725
|
-
for (let index = startIndex; index < event.results.length; index += 1) {
|
|
1726
|
-
const result = event.results[index];
|
|
1727
|
-
const transcript = result?.[0]?.transcript ?? "";
|
|
1728
|
-
if (!transcript) continue;
|
|
1729
|
-
if (result?.isFinal) {
|
|
1730
|
-
finalTranscript += transcript;
|
|
1731
|
-
} else {
|
|
1732
|
-
interimTranscript += transcript;
|
|
1733
|
-
}
|
|
1734
|
-
}
|
|
1735
|
-
if (interimTranscript) onInterimResult?.(interimTranscript);
|
|
1736
|
-
if (finalTranscript) onResult(finalTranscript);
|
|
1737
|
-
};
|
|
1738
|
-
rec.onerror = (event) => {
|
|
1739
|
-
onError?.(event);
|
|
1740
|
-
setListening(false);
|
|
1741
|
-
};
|
|
1742
|
-
rec.onend = () => setListening(false);
|
|
1743
|
-
recognitionRef.current = rec;
|
|
1744
|
-
rec.start();
|
|
1745
|
-
setListening(true);
|
|
1746
|
-
}, [lang, onInterimResult, onResult, onError, stop]);
|
|
1747
|
-
return { listening, supported, start, stop };
|
|
1748
|
-
}
|
|
1749
|
-
|
|
1750
893
|
// src/utils/commonUtils.ts
|
|
1751
894
|
var IS_BROWSER = typeof window !== "undefined";
|
|
1752
895
|
function toEventString(value, fallback = " ") {
|
|
@@ -1754,119 +897,24 @@ function toEventString(value, fallback = " ") {
|
|
|
1754
897
|
const str = String(value).trim();
|
|
1755
898
|
return str.length > 0 ? str : fallback;
|
|
1756
899
|
}
|
|
1757
|
-
function getLocaleFromUrl(pathname) {
|
|
1758
|
-
const path = pathname ?? (IS_BROWSER ? window.location.pathname : "");
|
|
1759
|
-
const [country, language] = path.split("/").filter(Boolean);
|
|
1760
|
-
if (!/^[a-z]{2}$/i.test(country ?? "") || !/^[a-z]{2}$/i.test(language ?? "")) {
|
|
1761
|
-
return {};
|
|
1762
|
-
}
|
|
1763
|
-
return {
|
|
1764
|
-
countryCode: country.toUpperCase(),
|
|
1765
|
-
language: language.toLowerCase()
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
|
-
function safeParse(raw) {
|
|
1769
|
-
try {
|
|
1770
|
-
return JSON.parse(raw);
|
|
1771
|
-
} catch {
|
|
1772
|
-
return raw;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
function getLocalStorage(key) {
|
|
1776
|
-
if (!IS_BROWSER) return void 0;
|
|
1777
|
-
try {
|
|
1778
|
-
const raw = window.localStorage.getItem(key);
|
|
1779
|
-
return raw === null ? void 0 : safeParse(raw);
|
|
1780
|
-
} catch {
|
|
1781
|
-
return void 0;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
function getSessionStorage2(key) {
|
|
1785
|
-
if (!IS_BROWSER) return void 0;
|
|
1786
|
-
try {
|
|
1787
|
-
const raw = window.sessionStorage.getItem(key);
|
|
1788
|
-
return raw === null ? void 0 : safeParse(raw);
|
|
1789
|
-
} catch {
|
|
1790
|
-
return void 0;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
|
|
1794
|
-
// src/utils/gtmConstants.ts
|
|
1795
|
-
var SEARCH_GTM_EVENTS = {
|
|
1796
|
-
CHAT_ENTRY_POINT: "Begin_chat",
|
|
1797
|
-
SEARCH_TYPE: "Search_Type",
|
|
1798
|
-
PRODUCT_SEARCH: "ProductSearch",
|
|
1799
|
-
ERROR_POPUP: "Error_popup"
|
|
1800
|
-
};
|
|
1801
|
-
var SEARCH_GTM_SEARCH_TYPES = {
|
|
1802
|
-
SEMANTIC: "semantic search",
|
|
1803
|
-
AI_AGENT: "AI agent"
|
|
1804
|
-
};
|
|
1805
|
-
var SEARCH_GTM_USER_STATUS = {
|
|
1806
|
-
GUEST: "guest",
|
|
1807
|
-
LOGGED_IN: "logged_in"
|
|
1808
|
-
};
|
|
1809
|
-
var AI_CHAT_CONVERSATION_ID_KEY = "ai_chat_conversation_id";
|
|
1810
|
-
var IS_AUTHENTICATED_KEY = "IsAuthenticated";
|
|
1811
|
-
var ACCOUNT_TYPE_KEY = "accountType";
|
|
1812
|
-
var NUSKIN_ACCOUNT_DATA_KEY = "nuskin.account.data";
|
|
1813
|
-
var GTM_FALLBACK_NA = "NA";
|
|
1814
900
|
|
|
1815
901
|
// src/utils/gtmTracking.ts
|
|
1816
|
-
function readSessionChatId() {
|
|
1817
|
-
const raw = getSessionStorage2(AI_CHAT_CONVERSATION_ID_KEY);
|
|
1818
|
-
return typeof raw === "string" || typeof raw === "number" ? String(raw) : void 0;
|
|
1819
|
-
}
|
|
1820
|
-
function readUserStatusFromStorage() {
|
|
1821
|
-
const value = getLocalStorage(IS_AUTHENTICATED_KEY);
|
|
1822
|
-
if (value === void 0) return void 0;
|
|
1823
|
-
if (value === SEARCH_GTM_USER_STATUS.GUEST || value === SEARCH_GTM_USER_STATUS.LOGGED_IN) {
|
|
1824
|
-
return value;
|
|
1825
|
-
}
|
|
1826
|
-
const isAuthenticated = value === true || typeof value === "string" && ["true", "1", "yes", "y"].includes(value.toLowerCase().trim());
|
|
1827
|
-
return isAuthenticated ? SEARCH_GTM_USER_STATUS.LOGGED_IN : SEARCH_GTM_USER_STATUS.GUEST;
|
|
1828
|
-
}
|
|
1829
|
-
function readAccountTypeFromStorage() {
|
|
1830
|
-
const value = getLocalStorage(ACCOUNT_TYPE_KEY);
|
|
1831
|
-
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
1832
|
-
}
|
|
1833
|
-
function readUserIdFromStorage() {
|
|
1834
|
-
const raw = getLocalStorage(NUSKIN_ACCOUNT_DATA_KEY);
|
|
1835
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
1836
|
-
const user = raw.user;
|
|
1837
|
-
if (!user || typeof user !== "object") return void 0;
|
|
1838
|
-
const id = user.id;
|
|
1839
|
-
return typeof id === "string" && id.trim().length > 0 ? id : void 0;
|
|
1840
|
-
}
|
|
1841
902
|
function resolveUserId(explicit) {
|
|
1842
|
-
return toEventString(explicit
|
|
903
|
+
return toEventString(explicit, GTM_FALLBACK_NA);
|
|
1843
904
|
}
|
|
1844
905
|
function baseEventPayload(ctx) {
|
|
1845
|
-
const urlLocale = getLocaleFromUrl();
|
|
1846
|
-
const chatId = ctx.chatId ?? readSessionChatId();
|
|
1847
|
-
const userStatus = ctx.userStatus ?? readUserStatusFromStorage();
|
|
1848
|
-
const accountType = ctx.accountType ?? readAccountTypeFromStorage();
|
|
1849
|
-
const resolvedUserStatus = userStatus ?? SEARCH_GTM_USER_STATUS.GUEST;
|
|
1850
|
-
const documentTitle = IS_BROWSER && typeof document?.title === "string" ? document.title : void 0;
|
|
1851
906
|
return {
|
|
1852
|
-
Chat_ID: toEventString(chatId, GTM_FALLBACK_NA),
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
),
|
|
1857
|
-
|
|
1858
|
-
ctx.countryCode ?? urlLocale.countryCode,
|
|
1859
|
-
GTM_FALLBACK_NA
|
|
1860
|
-
),
|
|
1861
|
-
language: toEventString(
|
|
1862
|
-
ctx.language ?? urlLocale.language,
|
|
1863
|
-
GTM_FALLBACK_NA
|
|
1864
|
-
),
|
|
907
|
+
Chat_ID: toEventString(ctx.chatId, GTM_FALLBACK_NA),
|
|
908
|
+
// TODO: COMX-3277 puts `page_type` in every event but defines no values. Send `NA` until
|
|
909
|
+
// the data team says what it should hold (perhaps the page path).
|
|
910
|
+
page_type: GTM_FALLBACK_NA,
|
|
911
|
+
country_code: toEventString(ctx.countryCode, GTM_FALLBACK_NA),
|
|
912
|
+
language: toEventString(ctx.language, GTM_FALLBACK_NA),
|
|
1865
913
|
user_status: toEventString(
|
|
1866
|
-
|
|
914
|
+
ctx.userStatus ?? SEARCH_GTM_USER_STATUS.GUEST,
|
|
1867
915
|
SEARCH_GTM_USER_STATUS.GUEST
|
|
1868
916
|
),
|
|
1869
|
-
account_type: toEventString(accountType, GTM_FALLBACK_NA)
|
|
917
|
+
account_type: toEventString(ctx.accountType, GTM_FALLBACK_NA)
|
|
1870
918
|
};
|
|
1871
919
|
}
|
|
1872
920
|
function serializeProductResults(productResultsData, productResults) {
|
|
@@ -1899,15 +947,6 @@ function trackChatEntryPoint(ctx) {
|
|
|
1899
947
|
...baseEventPayload(ctx)
|
|
1900
948
|
});
|
|
1901
949
|
}
|
|
1902
|
-
function trackSearchType(input) {
|
|
1903
|
-
pushGtmEvent({
|
|
1904
|
-
event: SEARCH_GTM_EVENTS.SEARCH_TYPE,
|
|
1905
|
-
...baseEventPayload(input),
|
|
1906
|
-
id: resolveUserId(input.userId),
|
|
1907
|
-
type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
|
|
1908
|
-
search_response: toEventString(input.searchResponse, GTM_FALLBACK_NA)
|
|
1909
|
-
});
|
|
1910
|
-
}
|
|
1911
950
|
function trackProductSearch(input) {
|
|
1912
951
|
pushGtmEvent({
|
|
1913
952
|
event: SEARCH_GTM_EVENTS.PRODUCT_SEARCH,
|
|
@@ -1920,54 +959,471 @@ function trackProductSearch(input) {
|
|
|
1920
959
|
)
|
|
1921
960
|
});
|
|
1922
961
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
962
|
+
|
|
963
|
+
// src/utils/nuiSearchGate.ts
|
|
964
|
+
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
965
|
+
function parseFlagValue(raw) {
|
|
966
|
+
const value = raw?.trim().toLowerCase();
|
|
967
|
+
if (value === "true" || value === "1") return true;
|
|
968
|
+
if (value === "false" || value === "0") return false;
|
|
969
|
+
return void 0;
|
|
970
|
+
}
|
|
971
|
+
function readCookieFlag(cookie) {
|
|
972
|
+
for (const part of cookie.split(";")) {
|
|
973
|
+
const separator = part.indexOf("=");
|
|
974
|
+
if (separator < 0) continue;
|
|
975
|
+
if (part.slice(0, separator).trim() !== NUI_SEARCH_FLAG) continue;
|
|
976
|
+
const rawValue = part.slice(separator + 1);
|
|
977
|
+
try {
|
|
978
|
+
return parseFlagValue(decodeURIComponent(rawValue));
|
|
979
|
+
} catch {
|
|
980
|
+
return void 0;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
return void 0;
|
|
984
|
+
}
|
|
985
|
+
function resolveNuiSearchFlag(env = {}) {
|
|
986
|
+
const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
987
|
+
const search = env.search ?? (inBrowser ? window.location.search : "");
|
|
988
|
+
const cookie = env.cookie ?? (inBrowser ? document.cookie : "");
|
|
989
|
+
const setCookie = env.setCookie ?? (inBrowser ? (value) => {
|
|
990
|
+
document.cookie = value;
|
|
991
|
+
} : void 0);
|
|
992
|
+
const fromQuery = parseFlagValue(
|
|
993
|
+
new URLSearchParams(search).get(NUI_SEARCH_FLAG)
|
|
994
|
+
);
|
|
995
|
+
if (fromQuery !== void 0) {
|
|
996
|
+
const secure = inBrowser && window.location.protocol === "https:" ? "; Secure" : "";
|
|
997
|
+
setCookie?.(
|
|
998
|
+
`${NUI_SEARCH_FLAG}=${fromQuery}; path=/; SameSite=Lax${secure}`
|
|
999
|
+
);
|
|
1000
|
+
return fromQuery;
|
|
1001
|
+
}
|
|
1002
|
+
return readCookieFlag(cookie);
|
|
1003
|
+
}
|
|
1004
|
+
function isNuiSearchEnabled({
|
|
1005
|
+
isAuthenticated,
|
|
1006
|
+
flag
|
|
1007
|
+
}) {
|
|
1008
|
+
return isAuthenticated === true && flag === true;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/utils/searchRouting.ts
|
|
1012
|
+
function decideSearchRoute({
|
|
1013
|
+
enabled,
|
|
1014
|
+
snapshot
|
|
1015
|
+
}) {
|
|
1016
|
+
const term = snapshot.query.trim();
|
|
1017
|
+
if (!enabled || !term) return "search";
|
|
1018
|
+
if (snapshot.loading || snapshot.settledQuery !== term) return "wait";
|
|
1019
|
+
if (snapshot.suggestionFetchFailed) return "search";
|
|
1020
|
+
return snapshot.suggestions.length > 0 ? "search" : "handoff";
|
|
1021
|
+
}
|
|
1022
|
+
var HANDLE_POLL_MS = 50;
|
|
1023
|
+
var HANDLE_TIMEOUT_MS = 15e3;
|
|
1024
|
+
var SalesAIChatWidget = react.lazy(async () => {
|
|
1025
|
+
const [widget] = await Promise.all([
|
|
1026
|
+
import('@nuskin/react-nui-chat-sdk'),
|
|
1027
|
+
import('@nuskin/react-nui-chat-sdk/styles.css')
|
|
1028
|
+
]);
|
|
1029
|
+
return { default: widget.SalesAIChatWidget };
|
|
1030
|
+
});
|
|
1031
|
+
function getSalesAIHandle() {
|
|
1032
|
+
return window.SalesAI;
|
|
1033
|
+
}
|
|
1034
|
+
var AgentErrorBoundary = class extends react.Component {
|
|
1035
|
+
constructor() {
|
|
1036
|
+
super(...arguments);
|
|
1037
|
+
this.state = { failed: false };
|
|
1038
|
+
}
|
|
1039
|
+
static getDerivedStateFromError() {
|
|
1040
|
+
return { failed: true };
|
|
1041
|
+
}
|
|
1042
|
+
componentDidCatch(_error, _info) {
|
|
1043
|
+
this.props.onError();
|
|
1044
|
+
}
|
|
1045
|
+
render() {
|
|
1046
|
+
return this.state.failed ? null : this.props.children;
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
function useHandoff({
|
|
1050
|
+
query,
|
|
1051
|
+
handoffContext,
|
|
1052
|
+
agentContext,
|
|
1053
|
+
onUnavailable
|
|
1054
|
+
}) {
|
|
1055
|
+
react.useEffect(() => {
|
|
1056
|
+
let cancelled = false;
|
|
1057
|
+
let timer;
|
|
1058
|
+
const startedAt = Date.now();
|
|
1059
|
+
const tryOpen = () => {
|
|
1060
|
+
if (cancelled) return;
|
|
1061
|
+
const handle = getSalesAIHandle();
|
|
1062
|
+
if (handle) {
|
|
1063
|
+
handle.open({
|
|
1064
|
+
query,
|
|
1065
|
+
context: { ...agentContext, ...handoffContext }
|
|
1066
|
+
});
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (Date.now() - startedAt >= HANDLE_TIMEOUT_MS) {
|
|
1070
|
+
onUnavailable();
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
timer = setTimeout(tryOpen, HANDLE_POLL_MS);
|
|
1074
|
+
};
|
|
1075
|
+
tryOpen();
|
|
1076
|
+
return () => {
|
|
1077
|
+
cancelled = true;
|
|
1078
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1079
|
+
};
|
|
1080
|
+
}, [query]);
|
|
1081
|
+
}
|
|
1082
|
+
function NuiAgentPanel({
|
|
1083
|
+
agent,
|
|
1084
|
+
query,
|
|
1085
|
+
handoffContext,
|
|
1086
|
+
onUnavailable
|
|
1087
|
+
}) {
|
|
1088
|
+
useHandoff({
|
|
1089
|
+
query,
|
|
1090
|
+
handoffContext,
|
|
1091
|
+
agentContext: agent.context,
|
|
1092
|
+
onUnavailable
|
|
1093
|
+
});
|
|
1094
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AgentErrorBoundary, { onError: onUnavailable, children: /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1095
|
+
SalesAIChatWidget,
|
|
1096
|
+
{
|
|
1097
|
+
appKey: agent.appKey,
|
|
1098
|
+
apiBaseUrl: agent.apiBaseUrl,
|
|
1099
|
+
theme: agent.theme ?? "nui-light",
|
|
1100
|
+
defaultOpen: true,
|
|
1101
|
+
hideFab: true,
|
|
1102
|
+
placement: "host",
|
|
1103
|
+
...agent.language === void 0 ? {} : { language: agent.language },
|
|
1104
|
+
...agent.renderProduct === void 0 ? {} : { renderProduct: agent.renderProduct }
|
|
1105
|
+
}
|
|
1106
|
+
) }) });
|
|
1107
|
+
}
|
|
1108
|
+
var SUBMIT_WAIT_TIMEOUT_MS = 2e3;
|
|
1109
|
+
function uniqueRecentSearches(queries) {
|
|
1110
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1111
|
+
return queries.filter((query) => {
|
|
1112
|
+
const key = query.trim().toLowerCase();
|
|
1113
|
+
if (!key || seen.has(key)) return false;
|
|
1114
|
+
seen.add(key);
|
|
1115
|
+
return true;
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
function CommerceSearchView({
|
|
1119
|
+
isOpen,
|
|
1120
|
+
onClose,
|
|
1121
|
+
service,
|
|
1122
|
+
navigate,
|
|
1123
|
+
isAuthenticated,
|
|
1124
|
+
agent,
|
|
1125
|
+
logo,
|
|
1126
|
+
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH,
|
|
1127
|
+
gtmContext
|
|
1128
|
+
}) {
|
|
1129
|
+
const [flag, setFlag] = react.useState(void 0);
|
|
1130
|
+
const [handoff, setHandoff] = react.useState(null);
|
|
1131
|
+
const agentQuery = handoff?.query ?? null;
|
|
1132
|
+
const [pendingTerm, setPendingTerm] = react.useState(null);
|
|
1133
|
+
const [recentSearches, setRecentSearches] = react.useState([]);
|
|
1134
|
+
const search = useSearchSuggestions({
|
|
1135
|
+
service,
|
|
1136
|
+
enabled: isOpen && agentQuery === null
|
|
1137
|
+
});
|
|
1138
|
+
const {
|
|
1139
|
+
clearQuery,
|
|
1140
|
+
didYouMean,
|
|
1141
|
+
emptyFetched,
|
|
1142
|
+
hasMoreResults,
|
|
1143
|
+
loading,
|
|
1144
|
+
query,
|
|
1145
|
+
settledQuery,
|
|
1146
|
+
suggestionFetchFailed,
|
|
1147
|
+
suggestions,
|
|
1148
|
+
updateQuery
|
|
1149
|
+
} = search;
|
|
1150
|
+
const enabled = agent !== void 0 && isNuiSearchEnabled({ isAuthenticated, flag });
|
|
1151
|
+
const items = getVisibleSuggestions({
|
|
1152
|
+
didYouMean,
|
|
1153
|
+
suggestions
|
|
1154
|
+
});
|
|
1155
|
+
const showDidYouMean = didYouMean.length > 0;
|
|
1156
|
+
react.useEffect(() => {
|
|
1157
|
+
if (isOpen) setFlag(resolveNuiSearchFlag());
|
|
1158
|
+
}, [isOpen]);
|
|
1159
|
+
react.useEffect(() => {
|
|
1160
|
+
if (isOpen) {
|
|
1161
|
+
setRecentSearches(
|
|
1162
|
+
uniqueRecentSearches(service.getRecentSearches())
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
}, [isOpen, service, agentQuery]);
|
|
1166
|
+
const close = react.useCallback(() => {
|
|
1167
|
+
setHandoff(null);
|
|
1168
|
+
setPendingTerm(null);
|
|
1169
|
+
clearQuery();
|
|
1170
|
+
onClose();
|
|
1171
|
+
}, [clearQuery, onClose]);
|
|
1172
|
+
react.useEffect(() => {
|
|
1173
|
+
if (!isOpen) {
|
|
1174
|
+
setHandoff(null);
|
|
1175
|
+
setPendingTerm(null);
|
|
1176
|
+
clearQuery();
|
|
1177
|
+
}
|
|
1178
|
+
}, [isOpen, clearQuery]);
|
|
1179
|
+
const navigateTo = react.useCallback(
|
|
1180
|
+
(path) => {
|
|
1181
|
+
if (!path) return;
|
|
1182
|
+
navigate(path);
|
|
1183
|
+
close();
|
|
1184
|
+
},
|
|
1185
|
+
[navigate, close]
|
|
1186
|
+
);
|
|
1187
|
+
const trackSemantic = react.useCallback(
|
|
1188
|
+
(productResultsData) => {
|
|
1189
|
+
trackProductSearch({
|
|
1190
|
+
...gtmContext,
|
|
1191
|
+
searchType: SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
|
|
1192
|
+
productResultsData
|
|
1193
|
+
});
|
|
1194
|
+
},
|
|
1195
|
+
[gtmContext]
|
|
1196
|
+
);
|
|
1197
|
+
const runSearch = react.useCallback(
|
|
1198
|
+
(term) => {
|
|
1199
|
+
trackSemantic(items.length > 0 ? items : { query: term });
|
|
1200
|
+
navigateTo(service.submitProductSearch(term));
|
|
1201
|
+
},
|
|
1202
|
+
[items, navigateTo, service, trackSemantic]
|
|
1203
|
+
);
|
|
1204
|
+
const decide = react.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 = react.useCallback(
|
|
1218
|
+
(term, route) => {
|
|
1219
|
+
setPendingTerm(null);
|
|
1220
|
+
if (route === "handoff") {
|
|
1221
|
+
trackChatEntryPoint(gtmContext ?? {});
|
|
1222
|
+
setHandoff({
|
|
1223
|
+
query: term,
|
|
1224
|
+
didYouMean: didYouMean.map((item) => item.value)
|
|
1225
|
+
});
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
runSearch(term);
|
|
1229
|
+
},
|
|
1230
|
+
[didYouMean, gtmContext, runSearch]
|
|
1231
|
+
);
|
|
1232
|
+
const handleSubmit = react.useCallback(
|
|
1233
|
+
(value) => {
|
|
1234
|
+
const term = value.trim();
|
|
1235
|
+
if (!term) return;
|
|
1236
|
+
if (term.length < minimumSearchLength) {
|
|
1237
|
+
runSearch(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, runSearch]
|
|
1248
|
+
);
|
|
1249
|
+
const latest = react.useRef({ act, decide });
|
|
1250
|
+
latest.current = { act, decide };
|
|
1251
|
+
react.useEffect(() => {
|
|
1252
|
+
if (pendingTerm === null) return void 0;
|
|
1253
|
+
const route = latest.current.decide(pendingTerm);
|
|
1254
|
+
if (route !== "wait") {
|
|
1255
|
+
latest.current.act(pendingTerm, route);
|
|
1256
|
+
return void 0;
|
|
1257
|
+
}
|
|
1258
|
+
const timer = setTimeout(
|
|
1259
|
+
() => latest.current.act(pendingTerm, "search"),
|
|
1260
|
+
SUBMIT_WAIT_TIMEOUT_MS
|
|
1261
|
+
);
|
|
1262
|
+
return () => clearTimeout(timer);
|
|
1263
|
+
}, [
|
|
1264
|
+
pendingTerm,
|
|
1265
|
+
loading,
|
|
1266
|
+
settledQuery,
|
|
1267
|
+
suggestions,
|
|
1268
|
+
suggestionFetchFailed
|
|
1269
|
+
]);
|
|
1270
|
+
const handleSelect = react.useCallback(
|
|
1271
|
+
(option) => {
|
|
1272
|
+
if (isSuggestionDisabled(
|
|
1273
|
+
option
|
|
1274
|
+
)) {
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
const term = String(option["value"] ?? option["label"] ?? "");
|
|
1278
|
+
if (term.trim()) trackSemantic([option]);
|
|
1279
|
+
const suggestionOption = option;
|
|
1280
|
+
const href = service.getSuggestionHref(suggestionOption);
|
|
1281
|
+
navigateTo(
|
|
1282
|
+
href || service.submitProductSearch(term, suggestionOption)
|
|
1283
|
+
);
|
|
1284
|
+
},
|
|
1285
|
+
[navigateTo, service, trackSemantic]
|
|
1286
|
+
);
|
|
1287
|
+
const handleRunAction = react.useCallback(
|
|
1288
|
+
(option) => {
|
|
1289
|
+
if (isSuggestionDisabled(
|
|
1290
|
+
option
|
|
1291
|
+
)) {
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
const result = service.activateSuggestionAction(
|
|
1295
|
+
option
|
|
1296
|
+
);
|
|
1297
|
+
void Promise.resolve(result).then(navigateTo).catch(() => void 0);
|
|
1298
|
+
},
|
|
1299
|
+
[navigateTo, service]
|
|
1300
|
+
);
|
|
1301
|
+
const handleSeeMore = react.useCallback(() => {
|
|
1302
|
+
const term = query.trim();
|
|
1303
|
+
if (term) navigateTo(service.viewMoreResults({ query: term }));
|
|
1304
|
+
}, [navigateTo, query, service]);
|
|
1305
|
+
const handleRecentSelect = react.useCallback(
|
|
1306
|
+
(recentQuery) => {
|
|
1307
|
+
trackSemantic({ query: recentQuery });
|
|
1308
|
+
navigateTo(service.submitProductSearch(recentQuery));
|
|
1309
|
+
},
|
|
1310
|
+
[navigateTo, service, trackSemantic]
|
|
1311
|
+
);
|
|
1312
|
+
const handleClearRecent = react.useCallback(() => {
|
|
1313
|
+
service.clearRecentSearches();
|
|
1314
|
+
setRecentSearches([]);
|
|
1315
|
+
}, [service]);
|
|
1316
|
+
const handleAgentUnavailable = react.useCallback(() => {
|
|
1317
|
+
if (agentQuery !== null)
|
|
1318
|
+
navigateTo(service.submitProductSearch(agentQuery));
|
|
1319
|
+
}, [agentQuery, navigateTo, service]);
|
|
1320
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1321
|
+
reactSearchComponents.SearchModal,
|
|
1322
|
+
{
|
|
1323
|
+
isOpen,
|
|
1324
|
+
onClose: close,
|
|
1325
|
+
logo,
|
|
1326
|
+
input: {
|
|
1327
|
+
value: query,
|
|
1328
|
+
onChange: updateQuery,
|
|
1329
|
+
onSubmit: handleSubmit
|
|
1330
|
+
},
|
|
1331
|
+
suggestions: {
|
|
1332
|
+
items,
|
|
1333
|
+
loading,
|
|
1334
|
+
showNoResults: !loading && emptyFetched && items.length === 0,
|
|
1335
|
+
showDidYouMean,
|
|
1336
|
+
hasMore: hasMoreResults,
|
|
1337
|
+
query,
|
|
1338
|
+
onSelect: handleSelect,
|
|
1339
|
+
onRunAction: handleRunAction,
|
|
1340
|
+
onSeeMore: handleSeeMore
|
|
1341
|
+
},
|
|
1342
|
+
recentSearches: {
|
|
1343
|
+
items: recentSearches,
|
|
1344
|
+
onSelect: handleRecentSelect,
|
|
1345
|
+
onClear: handleClearRecent
|
|
1346
|
+
},
|
|
1347
|
+
agentPanel: handoff === null || agent === void 0 ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1348
|
+
NuiAgentPanel,
|
|
1349
|
+
{
|
|
1350
|
+
agent,
|
|
1351
|
+
query: handoff.query,
|
|
1352
|
+
handoffContext: {
|
|
1353
|
+
source: "storefront_search",
|
|
1354
|
+
trigger: "zero_results",
|
|
1355
|
+
query: handoff.query,
|
|
1356
|
+
did_you_mean: handoff.didYouMean
|
|
1357
|
+
},
|
|
1358
|
+
onUnavailable: handleAgentUnavailable
|
|
1359
|
+
}
|
|
1360
|
+
)
|
|
1361
|
+
}
|
|
1362
|
+
);
|
|
1363
|
+
}
|
|
1364
|
+
var GUEST = "guest";
|
|
1365
|
+
function CommerceSearch({
|
|
1366
|
+
isOpen,
|
|
1367
|
+
onClose,
|
|
1368
|
+
navigate,
|
|
1369
|
+
customer,
|
|
1370
|
+
country,
|
|
1371
|
+
language,
|
|
1372
|
+
logo
|
|
1373
|
+
}) {
|
|
1374
|
+
const [hasOpened, setHasOpened] = react.useState(false);
|
|
1375
|
+
react.useEffect(() => {
|
|
1376
|
+
if (isOpen) setHasOpened(true);
|
|
1377
|
+
}, [isOpen]);
|
|
1378
|
+
const { minimumSearchLength, agent } = useCommerceSearchConfig({
|
|
1379
|
+
country,
|
|
1380
|
+
language,
|
|
1381
|
+
enabled: hasOpened
|
|
1929
1382
|
});
|
|
1383
|
+
const customerTypeRef = react.useRef(GUEST);
|
|
1384
|
+
customerTypeRef.current = customer.isAuthenticated ? customer.accountType ?? GUEST : GUEST;
|
|
1385
|
+
const service = react.useMemo(
|
|
1386
|
+
() => createSearchService({
|
|
1387
|
+
country,
|
|
1388
|
+
language,
|
|
1389
|
+
locale: `${country.toUpperCase()}-${language.toLowerCase()}`,
|
|
1390
|
+
getMarketConfig: fetchMarketConfig,
|
|
1391
|
+
minimumSearchLength,
|
|
1392
|
+
getCustomerType: () => customerTypeRef.current
|
|
1393
|
+
}),
|
|
1394
|
+
[country, language, minimumSearchLength]
|
|
1395
|
+
);
|
|
1396
|
+
const navigateToPath = react.useCallback(
|
|
1397
|
+
(path) => navigate(toNavigationTarget(path)),
|
|
1398
|
+
[navigate]
|
|
1399
|
+
);
|
|
1400
|
+
const { isAuthenticated, accountType, userId } = customer;
|
|
1401
|
+
const gtmContext = react.useMemo(
|
|
1402
|
+
() => ({
|
|
1403
|
+
countryCode: country,
|
|
1404
|
+
language,
|
|
1405
|
+
userStatus: isAuthenticated ? SEARCH_GTM_USER_STATUS.LOGGED_IN : SEARCH_GTM_USER_STATUS.GUEST,
|
|
1406
|
+
...isAuthenticated && accountType ? { accountType } : {},
|
|
1407
|
+
...isAuthenticated && userId ? { userId } : {}
|
|
1408
|
+
}),
|
|
1409
|
+
[country, language, isAuthenticated, accountType, userId]
|
|
1410
|
+
);
|
|
1411
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1412
|
+
CommerceSearchView,
|
|
1413
|
+
{
|
|
1414
|
+
isOpen,
|
|
1415
|
+
onClose,
|
|
1416
|
+
service,
|
|
1417
|
+
navigate: navigateToPath,
|
|
1418
|
+
isAuthenticated,
|
|
1419
|
+
...agent ? { agent } : {},
|
|
1420
|
+
logo,
|
|
1421
|
+
minimumSearchLength,
|
|
1422
|
+
gtmContext
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
1930
1425
|
}
|
|
1931
1426
|
|
|
1932
|
-
exports.
|
|
1933
|
-
exports.DEBOUNCE_MS = DEBOUNCE_MS;
|
|
1934
|
-
exports.DEFAULT_AI_CHAT_BASE_URL = DEFAULT_AI_CHAT_BASE_URL;
|
|
1935
|
-
exports.DEFAULT_AI_CHAT_CONVERSATIONS_URL = DEFAULT_AI_CHAT_CONVERSATIONS_URL;
|
|
1936
|
-
exports.DEFAULT_AI_CHAT_MESSAGES_URL = DEFAULT_AI_CHAT_MESSAGES_URL;
|
|
1937
|
-
exports.DEFAULT_SEARCH_PATH = DEFAULT_SEARCH_PATH;
|
|
1938
|
-
exports.DEFAULT_SUGGESTION_PAGE = DEFAULT_SUGGESTION_PAGE;
|
|
1939
|
-
exports.DEFAULT_SUGGESTION_SIZE = DEFAULT_SUGGESTION_SIZE;
|
|
1940
|
-
exports.MAX_VISIBLE_SUGGESTIONS = MAX_VISIBLE_SUGGESTIONS;
|
|
1941
|
-
exports.RECENT_SEARCHES_DEFAULT_LIMIT = RECENT_SEARCHES_DEFAULT_LIMIT;
|
|
1942
|
-
exports.RECENT_SEARCHES_PREFIX = RECENT_SEARCHES_PREFIX;
|
|
1943
|
-
exports.SEARCH_GTM_EVENTS = SEARCH_GTM_EVENTS;
|
|
1944
|
-
exports.SEARCH_GTM_SEARCH_TYPES = SEARCH_GTM_SEARCH_TYPES;
|
|
1945
|
-
exports.SEARCH_GTM_USER_STATUS = SEARCH_GTM_USER_STATUS;
|
|
1946
|
-
exports.SEARCH_PLACEHOLDER = SEARCH_PLACEHOLDER;
|
|
1947
|
-
exports.STOREFRONT_SHOPPING_CONTEXT = STOREFRONT_SHOPPING_CONTEXT;
|
|
1948
|
-
exports.TRIGGER_PLACEHOLDER = TRIGGER_PLACEHOLDER;
|
|
1949
|
-
exports.createChatService = createChatService;
|
|
1950
|
-
exports.createSearchService = createSearchService;
|
|
1951
|
-
exports.getImageFromProperty = getImageFromProperty;
|
|
1952
|
-
exports.getIngredientTerms = getIngredientTerms;
|
|
1953
|
-
exports.getLocaleFromUrl = getLocaleFromUrl;
|
|
1954
|
-
exports.getSeeMoreResultsPayload = getSeeMoreResultsPayload;
|
|
1955
|
-
exports.getVisibleSuggestions = getVisibleSuggestions;
|
|
1956
|
-
exports.isCartActionDisabled = isCartActionDisabled;
|
|
1957
|
-
exports.isSellableProductStatus = isSellableProductStatus;
|
|
1958
|
-
exports.isSuggestionDisabled = isSuggestionDisabled;
|
|
1959
|
-
exports.mapCatalogProductToSuggestion = mapCatalogProductToSuggestion;
|
|
1960
|
-
exports.normalizeDidYouMean = normalizeDidYouMean;
|
|
1961
|
-
exports.pushGtmEvent = pushGtmEvent;
|
|
1962
|
-
exports.resolveApiGatewayPath = resolveApiGatewayPath;
|
|
1963
|
-
exports.resolveSuggestionImageSrc = resolveSuggestionImageSrc;
|
|
1964
|
-
exports.trackChatEntryPoint = trackChatEntryPoint;
|
|
1965
|
-
exports.trackErrorPopup = trackErrorPopup;
|
|
1966
|
-
exports.trackProductSearch = trackProductSearch;
|
|
1967
|
-
exports.trackSearchType = trackSearchType;
|
|
1968
|
-
exports.useSearchHistory = useSearchHistory;
|
|
1969
|
-
exports.useSearchHistoryStore = useSearchHistoryStore;
|
|
1970
|
-
exports.useSearchSuggestions = useSearchSuggestions;
|
|
1971
|
-
exports.useVoiceSearch = useVoiceSearch;
|
|
1427
|
+
exports.CommerceSearch = CommerceSearch;
|
|
1972
1428
|
//# sourceMappingURL=index.js.map
|
|
1973
1429
|
//# sourceMappingURL=index.js.map
|