@nuskin/react-search-sdk 1.0.1-code-cleanup.1 → 1.0.1-code-cleanup.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 +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +140 -50
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +140 -50
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useState, useEffect, useRef, useMemo, useCallback, Component } from 'react';
|
|
2
2
|
import configurationSdk from '@nuskin/configuration-sdk';
|
|
3
3
|
import { SearchPanel } from '@nuskin/react-search-components';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
5
|
import { SalesAIChatWidget } from '@nuskin/react-nui-chat-sdk';
|
|
5
6
|
import '@nuskin/react-nui-chat-sdk/styles.css';
|
|
6
7
|
import { createProductClient, ProductProvider, useProductById, ProductCard } from '@nuskin/react-product-sdk';
|
|
7
8
|
import styled from '@emotion/styled';
|
|
8
|
-
import { jsx } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
// src/components/CommerceSearch/CommerceSearch.tsx
|
|
11
11
|
var { getConfiguration, getEnvironmentCode } = configurationSdk;
|
|
@@ -173,25 +173,38 @@ function useSearchPanelCopy({
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// src/services/localeParts.ts
|
|
176
|
-
|
|
176
|
+
var TWO_LETTERS = /^[A-Za-z]{2}$/;
|
|
177
|
+
function marketProblem(country, language) {
|
|
178
|
+
const countryText = String(country ?? "").trim();
|
|
179
|
+
const languageText = String(language ?? "").trim();
|
|
180
|
+
if (TWO_LETTERS.test(countryText) && TWO_LETTERS.test(languageText)) {
|
|
181
|
+
return void 0;
|
|
182
|
+
}
|
|
183
|
+
return `the market is not valid (country ${JSON.stringify(countryText)}, language ${JSON.stringify(languageText)}): each must be two letters, for example US and en`;
|
|
184
|
+
}
|
|
185
|
+
function isValidMarket(country, language) {
|
|
186
|
+
return marketProblem(country, language) === void 0;
|
|
187
|
+
}
|
|
188
|
+
function createLocaleParts({
|
|
177
189
|
country,
|
|
178
|
-
language
|
|
179
|
-
locale
|
|
190
|
+
language
|
|
180
191
|
}) {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const
|
|
192
|
+
const problem = marketProblem(country, language);
|
|
193
|
+
if (problem) throw new Error(problem);
|
|
194
|
+
const normalizedCountry = country.trim().toUpperCase();
|
|
195
|
+
const normalizedLanguage = language.trim().toLowerCase();
|
|
184
196
|
return {
|
|
185
|
-
country: normalizedCountry
|
|
186
|
-
language: normalizedLanguage
|
|
187
|
-
locale:
|
|
197
|
+
country: normalizedCountry,
|
|
198
|
+
language: normalizedLanguage,
|
|
199
|
+
locale: `${normalizedCountry}-${normalizedLanguage}`
|
|
188
200
|
};
|
|
189
201
|
}
|
|
190
|
-
function toCatalogLocale(
|
|
191
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
192
|
-
const country = String(localeParts.country ?? "US").toUpperCase();
|
|
202
|
+
function toCatalogLocale({ country, language }) {
|
|
193
203
|
return `${language}_${country}`;
|
|
194
204
|
}
|
|
205
|
+
function toPathPrefix({ country, language }) {
|
|
206
|
+
return `/${country.toLowerCase()}/${language}`;
|
|
207
|
+
}
|
|
195
208
|
|
|
196
209
|
// src/services/catalogRequest.ts
|
|
197
210
|
function resolveApiGatewayPath(apiGatewayPath, apiName = STOREFRONT_SHOPPING_CONTEXT) {
|
|
@@ -234,12 +247,21 @@ async function resolveCatalogRequestConfig({
|
|
|
234
247
|
getMarketConfig
|
|
235
248
|
}) {
|
|
236
249
|
const marketConfig = getMarketConfig ? await getMarketConfig({ locationLike, country }) ?? null : null;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
250
|
+
const suggestionsUrl = buildCatalogSuggestionsUrl(
|
|
251
|
+
resolveCatalogApiBaseUrl(marketConfig)
|
|
252
|
+
);
|
|
253
|
+
if (!suggestionsUrl) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`no suggestions API for market ${country}: the market configuration has no gateway`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
const storeId = resolveCatalogStoreId(marketConfig);
|
|
259
|
+
if (!storeId) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
`no suggestions API for market ${country}: the market configuration has no store`
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return { suggestionsUrl, storeId };
|
|
243
265
|
}
|
|
244
266
|
function resolveCustomerTypeHeaders(getCustomerType) {
|
|
245
267
|
const customerType = getCustomerType?.()?.trim();
|
|
@@ -284,7 +306,7 @@ function getSessionStorage(providedStorage) {
|
|
|
284
306
|
return null;
|
|
285
307
|
}
|
|
286
308
|
function getRecentHeaderSearchesStorageKey(localeParts) {
|
|
287
|
-
const { country, language, locale } =
|
|
309
|
+
const { country, language, locale } = localeParts;
|
|
288
310
|
return `${RECENT_SEARCHES_PREFIX}:${locale}:${country}:${language}`;
|
|
289
311
|
}
|
|
290
312
|
function getRecentHeaderSearches(localeParts, storage) {
|
|
@@ -347,13 +369,14 @@ function buildSearchResultsPath(query, localeParts) {
|
|
|
347
369
|
/%2D/gi,
|
|
348
370
|
"-"
|
|
349
371
|
);
|
|
350
|
-
|
|
351
|
-
const language = localeParts.language.toLowerCase();
|
|
352
|
-
return `/${country}/${language}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
|
|
372
|
+
return `${toPathPrefix(localeParts)}${DEFAULT_SEARCH_PATH}/${encodedQuery}`;
|
|
353
373
|
}
|
|
374
|
+
var SCHEME = /^[a-z][a-z0-9+.-]*:/i;
|
|
354
375
|
function normalizeOptionUrl(rawValue) {
|
|
355
376
|
if (typeof rawValue !== "string") return "";
|
|
356
|
-
|
|
377
|
+
const value = rawValue.trim();
|
|
378
|
+
if (!value || value.startsWith("/") || SCHEME.test(value)) return value;
|
|
379
|
+
return `/${value}`;
|
|
357
380
|
}
|
|
358
381
|
function safeDecodeURIComponent(value) {
|
|
359
382
|
try {
|
|
@@ -383,16 +406,14 @@ function slugifyPathSegment(value) {
|
|
|
383
406
|
function buildProductSlugPath(slugValue, localeParts) {
|
|
384
407
|
const slug = slugifyPathSegment(slugValue);
|
|
385
408
|
if (!slug) return "";
|
|
386
|
-
|
|
387
|
-
const language = String(localeParts.language ?? "en").toLowerCase();
|
|
388
|
-
return `/${country}/${language}/product/${slug}`;
|
|
409
|
+
return `${toPathPrefix(localeParts)}/product/${slug}`;
|
|
389
410
|
}
|
|
390
411
|
function isNavigableUrl(value) {
|
|
391
412
|
if (!value) return false;
|
|
392
413
|
return value.startsWith("/") || /^https?:\/\//i.test(value);
|
|
393
414
|
}
|
|
394
415
|
function stripLocalePrefix(path, localeParts) {
|
|
395
|
-
const prefix =
|
|
416
|
+
const prefix = toPathPrefix(localeParts);
|
|
396
417
|
return path.startsWith(prefix) ? path.slice(prefix.length) || "/" : path;
|
|
397
418
|
}
|
|
398
419
|
function resolveOptionHref(option, localeParts) {
|
|
@@ -446,26 +467,20 @@ function getBrowserLocation(providedLocation) {
|
|
|
446
467
|
return window.location;
|
|
447
468
|
return null;
|
|
448
469
|
}
|
|
449
|
-
function
|
|
450
|
-
if (fetchImplOption !== void 0) return fetchImplOption;
|
|
451
|
-
return typeof fetch === "function" ? fetch.bind(globalThis) : null;
|
|
452
|
-
}
|
|
453
|
-
function createSearchService(options = {}) {
|
|
470
|
+
function createSearchService(options) {
|
|
454
471
|
const {
|
|
455
472
|
fetchImpl: fetchImplOption,
|
|
456
473
|
location: providedLocation,
|
|
457
474
|
storage: providedStorage,
|
|
458
475
|
country,
|
|
459
476
|
language,
|
|
460
|
-
locale,
|
|
461
477
|
getMarketConfig,
|
|
462
478
|
getCustomerType,
|
|
463
479
|
minimumSearchLength = MINIMUM_SEARCH_CHAR_LENGTH
|
|
464
480
|
} = options;
|
|
465
|
-
const fetchImpl = resolveFetch(fetchImplOption);
|
|
466
481
|
const locationLike = getBrowserLocation(providedLocation);
|
|
467
482
|
const storage = getSessionStorage(providedStorage);
|
|
468
|
-
const localeParts =
|
|
483
|
+
const localeParts = createLocaleParts({ country, language });
|
|
469
484
|
let catalogRequestConfigPromise = null;
|
|
470
485
|
function loadCatalogRequestConfig() {
|
|
471
486
|
if (!catalogRequestConfigPromise) {
|
|
@@ -487,13 +502,12 @@ function createSearchService(options = {}) {
|
|
|
487
502
|
const service = {
|
|
488
503
|
async fetchSuggestions(searchTerm) {
|
|
489
504
|
const trimmedSearchTerm = String(searchTerm ?? "").trim();
|
|
490
|
-
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength
|
|
505
|
+
if (!trimmedSearchTerm || trimmedSearchTerm.length < minimumSearchLength) {
|
|
491
506
|
return emptySuggestions();
|
|
492
507
|
}
|
|
493
508
|
const { suggestionsUrl, storeId } = await loadCatalogRequestConfig();
|
|
494
|
-
if (!suggestionsUrl || !storeId) return emptySuggestions();
|
|
495
509
|
const responsePayload = await fetchJson(
|
|
496
|
-
|
|
510
|
+
fetchImplOption ?? fetch.bind(globalThis),
|
|
497
511
|
buildSuggestionsRequestUrl({
|
|
498
512
|
suggestionsUrl,
|
|
499
513
|
searchTerm: trimmedSearchTerm,
|
|
@@ -547,7 +561,7 @@ function createSearchService(options = {}) {
|
|
|
547
561
|
|
|
548
562
|
// src/services/marketConfig.ts
|
|
549
563
|
var fetchMarketConfig = async ({ country }) => {
|
|
550
|
-
const countryCode =
|
|
564
|
+
const countryCode = country.trim().toUpperCase();
|
|
551
565
|
const configuration = await getConfiguration([EQUINOX_MARKETS_MAP], {
|
|
552
566
|
country: countryCode,
|
|
553
567
|
...getEnvironmentCode() ? { environment: getEnvironmentCode() } : {},
|
|
@@ -570,14 +584,25 @@ var SEARCH_GTM_USER_STATUS = {
|
|
|
570
584
|
var GTM_FALLBACK_NA = "NA";
|
|
571
585
|
|
|
572
586
|
// src/utils/navigationTarget.ts
|
|
587
|
+
var EXTERNAL_URL = /^https?:\/\//i;
|
|
588
|
+
var INTERNAL_PATH = /^\/(?![/\\])[^\t\n\r]*$/;
|
|
573
589
|
function toNavigationTarget(path) {
|
|
574
|
-
|
|
590
|
+
if (EXTERNAL_URL.test(path)) return { type: "external", url: path };
|
|
591
|
+
if (INTERNAL_PATH.test(path)) return { type: "internal", path };
|
|
592
|
+
return void 0;
|
|
575
593
|
}
|
|
576
594
|
|
|
577
595
|
// src/utils/productEnvironment.ts
|
|
578
|
-
var PRODUCT_ENVIRONMENTS =
|
|
596
|
+
var PRODUCT_ENVIRONMENTS = {
|
|
597
|
+
dev: true,
|
|
598
|
+
test: true,
|
|
599
|
+
prod: true
|
|
600
|
+
};
|
|
601
|
+
function isProductEnvironment(code) {
|
|
602
|
+
return Object.keys(PRODUCT_ENVIRONMENTS).includes(code);
|
|
603
|
+
}
|
|
579
604
|
function toProductEnvironment(environmentCode) {
|
|
580
|
-
return environmentCode &&
|
|
605
|
+
return environmentCode !== void 0 && isProductEnvironment(environmentCode) ? environmentCode : void 0;
|
|
581
606
|
}
|
|
582
607
|
function getProductEnvironment() {
|
|
583
608
|
try {
|
|
@@ -586,6 +611,47 @@ function getProductEnvironment() {
|
|
|
586
611
|
return void 0;
|
|
587
612
|
}
|
|
588
613
|
}
|
|
614
|
+
var NOTHING = () => void 0;
|
|
615
|
+
function CommerceSearchUnavailable({
|
|
616
|
+
isOpen,
|
|
617
|
+
onClose,
|
|
618
|
+
country,
|
|
619
|
+
language,
|
|
620
|
+
logo
|
|
621
|
+
}) {
|
|
622
|
+
useEffect(() => {
|
|
623
|
+
console.error(
|
|
624
|
+
`[search] Search is unavailable: ${marketProblem(country, language)}`
|
|
625
|
+
);
|
|
626
|
+
}, [country, language]);
|
|
627
|
+
return /* @__PURE__ */ jsx(
|
|
628
|
+
SearchPanel,
|
|
629
|
+
{
|
|
630
|
+
isOpen,
|
|
631
|
+
onClose,
|
|
632
|
+
copy: SEARCH_PANEL_COPY_EN,
|
|
633
|
+
logo,
|
|
634
|
+
input: { value: "", onChange: NOTHING, onSubmit: NOTHING },
|
|
635
|
+
suggestions: {
|
|
636
|
+
items: [],
|
|
637
|
+
loading: false,
|
|
638
|
+
showNoResults: false,
|
|
639
|
+
showDidYouMean: false,
|
|
640
|
+
hasMore: false,
|
|
641
|
+
query: "",
|
|
642
|
+
onSelect: NOTHING,
|
|
643
|
+
onRunAction: NOTHING,
|
|
644
|
+
onSeeMore: NOTHING
|
|
645
|
+
},
|
|
646
|
+
recentSearches: {
|
|
647
|
+
items: [],
|
|
648
|
+
onSelect: NOTHING,
|
|
649
|
+
onClear: NOTHING
|
|
650
|
+
},
|
|
651
|
+
agentPanel: /* @__PURE__ */ jsx("p", { role: "alert", children: "Search is unavailable right now." })
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
589
655
|
|
|
590
656
|
// src/utils/nuiSearchGate.ts
|
|
591
657
|
var NUI_SEARCH_FLAG = "enableNuiSearch";
|
|
@@ -1069,6 +1135,7 @@ function useSearchSuggestions({
|
|
|
1069
1135
|
[enabled, service, resetSuggestionsState, settle]
|
|
1070
1136
|
);
|
|
1071
1137
|
useEffect(() => {
|
|
1138
|
+
latestRequestId.current += 1;
|
|
1072
1139
|
if (!enabled) {
|
|
1073
1140
|
resetSuggestionsState();
|
|
1074
1141
|
return void 0;
|
|
@@ -1076,7 +1143,6 @@ function useSearchSuggestions({
|
|
|
1076
1143
|
if (fetchDebounced.current) {
|
|
1077
1144
|
clearTimeout(fetchDebounced.current);
|
|
1078
1145
|
}
|
|
1079
|
-
latestRequestId.current += 1;
|
|
1080
1146
|
fetchDebounced.current = setTimeout(() => {
|
|
1081
1147
|
void runFetch(query);
|
|
1082
1148
|
}, DEBOUNCE_MS);
|
|
@@ -1358,11 +1424,11 @@ function useHandoff({
|
|
|
1358
1424
|
onUnavailableRef.current = onUnavailable;
|
|
1359
1425
|
});
|
|
1360
1426
|
useEffect(() => {
|
|
1361
|
-
let
|
|
1427
|
+
let canceled = false;
|
|
1362
1428
|
let timer;
|
|
1363
1429
|
const startedAt = Date.now();
|
|
1364
1430
|
const tryOpen = () => {
|
|
1365
|
-
if (
|
|
1431
|
+
if (canceled) return;
|
|
1366
1432
|
const handle = getSalesAIHandle();
|
|
1367
1433
|
if (handle) {
|
|
1368
1434
|
handle.open({
|
|
@@ -1382,7 +1448,7 @@ function useHandoff({
|
|
|
1382
1448
|
};
|
|
1383
1449
|
tryOpen();
|
|
1384
1450
|
return () => {
|
|
1385
|
-
|
|
1451
|
+
canceled = true;
|
|
1386
1452
|
if (timer !== void 0) clearTimeout(timer);
|
|
1387
1453
|
};
|
|
1388
1454
|
}, [query]);
|
|
@@ -1610,7 +1676,22 @@ function CommerceSearchView({
|
|
|
1610
1676
|
);
|
|
1611
1677
|
}
|
|
1612
1678
|
var GUEST = "guest";
|
|
1613
|
-
function CommerceSearch({
|
|
1679
|
+
function CommerceSearch(props) {
|
|
1680
|
+
if (!isValidMarket(props.country, props.language)) {
|
|
1681
|
+
return /* @__PURE__ */ jsx(
|
|
1682
|
+
CommerceSearchUnavailable,
|
|
1683
|
+
{
|
|
1684
|
+
isOpen: props.isOpen,
|
|
1685
|
+
onClose: props.onClose,
|
|
1686
|
+
country: props.country,
|
|
1687
|
+
language: props.language,
|
|
1688
|
+
logo: props.logo
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
return /* @__PURE__ */ jsx(ValidMarketSearch, { ...props });
|
|
1693
|
+
}
|
|
1694
|
+
function ValidMarketSearch({
|
|
1614
1695
|
isOpen,
|
|
1615
1696
|
onClose,
|
|
1616
1697
|
navigate,
|
|
@@ -1638,7 +1719,6 @@ function CommerceSearch({
|
|
|
1638
1719
|
() => createSearchService({
|
|
1639
1720
|
country,
|
|
1640
1721
|
language,
|
|
1641
|
-
locale: `${country.toUpperCase()}-${language.toLowerCase()}`,
|
|
1642
1722
|
getMarketConfig: fetchMarketConfig,
|
|
1643
1723
|
minimumSearchLength,
|
|
1644
1724
|
getCustomerType: () => customerTypeRef.current
|
|
@@ -1646,7 +1726,17 @@ function CommerceSearch({
|
|
|
1646
1726
|
[country, language, minimumSearchLength]
|
|
1647
1727
|
);
|
|
1648
1728
|
const navigateToPath = useCallback(
|
|
1649
|
-
(path) =>
|
|
1729
|
+
(path) => {
|
|
1730
|
+
const target = toNavigationTarget(path);
|
|
1731
|
+
if (target) {
|
|
1732
|
+
navigate(target);
|
|
1733
|
+
} else {
|
|
1734
|
+
console.warn(
|
|
1735
|
+
"[search] Not navigating to a path the SDK does not recognize:",
|
|
1736
|
+
path
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
},
|
|
1650
1740
|
[navigate]
|
|
1651
1741
|
);
|
|
1652
1742
|
const { isAuthenticated, accountType, userId } = customer;
|