@cimplify/sdk 0.99.2 → 0.100.1

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 (62) 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-R7FAU2ZF.js → chunk-BHQCSHIG.js} +78 -72
  6. package/dist/{chunk-KFLGQGMC.mjs → chunk-DLW3RWRK.mjs} +35 -15
  7. package/dist/{chunk-QYBGG3ZJ.mjs → chunk-KEC2CX4R.mjs} +2 -2
  8. package/dist/{chunk-FECGCIVV.js → chunk-NH3HYUC3.js} +3 -3
  9. package/dist/{chunk-AETVRBKI.js → chunk-NIOUSKQQ.js} +1 -0
  10. package/dist/{chunk-7TEMLVDT.mjs → chunk-NKZIXLKW.mjs} +1 -1
  11. package/dist/{chunk-J64F56EB.js → chunk-NR4Z2SFY.js} +4 -4
  12. package/dist/{chunk-K5EN2W2Q.mjs → chunk-O3YUHLZL.mjs} +1 -0
  13. package/dist/{chunk-ZFI2TKTO.mjs → chunk-PTKAN4SI.mjs} +25 -15
  14. package/dist/{chunk-4S5EMSF5.js → chunk-QGFKIAEM.js} +48 -38
  15. package/dist/{chunk-DOEGVGTM.mjs → chunk-T2B656RS.mjs} +8 -3
  16. package/dist/{chunk-PTPOQVXS.js → chunk-WMEC5JWN.js} +37 -16
  17. package/dist/{client-CX59ew7C.d.ts → client-B0Lku_lc.d.mts} +17 -16
  18. package/dist/{client-CCCOOaI3.d.ts → client-DRgEa78P.d.ts} +2 -2
  19. package/dist/{client-BNfVL2IL.d.mts → client-DxgC_lXU.d.ts} +17 -16
  20. package/dist/{client-BeMhSkRT.d.mts → client-T5c-Nxq-.d.mts} +2 -2
  21. package/dist/{index-D-w5h82t.d.ts → index-CzKN1hjf.d.ts} +3 -3
  22. package/dist/{index-BuNjyfaV.d.mts → index-DzuLT5wi.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/{payment-pKPpaORI.d.mts → payment-D8NEwCna.d.mts} +2 -2
  28. package/dist/{payment-By0JidTe.d.ts → payment-lZBP7h5M.d.ts} +2 -2
  29. package/dist/{price-BvRtp6ak.d.mts → price-DepWAmcL.d.mts} +1 -1
  30. package/dist/{price-23RNXftz.d.ts → price-ncQb-TC8.d.ts} +1 -1
  31. package/dist/{product-O8-wcHSe.d.ts → product-B384_Qm2.d.mts} +38 -49
  32. package/dist/{product-O8-wcHSe.d.mts → product-B384_Qm2.d.ts} +38 -49
  33. package/dist/react.d.mts +10 -5
  34. package/dist/react.d.ts +10 -5
  35. package/dist/react.js +830 -446
  36. package/dist/react.mjs +711 -327
  37. package/dist/{server-JBuTPY-j.d.ts → server-B7O4ttpv.d.ts} +1 -1
  38. package/dist/{server-HVDbbqR9.d.mts → server-oCGNIO_K.d.mts} +1 -1
  39. package/dist/server.d.mts +4 -4
  40. package/dist/server.d.ts +4 -4
  41. package/dist/server.js +10 -10
  42. package/dist/server.mjs +4 -4
  43. package/dist/testing/msw.d.mts +2 -2
  44. package/dist/testing/msw.d.ts +2 -2
  45. package/dist/testing/msw.js +3 -3
  46. package/dist/testing/msw.mjs +2 -2
  47. package/dist/testing/suite.d.mts +6 -6
  48. package/dist/testing/suite.d.ts +6 -6
  49. package/dist/testing/suite.js +25 -25
  50. package/dist/testing/suite.mjs +6 -6
  51. package/dist/testing.d.mts +5 -5
  52. package/dist/testing.d.ts +5 -5
  53. package/dist/testing.js +81 -81
  54. package/dist/testing.mjs +7 -7
  55. package/dist/utils.d.mts +4 -4
  56. package/dist/utils.d.ts +4 -4
  57. package/dist/utils.js +29 -29
  58. package/dist/utils.mjs +2 -2
  59. package/package.json +1 -1
  60. package/registry/cart-summary.json +1 -1
  61. package/registry/product-customizer.json +1 -1
  62. 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-DOEGVGTM.mjs';
7
- import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-ZFI2TKTO.mjs';
8
- import { parsePrice, formatMoney, formatPrice, getProductCurrency, getUnitPriceAtQuantity, isOnSale, getDiscountPercentage, getBasePrice, getPriceRange, formatPriceRange } from './chunk-KFLGQGMC.mjs';
9
- import { isSupportedCurrency, CimplifyError } from './chunk-K5EN2W2Q.mjs';
6
+ import { SESSION_CLAIMED, SESSION_CLEARED, createCimplifyClient, AUTH_TOKEN_CHANGED, MUTATION_ERROR } from './chunk-T2B656RS.mjs';
7
+ import { generateIdempotencyKey, ELEMENT_TYPES, EVENT_TYPES, startSignIn } from './chunk-PTKAN4SI.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';
@@ -574,10 +574,20 @@ function lineKeyFromItem(item) {
574
574
  staffId: item.staffId
575
575
  });
576
576
  }
577
- function attachInternalItemMetadata(item) {
578
- return { ...item, _lineKey: lineKeyFromItem(item) };
577
+ function attachInternalItemMetadata(item, metadata = {}) {
578
+ const internalItem = {
579
+ ...item,
580
+ _lineKey: lineKeyFromItem(item)
581
+ };
582
+ if (metadata.quotedUnitPrice !== void 0) {
583
+ internalItem._quotedUnitPrice = metadata.quotedUnitPrice;
584
+ }
585
+ return internalItem;
579
586
  }
580
587
  function calculateLineSubtotal(item) {
588
+ if (item._quotedUnitPrice !== void 0) {
589
+ return roundMoney(parsePrice(item._quotedUnitPrice) * item.quantity);
590
+ }
581
591
  let unitPrice = parsePrice(item.product.default_price);
582
592
  if (item.variant?.price_adjustment) {
583
593
  unitPrice += parsePrice(item.variant.price_adjustment);
@@ -653,10 +663,7 @@ function emptyBase(currency, isLoading = true) {
653
663
  return { items: [], subtotal: 0, tax: 0, total: 0, currency, isLoading };
654
664
  }
655
665
  function buildStoreKey(client, locationId) {
656
- return [
657
- client.getPublicKey() || "__no_public_key__",
658
- locationId || "__no_location__"
659
- ].join(":");
666
+ return [client.getPublicKey() || "__no_public_key__", locationId || "__no_location__"].join(":");
660
667
  }
661
668
  function purgeLegacyCartCachesOnce() {
662
669
  if (legacyCachesPurged) return;
@@ -685,7 +692,10 @@ function toProductFromServerItem(item, businessId) {
685
692
  slug: item.item_id,
686
693
  description: item.description,
687
694
  image_url: item.image_url,
688
- default_price: item.base_price,
695
+ // Cart price_info is the accepted item price path. Do not reconstruct it
696
+ // from the catalogue base after a sale or shopper-specific quote.
697
+ default_price: item.price_info.final_price,
698
+ currency: item.price_info.currency,
689
699
  type: "product",
690
700
  inventory_type: "none",
691
701
  variant_strategy: "fetch_all",
@@ -700,37 +710,44 @@ function mapServerCart(serverCart, fallbackCurrency) {
700
710
  const variant = item.variant_info || item.variant_name ? {
701
711
  id: item.variant_info?.id || item.variant_id || "",
702
712
  name: item.variant_info?.name || item.variant_name || "Variant",
703
- price_adjustment: item.variant_info?.price_adjustment
713
+ price_adjustment: item.variant_info?.price_adjustment,
714
+ price_info: item.price_info
704
715
  } : void 0;
705
716
  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
- });
717
+ return attachInternalItemMetadata(
718
+ {
719
+ id: item.id,
720
+ product: toProductFromServerItem(item, serverCart.business_id),
721
+ quantity: clampQuantity(item.quantity),
722
+ locationId: serverCart.location_id,
723
+ variantId: item.variant_id,
724
+ variant,
725
+ quoteId,
726
+ addOnOptionIds: normalizeOptionIds(item.add_on_option_ids || void 0),
727
+ addOnOptions: (item.add_on_options || []).map((option) => ({
728
+ id: option.id,
729
+ name: option.name,
730
+ default_price: option.price
731
+ })),
732
+ bundleSelections: item.bundle_selections || void 0,
733
+ compositeSelections: item.composite_selections || void 0,
734
+ specialInstructions: item.special_instructions,
735
+ scheduledStart: item.scheduled_start || void 0,
736
+ scheduledEnd: item.scheduled_end || void 0,
737
+ staffId: item.staff_id || void 0,
738
+ units: item.units || void 0,
739
+ lineType: item.line_type,
740
+ confirmationCode: item.confirmation_code || void 0,
741
+ serviceStatus: item.service_status || void 0,
742
+ bundleResolved: item.bundle_resolved || void 0,
743
+ compositeResolved: item.composite_resolved || void 0,
744
+ customerInputs: item.customer_inputs || void 0
745
+ },
746
+ // CartItemDetails.total_price is the accepted configured unit amount,
747
+ // including add-ons. It is the only safe basis for optimistic quantity
748
+ // changes and line rendering after the server replaces the temp item.
749
+ { quotedUnitPrice: item.total_price }
750
+ );
734
751
  });
735
752
  return {
736
753
  items,
@@ -871,24 +888,27 @@ async function performAddItem(entry, product, quantity, options) {
871
888
  addOnOptionIds: normalizeOptionIds(options.addOnOptionIds)
872
889
  };
873
890
  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
- });
891
+ const tempItem = attachInternalItemMetadata(
892
+ {
893
+ id: `tmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
894
+ product,
895
+ quantity: resolvedQuantity,
896
+ locationId: normalizedOptions.locationId,
897
+ variantId: normalizedOptions.variantId,
898
+ variant: normalizedOptions.variant,
899
+ quoteId: normalizedOptions.quoteId,
900
+ addOnOptionIds: normalizedOptions.addOnOptionIds,
901
+ addOnOptions: normalizedOptions.addOnOptions,
902
+ bundleSelections: normalizedOptions.bundleSelections,
903
+ compositeSelections: normalizedOptions.compositeSelections,
904
+ specialInstructions: normalizedOptions.specialInstructions,
905
+ scheduledStart: normalizedOptions.scheduledStart,
906
+ scheduledEnd: normalizedOptions.scheduledEnd,
907
+ staffId: normalizedOptions.staffId,
908
+ units: normalizedOptions.units
909
+ },
910
+ { quotedUnitPrice: normalizedOptions.quotedUnitPrice }
911
+ );
892
912
  const idempotencyKey = `idem_${typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}_${Math.random().toString(36).slice(2, 10)}`}`;
893
913
  const patch = { kind: "add_item", tempItem, lineKey };
894
914
  const useOptimistic = entry.optimisticEnabled;
@@ -998,12 +1018,12 @@ async function performReorder(entry, orderId) {
998
1018
  return result.value;
999
1019
  }
1000
1020
  function useCart(options = {}) {
1001
- const context = useOptionalCimplifyClient();
1021
+ const context = useOptionalCimplify();
1002
1022
  const client = options.client ?? context?.client;
1003
1023
  if (!client) {
1004
1024
  throw new Error("useCart must be used within CimplifyProvider or passed { client }.");
1005
1025
  }
1006
- const locationId = options.locationId ?? client.getLocationId();
1026
+ const locationId = options.locationId ?? (context?.client === client ? context.currentLocation?.id ?? (context.isReady ? null : client.getLocationId()) : client.getLocationId());
1007
1027
  const currency = options.currency ?? context?.currency ?? "USD";
1008
1028
  const optimisticEnabled = context?.optimistic ?? true;
1009
1029
  const entry = useMemo(
@@ -1016,6 +1036,11 @@ function useCart(options = {}) {
1016
1036
  [client, currency, locationId, optimisticEnabled]
1017
1037
  );
1018
1038
  const view = useCollectionStore(entry.collection);
1039
+ useEffect(() => {
1040
+ if (client.getLocationId() !== locationId) {
1041
+ client.setLocationId(locationId);
1042
+ }
1043
+ }, [client, locationId]);
1019
1044
  useEffect(() => {
1020
1045
  void ensureInitialized(entry);
1021
1046
  }, [entry]);
@@ -4875,19 +4900,53 @@ function useSearch(options = {}) {
4875
4900
  }
4876
4901
  var quoteCache = /* @__PURE__ */ new Map();
4877
4902
  var quoteInflight = /* @__PURE__ */ new Map();
4878
- function buildQuoteCacheKey(client, locationId, inputSignature) {
4879
- return [client.getPublicKey(), locationId ?? "", inputSignature].join("|");
4903
+ var quoteClientNamespaces = /* @__PURE__ */ new WeakMap();
4904
+ var nextQuoteClientNamespace = 0;
4905
+ var QUOTE_LOAD_DEBOUNCE_MS = 150;
4906
+ var MIN_AUTO_REFRESH_DELAY_MS = 1e3;
4907
+ function getQuoteClientNamespace(client) {
4908
+ const existing = quoteClientNamespaces.get(client);
4909
+ if (existing !== void 0) {
4910
+ return existing;
4911
+ }
4912
+ const namespace = nextQuoteClientNamespace++;
4913
+ quoteClientNamespaces.set(client, namespace);
4914
+ return namespace;
4915
+ }
4916
+ function buildQuoteCacheKey(client, sessionEpoch, locationId, inputSignature) {
4917
+ return JSON.stringify([
4918
+ getQuoteClientNamespace(client),
4919
+ client.getPublicKey(),
4920
+ sessionEpoch,
4921
+ locationId ?? "",
4922
+ inputSignature
4923
+ ]);
4924
+ }
4925
+ function useClientSessionEpoch(client) {
4926
+ const subscribe = useCallback(
4927
+ (notify) => {
4928
+ const unsubscribe = [
4929
+ client.events.on(AUTH_TOKEN_CHANGED, notify),
4930
+ client.events.on(SESSION_CLAIMED, notify),
4931
+ client.events.on(SESSION_CLEARED, notify)
4932
+ ];
4933
+ return () => {
4934
+ for (const stopListening of unsubscribe) {
4935
+ stopListening();
4936
+ }
4937
+ };
4938
+ },
4939
+ [client]
4940
+ );
4941
+ const getSnapshot = useCallback(() => client.getSessionEpoch(), [client]);
4942
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
4880
4943
  }
4881
4944
  function serializeQuoteBundleSelections(selections) {
4882
4945
  if (!selections || selections.length === 0) {
4883
4946
  return "";
4884
4947
  }
4885
4948
  return selections.map((selection) => {
4886
- return [
4887
- selection.component_id,
4888
- selection.quantity,
4889
- selection.variant_id ?? ""
4890
- ].join(":");
4949
+ return [selection.component_id, selection.quantity, selection.variant_id ?? ""].join(":");
4891
4950
  }).join(";");
4892
4951
  }
4893
4952
  function serializeQuoteCompositeSelections(selections) {
@@ -4917,15 +4976,37 @@ function buildQuoteInputSignature(input) {
4917
4976
  serializeQuoteCompositeSelections(input.compositeSelections)
4918
4977
  ].join("|");
4919
4978
  }
4920
- function isQuoteExpired(quote) {
4979
+ function quoteExpiryTimestamp(quote) {
4921
4980
  if (!quote?.expires_at) {
4922
- return false;
4981
+ return Number.NaN;
4923
4982
  }
4924
4983
  const expiresAt = Date.parse(quote.expires_at);
4925
- if (!Number.isFinite(expiresAt)) {
4926
- return false;
4984
+ return Number.isFinite(expiresAt) ? expiresAt : Number.NaN;
4985
+ }
4986
+ function isQuoteExpired(quote, now = Date.now()) {
4987
+ const expiresAt = quoteExpiryTimestamp(quote);
4988
+ return Number.isFinite(expiresAt) && expiresAt <= now;
4989
+ }
4990
+ function readUsableCachedQuote(cacheKey2) {
4991
+ const entry = quoteCache.get(cacheKey2);
4992
+ if (!entry) {
4993
+ return null;
4927
4994
  }
4928
- return expiresAt <= Date.now();
4995
+ if (isQuoteExpired(entry.quote)) {
4996
+ quoteCache.delete(cacheKey2);
4997
+ return null;
4998
+ }
4999
+ return entry.quote;
5000
+ }
5001
+ function requestState(cacheKey2, quote, isLoading) {
5002
+ return {
5003
+ cacheKey: cacheKey2,
5004
+ quote,
5005
+ isLoading,
5006
+ error: null,
5007
+ isExpired: false,
5008
+ messages: quote?.ui_messages ?? []
5009
+ };
4929
5010
  }
4930
5011
  function normalizeInput(input, fallbackLocationId) {
4931
5012
  const productId = input.productId.trim();
@@ -4942,7 +5023,7 @@ function normalizeInput(input, fallbackLocationId) {
4942
5023
  };
4943
5024
  }
4944
5025
  function useQuote(input, options = {}) {
4945
- const context = useOptionalCimplifyClient();
5026
+ const context = useOptionalCimplify();
4946
5027
  const client = options.client ?? context?.client;
4947
5028
  if (!client) {
4948
5029
  throw new Error("useQuote must be used within CimplifyProvider or passed { client }.");
@@ -4950,59 +5031,58 @@ function useQuote(input, options = {}) {
4950
5031
  const enabled = options.enabled ?? true;
4951
5032
  const autoRefresh = options.autoRefresh ?? true;
4952
5033
  const refreshBeforeExpiryMs = Math.max(0, options.refreshBeforeExpiryMs ?? 3e4);
4953
- const locationId = client.getLocationId();
5034
+ const sessionEpoch = useClientSessionEpoch(client);
5035
+ const fallbackLocationId = context?.client === client ? context.currentLocation?.id ?? (context.isReady ? null : client.getLocationId()) : client.getLocationId();
4954
5036
  const requestIdRef = useRef(0);
4955
- const debounceTimerRef = useRef(null);
4956
5037
  const inputSignature = buildQuoteInputSignature(input);
4957
5038
  const normalizedInput = useMemo(() => {
4958
5039
  if (!input) {
4959
5040
  return null;
4960
5041
  }
4961
- const normalized = normalizeInput(input, locationId);
5042
+ const normalized = normalizeInput(input, fallbackLocationId);
4962
5043
  return normalized.product_id.length > 0 ? normalized : null;
4963
- }, [inputSignature, locationId]);
5044
+ }, [inputSignature, fallbackLocationId]);
4964
5045
  const cacheKey2 = useMemo(
4965
- () => buildQuoteCacheKey(client, locationId, inputSignature),
4966
- [client, inputSignature, locationId]
5046
+ () => buildQuoteCacheKey(
5047
+ client,
5048
+ sessionEpoch,
5049
+ normalizedInput?.location_id ?? null,
5050
+ inputSignature
5051
+ ),
5052
+ [client, inputSignature, normalizedInput?.location_id, sessionEpoch]
4967
5053
  );
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]);
5054
+ const [state, setState] = useState(() => {
5055
+ const cached = enabled && normalizedInput ? readUsableCachedQuote(cacheKey2) : null;
5056
+ return requestState(cacheKey2, cached, enabled && normalizedInput !== null && !cached);
5057
+ });
5058
+ const stateIsCurrent = state.cacheKey === cacheKey2 && enabled && normalizedInput !== null;
5059
+ const activeQuote = stateIsCurrent ? state.quote : null;
5060
+ const activeQuoteExpired = isQuoteExpired(activeQuote);
5061
+ const quote = activeQuoteExpired ? null : activeQuote;
5062
+ const isLoading = enabled && normalizedInput !== null && (!stateIsCurrent || state.isLoading);
5063
+ const error = stateIsCurrent ? state.error : null;
5064
+ const isExpired = stateIsCurrent ? state.isExpired || activeQuoteExpired : false;
5065
+ const messages = stateIsCurrent ? state.messages : [];
5066
+ const expiresAt = quoteExpiryTimestamp(activeQuote);
4981
5067
  const load = useCallback(
4982
5068
  async (force = false) => {
4983
5069
  if (!enabled || !normalizedInput) {
4984
- setQuote(null);
4985
- setMessages([]);
4986
- setIsExpired(false);
4987
- setError(null);
4988
- setIsLoading(false);
5070
+ requestIdRef.current += 1;
5071
+ setState(requestState(cacheKey2, null, false));
4989
5072
  return;
4990
5073
  }
4991
5074
  const nextRequestId = ++requestIdRef.current;
4992
- setError(null);
4993
5075
  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);
5076
+ const cachedQuote = readUsableCachedQuote(cacheKey2);
5077
+ if (cachedQuote) {
5078
+ setState(requestState(cacheKey2, cachedQuote, false));
5000
5079
  return;
5001
5080
  }
5002
5081
  }
5003
- setIsLoading(true);
5004
- setQuote(null);
5005
- setMessages([]);
5082
+ setState((current) => ({
5083
+ ...requestState(cacheKey2, null, true),
5084
+ isExpired: current.cacheKey === cacheKey2 && current.isExpired
5085
+ }));
5006
5086
  try {
5007
5087
  const existing = quoteInflight.get(cacheKey2);
5008
5088
  const promise = existing ?? (async () => {
@@ -5019,55 +5099,58 @@ function useQuote(input, options = {}) {
5019
5099
  }).catch(() => void 0);
5020
5100
  }
5021
5101
  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);
5102
+ const expired = isQuoteExpired(value);
5103
+ if (expired) {
5104
+ quoteCache.delete(cacheKey2);
5105
+ } else {
5106
+ quoteCache.set(cacheKey2, { quote: value });
5028
5107
  }
5029
- } catch (loadError) {
5030
5108
  if (nextRequestId === requestIdRef.current) {
5031
- setError(loadError);
5109
+ setState({
5110
+ ...requestState(cacheKey2, expired ? null : value, false),
5111
+ isExpired: expired
5112
+ });
5032
5113
  }
5033
- } finally {
5114
+ } catch (loadError) {
5034
5115
  if (nextRequestId === requestIdRef.current) {
5035
- setIsLoading(false);
5116
+ setState((current) => ({
5117
+ ...requestState(cacheKey2, null, false),
5118
+ error: loadError,
5119
+ isExpired: current.cacheKey === cacheKey2 && current.isExpired
5120
+ }));
5036
5121
  }
5037
5122
  }
5038
5123
  },
5039
5124
  [cacheKey2, client, enabled, normalizedInput]
5040
5125
  );
5041
5126
  useEffect(() => {
5042
- if (debounceTimerRef.current) {
5043
- clearTimeout(debounceTimerRef.current);
5044
- }
5045
- const hasCached = quoteCache.has(cacheKey2);
5046
- if (hasCached || !enabled || !normalizedInput) {
5127
+ if (!enabled || !normalizedInput || readUsableCachedQuote(cacheKey2)) {
5047
5128
  void load(false);
5048
- } else {
5049
- debounceTimerRef.current = setTimeout(() => {
5050
- void load(false);
5051
- }, 150);
5129
+ return;
5052
5130
  }
5131
+ const timer = setTimeout(() => {
5132
+ void load(false);
5133
+ }, QUOTE_LOAD_DEBOUNCE_MS);
5053
5134
  return () => {
5054
- if (debounceTimerRef.current) {
5055
- clearTimeout(debounceTimerRef.current);
5056
- debounceTimerRef.current = null;
5057
- }
5135
+ clearTimeout(timer);
5058
5136
  };
5059
5137
  }, [load, cacheKey2, enabled, normalizedInput]);
5060
5138
  const refresh = useCallback(async () => {
5061
5139
  if (!enabled || !normalizedInput) {
5062
5140
  return;
5063
5141
  }
5064
- if (!quote?.quote_id) {
5142
+ if (!quote?.quote_id || isQuoteExpired(quote)) {
5143
+ quoteCache.delete(cacheKey2);
5065
5144
  await load(true);
5066
5145
  return;
5067
5146
  }
5068
5147
  const nextRequestId = ++requestIdRef.current;
5069
- setError(null);
5070
- setIsLoading(true);
5148
+ setState((current) => ({
5149
+ ...current,
5150
+ cacheKey: cacheKey2,
5151
+ isLoading: true,
5152
+ error: null
5153
+ }));
5071
5154
  try {
5072
5155
  const result = await client.catalogue.refreshQuote({
5073
5156
  quote_id: quote.quote_id,
@@ -5077,52 +5160,67 @@ function useQuote(input, options = {}) {
5077
5160
  throw result.error;
5078
5161
  }
5079
5162
  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);
5163
+ const expired = isQuoteExpired(refreshed);
5164
+ if (expired) {
5165
+ quoteCache.delete(cacheKey2);
5166
+ } else {
5167
+ quoteCache.set(cacheKey2, { quote: refreshed });
5086
5168
  }
5087
- } catch (refreshError) {
5088
5169
  if (nextRequestId === requestIdRef.current) {
5089
- setError(refreshError);
5170
+ setState({
5171
+ ...requestState(cacheKey2, expired ? null : refreshed, false),
5172
+ isExpired: expired
5173
+ });
5090
5174
  }
5091
- } finally {
5175
+ } catch (refreshError) {
5092
5176
  if (nextRequestId === requestIdRef.current) {
5093
- setIsLoading(false);
5177
+ setState((current) => ({
5178
+ ...current,
5179
+ cacheKey: cacheKey2,
5180
+ isLoading: false,
5181
+ error: refreshError
5182
+ }));
5094
5183
  }
5095
5184
  }
5096
5185
  }, [cacheKey2, client, enabled, load, normalizedInput, quote]);
5097
5186
  useEffect(() => {
5098
- if (!Number.isFinite(quoteExpiryTimestamp)) {
5099
- setIsExpired(false);
5187
+ if (!activeQuote || !Number.isFinite(expiresAt)) {
5100
5188
  return;
5101
5189
  }
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
- );
5190
+ const remainingMs = expiresAt - Date.now();
5191
+ if (remainingMs <= 0) {
5192
+ quoteCache.delete(cacheKey2);
5193
+ void load(true);
5194
+ return;
5119
5195
  }
5196
+ const expiryTimer = setTimeout(() => {
5197
+ quoteCache.delete(cacheKey2);
5198
+ setState({
5199
+ ...requestState(cacheKey2, null, true),
5200
+ isExpired: true
5201
+ });
5202
+ void load(true);
5203
+ }, remainingMs);
5204
+ const refreshDelayMs = remainingMs - refreshBeforeExpiryMs;
5205
+ const refreshTimer = autoRefresh && enabled && refreshDelayMs >= MIN_AUTO_REFRESH_DELAY_MS ? setTimeout(() => {
5206
+ void refresh();
5207
+ }, refreshDelayMs) : null;
5120
5208
  return () => {
5121
- for (const timer of timers) {
5122
- clearTimeout(timer);
5209
+ clearTimeout(expiryTimer);
5210
+ if (refreshTimer) {
5211
+ clearTimeout(refreshTimer);
5123
5212
  }
5124
5213
  };
5125
- }, [autoRefresh, enabled, quoteExpiryTimestamp, refresh, refreshBeforeExpiryMs]);
5214
+ }, [
5215
+ activeQuote,
5216
+ autoRefresh,
5217
+ cacheKey2,
5218
+ enabled,
5219
+ expiresAt,
5220
+ load,
5221
+ refresh,
5222
+ refreshBeforeExpiryMs
5223
+ ]);
5126
5224
  return { quote, isLoading, error, refresh, isExpired, messages };
5127
5225
  }
5128
5226
 
@@ -5311,14 +5409,7 @@ function useBundles(options = {}) {
5311
5409
  return { bundles: data ?? [], isLoading, error, refetch };
5312
5410
  }
5313
5411
  function useProductPrice(input) {
5314
- const {
5315
- product,
5316
- quantity = 1,
5317
- variant,
5318
- addOnOptions,
5319
- compositePrice,
5320
- bundleTotalPrice
5321
- } = input;
5412
+ const { product, quantity = 1, variant, addOnOptions, compositePrice, bundleTotalPrice } = input;
5322
5413
  return useMemo(() => {
5323
5414
  let unitPrice;
5324
5415
  if (bundleTotalPrice != null) {
@@ -5326,10 +5417,7 @@ function useProductPrice(input) {
5326
5417
  } else if (compositePrice) {
5327
5418
  unitPrice = parsePrice(compositePrice.final_price);
5328
5419
  } else {
5329
- unitPrice = parsePrice(product.default_price);
5330
- if (variant?.price_adjustment) {
5331
- unitPrice += parsePrice(variant.price_adjustment);
5332
- }
5420
+ unitPrice = getVariantUnitPrice(product, variant);
5333
5421
  if (addOnOptions) {
5334
5422
  for (const option of addOnOptions) {
5335
5423
  if (option.default_price) {
@@ -5345,7 +5433,15 @@ function useProductPrice(input) {
5345
5433
  totalPrice,
5346
5434
  displayTotalPrice: totalPrice
5347
5435
  };
5348
- }, [product.default_price, quantity, variant?.price_adjustment, addOnOptions, compositePrice, bundleTotalPrice]);
5436
+ }, [
5437
+ product,
5438
+ quantity,
5439
+ variant?.price_adjustment,
5440
+ variant?.price_info,
5441
+ addOnOptions,
5442
+ compositePrice,
5443
+ bundleTotalPrice
5444
+ ]);
5349
5445
  }
5350
5446
 
5351
5447
  // src/react/hooks/use-orders.ts
@@ -9597,7 +9693,18 @@ function DateSlotPicker({
9597
9693
  }
9598
9694
  );
9599
9695
  }
9600
- function ProductCustomizer({
9696
+ var REQUIRED_OPTIONS_MESSAGE = "Please select all required options";
9697
+ var QUOTE_LOADING_MESSAGE = "Updating price\u2026";
9698
+ var QUOTE_UNAVAILABLE_MESSAGE = "Price is unavailable. Please try again.";
9699
+ var CUSTOMIZER_VALIDATION_ID = "cimplify-customizer-validation";
9700
+ function initialVariantSelection(product) {
9701
+ const variant = product.variants?.find((candidate) => candidate.is_default) ?? product.variants?.[0];
9702
+ return { id: variant?.id, variant };
9703
+ }
9704
+ function ProductCustomizer(props) {
9705
+ return /* @__PURE__ */ jsx(ProductCustomizerContent, { ...props }, props.product.id);
9706
+ }
9707
+ function ProductCustomizerContent({
9601
9708
  product,
9602
9709
  onAddToCart,
9603
9710
  onVariantChange,
@@ -9609,8 +9716,11 @@ function ProductCustomizer({
9609
9716
  const [quantity, setQuantity] = useState(product.min_order_quantity ?? 1);
9610
9717
  const [isAdded, setIsAdded] = useState(false);
9611
9718
  const [isSubmitting, setIsSubmitting] = useState(false);
9612
- const [selectedVariantId, setSelectedVariantId] = useState();
9613
- const [selectedVariant, setSelectedVariant] = useState();
9719
+ const [variantSelection, setVariantSelection] = useState(
9720
+ () => initialVariantSelection(product)
9721
+ );
9722
+ const selectedVariantId = variantSelection.id;
9723
+ const selectedVariant = variantSelection.variant;
9614
9724
  const [selectedAddOnOptionIds, setSelectedAddOnOptionIds] = useState([]);
9615
9725
  const [compositeSelections, setCompositeSelections] = useState([]);
9616
9726
  const [compositePrice, setCompositePrice] = useState(null);
@@ -9625,7 +9735,6 @@ function ProductCustomizer({
9625
9735
  const [selectedStaffId, setSelectedStaffId] = useState(null);
9626
9736
  const [unitsCount, setUnitsCount] = useState(1);
9627
9737
  const cart = useCart();
9628
- const currency = getProductCurrency(product);
9629
9738
  const productType = product.type || "product";
9630
9739
  const isComposite = productType === "composite";
9631
9740
  const isBundle = productType === "bundle";
@@ -9635,26 +9744,6 @@ function ProductCustomizer({
9635
9744
  const isStandard = !isComposite && !isBundle;
9636
9745
  const hasVariants = isStandard && product.variants && product.variants.length > 0;
9637
9746
  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
9747
  const slotStaff = useMemo(
9659
9748
  () => (selectedSlot?.available_staff ?? []).map((member) => ({
9660
9749
  id: member.staff_id,
@@ -9677,7 +9766,9 @@ function ProductCustomizer({
9677
9766
  }, [product.add_ons, selectedAddOnOptionIds]);
9678
9767
  const normalizedAddOnOptionIds = useMemo(() => {
9679
9768
  if (selectedAddOnOptionIds.length === 0) return [];
9680
- return Array.from(new Set(selectedAddOnOptionIds.map((id) => id.trim()).filter(Boolean))).sort();
9769
+ return Array.from(
9770
+ new Set(selectedAddOnOptionIds.map((id) => id.trim()).filter(Boolean))
9771
+ ).sort();
9681
9772
  }, [selectedAddOnOptionIds]);
9682
9773
  const localTotalPrice = useMemo(() => {
9683
9774
  if (isComposite && compositePrice) {
@@ -9686,17 +9777,23 @@ function ProductCustomizer({
9686
9777
  if (isBundle && bundleTotalPrice != null) {
9687
9778
  return bundleTotalPrice * quantity;
9688
9779
  }
9689
- let price = parsePrice(product.default_price);
9690
- if (selectedVariant?.price_adjustment) {
9691
- price += parsePrice(selectedVariant.price_adjustment);
9692
- }
9780
+ let price = getVariantUnitPrice(product, selectedVariant);
9693
9781
  for (const option of selectedAddOnOptions) {
9694
9782
  if (option.default_price) {
9695
9783
  price += parsePrice(option.default_price);
9696
9784
  }
9697
9785
  }
9698
9786
  return price * quantity;
9699
- }, [product.default_price, selectedVariant, selectedAddOnOptions, quantity, isComposite, compositePrice, isBundle, bundleTotalPrice]);
9787
+ }, [
9788
+ product.default_price,
9789
+ selectedVariant,
9790
+ selectedAddOnOptions,
9791
+ quantity,
9792
+ isComposite,
9793
+ compositePrice,
9794
+ isBundle,
9795
+ bundleTotalPrice
9796
+ ]);
9700
9797
  const requiredAddOnsSatisfied = useMemo(() => {
9701
9798
  if (!product.add_ons) return true;
9702
9799
  for (const addOn of product.add_ons) {
@@ -9721,7 +9818,16 @@ function ProductCustomizer({
9721
9818
  bundleSelections: isBundle && bundleSelections.length > 0 ? bundleSelections : void 0,
9722
9819
  compositeSelections: isComposite && compositeSelections.length > 0 ? compositeSelections : void 0
9723
9820
  }),
9724
- [product.id, quantity, selectedVariantId, normalizedAddOnOptionIds, isBundle, bundleSelections, isComposite, compositeSelections]
9821
+ [
9822
+ product.id,
9823
+ quantity,
9824
+ selectedVariantId,
9825
+ normalizedAddOnOptionIds,
9826
+ isBundle,
9827
+ bundleSelections,
9828
+ isComposite,
9829
+ compositeSelections
9830
+ ]
9725
9831
  );
9726
9832
  const requiredInputsSatisfied = useMemo(() => {
9727
9833
  if (!product.input_fields || product.input_fields.length === 0) return true;
@@ -9732,17 +9838,23 @@ function ProductCustomizer({
9732
9838
  });
9733
9839
  }, [product.input_fields, customerInputValues]);
9734
9840
  const quoteEnabled = isComposite ? compositeReady && requiredInputsSatisfied : isBundle ? bundleReady && requiredInputsSatisfied : requiredAddOnsSatisfied && requiredInputsSatisfied;
9735
- const { quote } = useQuote(quoteInput, {
9841
+ const {
9842
+ quote,
9843
+ isLoading: isQuoteLoading,
9844
+ error: quoteError,
9845
+ isExpired: isQuoteExpired2
9846
+ } = useQuote(quoteInput, {
9736
9847
  enabled: quoteEnabled
9737
9848
  });
9849
+ const currency = quote?.currency ?? getProductCurrency(product);
9738
9850
  const quoteId = quote?.quote_id;
9851
+ const quotedUnitPrice = (quote?.quoted_total_price_info ?? quote?.final_price_info)?.final_price;
9739
9852
  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]);
9853
+ if (quotedUnitPrice === void 0 || quotedUnitPrice === null) return void 0;
9854
+ return parsePrice(quotedUnitPrice) * quantity;
9855
+ }, [quotedUnitPrice, quantity]);
9856
+ const quoteIsReady = quoteEnabled && quote !== null && !isQuoteLoading && !quoteError && !isQuoteExpired2;
9857
+ const validationMessage = !quoteEnabled ? REQUIRED_OPTIONS_MESSAGE : quoteError ? QUOTE_UNAVAILABLE_MESSAGE : !quoteIsReady ? QUOTE_LOADING_MESSAGE : null;
9746
9858
  const displayTotalPrice = useMemo(() => {
9747
9859
  const total = quotedTotalPrice ?? localTotalPrice;
9748
9860
  const multiplier = serviceBillingMultiplier(product, {
@@ -9756,8 +9868,7 @@ function ProductCustomizer({
9756
9868
  }, [quotedTotalPrice, localTotalPrice, product, quantity, unitsCount, selectedSlot]);
9757
9869
  const handleVariantChange = useCallback(
9758
9870
  (variantId, variant) => {
9759
- setSelectedVariantId(variantId);
9760
- setSelectedVariant(variant);
9871
+ setVariantSelection({ id: variantId, variant });
9761
9872
  onVariantChange?.(variantId, variant);
9762
9873
  },
9763
9874
  [onVariantChange]
@@ -9765,12 +9876,21 @@ function ProductCustomizer({
9765
9876
  const addedTimerRef = useRef(void 0);
9766
9877
  useEffect(() => () => clearTimeout(addedTimerRef.current), []);
9767
9878
  const handleAddToCart = useCallback(async () => {
9768
- if (isSubmitting) return;
9879
+ if (isSubmitting || !quoteIsReady || !quote || quotedUnitPrice === void 0 || quotedUnitPrice === null) {
9880
+ return;
9881
+ }
9769
9882
  setIsSubmitting(true);
9770
9883
  const options = {
9884
+ locationId: quote.location_id ?? void 0,
9771
9885
  variantId: selectedVariantId,
9772
- variant: selectedVariant ? { id: selectedVariant.id, name: selectedVariant.name || "", price_adjustment: selectedVariant.price_adjustment } : void 0,
9886
+ variant: selectedVariant ? {
9887
+ id: selectedVariant.id,
9888
+ name: selectedVariant.name || "",
9889
+ price_adjustment: selectedVariant.price_adjustment,
9890
+ price_info: selectedVariant.price_info
9891
+ } : void 0,
9773
9892
  quoteId,
9893
+ quotedUnitPrice,
9774
9894
  addOnOptionIds: normalizedAddOnOptionIds.length > 0 ? normalizedAddOnOptionIds : void 0,
9775
9895
  addOnOptions: selectedAddOnOptions.length > 0 ? selectedAddOnOptions.map((opt) => ({
9776
9896
  id: opt.id,
@@ -9807,7 +9927,32 @@ function ProductCustomizer({
9807
9927
  } finally {
9808
9928
  setIsSubmitting(false);
9809
9929
  }
9810
- }, [product, quantity, selectedVariantId, selectedVariant, quoteId, normalizedAddOnOptionIds, selectedAddOnOptions, isComposite, compositeSelections, isBundle, bundleSelections, selectedBillingPlan, customerInputValues, specialInstructions, isService, selectedSlot, selectedStaffId, unitsCount, isSubmitting, onAddToCart, cart]);
9930
+ }, [
9931
+ product,
9932
+ quantity,
9933
+ selectedVariantId,
9934
+ selectedVariant,
9935
+ quoteId,
9936
+ quotedUnitPrice,
9937
+ normalizedAddOnOptionIds,
9938
+ selectedAddOnOptions,
9939
+ isComposite,
9940
+ compositeSelections,
9941
+ isBundle,
9942
+ bundleSelections,
9943
+ selectedBillingPlan,
9944
+ customerInputValues,
9945
+ specialInstructions,
9946
+ isService,
9947
+ selectedSlot,
9948
+ selectedStaffId,
9949
+ unitsCount,
9950
+ isSubmitting,
9951
+ onAddToCart,
9952
+ cart,
9953
+ quoteIsReady,
9954
+ quote
9955
+ ]);
9811
9956
  return /* @__PURE__ */ jsxs("div", { "data-cimplify-customizer": true, className: cn("space-y-6", className, classNames?.root), children: [
9812
9957
  isComposite && product.groups && product.composite_id && /* @__PURE__ */ jsx(
9813
9958
  CompositeSelector,
@@ -9920,27 +10065,48 @@ function ProductCustomizer({
9920
10065
  currency
9921
10066
  }
9922
10067
  ),
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",
10068
+ product.deposit_type && product.deposit_type !== "none" && product.deposit_amount && /* @__PURE__ */ jsx(
10069
+ "div",
9933
10070
  {
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
- ] }),
10071
+ "data-cimplify-customizer-deposit": true,
10072
+ className: cn("text-sm text-muted-foreground", classNames?.depositInfo),
10073
+ children: product.deposit_type === "fixed" ? /* @__PURE__ */ jsxs("span", { children: [
10074
+ "Deposit required: ",
10075
+ /* @__PURE__ */ jsx(Price, { amount: product.deposit_amount, currency })
10076
+ ] }) : /* @__PURE__ */ jsxs("span", { children: [
10077
+ "Deposit required: ",
10078
+ parsePrice(product.deposit_amount),
10079
+ "%"
10080
+ ] })
10081
+ }
10082
+ ),
10083
+ product.allergies && product.allergies.length > 0 && /* @__PURE__ */ jsx(
10084
+ "div",
10085
+ {
10086
+ "data-cimplify-customizer-allergens": true,
10087
+ className: cn("flex flex-wrap gap-1.5", classNames?.allergens),
10088
+ children: product.allergies.map((allergen) => /* @__PURE__ */ jsx(
10089
+ "span",
10090
+ {
10091
+ "data-cimplify-allergen-tag": true,
10092
+ className: "inline-block text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground",
10093
+ children: allergen
10094
+ },
10095
+ allergen
10096
+ ))
10097
+ }
10098
+ ),
10099
+ isService && product.duration_minutes != null && /* @__PURE__ */ jsxs(
10100
+ "div",
10101
+ {
10102
+ "data-cimplify-customizer-duration": true,
10103
+ className: cn("text-sm text-muted-foreground", classNames?.duration),
10104
+ children: [
10105
+ "Duration: ",
10106
+ formatDuration2(product.duration_minutes, product.duration_unit)
10107
+ ]
10108
+ }
10109
+ ),
9944
10110
  showSpecialInstructions && !isDigital && /* @__PURE__ */ jsx("div", { "data-cimplify-customizer-special-instructions": true, children: /* @__PURE__ */ jsx(
9945
10111
  "textarea",
9946
10112
  {
@@ -9973,13 +10139,17 @@ function ProductCustomizer({
9973
10139
  "data-cimplify-customizer-actions": true,
9974
10140
  className: cn("pt-4 border-t border-border", classNames?.actions),
9975
10141
  children: [
9976
- !quoteEnabled && /* @__PURE__ */ jsx(
10142
+ validationMessage && /* @__PURE__ */ jsx(
9977
10143
  "p",
9978
10144
  {
9979
- id: "cimplify-customizer-validation",
10145
+ id: CUSTOMIZER_VALIDATION_ID,
9980
10146
  "data-cimplify-customizer-validation": true,
9981
- className: cn("text-sm text-destructive mb-3", classNames?.validation),
9982
- children: "Please select all required options"
10147
+ className: cn(
10148
+ "text-sm mb-3",
10149
+ quoteError ? "text-destructive" : "text-muted-foreground",
10150
+ classNames?.validation
10151
+ ),
10152
+ children: validationMessage
9983
10153
  }
9984
10154
  ),
9985
10155
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
@@ -9992,28 +10162,54 @@ function ProductCustomizer({
9992
10162
  min: product.min_order_quantity ?? 1
9993
10163
  }
9994
10164
  ),
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."
10165
+ product.min_order_quantity && product.min_order_quantity > 1 && /* @__PURE__ */ jsxs(
10166
+ "span",
10167
+ {
10168
+ "data-cimplify-customizer-min-hint": true,
10169
+ className: "text-xs text-muted-foreground text-center",
10170
+ children: [
10171
+ "Min. ",
10172
+ product.min_order_quantity
10173
+ ]
10174
+ }
10175
+ )
10003
10176
  ] }),
10177
+ product.quantity_pricing && product.quantity_pricing.length > 0 && /* @__PURE__ */ jsxs(
10178
+ "div",
10179
+ {
10180
+ "data-cimplify-customizer-unit-price": true,
10181
+ className: "text-sm text-muted-foreground text-right shrink-0",
10182
+ children: [
10183
+ /* @__PURE__ */ jsx(
10184
+ Price,
10185
+ {
10186
+ amount: getUnitPriceAtQuantity(
10187
+ product.quantity_pricing,
10188
+ quantity,
10189
+ parsePrice(product.default_price)
10190
+ ),
10191
+ currency,
10192
+ className: "font-medium text-foreground"
10193
+ }
10194
+ ),
10195
+ " ",
10196
+ "ea."
10197
+ ]
10198
+ }
10199
+ ),
10004
10200
  /* @__PURE__ */ jsx(
10005
10201
  Button$1,
10006
10202
  {
10007
10203
  onClick: handleAddToCart,
10008
- disabled: isAdded || isSubmitting || !quoteEnabled,
10009
- "aria-describedby": !quoteEnabled ? "cimplify-customizer-validation" : void 0,
10204
+ disabled: isAdded || isSubmitting || !quoteIsReady,
10205
+ "aria-describedby": validationMessage ? CUSTOMIZER_VALIDATION_ID : void 0,
10010
10206
  "data-cimplify-customizer-submit": true,
10011
10207
  className: cn(
10012
10208
  "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
10209
  isAdded && classNames?.submitButtonAdded,
10014
10210
  classNames?.submitButton
10015
10211
  ),
10016
- children: isAdded ? "Added" : /* @__PURE__ */ jsxs(Fragment, { children: [
10212
+ children: isAdded ? "Added" : !quoteEnabled ? submitLabel ?? "Add to Cart" : !quoteIsReady ? quoteError ? QUOTE_UNAVAILABLE_MESSAGE : QUOTE_LOADING_MESSAGE : /* @__PURE__ */ jsxs(Fragment, { children: [
10017
10213
  submitLabel ?? "Add to Cart",
10018
10214
  " \xB7",
10019
10215
  " ",
@@ -10447,13 +10643,16 @@ function CartLineItemRow({
10447
10643
  item,
10448
10644
  onRemove,
10449
10645
  onQuantityChange,
10646
+ currency,
10450
10647
  classNames
10451
10648
  }) {
10452
- const { unitPrice } = useProductPrice({
10649
+ const { unitPrice: locallyResolvedUnitPrice } = useProductPrice({
10453
10650
  product: item.product,
10454
10651
  variant: item.variant,
10455
10652
  addOnOptions: item.addOnOptions
10456
10653
  });
10654
+ const pinnedUnitPrice = item._quotedUnitPrice;
10655
+ const unitPrice = pinnedUnitPrice === void 0 ? locallyResolvedUnitPrice : parsePrice(pinnedUnitPrice);
10457
10656
  const hasComposite = item.compositeSelections && item.compositeSelections.length > 0;
10458
10657
  const hasBundle = item.bundleSelections && item.bundleSelections.length > 0;
10459
10658
  const isOptimistic2 = item.isOptimistic === true;
@@ -10476,15 +10675,25 @@ function CartLineItemRow({
10476
10675
  {
10477
10676
  "data-cimplify-cart-item-image": true,
10478
10677
  className: "relative w-20 h-20 shrink-0 rounded-md overflow-hidden bg-muted",
10479
- children: imageUrl ? /* @__PURE__ */ jsx(
10480
- "img",
10678
+ 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(
10679
+ "svg",
10481
10680
  {
10482
- src: imageUrl,
10483
- alt: "",
10484
- className: "w-full h-full object-cover",
10485
- loading: "lazy"
10681
+ className: "w-6 h-6",
10682
+ fill: "none",
10683
+ stroke: "currentColor",
10684
+ strokeWidth: "1.5",
10685
+ viewBox: "0 0 24 24",
10686
+ "aria-hidden": true,
10687
+ children: /* @__PURE__ */ jsx(
10688
+ "path",
10689
+ {
10690
+ strokeLinecap: "round",
10691
+ strokeLinejoin: "round",
10692
+ 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"
10693
+ }
10694
+ )
10486
10695
  }
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" }) }) })
10696
+ ) })
10488
10697
  }
10489
10698
  ),
10490
10699
  /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-item-info": true, className: "flex-1 min-w-0 flex flex-col", children: [
@@ -10508,7 +10717,18 @@ function CartLineItemRow({
10508
10717
  classNames?.removeButton
10509
10718
  ),
10510
10719
  "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" }) })
10720
+ children: /* @__PURE__ */ jsx(
10721
+ "svg",
10722
+ {
10723
+ className: "w-3.5 h-3.5",
10724
+ fill: "none",
10725
+ stroke: "currentColor",
10726
+ strokeWidth: "2",
10727
+ viewBox: "0 0 24 24",
10728
+ "aria-hidden": true,
10729
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })
10730
+ }
10731
+ )
10512
10732
  }
10513
10733
  )
10514
10734
  ] }),
@@ -10554,43 +10774,124 @@ function CartLineItemRow({
10554
10774
  ]
10555
10775
  }
10556
10776
  ),
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
- ] }),
10777
+ item.customerInputs && item.customerInputs.length > 0 && /* @__PURE__ */ jsx(
10778
+ "dl",
10779
+ {
10780
+ "data-cimplify-cart-customer-inputs": true,
10781
+ className: cn("mt-1.5 text-xs space-y-0.5", classNames?.customerInputs),
10782
+ children: item.customerInputs.map((input) => /* @__PURE__ */ jsxs("div", { "data-cimplify-cart-input": true, className: "flex gap-1.5", children: [
10783
+ /* @__PURE__ */ jsxs("dt", { "data-cimplify-cart-input-label": true, className: "text-muted-foreground", children: [
10784
+ input.field_name,
10785
+ ":"
10786
+ ] }),
10787
+ /* @__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) })
10788
+ ] }, input.field_id))
10789
+ }
10790
+ ),
10791
+ (item.lineType === "service" || item.product.type === "service") && item.scheduledStart && /* @__PURE__ */ jsxs(
10792
+ "div",
10793
+ {
10794
+ "data-cimplify-cart-service-info": true,
10795
+ className: cn(
10796
+ "mt-1.5 flex items-center gap-1.5 text-xs text-muted-foreground",
10797
+ classNames?.serviceInfo
10798
+ ),
10799
+ children: [
10800
+ /* @__PURE__ */ jsx(
10801
+ "svg",
10802
+ {
10803
+ className: "w-3.5 h-3.5 shrink-0",
10804
+ fill: "none",
10805
+ stroke: "currentColor",
10806
+ strokeWidth: "2",
10807
+ viewBox: "0 0 24 24",
10808
+ "aria-hidden": true,
10809
+ children: /* @__PURE__ */ jsx(
10810
+ "path",
10811
+ {
10812
+ strokeLinecap: "round",
10813
+ strokeLinejoin: "round",
10814
+ 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"
10815
+ }
10816
+ )
10817
+ }
10818
+ ),
10819
+ /* @__PURE__ */ jsxs("span", { children: [
10820
+ new Date(item.scheduledStart).toLocaleDateString(void 0, {
10821
+ month: "short",
10822
+ day: "numeric"
10823
+ }),
10824
+ " ",
10825
+ "at",
10826
+ " ",
10827
+ new Date(item.scheduledStart).toLocaleTimeString([], {
10828
+ hour: "2-digit",
10829
+ minute: "2-digit"
10830
+ })
10831
+ ] }),
10832
+ item.serviceStatus && /* @__PURE__ */ jsx(
10833
+ "span",
10834
+ {
10835
+ "data-status": item.serviceStatus,
10836
+ className: "ml-auto px-1.5 py-0.5 rounded-full bg-muted text-[10px] uppercase tracking-wider",
10837
+ children: item.serviceStatus
10838
+ }
10839
+ )
10840
+ ]
10841
+ }
10842
+ ),
10843
+ (item.lineType === "digital" || item.product.type === "digital") && /* @__PURE__ */ jsxs(
10844
+ "div",
10845
+ {
10846
+ "data-cimplify-cart-digital-info": true,
10847
+ className: cn(
10848
+ "mt-1.5 flex items-center gap-1 text-xs text-muted-foreground",
10849
+ classNames?.digitalInfo
10850
+ ),
10851
+ children: [
10852
+ /* @__PURE__ */ jsx(
10853
+ "svg",
10854
+ {
10855
+ className: "w-3.5 h-3.5 shrink-0",
10856
+ fill: "none",
10857
+ stroke: "currentColor",
10858
+ strokeWidth: "2",
10859
+ viewBox: "0 0 24 24",
10860
+ "aria-hidden": true,
10861
+ children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" })
10862
+ }
10863
+ ),
10864
+ /* @__PURE__ */ jsx("span", { children: "Instant digital delivery" })
10865
+ ]
10866
+ }
10867
+ ),
10578
10868
  (item.bundleResolved || item.compositeResolved) && /* @__PURE__ */ jsx(
10579
10869
  "ul",
10580
10870
  {
10581
10871
  "data-cimplify-cart-composite-breakdown": item.compositeResolved ? "" : void 0,
10582
10872
  "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),
10873
+ className: cn(
10874
+ "mt-1.5 pl-3 border-l border-border space-y-0.5 text-xs text-muted-foreground",
10875
+ item.compositeResolved ? classNames?.compositeBreakdown : classNames?.bundleBreakdown
10876
+ ),
10584
10877
  children: (item.bundleResolved?.selections ?? item.compositeResolved?.selections ?? []).map((sel) => {
10585
10878
  const name = sel.product_name ?? sel.component_name ?? "";
10586
10879
  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);
10880
+ return /* @__PURE__ */ jsxs(
10881
+ "li",
10882
+ {
10883
+ "data-product-type": sel.product_type,
10884
+ className: "flex items-baseline gap-1.5 m-0",
10885
+ children: [
10886
+ /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground/80", children: [
10887
+ sel.quantity,
10888
+ "\\u00D7"
10889
+ ] }),
10890
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: name })
10891
+ ]
10892
+ },
10893
+ compId
10894
+ );
10594
10895
  })
10595
10896
  }
10596
10897
  ),
@@ -10599,6 +10900,7 @@ function CartLineItemRow({
10599
10900
  Price,
10600
10901
  {
10601
10902
  amount: lineTotal,
10903
+ currency,
10602
10904
  className: cn("text-sm font-semibold tabular-nums", classNames?.itemPrice)
10603
10905
  }
10604
10906
  ),
@@ -10636,9 +10938,11 @@ function CartSummary({
10636
10938
  isEmpty,
10637
10939
  isOptimistic: isOptimistic2,
10638
10940
  pendingOpCount,
10941
+ currency,
10639
10942
  removeItem,
10640
10943
  updateQuantity
10641
10944
  } = useCart();
10945
+ const displayCurrency = serverCart?.currency ?? currencyCode(currency);
10642
10946
  const deliveryFee = serverCart?.delivery_fee;
10643
10947
  const serviceCharge = serverCart?.service_charge;
10644
10948
  const totalDiscounts = serverCart?.total_discounts;
@@ -10670,6 +10974,7 @@ function CartSummary({
10670
10974
  item,
10671
10975
  onRemove: handleRemove,
10672
10976
  onQuantityChange: handleQuantityChange,
10977
+ currency: displayCurrency,
10673
10978
  classNames
10674
10979
  },
10675
10980
  item.id
@@ -10678,38 +10983,115 @@ function CartSummary({
10678
10983
  "div",
10679
10984
  {
10680
10985
  "data-cimplify-cart-totals": true,
10681
- className: cn("mt-6 pt-4 border-t border-border space-y-2 text-sm", classNames?.totals),
10986
+ className: cn(
10987
+ "mt-6 pt-4 border-t border-border space-y-2 text-sm",
10988
+ classNames?.totals
10989
+ ),
10682
10990
  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
- ] })
10991
+ /* @__PURE__ */ jsxs(
10992
+ "div",
10993
+ {
10994
+ "data-cimplify-cart-subtotal": true,
10995
+ className: cn("flex items-baseline justify-between", classNames?.subtotal),
10996
+ children: [
10997
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
10998
+ "Subtotal (",
10999
+ itemCount,
11000
+ " ",
11001
+ itemCount === 1 ? "item" : "items",
11002
+ ")"
11003
+ ] }),
11004
+ /* @__PURE__ */ jsx(Price, { amount: subtotal, currency: displayCurrency, className: "tabular-nums" })
11005
+ ]
11006
+ }
11007
+ ),
11008
+ totalDiscounts != null && parsePrice(totalDiscounts) > 0 && /* @__PURE__ */ jsxs(
11009
+ "div",
11010
+ {
11011
+ "data-cimplify-cart-discount": true,
11012
+ className: cn("flex items-baseline justify-between", classNames?.discount),
11013
+ children: [
11014
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Discount" }),
11015
+ /* @__PURE__ */ jsx(
11016
+ Price,
11017
+ {
11018
+ amount: totalDiscounts,
11019
+ currency: displayCurrency,
11020
+ prefix: "-",
11021
+ className: "tabular-nums text-primary"
11022
+ }
11023
+ )
11024
+ ]
11025
+ }
11026
+ ),
11027
+ deliveryFee != null && /* @__PURE__ */ jsxs(
11028
+ "div",
11029
+ {
11030
+ "data-cimplify-cart-delivery-fee": true,
11031
+ className: cn("flex items-baseline justify-between", classNames?.deliveryFee),
11032
+ children: [
11033
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Delivery" }),
11034
+ parsePrice(deliveryFee) > 0 ? /* @__PURE__ */ jsx(
11035
+ Price,
11036
+ {
11037
+ amount: deliveryFee,
11038
+ currency: displayCurrency,
11039
+ className: "tabular-nums"
11040
+ }
11041
+ ) : /* @__PURE__ */ jsx("span", { className: "font-medium text-primary", children: "Free" })
11042
+ ]
11043
+ }
11044
+ ),
11045
+ serviceCharge != null && parsePrice(serviceCharge) > 0 && /* @__PURE__ */ jsxs(
11046
+ "div",
11047
+ {
11048
+ "data-cimplify-cart-service-charge": true,
11049
+ className: cn("flex items-baseline justify-between", classNames?.serviceCharge),
11050
+ children: [
11051
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Service charge" }),
11052
+ /* @__PURE__ */ jsx(
11053
+ Price,
11054
+ {
11055
+ amount: serviceCharge,
11056
+ currency: displayCurrency,
11057
+ className: "tabular-nums"
11058
+ }
11059
+ )
11060
+ ]
11061
+ }
11062
+ ),
11063
+ /* @__PURE__ */ jsxs(
11064
+ "div",
11065
+ {
11066
+ "data-cimplify-cart-tax": true,
11067
+ className: cn("flex items-baseline justify-between", classNames?.tax),
11068
+ children: [
11069
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Tax" }),
11070
+ /* @__PURE__ */ jsx(Price, { amount: tax, currency: displayCurrency, className: "tabular-nums" })
11071
+ ]
11072
+ }
11073
+ ),
11074
+ /* @__PURE__ */ jsxs(
11075
+ "div",
11076
+ {
11077
+ "data-cimplify-cart-total": true,
11078
+ className: cn(
11079
+ "flex items-baseline justify-between pt-3 mt-1 border-t border-border",
11080
+ classNames?.total
11081
+ ),
11082
+ children: [
11083
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: "Total" }),
11084
+ /* @__PURE__ */ jsx(
11085
+ Price,
11086
+ {
11087
+ amount: total,
11088
+ currency: displayCurrency,
11089
+ className: "text-base font-bold tabular-nums"
11090
+ }
11091
+ )
11092
+ ]
11093
+ }
11094
+ )
10713
11095
  ]
10714
11096
  }
10715
11097
  ),
@@ -10794,12 +11176,14 @@ function SaleBadge({
10794
11176
  return null;
10795
11177
  }
10796
11178
  let percentage = null;
10797
- if (hasDeal && dealInfo.benefit_type === "percentage") {
11179
+ if (product.sale) {
11180
+ percentage = parsePrice(product.sale.percent_off);
11181
+ } else if (hasDeal && dealInfo.benefit_type === "percentage") {
10798
11182
  percentage = parsePrice(dealInfo.value);
10799
11183
  } else if (onSale) {
10800
11184
  percentage = getDiscountPercentage(product);
10801
11185
  }
10802
- const badgeText = label ?? dealInfo?.label ?? (percentage != null && percentage > 0 ? `${percentage}% off` : null);
11186
+ const badgeText = label ?? product.sale?.badge_text ?? dealInfo?.label ?? (percentage != null && percentage > 0 ? `${percentage}% off` : null);
10803
11187
  if (!badgeText) {
10804
11188
  return null;
10805
11189
  }