@nuskin/react-search-sdk 1.0.0-trying-again.1 → 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.mts CHANGED
@@ -1,652 +1,60 @@
1
- import React, { ReactNode, ReactElement } from 'react';
2
- import { SearchModalCopy } from '@nuskin/react-search-components';
3
- import { SalesAIChatWidgetProps } from '@nuskin/react-nui-chat-sdk';
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
- /**
160
- * The trimmed query the current results (or failure) belong to; `""` until a
161
- * fetch has finished. Compare with `query.trim()` to know whether the
162
- * results on screen are for the term the user has typed.
163
- */
164
- settledQuery: string;
165
- /** Raw React state setter — prefer `updateQuery` for controlled inputs. */
166
- setQuery: React.Dispatch<React.SetStateAction<string>>;
167
- /** Normalised product suggestion items from the catalog API. */
168
- suggestions: CatalogSuggestionItem[];
169
- /** Total match count reported by the Equinox API. */
170
- totalResultsCount: number;
171
- /**
172
- * Preferred setter — updates `query`, optimistically sets `loading`, and
173
- * clears `emptyFetched` before the debounce fires.
174
- */
175
- updateQuery: (query: string) => void;
176
- }
177
- /** Options accepted by `createSearchService`. */
178
- interface SearchServiceOptions {
179
- /** Custom `fetch` implementation. Defaults to the global `fetch` when available. */
180
- fetchImpl?: typeof fetch | null | undefined;
181
- /** DOM `Location`-like object. Defaults to `window.location` in the browser. */
182
- location?: LocationLike | null | undefined;
183
- /** Storage-like object. Defaults to `window.sessionStorage` in the browser. */
184
- storage?: Storage | null | undefined;
185
- /**
186
- * Explicit suggestions URL or path. When provided, overrides the market-config
187
- * lookup. Absolute URLs are used as-is; paths are resolved against
188
- * `location.origin`.
189
- */
190
- suggestionsApi?: string | undefined;
191
- /** Explicit Equinox store ID. When provided, overrides the market-config lookup. */
192
- storeId?: string | number | undefined;
193
- /** ISO 3166-1 alpha-2 country code. Normalised to upper-case. */
194
- country?: string | undefined;
195
- /** ISO 639-1 language code. Normalised to lower-case. */
196
- language?: string | undefined;
197
- /** Full Contentstack-style locale string. Falls back to `"UNKNOWN"`. */
198
- locale?: string | undefined;
199
- /** Placeholder text for the expanded input. */
200
- searchPlaceholder?: string | undefined;
201
- /** Aria-label for the expanded input. */
202
- inputAriaLabel?: string | undefined;
203
- /**
204
- * Async callback resolving to a `MarketConfig`. Required for automatic
205
- * suggestion-URL/storeId resolution when `suggestionsApi`/`storeId` are
206
- * not passed explicitly.
207
- */
208
- getMarketConfig?: GetMarketConfig | undefined;
209
- /**
210
- * Optional callback invoked when a suggestion action (cart icon click) is activated.
211
- * Allows consumers to implement custom behavior (e.g., add-to-cart) before navigation.
212
- * If this returns `true`, navigation is skipped; if `false` or undefined, normal navigation proceeds.
213
- */
214
- onSuggestionAction?: (option: SuggestionOption) => Promise<boolean> | boolean | undefined;
215
- }
216
- /** Public API returned by `createSearchService`. */
217
- interface SearchService {
218
- /** Returns fallback placeholder + aria-label copy. */
219
- getSearchChromeCopy: () => Promise<SearchChromeCopy>;
220
- /** Fetches typeahead suggestions from the Equinox catalog. */
221
- fetchSuggestions: (searchTerm: string) => Promise<SuggestionsResponse>;
222
- /**
223
- * Records the query in recent searches, then returns the option URL or the
224
- * fallback search-results path for the consuming router to navigate.
225
- */
226
- submitProductSearch: (query: string, option?: SuggestionOption) => string | undefined;
227
- /**
228
- * Returns the suggestion's URL if one is resolvable; otherwise submits the
229
- * suggestion's textual value as a product search.
230
- */
231
- activateSuggestionAction: (option: SuggestionOption) => string | undefined;
232
- /** Returns the navigable URL for a suggestion option (or `""`). */
233
- getSuggestionHref: (option: SuggestionOption) => string;
234
- /** Returns the search-results page path for `query`. */
235
- viewMoreResults: (input: {
236
- query: string;
237
- }) => string | undefined;
238
- /** Returns the persisted recent searches for the current locale bucket. */
239
- getRecentSearches: () => string[];
240
- /** Removes all persisted recent searches for the current locale bucket. */
241
- clearRecentSearches: () => void;
242
- }
243
-
244
- /**
245
- * SearchApiService — Phase 0 lift-and-shift of
246
- * `header-mfe/src/services/HeaderSearchService.js`.
247
- *
248
- * Behaviour follows the header search contract for URL formats,
249
- * `sessionStorage` key format, fetch parameter shape, and fallback values.
250
- * Navigation side effects are left to the consuming router.
251
- *
252
- * Two responsibilities were intentionally moved OUT of the SDK to preserve
253
- * the SDK-narrow boundary:
254
- * 1. Market-config resolution (`equinoxMarketsConfig.js` — depends on
255
- * `@nuskin/configuration-sdk`). Consumers inject a `getMarketConfig`
256
- * callback if they want automatic URL/store-id resolution.
257
- * 2. Locale resolution from the browser URL (`getLocale`). Consumers must
258
- * pass `country`/`language` explicitly.
259
- */
260
-
261
- /**
262
- * Replaces `{api_name}` in a market-config gateway path with the given API
263
- * name, then trims a single trailing slash. Ported from the equivalent helper
264
- * in `equinoxMarketsConfig.js` so the SDK has no runtime dependency on it.
265
- */
266
- declare function resolveApiGatewayPath(apiGatewayPath: string | undefined | null, apiName?: string): string;
267
- /**
268
- * Creates a search service instance. All options are optional; sensible
269
- * fallbacks are used when the browser environment is absent (SSR).
270
- */
271
- declare function createSearchService(options?: SearchServiceOptions): SearchService;
272
-
273
- /**
274
- * useSearchSuggestions — Phase 0 lift-and-shift of
275
- * `header-mfe/src/components/search/auto-complete/hooks/useAutoComplete.js`
276
- * (`useAutoCompleteSuggestions` export).
277
- *
278
- * Behaviour is preserved verbatim:
279
- * - 220 ms debounce (DEBOUNCE_MS constant — do not change)
280
- * - Request-ID stale-response guard (replaces AbortController in the original)
281
- * - `MAX_VISIBLE_SUGGESTIONS` cap applied before setting state
282
- * - `didYouMean` items take precedence: when present, `suggestions` is set
283
- * to `[]` and the DYM items are used as the visible list
284
- * - `emptyFetched` is only set to `true` when BOTH arrays are empty after
285
- * a successful fetch
286
- * - `enabled = false` clears state without firing an API call
287
- *
288
- * Keyboard navigation (`useAutoCompleteKeyboard`) and all display-data helpers
289
- * live in `react-search-components` — they depend on DOM refs and component props.
290
- */
291
-
292
- /**
293
- * Manages debounced typeahead suggestions from the Equinox catalog API.
294
- *
295
- * @example
296
- * ```tsx
297
- * const service = createSearchService({ country, language, locale, suggestionsApi, storeId });
298
- * const { suggestions, didYouMean, loading, updateQuery, clearQuery } =
299
- * useSearchSuggestions({ service });
300
- * ```
301
- */
302
- declare function useSearchSuggestions({ service, enabled, }: UseSearchSuggestionsOptions): UseSearchSuggestionsResult;
303
-
304
- /**
305
- * Catalog-product normalisation helpers — Phase 0 lift-and-shift of the
306
- * relevant portions of
307
- * `header-mfe/src/components/search/auto-complete/autoComplete.helpers.js`.
308
- *
309
- * Only the data-processing helpers are included here. UI-specific helpers
310
- * (keyboard event handlers, ARIA attribute computation, display-data
311
- * extraction) live in `react-search-components` because they depend on DOM
312
- * refs and component-level props.
313
- *
314
- * Key difference from the original:
315
- * `resolveSuggestionImageSrc` returns `""` when the source is absent
316
- * instead of falling back to a bundled PNG asset. Components apply their
317
- * own `<img onError>` / CSS placeholder; the SDK must not bundle assets.
318
- */
319
-
320
- /** Returns `true` when `productStatus` is `"sellable"`. */
321
- declare function isSellableProductStatus(productStatus: unknown): boolean;
322
- /**
323
- * Returns `true` when the suggestion option should not allow add-to-cart
324
- * (out of stock, discontinued, inactive, etc.).
325
- */
326
- declare function isCartActionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
327
- /**
328
- * Returns `true` when the suggestion row should be displayed as
329
- * non-interactive (out-of-stock only — a subset of `isCartActionDisabled`).
330
- */
331
- declare function isSuggestionDisabled(suggestionOption: Record<string, unknown> | null | undefined): boolean;
332
- /**
333
- * Reads the first non-empty image URL from a Contentstack / catalog property
334
- * bag. Returns `""` when no image is found.
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.
335
7
  */
336
- declare function getImageFromProperty(properties?: Record<string, unknown>): string;
337
- /**
338
- * Normalises an image source value. Returns `""` when the source is absent or
339
- * blank. Components are responsible for applying a fallback placeholder image.
340
- */
341
- declare function resolveSuggestionImageSrc(imageSrc: unknown): string;
342
- /**
343
- * Extracts a flat list of ingredient terms from a Contentstack ingredient
344
- * structure. Used to populate `CatalogSuggestionItem.ingredientTerms`.
345
- */
346
- declare function getIngredientTerms(rawIngredients: unknown): string[];
347
- /**
348
- * Maps a raw Equinox catalog product object to the normalised
349
- * `CatalogSuggestionItem` shape consumed by the UI components.
350
- * Returns `null` for invalid input so callers can `.filter(Boolean)`.
351
- */
352
- declare function mapCatalogProductToSuggestion(product: unknown, index?: number): CatalogSuggestionItem | null;
353
- /**
354
- * Normalises the raw `didYouMean` / `did_you_mean` array from the Equinox
355
- * API into a consistent shape consumed by `useSearchSuggestions`.
356
- */
357
- declare function normalizeDidYouMean(didYouMean: unknown): DidYouMeanItem[];
358
- /**
359
- * Returns the items that should actually be rendered in the suggestions list.
360
- * When "Did You Mean?" results are present they take precedence over product
361
- * suggestions (matching the original header-mfe behaviour).
362
- */
363
- declare function getVisibleSuggestions(opts: {
364
- didYouMean: DidYouMeanItem[];
365
- suggestions: CatalogSuggestionItem[];
366
- }): DidYouMeanItem[] | CatalogSuggestionItem[];
367
- /**
368
- * Builds the analytics payload emitted when the user clicks "See More Results".
369
- */
370
- declare function getSeeMoreResultsPayload(query: string, totalResultsCount: number, suggestions: CatalogSuggestionItem[]): {
371
- query: string;
372
- source: string;
373
- totalResultsCount: number;
374
- visibleResultsCount: number;
8
+ type NavigationTarget = {
9
+ type: 'internal';
10
+ path: string;
11
+ } | {
12
+ type: 'external';
13
+ url: string;
375
14
  };
376
15
 
377
16
  /**
378
- * Types + Window augmentation for the GTM (Google Tag Manager) tracking layer.
379
- */
380
- declare global {
381
- interface Window {
382
- dataLayer?: unknown[];
383
- }
384
- }
385
- type SearchGtmUserStatus = 'guest' | 'logged_in';
386
- type SearchGtmSearchType = 'semantic search' | 'AI agent';
387
- /**
388
- * Context shared by every AI-chat GTM event. Values that are unknown at
389
- * emit time should be passed as an empty string so the schema stays stable
390
- * (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.
391
21
  */
392
- interface SearchGtmContext {
393
- /** NUi conversation id (the widget's `onConversationChange`), coerced to string. */
394
- chatId?: string | number;
395
- /** Page type where the chat was initiated (e.g. `plp`, `pdp`, `home`). */
396
- pageType?: string;
397
- /** ISO 3166-1 alpha-2 country code (e.g. `US`, `DE`). */
398
- countryCode?: string;
399
- /** Language code (e.g. `en`, `de`). */
400
- language?: string;
401
- /**
402
- * Whether the visitor is a guest or a logged-in user.
403
- * Falls back to the `IsAuthenticated` localStorage flag written by the
404
- * storefront `Session` model when not supplied by the host.
405
- */
406
- userStatus?: SearchGtmUserStatus;
407
- /** 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. */
408
30
  userId?: string;
409
- /**
410
- * Customer account type (e.g. `Retail Customer`, `Brand Affiliate`).
411
- * Falls back to the `accountType` localStorage value written by the
412
- * storefront `Customer` / `MyAccount` flows when not supplied.
413
- */
414
- accountType?: string;
415
- }
416
- /** Payload for the Search_Type event (query the user submitted). */
417
- interface SearchTypeEventInput extends SearchGtmContext {
418
- /** Type of AI search: `semantic search` or `AI agent`. */
419
- searchType?: SearchGtmSearchType;
420
- /** User's raw search query / response text. */
421
- searchResponse?: string;
422
- }
423
- /** Payload for the ProductSearch event (results returned to the user). */
424
- interface ProductSearchEventInput extends SearchGtmContext {
425
- searchType?: SearchGtmSearchType;
426
- /** Number of product results shown to the user. */
427
- productResults?: number;
428
- /** Raw products payload to send into Product_results as JSON string. */
429
- productResultsData?: unknown;
430
- }
431
- /** Payload for the Error_popup event. */
432
- interface ErrorPopupEventInput extends SearchGtmContext {
433
- searchType?: SearchGtmSearchType;
434
- }
435
-
436
- /** How the host's NUi app is reached and presented. */
437
- interface NuiAgentConfig {
438
- /** The NUi app key for the search surface. */
439
- appKey: string;
440
- /** Origin of the NUi API. */
441
- apiBaseUrl: string;
442
- /** UI language, e.g. `en-US`. */
443
- language?: string;
444
- /** Widget theme. Defaults to `nui-light`. */
445
- theme?: SalesAIChatWidgetProps['theme'];
446
- /** Context sent with every turn of the conversation, under the handoff context. */
447
- context?: Record<string, unknown>;
448
- /** Lets the host draw the agent's product recommendations. */
449
- renderProduct?: SalesAIChatWidgetProps['renderProduct'];
450
31
  }
451
-
452
- /** How long a submit waits for the typeahead of that exact term before it stays on search. */
453
- declare const SUBMIT_WAIT_TIMEOUT_MS = 2000;
454
- interface ProductSearchProps {
32
+ interface CommerceSearchProps {
455
33
  isOpen: boolean;
456
34
  onClose: () => void;
457
- /** From `createSearchService`. Keep its identity stable (memoize it). */
458
- service: SearchService;
459
- /** Takes the shopper to a path, using the host's router. The panel closes afterwards. */
460
- navigate: (path: string) => void;
461
- /** Whether the visitor is signed in (the storefront's `customerData.isAuthenticated`). */
462
- isAuthenticated: boolean;
463
- /** The NUi app the agent runs on. */
464
- agent: NuiAgentConfig;
35
+ /**
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.
38
+ */
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. */
465
47
  logo?: ReactNode;
466
- cancelLabel?: string;
467
- fallbackImageUrl?: string;
468
- copy?: SearchModalCopy;
469
- /** Host-known values for the search analytics events. */
470
- gtmContext?: Omit<SearchGtmContext, 'chatId'>;
471
- }
472
- /**
473
- * Product search with an agent fallback.
474
- *
475
- * Shows Solr typeahead and recent searches in the library's `SearchModal`. When a signed-in
476
- * shopper submits a term whose typeahead shows no products, the NUi agent takes over in the same
477
- * panel with that term as the first message. Everything else behaves as plain search.
478
- */
479
- declare function ProductSearch({ isOpen, onClose, service, navigate, isAuthenticated, agent, logo, cancelLabel, fallbackImageUrl, copy, gtmContext, }: Readonly<ProductSearchProps>): ReactElement;
480
-
481
- /**
482
- * The enable gate for NUi search.
483
- *
484
- * NUi search is on only for signed-in visitors who have switched it on with
485
- * the `enableNuiSearch` flag, set by a cookie or by a URL query parameter
486
- * (`?enableNuiSearch=true` or `=false`). The query parameter also writes the
487
- * cookie, so the choice sticks while the visitor navigates. Off by default.
488
- *
489
- * The flag is a client-side feature toggle, not a security control. Real
490
- * per-market and per-locale switching is a later step.
491
- */
492
- /** Name shared by the cookie and the URL query parameter. */
493
- declare const NUI_SEARCH_FLAG = "enableNuiSearch";
494
- /** Where the flag is read from and written to. Defaults to the browser. */
495
- interface NuiSearchFlagEnv {
496
- /** Query string, with or without the leading `?`. */
497
- search?: string;
498
- /** Cookie string, as in `document.cookie`. */
499
- cookie?: string;
500
- /** Called with a cookie string to store when the query parameter is set. */
501
- setCookie?: (cookie: string) => void;
502
- }
503
- /**
504
- * Resolves the flag. A valid query parameter wins over the cookie and is
505
- * written back to the cookie; otherwise the cookie decides. `undefined` means
506
- * it was never set. Safe to call during server rendering.
507
- */
508
- declare function resolveNuiSearchFlag(env?: NuiSearchFlagEnv): boolean | undefined;
509
- interface IsNuiSearchEnabledInput {
510
- /** From the host (the storefront's `customerData.isAuthenticated`). */
511
- isAuthenticated: boolean | undefined;
512
- /** The resolved flag; see `resolveNuiSearchFlag`. */
513
- flag: boolean | undefined;
514
- }
515
- /** On only when the visitor is signed in and the flag is explicitly on. */
516
- declare function isNuiSearchEnabled({ isAuthenticated, flag, }: IsNuiSearchEnabledInput): boolean;
517
-
518
- /**
519
- * Routing between Solr search and the NUi agent.
520
- *
521
- * Rule: when the shopper submits a search and the typeahead response for that
522
- * exact term shows no products, the NUi widget takes over in the same panel.
523
- * Anything else stays plain search. A pure function, so a smarter intent
524
- * heuristic can replace it later without touching any UI.
525
- */
526
-
527
- /**
528
- * - `search` — stay on Solr search (submit behaves as it does today).
529
- * - `handoff` — hand the query to the NUi agent.
530
- * - `wait` — the typeahead for this term has not settled yet; ask again when
531
- * the snapshot changes. Callers should apply their own timeout and
532
- * fall back to `search`, so a slow request never blocks a submit.
533
- */
534
- type SearchRoute = 'search' | 'handoff' | 'wait';
535
- /** The slice of `useSearchSuggestions` state the rule reads. */
536
- type RoutingSnapshot = Pick<UseSearchSuggestionsResult, 'query' | 'loading' | 'settledQuery' | 'suggestions' | 'suggestionFetchFailed'>;
537
- interface DecideSearchRouteInput {
538
- /** Whether NUi search is on for this visitor (see `isNuiSearchEnabled`). */
539
- enabled: boolean;
540
- snapshot: RoutingSnapshot;
541
48
  }
542
49
  /**
543
- * Decides what a submitted search does.
544
- *
545
- * "No products" means the hook has nothing to show for the term. A response
546
- * that only offers "did you mean" terms counts as no products: the hook hides
547
- * the products in that case, so the shopper sees none either. A failed request
548
- * is not "no products": an outage must not send every shopper to the agent.
549
- */
550
- declare function decideSearchRoute({ enabled, snapshot, }: DecideSearchRouteInput): SearchRoute;
551
-
552
- /**
553
- * Extracts `{ countryCode, language }` from a storefront-shaped path
554
- * (`/{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.
555
54
  *
556
- * Reads `window.location.pathname` when called with no argument. Returns `{}`
557
- * if the first two path segments are not both 2-letter locale codes.
558
- */
559
- declare function getLocaleFromUrl(pathname?: string): {
560
- countryCode?: string;
561
- language?: string;
562
- };
563
-
564
- /**
565
- * GTM constants — event names, storage keys, and enum-like value maps.
566
- * Types are declared in `./gtmTypes` and imported here only for `satisfies`.
567
- */
568
- declare const SEARCH_GTM_EVENTS: {
569
- readonly CHAT_ENTRY_POINT: "Begin_chat";
570
- readonly SEARCH_TYPE: "Search_Type";
571
- readonly PRODUCT_SEARCH: "ProductSearch";
572
- readonly ERROR_POPUP: "Error_popup";
573
- };
574
- declare const SEARCH_GTM_SEARCH_TYPES: {
575
- readonly SEMANTIC: "semantic search";
576
- readonly AI_AGENT: "AI agent";
577
- };
578
- declare const SEARCH_GTM_USER_STATUS: {
579
- readonly GUEST: "guest";
580
- readonly LOGGED_IN: "logged_in";
581
- };
582
-
583
- /**
584
- * GTM (Google Tag Manager) dataLayer tracking for the AI Chat Bot.
585
- *
586
- * Implements the events specified in COMX-3277:
587
- * 1. Chatentry_Point - user initiates the AI chat from an entry point
588
- * 2. Search_Type - user submits a query in the AI chat
589
- * 3. ProductSearch - AI chat returns product results (fires even on 0)
590
- * 4. Error_popup - API errors / unhandled exceptions during a session
591
- *
592
- * All UI repos (react-search-components, and any host consuming the SDK)
593
- * should call these helpers instead of touching `window.dataLayer` directly,
594
- * so that payload shape and error handling stay consistent.
595
- *
596
- * Types live in `./gtmTypes`, constants in `./gtmConstants`, and generic
597
- * helpers in `./commonUtils` / `./storage`.
598
- */
599
-
600
- /**
601
- * Pushes a raw event object onto `window.dataLayer`.
602
- *
603
- * Silently no-ops when `window` is unavailable (SSR) or the dataLayer is
604
- * missing - GTM tag firing is a best-effort side effect, never a blocker for
605
- * the search flow.
606
- */
607
- declare function pushGtmEvent(event: Record<string, unknown>): void;
608
- /** Fires `Begin_chat` when the user initiates the AI chat. */
609
- declare function trackChatEntryPoint(ctx: SearchGtmContext): void;
610
- /** Fires `Search_Type` when the user submits a query in the AI chat. */
611
- declare function trackSearchType(input: SearchTypeEventInput): void;
612
- /**
613
- * Fires `ProductSearch` when the AI chat returns results.
614
- * Must be called even when 0 products are returned.
615
- */
616
- declare function trackProductSearch(input: ProductSearchEventInput): void;
617
- /** Fires `Error_popup` for API errors and unhandled exceptions during chat. */
618
- declare function trackErrorPopup(input: ErrorPopupEventInput): void;
619
-
620
- /**
621
- * Constants for the search SDK. Ported from
622
- * `header-mfe/src/utils/search/search.constants.js` — only the values the
623
- * SDK itself needs (Phase 0 lift-and-shift). CMS keys, GTM tracking config,
624
- * and mobile-only placeholder rotation constants stay in header-mfe.
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.
625
57
  */
626
- /** API shopping-context segment used when resolving the Equinox gateway URL. */
627
- declare const STOREFRONT_SHOPPING_CONTEXT = "storefront";
628
- /** Catalog suggestions path appended when the market gateway is not already
629
- * under the storefront shopping context. */
630
- declare const CATALOG_SUGGESTIONS_PATH = "/storefront/catalogs/search/suggestions";
631
- /** Prefix for the `sessionStorage` key that persists recent searches.
632
- * Full key format: `${RECENT_SEARCHES_PREFIX}:{locale}:{COUNTRY}:{lang}`.
633
- * Must not change — existing sessions rely on this key. */
634
- declare const RECENT_SEARCHES_PREFIX = "nuskin:header:recentSearches";
635
- /** Maximum number of recent searches persisted per locale bucket. */
636
- declare const RECENT_SEARCHES_DEFAULT_LIMIT = 10;
637
- /** Equinox suggestion pagination — page is 1-indexed. */
638
- declare const DEFAULT_SUGGESTION_PAGE = 1;
639
- /** Number of suggestion rows requested from the Equinox catalog per call. */
640
- declare const DEFAULT_SUGGESTION_SIZE = 6;
641
- /** Segment used when building the fallback search-results URL. */
642
- declare const DEFAULT_SEARCH_PATH = "/search";
643
- /** Debounce for typeahead input → API dispatch. Preserved from header-mfe. */
644
- declare const DEBOUNCE_MS = 220;
645
- /** Cap on suggestions rendered in the dropdown, regardless of API response size. */
646
- declare const MAX_VISIBLE_SUGGESTIONS = 6;
647
- /** Fallback placeholder shown inside the expanded search input. */
648
- declare const SEARCH_PLACEHOLDER = "Search by product name, ingredient, benefits, or SKU";
649
- /** Fallback placeholder shown on the collapsed search trigger. */
650
- declare const TRIGGER_PLACEHOLDER = "Product Search";
58
+ declare function CommerceSearch({ isOpen, onClose, navigate, customer, country, language, logo, }: Readonly<CommerceSearchProps>): ReactElement;
651
59
 
652
- export { CATALOG_SUGGESTIONS_PATH, type CatalogSuggestionItem, DEBOUNCE_MS, DEFAULT_SEARCH_PATH, DEFAULT_SUGGESTION_PAGE, DEFAULT_SUGGESTION_SIZE, type DecideSearchRouteInput, type DidYouMeanItem, type ErrorPopupEventInput, type GetMarketConfig, type IsNuiSearchEnabledInput, type LocaleParts, type LocationLike, MAX_VISIBLE_SUGGESTIONS, type MarketConfig, NUI_SEARCH_FLAG, type NuiAgentConfig, type NuiSearchFlagEnv, ProductSearch, type ProductSearchEventInput, type ProductSearchProps, type ProductType, RECENT_SEARCHES_DEFAULT_LIMIT, RECENT_SEARCHES_PREFIX, type RoutingSnapshot, SEARCH_GTM_EVENTS, SEARCH_GTM_SEARCH_TYPES, SEARCH_GTM_USER_STATUS, SEARCH_PLACEHOLDER, STOREFRONT_SHOPPING_CONTEXT, SUBMIT_WAIT_TIMEOUT_MS, type SearchChromeCopy, type SearchGtmContext, type SearchGtmSearchType, type SearchGtmUserStatus, type SearchRoute, type SearchService, type SearchServiceOptions, type SearchTypeEventInput, type SuggestionOption, type SuggestionsResponse, TRIGGER_PLACEHOLDER, type UseSearchSuggestionsOptions, type UseSearchSuggestionsResult, createSearchService, decideSearchRoute, getImageFromProperty, getIngredientTerms, getLocaleFromUrl, getSeeMoreResultsPayload, getVisibleSuggestions, isCartActionDisabled, isNuiSearchEnabled, isSellableProductStatus, isSuggestionDisabled, mapCatalogProductToSuggestion, normalizeDidYouMean, pushGtmEvent, resolveApiGatewayPath, resolveNuiSearchFlag, resolveSuggestionImageSrc, trackChatEntryPoint, trackErrorPopup, trackProductSearch, trackSearchType, useSearchSuggestions };
60
+ export { CommerceSearch, type CommerceSearchCustomer, type CommerceSearchProps, type CustomerAccountType, type NavigationTarget };