@cimplify/sdk 0.100.0 → 0.100.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +22 -22
  4. package/dist/advanced.mjs +3 -3
  5. package/dist/{chunk-X6G6QERR.js → chunk-6JEKKL3M.js} +53 -38
  6. package/dist/{chunk-7TEMLVDT.mjs → chunk-B2YOCOLH.mjs} +2 -2
  7. package/dist/{chunk-V5TU5JKA.mjs → chunk-DLW3RWRK.mjs} +30 -6
  8. package/dist/{chunk-FECGCIVV.js → chunk-IFAQLFAJ.js} +4 -4
  9. package/dist/{chunk-J3UYIBKH.js → chunk-IJI7KMTG.js} +89 -82
  10. package/dist/{chunk-AETVRBKI.js → chunk-NIOUSKQQ.js} +1 -0
  11. package/dist/{chunk-K5EN2W2Q.mjs → chunk-O3YUHLZL.mjs} +1 -0
  12. package/dist/{chunk-GWKIDFN6.mjs → chunk-PJUXAUM2.mjs} +20 -14
  13. package/dist/{chunk-ZA5VKVUB.js → chunk-QRBBSIAY.js} +4 -4
  14. package/dist/{chunk-OX5L6XE7.mjs → chunk-TIQLOH2B.mjs} +30 -15
  15. package/dist/{chunk-64EDCDBR.js → chunk-WMEC5JWN.js} +32 -7
  16. package/dist/{chunk-I7L4VRHI.mjs → chunk-YWNJJO2U.mjs} +2 -2
  17. package/dist/{client-DQpvwf-p.d.mts → client-C6wb92AD.d.mts} +2 -2
  18. package/dist/{client-DmR3Z6pQ.d.ts → client-CgXkRu2o.d.ts} +2 -2
  19. package/dist/{client-C1vS5pf0.d.ts → client-D8XcrONA.d.mts} +38 -26
  20. package/dist/{client-B5MeD8da.d.mts → client-LPg_AUUr.d.ts} +38 -26
  21. package/dist/{index-Br-vFygC.d.ts → index-B_RYmvlF.d.ts} +3 -3
  22. package/dist/{index-DUOCn1Cs.d.mts → index-D-LUYk1Y.d.mts} +3 -3
  23. package/dist/index.d.mts +7 -7
  24. package/dist/index.d.ts +7 -7
  25. package/dist/index.js +117 -117
  26. package/dist/index.mjs +5 -5
  27. package/dist/mock/cli.mjs +1 -1
  28. package/dist/mock/library.js +1 -1
  29. package/dist/mock/library.mjs +1 -1
  30. package/dist/mock/msw.js +1 -1
  31. package/dist/mock/msw.mjs +1 -1
  32. package/dist/{payment-BIpS9oPG.d.mts → payment-BSzy7sly.d.mts} +1 -1
  33. package/dist/{payment-CsuBKHqg.d.ts → payment-jK8ztICx.d.ts} +1 -1
  34. package/dist/{price-Ml_uPoiY.d.ts → price-CJLfcYKd.d.ts} +1 -1
  35. package/dist/{price-IOOoVYS-.d.mts → price-D96h6c6L.d.mts} +1 -1
  36. package/dist/{product-BKV6TBj_.d.ts → product-CeV8euw4.d.mts} +23 -11
  37. package/dist/{product-BKV6TBj_.d.mts → product-CeV8euw4.d.ts} +23 -11
  38. package/dist/react.d.mts +10 -5
  39. package/dist/react.d.ts +10 -5
  40. package/dist/react.js +856 -450
  41. package/dist/react.mjs +737 -331
  42. package/dist/{server-C0iEk55_.d.mts → server--RyVAfPN.d.mts} +1 -1
  43. package/dist/{server-CfpYhSV_.d.ts → server-CNyy47HL.d.ts} +1 -1
  44. package/dist/server.d.mts +4 -4
  45. package/dist/server.d.ts +4 -4
  46. package/dist/server.js +10 -10
  47. package/dist/server.mjs +4 -4
  48. package/dist/testing/msw.d.mts +2 -2
  49. package/dist/testing/msw.d.ts +2 -2
  50. package/dist/testing/msw.js +3 -3
  51. package/dist/testing/msw.mjs +2 -2
  52. package/dist/testing/suite.d.mts +6 -6
  53. package/dist/testing/suite.d.ts +6 -6
  54. package/dist/testing/suite.js +25 -25
  55. package/dist/testing/suite.mjs +6 -6
  56. package/dist/testing.d.mts +5 -5
  57. package/dist/testing.d.ts +5 -5
  58. package/dist/testing.js +81 -81
  59. package/dist/testing.mjs +7 -7
  60. package/dist/utils.d.mts +4 -4
  61. package/dist/utils.d.ts +4 -4
  62. package/dist/utils.js +29 -29
  63. package/dist/utils.mjs +2 -2
  64. package/package.json +1 -1
  65. package/registry/cart-summary.json +1 -1
  66. package/registry/product-customizer.json +1 -1
  67. package/registry/sale-badge.json +1 -1
package/dist/react.mjs CHANGED
@@ -3,10 +3,10 @@ import { DURATION_UNIT, getVariantDisplayName, INPUT_FIELD_TYPE, isMultiDayServi
3
3
  export { getVariantDisplayName } from './chunk-4G5UWFC3.mjs';
4
4
  import { splitGenUIContent, messagePreview } from './chunk-U4MW6PYU.mjs';
5
5
  export { messagePreview, splitGenUIContent } from './chunk-U4MW6PYU.mjs';
6
- import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, MUTATION_ERROR } from './chunk-GWKIDFN6.mjs';
7
- import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-OX5L6XE7.mjs';
8
- import { parsePrice, formatMoney, formatPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-V5TU5JKA.mjs';
9
- import { isSupportedCurrency, CimplifyError } from './chunk-K5EN2W2Q.mjs';
6
+ import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, AUTH_TOKEN_CHANGED, MUTATION_ERROR } from './chunk-PJUXAUM2.mjs';
7
+ import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-TIQLOH2B.mjs';
8
+ import { parsePrice, formatMoney, formatPrice, getVariantUnitPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-DLW3RWRK.mjs';
9
+ import { isSupportedCurrency, currencyCode, CimplifyError } from './chunk-O3YUHLZL.mjs';
10
10
  import './chunk-3G6RQLXK.mjs';
11
11
  import React14, { createContext, lazy, memo, Suspense, useMemo, useContext, useRef, useCallback, useState, useEffect, useId, useSyncExternalStore } from 'react';
12
12
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -355,6 +355,26 @@ function Ad({
355
355
  }
356
356
 
357
357
  // src/utils/cart-transform.ts
358
+ var DECIMAL_AMOUNT_PATTERN = /^([+-]?)(\d+)(?:\.(\d+))?$/;
359
+ function multiplyDecimalByInteger(value, multiplier) {
360
+ if (!Number.isSafeInteger(multiplier) || multiplier < 0) {
361
+ throw new RangeError("Cart item quantity must be a non-negative safe integer");
362
+ }
363
+ const source = String(value).trim();
364
+ const match = DECIMAL_AMOUNT_PATTERN.exec(source);
365
+ if (!match) {
366
+ throw new TypeError("Cart item price must be a decimal amount");
367
+ }
368
+ const [, sign, whole, fraction = ""] = match;
369
+ const scaledAmount = BigInt(`${sign}${whole}${fraction}`);
370
+ const scaledTotal = scaledAmount * BigInt(multiplier);
371
+ if (fraction.length === 0) return scaledTotal.toString();
372
+ const negative = scaledTotal < 0n;
373
+ const digits = (negative ? -scaledTotal : scaledTotal).toString().padStart(fraction.length + 1, "0");
374
+ const splitAt = digits.length - fraction.length;
375
+ const formatted = `${digits.slice(0, splitAt)}.${digits.slice(splitAt)}`;
376
+ return negative ? `-${formatted}` : formatted;
377
+ }
358
378
  function getSelections(item) {
359
379
  if (item.bundle_resolved?.selections?.length) {
360
380
  return item.bundle_resolved.selections.map((s) => ({
@@ -373,11 +393,12 @@ function getSelections(item) {
373
393
  }
374
394
  function mapItem(item, fx) {
375
395
  const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
396
+ const currentUnitPrice = item.total_price;
376
397
  const result = {
377
398
  name: item.name,
378
399
  quantity: item.quantity,
379
- unit_price: amt(item.base_price),
380
- total_price: amt(item.total_price),
400
+ unit_price: amt(currentUnitPrice),
401
+ total_price: amt(multiplyDecimalByInteger(currentUnitPrice, item.quantity)),
381
402
  line_type: item.line_type
382
403
  };
383
404
  if (item.image_url) result.image_url = item.image_url;
@@ -398,9 +419,9 @@ function mapItem(item, fx) {
398
419
  }
399
420
  return result;
400
421
  }
401
- function transformToCheckoutCart(cart, fx) {
422
+ function transformToCheckoutCart(cart, fx, checkout) {
402
423
  const amt = (value) => fx ? String(fx.convertPrice(value)) : String(value);
403
- return {
424
+ const checkoutCart = {
404
425
  items: cart.items.map((item) => mapItem(item, fx)),
405
426
  subtotal: amt(cart.pricing.subtotal),
406
427
  tax_amount: amt(cart.pricing.tax_amount),
@@ -413,6 +434,7 @@ function transformToCheckoutCart(cart, fx) {
413
434
  deposit_amount: cart.pricing.deposit_amount != null ? amt(cart.pricing.deposit_amount) : void 0,
414
435
  requires_delivery: cart.requires_delivery
415
436
  };
437
+ return checkoutCart;
416
438
  }
417
439
 
418
440
  // src/internal/collection.ts
@@ -574,10 +596,20 @@ function lineKeyFromItem(item) {
574
596
  staffId: item.staffId
575
597
  });
576
598
  }
577
- function attachInternalItemMetadata(item) {
578
- return { ...item, _lineKey: lineKeyFromItem(item) };
599
+ function attachInternalItemMetadata(item, metadata = {}) {
600
+ const internalItem = {
601
+ ...item,
602
+ _lineKey: lineKeyFromItem(item)
603
+ };
604
+ if (metadata.quotedUnitPrice !== void 0) {
605
+ internalItem._quotedUnitPrice = metadata.quotedUnitPrice;
606
+ }
607
+ return internalItem;
579
608
  }
580
609
  function calculateLineSubtotal(item) {
610
+ if (item._quotedUnitPrice !== void 0) {
611
+ return roundMoney(parsePrice(item._quotedUnitPrice) * item.quantity);
612
+ }
581
613
  let unitPrice = parsePrice(item.product.default_price);
582
614
  if (item.variant?.price_adjustment) {
583
615
  unitPrice += parsePrice(item.variant.price_adjustment);
@@ -653,10 +685,7 @@ function emptyBase(currency, isLoading = true) {
653
685
  return { items: [], subtotal: 0, tax: 0, total: 0, currency, isLoading };
654
686
  }
655
687
  function buildStoreKey(client, locationId) {
656
- return [
657
- client.getPublicKey() || "__no_public_key__",
658
- locationId || "__no_location__"
659
- ].join(":");
688
+ return [client.getPublicKey() || "__no_public_key__", locationId || "__no_location__"].join(":");
660
689
  }
661
690
  function purgeLegacyCartCachesOnce() {
662
691
  if (legacyCachesPurged) return;
@@ -685,7 +714,10 @@ function toProductFromServerItem(item, businessId) {
685
714
  slug: item.item_id,
686
715
  description: item.description,
687
716
  image_url: item.image_url,
688
- default_price: item.base_price,
717
+ // Cart price_info is the accepted item price path. Do not reconstruct it
718
+ // from the catalogue base after a sale or shopper-specific quote.
719
+ default_price: item.price_info.final_price,
720
+ currency: item.price_info.currency,
689
721
  type: "product",
690
722
  inventory_type: "none",
691
723
  variant_strategy: "fetch_all",
@@ -700,37 +732,44 @@ function mapServerCart(serverCart, fallbackCurrency) {
700
732
  const variant = item.variant_info || item.variant_name ? {
701
733
  id: item.variant_info?.id || item.variant_id || "",
702
734
  name: item.variant_info?.name || item.variant_name || "Variant",
703
- price_adjustment: item.variant_info?.price_adjustment
735
+ price_adjustment: item.variant_info?.price_adjustment,
736
+ price_info: item.price_info
704
737
  } : void 0;
705
738
  const quoteId = typeof item.metadata?.quote_id === "string" ? item.metadata.quote_id : void 0;
706
- return attachInternalItemMetadata({
707
- id: item.id,
708
- product: toProductFromServerItem(item, serverCart.business_id),
709
- quantity: clampQuantity(item.quantity),
710
- locationId: serverCart.location_id,
711
- variantId: item.variant_id,
712
- variant,
713
- quoteId,
714
- addOnOptionIds: normalizeOptionIds(item.add_on_option_ids || void 0),
715
- addOnOptions: (item.add_on_options || []).map((option) => ({
716
- id: option.id,
717
- name: option.name,
718
- default_price: option.price
719
- })),
720
- bundleSelections: item.bundle_selections || void 0,
721
- compositeSelections: item.composite_selections || void 0,
722
- specialInstructions: item.special_instructions,
723
- scheduledStart: item.scheduled_start || void 0,
724
- scheduledEnd: item.scheduled_end || void 0,
725
- staffId: item.staff_id || void 0,
726
- units: item.units || void 0,
727
- lineType: item.line_type,
728
- confirmationCode: item.confirmation_code || void 0,
729
- serviceStatus: item.service_status || void 0,
730
- bundleResolved: item.bundle_resolved || void 0,
731
- compositeResolved: item.composite_resolved || void 0,
732
- customerInputs: item.customer_inputs || void 0
733
- });
739
+ return attachInternalItemMetadata(
740
+ {
741
+ id: item.id,
742
+ product: toProductFromServerItem(item, serverCart.business_id),
743
+ quantity: clampQuantity(item.quantity),
744
+ locationId: serverCart.location_id,
745
+ variantId: item.variant_id,
746
+ variant,
747
+ quoteId,
748
+ addOnOptionIds: normalizeOptionIds(item.add_on_option_ids || void 0),
749
+ addOnOptions: (item.add_on_options || []).map((option) => ({
750
+ id: option.id,
751
+ name: option.name,
752
+ default_price: option.price
753
+ })),
754
+ bundleSelections: item.bundle_selections || void 0,
755
+ compositeSelections: item.composite_selections || void 0,
756
+ specialInstructions: item.special_instructions,
757
+ scheduledStart: item.scheduled_start || void 0,
758
+ scheduledEnd: item.scheduled_end || void 0,
759
+ staffId: item.staff_id || void 0,
760
+ units: item.units || void 0,
761
+ lineType: item.line_type,
762
+ confirmationCode: item.confirmation_code || void 0,
763
+ serviceStatus: item.service_status || void 0,
764
+ bundleResolved: item.bundle_resolved || void 0,
765
+ compositeResolved: item.composite_resolved || void 0,
766
+ customerInputs: item.customer_inputs || void 0
767
+ },
768
+ // CartItemDetails.total_price is the accepted configured unit amount,
769
+ // including add-ons. It is the only safe basis for optimistic quantity
770
+ // changes and line rendering after the server replaces the temp item.
771
+ { quotedUnitPrice: item.total_price }
772
+ );
734
773
  });
735
774
  return {
736
775
  items,
@@ -871,24 +910,27 @@ async function performAddItem(entry, product, quantity, options) {
871
910
  addOnOptionIds: normalizeOptionIds(options.addOnOptionIds)
872
911
  };
873
912
  const lineKey = buildLineKey(product.id, normalizedOptions);
874
- const tempItem = attachInternalItemMetadata({
875
- id: `tmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
876
- product,
877
- quantity: resolvedQuantity,
878
- locationId: normalizedOptions.locationId,
879
- variantId: normalizedOptions.variantId,
880
- variant: normalizedOptions.variant,
881
- quoteId: normalizedOptions.quoteId,
882
- addOnOptionIds: normalizedOptions.addOnOptionIds,
883
- addOnOptions: normalizedOptions.addOnOptions,
884
- bundleSelections: normalizedOptions.bundleSelections,
885
- compositeSelections: normalizedOptions.compositeSelections,
886
- specialInstructions: normalizedOptions.specialInstructions,
887
- scheduledStart: normalizedOptions.scheduledStart,
888
- scheduledEnd: normalizedOptions.scheduledEnd,
889
- staffId: normalizedOptions.staffId,
890
- units: normalizedOptions.units
891
- });
913
+ const tempItem = attachInternalItemMetadata(
914
+ {
915
+ id: `tmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
916
+ product,
917
+ quantity: resolvedQuantity,
918
+ locationId: normalizedOptions.locationId,
919
+ variantId: normalizedOptions.variantId,
920
+ variant: normalizedOptions.variant,
921
+ quoteId: normalizedOptions.quoteId,
922
+ addOnOptionIds: normalizedOptions.addOnOptionIds,
923
+ addOnOptions: normalizedOptions.addOnOptions,
924
+ bundleSelections: normalizedOptions.bundleSelections,
925
+ compositeSelections: normalizedOptions.compositeSelections,
926
+ specialInstructions: normalizedOptions.specialInstructions,
927
+ scheduledStart: normalizedOptions.scheduledStart,
928
+ scheduledEnd: normalizedOptions.scheduledEnd,
929
+ staffId: normalizedOptions.staffId,
930
+ units: normalizedOptions.units
931
+ },
932
+ { quotedUnitPrice: normalizedOptions.quotedUnitPrice }
933
+ );
892
934
  const idempotencyKey = `idem_${typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`}`;
893
935
  const patch = { kind: "add_item", tempItem, lineKey };
894
936
  const useOptimistic = entry.optimisticEnabled;
@@ -998,12 +1040,12 @@ async function performReorder(entry, orderId) {
998
1040
  return result.value;
999
1041
  }
1000
1042
  function useCart(options = {}) {
1001
- const context = useOptionalCimplifyClient();
1043
+ const context = useOptionalCimplify();
1002
1044
  const client = options.client ?? context?.client;
1003
1045
  if (!client) {
1004
1046
  throw new Error("useCart must be used within CimplifyProvider or passed { client }.");
1005
1047
  }
1006
- const locationId = options.locationId ?? client.getLocationId();
1048
+ const locationId = options.locationId ?? (context?.client === client ? context.currentLocation?.id ?? (context.isReady ? null : client.getLocationId()) : client.getLocationId());
1007
1049
  const currency = options.currency ?? context?.currency ?? "USD";
1008
1050
  const optimisticEnabled = context?.optimistic ?? true;
1009
1051
  const entry = useMemo(
@@ -1016,6 +1058,11 @@ function useCart(options = {}) {
1016
1058
  [client, currency, locationId, optimisticEnabled]
1017
1059
  );
1018
1060
  const view = useCollectionStore(entry.collection);
1061
+ useEffect(() => {
1062
+ if (client.getLocationId() !== locationId) {
1063
+ client.setLocationId(locationId);
1064
+ }
1065
+ }, [client, locationId]);
1019
1066
  useEffect(() => {
1020
1067
  void ensureInitialized(entry);
1021
1068
  }, [entry]);
@@ -4875,19 +4922,53 @@ function useSearch(options = {}) {
4875
4922
  }
4876
4923
  var quoteCache = /* @__PURE__ */ new Map();
4877
4924
  var quoteInflight = /* @__PURE__ */ new Map();
4878
- function buildQuoteCacheKey(client, locationId, inputSignature) {
4879
- return [client.getPublicKey(), locationId ?? "", inputSignature].join("|");
4925
+ var quoteClientNamespaces = /* @__PURE__ */ new WeakMap();
4926
+ var nextQuoteClientNamespace = 0;
4927
+ var QUOTE_LOAD_DEBOUNCE_MS = 150;
4928
+ var MIN_AUTO_REFRESH_DELAY_MS = 1e3;
4929
+ function getQuoteClientNamespace(client) {
4930
+ const existing = quoteClientNamespaces.get(client);
4931
+ if (existing !== void 0) {
4932
+ return existing;
4933
+ }
4934
+ const namespace = nextQuoteClientNamespace++;
4935
+ quoteClientNamespaces.set(client, namespace);
4936
+ return namespace;
4937
+ }
4938
+ function buildQuoteCacheKey(client, sessionEpoch, locationId, inputSignature) {
4939
+ return JSON.stringify([
4940
+ getQuoteClientNamespace(client),
4941
+ client.getPublicKey(),
4942
+ sessionEpoch,
4943
+ locationId ?? "",
4944
+ inputSignature
4945
+ ]);
4946
+ }
4947
+ function useClientSessionEpoch(client) {
4948
+ const subscribe = useCallback(
4949
+ (notify) => {
4950
+ const unsubscribe = [
4951
+ client.events.on(AUTH_TOKEN_CHANGED, notify),
4952
+ client.events.on(SESSION_CLAIMED, notify),
4953
+ client.events.on(SESSION_CLEARED, notify)
4954
+ ];
4955
+ return () => {
4956
+ for (const stopListening of unsubscribe) {
4957
+ stopListening();
4958
+ }
4959
+ };
4960
+ },
4961
+ [client]
4962
+ );
4963
+ const getSnapshot = useCallback(() => client.getSessionEpoch(), [client]);
4964
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
4880
4965
  }
4881
4966
  function serializeQuoteBundleSelections(selections) {
4882
4967
  if (!selections || selections.length === 0) {
4883
4968
  return "";
4884
4969
  }
4885
4970
  return selections.map((selection) => {
4886
- return [
4887
- selection.component_id,
4888
- selection.quantity,
4889
- selection.variant_id ?? ""
4890
- ].join(":");
4971
+ return [selection.component_id, selection.quantity, selection.variant_id ?? ""].join(":");
4891
4972
  }).join(";");
4892
4973
  }
4893
4974
  function serializeQuoteCompositeSelections(selections) {
@@ -4917,15 +4998,37 @@ function buildQuoteInputSignature(input) {
4917
4998
  serializeQuoteCompositeSelections(input.compositeSelections)
4918
4999
  ].join("|");
4919
5000
  }
4920
- function isQuoteExpired(quote) {
5001
+ function quoteExpiryTimestamp(quote) {
4921
5002
  if (!quote?.expires_at) {
4922
- return false;
5003
+ return Number.NaN;
4923
5004
  }
4924
5005
  const expiresAt = Date.parse(quote.expires_at);
4925
- if (!Number.isFinite(expiresAt)) {
4926
- return false;
5006
+ return Number.isFinite(expiresAt) ? expiresAt : Number.NaN;
5007
+ }
5008
+ function isQuoteExpired(quote, now = Date.now()) {
5009
+ const expiresAt = quoteExpiryTimestamp(quote);
5010
+ return Number.isFinite(expiresAt) && expiresAt <= now;
5011
+ }
5012
+ function readUsableCachedQuote(cacheKey2) {
5013
+ const entry = quoteCache.get(cacheKey2);
5014
+ if (!entry) {
5015
+ return null;
4927
5016
  }
4928
- return expiresAt <= Date.now();
5017
+ if (isQuoteExpired(entry.quote)) {
5018
+ quoteCache.delete(cacheKey2);
5019
+ return null;
5020
+ }
5021
+ return entry.quote;
5022
+ }
5023
+ function requestState(cacheKey2, quote, isLoading) {
5024
+ return {
5025
+ cacheKey: cacheKey2,
5026
+ quote,
5027
+ isLoading,
5028
+ error: null,
5029
+ isExpired: false,
5030
+ messages: quote?.ui_messages ?? []
5031
+ };
4929
5032
  }
4930
5033
  function normalizeInput(input, fallbackLocationId) {
4931
5034
  const productId = input.productId.trim();
@@ -4942,7 +5045,7 @@ function normalizeInput(input, fallbackLocationId) {
4942
5045
  };
4943
5046
  }
4944
5047
  function useQuote(input, options = {}) {
4945
- const context = useOptionalCimplifyClient();
5048
+ const context = useOptionalCimplify();
4946
5049
  const client = options.client ?? context?.client;
4947
5050
  if (!client) {
4948
5051
  throw new Error("useQuote must be used within CimplifyProvider or passed { client }.");
@@ -4950,59 +5053,58 @@ function useQuote(input, options = {}) {
4950
5053
  const enabled = options.enabled ?? true;
4951
5054
  const autoRefresh = options.autoRefresh ?? true;
4952
5055
  const refreshBeforeExpiryMs = Math.max(0, options.refreshBeforeExpiryMs ?? 3e4);
4953
- const locationId = client.getLocationId();
5056
+ const sessionEpoch = useClientSessionEpoch(client);
5057
+ const fallbackLocationId = context?.client === client ? context.currentLocation?.id ?? (context.isReady ? null : client.getLocationId()) : client.getLocationId();
4954
5058
  const requestIdRef = useRef(0);
4955
- const debounceTimerRef = useRef(null);
4956
5059
  const inputSignature = buildQuoteInputSignature(input);
4957
5060
  const normalizedInput = useMemo(() => {
4958
5061
  if (!input) {
4959
5062
  return null;
4960
5063
  }
4961
- const normalized = normalizeInput(input, locationId);
5064
+ const normalized = normalizeInput(input, fallbackLocationId);
4962
5065
  return normalized.product_id.length > 0 ? normalized : null;
4963
- }, [inputSignature, locationId]);
5066
+ }, [inputSignature, fallbackLocationId]);
4964
5067
  const cacheKey2 = useMemo(
4965
- () => buildQuoteCacheKey(client, locationId, inputSignature),
4966
- [client, inputSignature, locationId]
5068
+ () => buildQuoteCacheKey(
5069
+ client,
5070
+ sessionEpoch,
5071
+ normalizedInput?.location_id ?? null,
5072
+ inputSignature
5073
+ ),
5074
+ [client, inputSignature, normalizedInput?.location_id, sessionEpoch]
4967
5075
  );
4968
- const cached = quoteCache.get(cacheKey2);
4969
- const [quote, setQuote] = useState(cached?.quote ?? null);
4970
- const [isLoading, setIsLoading] = useState(enabled && normalizedInput !== null && !cached);
4971
- const [error, setError] = useState(null);
4972
- const [isExpired, setIsExpired] = useState(isQuoteExpired(cached?.quote ?? null));
4973
- const [messages, setMessages] = useState(cached?.quote?.ui_messages ?? []);
4974
- const quoteExpiryTimestamp = useMemo(() => {
4975
- if (!quote?.expires_at) {
4976
- return Number.NaN;
4977
- }
4978
- const expiresAt = Date.parse(quote.expires_at);
4979
- return Number.isFinite(expiresAt) ? expiresAt : Number.NaN;
4980
- }, [quote?.expires_at]);
5076
+ const [state, setState] = useState(() => {
5077
+ const cached = enabled && normalizedInput ? readUsableCachedQuote(cacheKey2) : null;
5078
+ return requestState(cacheKey2, cached, enabled && normalizedInput !== null && !cached);
5079
+ });
5080
+ const stateIsCurrent = state.cacheKey === cacheKey2 && enabled && normalizedInput !== null;
5081
+ const activeQuote = stateIsCurrent ? state.quote : null;
5082
+ const activeQuoteExpired = isQuoteExpired(activeQuote);
5083
+ const quote = activeQuoteExpired ? null : activeQuote;
5084
+ const isLoading = enabled && normalizedInput !== null && (!stateIsCurrent || state.isLoading);
5085
+ const error = stateIsCurrent ? state.error : null;
5086
+ const isExpired = stateIsCurrent ? state.isExpired || activeQuoteExpired : false;
5087
+ const messages = stateIsCurrent ? state.messages : [];
5088
+ const expiresAt = quoteExpiryTimestamp(activeQuote);
4981
5089
  const load = useCallback(
4982
5090
  async (force = false) => {
4983
5091
  if (!enabled || !normalizedInput) {
4984
- setQuote(null);
4985
- setMessages([]);
4986
- setIsExpired(false);
4987
- setError(null);
4988
- setIsLoading(false);
5092
+ requestIdRef.current += 1;
5093
+ setState(requestState(cacheKey2, null, false));
4989
5094
  return;
4990
5095
  }
4991
5096
  const nextRequestId = ++requestIdRef.current;
4992
- setError(null);
4993
5097
  if (!force) {
4994
- const cacheEntry = quoteCache.get(cacheKey2);
4995
- if (cacheEntry) {
4996
- setQuote(cacheEntry.quote);
4997
- setMessages(cacheEntry.quote?.ui_messages ?? []);
4998
- setIsExpired(isQuoteExpired(cacheEntry.quote));
4999
- setIsLoading(false);
5098
+ const cachedQuote = readUsableCachedQuote(cacheKey2);
5099
+ if (cachedQuote) {
5100
+ setState(requestState(cacheKey2, cachedQuote, false));
5000
5101
  return;
5001
5102
  }
5002
5103
  }
5003
- setIsLoading(true);
5004
- setQuote(null);
5005
- setMessages([]);
5104
+ setState((current) => ({
5105
+ ...requestState(cacheKey2, null, true),
5106
+ isExpired: current.cacheKey === cacheKey2 && current.isExpired
5107
+ }));
5006
5108
  try {
5007
5109
  const existing = quoteInflight.get(cacheKey2);
5008
5110
  const promise = existing ?? (async () => {
@@ -5019,55 +5121,58 @@ function useQuote(input, options = {}) {
5019
5121
  }).catch(() => void 0);
5020
5122
  }
5021
5123
  const value = await promise;
5022
- quoteCache.set(cacheKey2, { quote: value });
5023
- if (nextRequestId === requestIdRef.current) {
5024
- setQuote(value);
5025
- setMessages(value.ui_messages ?? []);
5026
- setIsExpired(isQuoteExpired(value));
5027
- setError(null);
5124
+ const expired = isQuoteExpired(value);
5125
+ if (expired) {
5126
+ quoteCache.delete(cacheKey2);
5127
+ } else {
5128
+ quoteCache.set(cacheKey2, { quote: value });
5028
5129
  }
5029
- } catch (loadError) {
5030
5130
  if (nextRequestId === requestIdRef.current) {
5031
- setError(loadError);
5131
+ setState({
5132
+ ...requestState(cacheKey2, expired ? null : value, false),
5133
+ isExpired: expired
5134
+ });
5032
5135
  }
5033
- } finally {
5136
+ } catch (loadError) {
5034
5137
  if (nextRequestId === requestIdRef.current) {
5035
- setIsLoading(false);
5138
+ setState((current) => ({
5139
+ ...requestState(cacheKey2, null, false),
5140
+ error: loadError,
5141
+ isExpired: current.cacheKey === cacheKey2 && current.isExpired
5142
+ }));
5036
5143
  }
5037
5144
  }
5038
5145
  },
5039
5146
  [cacheKey2, client, enabled, normalizedInput]
5040
5147
  );
5041
5148
  useEffect(() => {
5042
- if (debounceTimerRef.current) {
5043
- clearTimeout(debounceTimerRef.current);
5044
- }
5045
- const hasCached = quoteCache.has(cacheKey2);
5046
- if (hasCached || !enabled || !normalizedInput) {
5149
+ if (!enabled || !normalizedInput || readUsableCachedQuote(cacheKey2)) {
5047
5150
  void load(false);
5048
- } else {
5049
- debounceTimerRef.current = setTimeout(() => {
5050
- void load(false);
5051
- }, 150);
5151
+ return;
5052
5152
  }
5153
+ const timer = setTimeout(() => {
5154
+ void load(false);
5155
+ }, QUOTE_LOAD_DEBOUNCE_MS);
5053
5156
  return () => {
5054
- if (debounceTimerRef.current) {
5055
- clearTimeout(debounceTimerRef.current);
5056
- debounceTimerRef.current = null;
5057
- }
5157
+ clearTimeout(timer);
5058
5158
  };
5059
5159
  }, [load, cacheKey2, enabled, normalizedInput]);
5060
5160
  const refresh = useCallback(async () => {
5061
5161
  if (!enabled || !normalizedInput) {
5062
5162
  return;
5063
5163
  }
5064
- if (!quote?.quote_id) {
5164
+ if (!quote?.quote_id || isQuoteExpired(quote)) {
5165
+ quoteCache.delete(cacheKey2);
5065
5166
  await load(true);
5066
5167
  return;
5067
5168
  }
5068
5169
  const nextRequestId = ++requestIdRef.current;
5069
- setError(null);
5070
- setIsLoading(true);
5170
+ setState((current) => ({
5171
+ ...current,
5172
+ cacheKey: cacheKey2,
5173
+ isLoading: true,
5174
+ error: null
5175
+ }));
5071
5176
  try {
5072
5177
  const result = await client.catalogue.refreshQuote({
5073
5178
  quote_id: quote.quote_id,
@@ -5077,52 +5182,67 @@ function useQuote(input, options = {}) {
5077
5182
  throw result.error;
5078
5183
  }
5079
5184
  const refreshed = result.value.quote;
5080
- quoteCache.set(cacheKey2, { quote: refreshed });
5081
- if (nextRequestId === requestIdRef.current) {
5082
- setQuote(refreshed);
5083
- setMessages(refreshed.ui_messages ?? []);
5084
- setIsExpired(isQuoteExpired(refreshed));
5085
- setError(null);
5185
+ const expired = isQuoteExpired(refreshed);
5186
+ if (expired) {
5187
+ quoteCache.delete(cacheKey2);
5188
+ } else {
5189
+ quoteCache.set(cacheKey2, { quote: refreshed });
5086
5190
  }
5087
- } catch (refreshError) {
5088
5191
  if (nextRequestId === requestIdRef.current) {
5089
- setError(refreshError);
5192
+ setState({
5193
+ ...requestState(cacheKey2, expired ? null : refreshed, false),
5194
+ isExpired: expired
5195
+ });
5090
5196
  }
5091
- } finally {
5197
+ } catch (refreshError) {
5092
5198
  if (nextRequestId === requestIdRef.current) {
5093
- setIsLoading(false);
5199
+ setState((current) => ({
5200
+ ...current,
5201
+ cacheKey: cacheKey2,
5202
+ isLoading: false,
5203
+ error: refreshError
5204
+ }));
5094
5205
  }
5095
5206
  }
5096
5207
  }, [cacheKey2, client, enabled, load, normalizedInput, quote]);
5097
5208
  useEffect(() => {
5098
- if (!Number.isFinite(quoteExpiryTimestamp)) {
5099
- setIsExpired(false);
5209
+ if (!activeQuote || !Number.isFinite(expiresAt)) {
5100
5210
  return;
5101
5211
  }
5102
- const now = Date.now();
5103
- const isCurrentlyExpired = quoteExpiryTimestamp <= now;
5104
- const timers = [];
5105
- setIsExpired(isCurrentlyExpired);
5106
- if (!isCurrentlyExpired) {
5107
- timers.push(
5108
- setTimeout(() => {
5109
- setIsExpired(true);
5110
- }, Math.max(0, quoteExpiryTimestamp - now))
5111
- );
5112
- }
5113
- if (autoRefresh && enabled && !isCurrentlyExpired) {
5114
- timers.push(
5115
- setTimeout(() => {
5116
- void refresh();
5117
- }, Math.max(0, quoteExpiryTimestamp - now - refreshBeforeExpiryMs))
5118
- );
5212
+ const remainingMs = expiresAt - Date.now();
5213
+ if (remainingMs <= 0) {
5214
+ quoteCache.delete(cacheKey2);
5215
+ void load(true);
5216
+ return;
5119
5217
  }
5218
+ const expiryTimer = setTimeout(() => {
5219
+ quoteCache.delete(cacheKey2);
5220
+ setState({
5221
+ ...requestState(cacheKey2, null, true),
5222
+ isExpired: true
5223
+ });
5224
+ void load(true);
5225
+ }, remainingMs);
5226
+ const refreshDelayMs = remainingMs - refreshBeforeExpiryMs;
5227
+ const refreshTimer = autoRefresh && enabled && refreshDelayMs >= MIN_AUTO_REFRESH_DELAY_MS ? setTimeout(() => {
5228
+ void refresh();
5229
+ }, refreshDelayMs) : null;
5120
5230
  return () => {
5121
- for (const timer of timers) {
5122
- clearTimeout(timer);
5231
+ clearTimeout(expiryTimer);
5232
+ if (refreshTimer) {
5233
+ clearTimeout(refreshTimer);
5123
5234
  }
5124
5235
  };
5125
- }, [autoRefresh, enabled, quoteExpiryTimestamp, refresh, refreshBeforeExpiryMs]);
5236
+ }, [
5237
+ activeQuote,
5238
+ autoRefresh,
5239
+ cacheKey2,
5240
+ enabled,
5241
+ expiresAt,
5242
+ load,
5243
+ refresh,
5244
+ refreshBeforeExpiryMs
5245
+ ]);
5126
5246
  return { quote, isLoading, error, refresh, isExpired, messages };
5127
5247
  }
5128
5248
 
@@ -5311,14 +5431,7 @@ function useBundles(options = {}) {
5311
5431
  return { bundles: data ?? [], isLoading, error, refetch };
5312
5432
  }
5313
5433
  function useProductPrice(input) {
5314
- const {
5315
- product,
5316
- quantity = 1,
5317
- variant,
5318
- addOnOptions,
5319
- compositePrice,
5320
- bundleTotalPrice
5321
- } = input;
5434
+ const { product, quantity = 1, variant, addOnOptions, compositePrice, bundleTotalPrice } = input;
5322
5435
  return useMemo(() => {
5323
5436
  let unitPrice;
5324
5437
  if (bundleTotalPrice != null) {
@@ -5326,10 +5439,7 @@ function useProductPrice(input) {
5326
5439
  } else if (compositePrice) {
5327
5440
  unitPrice = parsePrice(compositePrice.final_price);
5328
5441
  } else {
5329
- unitPrice = parsePrice(product.default_price);
5330
- if (variant?.price_adjustment) {
5331
- unitPrice += parsePrice(variant.price_adjustment);
5332
- }
5442
+ unitPrice = getVariantUnitPrice(product, variant);
5333
5443
  if (addOnOptions) {
5334
5444
  for (const option of addOnOptions) {
5335
5445
  if (option.default_price) {
@@ -5345,7 +5455,15 @@ function useProductPrice(input) {
5345
5455
  totalPrice,
5346
5456
  displayTotalPrice: totalPrice
5347
5457
  };
5348
- }, [product.default_price, quantity, variant?.price_adjustment, addOnOptions, compositePrice, bundleTotalPrice]);
5458
+ }, [
5459
+ product,
5460
+ quantity,
5461
+ variant?.price_adjustment,
5462
+ variant?.price_info,
5463
+ addOnOptions,
5464
+ compositePrice,
5465
+ bundleTotalPrice
5466
+ ]);
5349
5467
  }
5350
5468
 
5351
5469
  // src/react/hooks/use-orders.ts
@@ -9597,7 +9715,18 @@ function DateSlotPicker({
9597
9715
  }
9598
9716
  );
9599
9717
  }
9600
- function ProductCustomizer({
9718
+ var REQUIRED_OPTIONS_MESSAGE = "Please select all required options";
9719
+ var QUOTE_LOADING_MESSAGE = "Updating price\u2026";
9720
+ var QUOTE_UNAVAILABLE_MESSAGE = "Price is unavailable. Please try again.";
9721
+ var CUSTOMIZER_VALIDATION_ID = "cimplify-customizer-validation";
9722
+ function initialVariantSelection(product) {
9723
+ const variant = product.variants?.find((candidate) => candidate.is_default) ?? product.variants?.[0];
9724
+ return { id: variant?.id, variant };
9725
+ }
9726
+ function ProductCustomizer(props) {
9727
+ return /* @__PURE__ */ jsx(ProductCustomizerContent, { ...props }, props.product.id);
9728
+ }
9729
+ function ProductCustomizerContent({
9601
9730
  product,
9602
9731
  onAddToCart,
9603
9732
  onVariantChange,
@@ -9609,8 +9738,11 @@ function ProductCustomizer({
9609
9738
  const [quantity, setQuantity] = useState(product.min_order_quantity ?? 1);
9610
9739
  const [isAdded, setIsAdded] = useState(false);
9611
9740
  const [isSubmitting, setIsSubmitting] = useState(false);
9612
- const [selectedVariantId, setSelectedVariantId] = useState();
9613
- const [selectedVariant, setSelectedVariant] = useState();
9741
+ const [variantSelection, setVariantSelection] = useState(
9742
+ () => initialVariantSelection(product)
9743
+ );
9744
+ const selectedVariantId = variantSelection.id;
9745
+ const selectedVariant = variantSelection.variant;
9614
9746
  const [selectedAddOnOptionIds, setSelectedAddOnOptionIds] = useState([]);
9615
9747
  const [compositeSelections, setCompositeSelections] = useState([]);
9616
9748
  const [compositePrice, setCompositePrice] = useState(null);
@@ -9625,7 +9757,6 @@ function ProductCustomizer({
9625
9757
  const [selectedStaffId, setSelectedStaffId] = useState(null);
9626
9758
  const [unitsCount, setUnitsCount] = useState(1);
9627
9759
  const cart = useCart();
9628
- const currency = getProductCurrency(product);
9629
9760
  const productType = product.type || "product";
9630
9761
  const isComposite = productType === "composite";
9631
9762
  const isBundle = productType === "bundle";
@@ -9635,26 +9766,6 @@ function ProductCustomizer({
9635
9766
  const isStandard = !isComposite && !isBundle;
9636
9767
  const hasVariants = isStandard && product.variants && product.variants.length > 0;
9637
9768
  const hasAddOns = isStandard && product.add_ons && product.add_ons.length > 0;
9638
- useEffect(() => {
9639
- setQuantity(product.min_order_quantity ?? 1);
9640
- setIsAdded(false);
9641
- setIsSubmitting(false);
9642
- setSelectedVariantId(void 0);
9643
- setSelectedVariant(void 0);
9644
- setSelectedAddOnOptionIds([]);
9645
- setCompositeSelections([]);
9646
- setCompositePrice(null);
9647
- setCompositeReady(false);
9648
- setBundleSelections([]);
9649
- setBundleTotalPrice(null);
9650
- setBundleReady(false);
9651
- setSelectedBillingPlan(null);
9652
- setCustomerInputValues({});
9653
- setSpecialInstructions("");
9654
- setSelectedSlot(null);
9655
- setSelectedStaffId(null);
9656
- setUnitsCount(1);
9657
- }, [product.id, product.min_order_quantity]);
9658
9769
  const slotStaff = useMemo(
9659
9770
  () => (selectedSlot?.available_staff ?? []).map((member) => ({
9660
9771
  id: member.staff_id,
@@ -9677,7 +9788,9 @@ function ProductCustomizer({
9677
9788
  }, [product.add_ons, selectedAddOnOptionIds]);
9678
9789
  const normalizedAddOnOptionIds = useMemo(() => {
9679
9790
  if (selectedAddOnOptionIds.length === 0) return [];
9680
- return Array.from(new Set(selectedAddOnOptionIds.map((id) => id.trim()).filter(Boolean))).sort();
9791
+ return Array.from(
9792
+ new Set(selectedAddOnOptionIds.map((id) => id.trim()).filter(Boolean))
9793
+ ).sort();
9681
9794
  }, [selectedAddOnOptionIds]);
9682
9795
  const localTotalPrice = useMemo(() => {
9683
9796
  if (isComposite && compositePrice) {
@@ -9686,17 +9799,23 @@ function ProductCustomizer({
9686
9799
  if (isBundle && bundleTotalPrice != null) {
9687
9800
  return bundleTotalPrice * quantity;
9688
9801
  }
9689
- let price = parsePrice(product.default_price);
9690
- if (selectedVariant?.price_adjustment) {
9691
- price += parsePrice(selectedVariant.price_adjustment);
9692
- }
9802
+ let price = getVariantUnitPrice(product, selectedVariant);
9693
9803
  for (const option of selectedAddOnOptions) {
9694
9804
  if (option.default_price) {
9695
9805
  price += parsePrice(option.default_price);
9696
9806
  }
9697
9807
  }
9698
9808
  return price * quantity;
9699
- }, [product.default_price, selectedVariant, selectedAddOnOptions, quantity, isComposite, compositePrice, isBundle, bundleTotalPrice]);
9809
+ }, [
9810
+ product.default_price,
9811
+ selectedVariant,
9812
+ selectedAddOnOptions,
9813
+ quantity,
9814
+ isComposite,
9815
+ compositePrice,
9816
+ isBundle,
9817
+ bundleTotalPrice
9818
+ ]);
9700
9819
  const requiredAddOnsSatisfied = useMemo(() => {
9701
9820
  if (!product.add_ons) return true;
9702
9821
  for (const addOn of product.add_ons) {
@@ -9721,7 +9840,16 @@ function ProductCustomizer({
9721
9840
  bundleSelections: isBundle && bundleSelections.length > 0 ? bundleSelections : void 0,
9722
9841
  compositeSelections: isComposite && compositeSelections.length > 0 ? compositeSelections : void 0
9723
9842
  }),
9724
- [product.id, quantity, selectedVariantId, normalizedAddOnOptionIds, isBundle, bundleSelections, isComposite, compositeSelections]
9843
+ [
9844
+ product.id,
9845
+ quantity,
9846
+ selectedVariantId,
9847
+ normalizedAddOnOptionIds,
9848
+ isBundle,
9849
+ bundleSelections,
9850
+ isComposite,
9851
+ compositeSelections
9852
+ ]
9725
9853
  );
9726
9854
  const requiredInputsSatisfied = useMemo(() => {
9727
9855
  if (!product.input_fields || product.input_fields.length === 0) return true;
@@ -9732,17 +9860,23 @@ function ProductCustomizer({
9732
9860
  });
9733
9861
  }, [product.input_fields, customerInputValues]);
9734
9862
  const quoteEnabled = isComposite ? compositeReady && requiredInputsSatisfied : isBundle ? bundleReady && requiredInputsSatisfied : requiredAddOnsSatisfied && requiredInputsSatisfied;
9735
- const { quote } = useQuote(quoteInput, {
9863
+ const {
9864
+ quote,
9865
+ isLoading: isQuoteLoading,
9866
+ error: quoteError,
9867
+ isExpired: isQuoteExpired2
9868
+ } = useQuote(quoteInput, {
9736
9869
  enabled: quoteEnabled
9737
9870
  });
9871
+ const currency = quote?.currency ?? getProductCurrency(product);
9738
9872
  const quoteId = quote?.quote_id;
9873
+ const quotedUnitPrice = (quote?.quoted_total_price_info ?? quote?.final_price_info)?.final_price;
9739
9874
  const quotedTotalPrice = useMemo(() => {
9740
- if (!quote) return void 0;
9741
- const priceInfo = quote.quoted_total_price_info ?? quote.final_price_info;
9742
- const perUnit = priceInfo.pre_tax_price;
9743
- if (perUnit === void 0 || perUnit === null) return void 0;
9744
- return parsePrice(perUnit) * quantity;
9745
- }, [quote, quantity]);
9875
+ if (quotedUnitPrice === void 0 || quotedUnitPrice === null) return void 0;
9876
+ return parsePrice(quotedUnitPrice) * quantity;
9877
+ }, [quotedUnitPrice, quantity]);
9878
+ const quoteIsReady = quoteEnabled && quote !== null && !isQuoteLoading && !quoteError && !isQuoteExpired2;
9879
+ const validationMessage = !quoteEnabled ? REQUIRED_OPTIONS_MESSAGE : quoteError ? QUOTE_UNAVAILABLE_MESSAGE : !quoteIsReady ? QUOTE_LOADING_MESSAGE : null;
9746
9880
  const displayTotalPrice = useMemo(() => {
9747
9881
  const total = quotedTotalPrice ?? localTotalPrice;
9748
9882
  const multiplier = serviceBillingMultiplier(product, {
@@ -9756,8 +9890,7 @@ function ProductCustomizer({
9756
9890
  }, [quotedTotalPrice, localTotalPrice, product, quantity, unitsCount, selectedSlot]);
9757
9891
  const handleVariantChange = useCallback(
9758
9892
  (variantId, variant) => {
9759
- setSelectedVariantId(variantId);
9760
- setSelectedVariant(variant);
9893
+ setVariantSelection({ id: variantId, variant });
9761
9894
  onVariantChange?.(variantId, variant);
9762
9895
  },
9763
9896
  [onVariantChange]
@@ -9765,12 +9898,21 @@ function ProductCustomizer({
9765
9898
  const addedTimerRef = useRef(void 0);
9766
9899
  useEffect(() => () => clearTimeout(addedTimerRef.current), []);
9767
9900
  const handleAddToCart = useCallback(async () => {
9768
- if (isSubmitting) return;
9901
+ if (isSubmitting || !quoteIsReady || !quote || quotedUnitPrice === void 0 || quotedUnitPrice === null) {
9902
+ return;
9903
+ }
9769
9904
  setIsSubmitting(true);
9770
9905
  const options = {
9906
+ locationId: quote.location_id ?? void 0,
9771
9907
  variantId: selectedVariantId,
9772
- variant: selectedVariant ? { id: selectedVariant.id, name: selectedVariant.name || "", price_adjustment: selectedVariant.price_adjustment } : void 0,
9908
+ variant: selectedVariant ? {
9909
+ id: selectedVariant.id,
9910
+ name: selectedVariant.name || "",
9911
+ price_adjustment: selectedVariant.price_adjustment,
9912
+ price_info: selectedVariant.price_info
9913
+ } : void 0,
9773
9914
  quoteId,
9915
+ quotedUnitPrice,
9774
9916
  addOnOptionIds: normalizedAddOnOptionIds.length > 0 ? normalizedAddOnOptionIds : void 0,
9775
9917
  addOnOptions: selectedAddOnOptions.length > 0 ? selectedAddOnOptions.map((opt) => ({
9776
9918
  id: opt.id,
@@ -9807,7 +9949,32 @@ function ProductCustomizer({
9807
9949
  } finally {
9808
9950
  setIsSubmitting(false);
9809
9951
  }
9810
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, unitsCount, isSubmitting, onAddToCart, cart]);
9952
+ }, [
9953
+ product,
9954
+ quantity,
9955
+ selectedVariantId,
9956
+ selectedVariant,
9957
+ quoteId,
9958
+ quotedUnitPrice,
9959
+ normalizedAddOnOptionIds,
9960
+ selectedAddOnOptions,
9961
+ isComposite,
9962
+ compositeSelections,
9963
+ isBundle,
9964
+ bundleSelections,
9965
+ selectedBillingPlan,
9966
+ customerInputValues,
9967
+ specialInstructions,
9968
+ isService,
9969
+ selectedSlot,
9970
+ selectedStaffId,
9971
+ unitsCount,
9972
+ isSubmitting,
9973
+ onAddToCart,
9974
+ cart,
9975
+ quoteIsReady,
9976
+ quote
9977
+ ]);
9811
9978
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
9812
9979
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
9813
9980
  CompositeSelector,
@@ -9920,27 +10087,48 @@ function ProductCustomizer({
9920
10087
  currency
9921
10088
  }
9922
10089
  ),
9923
- product.deposit_type && product.deposit_type !== "none" && product.deposit_amount && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-deposit": true, className: cn("text-sm text-muted-foreground", classNames?.depositInfo), children: product.deposit_type === "fixed" ? /* @__PURE__ */ jsxs("span", { children: [
9924
- "Deposit required: ",
9925
- /* @__PURE__ */ jsx(Price, { amount: product.deposit_amount, currency })
9926
- ] }) : /* @__PURE__ */ jsxs("span", { children: [
9927
- "Deposit required: ",
9928
- parsePrice(product.deposit_amount),
9929
- "%"
9930
- ] }) }),
9931
- product.allergies && product.allergies.length > 0 && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-allergens": true, className: cn("flex flex-wrap gap-1.5", classNames?.allergens), children: product.allergies.map((allergen) => /* @__PURE__ */ jsx(
9932
- "span",
10090
+ product.deposit_type && product.deposit_type !== "none" && product.deposit_amount && /* @__PURE__ */ jsx(
10091
+ "div",
9933
10092
  {
9934
- "data-cimplify-allergen-tag": true,
9935
- className: "inline-block text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground",
9936
- children: allergen
9937
- },
9938
- allergen
9939
- )) }),
9940
- isService && product.duration_minutes != null && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-duration": true, className: cn("text-sm text-muted-foreground", classNames?.duration), children: [
9941
- "Duration: ",
9942
- formatDuration2(product.duration_minutes, product.duration_unit)
9943
- ] }),
10093
+ "data-cimplify-customizer-deposit": true,
10094
+ className: cn("text-sm text-muted-foreground", classNames?.depositInfo),
10095
+ children: product.deposit_type === "fixed" ? /* @__PURE__ */ jsxs("span", { children: [
10096
+ "Deposit required: ",
10097
+ /* @__PURE__ */ jsx(Price, { amount: product.deposit_amount, currency })
10098
+ ] }) : /* @__PURE__ */ jsxs("span", { children: [
10099
+ "Deposit required: ",
10100
+ parsePrice(product.deposit_amount),
10101
+ "%"
10102
+ ] })
10103
+ }
10104
+ ),
10105
+ product.allergies && product.allergies.length > 0 && /* @__PURE__ */ jsx(
10106
+ "div",
10107
+ {
10108
+ "data-cimplify-customizer-allergens": true,
10109
+ className: cn("flex flex-wrap gap-1.5", classNames?.allergens),
10110
+ children: product.allergies.map((allergen) => /* @__PURE__ */ jsx(
10111
+ "span",
10112
+ {
10113
+ "data-cimplify-allergen-tag": true,
10114
+ className: "inline-block text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground",
10115
+ children: allergen
10116
+ },
10117
+ allergen
10118
+ ))
10119
+ }
10120
+ ),
10121
+ isService && product.duration_minutes != null && /* @__PURE__ */ jsxs(
10122
+ "div",
10123
+ {
10124
+ "data-cimplify-customizer-duration": true,
10125
+ className: cn("text-sm text-muted-foreground", classNames?.duration),
10126
+ children: [
10127
+ "Duration: ",
10128
+ formatDuration2(product.duration_minutes, product.duration_unit)
10129
+ ]
10130
+ }
10131
+ ),
9944
10132
  showSpecialInstructions && !isDigital && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-special-instructions": true, children: /* @__PURE__ */ jsx(
9945
10133
  "textarea",
9946
10134
  {
@@ -9973,13 +10161,17 @@ function ProductCustomizer({
9973
10161
  "data-cimplify-customizer-actions": true,
9974
10162
  className: cn("pt-4 border-t border-border", classNames?.actions),
9975
10163
  children: [
9976
- !quoteEnabled && /* @__PURE__ */ jsx(
10164
+ validationMessage && /* @__PURE__ */ jsx(
9977
10165
  "p",
9978
10166
  {
9979
- id: "cimplify-customizer-validation",
10167
+ id: CUSTOMIZER_VALIDATION_ID,
9980
10168
  "data-cimplify-customizer-validation": true,
9981
- className: cn("text-sm text-destructive mb-3", classNames?.validation),
9982
- children: "Please select all required options"
10169
+ className: cn(
10170
+ "text-sm mb-3",
10171
+ quoteError ? "text-destructive" : "text-muted-foreground",
10172
+ classNames?.validation
10173
+ ),
10174
+ children: validationMessage
9983
10175
  }
9984
10176
  ),
9985
10177
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
@@ -9992,28 +10184,54 @@ function ProductCustomizer({
9992
10184
  min: product.min_order_quantity ?? 1
9993
10185
  }
9994
10186
  ),
9995
- product.min_order_quantity && product.min_order_quantity > 1 && /* @__PURE__ */ jsxs("span", { "data-cimplify-customizer-min-hint": true, className: "text-xs text-muted-foreground text-center", children: [
9996
- "Min. ",
9997
- product.min_order_quantity
9998
- ] })
9999
- ] }),
10000
- product.quantity_pricing && product.quantity_pricing.length > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer-unit-price": true, className: "text-sm text-muted-foreground text-right shrink-0", children: [
10001
- /* @__PURE__ */ jsx(Price, { amount: getUnitPriceAtQuantity(product.quantity_pricing, quantity, parsePrice(product.default_price)), currency, className: "font-medium text-foreground" }),
10002
- " ea."
10187
+ product.min_order_quantity && product.min_order_quantity > 1 && /* @__PURE__ */ jsxs(
10188
+ "span",
10189
+ {
10190
+ "data-cimplify-customizer-min-hint": true,
10191
+ className: "text-xs text-muted-foreground text-center",
10192
+ children: [
10193
+ "Min. ",
10194
+ product.min_order_quantity
10195
+ ]
10196
+ }
10197
+ )
10003
10198
  ] }),
10199
+ product.quantity_pricing && product.quantity_pricing.length > 0 && /* @__PURE__ */ jsxs(
10200
+ "div",
10201
+ {
10202
+ "data-cimplify-customizer-unit-price": true,
10203
+ className: "text-sm text-muted-foreground text-right shrink-0",
10204
+ children: [
10205
+ /* @__PURE__ */ jsx(
10206
+ Price,
10207
+ {
10208
+ amount: getUnitPriceAtQuantity(
10209
+ product.quantity_pricing,
10210
+ quantity,
10211
+ parsePrice(product.default_price)
10212
+ ),
10213
+ currency,
10214
+ className: "font-medium text-foreground"
10215
+ }
10216
+ ),
10217
+ " ",
10218
+ "ea."
10219
+ ]
10220
+ }
10221
+ ),
10004
10222
  /* @__PURE__ */ jsx(
10005
10223
  Button$1,
10006
10224
  {
10007
10225
  onClick: handleAddToCart,
10008
- disabled: isAdded || isSubmitting || !quoteEnabled,
10009
- "aria-describedby": !quoteEnabled ? "cimplify-customizer-validation" : void 0,
10226
+ disabled: isAdded || isSubmitting || !quoteIsReady,
10227
+ "aria-describedby": validationMessage ? CUSTOMIZER_VALIDATION_ID : void 0,
10010
10228
  "data-cimplify-customizer-submit": true,
10011
10229
  className: cn(
10012
10230
  "flex-1 h-14 text-base bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed rounded-full",
10013
10231
  isAdded && classNames?.submitButtonAdded,
10014
10232
  classNames?.submitButton
10015
10233
  ),
10016
- children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
10234
+ children: isAdded ? "Added" : !quoteEnabled ? submitLabel ?? "Add to Cart" : !quoteIsReady ? quoteError ? QUOTE_UNAVAILABLE_MESSAGE : QUOTE_LOADING_MESSAGE : /* @__PURE__ */ jsxs(Fragment, { children: [
10017
10235
  submitLabel ?? "Add to Cart",
10018
10236
  " \xB7",
10019
10237
  " ",
@@ -10447,13 +10665,16 @@ function CartLineItemRow({
10447
10665
  item,
10448
10666
  onRemove,
10449
10667
  onQuantityChange,
10668
+ currency,
10450
10669
  classNames
10451
10670
  }) {
10452
- const { unitPrice } = useProductPrice({
10671
+ const { unitPrice: locallyResolvedUnitPrice } = useProductPrice({
10453
10672
  product: item.product,
10454
10673
  variant: item.variant,
10455
10674
  addOnOptions: item.addOnOptions
10456
10675
  });
10676
+ const pinnedUnitPrice = item._quotedUnitPrice;
10677
+ const unitPrice = pinnedUnitPrice === void 0 ? locallyResolvedUnitPrice : parsePrice(pinnedUnitPrice);
10457
10678
  const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
10458
10679
  const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
10459
10680
  const isOptimistic2 = item.isOptimistic === true;
@@ -10476,15 +10697,25 @@ function CartLineItemRow({
10476
10697
  {
10477
10698
  "data-cimplify-cart-item-image": true,
10478
10699
  className: "relative w-20 h-20 shrink-0 rounded-md overflow-hidden bg-muted",
10479
- children: imageUrl ? /* @__PURE__ */ jsx(
10480
- "img",
10700
+ children: imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: "", className: "w-full h-full object-cover", loading: "lazy" }) : /* @__PURE__ */ jsx("div", { className: "w-full h-full grid place-items-center text-muted-foreground/40", children: /* @__PURE__ */ jsx(
10701
+ "svg",
10481
10702
  {
10482
- src: imageUrl,
10483
- alt: "",
10484
- className: "w-full h-full object-cover",
10485
- loading: "lazy"
10703
+ className: "w-6 h-6",
10704
+ fill: "none",
10705
+ stroke: "currentColor",
10706
+ strokeWidth: "1.5",
10707
+ viewBox: "0 0 24 24",
10708
+ "aria-hidden": true,
10709
+ children: /* @__PURE__ */ jsx(
10710
+ "path",
10711
+ {
10712
+ strokeLinecap: "round",
10713
+ strokeLinejoin: "round",
10714
+ d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z"
10715
+ }
10716
+ )
10486
10717
  }
10487
- ) : /* @__PURE__ */ jsx("div", { className: "w-full h-full grid place-items-center text-muted-foreground/40", children: /* @__PURE__ */ jsx("svg", { className: "w-6 h-6", fill: "none", stroke: "currentColor", strokeWidth: "1.5", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 001.5-1.5V6a1.5 1.5 0 00-1.5-1.5H3.75A1.5 1.5 0 002.25 6v12a1.5 1.5 0 001.5 1.5zm10.5-11.25h.008v.008h-.008V8.25zm.375 0a.375.375 0 11-.75 0 .375.375 0 01.75 0z" }) }) })
10718
+ ) })
10488
10719
  }
10489
10720
  ),
10490
10721
  /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-item-info": true, className: "flex-1 min-w-0 flex flex-col", children: [
@@ -10508,7 +10739,18 @@ function CartLineItemRow({
10508
10739
  classNames?.removeButton
10509
10740
  ),
10510
10741
  "aria-label": `Remove ${item.product.name}`,
10511
- children: /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" }) })
10742
+ children: /* @__PURE__ */ jsx(
10743
+ "svg",
10744
+ {
10745
+ className: "w-3.5 h-3.5",
10746
+ fill: "none",
10747
+ stroke: "currentColor",
10748
+ strokeWidth: "2",
10749
+ viewBox: "0 0 24 24",
10750
+ "aria-hidden": true,
10751
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })
10752
+ }
10753
+ )
10512
10754
  }
10513
10755
  )
10514
10756
  ] }),
@@ -10554,43 +10796,124 @@ function CartLineItemRow({
10554
10796
  ]
10555
10797
  }
10556
10798
  ),
10557
- item.customerInputs && item.customerInputs.length > 0 && /* @__PURE__ */ jsx("dl", { "data-cimplify-cart-customer-inputs": true, className: cn("mt-1.5 text-xs space-y-0.5", classNames?.customerInputs), children: item.customerInputs.map((input) => /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-input": true, className: "flex gap-1.5", children: [
10558
- /* @__PURE__ */ jsxs("dt", { "data-cimplify-cart-input-label": true, className: "text-muted-foreground", children: [
10559
- input.field_name,
10560
- ":"
10561
- ] }),
10562
- /* @__PURE__ */ jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === INPUT_FIELD_TYPE.File || input.field_type === INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10563
- ] }, input.field_id)) }),
10564
- (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-service-info": true, className: cn("mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground", classNames?.serviceInfo), children: [
10565
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
10566
- /* @__PURE__ */ jsxs("span", { children: [
10567
- new Date(item.scheduledStart).toLocaleDateString(void 0, { month: "short", day: "numeric" }),
10568
- " at",
10569
- " ",
10570
- new Date(item.scheduledStart).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
10571
- ] }),
10572
- item.serviceStatus && /* @__PURE__ */ jsx("span", { "data-status": item.serviceStatus, className: "ml-auto px-1.5 py-0.5 rounded-full bg-muted text-[10px] uppercase tracking-wider", children: item.serviceStatus })
10573
- ] }),
10574
- (item.lineType === "digital" || item.product.type === "digital") && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-digital-info": true, className: cn("mt-1.5 flex items-center gap-1 text-xs text-muted-foreground", classNames?.digitalInfo), children: [
10575
- /* @__PURE__ */ jsx("svg", { className: "w-3.5 h-3.5 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) }),
10576
- /* @__PURE__ */ jsx("span", { children: "Instant digital delivery" })
10577
- ] }),
10799
+ item.customerInputs && item.customerInputs.length > 0 && /* @__PURE__ */ jsx(
10800
+ "dl",
10801
+ {
10802
+ "data-cimplify-cart-customer-inputs": true,
10803
+ className: cn("mt-1.5 text-xs space-y-0.5", classNames?.customerInputs),
10804
+ children: item.customerInputs.map((input) => /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-input": true, className: "flex gap-1.5", children: [
10805
+ /* @__PURE__ */ jsxs("dt", { "data-cimplify-cart-input-label": true, className: "text-muted-foreground", children: [
10806
+ input.field_name,
10807
+ ":"
10808
+ ] }),
10809
+ /* @__PURE__ */ jsx("dd", { "data-cimplify-cart-input-value": true, className: "text-foreground m-0 truncate", children: input.field_type === INPUT_FIELD_TYPE.File || input.field_type === INPUT_FIELD_TYPE.Image ? String(input.value).split("/").pop() || "Uploaded file" : String(input.value) })
10810
+ ] }, input.field_id))
10811
+ }
10812
+ ),
10813
+ (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxs(
10814
+ "div",
10815
+ {
10816
+ "data-cimplify-cart-service-info": true,
10817
+ className: cn(
10818
+ "mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground",
10819
+ classNames?.serviceInfo
10820
+ ),
10821
+ children: [
10822
+ /* @__PURE__ */ jsx(
10823
+ "svg",
10824
+ {
10825
+ className: "w-3.5 h-3.5 shrink-0",
10826
+ fill: "none",
10827
+ stroke: "currentColor",
10828
+ strokeWidth: "2",
10829
+ viewBox: "0 0 24 24",
10830
+ "aria-hidden": true,
10831
+ children: /* @__PURE__ */ jsx(
10832
+ "path",
10833
+ {
10834
+ strokeLinecap: "round",
10835
+ strokeLinejoin: "round",
10836
+ d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
10837
+ }
10838
+ )
10839
+ }
10840
+ ),
10841
+ /* @__PURE__ */ jsxs("span", { children: [
10842
+ new Date(item.scheduledStart).toLocaleDateString(void 0, {
10843
+ month: "short",
10844
+ day: "numeric"
10845
+ }),
10846
+ " ",
10847
+ "at",
10848
+ " ",
10849
+ new Date(item.scheduledStart).toLocaleTimeString([], {
10850
+ hour: "2-digit",
10851
+ minute: "2-digit"
10852
+ })
10853
+ ] }),
10854
+ item.serviceStatus && /* @__PURE__ */ jsx(
10855
+ "span",
10856
+ {
10857
+ "data-status": item.serviceStatus,
10858
+ className: "ml-auto px-1.5 py-0.5 rounded-full bg-muted text-[10px] uppercase tracking-wider",
10859
+ children: item.serviceStatus
10860
+ }
10861
+ )
10862
+ ]
10863
+ }
10864
+ ),
10865
+ (item.lineType === "digital" || item.product.type === "digital") && /* @__PURE__ */ jsxs(
10866
+ "div",
10867
+ {
10868
+ "data-cimplify-cart-digital-info": true,
10869
+ className: cn(
10870
+ "mt-1.5 flex items-center gap-1 text-xs text-muted-foreground",
10871
+ classNames?.digitalInfo
10872
+ ),
10873
+ children: [
10874
+ /* @__PURE__ */ jsx(
10875
+ "svg",
10876
+ {
10877
+ className: "w-3.5 h-3.5 shrink-0",
10878
+ fill: "none",
10879
+ stroke: "currentColor",
10880
+ strokeWidth: "2",
10881
+ viewBox: "0 0 24 24",
10882
+ "aria-hidden": true,
10883
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" })
10884
+ }
10885
+ ),
10886
+ /* @__PURE__ */ jsx("span", { children: "Instant digital delivery" })
10887
+ ]
10888
+ }
10889
+ ),
10578
10890
  (item.bundleResolved || item.compositeResolved) && /* @__PURE__ */ jsx(
10579
10891
  "ul",
10580
10892
  {
10581
10893
  "data-cimplify-cart-composite-breakdown": item.compositeResolved ? "" : void 0,
10582
10894
  "data-cimplify-cart-bundle-breakdown": item.bundleResolved ? "" : void 0,
10583
- className: cn("mt-1.5 pl-3 border-l border-border space-y-0.5 text-xs text-muted-foreground", item.compositeResolved ? classNames?.compositeBreakdown : classNames?.bundleBreakdown),
10895
+ className: cn(
10896
+ "mt-1.5 pl-3 border-l border-border space-y-0.5 text-xs text-muted-foreground",
10897
+ item.compositeResolved ? classNames?.compositeBreakdown : classNames?.bundleBreakdown
10898
+ ),
10584
10899
  children: (item.bundleResolved?.selections ?? item.compositeResolved?.selections ?? []).map((sel) => {
10585
10900
  const name = sel.product_name ?? sel.component_name ?? "";
10586
10901
  const compId = sel.component_id ?? `${name}-${sel.quantity}`;
10587
- return /* @__PURE__ */ jsxs("li", { "data-product-type": sel.product_type, className: "flex items-baseline gap-1.5 m-0", children: [
10588
- /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground/80", children: [
10589
- sel.quantity,
10590
- "\\u00D7"
10591
- ] }),
10592
- /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
10593
- ] }, compId);
10902
+ return /* @__PURE__ */ jsxs(
10903
+ "li",
10904
+ {
10905
+ "data-product-type": sel.product_type,
10906
+ className: "flex items-baseline gap-1.5 m-0",
10907
+ children: [
10908
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground/80", children: [
10909
+ sel.quantity,
10910
+ "\\u00D7"
10911
+ ] }),
10912
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
10913
+ ]
10914
+ },
10915
+ compId
10916
+ );
10594
10917
  })
10595
10918
  }
10596
10919
  ),
@@ -10599,6 +10922,7 @@ function CartLineItemRow({
10599
10922
  Price,
10600
10923
  {
10601
10924
  amount: lineTotal,
10925
+ currency,
10602
10926
  className: cn("text-sm font-semibold tabular-nums", classNames?.itemPrice)
10603
10927
  }
10604
10928
  ),
@@ -10636,9 +10960,11 @@ function CartSummary({
10636
10960
  isEmpty,
10637
10961
  isOptimistic: isOptimistic2,
10638
10962
  pendingOpCount,
10963
+ currency,
10639
10964
  removeItem,
10640
10965
  updateQuantity
10641
10966
  } = useCart();
10967
+ const displayCurrency = serverCart?.currency ?? currencyCode(currency);
10642
10968
  const deliveryFee = serverCart?.delivery_fee;
10643
10969
  const serviceCharge = serverCart?.service_charge;
10644
10970
  const totalDiscounts = serverCart?.total_discounts;
@@ -10670,6 +10996,7 @@ function CartSummary({
10670
10996
  item,
10671
10997
  onRemove: handleRemove,
10672
10998
  onQuantityChange: handleQuantityChange,
10999
+ currency: displayCurrency,
10673
11000
  classNames
10674
11001
  },
10675
11002
  item.id
@@ -10678,38 +11005,115 @@ function CartSummary({
10678
11005
  "div",
10679
11006
  {
10680
11007
  "data-cimplify-cart-totals": true,
10681
- className: cn("mt-6 pt-4 border-t border-border space-y-2 text-sm", classNames?.totals),
11008
+ className: cn(
11009
+ "mt-6 pt-4 border-t border-border space-y-2 text-sm",
11010
+ classNames?.totals
11011
+ ),
10682
11012
  children: [
10683
- /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-subtotal": true, className: cn("flex items-baseline justify-between", classNames?.subtotal), children: [
10684
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
10685
- "Subtotal (",
10686
- itemCount,
10687
- " ",
10688
- itemCount === 1 ? "item" : "items",
10689
- ")"
10690
- ] }),
10691
- /* @__PURE__ */ jsx(Price, { amount: subtotal, className: "tabular-nums" })
10692
- ] }),
10693
- totalDiscounts != null && parsePrice(totalDiscounts) > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-discount": true, className: cn("flex items-baseline justify-between", classNames?.discount), children: [
10694
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Discount" }),
10695
- /* @__PURE__ */ jsx(Price, { amount: totalDiscounts, prefix: "-", className: "tabular-nums text-primary" })
10696
- ] }),
10697
- deliveryFee != null && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-delivery-fee": true, className: cn("flex items-baseline justify-between", classNames?.deliveryFee), children: [
10698
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Delivery" }),
10699
- parsePrice(deliveryFee) > 0 ? /* @__PURE__ */ jsx(Price, { amount: deliveryFee, className: "tabular-nums" }) : /* @__PURE__ */ jsx("span", { className: "font-medium text-primary", children: "Free" })
10700
- ] }),
10701
- serviceCharge != null && parsePrice(serviceCharge) > 0 && /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-service-charge": true, className: cn("flex items-baseline justify-between", classNames?.serviceCharge), children: [
10702
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Service charge" }),
10703
- /* @__PURE__ */ jsx(Price, { amount: serviceCharge, className: "tabular-nums" })
10704
- ] }),
10705
- /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-tax": true, className: cn("flex items-baseline justify-between", classNames?.tax), children: [
10706
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Tax" }),
10707
- /* @__PURE__ */ jsx(Price, { amount: tax, className: "tabular-nums" })
10708
- ] }),
10709
- /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-total": true, className: cn("flex items-baseline justify-between pt-3 mt-1 border-t border-border", classNames?.total), children: [
10710
- /* @__PURE__ */ jsx("span", { className: "font-semibold", children: "Total" }),
10711
- /* @__PURE__ */ jsx(Price, { amount: total, className: "text-base font-bold tabular-nums" })
10712
- ] })
11013
+ /* @__PURE__ */ jsxs(
11014
+ "div",
11015
+ {
11016
+ "data-cimplify-cart-subtotal": true,
11017
+ className: cn("flex items-baseline justify-between", classNames?.subtotal),
11018
+ children: [
11019
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
11020
+ "Subtotal (",
11021
+ itemCount,
11022
+ " ",
11023
+ itemCount === 1 ? "item" : "items",
11024
+ ")"
11025
+ ] }),
11026
+ /* @__PURE__ */ jsx(Price, { amount: subtotal, currency: displayCurrency, className: "tabular-nums" })
11027
+ ]
11028
+ }
11029
+ ),
11030
+ totalDiscounts != null && parsePrice(totalDiscounts) > 0 && /* @__PURE__ */ jsxs(
11031
+ "div",
11032
+ {
11033
+ "data-cimplify-cart-discount": true,
11034
+ className: cn("flex items-baseline justify-between", classNames?.discount),
11035
+ children: [
11036
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Discount" }),
11037
+ /* @__PURE__ */ jsx(
11038
+ Price,
11039
+ {
11040
+ amount: totalDiscounts,
11041
+ currency: displayCurrency,
11042
+ prefix: "-",
11043
+ className: "tabular-nums text-primary"
11044
+ }
11045
+ )
11046
+ ]
11047
+ }
11048
+ ),
11049
+ deliveryFee != null && /* @__PURE__ */ jsxs(
11050
+ "div",
11051
+ {
11052
+ "data-cimplify-cart-delivery-fee": true,
11053
+ className: cn("flex items-baseline justify-between", classNames?.deliveryFee),
11054
+ children: [
11055
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Delivery" }),
11056
+ parsePrice(deliveryFee) > 0 ? /* @__PURE__ */ jsx(
11057
+ Price,
11058
+ {
11059
+ amount: deliveryFee,
11060
+ currency: displayCurrency,
11061
+ className: "tabular-nums"
11062
+ }
11063
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium text-primary", children: "Free" })
11064
+ ]
11065
+ }
11066
+ ),
11067
+ serviceCharge != null && parsePrice(serviceCharge) > 0 && /* @__PURE__ */ jsxs(
11068
+ "div",
11069
+ {
11070
+ "data-cimplify-cart-service-charge": true,
11071
+ className: cn("flex items-baseline justify-between", classNames?.serviceCharge),
11072
+ children: [
11073
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Service charge" }),
11074
+ /* @__PURE__ */ jsx(
11075
+ Price,
11076
+ {
11077
+ amount: serviceCharge,
11078
+ currency: displayCurrency,
11079
+ className: "tabular-nums"
11080
+ }
11081
+ )
11082
+ ]
11083
+ }
11084
+ ),
11085
+ /* @__PURE__ */ jsxs(
11086
+ "div",
11087
+ {
11088
+ "data-cimplify-cart-tax": true,
11089
+ className: cn("flex items-baseline justify-between", classNames?.tax),
11090
+ children: [
11091
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Tax" }),
11092
+ /* @__PURE__ */ jsx(Price, { amount: tax, currency: displayCurrency, className: "tabular-nums" })
11093
+ ]
11094
+ }
11095
+ ),
11096
+ /* @__PURE__ */ jsxs(
11097
+ "div",
11098
+ {
11099
+ "data-cimplify-cart-total": true,
11100
+ className: cn(
11101
+ "flex items-baseline justify-between pt-3 mt-1 border-t border-border",
11102
+ classNames?.total
11103
+ ),
11104
+ children: [
11105
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: "Total" }),
11106
+ /* @__PURE__ */ jsx(
11107
+ Price,
11108
+ {
11109
+ amount: total,
11110
+ currency: displayCurrency,
11111
+ className: "text-base font-bold tabular-nums"
11112
+ }
11113
+ )
11114
+ ]
11115
+ }
11116
+ )
10713
11117
  ]
10714
11118
  }
10715
11119
  ),
@@ -10794,12 +11198,14 @@ function SaleBadge({
10794
11198
  return null;
10795
11199
  }
10796
11200
  let percentage = null;
10797
- if (hasDeal && dealInfo.benefit_type === "percentage") {
11201
+ if (product.sale) {
11202
+ percentage = parsePrice(product.sale.percent_off);
11203
+ } else if (hasDeal && dealInfo.benefit_type === "percentage") {
10798
11204
  percentage = parsePrice(dealInfo.value);
10799
11205
  } else if (onSale) {
10800
11206
  percentage = getDiscountPercentage(product);
10801
11207
  }
10802
- const badgeText = label ?? dealInfo?.label ?? (percentage != null && percentage > 0 ? `${percentage}% off` : null);
11208
+ const badgeText = label ?? product.sale?.badge_text ?? dealInfo?.label ?? (percentage != null && percentage > 0 ? `${percentage}% off` : null);
10803
11209
  if (!badgeText) {
10804
11210
  return null;
10805
11211
  }