@nuskin/react-search-sdk 1.0.0-comx-2233.3 → 1.0.0-comx-2233.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -148,6 +148,8 @@ interface UseSearchSuggestionsResult {
148
148
  hasMoreResults: boolean;
149
149
  /** `true` when the trimmed query is non-empty. */
150
150
  hasQuery: boolean;
151
+ /** `true` when the latest suggestions request failed. */
152
+ suggestionFetchFailed: boolean;
151
153
  /** Normalised "Did You Mean?" items from the API. */
152
154
  didYouMean: DidYouMeanItem[];
153
155
  /** `true` while a debounced fetch is in flight. */
@@ -205,25 +207,25 @@ interface SearchServiceOptions {
205
207
  */
206
208
  onSuggestionAction?: (option: SuggestionOption) => Promise<boolean> | boolean | undefined;
207
209
  }
208
- /** Public API returned by `createSearchService`. Behaviour matches header-mfe. */
210
+ /** Public API returned by `createSearchService`. */
209
211
  interface SearchService {
210
212
  /** Returns fallback placeholder + aria-label copy. */
211
213
  getSearchChromeCopy: () => Promise<SearchChromeCopy>;
212
214
  /** Fetches typeahead suggestions from the Equinox catalog. */
213
215
  fetchSuggestions: (searchTerm: string) => Promise<SuggestionsResponse>;
214
216
  /**
215
- * Records the query in recent searches, then navigates to the option URL
216
- * or to the fallback search-results path. Returns the URL that was used.
217
+ * Records the query in recent searches, then returns the option URL or the
218
+ * fallback search-results path for the consuming router to navigate.
217
219
  */
218
220
  submitProductSearch: (query: string, option?: SuggestionOption) => string | undefined;
219
221
  /**
220
- * Follows the suggestion's URL if one is resolvable; otherwise submits
221
- * the suggestion's textual value as a product search.
222
+ * Returns the suggestion's URL if one is resolvable; otherwise submits the
223
+ * suggestion's textual value as a product search.
222
224
  */
223
225
  activateSuggestionAction: (option: SuggestionOption) => string | undefined;
224
226
  /** Returns the navigable URL for a suggestion option (or `""`). */
225
227
  getSuggestionHref: (option: SuggestionOption) => string;
226
- /** Navigates to the search-results page for `query`. */
228
+ /** Returns the search-results page path for `query`. */
227
229
  viewMoreResults: (input: {
228
230
  query: string;
229
231
  }) => string | undefined;
@@ -473,6 +475,133 @@ declare function getSeeMoreResultsPayload(query: string, totalResultsCount: numb
473
475
  visibleResultsCount: number;
474
476
  };
475
477
 
478
+ /**
479
+ * Types + Window augmentation for the GTM (Google Tag Manager) tracking layer.
480
+ */
481
+ declare global {
482
+ interface Window {
483
+ dataLayer?: unknown[];
484
+ }
485
+ }
486
+ type SearchGtmUserStatus = 'guest' | 'logged_in';
487
+ type SearchGtmSearchType = 'semantic search' | 'AI agent';
488
+ /**
489
+ * Context shared by every AI-chat GTM event. Values that are unknown at
490
+ * emit time should be passed as an empty string so the schema stays stable
491
+ * (matches the placeholder shape the data team provided in COMX-3277).
492
+ */
493
+ interface SearchGtmContext {
494
+ /** Conversation id from ChatService.createConversation, coerced to string. */
495
+ chatId?: string | number;
496
+ /** Page type where the chat was initiated (e.g. `plp`, `pdp`, `home`). */
497
+ pageType?: string;
498
+ /** ISO 3166-1 alpha-2 country code (e.g. `US`, `DE`). */
499
+ countryCode?: string;
500
+ /** Language code (e.g. `en`, `de`). */
501
+ language?: string;
502
+ /**
503
+ * Whether the visitor is a guest or a logged-in user.
504
+ * Falls back to the `IsAuthenticated` localStorage flag written by the
505
+ * storefront `Session` model when not supplied by the host.
506
+ */
507
+ userStatus?: SearchGtmUserStatus;
508
+ /** Authenticated user id, or empty for guests. */
509
+ userId?: string;
510
+ /**
511
+ * Customer account type (e.g. `Retail Customer`, `Brand Affiliate`).
512
+ * Falls back to the `accountType` localStorage value written by the
513
+ * storefront `Customer` / `MyAccount` flows when not supplied.
514
+ */
515
+ accountType?: string;
516
+ }
517
+ /** Payload for the Search_Type event (query the user submitted). */
518
+ interface SearchTypeEventInput extends SearchGtmContext {
519
+ /** Type of AI search: `semantic search` or `AI agent`. */
520
+ searchType?: SearchGtmSearchType;
521
+ /** User's raw search query / response text. */
522
+ searchResponse?: string;
523
+ }
524
+ /** Payload for the ProductSearch event (results returned to the user). */
525
+ interface ProductSearchEventInput extends SearchGtmContext {
526
+ searchType?: SearchGtmSearchType;
527
+ /** Number of product results shown to the user. */
528
+ productResults?: number;
529
+ /** Raw products payload to send into Product_results as JSON string. */
530
+ productResultsData?: unknown;
531
+ }
532
+ /** Payload for the Error_popup event. */
533
+ interface ErrorPopupEventInput extends SearchGtmContext {
534
+ searchType?: SearchGtmSearchType;
535
+ }
536
+
537
+ /**
538
+ * Extracts `{ countryCode, language }` from a storefront-shaped path
539
+ * (`/{country}/{language}/...`, e.g. `/us/en/product/x`).
540
+ *
541
+ * Reads `window.location.pathname` when called with no argument. Returns `{}`
542
+ * if the first two path segments are not both 2-letter locale codes.
543
+ */
544
+ declare function getLocaleFromUrl(pathname?: string): {
545
+ countryCode?: string;
546
+ language?: string;
547
+ };
548
+
549
+ /**
550
+ * GTM constants — event names, storage keys, and enum-like value maps.
551
+ * Types are declared in `./gtmTypes` and imported here only for `satisfies`.
552
+ */
553
+ declare const SEARCH_GTM_EVENTS: {
554
+ readonly CHAT_ENTRY_POINT: "Begin_chat";
555
+ readonly SEARCH_TYPE: "Search_Type";
556
+ readonly PRODUCT_SEARCH: "ProductSearch";
557
+ readonly ERROR_POPUP: "Error_popup";
558
+ };
559
+ declare const SEARCH_GTM_SEARCH_TYPES: {
560
+ readonly SEMANTIC: "semantic search";
561
+ readonly AI_AGENT: "AI agent";
562
+ };
563
+ declare const SEARCH_GTM_USER_STATUS: {
564
+ readonly GUEST: "guest";
565
+ readonly LOGGED_IN: "logged_in";
566
+ };
567
+
568
+ /**
569
+ * GTM (Google Tag Manager) dataLayer tracking for the AI Chat Bot.
570
+ *
571
+ * Implements the events specified in COMX-3277:
572
+ * 1. Chatentry_Point - user initiates the AI chat from an entry point
573
+ * 2. Search_Type - user submits a query in the AI chat
574
+ * 3. ProductSearch - AI chat returns product results (fires even on 0)
575
+ * 4. Error_popup - API errors / unhandled exceptions during a session
576
+ *
577
+ * All UI repos (react-search-components, and any host consuming the SDK)
578
+ * should call these helpers instead of touching `window.dataLayer` directly,
579
+ * so that payload shape and error handling stay consistent.
580
+ *
581
+ * Types live in `./gtmTypes`, constants in `./gtmConstants`, and generic
582
+ * helpers in `./commonUtils` / `./storage`.
583
+ */
584
+
585
+ /**
586
+ * Pushes a raw event object onto `window.dataLayer`.
587
+ *
588
+ * Silently no-ops when `window` is unavailable (SSR) or the dataLayer is
589
+ * missing - GTM tag firing is a best-effort side effect, never a blocker for
590
+ * the search flow.
591
+ */
592
+ declare function pushGtmEvent(event: Record<string, unknown>): void;
593
+ /** Fires `Begin_chat` when the user initiates the AI chat. */
594
+ declare function trackChatEntryPoint(ctx: SearchGtmContext): void;
595
+ /** Fires `Search_Type` when the user submits a query in the AI chat. */
596
+ declare function trackSearchType(input: SearchTypeEventInput): void;
597
+ /**
598
+ * Fires `ProductSearch` when the AI chat returns results.
599
+ * Must be called even when 0 products are returned.
600
+ */
601
+ declare function trackProductSearch(input: ProductSearchEventInput): void;
602
+ /** Fires `Error_popup` for API errors and unhandled exceptions during chat. */
603
+ declare function trackErrorPopup(input: ErrorPopupEventInput): void;
604
+
476
605
  /**
477
606
  * Constants for the search SDK. Ported from
478
607
  * `header-mfe/src/utils/search/search.constants.js` — only the values the
@@ -515,4 +644,4 @@ declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/a
515
644
  /** Default POST endpoint for sending AI chat messages. */
516
645
  declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/messages";
517
646
 
518
- export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, resolveApiGatewayPath, resolveSuggestionImageSrc, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
647
+ export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type ErrorPopupEventInput, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductSearchEventInput, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_GTM_EVENTS, SEARCH_GTM_SEARCH_TYPES, SEARCH_GTM_USER_STATUS, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchGtmContext, type SearchGtmSearchType, type SearchGtmUserStatus, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SearchTypeEventInput, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getLocaleFromUrl, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, pushGtmEvent, resolveApiGatewayPath, resolveSuggestionImageSrc, trackChatEntryPoint, trackErrorPopup, trackProductSearch, trackSearchType, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
package/dist/index.d.ts CHANGED
@@ -148,6 +148,8 @@ interface UseSearchSuggestionsResult {
148
148
  hasMoreResults: boolean;
149
149
  /** `true` when the trimmed query is non-empty. */
150
150
  hasQuery: boolean;
151
+ /** `true` when the latest suggestions request failed. */
152
+ suggestionFetchFailed: boolean;
151
153
  /** Normalised "Did You Mean?" items from the API. */
152
154
  didYouMean: DidYouMeanItem[];
153
155
  /** `true` while a debounced fetch is in flight. */
@@ -205,25 +207,25 @@ interface SearchServiceOptions {
205
207
  */
206
208
  onSuggestionAction?: (option: SuggestionOption) => Promise<boolean> | boolean | undefined;
207
209
  }
208
- /** Public API returned by `createSearchService`. Behaviour matches header-mfe. */
210
+ /** Public API returned by `createSearchService`. */
209
211
  interface SearchService {
210
212
  /** Returns fallback placeholder + aria-label copy. */
211
213
  getSearchChromeCopy: () => Promise<SearchChromeCopy>;
212
214
  /** Fetches typeahead suggestions from the Equinox catalog. */
213
215
  fetchSuggestions: (searchTerm: string) => Promise<SuggestionsResponse>;
214
216
  /**
215
- * Records the query in recent searches, then navigates to the option URL
216
- * or to the fallback search-results path. Returns the URL that was used.
217
+ * Records the query in recent searches, then returns the option URL or the
218
+ * fallback search-results path for the consuming router to navigate.
217
219
  */
218
220
  submitProductSearch: (query: string, option?: SuggestionOption) => string | undefined;
219
221
  /**
220
- * Follows the suggestion's URL if one is resolvable; otherwise submits
221
- * the suggestion's textual value as a product search.
222
+ * Returns the suggestion's URL if one is resolvable; otherwise submits the
223
+ * suggestion's textual value as a product search.
222
224
  */
223
225
  activateSuggestionAction: (option: SuggestionOption) => string | undefined;
224
226
  /** Returns the navigable URL for a suggestion option (or `""`). */
225
227
  getSuggestionHref: (option: SuggestionOption) => string;
226
- /** Navigates to the search-results page for `query`. */
228
+ /** Returns the search-results page path for `query`. */
227
229
  viewMoreResults: (input: {
228
230
  query: string;
229
231
  }) => string | undefined;
@@ -473,6 +475,133 @@ declare function getSeeMoreResultsPayload(query: string, totalResultsCount: numb
473
475
  visibleResultsCount: number;
474
476
  };
475
477
 
478
+ /**
479
+ * Types + Window augmentation for the GTM (Google Tag Manager) tracking layer.
480
+ */
481
+ declare global {
482
+ interface Window {
483
+ dataLayer?: unknown[];
484
+ }
485
+ }
486
+ type SearchGtmUserStatus = 'guest' | 'logged_in';
487
+ type SearchGtmSearchType = 'semantic search' | 'AI agent';
488
+ /**
489
+ * Context shared by every AI-chat GTM event. Values that are unknown at
490
+ * emit time should be passed as an empty string so the schema stays stable
491
+ * (matches the placeholder shape the data team provided in COMX-3277).
492
+ */
493
+ interface SearchGtmContext {
494
+ /** Conversation id from ChatService.createConversation, coerced to string. */
495
+ chatId?: string | number;
496
+ /** Page type where the chat was initiated (e.g. `plp`, `pdp`, `home`). */
497
+ pageType?: string;
498
+ /** ISO 3166-1 alpha-2 country code (e.g. `US`, `DE`). */
499
+ countryCode?: string;
500
+ /** Language code (e.g. `en`, `de`). */
501
+ language?: string;
502
+ /**
503
+ * Whether the visitor is a guest or a logged-in user.
504
+ * Falls back to the `IsAuthenticated` localStorage flag written by the
505
+ * storefront `Session` model when not supplied by the host.
506
+ */
507
+ userStatus?: SearchGtmUserStatus;
508
+ /** Authenticated user id, or empty for guests. */
509
+ userId?: string;
510
+ /**
511
+ * Customer account type (e.g. `Retail Customer`, `Brand Affiliate`).
512
+ * Falls back to the `accountType` localStorage value written by the
513
+ * storefront `Customer` / `MyAccount` flows when not supplied.
514
+ */
515
+ accountType?: string;
516
+ }
517
+ /** Payload for the Search_Type event (query the user submitted). */
518
+ interface SearchTypeEventInput extends SearchGtmContext {
519
+ /** Type of AI search: `semantic search` or `AI agent`. */
520
+ searchType?: SearchGtmSearchType;
521
+ /** User's raw search query / response text. */
522
+ searchResponse?: string;
523
+ }
524
+ /** Payload for the ProductSearch event (results returned to the user). */
525
+ interface ProductSearchEventInput extends SearchGtmContext {
526
+ searchType?: SearchGtmSearchType;
527
+ /** Number of product results shown to the user. */
528
+ productResults?: number;
529
+ /** Raw products payload to send into Product_results as JSON string. */
530
+ productResultsData?: unknown;
531
+ }
532
+ /** Payload for the Error_popup event. */
533
+ interface ErrorPopupEventInput extends SearchGtmContext {
534
+ searchType?: SearchGtmSearchType;
535
+ }
536
+
537
+ /**
538
+ * Extracts `{ countryCode, language }` from a storefront-shaped path
539
+ * (`/{country}/{language}/...`, e.g. `/us/en/product/x`).
540
+ *
541
+ * Reads `window.location.pathname` when called with no argument. Returns `{}`
542
+ * if the first two path segments are not both 2-letter locale codes.
543
+ */
544
+ declare function getLocaleFromUrl(pathname?: string): {
545
+ countryCode?: string;
546
+ language?: string;
547
+ };
548
+
549
+ /**
550
+ * GTM constants — event names, storage keys, and enum-like value maps.
551
+ * Types are declared in `./gtmTypes` and imported here only for `satisfies`.
552
+ */
553
+ declare const SEARCH_GTM_EVENTS: {
554
+ readonly CHAT_ENTRY_POINT: "Begin_chat";
555
+ readonly SEARCH_TYPE: "Search_Type";
556
+ readonly PRODUCT_SEARCH: "ProductSearch";
557
+ readonly ERROR_POPUP: "Error_popup";
558
+ };
559
+ declare const SEARCH_GTM_SEARCH_TYPES: {
560
+ readonly SEMANTIC: "semantic search";
561
+ readonly AI_AGENT: "AI agent";
562
+ };
563
+ declare const SEARCH_GTM_USER_STATUS: {
564
+ readonly GUEST: "guest";
565
+ readonly LOGGED_IN: "logged_in";
566
+ };
567
+
568
+ /**
569
+ * GTM (Google Tag Manager) dataLayer tracking for the AI Chat Bot.
570
+ *
571
+ * Implements the events specified in COMX-3277:
572
+ * 1. Chatentry_Point - user initiates the AI chat from an entry point
573
+ * 2. Search_Type - user submits a query in the AI chat
574
+ * 3. ProductSearch - AI chat returns product results (fires even on 0)
575
+ * 4. Error_popup - API errors / unhandled exceptions during a session
576
+ *
577
+ * All UI repos (react-search-components, and any host consuming the SDK)
578
+ * should call these helpers instead of touching `window.dataLayer` directly,
579
+ * so that payload shape and error handling stay consistent.
580
+ *
581
+ * Types live in `./gtmTypes`, constants in `./gtmConstants`, and generic
582
+ * helpers in `./commonUtils` / `./storage`.
583
+ */
584
+
585
+ /**
586
+ * Pushes a raw event object onto `window.dataLayer`.
587
+ *
588
+ * Silently no-ops when `window` is unavailable (SSR) or the dataLayer is
589
+ * missing - GTM tag firing is a best-effort side effect, never a blocker for
590
+ * the search flow.
591
+ */
592
+ declare function pushGtmEvent(event: Record<string, unknown>): void;
593
+ /** Fires `Begin_chat` when the user initiates the AI chat. */
594
+ declare function trackChatEntryPoint(ctx: SearchGtmContext): void;
595
+ /** Fires `Search_Type` when the user submits a query in the AI chat. */
596
+ declare function trackSearchType(input: SearchTypeEventInput): void;
597
+ /**
598
+ * Fires `ProductSearch` when the AI chat returns results.
599
+ * Must be called even when 0 products are returned.
600
+ */
601
+ declare function trackProductSearch(input: ProductSearchEventInput): void;
602
+ /** Fires `Error_popup` for API errors and unhandled exceptions during chat. */
603
+ declare function trackErrorPopup(input: ErrorPopupEventInput): void;
604
+
476
605
  /**
477
606
  * Constants for the search SDK. Ported from
478
607
  * `header-mfe/src/utils/search/search.constants.js` — only the values the
@@ -515,4 +644,4 @@ declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/a
515
644
  /** Default POST endpoint for sending AI chat messages. */
516
645
  declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/messages";
517
646
 
518
- export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, resolveApiGatewayPath, resolveSuggestionImageSrc, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
647
+ export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, type ChatMessageResult, type ChatProduct, type ChatService, type ChatServiceOptions, DEBOUNCE_MS, DEFAULT_AI_CHAT_BASE_URL, DEFAULT_AI_CHAT_CONVERSATIONS_URL, DEFAULT_AI_CHAT_MESSAGES_URL, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DidYouMeanItem, type ErrorPopupEventInput, type GetMarketConfig, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, type ProductSearchEventInput, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, SEARCH_GTM_EVENTS, SEARCH_GTM_SEARCH_TYPES, SEARCH_GTM_USER_STATUS, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, type SearchChromeCopy, type SearchGtmContext, type SearchGtmSearchType, type SearchGtmUserStatus, type SearchHistoryEntry, type SearchHistoryState, type SearchHistoryEntry as SearchHistoryStoreEntry, type SearchService, type SearchServiceOptions, type SearchTypeEventInput, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchHistoryResult, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, type UseVoiceSearchOptions, type UseVoiceSearchResult, createChatService, createSearchService, getImageFromProperty, getIngredientTerms, getLocaleFromUrl, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, pushGtmEvent, resolveApiGatewayPath, resolveSuggestionImageSrc, trackChatEntryPoint, trackErrorPopup, trackProductSearch, trackSearchType, useSearchHistory, useSearchHistoryStore, useSearchSuggestions, useVoiceSearch };
package/dist/index.js CHANGED
@@ -1254,6 +1254,7 @@ function useSearchSuggestions({
1254
1254
  const [didYouMean, setDidYouMean] = React.useState([]);
1255
1255
  const [loading, setLoading] = React.useState(false);
1256
1256
  const [emptyFetched, setEmptyFetched] = React.useState(false);
1257
+ const [suggestionFetchFailed, setSuggestionFetchFailed] = React.useState(false);
1257
1258
  const [hasMoreResults, setHasMoreResults] = React.useState(false);
1258
1259
  const [totalResultsCount, setTotalResultsCount] = React.useState(0);
1259
1260
  const fetchDebounced = React.useRef(null);
@@ -1262,6 +1263,7 @@ function useSearchSuggestions({
1262
1263
  setSuggestions([]);
1263
1264
  setDidYouMean([]);
1264
1265
  setEmptyFetched(false);
1266
+ setSuggestionFetchFailed(false);
1265
1267
  setHasMoreResults(false);
1266
1268
  setTotalResultsCount(0);
1267
1269
  setLoading(false);
@@ -1270,6 +1272,7 @@ function useSearchSuggestions({
1270
1272
  (nextQuery) => {
1271
1273
  const hasNextQuery = Boolean(nextQuery.trim());
1272
1274
  setEmptyFetched(false);
1275
+ setSuggestionFetchFailed(false);
1273
1276
  setLoading(enabled && hasNextQuery);
1274
1277
  setQuery(nextQuery);
1275
1278
  },
@@ -1289,6 +1292,7 @@ function useSearchSuggestions({
1289
1292
  }
1290
1293
  setLoading(true);
1291
1294
  setEmptyFetched(false);
1295
+ setSuggestionFetchFailed(false);
1292
1296
  try {
1293
1297
  const fetchedSuggestions = await service.fetchSuggestions(trimmedQuery);
1294
1298
  if (requestId !== latestRequestId.current) {
@@ -1311,6 +1315,7 @@ function useSearchSuggestions({
1311
1315
  !hasDidYouMean && effectiveTotal > MAX_VISIBLE_SUGGESTIONS
1312
1316
  );
1313
1317
  setTotalResultsCount(effectiveTotal);
1318
+ setSuggestionFetchFailed(false);
1314
1319
  setEmptyFetched(
1315
1320
  hasDidYouMean ? false : visibleSuggestions.length === 0 && suggestedTerms.length === 0
1316
1321
  );
@@ -1321,6 +1326,7 @@ function useSearchSuggestions({
1321
1326
  setSuggestions([]);
1322
1327
  setDidYouMean([]);
1323
1328
  setEmptyFetched(true);
1329
+ setSuggestionFetchFailed(true);
1324
1330
  setHasMoreResults(false);
1325
1331
  setTotalResultsCount(0);
1326
1332
  } finally {
@@ -1358,6 +1364,7 @@ function useSearchSuggestions({
1358
1364
  loading,
1359
1365
  query,
1360
1366
  setQuery,
1367
+ suggestionFetchFailed,
1361
1368
  suggestions,
1362
1369
  totalResultsCount,
1363
1370
  updateQuery
@@ -1740,6 +1747,188 @@ function useVoiceSearch({
1740
1747
  return { listening, supported, start, stop };
1741
1748
  }
1742
1749
 
1750
+ // src/utils/commonUtils.ts
1751
+ var IS_BROWSER = typeof window !== "undefined";
1752
+ function toEventString(value, fallback = " ") {
1753
+ if (value === void 0 || value === null) return fallback;
1754
+ const str = String(value).trim();
1755
+ return str.length > 0 ? str : fallback;
1756
+ }
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
+
1815
+ // 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
+ function resolveUserId(explicit) {
1842
+ return toEventString(explicit ?? readUserIdFromStorage(), GTM_FALLBACK_NA);
1843
+ }
1844
+ 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
+ return {
1852
+ Chat_ID: toEventString(chatId, GTM_FALLBACK_NA),
1853
+ page_type: toEventString(
1854
+ ctx.pageType ?? documentTitle,
1855
+ GTM_FALLBACK_NA
1856
+ ),
1857
+ country_code: toEventString(
1858
+ ctx.countryCode ?? urlLocale.countryCode,
1859
+ GTM_FALLBACK_NA
1860
+ ),
1861
+ language: toEventString(
1862
+ ctx.language ?? urlLocale.language,
1863
+ GTM_FALLBACK_NA
1864
+ ),
1865
+ user_status: toEventString(
1866
+ resolvedUserStatus,
1867
+ SEARCH_GTM_USER_STATUS.GUEST
1868
+ ),
1869
+ account_type: toEventString(accountType, GTM_FALLBACK_NA)
1870
+ };
1871
+ }
1872
+ function serializeProductResults(productResultsData, productResults) {
1873
+ if (productResultsData !== void 0) {
1874
+ try {
1875
+ return JSON.stringify(productResultsData);
1876
+ } catch {
1877
+ return GTM_FALLBACK_NA;
1878
+ }
1879
+ }
1880
+ if (productResults === void 0 || productResults === null) {
1881
+ return GTM_FALLBACK_NA;
1882
+ }
1883
+ return String(productResults);
1884
+ }
1885
+ function pushGtmEvent(event) {
1886
+ if (!IS_BROWSER) return;
1887
+ try {
1888
+ const layer = window.dataLayer ?? (window.dataLayer = []);
1889
+ layer.push(event);
1890
+ } catch (err) {
1891
+ if (typeof console !== "undefined") {
1892
+ console.warn("[react-search-sdk] pushGtmEvent failed:", err);
1893
+ }
1894
+ }
1895
+ }
1896
+ function trackChatEntryPoint(ctx) {
1897
+ pushGtmEvent({
1898
+ event: SEARCH_GTM_EVENTS.CHAT_ENTRY_POINT,
1899
+ ...baseEventPayload(ctx)
1900
+ });
1901
+ }
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
+ function trackProductSearch(input) {
1912
+ pushGtmEvent({
1913
+ event: SEARCH_GTM_EVENTS.PRODUCT_SEARCH,
1914
+ ...baseEventPayload(input),
1915
+ id: resolveUserId(input.userId),
1916
+ search_type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC,
1917
+ Product_results: serializeProductResults(
1918
+ input.productResultsData,
1919
+ input.productResults
1920
+ )
1921
+ });
1922
+ }
1923
+ function trackErrorPopup(input) {
1924
+ pushGtmEvent({
1925
+ event: SEARCH_GTM_EVENTS.ERROR_POPUP,
1926
+ ...baseEventPayload(input),
1927
+ id: resolveUserId(input.userId),
1928
+ search_type: input.searchType ?? SEARCH_GTM_SEARCH_TYPES.SEMANTIC
1929
+ });
1930
+ }
1931
+
1743
1932
  exports.CATALOG_SUGGESTIONS_PATH = CATALOG_SUGGESTIONS_PATH;
1744
1933
  exports.DEBOUNCE_MS = DEBOUNCE_MS;
1745
1934
  exports.DEFAULT_AI_CHAT_BASE_URL = DEFAULT_AI_CHAT_BASE_URL;
@@ -1751,6 +1940,9 @@ exports.DEFAULT_SUGGESTION_SIZE = DEFAULT_SUGGESTION_SIZE;
1751
1940
  exports.MAX_VISIBLE_SUGGESTIONS = MAX_VISIBLE_SUGGESTIONS;
1752
1941
  exports.RECENT_SEARCHES_DEFAULT_LIMIT = RECENT_SEARCHES_DEFAULT_LIMIT;
1753
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;
1754
1946
  exports.SEARCH_PLACEHOLDER = SEARCH_PLACEHOLDER;
1755
1947
  exports.STOREFRONT_SHOPPING_CONTEXT = STOREFRONT_SHOPPING_CONTEXT;
1756
1948
  exports.TRIGGER_PLACEHOLDER = TRIGGER_PLACEHOLDER;
@@ -1758,6 +1950,7 @@ exports.createChatService = createChatService;
1758
1950
  exports.createSearchService = createSearchService;
1759
1951
  exports.getImageFromProperty = getImageFromProperty;
1760
1952
  exports.getIngredientTerms = getIngredientTerms;
1953
+ exports.getLocaleFromUrl = getLocaleFromUrl;
1761
1954
  exports.getSeeMoreResultsPayload = getSeeMoreResultsPayload;
1762
1955
  exports.getVisibleSuggestions = getVisibleSuggestions;
1763
1956
  exports.isCartActionDisabled = isCartActionDisabled;
@@ -1765,8 +1958,13 @@ exports.isSellableProductStatus = isSellableProductStatus;
1765
1958
  exports.isSuggestionDisabled = isSuggestionDisabled;
1766
1959
  exports.mapCatalogProductToSuggestion = mapCatalogProductToSuggestion;
1767
1960
  exports.normalizeDidYouMean = normalizeDidYouMean;
1961
+ exports.pushGtmEvent = pushGtmEvent;
1768
1962
  exports.resolveApiGatewayPath = resolveApiGatewayPath;
1769
1963
  exports.resolveSuggestionImageSrc = resolveSuggestionImageSrc;
1964
+ exports.trackChatEntryPoint = trackChatEntryPoint;
1965
+ exports.trackErrorPopup = trackErrorPopup;
1966
+ exports.trackProductSearch = trackProductSearch;
1967
+ exports.trackSearchType = trackSearchType;
1770
1968
  exports.useSearchHistory = useSearchHistory;
1771
1969
  exports.useSearchHistoryStore = useSearchHistoryStore;
1772
1970
  exports.useSearchSuggestions = useSearchSuggestions;