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