@commerce-ai-tool/react 2.4.0

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +16 -0
  4. package/dist/components/CameraCaptureOverlay.d.ts +11 -0
  5. package/dist/components/CameraCaptureOverlay.d.ts.map +1 -0
  6. package/dist/components/CartPanel.d.ts +22 -0
  7. package/dist/components/CartPanel.d.ts.map +1 -0
  8. package/dist/components/CommerceAICheckout.d.ts +15 -0
  9. package/dist/components/CommerceAICheckout.d.ts.map +1 -0
  10. package/dist/components/CommerceAIOrderStatus.d.ts +15 -0
  11. package/dist/components/CommerceAIOrderStatus.d.ts.map +1 -0
  12. package/dist/components/CommerceAISearch.d.ts +43 -0
  13. package/dist/components/CommerceAISearch.d.ts.map +1 -0
  14. package/dist/components/MissionResults.d.ts +13 -0
  15. package/dist/components/MissionResults.d.ts.map +1 -0
  16. package/dist/components/SearchFacets.d.ts +12 -0
  17. package/dist/components/SearchFacets.d.ts.map +1 -0
  18. package/dist/components/VoiceStatusBanner.d.ts +12 -0
  19. package/dist/components/VoiceStatusBanner.d.ts.map +1 -0
  20. package/dist/hooks/useCameraCapture.d.ts +18 -0
  21. package/dist/hooks/useCameraCapture.d.ts.map +1 -0
  22. package/dist/hooks/useCart.d.ts +57 -0
  23. package/dist/hooks/useCart.d.ts.map +1 -0
  24. package/dist/hooks/useCommerceAISearch.d.ts +59 -0
  25. package/dist/hooks/useCommerceAISearch.d.ts.map +1 -0
  26. package/dist/hooks/useRecordingDuration.d.ts +3 -0
  27. package/dist/hooks/useRecordingDuration.d.ts.map +1 -0
  28. package/dist/hooks/useTheme.d.ts +4 -0
  29. package/dist/hooks/useTheme.d.ts.map +1 -0
  30. package/dist/hooks/useVoiceSearch.d.ts +24 -0
  31. package/dist/hooks/useVoiceSearch.d.ts.map +1 -0
  32. package/dist/icons.d.ts +2 -0
  33. package/dist/icons.d.ts.map +1 -0
  34. package/dist/index.cjs +3714 -0
  35. package/dist/index.cjs.map +1 -0
  36. package/dist/index.css +1783 -0
  37. package/dist/index.css.map +1 -0
  38. package/dist/index.d.cts +19 -0
  39. package/dist/index.d.ts +19 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +3702 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/utils/camera.d.ts +7 -0
  44. package/dist/utils/camera.d.ts.map +1 -0
  45. package/dist/vendor-core/client.d.cts +12 -0
  46. package/dist/vendor-core/client.d.ts +12 -0
  47. package/dist/vendor-core/commercetools/facet-color.d.ts +12 -0
  48. package/dist/vendor-core/commercetools/facets.d.ts +31 -0
  49. package/dist/vendor-core/messages/index.d.ts +110 -0
  50. package/dist/vendor-core/payments/types.d.ts +35 -0
  51. package/dist/vendor-core/search/compound-shopping-list.d.ts +6 -0
  52. package/dist/vendor-core/types/index.d.ts +510 -0
  53. package/package.json +84 -0
package/dist/index.js ADDED
@@ -0,0 +1,3702 @@
1
+ "use client";
2
+
3
+ // src/components/CommerceAISearch.tsx
4
+ import { useCallback as useCallback5, useMemo as useMemo3, useRef as useRef7, useState as useState8 } from "react";
5
+ import {
6
+ Camera as Camera2,
7
+ Check as Check2,
8
+ ImageIcon,
9
+ Mic,
10
+ Package as Package3,
11
+ Search,
12
+ SearchX as SearchX2,
13
+ ShoppingCart as ShoppingCart2,
14
+ Square,
15
+ Volume2
16
+ } from "lucide-react";
17
+
18
+ // ../core/dist/client.js
19
+ var CART_SESSION_HEADER = "x-commerce-ai-cart-session";
20
+ var DEFAULT_COMMERCE_AI_SEARCH_MESSAGES = {
21
+ placeholder: "What are you looking for?",
22
+ searchAriaLabel: "Search query",
23
+ productSearchAriaLabel: "Product search",
24
+ searchResultsAriaLabel: "Search results",
25
+ searching: "Searching...",
26
+ noProductsFound: "No products found",
27
+ searchedFor: "Searched for:",
28
+ dropImageToSearch: "Drop image to search",
29
+ voiceSearch: "Voice search",
30
+ stopRecording: "Stop recording",
31
+ searchByCamera: "Search by camera",
32
+ searchByImage: "Search by image",
33
+ replayVoiceSummary: "Replay voice result summary",
34
+ listening: "Listening\u2026",
35
+ tapMicToStop: "Tap mic to stop",
36
+ understandingQuery: "Understanding your query\u2026",
37
+ preparingAudioSummary: "Preparing audio summary\u2026",
38
+ cameraCapture: "Camera capture",
39
+ cameraPreview: "Camera preview",
40
+ capturePhoto: "Capture",
41
+ cancel: "Cancel",
42
+ dismiss: "Dismiss",
43
+ searchFailed: "Search failed",
44
+ imageSearchFailed: "Image search failed",
45
+ couldNotCapturePhoto: "Could not capture photo",
46
+ suggestionsAriaLabel: "Search suggestions",
47
+ loadingSuggestions: "Loading suggestions...",
48
+ noSuggestions: "No suggestions",
49
+ filtersAriaLabel: "Search filters",
50
+ clearFilters: "Clear filters",
51
+ narrowResults: "Narrow your results",
52
+ newSearch: "New search",
53
+ addToCart: "Add to cart",
54
+ cart: "Cart",
55
+ emptyCart: "Your cart is empty",
56
+ removeItem: "Remove item",
57
+ total: "Total",
58
+ each: "each",
59
+ cartAriaLabel: "Shopping cart",
60
+ itemAdded: "Added to cart",
61
+ increaseQuantity: "Increase quantity",
62
+ decreaseQuantity: "Decrease quantity",
63
+ closeCart: "Close cart",
64
+ unableToAddToCart: "This product cannot be added to the cart",
65
+ missionTitle: "Shopping list",
66
+ missionQuantity: "Looking for",
67
+ missionAddAll: "Add {count} top picks to cart",
68
+ missionItemsAdded: "Added to cart",
69
+ missionSelectProduct: "Select this product",
70
+ missionIntentEmpty: "No matching products",
71
+ missionIntentFailed: "Couldn't search this item",
72
+ signIn: "Sign in",
73
+ signInToSyncCart: "Sign in to sync your cart",
74
+ signOut: "Sign out",
75
+ email: "Email",
76
+ password: "Password",
77
+ signedInAs: "Signed in as",
78
+ invalidCredentials: "Invalid email or password",
79
+ signInFailed: "Sign in failed",
80
+ checkout: "Checkout",
81
+ checkoutTitle: "Complete your order",
82
+ orderSummary: "Order summary",
83
+ shippingAddress: "Shipping address",
84
+ billingAddress: "Billing address",
85
+ billingSameAsShipping: "Billing address is the same as shipping",
86
+ continueToDelivery: "Continue to delivery",
87
+ shippingMethod: "Delivery method",
88
+ selectShippingMethod: "Select a delivery method",
89
+ noShippingMethods: "No delivery methods are available for this address",
90
+ completeAddressToContinue: "Add a shipping address to continue",
91
+ selectDeliveryToContinue: "Select a delivery method to continue",
92
+ selectPaymentToContinue: "Select a payment method to continue",
93
+ checkoutStepAddress: "Address",
94
+ checkoutStepDelivery: "Delivery",
95
+ checkoutStepPayment: "Payment",
96
+ paymentMethod: "Payment method",
97
+ selectPaymentMethod: "Select a payment method",
98
+ paymentAuthorized: "Payment authorized",
99
+ paymentDeclined: "Payment was declined",
100
+ continueToPayment: "Continue to payment",
101
+ placeOrder: "Place order",
102
+ placingOrder: "Placing order\u2026",
103
+ orderPlaced: "Order placed",
104
+ viewOrderStatus: "View order status",
105
+ continueShopping: "Continue shopping",
106
+ checkoutFailed: "Checkout failed",
107
+ orderStatus: "Order status",
108
+ orderPlacedAt: "Placed",
109
+ paymentStateLabel: "Payment",
110
+ shipmentStateLabel: "Shipment",
111
+ trackingNumber: "Tracking number",
112
+ carrier: "Carrier",
113
+ noTrackingYet: "Tracking details will appear when the order ships",
114
+ orderNotFound: "Order not found",
115
+ noOrdersYet: "No orders yet",
116
+ yourOrders: "Your orders",
117
+ firstName: "First name",
118
+ lastName: "Last name",
119
+ streetName: "Street address",
120
+ additionalAddress: "Apartment, suite, etc. (optional)",
121
+ postalCode: "Postal code",
122
+ city: "City",
123
+ region: "State or region (optional)",
124
+ country: "Country"
125
+ };
126
+ function resolveCommerceAISearchMessages(overrides) {
127
+ return {
128
+ ...DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
129
+ ...overrides
130
+ };
131
+ }
132
+ var COMPOUND_SHOPPING_LIST_PATTERN = /\band\b|,|\bplus\b|\boraz\b/i;
133
+ function looksLikeCompoundShoppingList(query) {
134
+ return COMPOUND_SHOPPING_LIST_PATTERN.test(query.trim());
135
+ }
136
+ var CSS_NAMED_COLOR_TABLE = `
137
+ Alice Blue:f0f8ff
138
+ Antique White:faebd7
139
+ Aqua:00ffff
140
+ Aquamarine:7fffd4
141
+ Azure:f0ffff
142
+ Beige:f5f5dc
143
+ Bisque:ffe4c4
144
+ Black:000000
145
+ Blanched Almond:ffebcd
146
+ Blue:0000ff
147
+ Blue Violet:8a2be2
148
+ Brown:a52a2a
149
+ Burlywood:deb887
150
+ Cadet Blue:5f9ea0
151
+ Chartreuse:7fff00
152
+ Chocolate:d2691e
153
+ Coral:ff7f50
154
+ Cornflower Blue:6495ed
155
+ Cornsilk:fff8dc
156
+ Crimson:dc143c
157
+ Dark Blue:00008b
158
+ Dark Cyan:008b8b
159
+ Dark Goldenrod:b8860b
160
+ Dark Gray:a9a9a9
161
+ Dark Green:006400
162
+ Dark Khaki:bdb76b
163
+ Dark Magenta:8b008b
164
+ Dark Olive Green:556b2f
165
+ Dark Orange:ff8c00
166
+ Dark Orchid:9932cc
167
+ Dark Red:8b0000
168
+ Dark Salmon:e9967a
169
+ Dark Sea Green:8fbc8f
170
+ Dark Slate Blue:483d8b
171
+ Dark Slate Gray:2f4f4f
172
+ Dark Turquoise:00ced1
173
+ Dark Violet:9400d3
174
+ Deep Pink:ff1493
175
+ Deep Sky Blue:00bfff
176
+ Dim Gray:696969
177
+ Dodger Blue:1e90ff
178
+ Firebrick:b22222
179
+ Floral White:fffaf0
180
+ Forest Green:228b22
181
+ Gainsboro:dcdcdc
182
+ Ghost White:f8f8ff
183
+ Gold:ffd700
184
+ Goldenrod:daa520
185
+ Gray:808080
186
+ Green:008000
187
+ Green Yellow:adff2f
188
+ Honeydew:f0fff0
189
+ Hot Pink:ff69b4
190
+ Indian Red:cd5c5c
191
+ Indigo:4b0082
192
+ Ivory:fffff0
193
+ Khaki:f0e68c
194
+ Lavender:e6e6fa
195
+ Lavender Blush:fff0f5
196
+ Lawn Green:7cfc00
197
+ Lemon Chiffon:fffacd
198
+ Light Blue:add8e6
199
+ Light Coral:f08080
200
+ Light Cyan:e0ffff
201
+ Light Goldenrod Yellow:fafad2
202
+ Light Gray:d3d3d3
203
+ Light Green:90ee90
204
+ Light Pink:ffb6c1
205
+ Light Salmon:ffa07a
206
+ Light Sea Green:20b2aa
207
+ Light Sky Blue:87cefa
208
+ Light Slate Gray:778899
209
+ Light Steel Blue:b0c4de
210
+ Light Yellow:ffffe0
211
+ Lime:00ff00
212
+ Lime Green:32cd32
213
+ Linen:faf0e6
214
+ Magenta:ff00ff
215
+ Maroon:800000
216
+ Medium Aquamarine:66cdaa
217
+ Medium Blue:0000cd
218
+ Medium Orchid:ba55d3
219
+ Medium Purple:9370db
220
+ Medium Sea Green:3cb371
221
+ Medium Slate Blue:7b68ee
222
+ Medium Spring Green:00fa9a
223
+ Medium Turquoise:48d1cc
224
+ Medium Violet Red:c71585
225
+ Midnight Blue:191970
226
+ Mint Cream:f5fffa
227
+ Misty Rose:ffe4e1
228
+ Moccasin:ffe4b5
229
+ Navajo White:ffdead
230
+ Navy:000080
231
+ Old Lace:fdf5e6
232
+ Olive:808000
233
+ Olive Drab:6b8e23
234
+ Orange:ffa500
235
+ Orange Red:ff4500
236
+ Orchid:da70d6
237
+ Pale Goldenrod:eee8aa
238
+ Pale Green:98fb98
239
+ Pale Turquoise:afeeee
240
+ Pale Violet Red:db7093
241
+ Papaya Whip:ffefd5
242
+ Peach Puff:ffdab9
243
+ Peru:cd853f
244
+ Pink:ffc0cb
245
+ Plum:dda0dd
246
+ Powder Blue:b0e0e6
247
+ Purple:800080
248
+ Rebecca Purple:663399
249
+ Red:ff0000
250
+ Rosy Brown:bc8f8f
251
+ Royal Blue:4169e1
252
+ Saddle Brown:8b4513
253
+ Salmon:fa8072
254
+ Sandy Brown:f4a460
255
+ Sea Green:2e8b57
256
+ Seashell:fff5ee
257
+ Sienna:a0522d
258
+ Silver:c0c0c0
259
+ Sky Blue:87ceeb
260
+ Slate Blue:6a5acd
261
+ Slate Gray:708090
262
+ Snow:fffafa
263
+ Spring Green:00ff7f
264
+ Steel Blue:4682b4
265
+ Tan:d2b48c
266
+ Teal:008080
267
+ Thistle:d8bfd8
268
+ Tomato:ff6347
269
+ Turquoise:40e0d0
270
+ Violet:ee82ee
271
+ Wheat:f5deb3
272
+ White:ffffff
273
+ White Smoke:f5f5f5
274
+ Yellow:ffff00
275
+ Yellow Green:9acd32
276
+ `;
277
+ var HEX_COLOR_RE = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
278
+ var NAMED_COLORS = CSS_NAMED_COLOR_TABLE.trim().split("\n").flatMap((line) => {
279
+ const separator = line.indexOf(":");
280
+ if (separator <= 0) {
281
+ return [];
282
+ }
283
+ const name = line.slice(0, separator);
284
+ const hex = line.slice(separator + 1);
285
+ const n = Number.parseInt(hex, 16);
286
+ if (!name || !Number.isFinite(n)) {
287
+ return [];
288
+ }
289
+ return [{ name, r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 }];
290
+ });
291
+ function parseHexRgb(value) {
292
+ const match = HEX_COLOR_RE.exec(value.trim());
293
+ const captured = match?.[1];
294
+ if (!captured) {
295
+ return null;
296
+ }
297
+ const hex = captured.length === 3 ? `${captured[0]}${captured[0]}${captured[1]}${captured[1]}${captured[2]}${captured[2]}` : captured;
298
+ const n = Number.parseInt(hex, 16);
299
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
300
+ }
301
+ var MAX_NAMED_COLOR_DISTANCE_SQ = 40 * 40;
302
+ var COLOR_LIKE_FACET_NAME = /(?:^|[-_])colou?r(?:[-_]|$)|(?:^|[-_])finish(?:[-_]|$)/i;
303
+ function isColorLikeFacetName(name) {
304
+ return COLOR_LIKE_FACET_NAME.test(name.trim());
305
+ }
306
+ function hexColorSwatchValue(value) {
307
+ const rgb = parseHexRgb(value);
308
+ if (!rgb) {
309
+ return void 0;
310
+ }
311
+ return `#${rgb.map((channel) => channel.toString(16).padStart(2, "0")).join("")}`;
312
+ }
313
+ var PRICE_FACET_ID = "price";
314
+ var CATEGORIES_FACET_ID = "categories";
315
+ var DEFAULT_PRICE_RANGES = [
316
+ { key: "under-50", to: 5e3 },
317
+ { key: "50-to-100", from: 5e3, to: 1e4 },
318
+ { key: "100-plus", from: 1e4 }
319
+ ];
320
+ function priceRangeToFilterValues(range) {
321
+ const filters = {};
322
+ if ("from" in range && range.from !== void 0) {
323
+ filters.priceMin = String(range.from / 100);
324
+ }
325
+ if ("to" in range && range.to !== void 0) {
326
+ filters.priceMax = String(range.to / 100);
327
+ }
328
+ return filters;
329
+ }
330
+ function priceBucketKeyFromFilters(filters) {
331
+ const minCents = parseMajorToCents(filters.priceMin);
332
+ const maxCents = parseMajorToCents(filters.priceMax);
333
+ for (const range of DEFAULT_PRICE_RANGES) {
334
+ const from = "from" in range ? range.from : void 0;
335
+ const to = "to" in range ? range.to : void 0;
336
+ if (minCents === from && maxCents === to) {
337
+ return range.key;
338
+ }
339
+ }
340
+ return void 0;
341
+ }
342
+ function isFacetFilterSelected(filters, facetId, bucketKey) {
343
+ if (facetId === CATEGORIES_FACET_ID) {
344
+ return filters.category === bucketKey;
345
+ }
346
+ if (facetId === PRICE_FACET_ID) {
347
+ return priceBucketKeyFromFilters(filters) === bucketKey;
348
+ }
349
+ return filters[facetId] === bucketKey;
350
+ }
351
+ function toggleFacetFilter(filters, facetId, bucketKey) {
352
+ const next = { ...filters };
353
+ if (facetId === PRICE_FACET_ID) {
354
+ if (priceBucketKeyFromFilters(next) === bucketKey) {
355
+ delete next.priceMin;
356
+ delete next.priceMax;
357
+ return next;
358
+ }
359
+ delete next.priceMin;
360
+ delete next.priceMax;
361
+ const range = DEFAULT_PRICE_RANGES.find((candidate) => candidate.key === bucketKey);
362
+ if (!range) {
363
+ return next;
364
+ }
365
+ return { ...next, ...priceRangeToFilterValues(range) };
366
+ }
367
+ const filterKey = facetId === CATEGORIES_FACET_ID ? "category" : facetId;
368
+ if (next[filterKey] === bucketKey) {
369
+ delete next[filterKey];
370
+ } else {
371
+ next[filterKey] = bucketKey;
372
+ }
373
+ return next;
374
+ }
375
+ function parseMajorToCents(value) {
376
+ if (!value) {
377
+ return void 0;
378
+ }
379
+ const parsed = Number(value);
380
+ if (!Number.isFinite(parsed) || parsed < 0) {
381
+ return void 0;
382
+ }
383
+ return Math.round(parsed * 100);
384
+ }
385
+
386
+ // src/hooks/useCommerceAISearch.ts
387
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
388
+ function buildLocalePayload(options) {
389
+ const payload = {};
390
+ const queryLocale = options.queryLocale ?? options.locale;
391
+ if (options.catalogLocale) {
392
+ payload.catalogLocale = options.catalogLocale;
393
+ }
394
+ if (queryLocale) {
395
+ payload.queryLocale = queryLocale;
396
+ }
397
+ return payload;
398
+ }
399
+ function appendLocaleFields(formData, options) {
400
+ const payload = buildLocalePayload(options);
401
+ for (const [key, value] of Object.entries(payload)) {
402
+ formData.append(key, value);
403
+ }
404
+ }
405
+ function useCommerceAISearch(options) {
406
+ const {
407
+ apiBaseUrl,
408
+ debounceMs = 250,
409
+ suggestionsDebounceMs = 200,
410
+ enableAutocomplete = false,
411
+ enableFacets = false,
412
+ persistSession = true,
413
+ enableMissions = false,
414
+ queryLocale,
415
+ catalogLocale,
416
+ locale
417
+ } = options;
418
+ const localePayload = useMemo(
419
+ () => buildLocalePayload({ queryLocale, catalogLocale, locale }),
420
+ [queryLocale, catalogLocale, locale]
421
+ );
422
+ const [query, setQueryState] = useState("");
423
+ const [suggestions, setSuggestions] = useState([]);
424
+ const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false);
425
+ const [suggestionsError, setSuggestionsError] = useState(null);
426
+ const [suggestionsReady, setSuggestionsReady] = useState(false);
427
+ const [results, setResults] = useState([]);
428
+ const [mission, setMission] = useState(null);
429
+ const [meta, setMeta] = useState(null);
430
+ const [isLoading, setIsLoading] = useState(false);
431
+ const [hasSearched, setHasSearched] = useState(false);
432
+ const [error, setError] = useState(null);
433
+ const [facets, setFacets] = useState([]);
434
+ const [suggestedFacets, setSuggestedFacets] = useState([]);
435
+ const [searchSession, setSearchSession] = useState(null);
436
+ const debounceRef = useRef(null);
437
+ const suggestionsDebounceRef = useRef(null);
438
+ const abortRef = useRef(null);
439
+ const suggestionsAbortRef = useRef(null);
440
+ const requestIdRef = useRef(0);
441
+ const suggestionsRequestIdRef = useRef(0);
442
+ const queryRef = useRef(query);
443
+ queryRef.current = query;
444
+ const baseUrl = apiBaseUrl.replace(/\/$/, "");
445
+ const sessionStorageKey = `cat:search-session:${baseUrl}`;
446
+ useEffect(() => {
447
+ if (!enableFacets || !persistSession || typeof window === "undefined") return;
448
+ const stored = window.sessionStorage.getItem(sessionStorageKey);
449
+ if (!stored) return;
450
+ try {
451
+ setSearchSession(JSON.parse(stored));
452
+ } catch {
453
+ window.sessionStorage.removeItem(sessionStorageKey);
454
+ }
455
+ }, [enableFacets, persistSession, sessionStorageKey]);
456
+ useEffect(() => {
457
+ if (!enableFacets || !persistSession || !searchSession || typeof window === "undefined") return;
458
+ window.sessionStorage.setItem(sessionStorageKey, JSON.stringify(searchSession));
459
+ }, [enableFacets, persistSession, searchSession, sessionStorageKey]);
460
+ const resetResults = useCallback(() => {
461
+ abortRef.current?.abort();
462
+ abortRef.current = null;
463
+ suggestionsAbortRef.current?.abort();
464
+ suggestionsAbortRef.current = null;
465
+ if (debounceRef.current) {
466
+ clearTimeout(debounceRef.current);
467
+ debounceRef.current = null;
468
+ }
469
+ if (suggestionsDebounceRef.current) {
470
+ clearTimeout(suggestionsDebounceRef.current);
471
+ suggestionsDebounceRef.current = null;
472
+ }
473
+ setResults([]);
474
+ setMission(null);
475
+ setMeta(null);
476
+ setError(null);
477
+ setIsLoading(false);
478
+ setHasSearched(false);
479
+ setSuggestions([]);
480
+ setSuggestionsError(null);
481
+ setIsLoadingSuggestions(false);
482
+ setSuggestionsReady(false);
483
+ setFacets([]);
484
+ setSuggestedFacets([]);
485
+ setSearchSession(null);
486
+ if (typeof window !== "undefined") {
487
+ window.sessionStorage.removeItem(sessionStorageKey);
488
+ }
489
+ }, [sessionStorageKey]);
490
+ const fetchSuggestions = useCallback(
491
+ async (searchQuery) => {
492
+ const trimmed = searchQuery.trim();
493
+ if (!enableAutocomplete || trimmed.length < 2) {
494
+ setSuggestions([]);
495
+ setSuggestionsError(null);
496
+ setIsLoadingSuggestions(false);
497
+ setSuggestionsReady(false);
498
+ return;
499
+ }
500
+ suggestionsAbortRef.current?.abort();
501
+ const controller = new AbortController();
502
+ suggestionsAbortRef.current = controller;
503
+ const requestId = ++suggestionsRequestIdRef.current;
504
+ setIsLoadingSuggestions(true);
505
+ setSuggestionsError(null);
506
+ setSuggestionsReady(false);
507
+ try {
508
+ const response = await fetch(`${baseUrl}/search/suggestions`, {
509
+ method: "POST",
510
+ headers: { "Content-Type": "application/json" },
511
+ body: JSON.stringify({ query: trimmed, ...localePayload }),
512
+ signal: controller.signal
513
+ });
514
+ if (!response.ok) {
515
+ const data2 = await response.json();
516
+ throw new Error(data2.error ?? "Suggestions failed");
517
+ }
518
+ const data = await response.json();
519
+ if (requestId !== suggestionsRequestIdRef.current) return;
520
+ setSuggestions(data.suggestions);
521
+ } catch (err) {
522
+ if (err instanceof DOMException && err.name === "AbortError") return;
523
+ if (requestId !== suggestionsRequestIdRef.current) return;
524
+ setSuggestions([]);
525
+ setSuggestionsError(err instanceof Error ? err.message : "Suggestions failed");
526
+ } finally {
527
+ if (requestId === suggestionsRequestIdRef.current) {
528
+ setIsLoadingSuggestions(false);
529
+ setSuggestionsReady(true);
530
+ }
531
+ }
532
+ },
533
+ [baseUrl, enableAutocomplete, localePayload]
534
+ );
535
+ const applySearchResult = useCallback(
536
+ (data, sessionQuery) => {
537
+ setResults(data.products);
538
+ setMission(data.mission ?? null);
539
+ setMeta(data.meta);
540
+ setFacets(data.facets ?? []);
541
+ setSuggestedFacets(data.suggestedFacets ?? []);
542
+ if (data.mission) {
543
+ setSearchSession(null);
544
+ if (typeof window !== "undefined") {
545
+ window.sessionStorage.removeItem(sessionStorageKey);
546
+ }
547
+ } else if (enableFacets && data.meta.searchTerms) {
548
+ setSearchSession({
549
+ query: sessionQuery?.trim() || queryRef.current || data.meta.queryInterpretation || "",
550
+ searchTerms: data.meta.searchTerms,
551
+ appliedFilters: data.meta.appliedFilters ?? {},
552
+ suggestedFacets: data.suggestedFacets ?? [],
553
+ facetSchema: data.facetSchema,
554
+ schemaEtag: data.meta.schemaEtag
555
+ });
556
+ }
557
+ },
558
+ [enableFacets, sessionStorageKey]
559
+ );
560
+ const search = useCallback(
561
+ async (searchQuery) => {
562
+ const q = (searchQuery ?? queryRef.current).trim();
563
+ if (!q) return;
564
+ if (debounceRef.current) {
565
+ clearTimeout(debounceRef.current);
566
+ debounceRef.current = null;
567
+ }
568
+ if (suggestionsDebounceRef.current) {
569
+ clearTimeout(suggestionsDebounceRef.current);
570
+ suggestionsDebounceRef.current = null;
571
+ }
572
+ abortRef.current?.abort();
573
+ const controller = new AbortController();
574
+ abortRef.current = controller;
575
+ const requestId = ++requestIdRef.current;
576
+ setIsLoading(true);
577
+ setError(null);
578
+ setResults([]);
579
+ setMission(null);
580
+ setMeta(null);
581
+ setSuggestions([]);
582
+ setSuggestionsError(null);
583
+ try {
584
+ const response = await fetch(`${baseUrl}/search`, {
585
+ method: "POST",
586
+ headers: { "Content-Type": "application/json" },
587
+ body: JSON.stringify({
588
+ query: q,
589
+ includeFacets: enableFacets,
590
+ ...enableMissions ? { enableMissions: true } : {},
591
+ ...localePayload
592
+ }),
593
+ signal: controller.signal
594
+ });
595
+ if (!response.ok) {
596
+ const data2 = await response.json();
597
+ throw new Error(data2.error ?? "Search failed");
598
+ }
599
+ const data = await response.json();
600
+ if (requestId !== requestIdRef.current) return;
601
+ applySearchResult(data, q);
602
+ } catch (err) {
603
+ if (err instanceof DOMException && err.name === "AbortError") return;
604
+ if (requestId !== requestIdRef.current) return;
605
+ setError(err instanceof Error ? err.message : "Search failed");
606
+ setResults([]);
607
+ setMission(null);
608
+ setMeta(null);
609
+ } finally {
610
+ if (requestId === requestIdRef.current) {
611
+ setIsLoading(false);
612
+ setHasSearched(true);
613
+ }
614
+ }
615
+ },
616
+ [applySearchResult, baseUrl, enableFacets, enableMissions, localePayload]
617
+ );
618
+ const submitRefinement = useCallback(
619
+ async (body) => {
620
+ if (!searchSession) return;
621
+ abortRef.current?.abort();
622
+ const controller = new AbortController();
623
+ abortRef.current = controller;
624
+ const requestId = ++requestIdRef.current;
625
+ setIsLoading(true);
626
+ setError(null);
627
+ try {
628
+ const response = await fetch(`${baseUrl}/search`, {
629
+ method: "POST",
630
+ headers: { "Content-Type": "application/json" },
631
+ body: JSON.stringify({
632
+ query: searchSession.query,
633
+ searchTerms: searchSession.searchTerms,
634
+ filters: body.filters ?? searchSession.appliedFilters,
635
+ refineQuery: body.refineQuery,
636
+ suggestedFacets: searchSession.suggestedFacets,
637
+ includeFacets: true,
638
+ ...localePayload
639
+ }),
640
+ signal: controller.signal
641
+ });
642
+ if (!response.ok) {
643
+ const data2 = await response.json();
644
+ throw new Error(data2.error ?? "Search refinement failed");
645
+ }
646
+ const data = await response.json();
647
+ if (requestId !== requestIdRef.current) return;
648
+ applySearchResult(data, searchSession.query);
649
+ } catch (err) {
650
+ if (err instanceof DOMException && err.name === "AbortError") return;
651
+ if (requestId === requestIdRef.current) {
652
+ setError(err instanceof Error ? err.message : "Search refinement failed");
653
+ }
654
+ } finally {
655
+ if (requestId === requestIdRef.current) {
656
+ setIsLoading(false);
657
+ setHasSearched(true);
658
+ }
659
+ }
660
+ },
661
+ [applySearchResult, baseUrl, localePayload, searchSession]
662
+ );
663
+ const refineFilters = useCallback(
664
+ async (filters) => submitRefinement({ filters }),
665
+ [submitRefinement]
666
+ );
667
+ const refine = useCallback(
668
+ async (refineQuery) => {
669
+ const trimmed = refineQuery.trim();
670
+ if (trimmed) await submitRefinement({ refineQuery: trimmed });
671
+ },
672
+ [submitRefinement]
673
+ );
674
+ const startNewSearch = useCallback(async () => {
675
+ const previousQuery = searchSession?.query;
676
+ const q = (queryRef.current.trim() || previousQuery || "").trim();
677
+ setSearchSession(null);
678
+ setFacets([]);
679
+ setSuggestedFacets([]);
680
+ if (typeof window !== "undefined") {
681
+ window.sessionStorage.removeItem(sessionStorageKey);
682
+ }
683
+ if (q) {
684
+ if (!queryRef.current.trim() && previousQuery) {
685
+ setQueryState(previousQuery);
686
+ }
687
+ await search(q);
688
+ }
689
+ }, [search, searchSession?.query, sessionStorageKey]);
690
+ const searchByImage = useCallback(
691
+ async (file) => {
692
+ abortRef.current?.abort();
693
+ if (debounceRef.current) {
694
+ clearTimeout(debounceRef.current);
695
+ debounceRef.current = null;
696
+ }
697
+ if (suggestionsDebounceRef.current) {
698
+ clearTimeout(suggestionsDebounceRef.current);
699
+ suggestionsDebounceRef.current = null;
700
+ }
701
+ setIsLoading(true);
702
+ setError(null);
703
+ setResults([]);
704
+ setMission(null);
705
+ setMeta(null);
706
+ setSuggestions([]);
707
+ try {
708
+ const formData = new FormData();
709
+ formData.append("image", file);
710
+ appendLocaleFields(formData, { queryLocale, catalogLocale, locale });
711
+ if (enableMissions) {
712
+ formData.append("enableMissions", "true");
713
+ }
714
+ const response = await fetch(`${baseUrl}/search/image`, {
715
+ method: "POST",
716
+ body: formData
717
+ });
718
+ if (!response.ok) {
719
+ const data2 = await response.json();
720
+ throw new Error(data2.error ?? "Image search failed");
721
+ }
722
+ const data = await response.json();
723
+ applySearchResult(data, data.interpretation);
724
+ if (data.interpretation) {
725
+ setQueryState(data.interpretation);
726
+ }
727
+ } catch (err) {
728
+ setError(err instanceof Error ? err.message : "Image search failed");
729
+ setResults([]);
730
+ setMission(null);
731
+ setMeta(null);
732
+ } finally {
733
+ setIsLoading(false);
734
+ setHasSearched(true);
735
+ }
736
+ },
737
+ [applySearchResult, baseUrl, catalogLocale, enableMissions, locale, queryLocale]
738
+ );
739
+ const setQuery = useCallback(
740
+ (value, options2) => {
741
+ setQueryState(value);
742
+ if (options2?.search === false) {
743
+ if (debounceRef.current) {
744
+ clearTimeout(debounceRef.current);
745
+ debounceRef.current = null;
746
+ }
747
+ if (suggestionsDebounceRef.current) {
748
+ clearTimeout(suggestionsDebounceRef.current);
749
+ suggestionsDebounceRef.current = null;
750
+ }
751
+ return;
752
+ }
753
+ if (debounceRef.current) clearTimeout(debounceRef.current);
754
+ if (suggestionsDebounceRef.current) clearTimeout(suggestionsDebounceRef.current);
755
+ const trimmed = value.trim();
756
+ if (trimmed.length === 0 || trimmed.length < 2) {
757
+ resetResults();
758
+ return;
759
+ }
760
+ if (enableAutocomplete) {
761
+ setResults([]);
762
+ setMission(null);
763
+ setError(null);
764
+ setIsLoading(false);
765
+ setHasSearched(false);
766
+ setIsLoadingSuggestions(true);
767
+ setSuggestionsError(null);
768
+ suggestionsDebounceRef.current = setTimeout(() => {
769
+ void fetchSuggestions(trimmed);
770
+ }, suggestionsDebounceMs);
771
+ return;
772
+ }
773
+ setIsLoading(true);
774
+ setError(null);
775
+ setResults([]);
776
+ setMission(null);
777
+ setMeta(null);
778
+ debounceRef.current = setTimeout(() => {
779
+ void search(trimmed);
780
+ }, debounceMs);
781
+ },
782
+ [debounceMs, enableAutocomplete, fetchSuggestions, resetResults, search, suggestionsDebounceMs]
783
+ );
784
+ const selectSuggestion = useCallback(
785
+ (suggestion) => {
786
+ setQueryState(suggestion);
787
+ setSuggestions([]);
788
+ setSuggestionsError(null);
789
+ void search(suggestion);
790
+ },
791
+ [search]
792
+ );
793
+ const clear = useCallback(() => {
794
+ setQueryState("");
795
+ resetResults();
796
+ }, [resetResults]);
797
+ return {
798
+ query,
799
+ setQuery,
800
+ suggestions,
801
+ isLoadingSuggestions,
802
+ suggestionsError,
803
+ suggestionsReady,
804
+ selectSuggestion,
805
+ results,
806
+ setResults,
807
+ mission,
808
+ setMission,
809
+ meta,
810
+ setMeta,
811
+ isLoading,
812
+ setIsLoading,
813
+ hasSearched,
814
+ setHasSearched,
815
+ error,
816
+ setError,
817
+ search,
818
+ searchByImage,
819
+ facets,
820
+ suggestedFacets,
821
+ hasFacetSession: searchSession !== null,
822
+ refineFilters,
823
+ refine,
824
+ startNewSearch,
825
+ applySearchResult,
826
+ clear
827
+ };
828
+ }
829
+
830
+ // src/hooks/useCameraCapture.ts
831
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
832
+
833
+ // src/utils/camera.ts
834
+ function prefersNativeCamera() {
835
+ if (typeof navigator === "undefined") return false;
836
+ return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent) || navigator.maxTouchPoints > 1 && matchMedia("(pointer: coarse)").matches;
837
+ }
838
+ function stopMediaStream(stream) {
839
+ stream?.getTracks().forEach((track) => track.stop());
840
+ }
841
+ function getCameraErrorMessage(error) {
842
+ if (error instanceof DOMException) {
843
+ if (error.name === "NotAllowedError" || error.name === "PermissionDeniedError") {
844
+ return "Camera access denied";
845
+ }
846
+ if (error.name === "NotFoundError" || error.name === "DevicesNotFoundError") {
847
+ return "No camera found";
848
+ }
849
+ if (error.name === "NotReadableError" || error.name === "TrackStartError") {
850
+ return "Camera is already in use";
851
+ }
852
+ }
853
+ return "Camera unavailable";
854
+ }
855
+ function createJpegFileFromVideo(video, fileName = "camera-capture.jpg") {
856
+ const width = video.videoWidth;
857
+ const height = video.videoHeight;
858
+ if (!width || !height) {
859
+ return Promise.reject(new Error("Camera preview is not ready"));
860
+ }
861
+ const canvas = document.createElement("canvas");
862
+ canvas.width = width;
863
+ canvas.height = height;
864
+ const context = canvas.getContext("2d");
865
+ if (!context) {
866
+ return Promise.reject(new Error("Could not capture photo"));
867
+ }
868
+ context.drawImage(video, 0, 0, width, height);
869
+ return new Promise((resolve, reject) => {
870
+ canvas.toBlob(
871
+ (blob) => {
872
+ if (!blob) {
873
+ reject(new Error("Could not capture photo"));
874
+ return;
875
+ }
876
+ resolve(new File([blob], fileName, { type: "image/jpeg" }));
877
+ },
878
+ "image/jpeg",
879
+ 0.92
880
+ );
881
+ });
882
+ }
883
+ function buildCameraConstraints(facingMode) {
884
+ return {
885
+ video: {
886
+ facingMode: { ideal: facingMode },
887
+ width: { ideal: 1920 },
888
+ height: { ideal: 1080 }
889
+ },
890
+ audio: false
891
+ };
892
+ }
893
+
894
+ // src/hooks/useCameraCapture.ts
895
+ function useCameraCapture(options = {}) {
896
+ const facingMode = options.facingMode ?? "environment";
897
+ const [isOpen, setIsOpen] = useState2(false);
898
+ const [stream, setStream] = useState2(null);
899
+ const [error, setError] = useState2(null);
900
+ const streamRef = useRef2(null);
901
+ const close = useCallback2(() => {
902
+ stopMediaStream(streamRef.current);
903
+ streamRef.current = null;
904
+ setStream(null);
905
+ setIsOpen(false);
906
+ }, []);
907
+ const clearError = useCallback2(() => {
908
+ setError(null);
909
+ }, []);
910
+ const openOverlay = useCallback2(async () => {
911
+ setError(null);
912
+ try {
913
+ const mediaStream = await navigator.mediaDevices.getUserMedia(
914
+ buildCameraConstraints(facingMode)
915
+ );
916
+ streamRef.current = mediaStream;
917
+ setStream(mediaStream);
918
+ setIsOpen(true);
919
+ } catch (err) {
920
+ setError(getCameraErrorMessage(err));
921
+ close();
922
+ }
923
+ }, [close, facingMode]);
924
+ const open = useCallback2(
925
+ (nativeInputRef) => {
926
+ setError(null);
927
+ if (prefersNativeCamera()) {
928
+ nativeInputRef.current?.click();
929
+ return;
930
+ }
931
+ void openOverlay();
932
+ },
933
+ [openOverlay]
934
+ );
935
+ const capturePhoto = useCallback2(
936
+ async (video) => {
937
+ const file = await createJpegFileFromVideo(video);
938
+ close();
939
+ return file;
940
+ },
941
+ [close]
942
+ );
943
+ useEffect2(() => {
944
+ return () => {
945
+ stopMediaStream(streamRef.current);
946
+ streamRef.current = null;
947
+ };
948
+ }, []);
949
+ return {
950
+ isOpen,
951
+ stream,
952
+ error,
953
+ facingMode,
954
+ open,
955
+ openOverlay,
956
+ capturePhoto,
957
+ close,
958
+ clearError
959
+ };
960
+ }
961
+
962
+ // src/hooks/useCart.ts
963
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
964
+ var ANONYMOUS_ID_STORAGE_KEY = "commerce-ai-tool:anonymousId";
965
+ var CUSTOMER_SESSION_STORAGE_KEY = "commerce-ai-tool:customerSession";
966
+ var CUSTOMER_STORAGE_KEY = "commerce-ai-tool:customer";
967
+ function readStorage(key) {
968
+ if (typeof window === "undefined") {
969
+ return null;
970
+ }
971
+ try {
972
+ return window.localStorage.getItem(key);
973
+ } catch {
974
+ return null;
975
+ }
976
+ }
977
+ function writeStorage(key, value) {
978
+ if (typeof window === "undefined") {
979
+ return;
980
+ }
981
+ try {
982
+ window.localStorage.setItem(key, value);
983
+ } catch {
984
+ }
985
+ }
986
+ function removeStorage(key) {
987
+ if (typeof window === "undefined") {
988
+ return;
989
+ }
990
+ try {
991
+ window.localStorage.removeItem(key);
992
+ } catch {
993
+ }
994
+ }
995
+ function readStoredCustomer() {
996
+ const raw = readStorage(CUSTOMER_STORAGE_KEY);
997
+ if (!raw) {
998
+ return null;
999
+ }
1000
+ try {
1001
+ const parsed = JSON.parse(raw);
1002
+ if (typeof parsed.id === "string" && typeof parsed.email === "string") {
1003
+ return parsed;
1004
+ }
1005
+ } catch {
1006
+ return null;
1007
+ }
1008
+ return null;
1009
+ }
1010
+ function createAnonymousId() {
1011
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1012
+ return crypto.randomUUID();
1013
+ }
1014
+ return `anon-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1015
+ }
1016
+ function getOrCreateAnonymousId() {
1017
+ const existing = readStorage(ANONYMOUS_ID_STORAGE_KEY);
1018
+ if (existing) {
1019
+ return existing;
1020
+ }
1021
+ const created = createAnonymousId();
1022
+ writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);
1023
+ return created;
1024
+ }
1025
+ function rotateAnonymousId() {
1026
+ const created = createAnonymousId();
1027
+ writeStorage(ANONYMOUS_ID_STORAGE_KEY, created);
1028
+ return created;
1029
+ }
1030
+ var cartSyncListeners = /* @__PURE__ */ new Set();
1031
+ var cartSyncSeq = 0;
1032
+ function emitCartSync(sourceId, cart) {
1033
+ for (const listener of cartSyncListeners) {
1034
+ listener(sourceId, cart);
1035
+ }
1036
+ }
1037
+ var CartRequestError = class extends Error {
1038
+ status;
1039
+ constructor(message, status) {
1040
+ super(message);
1041
+ this.name = "CartRequestError";
1042
+ this.status = status;
1043
+ }
1044
+ };
1045
+ async function parseCartApi(response) {
1046
+ const body = await response.json();
1047
+ if (!response.ok) {
1048
+ throw new CartRequestError(body.error ?? "Cart request failed", response.status);
1049
+ }
1050
+ return body;
1051
+ }
1052
+ function useCart(options) {
1053
+ const {
1054
+ apiBaseUrl,
1055
+ currency,
1056
+ country,
1057
+ catalogLocale,
1058
+ enabled = true,
1059
+ onCartChange
1060
+ } = options;
1061
+ const [anonymousId, setAnonymousId] = useState3("");
1062
+ const [sessionToken, setSessionToken] = useState3(null);
1063
+ const [customer, setCustomer] = useState3(null);
1064
+ const [cart, setCart] = useState3(null);
1065
+ const [isLoading, setIsLoading] = useState3(false);
1066
+ const [isMutating, setIsMutating] = useState3(false);
1067
+ const [isLoggingIn, setIsLoggingIn] = useState3(false);
1068
+ const [error, setError] = useState3(null);
1069
+ const [isCartOpen, setIsCartOpen] = useState3(false);
1070
+ const onCartChangeRef = useRef3(onCartChange);
1071
+ onCartChangeRef.current = onCartChange;
1072
+ const cartRef = useRef3(null);
1073
+ const sessionTokenRef = useRef3(null);
1074
+ const mutationChainRef = useRef3(Promise.resolve(void 0));
1075
+ const orderNumberRef = useRef3(null);
1076
+ const [syncId] = useState3(() => {
1077
+ cartSyncSeq += 1;
1078
+ return cartSyncSeq;
1079
+ });
1080
+ const applyCart = useCallback3((next, sync = true) => {
1081
+ cartRef.current = next;
1082
+ setCart(next);
1083
+ onCartChangeRef.current?.(next);
1084
+ if (sync) {
1085
+ emitCartSync(syncId, next);
1086
+ }
1087
+ }, [syncId]);
1088
+ const persistSession = useCallback3((token, nextCustomer) => {
1089
+ sessionTokenRef.current = token;
1090
+ setSessionToken(token);
1091
+ setCustomer(nextCustomer);
1092
+ writeStorage(CUSTOMER_SESSION_STORAGE_KEY, token);
1093
+ writeStorage(CUSTOMER_STORAGE_KEY, JSON.stringify(nextCustomer));
1094
+ }, []);
1095
+ const clearSession = useCallback3(() => {
1096
+ sessionTokenRef.current = null;
1097
+ setSessionToken(null);
1098
+ setCustomer(null);
1099
+ removeStorage(CUSTOMER_SESSION_STORAGE_KEY);
1100
+ removeStorage(CUSTOMER_STORAGE_KEY);
1101
+ }, []);
1102
+ const refresh = useCallback3(async () => {
1103
+ if (!enabled || !anonymousId) {
1104
+ return;
1105
+ }
1106
+ const params = new URLSearchParams();
1107
+ const headers = {};
1108
+ const token = sessionTokenRef.current;
1109
+ if (token) {
1110
+ headers[CART_SESSION_HEADER] = token;
1111
+ } else {
1112
+ params.set("anonymousId", anonymousId);
1113
+ }
1114
+ if (catalogLocale) {
1115
+ params.set("catalogLocale", catalogLocale);
1116
+ }
1117
+ setIsLoading(true);
1118
+ setError(null);
1119
+ try {
1120
+ const query = params.toString();
1121
+ const url = `${apiBaseUrl}/cart${query ? `?${query}` : ""}`;
1122
+ const response = await (token ? fetch(url, { headers }) : fetch(url));
1123
+ const body = await parseCartApi(response);
1124
+ applyCart(body.cart ?? null);
1125
+ } catch (err) {
1126
+ if (err instanceof CartRequestError && err.status === 401 && sessionTokenRef.current) {
1127
+ clearSession();
1128
+ const nextAnonymousId = rotateAnonymousId();
1129
+ setAnonymousId(nextAnonymousId);
1130
+ applyCart(null);
1131
+ }
1132
+ setError(err instanceof Error ? err.message : "Cart request failed");
1133
+ } finally {
1134
+ setIsLoading(false);
1135
+ }
1136
+ }, [anonymousId, apiBaseUrl, applyCart, catalogLocale, clearSession, enabled]);
1137
+ useEffect3(() => {
1138
+ if (!enabled) {
1139
+ return;
1140
+ }
1141
+ setAnonymousId(getOrCreateAnonymousId());
1142
+ const storedToken = readStorage(CUSTOMER_SESSION_STORAGE_KEY);
1143
+ const storedCustomer = readStoredCustomer();
1144
+ if (storedToken && storedCustomer) {
1145
+ sessionTokenRef.current = storedToken;
1146
+ setSessionToken(storedToken);
1147
+ setCustomer(storedCustomer);
1148
+ }
1149
+ }, [enabled]);
1150
+ useEffect3(() => {
1151
+ if (!enabled) {
1152
+ return;
1153
+ }
1154
+ const listener = (sourceId, next) => {
1155
+ if (sourceId === syncId) {
1156
+ return;
1157
+ }
1158
+ applyCart(next, false);
1159
+ };
1160
+ cartSyncListeners.add(listener);
1161
+ return () => {
1162
+ cartSyncListeners.delete(listener);
1163
+ };
1164
+ }, [applyCart, enabled, syncId]);
1165
+ useEffect3(() => {
1166
+ if (!enabled || !anonymousId) {
1167
+ return;
1168
+ }
1169
+ void refresh();
1170
+ }, [anonymousId, enabled, refresh]);
1171
+ const mutate = useCallback3(
1172
+ async (path, body) => {
1173
+ if (!enabled || !anonymousId) {
1174
+ return null;
1175
+ }
1176
+ const run = async () => {
1177
+ setIsMutating(true);
1178
+ setError(null);
1179
+ try {
1180
+ const response = await fetch(`${apiBaseUrl}${path}`, {
1181
+ method: "POST",
1182
+ headers: { "Content-Type": "application/json" },
1183
+ body: JSON.stringify({
1184
+ anonymousId,
1185
+ sessionToken: sessionTokenRef.current ?? void 0,
1186
+ currency,
1187
+ country,
1188
+ catalogLocale,
1189
+ cartId: cartRef.current?.id,
1190
+ cartVersion: cartRef.current?.version,
1191
+ ...body
1192
+ })
1193
+ });
1194
+ const next = await parseCartApi(response);
1195
+ applyCart(next.cart ?? null);
1196
+ return next.cart ?? null;
1197
+ } catch (err) {
1198
+ if (err instanceof CartRequestError && err.status === 401 && sessionTokenRef.current) {
1199
+ clearSession();
1200
+ setAnonymousId(rotateAnonymousId());
1201
+ applyCart(null);
1202
+ }
1203
+ setError(err instanceof Error ? err.message : "Cart request failed");
1204
+ return null;
1205
+ } finally {
1206
+ setIsMutating(false);
1207
+ }
1208
+ };
1209
+ const pending = mutationChainRef.current.then(run, run);
1210
+ mutationChainRef.current = pending.then(
1211
+ () => void 0,
1212
+ () => void 0
1213
+ );
1214
+ return pending;
1215
+ },
1216
+ [anonymousId, apiBaseUrl, applyCart, catalogLocale, clearSession, country, currency, enabled]
1217
+ );
1218
+ const addToCart = useCallback3(
1219
+ async (item) => mutate("/cart/add", {
1220
+ sku: item.sku,
1221
+ productId: item.productId,
1222
+ variantId: item.variantId,
1223
+ quantity: item.quantity
1224
+ }),
1225
+ [mutate]
1226
+ );
1227
+ const addItems = useCallback3(
1228
+ async (items) => mutate("/cart/add-items", { items }),
1229
+ [mutate]
1230
+ );
1231
+ const removeFromCart = useCallback3(
1232
+ (lineItemId) => mutate("/cart/remove", { lineItemId }),
1233
+ [mutate]
1234
+ );
1235
+ const updateQuantity = useCallback3(
1236
+ (lineItemId, quantity) => mutate("/cart/update-quantity", { lineItemId, quantity }),
1237
+ [mutate]
1238
+ );
1239
+ const setAddresses = useCallback3(
1240
+ (shippingAddress, billingAddress) => mutate("/cart/addresses", { shippingAddress, billingAddress }),
1241
+ [mutate]
1242
+ );
1243
+ const getShippingMethods = useCallback3(async () => {
1244
+ if (!enabled || !anonymousId) {
1245
+ return [];
1246
+ }
1247
+ const params = new URLSearchParams();
1248
+ const headers = {};
1249
+ const token = sessionTokenRef.current;
1250
+ if (token) {
1251
+ headers[CART_SESSION_HEADER] = token;
1252
+ } else {
1253
+ params.set("anonymousId", anonymousId);
1254
+ }
1255
+ if (cartRef.current?.id) {
1256
+ params.set("cartId", cartRef.current.id);
1257
+ }
1258
+ if (catalogLocale) {
1259
+ params.set("catalogLocale", catalogLocale);
1260
+ }
1261
+ setIsMutating(true);
1262
+ setError(null);
1263
+ try {
1264
+ const response = await fetch(
1265
+ `${apiBaseUrl}/cart/shipping-methods?${params.toString()}`,
1266
+ token ? { headers } : void 0
1267
+ );
1268
+ const body = await parseCartApi(response);
1269
+ return body.shippingMethods ?? [];
1270
+ } catch (err) {
1271
+ setError(err instanceof Error ? err.message : "Shipping methods request failed");
1272
+ return null;
1273
+ } finally {
1274
+ setIsMutating(false);
1275
+ }
1276
+ }, [anonymousId, apiBaseUrl, catalogLocale, enabled]);
1277
+ const setShippingMethod = useCallback3(
1278
+ (shippingMethodId) => mutate("/cart/shipping-method", { shippingMethodId }),
1279
+ [mutate]
1280
+ );
1281
+ const getPaymentMethods = useCallback3(async () => {
1282
+ if (!enabled || !anonymousId) {
1283
+ return [];
1284
+ }
1285
+ const params = new URLSearchParams();
1286
+ const headers = {};
1287
+ const token = sessionTokenRef.current;
1288
+ if (token) {
1289
+ headers[CART_SESSION_HEADER] = token;
1290
+ } else {
1291
+ params.set("anonymousId", anonymousId);
1292
+ }
1293
+ if (cartRef.current?.id) {
1294
+ params.set("cartId", cartRef.current.id);
1295
+ }
1296
+ if (catalogLocale) {
1297
+ params.set("catalogLocale", catalogLocale);
1298
+ }
1299
+ setIsMutating(true);
1300
+ setError(null);
1301
+ try {
1302
+ const response = await fetch(
1303
+ `${apiBaseUrl}/cart/payment-methods?${params.toString()}`,
1304
+ token ? { headers } : void 0
1305
+ );
1306
+ const body = await parseCartApi(response);
1307
+ return body.paymentMethods ?? [];
1308
+ } catch (err) {
1309
+ setError(err instanceof Error ? err.message : "Payment methods request failed");
1310
+ return null;
1311
+ } finally {
1312
+ setIsMutating(false);
1313
+ }
1314
+ }, [anonymousId, apiBaseUrl, catalogLocale, enabled]);
1315
+ const authorizePayment = useCallback3(
1316
+ async (method) => {
1317
+ if (!enabled || !anonymousId) {
1318
+ return null;
1319
+ }
1320
+ const run = async () => {
1321
+ orderNumberRef.current ??= `cat-${createAnonymousId()}`;
1322
+ setIsMutating(true);
1323
+ setError(null);
1324
+ try {
1325
+ const response = await fetch(`${apiBaseUrl}/cart/payment`, {
1326
+ method: "POST",
1327
+ headers: { "Content-Type": "application/json" },
1328
+ body: JSON.stringify({
1329
+ anonymousId,
1330
+ sessionToken: sessionTokenRef.current ?? void 0,
1331
+ cartId: cartRef.current?.id,
1332
+ catalogLocale,
1333
+ method,
1334
+ orderNumber: orderNumberRef.current
1335
+ })
1336
+ });
1337
+ const body = await parseCartApi(response);
1338
+ if (body.cart) {
1339
+ applyCart(body.cart);
1340
+ }
1341
+ if (!body.payment) {
1342
+ throw new CartRequestError("Payment response is missing", response.status);
1343
+ }
1344
+ return body.payment;
1345
+ } catch (err) {
1346
+ setError(err instanceof Error ? err.message : "Payment failed");
1347
+ return null;
1348
+ } finally {
1349
+ setIsMutating(false);
1350
+ }
1351
+ };
1352
+ const pending = mutationChainRef.current.then(run, run);
1353
+ mutationChainRef.current = pending.then(
1354
+ () => void 0,
1355
+ () => void 0
1356
+ );
1357
+ return pending;
1358
+ },
1359
+ [anonymousId, apiBaseUrl, applyCart, catalogLocale, enabled]
1360
+ );
1361
+ const getOrder = useCallback3(
1362
+ async (orderNumber) => {
1363
+ if (!enabled || !anonymousId || !orderNumber.trim()) {
1364
+ return null;
1365
+ }
1366
+ const params = new URLSearchParams();
1367
+ const headers = {};
1368
+ const token = sessionTokenRef.current;
1369
+ if (token) {
1370
+ headers[CART_SESSION_HEADER] = token;
1371
+ }
1372
+ params.set("anonymousId", anonymousId);
1373
+ params.set("orderNumber", orderNumber.trim());
1374
+ if (catalogLocale) {
1375
+ params.set("catalogLocale", catalogLocale);
1376
+ }
1377
+ setIsLoading(true);
1378
+ setError(null);
1379
+ try {
1380
+ const response = await fetch(`${apiBaseUrl}/orders?${params.toString()}`, {
1381
+ headers
1382
+ });
1383
+ const body = await parseCartApi(response);
1384
+ return body.order ?? null;
1385
+ } catch (err) {
1386
+ setError(err instanceof Error ? err.message : "Order request failed");
1387
+ return null;
1388
+ } finally {
1389
+ setIsLoading(false);
1390
+ }
1391
+ },
1392
+ [anonymousId, apiBaseUrl, catalogLocale, enabled]
1393
+ );
1394
+ const listOrders = useCallback3(async () => {
1395
+ if (!enabled || !anonymousId) {
1396
+ return [];
1397
+ }
1398
+ const params = new URLSearchParams();
1399
+ const headers = {};
1400
+ const token = sessionTokenRef.current;
1401
+ if (token) {
1402
+ headers[CART_SESSION_HEADER] = token;
1403
+ }
1404
+ params.set("anonymousId", anonymousId);
1405
+ if (catalogLocale) {
1406
+ params.set("catalogLocale", catalogLocale);
1407
+ }
1408
+ setIsLoading(true);
1409
+ setError(null);
1410
+ try {
1411
+ const response = await fetch(`${apiBaseUrl}/orders?${params.toString()}`, {
1412
+ headers
1413
+ });
1414
+ const body = await parseCartApi(response);
1415
+ return body.orders ?? [];
1416
+ } catch (err) {
1417
+ setError(err instanceof Error ? err.message : "Order request failed");
1418
+ return [];
1419
+ } finally {
1420
+ setIsLoading(false);
1421
+ }
1422
+ }, [anonymousId, apiBaseUrl, catalogLocale, enabled]);
1423
+ const placeOrder = useCallback3(async () => {
1424
+ if (!enabled || !anonymousId) {
1425
+ return null;
1426
+ }
1427
+ const run = async () => {
1428
+ if (!cartRef.current) {
1429
+ return null;
1430
+ }
1431
+ orderNumberRef.current ??= `cat-${createAnonymousId()}`;
1432
+ setIsMutating(true);
1433
+ setError(null);
1434
+ try {
1435
+ const response = await fetch(`${apiBaseUrl}/cart/order`, {
1436
+ method: "POST",
1437
+ headers: { "Content-Type": "application/json" },
1438
+ body: JSON.stringify({
1439
+ anonymousId,
1440
+ sessionToken: sessionTokenRef.current ?? void 0,
1441
+ cartId: cartRef.current.id,
1442
+ catalogLocale,
1443
+ orderNumber: orderNumberRef.current
1444
+ })
1445
+ });
1446
+ const body = await parseCartApi(response);
1447
+ if (!body.order) {
1448
+ throw new CartRequestError("Order response is missing", response.status);
1449
+ }
1450
+ orderNumberRef.current = null;
1451
+ applyCart(null);
1452
+ return body.order;
1453
+ } catch (err) {
1454
+ setError(err instanceof Error ? err.message : "Checkout failed");
1455
+ return null;
1456
+ } finally {
1457
+ setIsMutating(false);
1458
+ }
1459
+ };
1460
+ const pending = mutationChainRef.current.then(run, run);
1461
+ mutationChainRef.current = pending.then(
1462
+ () => void 0,
1463
+ () => void 0
1464
+ );
1465
+ return pending;
1466
+ }, [anonymousId, apiBaseUrl, applyCart, catalogLocale, enabled]);
1467
+ const login = useCallback3(
1468
+ async (input) => {
1469
+ if (!enabled) {
1470
+ return null;
1471
+ }
1472
+ setIsLoggingIn(true);
1473
+ setError(null);
1474
+ try {
1475
+ const response = await fetch(`${apiBaseUrl}/cart/login`, {
1476
+ method: "POST",
1477
+ headers: { "Content-Type": "application/json" },
1478
+ body: JSON.stringify({
1479
+ email: input.email,
1480
+ password: input.password,
1481
+ anonymousId,
1482
+ catalogLocale,
1483
+ cartId: cartRef.current?.id
1484
+ })
1485
+ });
1486
+ const body = await parseCartApi(response);
1487
+ if (!body.sessionToken || !body.customer) {
1488
+ throw new CartRequestError("Sign in failed", response.status);
1489
+ }
1490
+ persistSession(body.sessionToken, body.customer);
1491
+ applyCart(body.cart ?? null);
1492
+ return body.cart ?? null;
1493
+ } catch (err) {
1494
+ setError(err instanceof Error ? err.message : "Sign in failed");
1495
+ return null;
1496
+ } finally {
1497
+ setIsLoggingIn(false);
1498
+ }
1499
+ },
1500
+ [anonymousId, apiBaseUrl, applyCart, catalogLocale, enabled, persistSession]
1501
+ );
1502
+ const logout = useCallback3(async () => {
1503
+ try {
1504
+ await fetch(`${apiBaseUrl}/cart/logout`, {
1505
+ method: "POST",
1506
+ headers: { "Content-Type": "application/json" },
1507
+ body: JSON.stringify({})
1508
+ });
1509
+ } catch {
1510
+ }
1511
+ clearSession();
1512
+ applyCart(null);
1513
+ setAnonymousId(rotateAnonymousId());
1514
+ }, [apiBaseUrl, applyCart, clearSession]);
1515
+ const openCart = useCallback3(() => {
1516
+ setIsCartOpen(true);
1517
+ void refresh();
1518
+ }, [refresh]);
1519
+ const closeCart = useCallback3(() => setIsCartOpen(false), []);
1520
+ const toggleCart = useCallback3(() => {
1521
+ setIsCartOpen((open) => {
1522
+ if (!open) {
1523
+ void refresh();
1524
+ }
1525
+ return !open;
1526
+ });
1527
+ }, [refresh]);
1528
+ return {
1529
+ cart,
1530
+ anonymousId,
1531
+ customer,
1532
+ isAuthenticated: Boolean(customer && sessionToken),
1533
+ isLoading,
1534
+ isMutating,
1535
+ isLoggingIn,
1536
+ error,
1537
+ isCartOpen,
1538
+ openCart,
1539
+ closeCart,
1540
+ toggleCart,
1541
+ addToCart,
1542
+ addItems,
1543
+ removeFromCart,
1544
+ updateQuantity,
1545
+ setAddresses,
1546
+ getShippingMethods,
1547
+ setShippingMethod,
1548
+ getPaymentMethods,
1549
+ authorizePayment,
1550
+ getOrder,
1551
+ listOrders,
1552
+ placeOrder,
1553
+ login,
1554
+ logout,
1555
+ refresh
1556
+ };
1557
+ }
1558
+
1559
+ // src/hooks/useRecordingDuration.ts
1560
+ import { useEffect as useEffect4, useState as useState4 } from "react";
1561
+ function formatRecordingDuration(seconds) {
1562
+ const mins = Math.floor(seconds / 60);
1563
+ const secs = seconds % 60;
1564
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
1565
+ }
1566
+ function useRecordingDuration(isRecording) {
1567
+ const [duration, setDuration] = useState4(0);
1568
+ useEffect4(() => {
1569
+ if (!isRecording) {
1570
+ setDuration(0);
1571
+ return;
1572
+ }
1573
+ const interval = setInterval(() => {
1574
+ setDuration((current) => current + 1);
1575
+ }, 1e3);
1576
+ return () => clearInterval(interval);
1577
+ }, [isRecording]);
1578
+ return duration;
1579
+ }
1580
+
1581
+ // src/hooks/useVoiceSearch.ts
1582
+ import { useCallback as useCallback4, useRef as useRef4, useState as useState5 } from "react";
1583
+ function playAudioSummary(base64) {
1584
+ const audio = new Audio(`data:audio/mpeg;base64,${base64}`);
1585
+ void audio.play();
1586
+ }
1587
+ async function fetchTtsSummary(baseUrl, text) {
1588
+ const response = await fetch(`${baseUrl}/tts`, {
1589
+ method: "POST",
1590
+ headers: { "Content-Type": "application/json" },
1591
+ body: JSON.stringify({ text })
1592
+ });
1593
+ if (!response.ok) {
1594
+ throw new Error("TTS failed");
1595
+ }
1596
+ const blob = await response.blob();
1597
+ const buffer = await blob.arrayBuffer();
1598
+ const bytes = new Uint8Array(buffer);
1599
+ let binary = "";
1600
+ for (let i = 0; i < bytes.length; i++) {
1601
+ binary += String.fromCharCode(bytes[i]);
1602
+ }
1603
+ return btoa(binary);
1604
+ }
1605
+ function useVoiceSearch(options) {
1606
+ const {
1607
+ apiBaseUrl,
1608
+ queryLocale,
1609
+ catalogLocale,
1610
+ locale,
1611
+ onResults,
1612
+ onTranscript,
1613
+ enableTts = true,
1614
+ enableMissions = false
1615
+ } = options;
1616
+ const [isRecording, setIsRecording] = useState5(false);
1617
+ const [isProcessing, setIsProcessing] = useState5(false);
1618
+ const [isLoadingTts, setIsLoadingTts] = useState5(false);
1619
+ const [error, setError] = useState5(null);
1620
+ const [audioSummary, setAudioSummary] = useState5(null);
1621
+ const mediaRecorderRef = useRef4(null);
1622
+ const chunksRef = useRef4([]);
1623
+ const baseUrl = apiBaseUrl.replace(/\/$/, "");
1624
+ const clearAudioSummary = useCallback4(() => {
1625
+ setAudioSummary(null);
1626
+ }, []);
1627
+ const replayAudioSummary = useCallback4(() => {
1628
+ if (audioSummary) {
1629
+ playAudioSummary(audioSummary);
1630
+ }
1631
+ }, [audioSummary]);
1632
+ const stopRecording = useCallback4(() => {
1633
+ if (mediaRecorderRef.current?.state === "recording") {
1634
+ mediaRecorderRef.current.stop();
1635
+ }
1636
+ setIsRecording(false);
1637
+ }, []);
1638
+ const startRecording = useCallback4(async () => {
1639
+ setError(null);
1640
+ setAudioSummary(null);
1641
+ try {
1642
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
1643
+ const mediaRecorder = new MediaRecorder(stream);
1644
+ mediaRecorderRef.current = mediaRecorder;
1645
+ chunksRef.current = [];
1646
+ mediaRecorder.ondataavailable = (event) => {
1647
+ if (event.data.size > 0) {
1648
+ chunksRef.current.push(event.data);
1649
+ }
1650
+ };
1651
+ mediaRecorder.onstop = async () => {
1652
+ stream.getTracks().forEach((track) => track.stop());
1653
+ const blob = new Blob(chunksRef.current, { type: "audio/webm" });
1654
+ setIsProcessing(true);
1655
+ try {
1656
+ const formData = new FormData();
1657
+ formData.append("audio", blob, "recording.webm");
1658
+ appendLocaleFields(formData, { queryLocale, catalogLocale, locale });
1659
+ formData.append("enableTts", String(enableTts));
1660
+ if (enableMissions) {
1661
+ formData.append("enableMissions", "true");
1662
+ }
1663
+ const response = await fetch(`${baseUrl}/search/voice`, {
1664
+ method: "POST",
1665
+ body: formData
1666
+ });
1667
+ if (!response.ok) {
1668
+ const data2 = await response.json();
1669
+ throw new Error(data2.error ?? "Voice search failed");
1670
+ }
1671
+ const data = await response.json();
1672
+ onTranscript?.(data.transcript);
1673
+ onResults?.(data.products, data.meta, { mission: data.mission });
1674
+ if (data.audioSummary) {
1675
+ setAudioSummary(data.audioSummary);
1676
+ playAudioSummary(data.audioSummary);
1677
+ } else if (enableTts && data.ttsText && data.ttsPending) {
1678
+ setIsLoadingTts(true);
1679
+ try {
1680
+ const summary = await fetchTtsSummary(baseUrl, data.ttsText);
1681
+ setAudioSummary(summary);
1682
+ playAudioSummary(summary);
1683
+ } catch {
1684
+ setAudioSummary(null);
1685
+ } finally {
1686
+ setIsLoadingTts(false);
1687
+ }
1688
+ } else {
1689
+ setAudioSummary(null);
1690
+ }
1691
+ } catch (err) {
1692
+ setError(err instanceof Error ? err.message : "Voice search failed");
1693
+ setAudioSummary(null);
1694
+ } finally {
1695
+ setIsProcessing(false);
1696
+ }
1697
+ };
1698
+ mediaRecorder.start();
1699
+ setIsRecording(true);
1700
+ } catch (err) {
1701
+ setError(err instanceof Error ? err.message : "Microphone access denied");
1702
+ }
1703
+ }, [baseUrl, catalogLocale, enableMissions, enableTts, locale, onResults, onTranscript, queryLocale]);
1704
+ const toggleRecording = useCallback4(async () => {
1705
+ if (isRecording) {
1706
+ stopRecording();
1707
+ } else {
1708
+ await startRecording();
1709
+ }
1710
+ }, [isRecording, startRecording, stopRecording]);
1711
+ const clearError = useCallback4(() => {
1712
+ setError(null);
1713
+ }, []);
1714
+ return {
1715
+ isRecording,
1716
+ isProcessing,
1717
+ isLoadingTts,
1718
+ error,
1719
+ clearError,
1720
+ audioSummary,
1721
+ clearAudioSummary,
1722
+ replayAudioSummary,
1723
+ toggleRecording,
1724
+ stopRecording
1725
+ };
1726
+ }
1727
+
1728
+ // src/icons.ts
1729
+ var ICON_STROKE = 1.25;
1730
+
1731
+ // src/components/CameraCaptureOverlay.tsx
1732
+ import { useEffect as useEffect5, useRef as useRef5 } from "react";
1733
+ import { Camera, X } from "lucide-react";
1734
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1735
+ function CameraCaptureOverlay({
1736
+ stream,
1737
+ error,
1738
+ messages,
1739
+ onCapture,
1740
+ onClose,
1741
+ onDismissError
1742
+ }) {
1743
+ const videoRef = useRef5(null);
1744
+ useEffect5(() => {
1745
+ function onKeyDown(event) {
1746
+ if (event.key === "Escape") {
1747
+ if (error) {
1748
+ onDismissError();
1749
+ } else {
1750
+ onClose();
1751
+ }
1752
+ }
1753
+ }
1754
+ window.addEventListener("keydown", onKeyDown);
1755
+ return () => window.removeEventListener("keydown", onKeyDown);
1756
+ }, [error, onClose, onDismissError]);
1757
+ useEffect5(() => {
1758
+ const video = videoRef.current;
1759
+ if (!video) return;
1760
+ video.srcObject = stream;
1761
+ if (stream) {
1762
+ const playPromise = video.play();
1763
+ if (playPromise) {
1764
+ void playPromise.catch(() => {
1765
+ });
1766
+ }
1767
+ }
1768
+ return () => {
1769
+ video.srcObject = null;
1770
+ };
1771
+ }, [stream]);
1772
+ return /* @__PURE__ */ jsx(
1773
+ "div",
1774
+ {
1775
+ className: "cat-camera-overlay",
1776
+ role: "dialog",
1777
+ "aria-modal": "true",
1778
+ "aria-label": messages.cameraCapture,
1779
+ children: /* @__PURE__ */ jsx("div", { className: "cat-camera-overlay__panel", children: error ? /* @__PURE__ */ jsxs("div", { className: "cat-camera-overlay__error", role: "alert", children: [
1780
+ /* @__PURE__ */ jsx("p", { children: error }),
1781
+ /* @__PURE__ */ jsx("button", { type: "button", className: "cat-camera-overlay__btn", onClick: onDismissError, children: messages.dismiss })
1782
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1783
+ /* @__PURE__ */ jsx(
1784
+ "video",
1785
+ {
1786
+ ref: videoRef,
1787
+ className: "cat-camera-preview",
1788
+ autoPlay: true,
1789
+ playsInline: true,
1790
+ muted: true,
1791
+ "aria-label": messages.cameraPreview
1792
+ }
1793
+ ),
1794
+ /* @__PURE__ */ jsxs("div", { className: "cat-camera-actions", children: [
1795
+ /* @__PURE__ */ jsxs(
1796
+ "button",
1797
+ {
1798
+ type: "button",
1799
+ className: "cat-camera-overlay__btn cat-camera-overlay__btn--secondary",
1800
+ onClick: onClose,
1801
+ "aria-label": messages.cancel,
1802
+ children: [
1803
+ /* @__PURE__ */ jsx(X, { size: 16, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
1804
+ messages.cancel
1805
+ ]
1806
+ }
1807
+ ),
1808
+ /* @__PURE__ */ jsxs(
1809
+ "button",
1810
+ {
1811
+ type: "button",
1812
+ className: "cat-camera-overlay__btn cat-camera-overlay__btn--primary",
1813
+ onClick: () => {
1814
+ if (videoRef.current) onCapture(videoRef.current);
1815
+ },
1816
+ "aria-label": messages.capturePhoto,
1817
+ children: [
1818
+ /* @__PURE__ */ jsx(Camera, { size: 16, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
1819
+ messages.capturePhoto
1820
+ ]
1821
+ }
1822
+ )
1823
+ ] })
1824
+ ] }) })
1825
+ }
1826
+ );
1827
+ }
1828
+
1829
+ // src/components/CartPanel.tsx
1830
+ import { useState as useState6 } from "react";
1831
+ import {
1832
+ ArrowRight,
1833
+ ChevronDown,
1834
+ Minus,
1835
+ Package,
1836
+ Plus,
1837
+ ShoppingBag,
1838
+ Trash2,
1839
+ UserRound,
1840
+ X as X2
1841
+ } from "lucide-react";
1842
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1843
+ function displayCartError(error, messages) {
1844
+ if (!error) {
1845
+ return null;
1846
+ }
1847
+ if (error === "Invalid credentials") {
1848
+ return messages.invalidCredentials;
1849
+ }
1850
+ if (error === "Sign in failed" || error === "Login failed") {
1851
+ return messages.signInFailed;
1852
+ }
1853
+ return error;
1854
+ }
1855
+ function formatLineTotal(price, quantity, locale) {
1856
+ if (!price) {
1857
+ return null;
1858
+ }
1859
+ return new Intl.NumberFormat(locale || void 0, {
1860
+ style: "currency",
1861
+ currency: price.currency
1862
+ }).format(price.amount * quantity);
1863
+ }
1864
+ function CartPanel({
1865
+ cart,
1866
+ customer,
1867
+ isLoading,
1868
+ isLoggingIn,
1869
+ error,
1870
+ messages,
1871
+ catalogLocale,
1872
+ onClose,
1873
+ onRemove,
1874
+ onQuantityChange,
1875
+ onLogin,
1876
+ onLogout,
1877
+ onCheckout
1878
+ }) {
1879
+ const isEmpty = !cart || cart.lineItems.length === 0;
1880
+ const [email, setEmail] = useState6("");
1881
+ const [password, setPassword] = useState6("");
1882
+ const [isAuthExpanded, setIsAuthExpanded] = useState6(false);
1883
+ const visibleError = displayCartError(error, messages);
1884
+ function handleLoginSubmit(event) {
1885
+ event.preventDefault();
1886
+ onLogin({ email, password });
1887
+ setPassword("");
1888
+ }
1889
+ const authBlock = /* @__PURE__ */ jsx2("div", { className: "cat-cart-panel__auth", children: customer ? /* @__PURE__ */ jsxs2("div", { className: "cat-cart-panel__signed-in", children: [
1890
+ /* @__PURE__ */ jsxs2("span", { children: [
1891
+ messages.signedInAs,
1892
+ " ",
1893
+ customer.email
1894
+ ] }),
1895
+ /* @__PURE__ */ jsx2(
1896
+ "button",
1897
+ {
1898
+ type: "button",
1899
+ className: "cat-cart-panel__sign-out",
1900
+ onClick: onLogout,
1901
+ children: messages.signOut
1902
+ }
1903
+ )
1904
+ ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1905
+ /* @__PURE__ */ jsxs2(
1906
+ "button",
1907
+ {
1908
+ type: "button",
1909
+ className: "cat-cart-panel__auth-toggle",
1910
+ disabled: isLoggingIn,
1911
+ "aria-expanded": isAuthExpanded,
1912
+ onClick: () => setIsAuthExpanded((expanded) => !expanded),
1913
+ children: [
1914
+ /* @__PURE__ */ jsxs2("span", { className: "cat-cart-panel__auth-toggle-copy", children: [
1915
+ /* @__PURE__ */ jsx2("span", { className: "cat-cart-panel__auth-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(UserRound, { size: 14, strokeWidth: ICON_STROKE }) }),
1916
+ messages.signInToSyncCart
1917
+ ] }),
1918
+ /* @__PURE__ */ jsx2(
1919
+ ChevronDown,
1920
+ {
1921
+ className: isAuthExpanded ? "cat-cart-panel__auth-chevron--open" : "",
1922
+ size: 15,
1923
+ strokeWidth: ICON_STROKE,
1924
+ "aria-hidden": "true"
1925
+ }
1926
+ )
1927
+ ]
1928
+ }
1929
+ ),
1930
+ isAuthExpanded ? /* @__PURE__ */ jsxs2("form", { className: "cat-cart-panel__auth-form", onSubmit: handleLoginSubmit, children: [
1931
+ /* @__PURE__ */ jsxs2("label", { className: "cat-cart-panel__field", children: [
1932
+ /* @__PURE__ */ jsx2("span", { className: "cat-cart-panel__label", children: messages.email }),
1933
+ /* @__PURE__ */ jsx2(
1934
+ "input",
1935
+ {
1936
+ className: "cat-cart-panel__input",
1937
+ type: "email",
1938
+ name: "email",
1939
+ autoComplete: "email",
1940
+ required: true,
1941
+ disabled: isLoggingIn,
1942
+ value: email,
1943
+ onChange: (event) => setEmail(event.target.value)
1944
+ }
1945
+ )
1946
+ ] }),
1947
+ /* @__PURE__ */ jsxs2("label", { className: "cat-cart-panel__field", children: [
1948
+ /* @__PURE__ */ jsx2("span", { className: "cat-cart-panel__label", children: messages.password }),
1949
+ /* @__PURE__ */ jsx2(
1950
+ "input",
1951
+ {
1952
+ className: "cat-cart-panel__input",
1953
+ type: "password",
1954
+ name: "password",
1955
+ autoComplete: "current-password",
1956
+ required: true,
1957
+ disabled: isLoggingIn,
1958
+ value: password,
1959
+ onChange: (event) => setPassword(event.target.value)
1960
+ }
1961
+ )
1962
+ ] }),
1963
+ /* @__PURE__ */ jsx2(
1964
+ "button",
1965
+ {
1966
+ type: "submit",
1967
+ className: "cat-cart-panel__submit",
1968
+ disabled: isLoggingIn,
1969
+ children: messages.signIn
1970
+ }
1971
+ )
1972
+ ] }) : null
1973
+ ] }) });
1974
+ return /* @__PURE__ */ jsxs2(
1975
+ "section",
1976
+ {
1977
+ className: `cat-cart-panel${isEmpty ? "" : " cat-cart-panel--with-items"}`,
1978
+ "aria-label": messages.cartAriaLabel,
1979
+ children: [
1980
+ /* @__PURE__ */ jsxs2("header", { className: "cat-cart-panel__header", children: [
1981
+ /* @__PURE__ */ jsx2("h2", { className: "cat-cart-panel__title", children: messages.cart }),
1982
+ /* @__PURE__ */ jsx2(
1983
+ "button",
1984
+ {
1985
+ type: "button",
1986
+ className: "cat-icon-btn",
1987
+ onClick: onClose,
1988
+ "aria-label": messages.closeCart,
1989
+ children: /* @__PURE__ */ jsx2(X2, { size: 16, strokeWidth: ICON_STROKE })
1990
+ }
1991
+ )
1992
+ ] }),
1993
+ visibleError && /* @__PURE__ */ jsx2("div", { className: "cat-status cat-status--error", role: "alert", children: visibleError }),
1994
+ isEmpty ? /* @__PURE__ */ jsxs2("div", { className: "cat-cart-panel__empty", children: [
1995
+ /* @__PURE__ */ jsx2(ShoppingBag, { size: 20, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
1996
+ /* @__PURE__ */ jsx2("span", { children: messages.emptyCart })
1997
+ ] }) : /* @__PURE__ */ jsx2("ul", { className: "cat-cart-panel__items", children: cart.lineItems.map((item) => /* @__PURE__ */ jsxs2("li", { className: "cat-cart-item", children: [
1998
+ item.imageUrl ? /* @__PURE__ */ jsx2("img", { src: item.imageUrl, alt: "", className: "cat-cart-item__image" }) : /* @__PURE__ */ jsx2("div", { className: "cat-cart-item__image cat-cart-item__image--placeholder", children: /* @__PURE__ */ jsx2(Package, { size: 16, strokeWidth: ICON_STROKE, "aria-hidden": "true" }) }),
1999
+ /* @__PURE__ */ jsxs2("div", { className: "cat-cart-item__info", children: [
2000
+ /* @__PURE__ */ jsx2("div", { className: "cat-cart-item__name", children: item.name }),
2001
+ item.price ? /* @__PURE__ */ jsxs2("div", { className: "cat-cart-item__price", children: [
2002
+ /* @__PURE__ */ jsxs2("span", { children: [
2003
+ item.price.formatted,
2004
+ " ",
2005
+ messages.each
2006
+ ] }),
2007
+ /* @__PURE__ */ jsx2("strong", { children: item.totalPrice?.formatted ?? formatLineTotal(item.price, item.quantity, catalogLocale) })
2008
+ ] }) : null,
2009
+ /* @__PURE__ */ jsxs2("div", { className: "cat-cart-item__qty", children: [
2010
+ /* @__PURE__ */ jsx2(
2011
+ "button",
2012
+ {
2013
+ type: "button",
2014
+ className: "cat-cart-item__qty-btn",
2015
+ "aria-label": messages.decreaseQuantity,
2016
+ disabled: isLoading || item.quantity <= 1,
2017
+ onClick: () => onQuantityChange(item.id, item.quantity - 1),
2018
+ children: /* @__PURE__ */ jsx2(Minus, { size: 12, strokeWidth: ICON_STROKE })
2019
+ }
2020
+ ),
2021
+ /* @__PURE__ */ jsx2("span", { className: "cat-cart-item__qty-value", children: item.quantity }),
2022
+ /* @__PURE__ */ jsx2(
2023
+ "button",
2024
+ {
2025
+ type: "button",
2026
+ className: "cat-cart-item__qty-btn",
2027
+ "aria-label": messages.increaseQuantity,
2028
+ disabled: isLoading,
2029
+ onClick: () => onQuantityChange(item.id, item.quantity + 1),
2030
+ children: /* @__PURE__ */ jsx2(Plus, { size: 12, strokeWidth: ICON_STROKE })
2031
+ }
2032
+ )
2033
+ ] })
2034
+ ] }),
2035
+ /* @__PURE__ */ jsx2(
2036
+ "button",
2037
+ {
2038
+ type: "button",
2039
+ className: "cat-icon-btn",
2040
+ "aria-label": messages.removeItem,
2041
+ disabled: isLoading,
2042
+ onClick: () => onRemove(item.id),
2043
+ children: /* @__PURE__ */ jsx2(Trash2, { size: 14, strokeWidth: ICON_STROKE })
2044
+ }
2045
+ )
2046
+ ] }, item.id)) }),
2047
+ authBlock,
2048
+ !isEmpty && /* @__PURE__ */ jsxs2("footer", { className: "cat-cart-panel__footer", children: [
2049
+ /* @__PURE__ */ jsxs2("div", { className: "cat-cart-panel__total", children: [
2050
+ /* @__PURE__ */ jsx2("span", { children: messages.total }),
2051
+ /* @__PURE__ */ jsx2("strong", { children: cart.totalPrice.formatted })
2052
+ ] }),
2053
+ onCheckout ? /* @__PURE__ */ jsxs2(
2054
+ "button",
2055
+ {
2056
+ type: "button",
2057
+ className: "cat-checkout-cta",
2058
+ disabled: isLoading,
2059
+ onClick: () => onCheckout(cart),
2060
+ children: [
2061
+ /* @__PURE__ */ jsx2("span", { children: messages.checkout }),
2062
+ /* @__PURE__ */ jsx2("span", { className: "cat-checkout-cta__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx2(ArrowRight, { size: 15, strokeWidth: ICON_STROKE }) })
2063
+ ]
2064
+ }
2065
+ ) : null
2066
+ ] })
2067
+ ]
2068
+ }
2069
+ );
2070
+ }
2071
+
2072
+ // src/components/MissionResults.tsx
2073
+ import { useEffect as useEffect6, useId, useMemo as useMemo2, useRef as useRef6, useState as useState7 } from "react";
2074
+ import { Check, Package as Package2, SearchX, ShoppingCart } from "lucide-react";
2075
+ import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2076
+ function formatMissionAddAll(template, count) {
2077
+ const withPlural = count === 1 ? template.replaceAll("top picks", "top pick") : template;
2078
+ if (withPlural.includes("{count}")) {
2079
+ return withPlural.replaceAll("{count}", String(count));
2080
+ }
2081
+ return `${withPlural} (${count})`;
2082
+ }
2083
+ function toCartItem(product, quantity) {
2084
+ return product.sku ? { sku: product.sku, quantity } : { productId: product.id, variantId: product.variantId, quantity };
2085
+ }
2086
+ function lineItemKey(item) {
2087
+ if (item.sku) {
2088
+ return `sku:${item.sku}`;
2089
+ }
2090
+ return `id:${item.productId ?? ""}:${item.variantId ?? ""}`;
2091
+ }
2092
+ function starterBundleItems(mission) {
2093
+ const items = [];
2094
+ const seen = /* @__PURE__ */ new Set();
2095
+ for (const group of mission.intents) {
2096
+ if (group.failed) {
2097
+ continue;
2098
+ }
2099
+ const product = group.products[0];
2100
+ if (!product || !product.sku && !product.id) {
2101
+ continue;
2102
+ }
2103
+ const item = toCartItem(product, 1);
2104
+ const key = lineItemKey(item);
2105
+ if (seen.has(key)) {
2106
+ continue;
2107
+ }
2108
+ seen.add(key);
2109
+ items.push(item);
2110
+ }
2111
+ return items;
2112
+ }
2113
+ function ProductCardBody({ product }) {
2114
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
2115
+ product.imageUrl ? /* @__PURE__ */ jsx3("img", { src: product.imageUrl, alt: "", className: "cat-result-image", loading: "lazy" }) : /* @__PURE__ */ jsx3("div", { className: "cat-result-image cat-result-image--placeholder", children: /* @__PURE__ */ jsx3(Package2, { size: 20, strokeWidth: ICON_STROKE, color: "var(--cat-text-muted)" }) }),
2116
+ /* @__PURE__ */ jsxs3("div", { className: "cat-result-info", children: [
2117
+ /* @__PURE__ */ jsx3("div", { className: "cat-result-name", children: product.name }),
2118
+ product.price ? /* @__PURE__ */ jsx3("div", { className: "cat-result-price", children: product.price.formatted }) : null
2119
+ ] })
2120
+ ] });
2121
+ }
2122
+ function MissionResults({
2123
+ mission,
2124
+ messages,
2125
+ enableCart,
2126
+ isMutating,
2127
+ addedProductIds,
2128
+ onProductSelect,
2129
+ onAddItem,
2130
+ onAddAll
2131
+ }) {
2132
+ const titleId = useId();
2133
+ const [justAddedAll, setJustAddedAll] = useState7(false);
2134
+ const addedAllTimeoutRef = useRef6(null);
2135
+ const bundleItems = useMemo2(() => starterBundleItems(mission), [mission]);
2136
+ const totalQuantity = bundleItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
2137
+ useEffect6(() => {
2138
+ setJustAddedAll(false);
2139
+ }, [mission]);
2140
+ useEffect6(() => {
2141
+ return () => {
2142
+ if (addedAllTimeoutRef.current !== null) {
2143
+ window.clearTimeout(addedAllTimeoutRef.current);
2144
+ }
2145
+ };
2146
+ }, []);
2147
+ return /* @__PURE__ */ jsxs3("div", { className: "cat-mission", children: [
2148
+ /* @__PURE__ */ jsxs3("div", { className: "cat-mission__header", children: [
2149
+ /* @__PURE__ */ jsx3("h2", { id: titleId, className: "cat-mission__title", children: messages.missionTitle }),
2150
+ mission.interpretation ? /* @__PURE__ */ jsx3("p", { className: "cat-mission__interpretation", children: mission.interpretation }) : null
2151
+ ] }),
2152
+ /* @__PURE__ */ jsx3("div", { className: "cat-mission__lanes", role: "list", "aria-labelledby": titleId, children: mission.intents.map((group) => /* @__PURE__ */ jsxs3("section", { className: "cat-mission-group", role: "listitem", children: [
2153
+ /* @__PURE__ */ jsxs3("div", { className: "cat-mission-group__header", children: [
2154
+ /* @__PURE__ */ jsx3("h3", { className: "cat-mission-group__label", children: group.intent.label }),
2155
+ group.intent.quantity > 1 ? /* @__PURE__ */ jsxs3("span", { className: "cat-mission-group__qty", children: [
2156
+ messages.missionQuantity,
2157
+ " ",
2158
+ group.intent.quantity
2159
+ ] }) : null
2160
+ ] }),
2161
+ group.failed ? /* @__PURE__ */ jsxs3("div", { className: "cat-status cat-status--empty", role: "status", children: [
2162
+ /* @__PURE__ */ jsx3(SearchX, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
2163
+ /* @__PURE__ */ jsx3("div", { className: "cat-status__content", children: /* @__PURE__ */ jsx3("div", { className: "cat-status__title", children: messages.missionIntentFailed }) })
2164
+ ] }) : group.products.length === 0 ? /* @__PURE__ */ jsxs3("div", { className: "cat-status cat-status--empty", role: "status", children: [
2165
+ /* @__PURE__ */ jsx3(SearchX, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
2166
+ /* @__PURE__ */ jsx3("div", { className: "cat-status__content", children: /* @__PURE__ */ jsx3("div", { className: "cat-status__title", children: messages.missionIntentEmpty }) })
2167
+ ] }) : /* @__PURE__ */ jsx3("div", { className: "cat-mission-group__products", children: group.products.map((product, index) => {
2168
+ const canAdd = Boolean(product.sku || product.id);
2169
+ const justAdded = Boolean(addedProductIds?.[product.id]);
2170
+ const body = /* @__PURE__ */ jsx3(ProductCardBody, { product });
2171
+ return /* @__PURE__ */ jsx3(
2172
+ "article",
2173
+ {
2174
+ className: `cat-result-card ${index === 0 ? "cat-result-card--primary" : "cat-result-card--compact"}`,
2175
+ children: /* @__PURE__ */ jsxs3("div", { className: "cat-result-card__core", children: [
2176
+ onProductSelect ? /* @__PURE__ */ jsx3(
2177
+ "button",
2178
+ {
2179
+ type: "button",
2180
+ className: "cat-result-card__select",
2181
+ onClick: () => onProductSelect(product),
2182
+ children: body
2183
+ }
2184
+ ) : /* @__PURE__ */ jsx3("div", { className: "cat-result-card__select", children: body }),
2185
+ enableCart ? /* @__PURE__ */ jsx3(
2186
+ "button",
2187
+ {
2188
+ type: "button",
2189
+ className: `cat-icon-btn cat-result-card__add${justAdded ? " cat-result-card__add--added" : ""}`,
2190
+ "aria-label": justAdded ? messages.itemAdded : canAdd ? messages.addToCart : messages.unableToAddToCart,
2191
+ disabled: !canAdd || isMutating,
2192
+ onClick: () => onAddItem?.(product),
2193
+ children: justAdded ? /* @__PURE__ */ jsx3(Check, { size: 16, strokeWidth: ICON_STROKE }) : /* @__PURE__ */ jsx3(ShoppingCart, { size: 16, strokeWidth: ICON_STROKE })
2194
+ }
2195
+ ) : null
2196
+ ] })
2197
+ },
2198
+ product.id
2199
+ );
2200
+ }) })
2201
+ ] }, group.intent.id)) }),
2202
+ enableCart ? /* @__PURE__ */ jsx3("div", { className: "cat-mission__footer", children: /* @__PURE__ */ jsx3(
2203
+ "button",
2204
+ {
2205
+ type: "button",
2206
+ className: `cat-checkout-cta cat-mission__add-all${justAddedAll ? " cat-mission__add-all--added" : ""}`,
2207
+ disabled: isMutating || bundleItems.length === 0,
2208
+ onClick: () => {
2209
+ void onAddAll(bundleItems).then((result) => {
2210
+ if (result) {
2211
+ setJustAddedAll(true);
2212
+ if (addedAllTimeoutRef.current !== null) {
2213
+ window.clearTimeout(addedAllTimeoutRef.current);
2214
+ }
2215
+ addedAllTimeoutRef.current = window.setTimeout(() => {
2216
+ setJustAddedAll(false);
2217
+ addedAllTimeoutRef.current = null;
2218
+ }, 1200);
2219
+ }
2220
+ });
2221
+ },
2222
+ children: justAddedAll ? messages.missionItemsAdded : formatMissionAddAll(messages.missionAddAll, totalQuantity)
2223
+ }
2224
+ ) }) : null
2225
+ ] });
2226
+ }
2227
+
2228
+ // src/components/SearchFacets.tsx
2229
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2230
+ function SearchFacets({
2231
+ facets,
2232
+ suggestedFacets,
2233
+ filters,
2234
+ messages,
2235
+ onChange,
2236
+ onNewSearch
2237
+ }) {
2238
+ const suggestedNames = new Set(suggestedFacets.map((facet) => facet.name));
2239
+ const visibleFacets = facets.filter((facet) => suggestedNames.size === 0 || suggestedNames.has(facet.id));
2240
+ if (!visibleFacets.length) return null;
2241
+ return /* @__PURE__ */ jsxs4("section", { className: "cat-facets", "aria-label": messages.filtersAriaLabel, children: [
2242
+ /* @__PURE__ */ jsxs4("div", { className: "cat-facets__header", children: [
2243
+ /* @__PURE__ */ jsx4("span", { children: messages.narrowResults }),
2244
+ /* @__PURE__ */ jsxs4("div", { className: "cat-facets__actions", children: [
2245
+ Object.keys(filters).length > 0 && /* @__PURE__ */ jsx4("button", { type: "button", className: "cat-facets__clear", onClick: () => onChange({}), children: messages.clearFilters }),
2246
+ onNewSearch && /* @__PURE__ */ jsx4("button", { type: "button", className: "cat-facets__new", onClick: onNewSearch, children: messages.newSearch })
2247
+ ] })
2248
+ ] }),
2249
+ visibleFacets.map((facet) => /* @__PURE__ */ jsxs4("div", { className: "cat-facet-group", role: "group", "aria-label": facet.label, children: [
2250
+ /* @__PURE__ */ jsx4("span", { className: "cat-facet-group__label", children: facet.label }),
2251
+ /* @__PURE__ */ jsx4("div", { className: "cat-facet-group__options", children: facet.buckets.map((bucket) => {
2252
+ const selected = isFacetFilterSelected(filters, facet.id, bucket.key);
2253
+ const swatch = isColorLikeFacetName(facet.id) ? hexColorSwatchValue(bucket.key) : void 0;
2254
+ return /* @__PURE__ */ jsxs4(
2255
+ "button",
2256
+ {
2257
+ type: "button",
2258
+ className: `cat-facet-chip ${selected ? "cat-facet-chip--selected" : ""}`,
2259
+ "aria-pressed": selected,
2260
+ onClick: () => onChange(toggleFacetFilter(filters, facet.id, bucket.key)),
2261
+ children: [
2262
+ swatch ? /* @__PURE__ */ jsx4(
2263
+ "span",
2264
+ {
2265
+ className: "cat-facet-chip__swatch",
2266
+ style: { backgroundColor: swatch },
2267
+ "aria-hidden": "true"
2268
+ }
2269
+ ) : null,
2270
+ bucket.label,
2271
+ " ",
2272
+ /* @__PURE__ */ jsx4("span", { className: "cat-facet-chip__count", "aria-hidden": "true", children: bucket.count })
2273
+ ]
2274
+ },
2275
+ bucket.key
2276
+ );
2277
+ }) })
2278
+ ] }, facet.id))
2279
+ ] });
2280
+ }
2281
+
2282
+ // src/components/VoiceStatusBanner.tsx
2283
+ import { X as X3 } from "lucide-react";
2284
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2285
+ function VoiceWaveform() {
2286
+ return /* @__PURE__ */ jsxs5("div", { className: "cat-voice-waveform", "aria-hidden": "true", children: [
2287
+ /* @__PURE__ */ jsx5("span", {}),
2288
+ /* @__PURE__ */ jsx5("span", {}),
2289
+ /* @__PURE__ */ jsx5("span", {}),
2290
+ /* @__PURE__ */ jsx5("span", {}),
2291
+ /* @__PURE__ */ jsx5("span", {})
2292
+ ] });
2293
+ }
2294
+ function ProcessingSpinner() {
2295
+ return /* @__PURE__ */ jsx5("span", { className: "cat-voice-spinner", "aria-hidden": "true" });
2296
+ }
2297
+ function VoiceStatusBanner({
2298
+ isRecording,
2299
+ isProcessing,
2300
+ isLoadingTts,
2301
+ error,
2302
+ durationSeconds,
2303
+ messages,
2304
+ onDismissError
2305
+ }) {
2306
+ const isError = Boolean(error);
2307
+ return /* @__PURE__ */ jsxs5(
2308
+ "div",
2309
+ {
2310
+ className: `cat-voice-banner ${isError ? "cat-voice-banner--error" : ""}`,
2311
+ role: "status",
2312
+ "aria-live": "polite",
2313
+ children: [
2314
+ isError && /* @__PURE__ */ jsxs5("div", { className: "cat-voice-banner__content", children: [
2315
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__label", children: error }),
2316
+ onDismissError && /* @__PURE__ */ jsx5(
2317
+ "button",
2318
+ {
2319
+ type: "button",
2320
+ className: "cat-voice-banner__dismiss",
2321
+ onClick: onDismissError,
2322
+ "aria-label": messages.dismiss,
2323
+ children: /* @__PURE__ */ jsx5(X3, { size: 14, strokeWidth: ICON_STROKE, "aria-hidden": "true" })
2324
+ }
2325
+ )
2326
+ ] }),
2327
+ !isError && isRecording && /* @__PURE__ */ jsxs5("div", { className: "cat-voice-banner__content", children: [
2328
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-dot", "aria-hidden": "true" }),
2329
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__label", children: messages.listening }),
2330
+ /* @__PURE__ */ jsx5(VoiceWaveform, {}),
2331
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__timer", children: formatRecordingDuration(durationSeconds) }),
2332
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__hint", children: messages.tapMicToStop })
2333
+ ] }),
2334
+ !isError && !isRecording && isProcessing && /* @__PURE__ */ jsxs5("div", { className: "cat-voice-banner__content", children: [
2335
+ /* @__PURE__ */ jsx5(ProcessingSpinner, {}),
2336
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__label", children: messages.understandingQuery })
2337
+ ] }),
2338
+ !isError && !isRecording && !isProcessing && isLoadingTts && /* @__PURE__ */ jsxs5("div", { className: "cat-voice-banner__content", children: [
2339
+ /* @__PURE__ */ jsx5(ProcessingSpinner, {}),
2340
+ /* @__PURE__ */ jsx5("span", { className: "cat-voice-banner__label", children: messages.preparingAudioSummary })
2341
+ ] })
2342
+ ]
2343
+ }
2344
+ );
2345
+ }
2346
+
2347
+ // src/components/CommerceAISearch.tsx
2348
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2349
+ function CommerceAISearch({
2350
+ apiBaseUrl,
2351
+ theme = "auto",
2352
+ catalogLocale,
2353
+ queryLocale,
2354
+ locale,
2355
+ placeholder,
2356
+ messages: messageOverrides,
2357
+ enableAutocomplete = false,
2358
+ enableFacets = false,
2359
+ persistSession = true,
2360
+ enableVoice = true,
2361
+ enableImageSearch = true,
2362
+ enableCameraSearch = true,
2363
+ cameraFacingMode = "environment",
2364
+ enableTts = true,
2365
+ enableCart = false,
2366
+ enableMissions = false,
2367
+ currency,
2368
+ country,
2369
+ className,
2370
+ onProductSelect,
2371
+ onCartChange,
2372
+ onCheckout
2373
+ }) {
2374
+ const messages = useMemo3(
2375
+ () => resolveCommerceAISearchMessages({
2376
+ ...messageOverrides,
2377
+ ...placeholder ? { placeholder } : {}
2378
+ }),
2379
+ [messageOverrides, placeholder]
2380
+ );
2381
+ const fileInputRef = useRef7(null);
2382
+ const cameraInputRef = useRef7(null);
2383
+ const [isDragging, setIsDragging] = useState8(false);
2384
+ const [lastSearchMode, setLastSearchMode] = useState8(null);
2385
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = useState8(-1);
2386
+ const [suggestionsDismissed, setSuggestionsDismissed] = useState8(false);
2387
+ const camera = useCameraCapture({ facingMode: cameraFacingMode });
2388
+ const {
2389
+ query,
2390
+ setQuery,
2391
+ suggestions,
2392
+ isLoadingSuggestions,
2393
+ suggestionsError,
2394
+ suggestionsReady,
2395
+ selectSuggestion,
2396
+ results,
2397
+ mission,
2398
+ isLoading,
2399
+ hasSearched,
2400
+ setHasSearched,
2401
+ error,
2402
+ meta,
2403
+ search,
2404
+ searchByImage,
2405
+ setError,
2406
+ setIsLoading,
2407
+ facets = [],
2408
+ suggestedFacets = [],
2409
+ hasFacetSession = false,
2410
+ refineFilters = async () => void 0,
2411
+ refine = async () => void 0,
2412
+ startNewSearch = async () => void 0,
2413
+ applySearchResult = () => void 0
2414
+ } = useCommerceAISearch({
2415
+ apiBaseUrl,
2416
+ catalogLocale,
2417
+ queryLocale,
2418
+ locale,
2419
+ enableAutocomplete,
2420
+ enableFacets,
2421
+ persistSession,
2422
+ enableMissions
2423
+ });
2424
+ const voice = useVoiceSearch({
2425
+ apiBaseUrl,
2426
+ catalogLocale,
2427
+ queryLocale,
2428
+ locale,
2429
+ enableTts,
2430
+ enableMissions,
2431
+ onResults: (products, resultMeta, extras) => {
2432
+ setLastSearchMode("voice");
2433
+ applySearchResult({
2434
+ products,
2435
+ meta: resultMeta,
2436
+ mission: extras?.mission
2437
+ });
2438
+ setError(null);
2439
+ setIsLoading(false);
2440
+ setHasSearched(true);
2441
+ },
2442
+ onTranscript: (transcript) => setQuery(transcript, { search: false })
2443
+ });
2444
+ const recordingDuration = useRecordingDuration(voice.isRecording);
2445
+ const showVoiceBanner = enableVoice && (voice.isRecording || voice.isProcessing || voice.isLoadingTts || Boolean(voice.error));
2446
+ const cart = useCart({
2447
+ apiBaseUrl,
2448
+ currency,
2449
+ country,
2450
+ catalogLocale,
2451
+ enabled: enableCart,
2452
+ onCartChange
2453
+ });
2454
+ const cartQuantity = cart.cart?.totalQuantity ?? 0;
2455
+ const cartBadgeLabel = cartQuantity > 99 ? "99+" : String(cartQuantity);
2456
+ const [addedProductIds, setAddedProductIds] = useState8({});
2457
+ const handleAddToCart = useCallback5(
2458
+ async (product) => {
2459
+ if (!product.sku && !product.id) {
2460
+ return;
2461
+ }
2462
+ const next = await cart.addToCart(
2463
+ product.sku ? { sku: product.sku } : { productId: product.id, variantId: product.variantId }
2464
+ );
2465
+ if (!next) {
2466
+ return;
2467
+ }
2468
+ setAddedProductIds((current) => ({ ...current, [product.id]: true }));
2469
+ window.setTimeout(() => {
2470
+ setAddedProductIds((current) => {
2471
+ const nextAdded = { ...current };
2472
+ delete nextAdded[product.id];
2473
+ return nextAdded;
2474
+ });
2475
+ }, 1200);
2476
+ },
2477
+ [cart.addToCart]
2478
+ );
2479
+ const displayResults = results;
2480
+ const showMission = Boolean(mission);
2481
+ const missionLaneCount = mission?.intents.length ?? 0;
2482
+ const showEmptyResults = !isLoading && !error && hasSearched && displayResults.length === 0 && !showMission && query.trim().length > 0;
2483
+ const showResults = query.trim().length > 0 && (isLoading || !!error || displayResults.length > 0 || showEmptyResults || showMission);
2484
+ const showSuggestions = enableAutocomplete && !suggestionsDismissed && !showResults && query.trim().length >= 2 && (isLoadingSuggestions || suggestions.length > 0 || !!suggestionsError || suggestionsReady);
2485
+ const handleSubmit = useCallback5(
2486
+ (event) => {
2487
+ event.preventDefault();
2488
+ setLastSearchMode("text");
2489
+ voice.clearAudioSummary();
2490
+ setActiveSuggestionIndex(-1);
2491
+ if (enableAutocomplete && activeSuggestionIndex >= 0 && suggestions[activeSuggestionIndex]) {
2492
+ selectSuggestion(suggestions[activeSuggestionIndex]);
2493
+ return;
2494
+ }
2495
+ if (enableFacets && hasFacetSession && !(enableMissions && looksLikeCompoundShoppingList(query))) {
2496
+ void refine(query);
2497
+ } else {
2498
+ void search(query);
2499
+ }
2500
+ },
2501
+ [
2502
+ activeSuggestionIndex,
2503
+ enableAutocomplete,
2504
+ enableFacets,
2505
+ enableMissions,
2506
+ hasFacetSession,
2507
+ query,
2508
+ refine,
2509
+ search,
2510
+ selectSuggestion,
2511
+ suggestions,
2512
+ voice
2513
+ ]
2514
+ );
2515
+ const handleQueryChange = useCallback5(
2516
+ (value) => {
2517
+ setActiveSuggestionIndex(-1);
2518
+ setSuggestionsDismissed(false);
2519
+ const trimmed = value.trim();
2520
+ if (!trimmed) {
2521
+ setLastSearchMode(null);
2522
+ voice.clearAudioSummary();
2523
+ } else if (trimmed.length >= 2) {
2524
+ setLastSearchMode("text");
2525
+ voice.clearAudioSummary();
2526
+ }
2527
+ setQuery(value);
2528
+ },
2529
+ [setQuery, voice]
2530
+ );
2531
+ const handleSuggestionSelect = useCallback5(
2532
+ (suggestion) => {
2533
+ setActiveSuggestionIndex(-1);
2534
+ setLastSearchMode("text");
2535
+ voice.clearAudioSummary();
2536
+ selectSuggestion(suggestion);
2537
+ },
2538
+ [selectSuggestion, voice]
2539
+ );
2540
+ const handleInputKeyDown = useCallback5(
2541
+ (event) => {
2542
+ if (event.key === "Escape") {
2543
+ setActiveSuggestionIndex(-1);
2544
+ setSuggestionsDismissed(true);
2545
+ return;
2546
+ }
2547
+ if (!showSuggestions || suggestions.length === 0) {
2548
+ return;
2549
+ }
2550
+ if (event.key === "ArrowDown") {
2551
+ event.preventDefault();
2552
+ setActiveSuggestionIndex((current) => current < suggestions.length - 1 ? current + 1 : 0);
2553
+ return;
2554
+ }
2555
+ if (event.key === "ArrowUp") {
2556
+ event.preventDefault();
2557
+ setActiveSuggestionIndex((current) => current > 0 ? current - 1 : suggestions.length - 1);
2558
+ return;
2559
+ }
2560
+ if (event.key === "Enter" && activeSuggestionIndex >= 0) {
2561
+ event.preventDefault();
2562
+ const suggestion = suggestions[activeSuggestionIndex];
2563
+ if (suggestion) {
2564
+ handleSuggestionSelect(suggestion);
2565
+ }
2566
+ }
2567
+ },
2568
+ [activeSuggestionIndex, handleSuggestionSelect, showSuggestions, suggestions]
2569
+ );
2570
+ const handleImageSelect = useCallback5(
2571
+ (file) => {
2572
+ if (!file.type.startsWith("image/")) return;
2573
+ setLastSearchMode("image");
2574
+ voice.clearAudioSummary();
2575
+ void searchByImage(file);
2576
+ },
2577
+ [searchByImage, voice]
2578
+ );
2579
+ const handleCameraCapture = useCallback5(
2580
+ async (video) => {
2581
+ try {
2582
+ const file = await camera.capturePhoto(video);
2583
+ handleImageSelect(file);
2584
+ } catch (err) {
2585
+ camera.close();
2586
+ setError(err instanceof Error ? err.message : messages.couldNotCapturePhoto);
2587
+ }
2588
+ },
2589
+ [camera, handleImageSelect, messages.couldNotCapturePhoto, setError]
2590
+ );
2591
+ const handleDrop = useCallback5(
2592
+ (event) => {
2593
+ event.preventDefault();
2594
+ setIsDragging(false);
2595
+ if (!enableImageSearch) {
2596
+ return;
2597
+ }
2598
+ const file = event.dataTransfer.files[0];
2599
+ if (file) handleImageSelect(file);
2600
+ },
2601
+ [enableImageSearch, handleImageSelect]
2602
+ );
2603
+ const showVoiceReplay = enableVoice && enableTts && lastSearchMode === "voice" && Boolean(voice.audioSummary);
2604
+ return /* @__PURE__ */ jsxs6(
2605
+ "div",
2606
+ {
2607
+ className: [
2608
+ "cat-root",
2609
+ "cat-wrapper",
2610
+ showMission && !isLoading && !error ? `cat-root--mission cat-root--mission-lanes-${Math.min(Math.max(missionLaneCount, 1), 5)}` : "",
2611
+ className ?? ""
2612
+ ].filter(Boolean).join(" "),
2613
+ "data-theme": theme,
2614
+ onDragOver: (e) => {
2615
+ if (!enableImageSearch) {
2616
+ return;
2617
+ }
2618
+ e.preventDefault();
2619
+ setIsDragging(true);
2620
+ },
2621
+ onDragLeave: () => setIsDragging(false),
2622
+ onDrop: handleDrop,
2623
+ role: "search",
2624
+ "aria-label": messages.productSearchAriaLabel,
2625
+ children: [
2626
+ isDragging && enableImageSearch && /* @__PURE__ */ jsx6("div", { className: "cat-drag-overlay", "aria-hidden": "true", children: messages.dropImageToSearch }),
2627
+ /* @__PURE__ */ jsx6("div", { className: "cat-search-shell", children: /* @__PURE__ */ jsxs6(
2628
+ "form",
2629
+ {
2630
+ className: `cat-search-bar ${voice.isRecording ? "cat-search-bar--voice-active" : ""}`,
2631
+ onSubmit: handleSubmit,
2632
+ children: [
2633
+ /* @__PURE__ */ jsx6(Search, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true", color: "var(--cat-text-muted)" }),
2634
+ /* @__PURE__ */ jsx6("div", { className: "cat-search-input-wrap", children: /* @__PURE__ */ jsx6(
2635
+ "input",
2636
+ {
2637
+ type: "search",
2638
+ className: "cat-search-input",
2639
+ value: query,
2640
+ onChange: (e) => handleQueryChange(e.target.value),
2641
+ onKeyDown: handleInputKeyDown,
2642
+ placeholder: messages.placeholder,
2643
+ "aria-label": messages.searchAriaLabel,
2644
+ "aria-haspopup": enableAutocomplete ? "listbox" : void 0,
2645
+ "aria-autocomplete": enableAutocomplete ? "list" : void 0,
2646
+ "aria-expanded": showSuggestions,
2647
+ "aria-controls": showSuggestions ? "cat-suggestions-listbox" : void 0,
2648
+ "aria-activedescendant": activeSuggestionIndex >= 0 ? `cat-suggestion-${activeSuggestionIndex}` : void 0,
2649
+ autoComplete: "off",
2650
+ role: enableAutocomplete ? "combobox" : void 0
2651
+ }
2652
+ ) }),
2653
+ /* @__PURE__ */ jsxs6("div", { className: "cat-search-actions", children: [
2654
+ enableVoice && /* @__PURE__ */ jsx6(
2655
+ "button",
2656
+ {
2657
+ type: "button",
2658
+ className: `cat-icon-btn ${voice.isRecording ? "cat-icon-btn--active" : ""}`,
2659
+ onClick: () => void voice.toggleRecording(),
2660
+ disabled: voice.isProcessing,
2661
+ "aria-label": voice.isRecording ? messages.stopRecording : messages.voiceSearch,
2662
+ "aria-pressed": voice.isRecording,
2663
+ children: voice.isRecording ? /* @__PURE__ */ jsx6(Square, { size: 16, strokeWidth: ICON_STROKE }) : /* @__PURE__ */ jsx6(Mic, { size: 16, strokeWidth: ICON_STROKE })
2664
+ }
2665
+ ),
2666
+ enableCameraSearch ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2667
+ /* @__PURE__ */ jsx6(
2668
+ "button",
2669
+ {
2670
+ type: "button",
2671
+ className: "cat-icon-btn",
2672
+ onClick: () => camera.open(cameraInputRef),
2673
+ disabled: isLoading,
2674
+ "aria-label": messages.searchByCamera,
2675
+ children: /* @__PURE__ */ jsx6(Camera2, { size: 16, strokeWidth: ICON_STROKE })
2676
+ }
2677
+ ),
2678
+ /* @__PURE__ */ jsx6(
2679
+ "input",
2680
+ {
2681
+ ref: cameraInputRef,
2682
+ type: "file",
2683
+ accept: "image/*",
2684
+ capture: cameraFacingMode,
2685
+ className: "cat-hidden-input",
2686
+ onChange: (e) => {
2687
+ const file = e.target.files?.[0];
2688
+ e.target.value = "";
2689
+ if (file) handleImageSelect(file);
2690
+ }
2691
+ }
2692
+ )
2693
+ ] }) : null,
2694
+ enableImageSearch ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2695
+ /* @__PURE__ */ jsx6(
2696
+ "button",
2697
+ {
2698
+ type: "button",
2699
+ className: "cat-icon-btn",
2700
+ onClick: () => fileInputRef.current?.click(),
2701
+ disabled: isLoading,
2702
+ "aria-label": messages.searchByImage,
2703
+ children: /* @__PURE__ */ jsx6(ImageIcon, { size: 16, strokeWidth: ICON_STROKE })
2704
+ }
2705
+ ),
2706
+ /* @__PURE__ */ jsx6(
2707
+ "input",
2708
+ {
2709
+ ref: fileInputRef,
2710
+ type: "file",
2711
+ accept: "image/*",
2712
+ className: "cat-hidden-input",
2713
+ onChange: (e) => {
2714
+ const file = e.target.files?.[0];
2715
+ if (file) handleImageSelect(file);
2716
+ }
2717
+ }
2718
+ )
2719
+ ] }) : null,
2720
+ showVoiceReplay && /* @__PURE__ */ jsx6(
2721
+ "button",
2722
+ {
2723
+ type: "button",
2724
+ className: "cat-icon-btn",
2725
+ onClick: () => voice.replayAudioSummary(),
2726
+ "aria-label": messages.replayVoiceSummary,
2727
+ children: /* @__PURE__ */ jsx6(Volume2, { size: 16, strokeWidth: ICON_STROKE })
2728
+ }
2729
+ )
2730
+ ] }),
2731
+ enableCart && /* @__PURE__ */ jsxs6(
2732
+ "button",
2733
+ {
2734
+ type: "button",
2735
+ className: `cat-icon-btn cat-cart-toggle ${cart.isCartOpen ? "cat-cart-toggle--open" : ""}`,
2736
+ onClick: cart.toggleCart,
2737
+ "aria-label": cartQuantity > 0 ? `${messages.cartAriaLabel} (${cartBadgeLabel})` : messages.cartAriaLabel,
2738
+ "aria-expanded": cart.isCartOpen,
2739
+ "aria-pressed": cart.isCartOpen,
2740
+ children: [
2741
+ /* @__PURE__ */ jsx6(ShoppingCart2, { size: 16, strokeWidth: ICON_STROKE }),
2742
+ cartQuantity > 0 && /* @__PURE__ */ jsx6("span", { className: "cat-cart-badge", "aria-hidden": "true", children: cartBadgeLabel })
2743
+ ]
2744
+ }
2745
+ ),
2746
+ showSuggestions && /* @__PURE__ */ jsxs6(
2747
+ "div",
2748
+ {
2749
+ id: "cat-suggestions-listbox",
2750
+ className: "cat-suggestions",
2751
+ role: "listbox",
2752
+ "aria-label": messages.suggestionsAriaLabel,
2753
+ children: [
2754
+ isLoadingSuggestions && suggestions.length === 0 && !suggestionsError && /* @__PURE__ */ jsx6("div", { className: "cat-suggestions__status", children: messages.loadingSuggestions }),
2755
+ suggestionsError && /* @__PURE__ */ jsx6("div", { className: "cat-suggestions__status cat-suggestions__status--error", role: "alert", children: suggestionsError }),
2756
+ !isLoadingSuggestions && !suggestionsError && suggestionsReady && suggestions.length === 0 && /* @__PURE__ */ jsx6("div", { className: "cat-suggestions__status", children: messages.noSuggestions }),
2757
+ suggestions.map((suggestion, index) => /* @__PURE__ */ jsx6(
2758
+ "button",
2759
+ {
2760
+ id: `cat-suggestion-${index}`,
2761
+ type: "button",
2762
+ className: `cat-suggestions__item ${index === activeSuggestionIndex ? "cat-suggestions__item--active" : ""}`,
2763
+ role: "option",
2764
+ "aria-selected": index === activeSuggestionIndex,
2765
+ onMouseDown: (event) => event.preventDefault(),
2766
+ onClick: () => handleSuggestionSelect(suggestion),
2767
+ children: suggestion
2768
+ },
2769
+ `${index}-${suggestion}`
2770
+ ))
2771
+ ]
2772
+ }
2773
+ )
2774
+ ]
2775
+ }
2776
+ ) }),
2777
+ showVoiceBanner && /* @__PURE__ */ jsx6(
2778
+ VoiceStatusBanner,
2779
+ {
2780
+ isRecording: voice.isRecording,
2781
+ isProcessing: voice.isProcessing,
2782
+ isLoadingTts: voice.isLoadingTts,
2783
+ error: voice.error,
2784
+ durationSeconds: recordingDuration,
2785
+ messages,
2786
+ onDismissError: voice.clearError
2787
+ }
2788
+ ),
2789
+ enableFacets && lastSearchMode === "text" && hasSearched && !showMission && /* @__PURE__ */ jsx6(
2790
+ SearchFacets,
2791
+ {
2792
+ facets,
2793
+ suggestedFacets,
2794
+ filters: meta?.appliedFilters ?? {},
2795
+ messages,
2796
+ onChange: (filters) => void refineFilters(filters),
2797
+ onNewSearch: () => void startNewSearch()
2798
+ }
2799
+ ),
2800
+ enableCameraSearch && (camera.isOpen || camera.error) && /* @__PURE__ */ jsx6(
2801
+ CameraCaptureOverlay,
2802
+ {
2803
+ stream: camera.stream,
2804
+ error: camera.error,
2805
+ messages,
2806
+ onCapture: (video) => void handleCameraCapture(video),
2807
+ onClose: camera.close,
2808
+ onDismissError: camera.clearError
2809
+ }
2810
+ ),
2811
+ showResults && isLoading && /* @__PURE__ */ jsx6("div", { className: "cat-status", children: messages.searching }),
2812
+ showResults && error && /* @__PURE__ */ jsx6("div", { className: "cat-status cat-status--error", children: error }),
2813
+ !isLoading && !error && showResults && showMission && mission ? /* @__PURE__ */ jsx6(
2814
+ MissionResults,
2815
+ {
2816
+ mission,
2817
+ messages,
2818
+ enableCart,
2819
+ isMutating: cart.isMutating,
2820
+ addedProductIds,
2821
+ onProductSelect,
2822
+ onAddItem: (product) => void handleAddToCart(product),
2823
+ onAddAll: (items) => cart.addItems(items)
2824
+ }
2825
+ ) : null,
2826
+ showResults && !showMission && !isLoading && !error && /* @__PURE__ */ jsxs6(
2827
+ "div",
2828
+ {
2829
+ className: "cat-results",
2830
+ role: !isLoading && displayResults.length > 0 ? "list" : void 0,
2831
+ "aria-label": !isLoading && displayResults.length > 0 ? messages.searchResultsAriaLabel : void 0,
2832
+ children: [
2833
+ !isLoading && displayResults.map((product, index) => {
2834
+ const canAdd = Boolean(product.sku || product.id);
2835
+ const justAdded = Boolean(addedProductIds[product.id]);
2836
+ const productBody = /* @__PURE__ */ jsxs6(Fragment4, { children: [
2837
+ product.imageUrl ? /* @__PURE__ */ jsx6(
2838
+ "img",
2839
+ {
2840
+ src: product.imageUrl,
2841
+ alt: "",
2842
+ className: "cat-result-image",
2843
+ loading: "lazy"
2844
+ }
2845
+ ) : /* @__PURE__ */ jsx6("div", { className: "cat-result-image cat-result-image--placeholder", children: /* @__PURE__ */ jsx6(Package3, { size: 20, strokeWidth: ICON_STROKE, color: "var(--cat-text-muted)" }) }),
2846
+ /* @__PURE__ */ jsxs6("div", { className: "cat-result-info", children: [
2847
+ /* @__PURE__ */ jsx6("div", { className: "cat-result-name", children: product.name }),
2848
+ product.price && /* @__PURE__ */ jsx6("div", { className: "cat-result-price", children: product.price.formatted })
2849
+ ] })
2850
+ ] });
2851
+ return /* @__PURE__ */ jsx6(
2852
+ "article",
2853
+ {
2854
+ className: `cat-result-card${index === 0 ? " cat-result-card--featured" : ""}`,
2855
+ role: "listitem",
2856
+ children: /* @__PURE__ */ jsxs6("div", { className: "cat-result-card__core", children: [
2857
+ /* @__PURE__ */ jsx6(
2858
+ "button",
2859
+ {
2860
+ type: "button",
2861
+ className: "cat-result-card__select",
2862
+ onClick: () => onProductSelect?.(product),
2863
+ children: productBody
2864
+ }
2865
+ ),
2866
+ enableCart ? /* @__PURE__ */ jsx6(
2867
+ "button",
2868
+ {
2869
+ type: "button",
2870
+ className: `cat-icon-btn cat-result-card__add ${justAdded ? "cat-result-card__add--added" : ""}`,
2871
+ "aria-label": justAdded ? messages.itemAdded : canAdd ? messages.addToCart : messages.unableToAddToCart,
2872
+ disabled: !canAdd || cart.isMutating,
2873
+ onClick: () => handleAddToCart(product),
2874
+ children: justAdded ? /* @__PURE__ */ jsx6(Check2, { size: 16, strokeWidth: ICON_STROKE }) : /* @__PURE__ */ jsx6(ShoppingCart2, { size: 16, strokeWidth: ICON_STROKE })
2875
+ }
2876
+ ) : null
2877
+ ] })
2878
+ },
2879
+ product.id
2880
+ );
2881
+ }),
2882
+ !isLoading && !error && displayResults.length === 0 && showEmptyResults && /* @__PURE__ */ jsxs6("div", { className: "cat-status cat-status--empty", role: "status", "aria-live": "polite", children: [
2883
+ /* @__PURE__ */ jsx6(SearchX2, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
2884
+ /* @__PURE__ */ jsxs6("div", { className: "cat-status__content", children: [
2885
+ /* @__PURE__ */ jsx6("div", { className: "cat-status__title", children: messages.noProductsFound }),
2886
+ meta?.queryInterpretation && /* @__PURE__ */ jsxs6("div", { className: "cat-status__hint", children: [
2887
+ messages.searchedFor,
2888
+ " ",
2889
+ meta.queryInterpretation
2890
+ ] })
2891
+ ] })
2892
+ ] })
2893
+ ]
2894
+ }
2895
+ ),
2896
+ enableCart && cart.isCartOpen && /* @__PURE__ */ jsx6(
2897
+ CartPanel,
2898
+ {
2899
+ cart: cart.cart,
2900
+ customer: cart.customer,
2901
+ isLoading: cart.isLoading || cart.isMutating,
2902
+ isLoggingIn: cart.isLoggingIn,
2903
+ error: cart.error,
2904
+ messages,
2905
+ catalogLocale,
2906
+ onClose: cart.closeCart,
2907
+ onRemove: (lineItemId) => void cart.removeFromCart(lineItemId),
2908
+ onQuantityChange: (lineItemId, quantity) => void cart.updateQuantity(lineItemId, quantity),
2909
+ onLogin: (input) => void cart.login(input),
2910
+ onLogout: () => void cart.logout(),
2911
+ onCheckout
2912
+ }
2913
+ )
2914
+ ]
2915
+ }
2916
+ );
2917
+ }
2918
+
2919
+ // src/components/CommerceAICheckout.tsx
2920
+ import {
2921
+ useMemo as useMemo4,
2922
+ useState as useState9,
2923
+ useTransition
2924
+ } from "react";
2925
+ import { ArrowLeft, ArrowRight as ArrowRight2, Check as Check3, CreditCard, MapPin, Package as Package4 } from "lucide-react";
2926
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2927
+ var CHECKOUT_COUNTRY_CODES = [
2928
+ "AT",
2929
+ "BE",
2930
+ "CH",
2931
+ "CZ",
2932
+ "DE",
2933
+ "DK",
2934
+ "ES",
2935
+ "FI",
2936
+ "FR",
2937
+ "GB",
2938
+ "IE",
2939
+ "IT",
2940
+ "NL",
2941
+ "NO",
2942
+ "PL",
2943
+ "PT",
2944
+ "SE",
2945
+ "US"
2946
+ ];
2947
+ function checkoutCountryCodes(selected) {
2948
+ const codes = new Set(CHECKOUT_COUNTRY_CODES);
2949
+ const normalized = selected.trim().toUpperCase();
2950
+ if (normalized.length === 2) {
2951
+ codes.add(normalized);
2952
+ }
2953
+ return [...codes].sort();
2954
+ }
2955
+ function countryLabel(code, locale = "en") {
2956
+ try {
2957
+ return new Intl.DisplayNames([locale, "en"], { type: "region" }).of(code) ?? code;
2958
+ } catch {
2959
+ try {
2960
+ return new Intl.DisplayNames(["en"], { type: "region" }).of(code) ?? code;
2961
+ } catch {
2962
+ return code;
2963
+ }
2964
+ }
2965
+ }
2966
+ function createEmptyAddress(country) {
2967
+ return {
2968
+ firstName: "",
2969
+ lastName: "",
2970
+ streetName: "",
2971
+ postalCode: "",
2972
+ city: "",
2973
+ country
2974
+ };
2975
+ }
2976
+ function AddressFields({
2977
+ address,
2978
+ messages,
2979
+ prefix,
2980
+ locale = "en",
2981
+ onChange
2982
+ }) {
2983
+ function update(field, event) {
2984
+ onChange({ ...address, [field]: event.target.value });
2985
+ }
2986
+ return /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__address-grid", children: [
2987
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
2988
+ /* @__PURE__ */ jsx7("span", { children: messages.firstName }),
2989
+ /* @__PURE__ */ jsx7(
2990
+ "input",
2991
+ {
2992
+ name: `${prefix}-firstName`,
2993
+ autoComplete: "given-name",
2994
+ required: true,
2995
+ value: address.firstName,
2996
+ onChange: (event) => update("firstName", event)
2997
+ }
2998
+ )
2999
+ ] }),
3000
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
3001
+ /* @__PURE__ */ jsx7("span", { children: messages.lastName }),
3002
+ /* @__PURE__ */ jsx7(
3003
+ "input",
3004
+ {
3005
+ name: `${prefix}-lastName`,
3006
+ autoComplete: "family-name",
3007
+ required: true,
3008
+ value: address.lastName,
3009
+ onChange: (event) => update("lastName", event)
3010
+ }
3011
+ )
3012
+ ] }),
3013
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field cat-checkout__field--wide", children: [
3014
+ /* @__PURE__ */ jsx7("span", { children: messages.streetName }),
3015
+ /* @__PURE__ */ jsx7(
3016
+ "input",
3017
+ {
3018
+ name: `${prefix}-streetName`,
3019
+ autoComplete: "address-line1",
3020
+ required: true,
3021
+ value: address.streetName,
3022
+ onChange: (event) => update("streetName", event)
3023
+ }
3024
+ )
3025
+ ] }),
3026
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field cat-checkout__field--wide", children: [
3027
+ /* @__PURE__ */ jsx7("span", { children: messages.additionalAddress }),
3028
+ /* @__PURE__ */ jsx7(
3029
+ "input",
3030
+ {
3031
+ name: `${prefix}-additionalStreetInfo`,
3032
+ autoComplete: "address-line2",
3033
+ value: address.additionalStreetInfo ?? "",
3034
+ onChange: (event) => update("additionalStreetInfo", event)
3035
+ }
3036
+ )
3037
+ ] }),
3038
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
3039
+ /* @__PURE__ */ jsx7("span", { children: messages.postalCode }),
3040
+ /* @__PURE__ */ jsx7(
3041
+ "input",
3042
+ {
3043
+ name: `${prefix}-postalCode`,
3044
+ autoComplete: "postal-code",
3045
+ required: true,
3046
+ value: address.postalCode,
3047
+ onChange: (event) => update("postalCode", event)
3048
+ }
3049
+ )
3050
+ ] }),
3051
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
3052
+ /* @__PURE__ */ jsx7("span", { children: messages.city }),
3053
+ /* @__PURE__ */ jsx7(
3054
+ "input",
3055
+ {
3056
+ name: `${prefix}-city`,
3057
+ autoComplete: "address-level2",
3058
+ required: true,
3059
+ value: address.city,
3060
+ onChange: (event) => update("city", event)
3061
+ }
3062
+ )
3063
+ ] }),
3064
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
3065
+ /* @__PURE__ */ jsx7("span", { children: messages.region }),
3066
+ /* @__PURE__ */ jsx7(
3067
+ "input",
3068
+ {
3069
+ name: `${prefix}-region`,
3070
+ autoComplete: "address-level1",
3071
+ value: address.region ?? "",
3072
+ onChange: (event) => update("region", event)
3073
+ }
3074
+ )
3075
+ ] }),
3076
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__field", children: [
3077
+ /* @__PURE__ */ jsx7("span", { children: messages.country }),
3078
+ /* @__PURE__ */ jsx7(
3079
+ "select",
3080
+ {
3081
+ name: `${prefix}-country`,
3082
+ autoComplete: "country",
3083
+ required: true,
3084
+ value: address.country,
3085
+ onChange: (event) => onChange({ ...address, country: event.target.value.toUpperCase() }),
3086
+ children: checkoutCountryCodes(address.country).map((code) => /* @__PURE__ */ jsx7("option", { value: code, children: countryLabel(code, locale) }, code))
3087
+ }
3088
+ )
3089
+ ] })
3090
+ ] });
3091
+ }
3092
+ function OrderSummary({
3093
+ cart,
3094
+ messages
3095
+ }) {
3096
+ return /* @__PURE__ */ jsx7("section", { className: "cat-checkout__bezel cat-checkout__summary", children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3097
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__section-heading", children: [
3098
+ /* @__PURE__ */ jsx7(Package4, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3099
+ /* @__PURE__ */ jsx7("h2", { children: messages.orderSummary })
3100
+ ] }),
3101
+ /* @__PURE__ */ jsx7("ul", { className: "cat-checkout__items", children: cart.lineItems.map((item) => /* @__PURE__ */ jsxs7("li", { className: "cat-checkout__item", children: [
3102
+ /* @__PURE__ */ jsxs7("span", { className: "cat-checkout__item-copy", children: [
3103
+ item.imageUrl ? /* @__PURE__ */ jsx7("img", { src: item.imageUrl, alt: "", className: "cat-checkout__item-image" }) : /* @__PURE__ */ jsx7("div", { className: "cat-checkout__item-image cat-checkout__item-image--placeholder", children: /* @__PURE__ */ jsx7(Package4, { size: 16, strokeWidth: ICON_STROKE, "aria-hidden": "true" }) }),
3104
+ /* @__PURE__ */ jsxs7("span", { children: [
3105
+ item.name,
3106
+ /* @__PURE__ */ jsxs7("small", { children: [
3107
+ "\xD7 ",
3108
+ item.quantity
3109
+ ] })
3110
+ ] })
3111
+ ] }),
3112
+ /* @__PURE__ */ jsx7("strong", { children: item.price?.formatted ?? "\u2014" })
3113
+ ] }, item.id)) }),
3114
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__summary-total", children: [
3115
+ /* @__PURE__ */ jsx7("span", { children: messages.total }),
3116
+ /* @__PURE__ */ jsx7("strong", { children: cart.totalPrice.formatted })
3117
+ ] })
3118
+ ] }) });
3119
+ }
3120
+ function ShippingMethodCard({
3121
+ method,
3122
+ selected,
3123
+ disabled,
3124
+ onSelect
3125
+ }) {
3126
+ return /* @__PURE__ */ jsxs7(
3127
+ "button",
3128
+ {
3129
+ type: "button",
3130
+ className: `cat-checkout__shipping-card${selected ? " cat-checkout__shipping-card--selected" : ""}`,
3131
+ disabled,
3132
+ "aria-pressed": selected,
3133
+ onClick: onSelect,
3134
+ children: [
3135
+ /* @__PURE__ */ jsxs7("span", { className: "cat-checkout__shipping-copy", children: [
3136
+ /* @__PURE__ */ jsx7("strong", { children: method.name }),
3137
+ method.description ? /* @__PURE__ */ jsx7("small", { children: method.description }) : null
3138
+ ] }),
3139
+ /* @__PURE__ */ jsxs7("span", { className: "cat-checkout__shipping-price", children: [
3140
+ method.price?.formatted ?? "",
3141
+ /* @__PURE__ */ jsx7("span", { className: "cat-checkout__shipping-check", "aria-hidden": "true", children: selected ? /* @__PURE__ */ jsx7(Check3, { size: 14, strokeWidth: ICON_STROKE }) : null })
3142
+ ] })
3143
+ ]
3144
+ }
3145
+ );
3146
+ }
3147
+ function CommerceAICheckout({
3148
+ apiBaseUrl,
3149
+ theme = "auto",
3150
+ catalogLocale,
3151
+ currency,
3152
+ country = "DE",
3153
+ messages: messageOverrides,
3154
+ continueShoppingHref = "/",
3155
+ orderStatusHref = "/orders",
3156
+ onOrderPlaced
3157
+ }) {
3158
+ const messages = useMemo4(
3159
+ () => resolveCommerceAISearchMessages(messageOverrides),
3160
+ [messageOverrides]
3161
+ );
3162
+ const cart = useCart({ apiBaseUrl, catalogLocale, currency, country });
3163
+ const [shippingAddress, setShippingAddress] = useState9(
3164
+ () => createEmptyAddress(country)
3165
+ );
3166
+ const [billingAddress, setBillingAddress] = useState9(
3167
+ () => createEmptyAddress(country)
3168
+ );
3169
+ const [billingMatchesShipping, setBillingMatchesShipping] = useState9(true);
3170
+ const [shippingMethods, setShippingMethods] = useState9([]);
3171
+ const [hasLoadedShippingMethods, setHasLoadedShippingMethods] = useState9(false);
3172
+ const [selectedShippingMethodId, setSelectedShippingMethodId] = useState9("");
3173
+ const [paymentMethods, setPaymentMethods] = useState9([]);
3174
+ const [paymentMethodsLoaded, setPaymentMethodsLoaded] = useState9(false);
3175
+ const [selectedPaymentMethod, setSelectedPaymentMethod] = useState9("");
3176
+ const [authorizedPayment, setAuthorizedPayment] = useState9(null);
3177
+ const [order, setOrder] = useState9(null);
3178
+ const [isPlacingOrder, setIsPlacingOrder] = useState9(false);
3179
+ const [isPending, startTransition] = useTransition();
3180
+ function handleAddressSubmit(event) {
3181
+ event.preventDefault();
3182
+ startTransition(async () => {
3183
+ const updated = await cart.setAddresses(
3184
+ shippingAddress,
3185
+ billingMatchesShipping ? void 0 : billingAddress
3186
+ );
3187
+ if (!updated) {
3188
+ return;
3189
+ }
3190
+ const [methods, payments] = await Promise.all([
3191
+ cart.getShippingMethods(),
3192
+ cart.getPaymentMethods()
3193
+ ]);
3194
+ if (methods === null) {
3195
+ return;
3196
+ }
3197
+ setShippingMethods(methods);
3198
+ setHasLoadedShippingMethods(true);
3199
+ setSelectedShippingMethodId(updated.shippingMethod?.id ?? "");
3200
+ if (payments === null) {
3201
+ setPaymentMethods([]);
3202
+ setPaymentMethodsLoaded(false);
3203
+ } else {
3204
+ setPaymentMethods(payments);
3205
+ setPaymentMethodsLoaded(true);
3206
+ }
3207
+ setSelectedPaymentMethod("");
3208
+ setAuthorizedPayment(null);
3209
+ });
3210
+ }
3211
+ function selectShippingMethod(methodId) {
3212
+ startTransition(async () => {
3213
+ const updated = await cart.setShippingMethod(methodId);
3214
+ if (updated) {
3215
+ setSelectedShippingMethodId(methodId);
3216
+ setSelectedPaymentMethod("");
3217
+ setAuthorizedPayment(null);
3218
+ }
3219
+ });
3220
+ }
3221
+ function selectPaymentMethod(method) {
3222
+ if (authorizedPayment?.status === "authorized" && authorizedPayment.method === method && authorizedPayment.amount.amount === cart.cart?.totalPrice.amount && authorizedPayment.amount.currency === cart.cart?.totalPrice.currency) {
3223
+ return;
3224
+ }
3225
+ startTransition(async () => {
3226
+ const payment = await cart.authorizePayment(method);
3227
+ if (payment?.status === "authorized") {
3228
+ setSelectedPaymentMethod(method);
3229
+ setAuthorizedPayment(payment);
3230
+ }
3231
+ });
3232
+ }
3233
+ function placeOrder() {
3234
+ setIsPlacingOrder(true);
3235
+ startTransition(async () => {
3236
+ try {
3237
+ const placed = await cart.placeOrder();
3238
+ if (placed) {
3239
+ setOrder(placed);
3240
+ onOrderPlaced?.(placed);
3241
+ }
3242
+ } finally {
3243
+ setIsPlacingOrder(false);
3244
+ }
3245
+ });
3246
+ }
3247
+ if (order) {
3248
+ return /* @__PURE__ */ jsx7("main", { className: "cat-root cat-checkout", "data-theme": theme, children: /* @__PURE__ */ jsx7("section", { className: "cat-checkout__success cat-checkout__bezel", children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3249
+ /* @__PURE__ */ jsx7("span", { className: "cat-checkout__success-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(Check3, { size: 24, strokeWidth: ICON_STROKE }) }),
3250
+ /* @__PURE__ */ jsx7("p", { className: "cat-checkout__eyebrow", children: messages.checkout }),
3251
+ /* @__PURE__ */ jsx7("h1", { children: messages.orderPlaced }),
3252
+ /* @__PURE__ */ jsx7("strong", { children: order.orderNumber ?? order.id }),
3253
+ (order.orderNumber || order.id) && orderStatusHref ? /* @__PURE__ */ jsx7(
3254
+ "a",
3255
+ {
3256
+ className: "cat-checkout__back",
3257
+ href: `${orderStatusHref}?orderNumber=${encodeURIComponent(order.orderNumber ?? order.id)}`,
3258
+ children: messages.viewOrderStatus
3259
+ }
3260
+ ) : null,
3261
+ /* @__PURE__ */ jsxs7("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3262
+ /* @__PURE__ */ jsx7(ArrowLeft, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3263
+ messages.continueShopping
3264
+ ] })
3265
+ ] }) }) });
3266
+ }
3267
+ if (!cart.cart) {
3268
+ return /* @__PURE__ */ jsx7("main", { className: "cat-root cat-checkout", "data-theme": theme, children: /* @__PURE__ */ jsx7("section", { className: "cat-checkout__empty cat-checkout__bezel", children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3269
+ /* @__PURE__ */ jsx7(Package4, { size: 24, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3270
+ /* @__PURE__ */ jsx7("h1", { children: cart.isLoading || !cart.anonymousId ? messages.searching : messages.emptyCart }),
3271
+ /* @__PURE__ */ jsxs7("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3272
+ /* @__PURE__ */ jsx7(ArrowLeft, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3273
+ messages.continueShopping
3274
+ ] })
3275
+ ] }) }) });
3276
+ }
3277
+ const checkoutBusy = isPending || cart.isMutating || isPlacingOrder;
3278
+ const shippingReady = hasLoadedShippingMethods && (shippingMethods.length === 0 || Boolean(selectedShippingMethodId));
3279
+ const paymentMethodsUnknown = hasLoadedShippingMethods && !paymentMethodsLoaded;
3280
+ const paymentOffered = paymentMethodsLoaded && paymentMethods.length > 0;
3281
+ const paymentRequired = paymentMethodsUnknown || paymentOffered;
3282
+ const paymentReady = !paymentRequired || authorizedPayment?.status === "authorized" && authorizedPayment.amount.amount === cart.cart.totalPrice.amount && authorizedPayment.amount.currency === cart.cart.totalPrice.currency;
3283
+ const canPlaceOrder = shippingReady && paymentReady;
3284
+ const placeOrderHint = !canPlaceOrder ? !hasLoadedShippingMethods ? messages.completeAddressToContinue : shippingMethods.length > 0 && !selectedShippingMethodId ? messages.selectDeliveryToContinue : paymentRequired && !paymentReady ? messages.selectPaymentToContinue : void 0 : void 0;
3285
+ return /* @__PURE__ */ jsxs7("main", { className: "cat-root cat-checkout", "data-theme": theme, children: [
3286
+ /* @__PURE__ */ jsxs7("header", { className: "cat-checkout__intro", children: [
3287
+ /* @__PURE__ */ jsx7("p", { className: "cat-checkout__eyebrow", children: messages.checkout }),
3288
+ /* @__PURE__ */ jsx7("h1", { children: messages.checkoutTitle }),
3289
+ /* @__PURE__ */ jsxs7("ol", { className: "cat-checkout__steps", children: [
3290
+ /* @__PURE__ */ jsx7(
3291
+ "li",
3292
+ {
3293
+ className: `cat-checkout__step${hasLoadedShippingMethods ? "" : " cat-checkout__step--current"}`,
3294
+ "aria-current": hasLoadedShippingMethods ? void 0 : "step",
3295
+ children: messages.checkoutStepAddress
3296
+ }
3297
+ ),
3298
+ /* @__PURE__ */ jsx7(
3299
+ "li",
3300
+ {
3301
+ className: `cat-checkout__step${hasLoadedShippingMethods ? " cat-checkout__step--current" : ""}`,
3302
+ "aria-current": hasLoadedShippingMethods && !paymentRequired ? "step" : void 0,
3303
+ children: messages.checkoutStepDelivery
3304
+ }
3305
+ ),
3306
+ paymentRequired ? /* @__PURE__ */ jsx7(
3307
+ "li",
3308
+ {
3309
+ className: `cat-checkout__step${hasLoadedShippingMethods ? " cat-checkout__step--current" : ""}`,
3310
+ "aria-current": hasLoadedShippingMethods ? "step" : void 0,
3311
+ children: messages.checkoutStepPayment
3312
+ }
3313
+ ) : null
3314
+ ] })
3315
+ ] }),
3316
+ cart.error ? /* @__PURE__ */ jsx7("div", { className: "cat-status cat-status--error", role: "alert", children: cart.error || messages.checkoutFailed }) : null,
3317
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__layout", children: [
3318
+ /* @__PURE__ */ jsx7(OrderSummary, { cart: cart.cart, messages }),
3319
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__flow", children: [
3320
+ /* @__PURE__ */ jsx7(
3321
+ "form",
3322
+ {
3323
+ className: "cat-checkout__bezel",
3324
+ onSubmit: handleAddressSubmit,
3325
+ children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3326
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__section-heading", children: [
3327
+ /* @__PURE__ */ jsx7(MapPin, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3328
+ /* @__PURE__ */ jsx7("h2", { children: messages.shippingAddress })
3329
+ ] }),
3330
+ /* @__PURE__ */ jsx7(
3331
+ AddressFields,
3332
+ {
3333
+ address: shippingAddress,
3334
+ messages,
3335
+ prefix: "shipping",
3336
+ locale: catalogLocale,
3337
+ onChange: setShippingAddress
3338
+ }
3339
+ ),
3340
+ /* @__PURE__ */ jsxs7("label", { className: "cat-checkout__checkbox", children: [
3341
+ /* @__PURE__ */ jsx7(
3342
+ "input",
3343
+ {
3344
+ type: "checkbox",
3345
+ checked: billingMatchesShipping,
3346
+ onChange: (event) => setBillingMatchesShipping(event.target.checked)
3347
+ }
3348
+ ),
3349
+ /* @__PURE__ */ jsx7("span", { children: messages.billingSameAsShipping })
3350
+ ] }),
3351
+ !billingMatchesShipping ? /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__billing", children: [
3352
+ /* @__PURE__ */ jsx7("h3", { children: messages.billingAddress }),
3353
+ /* @__PURE__ */ jsx7(
3354
+ AddressFields,
3355
+ {
3356
+ address: billingAddress,
3357
+ messages,
3358
+ prefix: "billing",
3359
+ locale: catalogLocale,
3360
+ onChange: setBillingAddress
3361
+ }
3362
+ )
3363
+ ] }) : null,
3364
+ /* @__PURE__ */ jsx7(
3365
+ "button",
3366
+ {
3367
+ className: "cat-checkout__secondary",
3368
+ type: "submit",
3369
+ disabled: checkoutBusy,
3370
+ children: messages.continueToDelivery
3371
+ }
3372
+ )
3373
+ ] })
3374
+ }
3375
+ ),
3376
+ hasLoadedShippingMethods ? /* @__PURE__ */ jsx7("section", { className: "cat-checkout__bezel", children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3377
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__section-heading", children: [
3378
+ /* @__PURE__ */ jsx7(Package4, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3379
+ /* @__PURE__ */ jsx7("h2", { children: messages.shippingMethod })
3380
+ ] }),
3381
+ shippingMethods.length > 0 ? /* @__PURE__ */ jsx7(
3382
+ "div",
3383
+ {
3384
+ className: "cat-checkout__shipping-methods",
3385
+ "aria-label": messages.selectShippingMethod,
3386
+ children: shippingMethods.map((method) => /* @__PURE__ */ jsx7(
3387
+ ShippingMethodCard,
3388
+ {
3389
+ method,
3390
+ selected: selectedShippingMethodId === method.id,
3391
+ disabled: checkoutBusy,
3392
+ onSelect: () => selectShippingMethod(method.id)
3393
+ },
3394
+ method.id
3395
+ ))
3396
+ }
3397
+ ) : /* @__PURE__ */ jsx7("p", { className: "cat-checkout__empty-methods", children: messages.noShippingMethods })
3398
+ ] }) }) : null,
3399
+ shippingReady && paymentRequired ? /* @__PURE__ */ jsx7("section", { className: "cat-checkout__bezel", children: /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__core", children: [
3400
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__section-heading", children: [
3401
+ /* @__PURE__ */ jsx7(CreditCard, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3402
+ /* @__PURE__ */ jsx7("h2", { children: messages.paymentMethod })
3403
+ ] }),
3404
+ /* @__PURE__ */ jsx7(
3405
+ "div",
3406
+ {
3407
+ className: "cat-checkout__shipping-methods",
3408
+ "aria-label": messages.selectPaymentMethod,
3409
+ children: paymentMethods.map((method) => /* @__PURE__ */ jsxs7(
3410
+ "button",
3411
+ {
3412
+ type: "button",
3413
+ className: `cat-checkout__shipping-card${selectedPaymentMethod === method.method ? " cat-checkout__shipping-card--selected" : ""}`,
3414
+ disabled: checkoutBusy,
3415
+ "aria-pressed": selectedPaymentMethod === method.method,
3416
+ onClick: () => selectPaymentMethod(method.method),
3417
+ children: [
3418
+ /* @__PURE__ */ jsxs7("span", { className: "cat-checkout__shipping-copy", children: [
3419
+ /* @__PURE__ */ jsx7("strong", { children: method.name }),
3420
+ method.description ? /* @__PURE__ */ jsx7("small", { children: method.description }) : null
3421
+ ] }),
3422
+ /* @__PURE__ */ jsxs7("span", { className: "cat-checkout__shipping-price", children: [
3423
+ authorizedPayment?.status === "authorized" && authorizedPayment.method === method.method ? messages.paymentAuthorized : "",
3424
+ /* @__PURE__ */ jsx7("span", { className: "cat-checkout__shipping-check", "aria-hidden": "true", children: authorizedPayment?.status === "authorized" && authorizedPayment.method === method.method ? /* @__PURE__ */ jsx7(Check3, { size: 14, strokeWidth: ICON_STROKE }) : null })
3425
+ ] })
3426
+ ]
3427
+ },
3428
+ method.method
3429
+ ))
3430
+ }
3431
+ )
3432
+ ] }) }) : null
3433
+ ] })
3434
+ ] }),
3435
+ /* @__PURE__ */ jsxs7("div", { className: "cat-checkout__place-island", children: [
3436
+ /* @__PURE__ */ jsxs7(
3437
+ "button",
3438
+ {
3439
+ type: "button",
3440
+ className: "cat-checkout-cta",
3441
+ disabled: !canPlaceOrder || checkoutBusy,
3442
+ onClick: placeOrder,
3443
+ "aria-busy": checkoutBusy && canPlaceOrder ? true : void 0,
3444
+ "aria-describedby": placeOrderHint ? "cat-checkout-place-hint" : void 0,
3445
+ children: [
3446
+ /* @__PURE__ */ jsx7("span", { children: isPlacingOrder ? messages.placingOrder : messages.placeOrder }),
3447
+ /* @__PURE__ */ jsx7("span", { className: "cat-checkout-cta__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx7(ArrowRight2, { size: 16, strokeWidth: ICON_STROKE }) })
3448
+ ]
3449
+ }
3450
+ ),
3451
+ placeOrderHint ? /* @__PURE__ */ jsx7("p", { id: "cat-checkout-place-hint", className: "cat-checkout__place-hint", children: placeOrderHint }) : null
3452
+ ] })
3453
+ ] });
3454
+ }
3455
+
3456
+ // src/components/CommerceAIOrderStatus.tsx
3457
+ import { useEffect as useEffect7, useMemo as useMemo5, useState as useState10 } from "react";
3458
+ import { ArrowLeft as ArrowLeft2, Package as Package5, Truck } from "lucide-react";
3459
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3460
+ function formatAddress(address) {
3461
+ return [
3462
+ `${address.firstName} ${address.lastName}`,
3463
+ address.streetName,
3464
+ address.additionalStreetInfo,
3465
+ `${address.postalCode} ${address.city}`,
3466
+ address.country
3467
+ ].filter(Boolean).join(", ");
3468
+ }
3469
+ function formatPlacedAt(iso) {
3470
+ const date = new Date(iso);
3471
+ if (Number.isNaN(date.getTime())) {
3472
+ return iso;
3473
+ }
3474
+ const utc = date.toISOString();
3475
+ return `${utc.slice(0, 10)} ${utc.slice(11, 16)} UTC`;
3476
+ }
3477
+ function orderHref(orderStatusHref, orderNumber) {
3478
+ const separator = orderStatusHref.includes("?") ? "&" : "?";
3479
+ return `${orderStatusHref}${separator}orderNumber=${encodeURIComponent(orderNumber)}`;
3480
+ }
3481
+ function StatusBadge({ label, value }) {
3482
+ if (!value) {
3483
+ return null;
3484
+ }
3485
+ return /* @__PURE__ */ jsxs8("span", { className: "cat-order-status__badge", children: [
3486
+ /* @__PURE__ */ jsx8("small", { children: label }),
3487
+ value
3488
+ ] });
3489
+ }
3490
+ function CommerceAIOrderStatus({
3491
+ apiBaseUrl,
3492
+ orderNumber = "",
3493
+ theme = "auto",
3494
+ catalogLocale,
3495
+ currency,
3496
+ country,
3497
+ messages: messageOverrides,
3498
+ continueShoppingHref = "/",
3499
+ orderStatusHref = "/orders"
3500
+ }) {
3501
+ const messages = useMemo5(
3502
+ () => resolveCommerceAISearchMessages(messageOverrides),
3503
+ [messageOverrides]
3504
+ );
3505
+ const cart = useCart({ apiBaseUrl, catalogLocale, currency, country });
3506
+ const trimmedNumber = orderNumber.trim();
3507
+ const [order, setOrder] = useState10(null);
3508
+ const [orders, setOrders] = useState10(null);
3509
+ const [loaded, setLoaded] = useState10(false);
3510
+ useEffect7(() => {
3511
+ if (!cart.anonymousId) {
3512
+ return;
3513
+ }
3514
+ let cancelled = false;
3515
+ setLoaded(false);
3516
+ setOrder(null);
3517
+ setOrders(null);
3518
+ const load = trimmedNumber ? cart.getOrder(trimmedNumber).then((next) => {
3519
+ if (!cancelled) {
3520
+ setOrder(next);
3521
+ setLoaded(true);
3522
+ }
3523
+ }) : cart.listOrders().then((next) => {
3524
+ if (!cancelled) {
3525
+ setOrders(next);
3526
+ setLoaded(true);
3527
+ }
3528
+ });
3529
+ void load;
3530
+ return () => {
3531
+ cancelled = true;
3532
+ };
3533
+ }, [cart.anonymousId, cart.getOrder, cart.listOrders, trimmedNumber]);
3534
+ if (!loaded || cart.isLoading) {
3535
+ return /* @__PURE__ */ jsx8("main", { className: "cat-root cat-checkout", "data-theme": theme, children: /* @__PURE__ */ jsx8("section", { className: "cat-checkout__empty cat-checkout__bezel", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3536
+ /* @__PURE__ */ jsx8(Package5, { size: 24, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3537
+ /* @__PURE__ */ jsx8("h1", { children: messages.searching })
3538
+ ] }) }) });
3539
+ }
3540
+ if (!trimmedNumber) {
3541
+ if (!orders?.length) {
3542
+ return /* @__PURE__ */ jsx8("main", { className: "cat-root cat-checkout", "data-theme": theme, children: /* @__PURE__ */ jsx8("section", { className: "cat-checkout__empty cat-checkout__bezel", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3543
+ /* @__PURE__ */ jsx8(Package5, { size: 24, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3544
+ /* @__PURE__ */ jsx8("h1", { children: messages.noOrdersYet }),
3545
+ /* @__PURE__ */ jsxs8("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3546
+ /* @__PURE__ */ jsx8(ArrowLeft2, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3547
+ messages.continueShopping
3548
+ ] })
3549
+ ] }) }) });
3550
+ }
3551
+ return /* @__PURE__ */ jsxs8("main", { className: "cat-root cat-checkout", "data-theme": theme, children: [
3552
+ /* @__PURE__ */ jsxs8("header", { className: "cat-checkout__intro", children: [
3553
+ /* @__PURE__ */ jsx8("p", { className: "cat-checkout__eyebrow", children: messages.orderStatus }),
3554
+ /* @__PURE__ */ jsx8("h1", { children: messages.yourOrders })
3555
+ ] }),
3556
+ cart.error ? /* @__PURE__ */ jsx8("div", { className: "cat-status cat-status--error", role: "alert", children: cart.error }) : null,
3557
+ /* @__PURE__ */ jsx8("section", { className: "cat-checkout__bezel cat-checkout__summary", children: /* @__PURE__ */ jsx8("div", { className: "cat-checkout__core", children: /* @__PURE__ */ jsx8("ul", { className: "cat-order-status__list", children: orders.map((item) => {
3558
+ const number = item.orderNumber ?? item.id;
3559
+ return /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsxs8("a", { className: "cat-order-status__list-link", href: orderHref(orderStatusHref, number), children: [
3560
+ /* @__PURE__ */ jsxs8("span", { className: "cat-order-status__list-copy", children: [
3561
+ /* @__PURE__ */ jsx8("strong", { children: number }),
3562
+ item.createdAt ? /* @__PURE__ */ jsx8("small", { children: formatPlacedAt(item.createdAt) }) : null
3563
+ ] }),
3564
+ /* @__PURE__ */ jsxs8("span", { className: "cat-order-status__list-meta", children: [
3565
+ item.orderState ? /* @__PURE__ */ jsx8("small", { children: item.orderState }) : null,
3566
+ /* @__PURE__ */ jsx8("strong", { children: item.totalPrice.formatted })
3567
+ ] })
3568
+ ] }) }, item.id);
3569
+ }) }) }) }),
3570
+ /* @__PURE__ */ jsxs8("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3571
+ /* @__PURE__ */ jsx8(ArrowLeft2, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3572
+ messages.continueShopping
3573
+ ] })
3574
+ ] });
3575
+ }
3576
+ if (!order) {
3577
+ return /* @__PURE__ */ jsx8("main", { className: "cat-root cat-checkout", "data-theme": theme, children: /* @__PURE__ */ jsx8("section", { className: "cat-checkout__empty cat-checkout__bezel", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3578
+ /* @__PURE__ */ jsx8(Package5, { size: 24, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3579
+ /* @__PURE__ */ jsx8("h1", { children: messages.orderNotFound }),
3580
+ /* @__PURE__ */ jsxs8("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3581
+ /* @__PURE__ */ jsx8(ArrowLeft2, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3582
+ messages.continueShopping
3583
+ ] })
3584
+ ] }) }) });
3585
+ }
3586
+ return /* @__PURE__ */ jsxs8("main", { className: "cat-root cat-checkout", "data-theme": theme, children: [
3587
+ /* @__PURE__ */ jsxs8("header", { className: "cat-checkout__intro", children: [
3588
+ /* @__PURE__ */ jsx8("p", { className: "cat-checkout__eyebrow", children: messages.orderStatus }),
3589
+ /* @__PURE__ */ jsx8("h1", { children: order.orderNumber ?? order.id }),
3590
+ /* @__PURE__ */ jsxs8("div", { className: "cat-order-status__badges", children: [
3591
+ /* @__PURE__ */ jsx8(StatusBadge, { label: messages.orderStatus, value: order.orderState }),
3592
+ /* @__PURE__ */ jsx8(StatusBadge, { label: messages.paymentStateLabel, value: order.paymentState }),
3593
+ /* @__PURE__ */ jsx8(StatusBadge, { label: messages.shipmentStateLabel, value: order.shipmentState })
3594
+ ] }),
3595
+ order.createdAt ? /* @__PURE__ */ jsxs8("p", { className: "cat-order-status__placed", children: [
3596
+ messages.orderPlacedAt,
3597
+ " ",
3598
+ formatPlacedAt(order.createdAt)
3599
+ ] }) : null
3600
+ ] }),
3601
+ cart.error ? /* @__PURE__ */ jsx8("div", { className: "cat-status cat-status--error", role: "alert", children: cart.error }) : null,
3602
+ /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__layout", children: [
3603
+ /* @__PURE__ */ jsx8("section", { className: "cat-checkout__bezel cat-checkout__summary", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3604
+ /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__section-heading", children: [
3605
+ /* @__PURE__ */ jsx8(Package5, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3606
+ /* @__PURE__ */ jsx8("h2", { children: messages.orderSummary })
3607
+ ] }),
3608
+ /* @__PURE__ */ jsx8("ul", { className: "cat-checkout__items", children: order.lineItems.map((item) => /* @__PURE__ */ jsxs8("li", { className: "cat-checkout__item", children: [
3609
+ /* @__PURE__ */ jsxs8("span", { className: "cat-checkout__item-copy", children: [
3610
+ item.imageUrl ? /* @__PURE__ */ jsx8("img", { src: item.imageUrl, alt: "", className: "cat-checkout__item-image" }) : /* @__PURE__ */ jsx8("div", { className: "cat-checkout__item-image cat-checkout__item-image--placeholder", children: /* @__PURE__ */ jsx8(Package5, { size: 16, strokeWidth: ICON_STROKE, "aria-hidden": "true" }) }),
3611
+ /* @__PURE__ */ jsxs8("span", { children: [
3612
+ item.name,
3613
+ /* @__PURE__ */ jsxs8("small", { children: [
3614
+ "\xD7 ",
3615
+ item.quantity
3616
+ ] })
3617
+ ] })
3618
+ ] }),
3619
+ /* @__PURE__ */ jsx8("strong", { children: item.totalPrice?.formatted ?? item.price?.formatted ?? "\u2014" })
3620
+ ] }, item.id)) }),
3621
+ /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__summary-total", children: [
3622
+ /* @__PURE__ */ jsx8("span", { children: messages.total }),
3623
+ /* @__PURE__ */ jsx8("strong", { children: order.totalPrice.formatted })
3624
+ ] })
3625
+ ] }) }),
3626
+ /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__flow", children: [
3627
+ order.shippingAddress ? /* @__PURE__ */ jsx8("section", { className: "cat-checkout__bezel", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3628
+ /* @__PURE__ */ jsx8("h2", { children: messages.shippingAddress }),
3629
+ /* @__PURE__ */ jsx8("p", { className: "cat-order-status__copy", children: formatAddress(order.shippingAddress) }),
3630
+ order.shippingMethod ? /* @__PURE__ */ jsx8("p", { className: "cat-order-status__copy", children: order.shippingMethod.name }) : null
3631
+ ] }) }) : null,
3632
+ /* @__PURE__ */ jsx8("section", { className: "cat-checkout__bezel", children: /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__core", children: [
3633
+ /* @__PURE__ */ jsxs8("div", { className: "cat-checkout__section-heading", children: [
3634
+ /* @__PURE__ */ jsx8(Truck, { size: 18, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3635
+ /* @__PURE__ */ jsx8("h2", { children: messages.shipmentStateLabel })
3636
+ ] }),
3637
+ order.deliveries?.length ? /* @__PURE__ */ jsx8("ul", { className: "cat-order-status__tracking", children: order.deliveries.map((delivery) => /* @__PURE__ */ jsxs8("li", { children: [
3638
+ /* @__PURE__ */ jsx8("strong", { children: messages.trackingNumber }),
3639
+ /* @__PURE__ */ jsx8("span", { children: delivery.trackingId ?? "\u2014" }),
3640
+ delivery.carrier ? /* @__PURE__ */ jsxs8("small", { children: [
3641
+ messages.carrier,
3642
+ ": ",
3643
+ delivery.carrier
3644
+ ] }) : null
3645
+ ] }, delivery.id)) }) : /* @__PURE__ */ jsx8("p", { className: "cat-checkout__empty-methods", children: messages.noTrackingYet })
3646
+ ] }) })
3647
+ ] })
3648
+ ] }),
3649
+ /* @__PURE__ */ jsxs8("a", { className: "cat-checkout__back", href: continueShoppingHref, children: [
3650
+ /* @__PURE__ */ jsx8(ArrowLeft2, { size: 15, strokeWidth: ICON_STROKE, "aria-hidden": "true" }),
3651
+ messages.continueShopping
3652
+ ] })
3653
+ ] });
3654
+ }
3655
+
3656
+ // src/hooks/useTheme.ts
3657
+ import { useEffect as useEffect8, useState as useState11 } from "react";
3658
+ function useTheme(theme = "auto") {
3659
+ const [resolved, setResolved] = useState11("light");
3660
+ useEffect8(() => {
3661
+ if (theme === "light" || theme === "dark") {
3662
+ setResolved(theme);
3663
+ return;
3664
+ }
3665
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
3666
+ const update = () => setResolved(media.matches ? "dark" : "light");
3667
+ update();
3668
+ media.addEventListener("change", update);
3669
+ return () => media.removeEventListener("change", update);
3670
+ }, [theme]);
3671
+ return theme === "auto" ? "auto" : resolved;
3672
+ }
3673
+ function useResolvedTheme(theme = "auto") {
3674
+ const [resolved, setResolved] = useState11("light");
3675
+ useEffect8(() => {
3676
+ if (theme !== "auto") {
3677
+ setResolved(theme);
3678
+ return;
3679
+ }
3680
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
3681
+ const update = () => setResolved(media.matches ? "dark" : "light");
3682
+ update();
3683
+ media.addEventListener("change", update);
3684
+ return () => media.removeEventListener("change", update);
3685
+ }, [theme]);
3686
+ return theme === "auto" ? "auto" : resolved;
3687
+ }
3688
+ export {
3689
+ CART_SESSION_HEADER,
3690
+ CommerceAICheckout,
3691
+ CommerceAIOrderStatus,
3692
+ CommerceAISearch,
3693
+ DEFAULT_COMMERCE_AI_SEARCH_MESSAGES,
3694
+ MissionResults,
3695
+ resolveCommerceAISearchMessages,
3696
+ useCart,
3697
+ useCommerceAISearch,
3698
+ useResolvedTheme,
3699
+ useTheme,
3700
+ useVoiceSearch
3701
+ };
3702
+ //# sourceMappingURL=index.js.map