@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/dist/index.d.ts CHANGED
@@ -1,647 +1,60 @@
1
- import React from 'react';
2
- import * as zustand_middleware from 'zustand/middleware';
3
- import * as zustand from 'zustand';
1
+ import { ReactNode, ReactElement } from 'react';
4
2
 
5
3
  /**
6
- * Shared TypeScript types for the search SDK.
7
- *
8
- * Kept intentionally permissive at the boundary (`unknown`, `[key: string]: unknown`)
9
- * to preserve the drop-in behavior of the ported JavaScript service. Downstream
10
- * consumers can narrow at their integration point.
11
- */
12
-
13
- /** Normalised locale parts as consumed by the SDK. */
14
- interface LocaleParts {
15
- /** ISO 3166-1 alpha-2, upper-case. Example: `"US"`. */
16
- country: string;
17
- /** ISO 639-1, lower-case. Example: `"en"`. */
18
- language: string;
19
- /** Full Contentstack-style locale string. Example: `"US-en"`. */
20
- locale: string;
21
- }
22
- /**
23
- * Shape returned by the market-config lookup. Only the fields the SDK reads
24
- * are typed; consumers may pass through additional properties.
25
- */
26
- interface MarketConfig {
27
- api_gateway_path?: string;
28
- store_id?: string | number;
29
- [key: string]: unknown;
30
- }
31
- /** Callback the consumer wires in to resolve market config on demand. */
32
- type GetMarketConfig = (context: {
33
- locationLike: LocationLike | null;
34
- country: string;
35
- }) => Promise<MarketConfig | null | undefined>;
36
- /**
37
- * The subset of the DOM `Location` interface the SDK depends on. Every
38
- * property is optional so tests can pass minimal fakes.
39
- */
40
- interface LocationLike {
41
- origin?: string;
42
- hostname?: string;
43
- pathname?: string;
44
- search?: string;
45
- href?: string;
46
- assign?: (url: string) => void;
47
- }
48
- /**
49
- * A raw suggestion / option payload used for navigation. Fields mirror the
50
- * shapes the original service defensively read from — none are required.
51
- */
52
- interface SuggestionOption {
53
- slug?: string | undefined;
54
- productSlug?: string | undefined;
55
- product_slug?: string | undefined;
56
- href?: string | undefined;
57
- url?: string | undefined;
58
- value?: string | undefined;
59
- label?: string | undefined;
60
- identifier?: string | undefined;
61
- [key: string]: unknown;
62
- }
63
- /** Response shape returned by `fetchSuggestions`. */
64
- interface SuggestionsResponse {
65
- /** Suggestions array. May be empty when the API returns no matches. */
66
- suggestions: unknown[];
67
- /** "Did you mean?" alternate-query suggestions. */
68
- didYouMean: unknown[];
69
- /** Total match count reported by the API. */
70
- totalResultsCount: number;
71
- /** Original product array from the catalog API. */
72
- product?: unknown;
73
- /** Pageable metadata from the catalog API. */
74
- pageableInfo?: {
75
- totalCount?: number;
76
- } & Record<string, unknown>;
77
- /** Original snake_case field name from some API variants. */
78
- did_you_mean?: unknown;
79
- [key: string]: unknown;
80
- }
81
- /** Copy strings surfaced to the input chrome. */
82
- interface SearchChromeCopy {
83
- placeholder: string;
84
- inputAriaLabel: string;
85
- }
86
- /** Mapped product type after normalisation. */
87
- type ProductType = 'bundle' | 'single';
88
- /**
89
- * A catalog suggestion item produced by `mapCatalogProductToSuggestion`.
90
- * All navigation fields are preserved from the original service shape.
91
- */
92
- interface CatalogSuggestionItem {
93
- id: string;
94
- identifier: string;
95
- sku: string;
96
- label: string;
97
- value: string;
98
- imageURL: string;
99
- thumbnail: string;
100
- productType: ProductType;
101
- product_type: ProductType;
102
- outOfStock: boolean;
103
- productStatus: string | undefined;
104
- status: string | undefined;
105
- cartActionDisabled: boolean;
106
- customProductType: string;
107
- slug: string | null;
108
- productSlug: string | null;
109
- url: string | null;
110
- href: string | null;
111
- keywords: string;
112
- ingredientTerms: string[];
113
- }
114
- /**
115
- * A "Did You Mean?" item produced by `normalizeDidYouMean`. Additional
116
- * fields from the raw API response are preserved via index signature.
117
- */
118
- interface DidYouMeanItem {
119
- id: string;
120
- value: string;
121
- suggestionType: 'did-you-mean';
122
- [key: string]: unknown;
123
- }
124
- /** Options accepted by `useSearchSuggestions`. */
125
- interface UseSearchSuggestionsOptions {
126
- /** The `SearchService` instance created by `createSearchService`. */
127
- service: SearchService;
128
- /**
129
- * When `false` the hook clears state and skips all API calls.
130
- * Useful for suppressing suggestions while the user is typing a question.
131
- * Defaults to `true`.
132
- */
133
- enabled?: boolean;
134
- }
135
- /** State and actions returned by `useSearchSuggestions`. */
136
- interface UseSearchSuggestionsResult {
137
- /**
138
- * Resets the query to `""` and clears all suggestions state.
139
- * Call when the user clicks the Clear button.
140
- */
141
- clearQuery: () => void;
142
- /**
143
- * `true` after a fetch completes with an empty result set.
144
- * Use this to show a "No suggestions" message.
145
- */
146
- emptyFetched: boolean;
147
- /** `true` when the server has more matches than `MAX_VISIBLE_SUGGESTIONS`. */
148
- hasMoreResults: boolean;
149
- /** `true` when the trimmed query is non-empty. */
150
- hasQuery: boolean;
151
- /** `true` when the latest suggestions request failed. */
152
- suggestionFetchFailed: boolean;
153
- /** Normalised "Did You Mean?" items from the API. */
154
- didYouMean: DidYouMeanItem[];
155
- /** `true` while a debounced fetch is in flight. */
156
- loading: boolean;
157
- /** The current query string (unmodified, as typed by the user). */
158
- query: string;
159
- /** Raw React state setter — prefer `updateQuery` for controlled inputs. */
160
- setQuery: React.Dispatch<React.SetStateAction<string>>;
161
- /** Normalised product suggestion items from the catalog API. */
162
- suggestions: CatalogSuggestionItem[];
163
- /** Total match count reported by the Equinox API. */
164
- totalResultsCount: number;
165
- /**
166
- * Preferred setter — updates `query`, optimistically sets `loading`, and
167
- * clears `emptyFetched` before the debounce fires.
168
- */
169
- updateQuery: (query: string) => void;
170
- }
171
- /** Options accepted by `createSearchService`. */
172
- interface SearchServiceOptions {
173
- /** Custom `fetch` implementation. Defaults to the global `fetch` when available. */
174
- fetchImpl?: typeof fetch | null | undefined;
175
- /** DOM `Location`-like object. Defaults to `window.location` in the browser. */
176
- location?: LocationLike | null | undefined;
177
- /** Storage-like object. Defaults to `window.sessionStorage` in the browser. */
178
- storage?: Storage | null | undefined;
179
- /**
180
- * Explicit suggestions URL or path. When provided, overrides the market-config
181
- * lookup. Absolute URLs are used as-is; paths are resolved against
182
- * `location.origin`.
183
- */
184
- suggestionsApi?: string | undefined;
185
- /** Explicit Equinox store ID. When provided, overrides the market-config lookup. */
186
- storeId?: string | number | undefined;
187
- /** ISO 3166-1 alpha-2 country code. Normalised to upper-case. */
188
- country?: string | undefined;
189
- /** ISO 639-1 language code. Normalised to lower-case. */
190
- language?: string | undefined;
191
- /** Full Contentstack-style locale string. Falls back to `"UNKNOWN"`. */
192
- locale?: string | undefined;
193
- /** Placeholder text for the expanded input. */
194
- searchPlaceholder?: string | undefined;
195
- /** Aria-label for the expanded input. */
196
- inputAriaLabel?: string | undefined;
197
- /**
198
- * Async callback resolving to a `MarketConfig`. Required for automatic
199
- * suggestion-URL/storeId resolution when `suggestionsApi`/`storeId` are
200
- * not passed explicitly.
201
- */
202
- getMarketConfig?: GetMarketConfig | undefined;
203
- /**
204
- * Optional callback invoked when a suggestion action (cart icon click) is activated.
205
- * Allows consumers to implement custom behavior (e.g., add-to-cart) before navigation.
206
- * If this returns `true`, navigation is skipped; if `false` or undefined, normal navigation proceeds.
207
- */
208
- onSuggestionAction?: (option: SuggestionOption) => Promise<boolean> | boolean | undefined;
209
- }
210
- /** Public API returned by `createSearchService`. */
211
- interface SearchService {
212
- /** Returns fallback placeholder + aria-label copy. */
213
- getSearchChromeCopy: () => Promise<SearchChromeCopy>;
214
- /** Fetches typeahead suggestions from the Equinox catalog. */
215
- fetchSuggestions: (searchTerm: string) => Promise<SuggestionsResponse>;
216
- /**
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.
219
- */
220
- submitProductSearch: (query: string, option?: SuggestionOption) => string | undefined;
221
- /**
222
- * Returns the suggestion's URL if one is resolvable; otherwise submits the
223
- * suggestion's textual value as a product search.
224
- */
225
- activateSuggestionAction: (option: SuggestionOption) => string | undefined;
226
- /** Returns the navigable URL for a suggestion option (or `""`). */
227
- getSuggestionHref: (option: SuggestionOption) => string;
228
- /** Returns the search-results page path for `query`. */
229
- viewMoreResults: (input: {
230
- query: string;
231
- }) => string | undefined;
232
- /** Returns the persisted recent searches for the current locale bucket. */
233
- getRecentSearches: () => string[];
234
- /** Removes all persisted recent searches for the current locale bucket. */
235
- clearRecentSearches: () => void;
236
- }
237
-
238
- /**
239
- * SearchApiService — Phase 0 lift-and-shift of
240
- * `header-mfe/src/services/HeaderSearchService.js`.
241
- *
242
- * Behaviour follows the header search contract for URL formats,
243
- * `sessionStorage` key format, fetch parameter shape, and fallback values.
244
- * Navigation side effects are left to the consuming router.
245
- *
246
- * Two responsibilities were intentionally moved OUT of the SDK to preserve
247
- * the SDK-narrow boundary:
248
- * 1. Market-config resolution (`equinoxMarketsConfig.js` — depends on
249
- * `@nuskin/configuration-sdk`). Consumers inject a `getMarketConfig`
250
- * callback if they want automatic URL/store-id resolution.
251
- * 2. Locale resolution from the browser URL (`getLocale`). Consumers must
252
- * pass `country`/`language` explicitly.
253
- */
254
-
255
- /**
256
- * Replaces `{api_name}` in a market-config gateway path with the given API
257
- * name, then trims a single trailing slash. Ported from the equivalent helper
258
- * in `equinoxMarketsConfig.js` so the SDK has no runtime dependency on it.
259
- */
260
- declare function resolveApiGatewayPath(apiGatewayPath: string | undefined | null, apiName?: string): string;
261
- /**
262
- * Creates a search service instance. All options are optional; sensible
263
- * fallbacks are used when the browser environment is absent (SSR).
264
- */
265
- declare function createSearchService(options?: SearchServiceOptions): SearchService;
266
-
267
- /**
268
- * ChatService — two-step NuSkin AI chat API.
269
- *
270
- * Step 1: POST /chat/conversations → receives conversation_id
271
- * Step 2: POST /chat/messages → SSE stream parsed with eventsource-parser
272
- *
273
- * Products are embedded in AI response content as ```products [...] ``` code blocks.
274
- */
275
- interface ChatProduct {
276
- sku: string;
277
- name: string;
278
- category?: string;
279
- pv?: number;
280
- retail_price?: number;
281
- description?: string;
282
- image_url?: string;
283
- badge?: string;
284
- [key: string]: unknown;
285
- }
286
- interface ChatMessageResult {
287
- summary: string;
288
- products: ChatProduct[];
289
- /** Raw accumulated content including markdown, for downstream rendering. */
290
- rawContent: string;
291
- }
292
- interface ChatServiceOptions {
293
- /** POST endpoint for creating a conversation. */
294
- conversationsUrl?: string;
295
- /** POST endpoint for sending a message (SSE response). */
296
- messagesUrl?: string;
297
- /** X-Sales-AI-App-Key header value (from CS configurations). */
298
- salesAiAppKey?: string;
299
- /** X-Sales-AI-Parent-Origin header value (from CS configurations). */
300
- salesAiParentOrigin?: string;
301
- /** Market code (e.g., "US", "JP") for scope_context in message requests. */
302
- market?: string;
303
- /** Language code (e.g., "en", "ja") for scope_context in message requests. */
304
- language?: string;
305
- /** Override fetch for testing. */
306
- fetchImpl?: typeof fetch | null;
307
- }
308
- interface ChatService {
309
- /** Creates a conversation and returns the numeric conversation ID. */
310
- createConversation: (title?: string) => Promise<number>;
311
- /** Sends a message and streams back a parsed result. */
312
- sendMessage: (conversationId: number, content: string, onChunk?: (type: string, text: string) => void) => Promise<ChatMessageResult>;
313
- }
314
- declare function createChatService(options: ChatServiceOptions): ChatService;
315
-
316
- /**
317
- * useSearchSuggestions — Phase 0 lift-and-shift of
318
- * `header-mfe/src/components/search/auto-complete/hooks/useAutoComplete.js`
319
- * (`useAutoCompleteSuggestions` export).
320
- *
321
- * Behaviour is preserved verbatim:
322
- * - 220 ms debounce (DEBOUNCE_MS constant — do not change)
323
- * - Request-ID stale-response guard (replaces AbortController in the original)
324
- * - `MAX_VISIBLE_SUGGESTIONS` cap applied before setting state
325
- * - `didYouMean` items take precedence: when present, `suggestions` is set
326
- * to `[]` and the DYM items are used as the visible list
327
- * - `emptyFetched` is only set to `true` when BOTH arrays are empty after
328
- * a successful fetch
329
- * - `enabled = false` clears state without firing an API call
330
- *
331
- * Keyboard navigation (`useAutoCompleteKeyboard`) and all display-data helpers
332
- * live in `react-search-components` — they depend on DOM refs and component props.
4
+ * Where `CommerceSearch` wants the host to take the shopper: a page inside the app (a path for
5
+ * the host's router, with the market prefix already removed), or somewhere that needs a full
6
+ * page load.
333
7
  */
334
-
335
- /**
336
- * Manages debounced typeahead suggestions from the Equinox catalog API.
337
- *
338
- * @example
339
- * ```tsx
340
- * const service = createSearchService({ country, language, locale, suggestionsApi, storeId });
341
- * const { suggestions, didYouMean, loading, updateQuery, clearQuery } =
342
- * useSearchSuggestions({ service });
343
- * ```
344
- */
345
- declare function useSearchSuggestions({ service, enabled, }: UseSearchSuggestionsOptions): UseSearchSuggestionsResult;
346
-
347
- interface SearchHistoryEntry {
348
- query: string;
349
- isAI: boolean;
350
- /** Serialized AI result payload; consumers cast to their concrete result type */
351
- aiResult?: unknown;
352
- timestamp: number;
353
- }
354
- interface SearchHistoryState {
355
- entries: SearchHistoryEntry[];
356
- /** Save a full entry (AI or non-AI); deduplicates by query (case-insensitive). */
357
- addEntry: (entry: Omit<SearchHistoryEntry, 'timestamp'>) => void;
358
- /** Convenience alias — saves a plain non-AI query entry. */
359
- addQuery: (query: string) => void;
360
- removeEntry: (query: string) => void;
361
- clearAll: () => void;
362
- }
363
- declare const useSearchHistoryStore: zustand.UseBoundStore<Omit<zustand.StoreApi<SearchHistoryState>, "persist"> & {
364
- persist: {
365
- setOptions: (options: Partial<zustand_middleware.PersistOptions<SearchHistoryState, unknown>>) => void;
366
- clearStorage: () => void;
367
- rehydrate: () => Promise<void> | void;
368
- hasHydrated: () => boolean;
369
- onHydrate: (fn: (state: SearchHistoryState) => void) => () => void;
370
- onFinishHydration: (fn: (state: SearchHistoryState) => void) => () => void;
371
- getOptions: () => Partial<zustand_middleware.PersistOptions<SearchHistoryState, unknown>>;
372
- };
373
- }>;
374
-
375
- interface UseSearchHistoryResult {
376
- entries: SearchHistoryEntry[];
377
- /** Derived string list of queries (backward compat) */
378
- queries: string[];
379
- addEntry: (entry: Omit<SearchHistoryEntry, 'timestamp'>) => void;
380
- /** @deprecated prefer addEntry */
381
- addQuery: (query: string) => void;
382
- removeEntry: (query: string) => void;
383
- clearAll: () => void;
384
- }
385
- /** React hook that subscribes to the tab-scoped search history store. */
386
- declare function useSearchHistory(): UseSearchHistoryResult;
387
-
388
- interface SpeechRecognitionErrorEventLike {
389
- error: string;
390
- }
391
- interface UseVoiceSearchOptions {
392
- lang?: string;
393
- onResult: (transcript: string) => void;
394
- onInterimResult?: (transcript: string) => void;
395
- onError?: (error: SpeechRecognitionErrorEventLike) => void;
396
- }
397
- interface UseVoiceSearchResult {
398
- listening: boolean;
399
- supported: boolean;
400
- start: () => void;
401
- stop: () => void;
402
- }
403
- declare function useVoiceSearch({ lang, onInterimResult, onResult, onError, }: UseVoiceSearchOptions): UseVoiceSearchResult;
404
-
405
- /**
406
- * Catalog-product normalisation helpers — Phase 0 lift-and-shift of the
407
- * relevant portions of
408
- * `header-mfe/src/components/search/auto-complete/autoComplete.helpers.js`.
409
- *
410
- * Only the data-processing helpers are included here. UI-specific helpers
411
- * (keyboard event handlers, ARIA attribute computation, display-data
412
- * extraction) live in `react-search-components` because they depend on DOM
413
- * refs and component-level props.
414
- *
415
- * Key difference from the original:
416
- * `resolveSuggestionImageSrc` returns `""` when the source is absent
417
- * instead of falling back to a bundled PNG asset. Components apply their
418
- * own `<img onError>` / CSS placeholder; the SDK must not bundle assets.
419
- */
420
-
421
- /** Returns `true` when `productStatus` is `"sellable"`. */
422
- declare function isSellableProductStatus(productStatus: unknown): boolean;
423
- /**
424
- * Returns `true` when the suggestion option should not allow add-to-cart
425
- * (out of stock, discontinued, inactive, etc.).
426
- */
427
- declare function isCartActionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
428
- /**
429
- * Returns `true` when the suggestion row should be displayed as
430
- * non-interactive (out-of-stock only — a subset of `isCartActionDisabled`).
431
- */
432
- declare function isSuggestionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
433
- /**
434
- * Reads the first non-empty image URL from a Contentstack / catalog property
435
- * bag. Returns `""` when no image is found.
436
- */
437
- declare function getImageFromProperty(properties?: Record<string, unknown>): string;
438
- /**
439
- * Normalises an image source value. Returns `""` when the source is absent or
440
- * blank. Components are responsible for applying a fallback placeholder image.
441
- */
442
- declare function resolveSuggestionImageSrc(imageSrc: unknown): string;
443
- /**
444
- * Extracts a flat list of ingredient terms from a Contentstack ingredient
445
- * structure. Used to populate `CatalogSuggestionItem.ingredientTerms`.
446
- */
447
- declare function getIngredientTerms(rawIngredients: unknown): string[];
448
- /**
449
- * Maps a raw Equinox catalog product object to the normalised
450
- * `CatalogSuggestionItem` shape consumed by the UI components.
451
- * Returns `null` for invalid input so callers can `.filter(Boolean)`.
452
- */
453
- declare function mapCatalogProductToSuggestion(product: unknown, index?: number): CatalogSuggestionItem | null;
454
- /**
455
- * Normalises the raw `didYouMean` / `did_you_mean` array from the Equinox
456
- * API into a consistent shape consumed by `useSearchSuggestions`.
457
- */
458
- declare function normalizeDidYouMean(didYouMean: unknown): DidYouMeanItem[];
459
- /**
460
- * Returns the items that should actually be rendered in the suggestions list.
461
- * When "Did You Mean?" results are present they take precedence over product
462
- * suggestions (matching the original header-mfe behaviour).
463
- */
464
- declare function getVisibleSuggestions(opts: {
465
- didYouMean: DidYouMeanItem[];
466
- suggestions: CatalogSuggestionItem[];
467
- }): DidYouMeanItem[] | CatalogSuggestionItem[];
468
- /**
469
- * Builds the analytics payload emitted when the user clicks "See More Results".
470
- */
471
- declare function getSeeMoreResultsPayload(query: string, totalResultsCount: number, suggestions: CatalogSuggestionItem[]): {
472
- query: string;
473
- source: string;
474
- totalResultsCount: number;
475
- visibleResultsCount: number;
8
+ type NavigationTarget = {
9
+ type: 'internal';
10
+ path: string;
11
+ } | {
12
+ type: 'external';
13
+ url: string;
476
14
  };
477
15
 
478
16
  /**
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).
17
+ * The account types the suggestions API distinguishes, sent as `X-Customer-Type`.
18
+ *
19
+ * TODO: this set is a best guess from the values the storefront sends. Confirm what the
20
+ * suggestions API accepts, experimentally, once the search runs against it.
492
21
  */
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. */
22
+ type CustomerAccountType = 'guest' | 'Brand Affiliate' | 'Retail Customer' | 'Preferred Customer/Member';
23
+ /** Who is searching. */
24
+ interface CommerceSearchCustomer {
25
+ /** Whether the visitor is signed in. The NUi agent is for signed-in visitors only. */
26
+ isAuthenticated: boolean;
27
+ /** Defaults to `guest`. Ignored for a visitor who is not signed in. */
28
+ accountType?: CustomerAccountType;
29
+ /** The signed-in visitor's id, for analytics. */
509
30
  userId?: string;
31
+ }
32
+ interface CommerceSearchProps {
33
+ isOpen: boolean;
34
+ onClose: () => void;
510
35
  /**
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.
36
+ * Takes the shopper to a destination, using the host's router for an internal path and a
37
+ * full page load for an external URL. The panel closes afterwards.
514
38
  */
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;
39
+ navigate: (target: NavigationTarget) => void;
40
+ /** Read on every request, so a change after sign-in is picked up. */
41
+ customer: CommerceSearchCustomer;
42
+ /** ISO 3166-1 alpha-2 market, for example `US`. */
43
+ country: string;
44
+ /** ISO 639-1 language, for example `en`. */
45
+ language: string;
46
+ /** Shown at the top of the panel, for example the brand logo. */
47
+ logo?: ReactNode;
535
48
  }
536
-
537
49
  /**
538
- * Extracts `{ countryCode, language }` from a storefront-shaped path
539
- * (`/{country}/{language}/...`, e.g. `/us/en/product/x`).
50
+ * The commerce website's single search entry point: typeahead and recent searches over the
51
+ * product catalog today, with the NUi agent taking over in the same panel when a signed-in
52
+ * shopper searches for something with no products. What it can answer is not limited to
53
+ * products — it is the commerce site's search, not a product-only search box.
540
54
  *
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
-
605
- /**
606
- * Constants for the search SDK. Ported from
607
- * `header-mfe/src/utils/search/search.constants.js` — only the values the
608
- * SDK itself needs (Phase 0 lift-and-shift). CMS keys, GTM tracking config,
609
- * and mobile-only placeholder rotation constants stay in header-mfe.
610
- */
611
- /** API shopping-context segment used when resolving the Equinox gateway URL. */
612
- declare const STOREFRONT_SHOPPING_CONTEXT = "storefront";
613
- /** Catalog suggestions path appended when the market gateway is not already
614
- * under the storefront shopping context. */
615
- declare const CATALOG_SUGGESTIONS_PATH = "/storefront/catalogs/search/suggestions";
616
- /** Prefix for the `sessionStorage` key that persists recent searches.
617
- * Full key format: `${RECENT_SEARCHES_PREFIX}:{locale}:{COUNTRY}:{lang}`.
618
- * Must not change — existing sessions rely on this key. */
619
- declare const RECENT_SEARCHES_PREFIX = "nuskin:header:recentSearches";
620
- /** Maximum number of recent searches persisted per locale bucket. */
621
- declare const RECENT_SEARCHES_DEFAULT_LIMIT = 10;
622
- /** Equinox suggestion pagination — page is 1-indexed. */
623
- declare const DEFAULT_SUGGESTION_PAGE = 1;
624
- /** Number of suggestion rows requested from the Equinox catalog per call. */
625
- declare const DEFAULT_SUGGESTION_SIZE = 6;
626
- /** Segment used when building the fallback search-results URL. */
627
- declare const DEFAULT_SEARCH_PATH = "/search";
628
- /** Debounce for typeahead input → API dispatch. Preserved from header-mfe. */
629
- declare const DEBOUNCE_MS = 220;
630
- /** Cap on suggestions rendered in the dropdown, regardless of API response size. */
631
- declare const MAX_VISIBLE_SUGGESTIONS = 6;
632
- /** Fallback placeholder shown inside the expanded search input. */
633
- declare const SEARCH_PLACEHOLDER = "Search by product name, ingredient, benefits, or SKU";
634
- /** Fallback placeholder shown on the collapsed search trigger. */
635
- declare const TRIGGER_PLACEHOLDER = "Product Search";
636
- /**
637
- * Full path segment for the AI commerce chat SSE endpoint.
638
- * Appended to the consumer-provided `chatBaseUrl`.
639
- * Source: https://admin.tst.nuskin.ai/api/analytics/v1/embed
55
+ * The package works out the rest itself: the market's catalog lookup, the minimum search length,
56
+ * which NUi app to use, the panel's text and the analytics fields.
640
57
  */
641
- declare const DEFAULT_AI_CHAT_BASE_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed";
642
- /** Default POST endpoint for creating an AI chat conversation. */
643
- declare const DEFAULT_AI_CHAT_CONVERSATIONS_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/conversations";
644
- /** Default POST endpoint for sending AI chat messages. */
645
- declare const DEFAULT_AI_CHAT_MESSAGES_URL = "https://admin.tst.nuskin.ai/api/analytics/v1/embed/messages";
58
+ declare function CommerceSearch({ isOpen, onClose, navigate, customer, country, language, logo, }: Readonly<CommerceSearchProps>): ReactElement;
646
59
 
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 };
60
+ export { CommerceSearch, type CommerceSearchCustomer, type CommerceSearchProps, type CustomerAccountType, type NavigationTarget };