@cimplify/sdk 0.6.6 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -406,9 +406,11 @@ function CimplifyCheckout({
406
406
  const elementsRef = react.useRef(null);
407
407
  const activeCheckoutRef = react.useRef(null);
408
408
  const initialAppearanceRef = react.useRef(appearance);
409
+ const hasWarnedInlineAppearanceRef = react.useRef(false);
409
410
  const isMountedRef = react.useRef(true);
410
411
  const demoRunRef = react.useRef(0);
411
412
  const isDemoCheckout = demoMode ?? client.getPublicKey().trim().length === 0;
413
+ const isTestMode = client.isTestMode();
412
414
  const emitStatus = react.useCallback(
413
415
  (nextStatus, context = {}) => {
414
416
  setStatus(nextStatus);
@@ -422,6 +424,14 @@ function CimplifyCheckout({
422
424
  setOrderType(resolvedOrderTypes[0] || "pickup");
423
425
  }
424
426
  }, [resolvedOrderTypes, orderType]);
427
+ react.useEffect(() => {
428
+ if (appearance && appearance !== initialAppearanceRef.current && !hasWarnedInlineAppearanceRef.current) {
429
+ hasWarnedInlineAppearanceRef.current = true;
430
+ console.warn(
431
+ "[Cimplify] `appearance` prop reference changed after mount. Elements keep the initial appearance to avoid iframe remount. Memoize appearance with useMemo() to remove this warning."
432
+ );
433
+ }
434
+ }, [appearance]);
425
435
  react.useEffect(() => {
426
436
  let cancelled = false;
427
437
  async function bootstrap() {
@@ -628,6 +638,19 @@ function CimplifyCheckout({
628
638
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className, "data-cimplify-checkout": "", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-cimplify-error": "", style: { fontSize: "14px", color: "#b91c1c" }, children: errorMessage || "Unable to initialize checkout. Please refresh and try again." }) });
629
639
  }
630
640
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-cimplify-checkout": "", children: [
641
+ isTestMode && !isDemoCheckout && /* @__PURE__ */ jsxRuntime.jsx(
642
+ "p",
643
+ {
644
+ "data-cimplify-test-mode": "",
645
+ style: {
646
+ marginBottom: "10px",
647
+ fontSize: "12px",
648
+ fontWeight: 600,
649
+ color: "#92400e"
650
+ },
651
+ children: "Test mode - no real charges"
652
+ }
653
+ ),
631
654
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cimplify-section": "auth", children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: isDemoCheckout ? void 0 : authMountRef }) }),
632
655
  /* @__PURE__ */ jsxRuntime.jsx("div", { "data-cimplify-section": "order-type", style: { marginTop: "12px" }, children: /* @__PURE__ */ jsxRuntime.jsx(
633
656
  "div",
@@ -690,17 +713,88 @@ function CimplifyCheckout({
690
713
  }
691
714
 
692
715
  // src/types/common.ts
716
+ function money(value) {
717
+ return value;
718
+ }
719
+ function moneyFromNumber(value) {
720
+ return value.toFixed(2);
721
+ }
722
+ function currencyCode(value) {
723
+ return value;
724
+ }
693
725
  var ErrorCode = {
694
726
  // General
695
727
  UNKNOWN_ERROR: "UNKNOWN_ERROR",
696
728
  NETWORK_ERROR: "NETWORK_ERROR",
697
729
  TIMEOUT: "TIMEOUT",
730
+ UNAUTHORIZED: "UNAUTHORIZED",
698
731
  NOT_FOUND: "NOT_FOUND"};
732
+ var DOCS_ERROR_BASE_URL = "https://docs.cimplify.io/reference/error-codes";
733
+ function docsUrlForCode(code) {
734
+ return `${DOCS_ERROR_BASE_URL}#${code.toLowerCase().replace(/_/g, "-")}`;
735
+ }
736
+ var ERROR_SUGGESTIONS = {
737
+ UNKNOWN_ERROR: "An unexpected error occurred. Capture the request/response payload and retry with exponential backoff.",
738
+ NETWORK_ERROR: "Check the shopper's connection and retry. If this persists, inspect CORS, DNS, and API reachability.",
739
+ TIMEOUT: "The request exceeded the timeout. Retry once, then poll order status before charging again.",
740
+ UNAUTHORIZED: "Authentication is missing or expired. Ensure a valid access token is set and refresh the session if needed.",
741
+ FORBIDDEN: "The key/session lacks permission for this resource. Verify business ownership and API key scope.",
742
+ NOT_FOUND: "The requested resource does not exist or is not visible in this environment.",
743
+ VALIDATION_ERROR: "One or more fields are invalid. Validate required fields and enum values before retrying.",
744
+ CART_EMPTY: "The cart has no items. Redirect back to menu/catalogue and require at least one line item.",
745
+ CART_EXPIRED: "This cart is no longer active. Recreate a new cart and re-add shopper selections.",
746
+ CART_NOT_FOUND: "Cart could not be located. It may have expired or belongs to a different key/location.",
747
+ ITEM_UNAVAILABLE: "The selected item is unavailable at this location/time. Prompt the shopper to pick an alternative.",
748
+ VARIANT_NOT_FOUND: "The requested variant no longer exists. Refresh product data and require re-selection.",
749
+ VARIANT_OUT_OF_STOCK: "The selected variant is out of stock. Show in-stock variants and block checkout for this line.",
750
+ ADDON_REQUIRED: "A required add-on is missing. Ensure required modifier groups are completed before add-to-cart.",
751
+ ADDON_MAX_EXCEEDED: "Too many add-ons were selected. Enforce max selections client-side before submission.",
752
+ CHECKOUT_VALIDATION_FAILED: "Checkout payload failed validation. Verify customer, order type, and address fields are complete.",
753
+ DELIVERY_ADDRESS_REQUIRED: "Delivery orders require an address. Collect and pass address info before processing checkout.",
754
+ CUSTOMER_INFO_REQUIRED: "Customer details are required. Ensure name/email/phone are available before checkout.",
755
+ PAYMENT_FAILED: "Payment provider rejected or failed processing. Show retry/change-method options to the shopper.",
756
+ PAYMENT_CANCELLED: "Payment was cancelled by the shopper or provider flow. Allow a safe retry path.",
757
+ INSUFFICIENT_FUNDS: "Payment method has insufficient funds. Prompt shopper to use another method.",
758
+ CARD_DECLINED: "Card was declined. Ask shopper to retry or switch payment method.",
759
+ INVALID_OTP: "Authorization code is invalid. Let shopper re-enter OTP/PIN and retry.",
760
+ OTP_EXPIRED: "Authorization code expired. Request a new OTP and re-submit authorization.",
761
+ AUTHORIZATION_FAILED: "Additional payment authorization failed. Retry authorization or change payment method.",
762
+ PAYMENT_ACTION_NOT_COMPLETED: "Required payment action was not completed. Resume provider flow and poll for status.",
763
+ SLOT_UNAVAILABLE: "Selected schedule slot is unavailable. Refresh available slots and ask shopper to reselect.",
764
+ BOOKING_CONFLICT: "The requested booking conflicts with an existing reservation. Pick another slot/resource.",
765
+ SERVICE_NOT_FOUND: "Requested service no longer exists. Refresh service catalogue and retry selection.",
766
+ OUT_OF_STOCK: "Inventory is depleted for this item. Remove it or reduce quantity before checkout.",
767
+ INSUFFICIENT_QUANTITY: "Requested quantity exceeds available inventory. Reduce quantity and retry.",
768
+ BUSINESS_ID_REQUIRED: "Business context could not be resolved. Verify the public key and business bootstrap call.",
769
+ INVALID_CART: "Cart is invalid for checkout. Sync cart state, ensure items exist, then retry.",
770
+ ORDER_TYPE_REQUIRED: "Order type is required. Provide one of delivery, pickup, or dine_in before checkout.",
771
+ NO_PAYMENT_ELEMENT: "PaymentElement is required for processCheckout(). Mount it before triggering checkout.",
772
+ PAYMENT_NOT_MOUNTED: "PaymentElement iframe is not mounted. Mount it in the DOM before processCheckout().",
773
+ AUTH_INCOMPLETE: "AuthElement has not completed authentication. Wait for AUTHENTICATED before checkout.",
774
+ AUTH_LOST: "Session was cleared during checkout. Re-authenticate and restart checkout safely.",
775
+ ALREADY_PROCESSING: "Checkout is already in progress. Disable duplicate submits until completion.",
776
+ CHECKOUT_NOT_READY: "Checkout elements are still initializing. Wait for readiness before submit.",
777
+ CANCELLED: "Checkout was cancelled. Preserve cart state and allow shopper to retry.",
778
+ REQUEST_TIMEOUT: "Provider call timed out. Poll payment/order status before issuing another charge attempt.",
779
+ POPUP_BLOCKED: "Browser blocked provider popup. Ask shopper to enable popups and retry.",
780
+ FX_QUOTE_FAILED: "Failed to lock FX quote. Retry currency quote or fallback to base currency."
781
+ };
782
+ var ERROR_HINTS = Object.fromEntries(
783
+ Object.entries(ERROR_SUGGESTIONS).map(([code, suggestion]) => [
784
+ code,
785
+ {
786
+ docs_url: docsUrlForCode(code),
787
+ suggestion
788
+ }
789
+ ])
790
+ );
699
791
  var CimplifyError = class extends Error {
700
- constructor(code, message, retryable = false) {
792
+ constructor(code, message, retryable = false, docs_url, suggestion) {
701
793
  super(message);
702
794
  this.code = code;
703
795
  this.retryable = retryable;
796
+ this.docs_url = docs_url;
797
+ this.suggestion = suggestion;
704
798
  this.name = "CimplifyError";
705
799
  }
706
800
  /** User-friendly message safe to display */
@@ -708,6 +802,28 @@ var CimplifyError = class extends Error {
708
802
  return this.message;
709
803
  }
710
804
  };
805
+ function getErrorHint(code) {
806
+ return ERROR_HINTS[code];
807
+ }
808
+ function enrichError(error, options = {}) {
809
+ const hint = getErrorHint(error.code);
810
+ if (hint) {
811
+ if (!error.docs_url) {
812
+ error.docs_url = hint.docs_url;
813
+ }
814
+ if (!error.suggestion) {
815
+ error.suggestion = hint.suggestion;
816
+ }
817
+ } else if (!error.docs_url) {
818
+ error.docs_url = docsUrlForCode(error.code || ErrorCode.UNKNOWN_ERROR);
819
+ }
820
+ if (options.isTestMode && !error.message.includes("pk_test_")) {
821
+ error.message = `${error.message}
822
+
823
+ \u2139 Your API key is a test-mode key (pk_test_...). Verify test data/session before retrying.`;
824
+ }
825
+ return error;
826
+ }
711
827
 
712
828
  // src/types/result.ts
713
829
  function ok(value) {
@@ -717,13 +833,18 @@ function err(error) {
717
833
  return { ok: false, error };
718
834
  }
719
835
 
836
+ // src/query/builder.ts
837
+ function escapeQueryValue(value) {
838
+ return value.replace(/'/g, "\\'");
839
+ }
840
+
720
841
  // src/catalogue.ts
721
842
  function toCimplifyError(error) {
722
- if (error instanceof CimplifyError) return error;
843
+ if (error instanceof CimplifyError) return enrichError(error);
723
844
  if (error instanceof Error) {
724
- return new CimplifyError("UNKNOWN_ERROR", error.message, false);
845
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
725
846
  }
726
- return new CimplifyError("UNKNOWN_ERROR", String(error), false);
847
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
727
848
  }
728
849
  async function safe(promise) {
729
850
  try {
@@ -797,7 +918,7 @@ var CatalogueQueries = class {
797
918
  let query = "products";
798
919
  const filters = [];
799
920
  if (options?.category) {
800
- filters.push(`@.category_id=='${options.category}'`);
921
+ filters.push(`@.category_id=='${escapeQueryValue(options.category)}'`);
801
922
  }
802
923
  if (options?.featured !== void 0) {
803
924
  filters.push(`@.featured==${options.featured}`);
@@ -806,7 +927,7 @@ var CatalogueQueries = class {
806
927
  filters.push(`@.in_stock==${options.in_stock}`);
807
928
  }
808
929
  if (options?.search) {
809
- filters.push(`@.name contains '${options.search}'`);
930
+ filters.push(`@.name contains '${escapeQueryValue(options.search)}'`);
810
931
  }
811
932
  if (options?.min_price !== void 0) {
812
933
  filters.push(`@.price>=${options.min_price}`);
@@ -837,7 +958,9 @@ var CatalogueQueries = class {
837
958
  }
838
959
  async getProductBySlug(slug) {
839
960
  const filteredResult = await safe(
840
- this.client.query(`products[?(@.slug=='${slug}')]`)
961
+ this.client.query(
962
+ `products[?(@.slug=='${escapeQueryValue(slug)}')]`
963
+ )
841
964
  );
842
965
  if (!filteredResult.ok) return filteredResult;
843
966
  const exactMatch = findProductBySlug(filteredResult.value, slug);
@@ -889,7 +1012,7 @@ var CatalogueQueries = class {
889
1012
  }
890
1013
  async getCategoryBySlug(slug) {
891
1014
  const result = await safe(
892
- this.client.query(`categories[?(@.slug=='${slug}')]`)
1015
+ this.client.query(`categories[?(@.slug=='${escapeQueryValue(slug)}')]`)
893
1016
  );
894
1017
  if (!result.ok) return result;
895
1018
  if (!result.value.length) {
@@ -898,7 +1021,11 @@ var CatalogueQueries = class {
898
1021
  return ok(result.value[0]);
899
1022
  }
900
1023
  async getCategoryProducts(categoryId) {
901
- return safe(this.client.query(`products[?(@.category_id=='${categoryId}')]`));
1024
+ return safe(
1025
+ this.client.query(
1026
+ `products[?(@.category_id=='${escapeQueryValue(categoryId)}')]`
1027
+ )
1028
+ );
902
1029
  }
903
1030
  async getCollections() {
904
1031
  return safe(this.client.query("collections"));
@@ -908,7 +1035,9 @@ var CatalogueQueries = class {
908
1035
  }
909
1036
  async getCollectionBySlug(slug) {
910
1037
  const result = await safe(
911
- this.client.query(`collections[?(@.slug=='${slug}')]`)
1038
+ this.client.query(
1039
+ `collections[?(@.slug=='${escapeQueryValue(slug)}')]`
1040
+ )
912
1041
  );
913
1042
  if (!result.ok) return result;
914
1043
  if (!result.value.length) {
@@ -921,7 +1050,9 @@ var CatalogueQueries = class {
921
1050
  }
922
1051
  async searchCollections(query, limit = 20) {
923
1052
  return safe(
924
- this.client.query(`collections[?(@.name contains '${query}')]#limit(${limit})`)
1053
+ this.client.query(
1054
+ `collections[?(@.name contains '${escapeQueryValue(query)}')]#limit(${limit})`
1055
+ )
925
1056
  );
926
1057
  }
927
1058
  async getBundles() {
@@ -932,7 +1063,9 @@ var CatalogueQueries = class {
932
1063
  }
933
1064
  async getBundleBySlug(slug) {
934
1065
  const result = await safe(
935
- this.client.query(`bundles[?(@.slug=='${slug}')]`)
1066
+ this.client.query(
1067
+ `bundles[?(@.slug=='${escapeQueryValue(slug)}')]`
1068
+ )
936
1069
  );
937
1070
  if (!result.ok) return result;
938
1071
  if (!result.value.length) {
@@ -942,7 +1075,9 @@ var CatalogueQueries = class {
942
1075
  }
943
1076
  async searchBundles(query, limit = 20) {
944
1077
  return safe(
945
- this.client.query(`bundles[?(@.name contains '${query}')]#limit(${limit})`)
1078
+ this.client.query(
1079
+ `bundles[?(@.name contains '${escapeQueryValue(query)}')]#limit(${limit})`
1080
+ )
946
1081
  );
947
1082
  }
948
1083
  async getComposites(options) {
@@ -982,9 +1117,9 @@ var CatalogueQueries = class {
982
1117
  }
983
1118
  async search(query, options) {
984
1119
  const limit = options?.limit ?? 20;
985
- let searchQuery = `products[?(@.name contains '${query}')]`;
1120
+ let searchQuery = `products[?(@.name contains '${escapeQueryValue(query)}')]`;
986
1121
  if (options?.category) {
987
- searchQuery = `products[?(@.name contains '${query}' && @.category_id=='${options.category}')]`;
1122
+ searchQuery = `products[?(@.name contains '${escapeQueryValue(query)}' && @.category_id=='${escapeQueryValue(options.category)}')]`;
988
1123
  }
989
1124
  searchQuery += `#limit(${limit})`;
990
1125
  return safe(this.client.query(searchQuery));
@@ -1001,7 +1136,7 @@ var CatalogueQueries = class {
1001
1136
  async getMenu(options) {
1002
1137
  let query = "menu";
1003
1138
  if (options?.category) {
1004
- query = `menu[?(@.category=='${options.category}')]`;
1139
+ query = `menu[?(@.category=='${escapeQueryValue(options.category)}')]`;
1005
1140
  }
1006
1141
  if (options?.limit) {
1007
1142
  query += `#limit(${options.limit})`;
@@ -1018,11 +1153,11 @@ var CatalogueQueries = class {
1018
1153
 
1019
1154
  // src/cart.ts
1020
1155
  function toCimplifyError2(error) {
1021
- if (error instanceof CimplifyError) return error;
1156
+ if (error instanceof CimplifyError) return enrichError(error);
1022
1157
  if (error instanceof Error) {
1023
- return new CimplifyError("UNKNOWN_ERROR", error.message, false);
1158
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
1024
1159
  }
1025
- return new CimplifyError("UNKNOWN_ERROR", String(error), false);
1160
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
1026
1161
  }
1027
1162
  async function safe2(promise) {
1028
1163
  try {
@@ -1182,6 +1317,19 @@ var ORDER_MUTATION = {
1182
1317
  UPDATE_CUSTOMER: "order.update_order_customer"
1183
1318
  };
1184
1319
 
1320
+ // src/utils/price.ts
1321
+ function parsePrice(value) {
1322
+ if (value === void 0 || value === null) {
1323
+ return 0;
1324
+ }
1325
+ if (typeof value === "number") {
1326
+ return isNaN(value) ? 0 : value;
1327
+ }
1328
+ const cleaned = value.replace(/[^\d.-]/g, "");
1329
+ const parsed = parseFloat(cleaned);
1330
+ return isNaN(parsed) ? 0 : parsed;
1331
+ }
1332
+
1185
1333
  // src/utils/payment.ts
1186
1334
  var PAYMENT_SUCCESS_STATUSES = /* @__PURE__ */ new Set([
1187
1335
  "success",
@@ -1282,6 +1430,8 @@ function normalizeStatusResponse(response) {
1282
1430
  }
1283
1431
  const res = response;
1284
1432
  const normalizedStatus = normalizePaymentStatusValue(res.status ?? void 0);
1433
+ const normalizedAmount = typeof res.amount === "string" ? money(res.amount) : typeof res.amount === "number" && Number.isFinite(res.amount) ? moneyFromNumber(res.amount) : void 0;
1434
+ const normalizedCurrency = typeof res.currency === "string" && res.currency.trim().length > 0 ? currencyCode(res.currency) : void 0;
1285
1435
  const paidValue = res.paid === true;
1286
1436
  const derivedPaid = paidValue || [
1287
1437
  "success",
@@ -1294,8 +1444,8 @@ function normalizeStatusResponse(response) {
1294
1444
  return {
1295
1445
  status: normalizedStatus,
1296
1446
  paid: derivedPaid,
1297
- amount: res.amount,
1298
- currency: res.currency,
1447
+ amount: normalizedAmount,
1448
+ currency: normalizedCurrency,
1299
1449
  reference: res.reference,
1300
1450
  message: res.message || ""
1301
1451
  };
@@ -1861,11 +2011,11 @@ var CheckoutResolver = class {
1861
2011
 
1862
2012
  // src/checkout.ts
1863
2013
  function toCimplifyError3(error) {
1864
- if (error instanceof CimplifyError) return error;
2014
+ if (error instanceof CimplifyError) return enrichError(error);
1865
2015
  if (error instanceof Error) {
1866
- return new CimplifyError("UNKNOWN_ERROR", error.message, false);
2016
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
1867
2017
  }
1868
- return new CimplifyError("UNKNOWN_ERROR", String(error), false);
2018
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
1869
2019
  }
1870
2020
  async function safe3(promise) {
1871
2021
  try {
@@ -1987,14 +2137,17 @@ var CheckoutService = class {
1987
2137
  pay_currency: data.pay_currency,
1988
2138
  fx_quote_id: data.fx_quote_id
1989
2139
  };
1990
- const baseCurrency = (cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase();
2140
+ const baseCurrency = currencyCode(
2141
+ (cart.pricing.currency || checkoutData.pay_currency || "GHS").toUpperCase()
2142
+ );
1991
2143
  const payCurrency = data.pay_currency?.trim().toUpperCase();
2144
+ const payCurrencyCode = payCurrency ? currencyCode(payCurrency) : void 0;
1992
2145
  const cartTotalAmount = Number.parseFloat(cart.pricing.total_price || "0");
1993
- if (payCurrency && payCurrency !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
2146
+ if (payCurrencyCode && payCurrencyCode !== baseCurrency && !checkoutData.fx_quote_id && Number.isFinite(cartTotalAmount) && cartTotalAmount > 0) {
1994
2147
  const fxQuoteResult = await this.client.fx.lockQuote({
1995
2148
  from: baseCurrency,
1996
- to: payCurrency,
1997
- amount: cartTotalAmount
2149
+ to: payCurrencyCode,
2150
+ amount: cart.pricing.total_price
1998
2151
  });
1999
2152
  if (!fxQuoteResult.ok) {
2000
2153
  return ok(
@@ -2005,7 +2158,7 @@ var CheckoutService = class {
2005
2158
  )
2006
2159
  );
2007
2160
  }
2008
- checkoutData.pay_currency = payCurrency;
2161
+ checkoutData.pay_currency = payCurrencyCode;
2009
2162
  checkoutData.fx_quote_id = fxQuoteResult.value.id;
2010
2163
  }
2011
2164
  data.on_status_change?.("processing", {});
@@ -2041,11 +2194,11 @@ var CheckoutService = class {
2041
2194
 
2042
2195
  // src/orders.ts
2043
2196
  function toCimplifyError4(error) {
2044
- if (error instanceof CimplifyError) return error;
2197
+ if (error instanceof CimplifyError) return enrichError(error);
2045
2198
  if (error instanceof Error) {
2046
- return new CimplifyError("UNKNOWN_ERROR", error.message, false);
2199
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
2047
2200
  }
2048
- return new CimplifyError("UNKNOWN_ERROR", String(error), false);
2201
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
2049
2202
  }
2050
2203
  async function safe4(promise) {
2051
2204
  try {
@@ -2302,11 +2455,11 @@ var AuthService = class {
2302
2455
 
2303
2456
  // src/business.ts
2304
2457
  function toCimplifyError7(error) {
2305
- if (error instanceof CimplifyError) return error;
2458
+ if (error instanceof CimplifyError) return enrichError(error);
2306
2459
  if (error instanceof Error) {
2307
- return new CimplifyError("UNKNOWN_ERROR", error.message, false);
2460
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, error.message, false));
2308
2461
  }
2309
- return new CimplifyError("UNKNOWN_ERROR", String(error), false);
2462
+ return enrichError(new CimplifyError(ErrorCode.UNKNOWN_ERROR, String(error), false));
2310
2463
  }
2311
2464
  async function safe7(promise) {
2312
2465
  try {
@@ -2653,12 +2806,15 @@ var FxService = class {
2653
2806
 
2654
2807
  // src/elements.ts
2655
2808
  function toCheckoutError(code, message, recoverable) {
2809
+ const hint = getErrorHint(code);
2656
2810
  return {
2657
2811
  success: false,
2658
2812
  error: {
2659
2813
  code,
2660
2814
  message,
2661
- recoverable
2815
+ recoverable,
2816
+ docs_url: hint?.docs_url,
2817
+ suggestion: hint?.suggestion
2662
2818
  }
2663
2819
  };
2664
2820
  }
@@ -2699,6 +2855,7 @@ var CimplifyElements = class {
2699
2855
  this.paymentData = null;
2700
2856
  this.checkoutInProgress = false;
2701
2857
  this.activeCheckoutAbort = null;
2858
+ this.hasWarnedMissingAuthElement = false;
2702
2859
  this.businessIdResolvePromise = null;
2703
2860
  this.client = client;
2704
2861
  this.businessId = businessId ?? null;
@@ -2794,6 +2951,12 @@ var CimplifyElements = class {
2794
2951
  );
2795
2952
  }
2796
2953
  const authElement = this.elements.get(ELEMENT_TYPES.AUTH);
2954
+ if (!authElement && !this.hasWarnedMissingAuthElement) {
2955
+ this.hasWarnedMissingAuthElement = true;
2956
+ console.warn(
2957
+ "[Cimplify] processCheckout() called without AuthElement mounted. For best conversion and Link enrollment, mount <AuthElement> before checkout."
2958
+ );
2959
+ }
2797
2960
  if (authElement && !this.accessToken) {
2798
2961
  return toCheckoutError(
2799
2962
  "AUTH_INCOMPLETE",
@@ -3230,28 +3393,37 @@ function isRetryable(error) {
3230
3393
  }
3231
3394
  return false;
3232
3395
  }
3233
- function toNetworkError(error) {
3396
+ function toNetworkError(error, isTestMode) {
3234
3397
  if (error instanceof DOMException && error.name === "AbortError") {
3235
- return new CimplifyError(
3236
- ErrorCode.TIMEOUT,
3237
- "Request timed out. Please check your connection and try again.",
3238
- true
3398
+ return enrichError(
3399
+ new CimplifyError(
3400
+ ErrorCode.TIMEOUT,
3401
+ "Request timed out. Please check your connection and try again.",
3402
+ true
3403
+ ),
3404
+ { isTestMode }
3239
3405
  );
3240
3406
  }
3241
3407
  if (error instanceof TypeError && error.message.includes("fetch")) {
3242
- return new CimplifyError(
3243
- ErrorCode.NETWORK_ERROR,
3244
- "Network error. Please check your internet connection.",
3245
- true
3408
+ return enrichError(
3409
+ new CimplifyError(
3410
+ ErrorCode.NETWORK_ERROR,
3411
+ "Network error. Please check your internet connection.",
3412
+ true
3413
+ ),
3414
+ { isTestMode }
3246
3415
  );
3247
3416
  }
3248
3417
  if (error instanceof CimplifyError) {
3249
- return error;
3418
+ return enrichError(error, { isTestMode });
3250
3419
  }
3251
- return new CimplifyError(
3252
- ErrorCode.UNKNOWN_ERROR,
3253
- error instanceof Error ? error.message : "An unknown error occurred",
3254
- false
3420
+ return enrichError(
3421
+ new CimplifyError(
3422
+ ErrorCode.UNKNOWN_ERROR,
3423
+ error instanceof Error ? error.message : "An unknown error occurred",
3424
+ false
3425
+ ),
3426
+ { isTestMode }
3255
3427
  );
3256
3428
  }
3257
3429
  function deriveUrls() {
@@ -3313,6 +3485,9 @@ var CimplifyClient = class {
3313
3485
  getPublicKey() {
3314
3486
  return this.publicKey;
3315
3487
  }
3488
+ isTestMode() {
3489
+ return this.publicKey.trim().startsWith("pk_test_");
3490
+ }
3316
3491
  setAccessToken(token) {
3317
3492
  const previous = this.accessToken;
3318
3493
  this.accessToken = token;
@@ -3434,7 +3609,7 @@ var CimplifyClient = class {
3434
3609
  signal: controller.signal
3435
3610
  });
3436
3611
  clearTimeout(timeoutId);
3437
- if (response.ok || response.status >= 400 && response.status < 500) {
3612
+ if (response.ok) {
3438
3613
  this.hooks.onRequestSuccess?.({
3439
3614
  ...context,
3440
3615
  status: response.status,
@@ -3442,6 +3617,21 @@ var CimplifyClient = class {
3442
3617
  });
3443
3618
  return response;
3444
3619
  }
3620
+ if (response.status >= 400 && response.status < 500) {
3621
+ this.hooks.onRequestError?.({
3622
+ ...context,
3623
+ error: new CimplifyError(
3624
+ `HTTP_${response.status}`,
3625
+ `Request failed with status ${response.status}`,
3626
+ false
3627
+ ),
3628
+ status: response.status,
3629
+ durationMs: Date.now() - startTime,
3630
+ retryCount,
3631
+ retryable: false
3632
+ });
3633
+ return response;
3634
+ }
3445
3635
  if (response.status >= 500 && attempt < this.maxRetries) {
3446
3636
  retryCount++;
3447
3637
  const delay = this.retryDelay * Math.pow(2, attempt);
@@ -3454,15 +3644,22 @@ var CimplifyClient = class {
3454
3644
  await sleep(delay);
3455
3645
  continue;
3456
3646
  }
3457
- this.hooks.onRequestSuccess?.({
3647
+ this.hooks.onRequestError?.({
3458
3648
  ...context,
3649
+ error: new CimplifyError(
3650
+ "SERVER_ERROR",
3651
+ `Server error ${response.status} after ${retryCount} retries`,
3652
+ false
3653
+ ),
3459
3654
  status: response.status,
3460
- durationMs: Date.now() - startTime
3655
+ durationMs: Date.now() - startTime,
3656
+ retryCount,
3657
+ retryable: false
3461
3658
  });
3462
3659
  return response;
3463
3660
  } catch (error) {
3464
3661
  lastError = error;
3465
- const networkError = toNetworkError(error);
3662
+ const networkError = toNetworkError(error, this.isTestMode());
3466
3663
  const errorRetryable = isRetryable(error);
3467
3664
  if (!errorRetryable || attempt >= this.maxRetries) {
3468
3665
  this.hooks.onRequestError?.({
@@ -3485,7 +3682,7 @@ var CimplifyClient = class {
3485
3682
  await sleep(delay);
3486
3683
  }
3487
3684
  }
3488
- const finalError = toNetworkError(lastError);
3685
+ const finalError = toNetworkError(lastError, this.isTestMode());
3489
3686
  this.hooks.onRequestError?.({
3490
3687
  ...context,
3491
3688
  error: finalError,
@@ -3603,22 +3800,40 @@ var CimplifyClient = class {
3603
3800
  async handleRestResponse(response) {
3604
3801
  const json = await response.json();
3605
3802
  if (!response.ok) {
3606
- throw new CimplifyError(
3607
- json.error?.error_code || "API_ERROR",
3608
- json.error?.error_message || "An error occurred",
3609
- false
3803
+ const error = enrichError(
3804
+ new CimplifyError(
3805
+ json.error?.error_code || "API_ERROR",
3806
+ json.error?.error_message || "An error occurred",
3807
+ false
3808
+ ),
3809
+ { isTestMode: this.isTestMode() }
3610
3810
  );
3811
+ if (response.status === 401 || error.code === ErrorCode.UNAUTHORIZED) {
3812
+ console.warn(
3813
+ "[Cimplify] Received 401 Unauthorized. Access token may be missing/expired. Refresh authentication and retry the request."
3814
+ );
3815
+ }
3816
+ throw error;
3611
3817
  }
3612
3818
  return json.data;
3613
3819
  }
3614
3820
  async handleResponse(response) {
3615
3821
  const json = await response.json();
3616
3822
  if (!json.success || json.error) {
3617
- throw new CimplifyError(
3618
- json.error?.code || "UNKNOWN_ERROR",
3619
- json.error?.message || "An unknown error occurred",
3620
- json.error?.retryable || false
3823
+ const error = enrichError(
3824
+ new CimplifyError(
3825
+ json.error?.code || "UNKNOWN_ERROR",
3826
+ json.error?.message || "An unknown error occurred",
3827
+ json.error?.retryable || false
3828
+ ),
3829
+ { isTestMode: this.isTestMode() }
3621
3830
  );
3831
+ if (response.status === 401 || error.code === ErrorCode.UNAUTHORIZED) {
3832
+ console.warn(
3833
+ "[Cimplify] Received 401 Unauthorized. Access token may be missing/expired. Refresh authentication and retry the request."
3834
+ );
3835
+ }
3836
+ throw error;
3622
3837
  }
3623
3838
  return json.data;
3624
3839
  }
@@ -3805,6 +4020,9 @@ function CimplifyProvider({
3805
4020
  const nextLocations = locationsResult.ok && Array.isArray(locationsResult.value) ? locationsResult.value : [];
3806
4021
  const initialLocation = resolveInitialLocation(nextLocations);
3807
4022
  setBusiness(nextBusiness);
4023
+ if (nextBusiness?.id) {
4024
+ resolvedClient.setBusinessId(nextBusiness.id);
4025
+ }
3808
4026
  setLocations(nextLocations);
3809
4027
  if (initialLocation) {
3810
4028
  setCurrentLocationState(initialLocation);
@@ -3863,6 +4081,1809 @@ function useCimplify() {
3863
4081
  }
3864
4082
  return context;
3865
4083
  }
4084
+ function useOptionalCimplify() {
4085
+ return react.useContext(CimplifyContext);
4086
+ }
4087
+ var productsCache = /* @__PURE__ */ new Map();
4088
+ var productsInflight = /* @__PURE__ */ new Map();
4089
+ function buildProductsCacheKey(client, locationId, options) {
4090
+ return JSON.stringify({
4091
+ key: client.getPublicKey(),
4092
+ location_id: locationId || "__none__",
4093
+ options
4094
+ });
4095
+ }
4096
+ function useProducts(options = {}) {
4097
+ const context = useOptionalCimplify();
4098
+ const client = options.client ?? context?.client;
4099
+ if (!client) {
4100
+ throw new Error("useProducts must be used within CimplifyProvider or passed { client }.");
4101
+ }
4102
+ const enabled = options.enabled ?? true;
4103
+ const locationId = client.getLocationId();
4104
+ const previousLocationIdRef = react.useRef(locationId);
4105
+ const requestIdRef = react.useRef(0);
4106
+ const queryOptions = react.useMemo(
4107
+ () => ({
4108
+ category: options.category,
4109
+ collection: options.collection,
4110
+ search: options.search,
4111
+ featured: options.featured,
4112
+ limit: options.limit
4113
+ }),
4114
+ [options.category, options.collection, options.featured, options.limit, options.search]
4115
+ );
4116
+ const cacheKey = react.useMemo(
4117
+ () => buildProductsCacheKey(client, locationId, queryOptions),
4118
+ [client, locationId, queryOptions]
4119
+ );
4120
+ const cached = productsCache.get(cacheKey);
4121
+ const [products, setProducts] = react.useState(cached?.products ?? []);
4122
+ const [isLoading, setIsLoading] = react.useState(enabled && !cached);
4123
+ const [error, setError] = react.useState(null);
4124
+ react.useEffect(() => {
4125
+ if (previousLocationIdRef.current !== locationId) {
4126
+ productsCache.clear();
4127
+ productsInflight.clear();
4128
+ previousLocationIdRef.current = locationId;
4129
+ }
4130
+ }, [locationId]);
4131
+ const load = react.useCallback(
4132
+ async (force = false) => {
4133
+ if (!enabled) {
4134
+ setIsLoading(false);
4135
+ return;
4136
+ }
4137
+ const nextRequestId = ++requestIdRef.current;
4138
+ setError(null);
4139
+ if (!force) {
4140
+ const cacheEntry = productsCache.get(cacheKey);
4141
+ if (cacheEntry) {
4142
+ setProducts(cacheEntry.products);
4143
+ setIsLoading(false);
4144
+ return;
4145
+ }
4146
+ }
4147
+ setIsLoading(true);
4148
+ try {
4149
+ const existing = productsInflight.get(cacheKey);
4150
+ const promise = existing ?? (async () => {
4151
+ const result = await client.catalogue.getProducts(queryOptions);
4152
+ if (!result.ok) {
4153
+ throw result.error;
4154
+ }
4155
+ return result.value;
4156
+ })();
4157
+ if (!existing) {
4158
+ productsInflight.set(
4159
+ cacheKey,
4160
+ promise.finally(() => {
4161
+ productsInflight.delete(cacheKey);
4162
+ })
4163
+ );
4164
+ }
4165
+ const value = await promise;
4166
+ productsCache.set(cacheKey, { products: value });
4167
+ if (nextRequestId === requestIdRef.current) {
4168
+ setProducts(value);
4169
+ setError(null);
4170
+ }
4171
+ } catch (loadError) {
4172
+ if (nextRequestId === requestIdRef.current) {
4173
+ setError(loadError);
4174
+ }
4175
+ } finally {
4176
+ if (nextRequestId === requestIdRef.current) {
4177
+ setIsLoading(false);
4178
+ }
4179
+ }
4180
+ },
4181
+ [cacheKey, client, enabled, queryOptions]
4182
+ );
4183
+ react.useEffect(() => {
4184
+ void load(false);
4185
+ }, [load]);
4186
+ const refetch = react.useCallback(async () => {
4187
+ productsCache.delete(cacheKey);
4188
+ await load(true);
4189
+ }, [cacheKey, load]);
4190
+ return { products, isLoading, error, refetch };
4191
+ }
4192
+ var productCache = /* @__PURE__ */ new Map();
4193
+ var productInflight = /* @__PURE__ */ new Map();
4194
+ function isLikelySlug(value) {
4195
+ return /^[a-z0-9-]+$/.test(value);
4196
+ }
4197
+ function buildProductCacheKey(client, locationId, slugOrId) {
4198
+ return JSON.stringify({
4199
+ key: client.getPublicKey(),
4200
+ location_id: locationId || "__none__",
4201
+ slug_or_id: slugOrId
4202
+ });
4203
+ }
4204
+ function useProduct(slugOrId, options = {}) {
4205
+ const context = useOptionalCimplify();
4206
+ const client = options.client ?? context?.client;
4207
+ if (!client) {
4208
+ throw new Error("useProduct must be used within CimplifyProvider or passed { client }.");
4209
+ }
4210
+ const enabled = options.enabled ?? true;
4211
+ const locationId = client.getLocationId();
4212
+ const previousLocationIdRef = react.useRef(locationId);
4213
+ const requestIdRef = react.useRef(0);
4214
+ const normalizedSlugOrId = react.useMemo(() => (slugOrId || "").trim(), [slugOrId]);
4215
+ const cacheKey = react.useMemo(
4216
+ () => buildProductCacheKey(client, locationId, normalizedSlugOrId),
4217
+ [client, locationId, normalizedSlugOrId]
4218
+ );
4219
+ const cached = productCache.get(cacheKey);
4220
+ const [product, setProduct] = react.useState(cached?.product ?? null);
4221
+ const [isLoading, setIsLoading] = react.useState(
4222
+ enabled && normalizedSlugOrId.length > 0 && !cached
4223
+ );
4224
+ const [error, setError] = react.useState(null);
4225
+ react.useEffect(() => {
4226
+ if (previousLocationIdRef.current !== locationId) {
4227
+ productCache.clear();
4228
+ productInflight.clear();
4229
+ previousLocationIdRef.current = locationId;
4230
+ }
4231
+ }, [locationId]);
4232
+ const load = react.useCallback(
4233
+ async (force = false) => {
4234
+ if (!enabled || normalizedSlugOrId.length === 0) {
4235
+ setProduct(null);
4236
+ setIsLoading(false);
4237
+ return;
4238
+ }
4239
+ const nextRequestId = ++requestIdRef.current;
4240
+ setError(null);
4241
+ if (!force) {
4242
+ const cacheEntry = productCache.get(cacheKey);
4243
+ if (cacheEntry) {
4244
+ setProduct(cacheEntry.product);
4245
+ setIsLoading(false);
4246
+ return;
4247
+ }
4248
+ }
4249
+ setIsLoading(true);
4250
+ try {
4251
+ const existing = productInflight.get(cacheKey);
4252
+ const promise = existing ?? (async () => {
4253
+ const result = isLikelySlug(normalizedSlugOrId) ? await client.catalogue.getProductBySlug(normalizedSlugOrId) : await client.catalogue.getProduct(normalizedSlugOrId);
4254
+ if (!result.ok) {
4255
+ throw result.error;
4256
+ }
4257
+ return result.value;
4258
+ })();
4259
+ if (!existing) {
4260
+ productInflight.set(
4261
+ cacheKey,
4262
+ promise.finally(() => {
4263
+ productInflight.delete(cacheKey);
4264
+ })
4265
+ );
4266
+ }
4267
+ const value = await promise;
4268
+ productCache.set(cacheKey, { product: value });
4269
+ if (nextRequestId === requestIdRef.current) {
4270
+ setProduct(value);
4271
+ setError(null);
4272
+ }
4273
+ } catch (loadError) {
4274
+ if (nextRequestId === requestIdRef.current) {
4275
+ setError(loadError);
4276
+ }
4277
+ } finally {
4278
+ if (nextRequestId === requestIdRef.current) {
4279
+ setIsLoading(false);
4280
+ }
4281
+ }
4282
+ },
4283
+ [cacheKey, client, enabled, normalizedSlugOrId]
4284
+ );
4285
+ react.useEffect(() => {
4286
+ void load(false);
4287
+ }, [load]);
4288
+ const refetch = react.useCallback(async () => {
4289
+ productCache.delete(cacheKey);
4290
+ await load(true);
4291
+ }, [cacheKey, load]);
4292
+ return { product, isLoading, error, refetch };
4293
+ }
4294
+ var categoriesCache = /* @__PURE__ */ new Map();
4295
+ var categoriesInflight = /* @__PURE__ */ new Map();
4296
+ function buildCategoriesCacheKey(client) {
4297
+ return client.getPublicKey() || "__demo__";
4298
+ }
4299
+ function useCategories(options = {}) {
4300
+ const context = useOptionalCimplify();
4301
+ const client = options.client ?? context?.client;
4302
+ if (!client) {
4303
+ throw new Error("useCategories must be used within CimplifyProvider or passed { client }.");
4304
+ }
4305
+ const enabled = options.enabled ?? true;
4306
+ const cacheKey = react.useMemo(() => buildCategoriesCacheKey(client), [client]);
4307
+ const cached = categoriesCache.get(cacheKey);
4308
+ const [categories, setCategories] = react.useState(cached ?? []);
4309
+ const [isLoading, setIsLoading] = react.useState(enabled && !cached);
4310
+ const [error, setError] = react.useState(null);
4311
+ const load = react.useCallback(
4312
+ async (force = false) => {
4313
+ if (!enabled) {
4314
+ setIsLoading(false);
4315
+ return;
4316
+ }
4317
+ setError(null);
4318
+ if (!force) {
4319
+ const cachedCategories = categoriesCache.get(cacheKey);
4320
+ if (cachedCategories) {
4321
+ setCategories(cachedCategories);
4322
+ setIsLoading(false);
4323
+ return;
4324
+ }
4325
+ }
4326
+ setIsLoading(true);
4327
+ try {
4328
+ const existing = categoriesInflight.get(cacheKey);
4329
+ const promise = existing ?? (async () => {
4330
+ const result = await client.catalogue.getCategories();
4331
+ if (!result.ok) {
4332
+ throw result.error;
4333
+ }
4334
+ return result.value;
4335
+ })();
4336
+ if (!existing) {
4337
+ categoriesInflight.set(
4338
+ cacheKey,
4339
+ promise.finally(() => {
4340
+ categoriesInflight.delete(cacheKey);
4341
+ })
4342
+ );
4343
+ }
4344
+ const value = await promise;
4345
+ categoriesCache.set(cacheKey, value);
4346
+ setCategories(value);
4347
+ } catch (loadError) {
4348
+ setError(loadError);
4349
+ } finally {
4350
+ setIsLoading(false);
4351
+ }
4352
+ },
4353
+ [cacheKey, client, enabled]
4354
+ );
4355
+ react.useEffect(() => {
4356
+ void load(false);
4357
+ }, [load]);
4358
+ const refetch = react.useCallback(async () => {
4359
+ categoriesCache.delete(cacheKey);
4360
+ await load(true);
4361
+ }, [cacheKey, load]);
4362
+ return { categories, isLoading, error, refetch };
4363
+ }
4364
+ var CART_STORAGE_PREFIX = "cimplify:cart:v2";
4365
+ var DEFAULT_TAX_RATE = 0.08875;
4366
+ var cartStores = /* @__PURE__ */ new Map();
4367
+ function toNumber(value) {
4368
+ if (typeof value === "number" && Number.isFinite(value)) {
4369
+ return value;
4370
+ }
4371
+ if (typeof value === "string") {
4372
+ const parsed = Number.parseFloat(value);
4373
+ return Number.isFinite(parsed) ? parsed : 0;
4374
+ }
4375
+ return 0;
4376
+ }
4377
+ function roundMoney(value) {
4378
+ return Math.max(0, Number(value.toFixed(2)));
4379
+ }
4380
+ function clampQuantity(quantity) {
4381
+ if (!Number.isFinite(quantity)) {
4382
+ return 1;
4383
+ }
4384
+ return Math.max(1, Math.floor(quantity));
4385
+ }
4386
+ function normalizeOptionIds(value) {
4387
+ if (!value || value.length === 0) {
4388
+ return void 0;
4389
+ }
4390
+ const normalized = Array.from(
4391
+ new Set(
4392
+ value.map((entry) => entry.trim()).filter((entry) => entry.length > 0)
4393
+ )
4394
+ ).sort();
4395
+ return normalized.length > 0 ? normalized : void 0;
4396
+ }
4397
+ function buildLineKey(productId, options) {
4398
+ const parts = [productId];
4399
+ if (options.locationId) {
4400
+ parts.push(`l:${options.locationId}`);
4401
+ }
4402
+ if (options.variantId) {
4403
+ parts.push(`v:${options.variantId}`);
4404
+ }
4405
+ const addOnOptionIds = normalizeOptionIds(options.addOnOptionIds);
4406
+ if (addOnOptionIds && addOnOptionIds.length > 0) {
4407
+ parts.push(`a:${addOnOptionIds.join(",")}`);
4408
+ }
4409
+ if (options.bundleSelections && options.bundleSelections.length > 0) {
4410
+ parts.push(`b:${JSON.stringify(options.bundleSelections)}`);
4411
+ }
4412
+ if (options.compositeSelections && options.compositeSelections.length > 0) {
4413
+ parts.push(`c:${JSON.stringify(options.compositeSelections)}`);
4414
+ }
4415
+ if (options.quoteId) {
4416
+ parts.push(`q:${options.quoteId}`);
4417
+ }
4418
+ return parts.join("|");
4419
+ }
4420
+ function calculateLineSubtotal(item) {
4421
+ let unitPrice = parsePrice(item.product.default_price);
4422
+ if (item.variant?.price_adjustment) {
4423
+ unitPrice += parsePrice(item.variant.price_adjustment);
4424
+ }
4425
+ for (const option of item.addOnOptions || []) {
4426
+ if (option.default_price) {
4427
+ unitPrice += parsePrice(option.default_price);
4428
+ }
4429
+ }
4430
+ return roundMoney(unitPrice * item.quantity);
4431
+ }
4432
+ function calculateSummary(items) {
4433
+ const subtotal = roundMoney(items.reduce((sum, item) => sum + calculateLineSubtotal(item), 0));
4434
+ const tax = roundMoney(subtotal * DEFAULT_TAX_RATE);
4435
+ return {
4436
+ subtotal,
4437
+ tax,
4438
+ total: roundMoney(subtotal + tax)
4439
+ };
4440
+ }
4441
+ function toProductFromServerItem(item, businessId) {
4442
+ return {
4443
+ id: item.item_id,
4444
+ business_id: businessId,
4445
+ category_id: item.category_id,
4446
+ name: item.name,
4447
+ slug: item.item_id,
4448
+ description: item.description,
4449
+ image_url: item.image_url,
4450
+ default_price: item.base_price,
4451
+ product_type: "product",
4452
+ inventory_type: "none",
4453
+ variant_strategy: "fetch_all",
4454
+ is_active: item.is_available,
4455
+ created_at: item.created_at,
4456
+ updated_at: item.updated_at,
4457
+ metadata: {
4458
+ from_sdk: true
4459
+ }
4460
+ };
4461
+ }
4462
+ function mapServerCart(serverCart) {
4463
+ const items = serverCart.items.map((item) => {
4464
+ const variant = item.variant_info || item.variant_name ? {
4465
+ id: item.variant_info?.id || item.variant_id || "",
4466
+ name: item.variant_info?.name || item.variant_name || "Variant",
4467
+ price_adjustment: item.variant_info?.price_adjustment
4468
+ } : void 0;
4469
+ const quoteId = typeof item.metadata?.quote_id === "string" ? item.metadata.quote_id : void 0;
4470
+ return {
4471
+ id: item.id,
4472
+ product: toProductFromServerItem(item, serverCart.business_id),
4473
+ quantity: clampQuantity(item.quantity),
4474
+ locationId: serverCart.location_id,
4475
+ variantId: item.variant_id,
4476
+ variant,
4477
+ quoteId,
4478
+ addOnOptionIds: item.add_on_option_ids || void 0,
4479
+ addOnOptions: (item.add_on_options || []).map((option) => ({
4480
+ id: option.id,
4481
+ name: option.name,
4482
+ default_price: option.price
4483
+ })),
4484
+ bundleSelections: item.bundle_selections || void 0,
4485
+ compositeSelections: item.composite_selections || void 0,
4486
+ specialInstructions: item.special_instructions
4487
+ };
4488
+ });
4489
+ return {
4490
+ items,
4491
+ subtotal: roundMoney(toNumber(serverCart.pricing.subtotal)),
4492
+ tax: roundMoney(toNumber(serverCart.pricing.tax_amount)),
4493
+ total: roundMoney(toNumber(serverCart.pricing.total_price)),
4494
+ currency: serverCart.pricing.currency || "USD"
4495
+ };
4496
+ }
4497
+ function buildStorageKey(storeKey) {
4498
+ return `${CART_STORAGE_PREFIX}:${storeKey}`;
4499
+ }
4500
+ function createEmptySnapshot(currency) {
4501
+ return {
4502
+ items: [],
4503
+ subtotal: 0,
4504
+ tax: 0,
4505
+ total: 0,
4506
+ currency,
4507
+ isLoading: true
4508
+ };
4509
+ }
4510
+ function createCartStore(params) {
4511
+ const { client, storeKey, locationId, isDemoMode, currency } = params;
4512
+ const storageKey = buildStorageKey(storeKey);
4513
+ const listeners = /* @__PURE__ */ new Set();
4514
+ let snapshot = createEmptySnapshot(currency);
4515
+ let initialized = false;
4516
+ let initializePromise = null;
4517
+ function emit() {
4518
+ listeners.forEach((listener) => {
4519
+ listener();
4520
+ });
4521
+ }
4522
+ function setSnapshot(nextSnapshot) {
4523
+ snapshot = nextSnapshot;
4524
+ persistSnapshot();
4525
+ emit();
4526
+ }
4527
+ function updateSnapshot(updater) {
4528
+ setSnapshot(updater(snapshot));
4529
+ }
4530
+ function persistSnapshot() {
4531
+ if (typeof window === "undefined" || !window.localStorage) {
4532
+ return;
4533
+ }
4534
+ try {
4535
+ window.localStorage.setItem(
4536
+ storageKey,
4537
+ JSON.stringify({
4538
+ items: snapshot.items,
4539
+ subtotal: snapshot.subtotal,
4540
+ tax: snapshot.tax,
4541
+ total: snapshot.total,
4542
+ currency: snapshot.currency
4543
+ })
4544
+ );
4545
+ } catch {
4546
+ }
4547
+ }
4548
+ function hydrateFromStorage() {
4549
+ if (typeof window === "undefined" || !window.localStorage) {
4550
+ return;
4551
+ }
4552
+ try {
4553
+ const raw = window.localStorage.getItem(storageKey);
4554
+ if (!raw) {
4555
+ return;
4556
+ }
4557
+ const parsed = JSON.parse(raw);
4558
+ if (!parsed || !Array.isArray(parsed.items)) {
4559
+ return;
4560
+ }
4561
+ snapshot = {
4562
+ items: parsed.items,
4563
+ subtotal: toNumber(parsed.subtotal),
4564
+ tax: toNumber(parsed.tax),
4565
+ total: toNumber(parsed.total),
4566
+ currency: typeof parsed.currency === "string" && parsed.currency ? parsed.currency : currency,
4567
+ isLoading: !isDemoMode
4568
+ };
4569
+ emit();
4570
+ } catch {
4571
+ }
4572
+ }
4573
+ async function sync() {
4574
+ if (isDemoMode) {
4575
+ updateSnapshot((current) => ({
4576
+ ...current,
4577
+ isLoading: false
4578
+ }));
4579
+ return;
4580
+ }
4581
+ updateSnapshot((current) => ({
4582
+ ...current,
4583
+ isLoading: true
4584
+ }));
4585
+ const result = await client.cart.get();
4586
+ if (!result.ok) {
4587
+ updateSnapshot((current) => ({
4588
+ ...current,
4589
+ isLoading: false
4590
+ }));
4591
+ throw result.error;
4592
+ }
4593
+ const next = mapServerCart(result.value);
4594
+ setSnapshot({
4595
+ ...next,
4596
+ isLoading: false
4597
+ });
4598
+ }
4599
+ async function maybeResolveQuoteId(product, quantity, options) {
4600
+ if (options.quoteId) {
4601
+ return options.quoteId;
4602
+ }
4603
+ const addOnOptionIds = normalizeOptionIds(options.addOnOptionIds);
4604
+ const requiresQuote = Boolean(
4605
+ options.variantId || addOnOptionIds && addOnOptionIds.length > 0 || options.bundleSelections && options.bundleSelections.length > 0 || options.compositeSelections && options.compositeSelections.length > 0
4606
+ );
4607
+ if (!requiresQuote || isDemoMode) {
4608
+ return void 0;
4609
+ }
4610
+ const quoteResult = await client.catalogue.fetchQuote({
4611
+ product_id: product.id,
4612
+ quantity,
4613
+ location_id: options.locationId || locationId || void 0,
4614
+ variant_id: options.variantId,
4615
+ add_on_option_ids: addOnOptionIds,
4616
+ bundle_selections: options.bundleSelections,
4617
+ composite_selections: options.compositeSelections
4618
+ });
4619
+ if (!quoteResult.ok) {
4620
+ throw quoteResult.error;
4621
+ }
4622
+ return quoteResult.value.quote_id;
4623
+ }
4624
+ async function addItem(product, quantity = 1, options = {}) {
4625
+ const resolvedQuantity = clampQuantity(quantity);
4626
+ const normalizedOptions = {
4627
+ ...options,
4628
+ locationId: options.locationId || locationId || void 0,
4629
+ addOnOptionIds: normalizeOptionIds(options.addOnOptionIds)
4630
+ };
4631
+ const maybeProductWithVariants = product;
4632
+ if (Array.isArray(maybeProductWithVariants.variants) && maybeProductWithVariants.variants.length > 0 && !normalizedOptions.variantId) {
4633
+ console.warn(
4634
+ "[Cimplify] addItem() called without variantId for a product that has variants. Default variant pricing will be used."
4635
+ );
4636
+ }
4637
+ const previousSnapshot = snapshot;
4638
+ const lineKey = buildLineKey(product.id, normalizedOptions);
4639
+ const optimisticItem = {
4640
+ id: `tmp_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
4641
+ product,
4642
+ quantity: resolvedQuantity,
4643
+ locationId: normalizedOptions.locationId,
4644
+ variantId: normalizedOptions.variantId,
4645
+ variant: normalizedOptions.variant,
4646
+ quoteId: normalizedOptions.quoteId,
4647
+ addOnOptionIds: normalizedOptions.addOnOptionIds,
4648
+ addOnOptions: normalizedOptions.addOnOptions,
4649
+ bundleSelections: normalizedOptions.bundleSelections,
4650
+ compositeSelections: normalizedOptions.compositeSelections,
4651
+ specialInstructions: normalizedOptions.specialInstructions
4652
+ };
4653
+ updateSnapshot((current) => {
4654
+ const existingIndex = current.items.findIndex((item) => {
4655
+ const itemKey = buildLineKey(item.product.id, {
4656
+ locationId: item.locationId,
4657
+ variantId: item.variantId,
4658
+ quoteId: item.quoteId,
4659
+ addOnOptionIds: item.addOnOptionIds,
4660
+ bundleSelections: item.bundleSelections,
4661
+ compositeSelections: item.compositeSelections
4662
+ });
4663
+ return itemKey === lineKey;
4664
+ });
4665
+ const nextItems = [...current.items];
4666
+ if (existingIndex >= 0) {
4667
+ const existing = nextItems[existingIndex];
4668
+ nextItems[existingIndex] = {
4669
+ ...existing,
4670
+ quantity: existing.quantity + resolvedQuantity
4671
+ };
4672
+ } else {
4673
+ nextItems.push(optimisticItem);
4674
+ }
4675
+ const summary = calculateSummary(nextItems);
4676
+ return {
4677
+ ...current,
4678
+ items: nextItems,
4679
+ subtotal: summary.subtotal,
4680
+ tax: summary.tax,
4681
+ total: summary.total
4682
+ };
4683
+ });
4684
+ if (isDemoMode) {
4685
+ return;
4686
+ }
4687
+ try {
4688
+ const quoteId = await maybeResolveQuoteId(product, resolvedQuantity, normalizedOptions);
4689
+ const result = await client.cart.addItem({
4690
+ item_id: product.id,
4691
+ quantity: resolvedQuantity,
4692
+ variant_id: normalizedOptions.variantId,
4693
+ quote_id: quoteId,
4694
+ add_on_options: normalizedOptions.addOnOptionIds,
4695
+ special_instructions: normalizedOptions.specialInstructions,
4696
+ bundle_selections: normalizedOptions.bundleSelections,
4697
+ composite_selections: normalizedOptions.compositeSelections
4698
+ });
4699
+ if (!result.ok) {
4700
+ throw result.error;
4701
+ }
4702
+ await sync();
4703
+ } catch (error) {
4704
+ snapshot = previousSnapshot;
4705
+ persistSnapshot();
4706
+ emit();
4707
+ throw error;
4708
+ }
4709
+ }
4710
+ async function removeItem(itemId) {
4711
+ const previousSnapshot = snapshot;
4712
+ updateSnapshot((current) => {
4713
+ const nextItems = current.items.filter((item) => item.id !== itemId);
4714
+ const summary = calculateSummary(nextItems);
4715
+ return {
4716
+ ...current,
4717
+ items: nextItems,
4718
+ subtotal: summary.subtotal,
4719
+ tax: summary.tax,
4720
+ total: summary.total
4721
+ };
4722
+ });
4723
+ if (isDemoMode) {
4724
+ return;
4725
+ }
4726
+ try {
4727
+ const result = await client.cart.removeItem(itemId);
4728
+ if (!result.ok) {
4729
+ throw result.error;
4730
+ }
4731
+ await sync();
4732
+ } catch (error) {
4733
+ snapshot = previousSnapshot;
4734
+ persistSnapshot();
4735
+ emit();
4736
+ throw error;
4737
+ }
4738
+ }
4739
+ async function updateQuantity(itemId, quantity) {
4740
+ if (quantity <= 0) {
4741
+ await removeItem(itemId);
4742
+ return;
4743
+ }
4744
+ const resolvedQuantity = clampQuantity(quantity);
4745
+ const previousSnapshot = snapshot;
4746
+ updateSnapshot((current) => {
4747
+ const nextItems = current.items.map(
4748
+ (item) => item.id === itemId ? {
4749
+ ...item,
4750
+ quantity: resolvedQuantity
4751
+ } : item
4752
+ );
4753
+ const summary = calculateSummary(nextItems);
4754
+ return {
4755
+ ...current,
4756
+ items: nextItems,
4757
+ subtotal: summary.subtotal,
4758
+ tax: summary.tax,
4759
+ total: summary.total
4760
+ };
4761
+ });
4762
+ if (isDemoMode) {
4763
+ return;
4764
+ }
4765
+ try {
4766
+ const result = await client.cart.updateQuantity(itemId, resolvedQuantity);
4767
+ if (!result.ok) {
4768
+ throw result.error;
4769
+ }
4770
+ await sync();
4771
+ } catch (error) {
4772
+ snapshot = previousSnapshot;
4773
+ persistSnapshot();
4774
+ emit();
4775
+ throw error;
4776
+ }
4777
+ }
4778
+ async function clearCart() {
4779
+ const previousSnapshot = snapshot;
4780
+ updateSnapshot((current) => ({
4781
+ ...current,
4782
+ items: [],
4783
+ subtotal: 0,
4784
+ tax: 0,
4785
+ total: 0
4786
+ }));
4787
+ if (isDemoMode) {
4788
+ return;
4789
+ }
4790
+ try {
4791
+ const result = await client.cart.clear();
4792
+ if (!result.ok) {
4793
+ throw result.error;
4794
+ }
4795
+ await sync();
4796
+ } catch (error) {
4797
+ snapshot = previousSnapshot;
4798
+ persistSnapshot();
4799
+ emit();
4800
+ throw error;
4801
+ }
4802
+ }
4803
+ async function initialize() {
4804
+ if (initialized) {
4805
+ if (initializePromise) {
4806
+ await initializePromise;
4807
+ }
4808
+ return;
4809
+ }
4810
+ initialized = true;
4811
+ hydrateFromStorage();
4812
+ if (isDemoMode) {
4813
+ updateSnapshot((current) => ({
4814
+ ...current,
4815
+ isLoading: false
4816
+ }));
4817
+ return;
4818
+ }
4819
+ initializePromise = sync().catch(() => {
4820
+ updateSnapshot((current) => ({
4821
+ ...current,
4822
+ isLoading: false
4823
+ }));
4824
+ });
4825
+ await initializePromise;
4826
+ initializePromise = null;
4827
+ }
4828
+ return {
4829
+ subscribe(listener) {
4830
+ listeners.add(listener);
4831
+ return () => {
4832
+ listeners.delete(listener);
4833
+ };
4834
+ },
4835
+ getSnapshot() {
4836
+ return snapshot;
4837
+ },
4838
+ initialize,
4839
+ addItem,
4840
+ removeItem,
4841
+ updateQuantity,
4842
+ clearCart,
4843
+ sync
4844
+ };
4845
+ }
4846
+ function getStoreKey(client, locationId, isDemoMode) {
4847
+ return [
4848
+ client.getPublicKey() || "__demo__",
4849
+ locationId || "__no_location__",
4850
+ isDemoMode ? "demo" : "live"
4851
+ ].join(":");
4852
+ }
4853
+ function getOrCreateStore(params) {
4854
+ const { client, locationId, isDemoMode, currency } = params;
4855
+ const storeKey = getStoreKey(client, locationId, isDemoMode);
4856
+ const existing = cartStores.get(storeKey);
4857
+ if (existing) {
4858
+ return existing;
4859
+ }
4860
+ const created = createCartStore({
4861
+ client,
4862
+ storeKey,
4863
+ locationId,
4864
+ isDemoMode,
4865
+ currency
4866
+ });
4867
+ cartStores.set(storeKey, created);
4868
+ return created;
4869
+ }
4870
+ function useCart(options = {}) {
4871
+ const context = useOptionalCimplify();
4872
+ const client = options.client ?? context?.client;
4873
+ if (!client) {
4874
+ throw new Error("useCart must be used within CimplifyProvider or passed { client }.");
4875
+ }
4876
+ const locationId = options.locationId ?? client.getLocationId();
4877
+ const isDemoMode = options.demoMode ?? context?.isDemoMode ?? client.getPublicKey().trim().length === 0;
4878
+ const currency = options.currency ?? context?.currency ?? "USD";
4879
+ const store = react.useMemo(
4880
+ () => getOrCreateStore({
4881
+ client,
4882
+ locationId,
4883
+ isDemoMode,
4884
+ currency
4885
+ }),
4886
+ [client, currency, isDemoMode, locationId]
4887
+ );
4888
+ const snapshot = react.useSyncExternalStore(
4889
+ store.subscribe,
4890
+ store.getSnapshot,
4891
+ store.getSnapshot
4892
+ );
4893
+ react.useEffect(() => {
4894
+ void store.initialize();
4895
+ }, [store]);
4896
+ const addItem = react.useCallback(
4897
+ async (product, quantity, addOptions) => {
4898
+ await store.addItem(product, quantity, addOptions);
4899
+ },
4900
+ [store]
4901
+ );
4902
+ const removeItem = react.useCallback(
4903
+ async (itemId) => {
4904
+ await store.removeItem(itemId);
4905
+ },
4906
+ [store]
4907
+ );
4908
+ const updateQuantity = react.useCallback(
4909
+ async (itemId, quantity) => {
4910
+ await store.updateQuantity(itemId, quantity);
4911
+ },
4912
+ [store]
4913
+ );
4914
+ const clearCart = react.useCallback(async () => {
4915
+ await store.clearCart();
4916
+ }, [store]);
4917
+ const sync = react.useCallback(async () => {
4918
+ try {
4919
+ await store.sync();
4920
+ } catch (syncError) {
4921
+ throw syncError;
4922
+ }
4923
+ }, [store]);
4924
+ const itemCount = react.useMemo(
4925
+ () => snapshot.items.reduce((sum, item) => sum + item.quantity, 0),
4926
+ [snapshot.items]
4927
+ );
4928
+ return {
4929
+ items: snapshot.items,
4930
+ itemCount,
4931
+ subtotal: snapshot.subtotal,
4932
+ tax: snapshot.tax,
4933
+ total: snapshot.total,
4934
+ currency: snapshot.currency,
4935
+ isEmpty: itemCount === 0,
4936
+ isLoading: snapshot.isLoading,
4937
+ addItem,
4938
+ removeItem,
4939
+ updateQuantity,
4940
+ clearCart,
4941
+ sync
4942
+ };
4943
+ }
4944
+ var orderCache = /* @__PURE__ */ new Map();
4945
+ var orderInflight = /* @__PURE__ */ new Map();
4946
+ function buildOrderCacheKey(client, orderId) {
4947
+ return `${client.getPublicKey() || "__demo__"}:${orderId}`;
4948
+ }
4949
+ function useOrder(orderId, options = {}) {
4950
+ const context = useOptionalCimplify();
4951
+ const client = options.client ?? context?.client;
4952
+ if (!client) {
4953
+ throw new Error("useOrder must be used within CimplifyProvider or passed { client }.");
4954
+ }
4955
+ const normalizedOrderId = react.useMemo(() => (orderId || "").trim(), [orderId]);
4956
+ const enabled = options.enabled ?? true;
4957
+ const poll = options.poll ?? false;
4958
+ const pollInterval = options.pollInterval ?? 5e3;
4959
+ const requestIdRef = react.useRef(0);
4960
+ const cacheKey = react.useMemo(
4961
+ () => buildOrderCacheKey(client, normalizedOrderId),
4962
+ [client, normalizedOrderId]
4963
+ );
4964
+ const cached = orderCache.get(cacheKey);
4965
+ const [order, setOrder] = react.useState(cached?.order ?? null);
4966
+ const [isLoading, setIsLoading] = react.useState(
4967
+ enabled && normalizedOrderId.length > 0 && !cached
4968
+ );
4969
+ const [error, setError] = react.useState(null);
4970
+ const load = react.useCallback(
4971
+ async (force = false) => {
4972
+ if (!enabled || normalizedOrderId.length === 0) {
4973
+ setOrder(null);
4974
+ setIsLoading(false);
4975
+ return;
4976
+ }
4977
+ const nextRequestId = ++requestIdRef.current;
4978
+ setError(null);
4979
+ if (!force) {
4980
+ const cacheEntry = orderCache.get(cacheKey);
4981
+ if (cacheEntry) {
4982
+ setOrder(cacheEntry.order);
4983
+ setIsLoading(false);
4984
+ return;
4985
+ }
4986
+ }
4987
+ setIsLoading(true);
4988
+ try {
4989
+ const existing = orderInflight.get(cacheKey);
4990
+ const promise = existing ?? (async () => {
4991
+ const result = await client.orders.get(normalizedOrderId);
4992
+ if (!result.ok) {
4993
+ throw result.error;
4994
+ }
4995
+ return result.value;
4996
+ })();
4997
+ if (!existing) {
4998
+ orderInflight.set(
4999
+ cacheKey,
5000
+ promise.finally(() => {
5001
+ orderInflight.delete(cacheKey);
5002
+ })
5003
+ );
5004
+ }
5005
+ const value = await promise;
5006
+ orderCache.set(cacheKey, { order: value });
5007
+ if (nextRequestId === requestIdRef.current) {
5008
+ setOrder(value);
5009
+ setError(null);
5010
+ }
5011
+ } catch (loadError) {
5012
+ if (nextRequestId === requestIdRef.current) {
5013
+ setError(loadError);
5014
+ }
5015
+ } finally {
5016
+ if (nextRequestId === requestIdRef.current) {
5017
+ setIsLoading(false);
5018
+ }
5019
+ }
5020
+ },
5021
+ [cacheKey, client, enabled, normalizedOrderId]
5022
+ );
5023
+ react.useEffect(() => {
5024
+ void load(false);
5025
+ }, [load]);
5026
+ react.useEffect(() => {
5027
+ if (!poll || !enabled || normalizedOrderId.length === 0) {
5028
+ return;
5029
+ }
5030
+ const timer = window.setInterval(() => {
5031
+ void load(true);
5032
+ }, Math.max(1e3, pollInterval));
5033
+ return () => {
5034
+ window.clearInterval(timer);
5035
+ };
5036
+ }, [enabled, load, normalizedOrderId.length, poll, pollInterval]);
5037
+ const refetch = react.useCallback(async () => {
5038
+ orderCache.delete(cacheKey);
5039
+ await load(true);
5040
+ }, [cacheKey, load]);
5041
+ return { order, isLoading, error, refetch };
5042
+ }
5043
+ var LOCATION_STORAGE_KEY2 = "cimplify_location_id";
5044
+ function readStoredLocationId() {
5045
+ if (typeof window === "undefined" || !window.localStorage) {
5046
+ return null;
5047
+ }
5048
+ const value = window.localStorage.getItem(LOCATION_STORAGE_KEY2);
5049
+ if (!value) {
5050
+ return null;
5051
+ }
5052
+ const normalized = value.trim();
5053
+ return normalized.length > 0 ? normalized : null;
5054
+ }
5055
+ function writeStoredLocationId(locationId) {
5056
+ if (typeof window === "undefined" || !window.localStorage) {
5057
+ return;
5058
+ }
5059
+ if (!locationId) {
5060
+ window.localStorage.removeItem(LOCATION_STORAGE_KEY2);
5061
+ return;
5062
+ }
5063
+ window.localStorage.setItem(LOCATION_STORAGE_KEY2, locationId);
5064
+ }
5065
+ function resolveLocation(locations, preferredId) {
5066
+ if (locations.length === 0) {
5067
+ return null;
5068
+ }
5069
+ if (preferredId) {
5070
+ const found = locations.find((location) => location.id === preferredId);
5071
+ if (found) {
5072
+ return found;
5073
+ }
5074
+ }
5075
+ return locations[0];
5076
+ }
5077
+ function useLocations(options = {}) {
5078
+ const context = useOptionalCimplify();
5079
+ if (context && (!options.client || context.client === options.client)) {
5080
+ return {
5081
+ locations: context.locations,
5082
+ currentLocation: context.currentLocation,
5083
+ setCurrentLocation: context.setCurrentLocation,
5084
+ isLoading: !context.isReady
5085
+ };
5086
+ }
5087
+ const client = options.client;
5088
+ const [locations, setLocations] = react.useState([]);
5089
+ const [currentLocation, setCurrentLocationState] = react.useState(null);
5090
+ const [isLoading, setIsLoading] = react.useState(true);
5091
+ const setCurrentLocation = react.useCallback(
5092
+ (location) => {
5093
+ setCurrentLocationState(location);
5094
+ if (client) {
5095
+ client.setLocationId(location.id);
5096
+ }
5097
+ writeStoredLocationId(location.id);
5098
+ },
5099
+ [client]
5100
+ );
5101
+ react.useEffect(() => {
5102
+ if (!client) {
5103
+ setLocations([]);
5104
+ setCurrentLocationState(null);
5105
+ setIsLoading(false);
5106
+ return;
5107
+ }
5108
+ const activeClient = client;
5109
+ let cancelled = false;
5110
+ async function loadLocations() {
5111
+ setIsLoading(true);
5112
+ const result = await activeClient.business.getLocations();
5113
+ if (cancelled) {
5114
+ return;
5115
+ }
5116
+ if (!result.ok) {
5117
+ setLocations([]);
5118
+ setCurrentLocationState(null);
5119
+ setIsLoading(false);
5120
+ return;
5121
+ }
5122
+ const nextLocations = result.value;
5123
+ const preferredId = activeClient.getLocationId() ?? readStoredLocationId();
5124
+ const resolved = resolveLocation(nextLocations, preferredId);
5125
+ setLocations(nextLocations);
5126
+ setCurrentLocationState(resolved);
5127
+ activeClient.setLocationId(resolved?.id || null);
5128
+ writeStoredLocationId(resolved?.id || null);
5129
+ setIsLoading(false);
5130
+ }
5131
+ void loadLocations();
5132
+ return () => {
5133
+ cancelled = true;
5134
+ };
5135
+ }, [client]);
5136
+ return {
5137
+ locations,
5138
+ currentLocation,
5139
+ setCurrentLocation,
5140
+ isLoading
5141
+ };
5142
+ }
5143
+ var collectionsCache = /* @__PURE__ */ new Map();
5144
+ var collectionsInflight = /* @__PURE__ */ new Map();
5145
+ function buildCollectionsCacheKey(client, locationId) {
5146
+ return JSON.stringify({
5147
+ key: client.getPublicKey(),
5148
+ location_id: locationId || "__none__"
5149
+ });
5150
+ }
5151
+ function useCollections(options = {}) {
5152
+ const context = useOptionalCimplify();
5153
+ const client = options.client ?? context?.client;
5154
+ if (!client) {
5155
+ throw new Error("useCollections must be used within CimplifyProvider or passed { client }.");
5156
+ }
5157
+ const enabled = options.enabled ?? true;
5158
+ const locationId = client.getLocationId();
5159
+ const previousLocationIdRef = react.useRef(locationId);
5160
+ const requestIdRef = react.useRef(0);
5161
+ const cacheKey = react.useMemo(
5162
+ () => buildCollectionsCacheKey(client, locationId),
5163
+ [client, locationId]
5164
+ );
5165
+ const cached = collectionsCache.get(cacheKey);
5166
+ const [collections, setCollections] = react.useState(cached?.collections ?? []);
5167
+ const [isLoading, setIsLoading] = react.useState(enabled && !cached);
5168
+ const [error, setError] = react.useState(null);
5169
+ react.useEffect(() => {
5170
+ if (previousLocationIdRef.current !== locationId) {
5171
+ collectionsCache.clear();
5172
+ collectionsInflight.clear();
5173
+ previousLocationIdRef.current = locationId;
5174
+ }
5175
+ }, [locationId]);
5176
+ const load = react.useCallback(
5177
+ async (force = false) => {
5178
+ if (!enabled) {
5179
+ setIsLoading(false);
5180
+ return;
5181
+ }
5182
+ const nextRequestId = ++requestIdRef.current;
5183
+ setError(null);
5184
+ if (!force) {
5185
+ const cacheEntry = collectionsCache.get(cacheKey);
5186
+ if (cacheEntry) {
5187
+ setCollections(cacheEntry.collections);
5188
+ setIsLoading(false);
5189
+ return;
5190
+ }
5191
+ }
5192
+ setIsLoading(true);
5193
+ try {
5194
+ const existing = collectionsInflight.get(cacheKey);
5195
+ const promise = existing ?? (async () => {
5196
+ const result = await client.catalogue.getCollections();
5197
+ if (!result.ok) {
5198
+ throw result.error;
5199
+ }
5200
+ return result.value;
5201
+ })();
5202
+ if (!existing) {
5203
+ collectionsInflight.set(cacheKey, promise);
5204
+ promise.finally(() => {
5205
+ collectionsInflight.delete(cacheKey);
5206
+ }).catch(() => void 0);
5207
+ }
5208
+ const value = await promise;
5209
+ collectionsCache.set(cacheKey, { collections: value });
5210
+ if (nextRequestId === requestIdRef.current) {
5211
+ setCollections(value);
5212
+ setError(null);
5213
+ }
5214
+ } catch (loadError) {
5215
+ if (nextRequestId === requestIdRef.current) {
5216
+ setError(loadError);
5217
+ }
5218
+ } finally {
5219
+ if (nextRequestId === requestIdRef.current) {
5220
+ setIsLoading(false);
5221
+ }
5222
+ }
5223
+ },
5224
+ [cacheKey, client, enabled]
5225
+ );
5226
+ react.useEffect(() => {
5227
+ void load(false);
5228
+ }, [load]);
5229
+ const refetch = react.useCallback(async () => {
5230
+ collectionsCache.delete(cacheKey);
5231
+ await load(true);
5232
+ }, [cacheKey, load]);
5233
+ return { collections, isLoading, error, refetch };
5234
+ }
5235
+ var collectionCache = /* @__PURE__ */ new Map();
5236
+ var collectionInflight = /* @__PURE__ */ new Map();
5237
+ function isLikelySlug2(value) {
5238
+ return /^[a-z0-9-]+$/.test(value);
5239
+ }
5240
+ function buildCollectionCacheKey(client, locationId, idOrSlug) {
5241
+ return JSON.stringify({
5242
+ key: client.getPublicKey(),
5243
+ location_id: locationId || "__none__",
5244
+ collection: idOrSlug
5245
+ });
5246
+ }
5247
+ function useCollection(idOrSlug, options = {}) {
5248
+ const context = useOptionalCimplify();
5249
+ const client = options.client ?? context?.client;
5250
+ if (!client) {
5251
+ throw new Error("useCollection must be used within CimplifyProvider or passed { client }.");
5252
+ }
5253
+ const enabled = options.enabled ?? true;
5254
+ const locationId = client.getLocationId();
5255
+ const previousLocationIdRef = react.useRef(locationId);
5256
+ const requestIdRef = react.useRef(0);
5257
+ const normalizedIdOrSlug = react.useMemo(() => (idOrSlug || "").trim(), [idOrSlug]);
5258
+ const cacheKey = react.useMemo(
5259
+ () => buildCollectionCacheKey(client, locationId, normalizedIdOrSlug),
5260
+ [client, locationId, normalizedIdOrSlug]
5261
+ );
5262
+ const cached = collectionCache.get(cacheKey);
5263
+ const [collection, setCollection] = react.useState(cached?.collection ?? null);
5264
+ const [products, setProducts] = react.useState(cached?.products ?? []);
5265
+ const [isLoading, setIsLoading] = react.useState(
5266
+ enabled && normalizedIdOrSlug.length > 0 && !cached
5267
+ );
5268
+ const [error, setError] = react.useState(null);
5269
+ react.useEffect(() => {
5270
+ if (previousLocationIdRef.current !== locationId) {
5271
+ collectionCache.clear();
5272
+ collectionInflight.clear();
5273
+ previousLocationIdRef.current = locationId;
5274
+ }
5275
+ }, [locationId]);
5276
+ const load = react.useCallback(
5277
+ async (force = false) => {
5278
+ if (!enabled || normalizedIdOrSlug.length === 0) {
5279
+ setCollection(null);
5280
+ setProducts([]);
5281
+ setIsLoading(false);
5282
+ return;
5283
+ }
5284
+ const nextRequestId = ++requestIdRef.current;
5285
+ setError(null);
5286
+ if (!force) {
5287
+ const cacheEntry = collectionCache.get(cacheKey);
5288
+ if (cacheEntry) {
5289
+ setCollection(cacheEntry.collection);
5290
+ setProducts(cacheEntry.products);
5291
+ setIsLoading(false);
5292
+ return;
5293
+ }
5294
+ }
5295
+ setIsLoading(true);
5296
+ try {
5297
+ const existing = collectionInflight.get(cacheKey);
5298
+ const promise = existing ?? (async () => {
5299
+ const collectionResult = isLikelySlug2(normalizedIdOrSlug) ? await client.catalogue.getCollectionBySlug(normalizedIdOrSlug) : await client.catalogue.getCollection(normalizedIdOrSlug);
5300
+ if (!collectionResult.ok) {
5301
+ throw collectionResult.error;
5302
+ }
5303
+ const productsResult = await client.catalogue.getCollectionProducts(
5304
+ collectionResult.value.id
5305
+ );
5306
+ if (!productsResult.ok) {
5307
+ throw productsResult.error;
5308
+ }
5309
+ return {
5310
+ collection: collectionResult.value,
5311
+ products: productsResult.value
5312
+ };
5313
+ })();
5314
+ if (!existing) {
5315
+ collectionInflight.set(cacheKey, promise);
5316
+ promise.finally(() => {
5317
+ collectionInflight.delete(cacheKey);
5318
+ }).catch(() => void 0);
5319
+ }
5320
+ const value = await promise;
5321
+ collectionCache.set(cacheKey, value);
5322
+ if (nextRequestId === requestIdRef.current) {
5323
+ setCollection(value.collection);
5324
+ setProducts(value.products);
5325
+ setError(null);
5326
+ }
5327
+ } catch (loadError) {
5328
+ if (nextRequestId === requestIdRef.current) {
5329
+ setError(loadError);
5330
+ }
5331
+ } finally {
5332
+ if (nextRequestId === requestIdRef.current) {
5333
+ setIsLoading(false);
5334
+ }
5335
+ }
5336
+ },
5337
+ [cacheKey, client, enabled, normalizedIdOrSlug]
5338
+ );
5339
+ react.useEffect(() => {
5340
+ void load(false);
5341
+ }, [load]);
5342
+ const refetch = react.useCallback(async () => {
5343
+ collectionCache.delete(cacheKey);
5344
+ await load(true);
5345
+ }, [cacheKey, load]);
5346
+ return { collection, products, isLoading, error, refetch };
5347
+ }
5348
+ var bundleCache = /* @__PURE__ */ new Map();
5349
+ var bundleInflight = /* @__PURE__ */ new Map();
5350
+ function isLikelySlug3(value) {
5351
+ return /^[a-z0-9-]+$/.test(value);
5352
+ }
5353
+ function buildBundleCacheKey(client, locationId, idOrSlug) {
5354
+ return JSON.stringify({
5355
+ key: client.getPublicKey(),
5356
+ location_id: locationId || "__none__",
5357
+ bundle: idOrSlug
5358
+ });
5359
+ }
5360
+ function useBundle(idOrSlug, options = {}) {
5361
+ const context = useOptionalCimplify();
5362
+ const client = options.client ?? context?.client;
5363
+ if (!client) {
5364
+ throw new Error("useBundle must be used within CimplifyProvider or passed { client }.");
5365
+ }
5366
+ const enabled = options.enabled ?? true;
5367
+ const locationId = client.getLocationId();
5368
+ const previousLocationIdRef = react.useRef(locationId);
5369
+ const requestIdRef = react.useRef(0);
5370
+ const normalizedIdOrSlug = react.useMemo(() => (idOrSlug || "").trim(), [idOrSlug]);
5371
+ const cacheKey = react.useMemo(
5372
+ () => buildBundleCacheKey(client, locationId, normalizedIdOrSlug),
5373
+ [client, locationId, normalizedIdOrSlug]
5374
+ );
5375
+ const cached = bundleCache.get(cacheKey);
5376
+ const [bundle, setBundle] = react.useState(cached?.bundle ?? null);
5377
+ const [isLoading, setIsLoading] = react.useState(
5378
+ enabled && normalizedIdOrSlug.length > 0 && !cached
5379
+ );
5380
+ const [error, setError] = react.useState(null);
5381
+ react.useEffect(() => {
5382
+ if (previousLocationIdRef.current !== locationId) {
5383
+ bundleCache.clear();
5384
+ bundleInflight.clear();
5385
+ previousLocationIdRef.current = locationId;
5386
+ }
5387
+ }, [locationId]);
5388
+ const load = react.useCallback(
5389
+ async (force = false) => {
5390
+ if (!enabled || normalizedIdOrSlug.length === 0) {
5391
+ setBundle(null);
5392
+ setIsLoading(false);
5393
+ return;
5394
+ }
5395
+ const nextRequestId = ++requestIdRef.current;
5396
+ setError(null);
5397
+ if (!force) {
5398
+ const cacheEntry = bundleCache.get(cacheKey);
5399
+ if (cacheEntry) {
5400
+ setBundle(cacheEntry.bundle);
5401
+ setIsLoading(false);
5402
+ return;
5403
+ }
5404
+ }
5405
+ setIsLoading(true);
5406
+ try {
5407
+ const existing = bundleInflight.get(cacheKey);
5408
+ const promise = existing ?? (async () => {
5409
+ const result = isLikelySlug3(normalizedIdOrSlug) ? await client.catalogue.getBundleBySlug(normalizedIdOrSlug) : await client.catalogue.getBundle(normalizedIdOrSlug);
5410
+ if (!result.ok) {
5411
+ throw result.error;
5412
+ }
5413
+ return result.value;
5414
+ })();
5415
+ if (!existing) {
5416
+ bundleInflight.set(cacheKey, promise);
5417
+ promise.finally(() => {
5418
+ bundleInflight.delete(cacheKey);
5419
+ }).catch(() => void 0);
5420
+ }
5421
+ const value = await promise;
5422
+ bundleCache.set(cacheKey, { bundle: value });
5423
+ if (nextRequestId === requestIdRef.current) {
5424
+ setBundle(value);
5425
+ setError(null);
5426
+ }
5427
+ } catch (loadError) {
5428
+ if (nextRequestId === requestIdRef.current) {
5429
+ setError(loadError);
5430
+ }
5431
+ } finally {
5432
+ if (nextRequestId === requestIdRef.current) {
5433
+ setIsLoading(false);
5434
+ }
5435
+ }
5436
+ },
5437
+ [cacheKey, client, enabled, normalizedIdOrSlug]
5438
+ );
5439
+ react.useEffect(() => {
5440
+ void load(false);
5441
+ }, [load]);
5442
+ const refetch = react.useCallback(async () => {
5443
+ bundleCache.delete(cacheKey);
5444
+ await load(true);
5445
+ }, [cacheKey, load]);
5446
+ return { bundle, isLoading, error, refetch };
5447
+ }
5448
+ var compositeCache = /* @__PURE__ */ new Map();
5449
+ var compositeInflight = /* @__PURE__ */ new Map();
5450
+ function shouldFetchByProductId(idOrProductId, byProductId) {
5451
+ if (typeof byProductId === "boolean") {
5452
+ return byProductId;
5453
+ }
5454
+ return idOrProductId.startsWith("prod_");
5455
+ }
5456
+ function buildCompositeCacheKey(client, locationId, idOrProductId, byProductId) {
5457
+ return JSON.stringify({
5458
+ key: client.getPublicKey(),
5459
+ location_id: locationId || "__none__",
5460
+ composite: idOrProductId,
5461
+ by_product_id: byProductId
5462
+ });
5463
+ }
5464
+ function useComposite(idOrProductId, options = {}) {
5465
+ const context = useOptionalCimplify();
5466
+ const client = options.client ?? context?.client;
5467
+ if (!client) {
5468
+ throw new Error("useComposite must be used within CimplifyProvider or passed { client }.");
5469
+ }
5470
+ const enabled = options.enabled ?? true;
5471
+ const locationId = client.getLocationId();
5472
+ const previousLocationIdRef = react.useRef(locationId);
5473
+ const requestIdRef = react.useRef(0);
5474
+ const priceRequestIdRef = react.useRef(0);
5475
+ const normalizedIdOrProductId = react.useMemo(
5476
+ () => (idOrProductId || "").trim(),
5477
+ [idOrProductId]
5478
+ );
5479
+ const byProductId = react.useMemo(
5480
+ () => shouldFetchByProductId(normalizedIdOrProductId, options.byProductId),
5481
+ [normalizedIdOrProductId, options.byProductId]
5482
+ );
5483
+ const cacheKey = react.useMemo(
5484
+ () => buildCompositeCacheKey(client, locationId, normalizedIdOrProductId, byProductId),
5485
+ [byProductId, client, locationId, normalizedIdOrProductId]
5486
+ );
5487
+ const cached = compositeCache.get(cacheKey);
5488
+ const [composite, setComposite] = react.useState(cached?.composite ?? null);
5489
+ const [isLoading, setIsLoading] = react.useState(
5490
+ enabled && normalizedIdOrProductId.length > 0 && !cached
5491
+ );
5492
+ const [error, setError] = react.useState(null);
5493
+ const [priceResult, setPriceResult] = react.useState(null);
5494
+ const [isPriceLoading, setIsPriceLoading] = react.useState(false);
5495
+ react.useEffect(() => {
5496
+ if (previousLocationIdRef.current !== locationId) {
5497
+ compositeCache.clear();
5498
+ compositeInflight.clear();
5499
+ previousLocationIdRef.current = locationId;
5500
+ }
5501
+ }, [locationId]);
5502
+ const load = react.useCallback(
5503
+ async (force = false) => {
5504
+ if (!enabled || normalizedIdOrProductId.length === 0) {
5505
+ setComposite(null);
5506
+ setPriceResult(null);
5507
+ setIsLoading(false);
5508
+ return;
5509
+ }
5510
+ const nextRequestId = ++requestIdRef.current;
5511
+ setError(null);
5512
+ if (!force) {
5513
+ const cacheEntry = compositeCache.get(cacheKey);
5514
+ if (cacheEntry) {
5515
+ setComposite(cacheEntry.composite);
5516
+ setIsLoading(false);
5517
+ return;
5518
+ }
5519
+ }
5520
+ setIsLoading(true);
5521
+ try {
5522
+ const existing = compositeInflight.get(cacheKey);
5523
+ const promise = existing ?? (async () => {
5524
+ const result = byProductId ? await client.catalogue.getCompositeByProductId(normalizedIdOrProductId) : await client.catalogue.getComposite(normalizedIdOrProductId);
5525
+ if (!result.ok) {
5526
+ throw result.error;
5527
+ }
5528
+ return result.value;
5529
+ })();
5530
+ if (!existing) {
5531
+ compositeInflight.set(cacheKey, promise);
5532
+ promise.finally(() => {
5533
+ compositeInflight.delete(cacheKey);
5534
+ }).catch(() => void 0);
5535
+ }
5536
+ const value = await promise;
5537
+ compositeCache.set(cacheKey, { composite: value });
5538
+ if (nextRequestId === requestIdRef.current) {
5539
+ setComposite(value);
5540
+ setPriceResult(null);
5541
+ setError(null);
5542
+ }
5543
+ } catch (loadError) {
5544
+ if (nextRequestId === requestIdRef.current) {
5545
+ setError(loadError);
5546
+ }
5547
+ } finally {
5548
+ if (nextRequestId === requestIdRef.current) {
5549
+ setIsLoading(false);
5550
+ }
5551
+ }
5552
+ },
5553
+ [byProductId, cacheKey, client, enabled, normalizedIdOrProductId]
5554
+ );
5555
+ react.useEffect(() => {
5556
+ void load(false);
5557
+ }, [load]);
5558
+ const calculatePrice = react.useCallback(
5559
+ async (selections, overrideLocationId) => {
5560
+ if (!composite) {
5561
+ return null;
5562
+ }
5563
+ const nextRequestId = ++priceRequestIdRef.current;
5564
+ setIsPriceLoading(true);
5565
+ try {
5566
+ const result = await client.catalogue.calculateCompositePrice(
5567
+ composite.id,
5568
+ selections,
5569
+ overrideLocationId
5570
+ );
5571
+ if (!result.ok) {
5572
+ throw result.error;
5573
+ }
5574
+ if (nextRequestId === priceRequestIdRef.current) {
5575
+ setPriceResult(result.value);
5576
+ setError(null);
5577
+ }
5578
+ return result.value;
5579
+ } catch (loadError) {
5580
+ if (nextRequestId === priceRequestIdRef.current) {
5581
+ setError(loadError);
5582
+ }
5583
+ return null;
5584
+ } finally {
5585
+ if (nextRequestId === priceRequestIdRef.current) {
5586
+ setIsPriceLoading(false);
5587
+ }
5588
+ }
5589
+ },
5590
+ [client, composite]
5591
+ );
5592
+ const refetch = react.useCallback(async () => {
5593
+ compositeCache.delete(cacheKey);
5594
+ await load(true);
5595
+ }, [cacheKey, load]);
5596
+ return { composite, isLoading, error, refetch, calculatePrice, priceResult, isPriceLoading };
5597
+ }
5598
+ function useSearch(options = {}) {
5599
+ const context = useOptionalCimplify();
5600
+ const client = options.client ?? context?.client;
5601
+ if (!client) {
5602
+ throw new Error("useSearch must be used within CimplifyProvider or passed { client }.");
5603
+ }
5604
+ const minLength = Math.max(0, options.minLength ?? 2);
5605
+ const debounceMs = Math.max(0, options.debounceMs ?? 300);
5606
+ const limit = Math.max(1, options.limit ?? 20);
5607
+ const [query, setQueryState] = react.useState("");
5608
+ const [results, setResults] = react.useState([]);
5609
+ const [isLoading, setIsLoading] = react.useState(false);
5610
+ const [error, setError] = react.useState(null);
5611
+ const requestIdRef = react.useRef(0);
5612
+ const timerRef = react.useRef(null);
5613
+ react.useEffect(() => {
5614
+ if (timerRef.current) {
5615
+ clearTimeout(timerRef.current);
5616
+ timerRef.current = null;
5617
+ }
5618
+ const trimmedQuery = query.trim();
5619
+ if (trimmedQuery.length < minLength) {
5620
+ setResults([]);
5621
+ setError(null);
5622
+ setIsLoading(false);
5623
+ return;
5624
+ }
5625
+ const nextRequestId = ++requestIdRef.current;
5626
+ setError(null);
5627
+ setIsLoading(true);
5628
+ timerRef.current = setTimeout(() => {
5629
+ void (async () => {
5630
+ try {
5631
+ const result = await client.catalogue.searchProducts(trimmedQuery, {
5632
+ limit,
5633
+ category: options.category
5634
+ });
5635
+ if (!result.ok) {
5636
+ throw result.error;
5637
+ }
5638
+ if (nextRequestId === requestIdRef.current) {
5639
+ setResults(result.value);
5640
+ setError(null);
5641
+ }
5642
+ } catch (loadError) {
5643
+ if (nextRequestId === requestIdRef.current) {
5644
+ setError(loadError);
5645
+ }
5646
+ } finally {
5647
+ if (nextRequestId === requestIdRef.current) {
5648
+ setIsLoading(false);
5649
+ }
5650
+ }
5651
+ })();
5652
+ }, debounceMs);
5653
+ return () => {
5654
+ if (timerRef.current) {
5655
+ clearTimeout(timerRef.current);
5656
+ timerRef.current = null;
5657
+ }
5658
+ };
5659
+ }, [client, debounceMs, limit, minLength, options.category, query]);
5660
+ const setQuery = react.useCallback((nextQuery) => {
5661
+ setQueryState(nextQuery);
5662
+ }, []);
5663
+ const clear = react.useCallback(() => {
5664
+ requestIdRef.current += 1;
5665
+ if (timerRef.current) {
5666
+ clearTimeout(timerRef.current);
5667
+ timerRef.current = null;
5668
+ }
5669
+ setQueryState("");
5670
+ setResults([]);
5671
+ setError(null);
5672
+ setIsLoading(false);
5673
+ }, []);
5674
+ return { results, isLoading, error, query, setQuery, clear };
5675
+ }
5676
+ var quoteCache = /* @__PURE__ */ new Map();
5677
+ var quoteInflight = /* @__PURE__ */ new Map();
5678
+ function buildQuoteCacheKey(client, locationId, inputSignature) {
5679
+ return JSON.stringify({
5680
+ key: client.getPublicKey(),
5681
+ location_id: locationId || "__none__",
5682
+ input: inputSignature
5683
+ });
5684
+ }
5685
+ function isQuoteExpired(quote) {
5686
+ if (!quote?.expires_at) {
5687
+ return false;
5688
+ }
5689
+ const expiresAt = Date.parse(quote.expires_at);
5690
+ if (!Number.isFinite(expiresAt)) {
5691
+ return false;
5692
+ }
5693
+ return expiresAt <= Date.now();
5694
+ }
5695
+ function normalizeInput(input, fallbackLocationId) {
5696
+ const productId = input.productId.trim();
5697
+ const variantId = input.variantId?.trim();
5698
+ const locationId = input.locationId?.trim() || fallbackLocationId || void 0;
5699
+ return {
5700
+ product_id: productId,
5701
+ variant_id: variantId && variantId.length > 0 ? variantId : void 0,
5702
+ location_id: locationId && locationId.length > 0 ? locationId : void 0,
5703
+ quantity: input.quantity,
5704
+ add_on_option_ids: input.addOnOptionIds,
5705
+ bundle_selections: input.bundleSelections,
5706
+ composite_selections: input.compositeSelections
5707
+ };
5708
+ }
5709
+ function useQuote(input, options = {}) {
5710
+ const context = useOptionalCimplify();
5711
+ const client = options.client ?? context?.client;
5712
+ if (!client) {
5713
+ throw new Error("useQuote must be used within CimplifyProvider or passed { client }.");
5714
+ }
5715
+ const enabled = options.enabled ?? true;
5716
+ const autoRefresh = options.autoRefresh ?? true;
5717
+ const refreshBeforeExpiryMs = Math.max(0, options.refreshBeforeExpiryMs ?? 3e4);
5718
+ const locationId = client.getLocationId();
5719
+ const requestIdRef = react.useRef(0);
5720
+ const refreshTimerRef = react.useRef(null);
5721
+ const expiryTimerRef = react.useRef(null);
5722
+ const inputSignature = react.useMemo(() => JSON.stringify(input ?? null), [input]);
5723
+ const normalizedInput = react.useMemo(() => {
5724
+ if (!input) {
5725
+ return null;
5726
+ }
5727
+ const normalized = normalizeInput(input, locationId);
5728
+ return normalized.product_id.length > 0 ? normalized : null;
5729
+ }, [inputSignature, locationId]);
5730
+ const cacheKey = react.useMemo(
5731
+ () => buildQuoteCacheKey(client, locationId, inputSignature),
5732
+ [client, inputSignature, locationId]
5733
+ );
5734
+ const cached = quoteCache.get(cacheKey);
5735
+ const [quote, setQuote] = react.useState(cached?.quote ?? null);
5736
+ const [isLoading, setIsLoading] = react.useState(enabled && normalizedInput !== null && !cached);
5737
+ const [error, setError] = react.useState(null);
5738
+ const [isExpired, setIsExpired] = react.useState(isQuoteExpired(cached?.quote ?? null));
5739
+ const [messages, setMessages] = react.useState(cached?.quote?.ui_messages ?? []);
5740
+ const load = react.useCallback(
5741
+ async (force = false) => {
5742
+ if (!enabled || !normalizedInput) {
5743
+ setQuote(null);
5744
+ setMessages([]);
5745
+ setIsExpired(false);
5746
+ setError(null);
5747
+ setIsLoading(false);
5748
+ return;
5749
+ }
5750
+ const nextRequestId = ++requestIdRef.current;
5751
+ setError(null);
5752
+ if (!force) {
5753
+ const cacheEntry = quoteCache.get(cacheKey);
5754
+ if (cacheEntry) {
5755
+ setQuote(cacheEntry.quote);
5756
+ setMessages(cacheEntry.quote?.ui_messages ?? []);
5757
+ setIsExpired(isQuoteExpired(cacheEntry.quote));
5758
+ setIsLoading(false);
5759
+ return;
5760
+ }
5761
+ }
5762
+ setIsLoading(true);
5763
+ try {
5764
+ const existing = quoteInflight.get(cacheKey);
5765
+ const promise = existing ?? (async () => {
5766
+ const result = await client.catalogue.fetchQuote(normalizedInput);
5767
+ if (!result.ok) {
5768
+ throw result.error;
5769
+ }
5770
+ return result.value;
5771
+ })();
5772
+ if (!existing) {
5773
+ quoteInflight.set(cacheKey, promise);
5774
+ promise.finally(() => {
5775
+ quoteInflight.delete(cacheKey);
5776
+ }).catch(() => void 0);
5777
+ }
5778
+ const value = await promise;
5779
+ quoteCache.set(cacheKey, { quote: value });
5780
+ if (nextRequestId === requestIdRef.current) {
5781
+ setQuote(value);
5782
+ setMessages(value.ui_messages ?? []);
5783
+ setIsExpired(isQuoteExpired(value));
5784
+ setError(null);
5785
+ }
5786
+ } catch (loadError) {
5787
+ if (nextRequestId === requestIdRef.current) {
5788
+ setError(loadError);
5789
+ }
5790
+ } finally {
5791
+ if (nextRequestId === requestIdRef.current) {
5792
+ setIsLoading(false);
5793
+ }
5794
+ }
5795
+ },
5796
+ [cacheKey, client, enabled, normalizedInput]
5797
+ );
5798
+ react.useEffect(() => {
5799
+ void load(false);
5800
+ }, [load]);
5801
+ const refresh = react.useCallback(async () => {
5802
+ if (!enabled || !normalizedInput) {
5803
+ return;
5804
+ }
5805
+ if (!quote?.quote_id) {
5806
+ await load(true);
5807
+ return;
5808
+ }
5809
+ const nextRequestId = ++requestIdRef.current;
5810
+ setError(null);
5811
+ setIsLoading(true);
5812
+ try {
5813
+ const result = await client.catalogue.refreshQuote({
5814
+ quote_id: quote.quote_id,
5815
+ ...normalizedInput
5816
+ });
5817
+ if (!result.ok) {
5818
+ throw result.error;
5819
+ }
5820
+ const refreshed = result.value.quote;
5821
+ quoteCache.set(cacheKey, { quote: refreshed });
5822
+ if (nextRequestId === requestIdRef.current) {
5823
+ setQuote(refreshed);
5824
+ setMessages(refreshed.ui_messages ?? []);
5825
+ setIsExpired(isQuoteExpired(refreshed));
5826
+ setError(null);
5827
+ }
5828
+ } catch (refreshError) {
5829
+ if (nextRequestId === requestIdRef.current) {
5830
+ setError(refreshError);
5831
+ }
5832
+ } finally {
5833
+ if (nextRequestId === requestIdRef.current) {
5834
+ setIsLoading(false);
5835
+ }
5836
+ }
5837
+ }, [cacheKey, client, enabled, load, normalizedInput, quote]);
5838
+ react.useEffect(() => {
5839
+ if (expiryTimerRef.current) {
5840
+ clearTimeout(expiryTimerRef.current);
5841
+ expiryTimerRef.current = null;
5842
+ }
5843
+ const expiresAt = quote?.expires_at ? Date.parse(quote.expires_at) : NaN;
5844
+ if (!Number.isFinite(expiresAt)) {
5845
+ setIsExpired(false);
5846
+ return;
5847
+ }
5848
+ const expired = expiresAt <= Date.now();
5849
+ setIsExpired(expired);
5850
+ if (!expired) {
5851
+ expiryTimerRef.current = setTimeout(() => {
5852
+ setIsExpired(true);
5853
+ }, Math.max(0, expiresAt - Date.now()));
5854
+ }
5855
+ return () => {
5856
+ if (expiryTimerRef.current) {
5857
+ clearTimeout(expiryTimerRef.current);
5858
+ expiryTimerRef.current = null;
5859
+ }
5860
+ };
5861
+ }, [quote?.expires_at, quote?.quote_id]);
5862
+ react.useEffect(() => {
5863
+ if (refreshTimerRef.current) {
5864
+ clearTimeout(refreshTimerRef.current);
5865
+ refreshTimerRef.current = null;
5866
+ }
5867
+ if (!autoRefresh || !enabled || !quote?.expires_at) {
5868
+ return;
5869
+ }
5870
+ const expiresAt = Date.parse(quote.expires_at);
5871
+ if (!Number.isFinite(expiresAt)) {
5872
+ return;
5873
+ }
5874
+ const delay = Math.max(0, expiresAt - Date.now() - refreshBeforeExpiryMs);
5875
+ refreshTimerRef.current = setTimeout(() => {
5876
+ void refresh();
5877
+ }, delay);
5878
+ return () => {
5879
+ if (refreshTimerRef.current) {
5880
+ clearTimeout(refreshTimerRef.current);
5881
+ refreshTimerRef.current = null;
5882
+ }
5883
+ };
5884
+ }, [autoRefresh, enabled, quote?.expires_at, quote?.quote_id, refresh, refreshBeforeExpiryMs]);
5885
+ return { quote, isLoading, error, refresh, isExpired, messages };
5886
+ }
3866
5887
  var ElementsContext = react.createContext({
3867
5888
  elements: null,
3868
5889
  isReady: false
@@ -4032,7 +6053,20 @@ exports.CimplifyProvider = CimplifyProvider;
4032
6053
  exports.ElementsProvider = ElementsProvider;
4033
6054
  exports.PaymentElement = PaymentElement;
4034
6055
  exports.useAds = useAds;
6056
+ exports.useBundle = useBundle;
6057
+ exports.useCart = useCart;
6058
+ exports.useCategories = useCategories;
4035
6059
  exports.useCheckout = useCheckout;
4036
6060
  exports.useCimplify = useCimplify;
6061
+ exports.useCollection = useCollection;
6062
+ exports.useCollections = useCollections;
6063
+ exports.useComposite = useComposite;
4037
6064
  exports.useElements = useElements;
4038
6065
  exports.useElementsReady = useElementsReady;
6066
+ exports.useLocations = useLocations;
6067
+ exports.useOptionalCimplify = useOptionalCimplify;
6068
+ exports.useOrder = useOrder;
6069
+ exports.useProduct = useProduct;
6070
+ exports.useProducts = useProducts;
6071
+ exports.useQuote = useQuote;
6072
+ exports.useSearch = useSearch;