@lime-bundles/react 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -18,151 +18,153 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
20
  // src/index.ts
21
- var src_exports = {};
22
- __export(src_exports, {
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BundleParseError: () => import_core8.BundleParseError,
23
24
  FixedBundle: () => FixedBundle,
24
25
  MixMatchBundle: () => MixMatchBundle,
26
+ StorefrontApiError: () => import_core8.StorefrontApiError,
25
27
  VolumeBundle: () => VolumeBundle,
28
+ fetchBundleData: () => fetchBundleData,
29
+ fetchShopCustomCss: () => fetchShopCustomCss,
26
30
  useAnalytics: () => useAnalytics,
27
- useBundleData: () => useBundleData,
28
- useCart: () => useCart
31
+ useBundleData: () => useBundleData
29
32
  });
30
- module.exports = __toCommonJS(src_exports);
33
+ module.exports = __toCommonJS(index_exports);
31
34
 
32
35
  // src/components/FixedBundle.tsx
33
- var import_react4 = require("react");
34
- var import_core4 = require("@lime-bundles/core");
36
+ var import_react3 = require("react");
37
+ var import_core5 = require("@lime-bundles/core");
35
38
 
36
39
  // src/hooks/useBundleData.ts
37
40
  var import_react = require("react");
41
+ var import_core3 = require("@lime-bundles/core");
42
+
43
+ // src/fetchBundleData.ts
38
44
  var import_core = require("@lime-bundles/core");
45
+ async function fetchBundleData(options) {
46
+ const client = (0, import_core.createStorefrontClient)({
47
+ shopDomain: options.shopDomain,
48
+ accessToken: options.storefrontAccessToken,
49
+ buyerIp: options.buyerIp,
50
+ apiVersion: options.apiVersion
51
+ });
52
+ const data = await client.query(
53
+ import_core.BUNDLE_METAOBJECT_QUERY,
54
+ { id: options.bundleGid },
55
+ { signal: options.signal }
56
+ );
57
+ if (!data.metaobject) {
58
+ throw new import_core.BundleParseError(
59
+ `Bundle not found: ${options.bundleGid}`,
60
+ "not_found"
61
+ );
62
+ }
63
+ return (0, import_core.parseMetaobjectBundleStrict)(
64
+ data.metaobject.id,
65
+ data.metaobject.fields
66
+ );
67
+ }
68
+
69
+ // src/fetchShopCustomCss.ts
70
+ var import_core2 = require("@lime-bundles/core");
71
+ var cache = /* @__PURE__ */ new Map();
72
+ async function fetchShopCustomCss(options) {
73
+ const cached = cache.get(options.shopDomain);
74
+ if (cached) return cached;
75
+ const promise = (async () => {
76
+ const client = (0, import_core2.createStorefrontClient)({
77
+ shopDomain: options.shopDomain,
78
+ accessToken: options.storefrontAccessToken,
79
+ buyerIp: options.buyerIp
80
+ });
81
+ const data = await client.query(
82
+ import_core2.SHOP_CUSTOM_CSS_QUERY,
83
+ void 0,
84
+ { signal: options.signal }
85
+ );
86
+ return data.shop?.metafield?.value ?? null;
87
+ })();
88
+ cache.set(options.shopDomain, promise);
89
+ promise.catch(() => {
90
+ if (cache.get(options.shopDomain) === promise) {
91
+ cache.delete(options.shopDomain);
92
+ }
93
+ });
94
+ return promise;
95
+ }
96
+
97
+ // src/hooks/useBundleData.ts
98
+ var INITIAL_STATE = {
99
+ status: "loading",
100
+ bundle: null,
101
+ error: null
102
+ };
39
103
  function useBundleData(options) {
40
- const [bundle, setBundle] = (0, import_react.useState)(null);
41
- const [loading, setLoading] = (0, import_react.useState)(true);
42
- const [error, setError] = (0, import_react.useState)(null);
43
- const abortRef = (0, import_react.useRef)(null);
104
+ const [state, setState] = (0, import_react.useState)(INITIAL_STATE);
44
105
  (0, import_react.useEffect)(() => {
45
- abortRef.current?.abort();
46
106
  const controller = new AbortController();
47
- abortRef.current = controller;
48
- setLoading(true);
49
- setError(null);
50
- const client = (0, import_core.createStorefrontClient)({
107
+ setState(INITIAL_STATE);
108
+ fetchBundleData({
51
109
  shopDomain: options.shopDomain,
52
- accessToken: options.storefrontAccessToken
53
- });
54
- client.query(import_core.BUNDLE_METAOBJECT_QUERY, {
55
- id: options.bundleGid
56
- }).then((data) => {
110
+ storefrontAccessToken: options.storefrontAccessToken,
111
+ bundleGid: options.bundleGid,
112
+ signal: controller.signal
113
+ }).then((bundle) => {
57
114
  if (controller.signal.aborted) return;
58
- if (!data.metaobject) {
59
- setError("Bundle not found");
60
- setBundle(null);
61
- } else {
62
- const parsed = (0, import_core.parseMetaobjectBundle)(
63
- data.metaobject.id,
64
- data.metaobject.fields
65
- );
66
- if (parsed) {
67
- setBundle(parsed);
68
- } else {
69
- setError("Bundle is not active or has expired");
70
- setBundle(null);
71
- }
72
- }
73
- setLoading(false);
115
+ setState({ status: "success", bundle, error: null });
74
116
  }).catch((err) => {
75
117
  if (controller.signal.aborted) return;
76
- setError(err instanceof Error ? err.message : "Failed to load bundle");
77
- setLoading(false);
118
+ setState({
119
+ status: "error",
120
+ bundle: null,
121
+ error: err instanceof Error ? err : new Error(String(err))
122
+ });
123
+ });
124
+ fetchShopCustomCss({
125
+ shopDomain: options.shopDomain,
126
+ storefrontAccessToken: options.storefrontAccessToken,
127
+ signal: controller.signal
128
+ }).then((css) => {
129
+ if (controller.signal.aborted) return;
130
+ (0, import_core3.injectCustomCss)(options.shopDomain, css);
131
+ }).catch(() => {
78
132
  });
79
133
  return () => {
80
134
  controller.abort();
81
135
  };
82
- }, [options.shopDomain, options.storefrontAccessToken, options.bundleGid]);
83
- return { bundle, loading, error };
84
- }
85
-
86
- // src/hooks/useCart.ts
87
- var import_react2 = require("react");
88
- var import_core2 = require("@lime-bundles/core");
89
- function useCart(options) {
90
- const [loading, setLoading] = (0, import_react2.useState)(false);
91
- const [error, setError] = (0, import_react2.useState)(null);
92
- const [lastResult, setLastResult] = (0, import_react2.useState)(null);
93
- const cart = (0, import_react2.useMemo)(() => {
94
- const apiType = options.cartApi ?? (0, import_core2.detectCartApi)();
95
- if (apiType === "ajax") {
96
- return (0, import_core2.createAjaxCartApi)(options.bundleGid, options.bundleType);
97
- }
98
- const client = (0, import_core2.createStorefrontClient)({
99
- shopDomain: options.shopDomain,
100
- accessToken: options.storefrontAccessToken
101
- });
102
- return (0, import_core2.createStorefrontCartApi)(
103
- client,
104
- options.bundleGid,
105
- options.bundleType,
106
- options.cartId
107
- );
108
136
  }, [
109
137
  options.shopDomain,
110
138
  options.storefrontAccessToken,
111
- options.bundleGid,
112
- options.bundleType,
113
- options.cartId,
114
- options.cartApi
139
+ options.bundleGid
115
140
  ]);
116
- const addToCart = (0, import_react2.useCallback)(
117
- async (items) => {
118
- setLoading(true);
119
- setError(null);
120
- try {
121
- const result = await cart.addLines(items);
122
- setLastResult(result);
123
- if (!result.success) {
124
- setError(result.error ?? "Failed to add to cart");
125
- }
126
- return result;
127
- } catch (err) {
128
- const message = err instanceof Error ? err.message : "Failed to add to cart";
129
- setError(message);
130
- const result = { success: false, error: message };
131
- setLastResult(result);
132
- return result;
133
- } finally {
134
- setLoading(false);
135
- }
136
- },
137
- [cart]
138
- );
139
- return { addToCart, loading, error, lastResult };
141
+ return state;
140
142
  }
141
143
 
142
144
  // src/hooks/useAnalytics.ts
143
- var import_react3 = require("react");
144
- var import_core3 = require("@lime-bundles/core");
145
+ var import_react2 = require("react");
146
+ var import_core4 = require("@lime-bundles/core");
145
147
  function useAnalytics(options) {
146
- const impressionFiredRef = (0, import_react3.useRef)(false);
147
- const [element, setElement] = (0, import_react3.useState)(null);
148
- const elementRef = (0, import_react3.useCallback)((el) => {
148
+ const impressionFiredRef = (0, import_react2.useRef)(false);
149
+ const [element, setElement] = (0, import_react2.useState)(null);
150
+ const elementRef = (0, import_react2.useCallback)((el) => {
149
151
  setElement(el);
150
152
  }, []);
151
- const config = (0, import_react3.useMemo)(
153
+ const config = (0, import_react2.useMemo)(
152
154
  () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),
153
155
  [options.shopDomain, options.appUrl]
154
156
  );
155
- (0, import_react3.useEffect)(() => {
157
+ (0, import_react2.useEffect)(() => {
156
158
  impressionFiredRef.current = false;
157
159
  }, [options.bundleGid, options.abVariant]);
158
- (0, import_react3.useEffect)(() => {
160
+ (0, import_react2.useEffect)(() => {
159
161
  if (options.enabled === false) return;
160
162
  if (impressionFiredRef.current) return;
161
163
  if (!element) return;
162
- const cleanup = (0, import_core3.observeImpression)(element, () => {
164
+ const cleanup = (0, import_core4.observeImpression)(element, () => {
163
165
  if (impressionFiredRef.current) return;
164
166
  impressionFiredRef.current = true;
165
- (0, import_core3.reportImpression)(config, {
167
+ (0, import_core4.reportImpression)(config, {
166
168
  bundleGid: options.bundleGid,
167
169
  bundleType: options.bundleType,
168
170
  abTestId: options.abTestId,
@@ -179,10 +181,10 @@ function useAnalytics(options) {
179
181
  options.abTestId,
180
182
  options.abVariant
181
183
  ]);
182
- const trackAddToCart = (0, import_react3.useCallback)(
184
+ const trackAddToCart = (0, import_react2.useCallback)(
183
185
  (event) => {
184
186
  if (options.enabled === false) return;
185
- (0, import_core3.reportAddToCart)(config, {
187
+ (0, import_core4.reportAddToCart)(config, {
186
188
  bundleGid: options.bundleGid,
187
189
  bundleType: options.bundleType,
188
190
  productId: event.productId,
@@ -211,27 +213,17 @@ function FixedBundle(props) {
211
213
  shopDomain,
212
214
  storefrontAccessToken,
213
215
  bundleGid,
214
- cartId,
215
- cartApi,
216
216
  appUrl,
217
217
  analyticsEnabled,
218
218
  onAddToCart,
219
219
  onError,
220
220
  className
221
221
  } = props;
222
- const { bundle, loading, error: loadError } = useBundleData({
222
+ const result = useBundleData({
223
223
  shopDomain,
224
224
  storefrontAccessToken,
225
225
  bundleGid
226
226
  });
227
- const { addToCart, loading: cartLoading, error: cartError } = useCart({
228
- shopDomain,
229
- storefrontAccessToken,
230
- bundleGid,
231
- bundleType: "fixed",
232
- cartId,
233
- cartApi
234
- });
235
227
  const { elementRef, trackAddToCart } = useAnalytics({
236
228
  shopDomain,
237
229
  appUrl: appUrl ?? `https://${shopDomain}`,
@@ -239,37 +231,69 @@ function FixedBundle(props) {
239
231
  bundleType: "fixed",
240
232
  enabled: analyticsEnabled !== false
241
233
  });
242
- const handleAddToCart = (0, import_react4.useCallback)(async () => {
234
+ const [addingToCart, setAddingToCart] = (0, import_react3.useState)(false);
235
+ const [cartError, setCartError] = (0, import_react3.useState)(null);
236
+ const bundle = result.status === "success" && result.bundle.bundleType === "fixed" ? result.bundle : null;
237
+ (0, import_react3.useEffect)(() => {
238
+ if (result.status === "error") {
239
+ onError?.(result.error);
240
+ return;
241
+ }
242
+ if (result.status === "success" && result.bundle.bundleType !== "fixed") {
243
+ onError?.(
244
+ new Error(
245
+ `FixedBundle: expected bundleType="fixed", got "${result.bundle.bundleType}"`
246
+ )
247
+ );
248
+ }
249
+ }, [result, onError]);
250
+ const handleAddToCart = (0, import_react3.useCallback)(async () => {
243
251
  if (!bundle) return;
244
- const items = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
252
+ const lines = bundle.products.filter((p) => p.variants.nodes.some((v) => v.availableForSale)).map((p) => {
245
253
  const variant = p.variants.nodes.find((v) => v.availableForSale);
246
- return { variantId: variant.id, quantity: 1 };
254
+ return {
255
+ merchandiseId: variant.id,
256
+ quantity: 1,
257
+ attributes: [
258
+ { key: "_lime_bundle_gid", value: bundle.id },
259
+ { key: "_lime_bundle_type", value: bundle.bundleType }
260
+ ]
261
+ };
247
262
  });
248
- if (items.length === 0) return;
249
- const result = await addToCart(items);
250
- if (result.success) {
263
+ if (lines.length === 0) return;
264
+ setAddingToCart(true);
265
+ setCartError(null);
266
+ try {
267
+ await onAddToCart(lines);
251
268
  const totalPrice = bundle.products.reduce((sum, p) => {
252
269
  const price = parseFloat(p.priceRange.minVariantPrice.amount);
253
- return sum + (0, import_core4.calculateDiscount)(price, bundle.discountConfig.discountType, bundle.discountConfig.discountValue);
270
+ return sum + (0, import_core5.calculateDiscount)(
271
+ price,
272
+ bundle.discountConfig.discountType,
273
+ bundle.discountConfig.discountValue
274
+ );
254
275
  }, 0);
255
276
  trackAddToCart({
256
277
  productId: bundle.products[0]?.id ?? "",
257
278
  quantity: 1,
258
279
  totalPrice: Math.round(totalPrice * 100) / 100
259
280
  });
260
- onAddToCart?.(items);
281
+ } catch (err) {
282
+ const error = err instanceof Error ? err : new Error(String(err));
283
+ setCartError(error.message);
284
+ onError?.(error);
285
+ } finally {
286
+ setAddingToCart(false);
261
287
  }
262
- }, [bundle, addToCart, trackAddToCart, onAddToCart]);
263
- if (loading) {
288
+ }, [bundle, onAddToCart, onError, trackAddToCart]);
289
+ if (result.status === "loading") {
264
290
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
265
291
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lb-skeleton lb-skeleton--title" }),
266
292
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "lb-skeleton lb-skeleton--products" })
267
293
  ] });
268
294
  }
269
- if (loadError || !bundle) {
270
- if (onError) onError(loadError ?? "Bundle not found");
271
- return null;
272
- }
295
+ if (result.status === "error") return null;
296
+ if (!bundle) return null;
273
297
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
274
298
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
275
299
  "div",
@@ -293,7 +317,7 @@ function FixedBundle(props) {
293
317
  ),
294
318
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "lb-bundle__product-info", children: [
295
319
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lb-bundle__product-title", children: product.title }),
296
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lb-bundle__product-price", children: (0, import_core4.formatMoney)(product.priceRange.minVariantPrice.amount, currency) })
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lb-bundle__product-price", children: (0, import_core5.formatMoney)(product.priceRange.minVariantPrice.amount, currency) })
297
321
  ] })
298
322
  ] }, product.id)) }),
299
323
  cartError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "lb-bundle__error", role: "alert", children: cartError }),
@@ -302,9 +326,9 @@ function FixedBundle(props) {
302
326
  {
303
327
  className: "lb-bundle__cta",
304
328
  onClick: handleAddToCart,
305
- disabled: cartLoading,
306
- "aria-busy": cartLoading,
307
- children: cartLoading ? "Adding..." : bundle.widgetConfig.ctaText ?? "Add Bundle to Cart"
329
+ disabled: addingToCart,
330
+ "aria-busy": addingToCart,
331
+ children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? "Add Bundle to Cart"
308
332
  }
309
333
  )
310
334
  ]
@@ -313,35 +337,25 @@ function FixedBundle(props) {
313
337
  }
314
338
 
315
339
  // src/components/MixMatchBundle.tsx
316
- var import_react5 = require("react");
317
- var import_core5 = require("@lime-bundles/core");
340
+ var import_react4 = require("react");
341
+ var import_core6 = require("@lime-bundles/core");
318
342
  var import_jsx_runtime2 = require("react/jsx-runtime");
319
343
  function MixMatchBundle(props) {
320
344
  const {
321
345
  shopDomain,
322
346
  storefrontAccessToken,
323
347
  bundleGid,
324
- cartId,
325
- cartApi,
326
348
  appUrl,
327
349
  analyticsEnabled,
328
350
  onAddToCart,
329
351
  onError,
330
352
  className
331
353
  } = props;
332
- const { bundle, loading, error: loadError } = useBundleData({
354
+ const result = useBundleData({
333
355
  shopDomain,
334
356
  storefrontAccessToken,
335
357
  bundleGid
336
358
  });
337
- const { addToCart, loading: cartLoading, error: cartError } = useCart({
338
- shopDomain,
339
- storefrontAccessToken,
340
- bundleGid,
341
- bundleType: "mix_match",
342
- cartId,
343
- cartApi
344
- });
345
359
  const { elementRef, trackAddToCart } = useAnalytics({
346
360
  shopDomain,
347
361
  appUrl: appUrl ?? `https://${shopDomain}`,
@@ -349,13 +363,31 @@ function MixMatchBundle(props) {
349
363
  bundleType: "mix_match",
350
364
  enabled: analyticsEnabled !== false
351
365
  });
352
- const [selections, setSelections] = (0, import_react5.useState)(/* @__PURE__ */ new Map());
366
+ const [selections, setSelections] = (0, import_react4.useState)(
367
+ /* @__PURE__ */ new Map()
368
+ );
369
+ const [addingToCart, setAddingToCart] = (0, import_react4.useState)(false);
370
+ const [cartError, setCartError] = (0, import_react4.useState)(null);
371
+ const bundle = result.status === "success" && result.bundle.bundleType === "mix_match" ? result.bundle : null;
372
+ (0, import_react4.useEffect)(() => {
373
+ if (result.status === "error") {
374
+ onError?.(result.error);
375
+ return;
376
+ }
377
+ if (result.status === "success" && result.bundle.bundleType !== "mix_match") {
378
+ onError?.(
379
+ new Error(
380
+ `MixMatchBundle: expected bundleType="mix_match", got "${result.bundle.bundleType}"`
381
+ )
382
+ );
383
+ }
384
+ }, [result, onError]);
353
385
  const totalQuantity = Array.from(selections.values()).reduce(
354
386
  (sum, s) => sum + s.quantity,
355
387
  0
356
388
  );
357
- const validation = bundle ? (0, import_core5.validateQuantity)(totalQuantity, bundle.minQuantity, bundle.maxQuantity) : { valid: false, totalQuantity: 0, message: null };
358
- const toggleProduct = (0, import_react5.useCallback)(
389
+ const validation = bundle ? (0, import_core6.validateQuantity)(totalQuantity, bundle.minQuantity, bundle.maxQuantity) : { valid: false, totalQuantity: 0, message: null };
390
+ const toggleProduct = (0, import_react4.useCallback)(
359
391
  (productId, variant) => {
360
392
  setSelections((prev) => {
361
393
  const next = new Map(prev);
@@ -370,7 +402,7 @@ function MixMatchBundle(props) {
370
402
  },
371
403
  []
372
404
  );
373
- const updateQuantity = (0, import_react5.useCallback)(
405
+ const updateQuantity = (0, import_react4.useCallback)(
374
406
  (productId, variantId, quantity) => {
375
407
  setSelections((prev) => {
376
408
  const next = new Map(prev);
@@ -385,42 +417,59 @@ function MixMatchBundle(props) {
385
417
  },
386
418
  []
387
419
  );
388
- const handleAddToCart = (0, import_react5.useCallback)(async () => {
420
+ const handleAddToCart = (0, import_react4.useCallback)(async () => {
389
421
  if (!bundle || !validation.valid) return;
390
- const items = Array.from(selections.values()).map((s) => ({
391
- variantId: s.variantId,
392
- quantity: s.quantity
422
+ const lines = Array.from(selections.values()).map((s) => ({
423
+ merchandiseId: s.variantId,
424
+ quantity: s.quantity,
425
+ attributes: [
426
+ { key: "_lime_bundle_gid", value: bundle.id },
427
+ { key: "_lime_bundle_type", value: bundle.bundleType }
428
+ ]
393
429
  }));
394
- const result = await addToCart(items);
395
- if (result.success) {
396
- const totalPrice = items.reduce((sum, item) => {
430
+ setAddingToCart(true);
431
+ setCartError(null);
432
+ try {
433
+ await onAddToCart(lines);
434
+ const totalPrice = lines.reduce((sum, line) => {
397
435
  const product = bundle.products.find(
398
- (p) => p.variants.nodes.some((v) => v.id === item.variantId)
436
+ (p) => p.variants.nodes.some((v) => v.id === line.merchandiseId)
399
437
  );
400
438
  const variant = product?.variants.nodes.find(
401
- (v) => v.id === item.variantId
439
+ (v) => v.id === line.merchandiseId
402
440
  );
403
- return sum + parseFloat(variant?.price.amount ?? "0") * item.quantity;
441
+ return sum + parseFloat(variant?.price.amount ?? "0") * line.quantity;
404
442
  }, 0);
405
443
  trackAddToCart({
406
444
  productId: bundle.products[0]?.id ?? "",
407
445
  quantity: totalQuantity,
408
446
  totalPrice: Math.round(totalPrice * 100) / 100
409
447
  });
410
- onAddToCart?.(items);
411
448
  setSelections(/* @__PURE__ */ new Map());
449
+ } catch (err) {
450
+ const error = err instanceof Error ? err : new Error(String(err));
451
+ setCartError(error.message);
452
+ onError?.(error);
453
+ } finally {
454
+ setAddingToCart(false);
412
455
  }
413
- }, [bundle, selections, validation.valid, addToCart, trackAddToCart, totalQuantity, onAddToCart]);
414
- if (loading) {
456
+ }, [
457
+ bundle,
458
+ selections,
459
+ validation.valid,
460
+ onAddToCart,
461
+ onError,
462
+ trackAddToCart,
463
+ totalQuantity
464
+ ]);
465
+ if (result.status === "loading") {
415
466
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
416
467
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lb-skeleton lb-skeleton--title" }),
417
468
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lb-skeleton lb-skeleton--products" })
418
469
  ] });
419
470
  }
420
- if (loadError || !bundle) {
421
- if (onError) onError(loadError ?? "Bundle not found");
422
- return null;
423
- }
471
+ if (result.status === "error") return null;
472
+ if (!bundle) return null;
424
473
  const currency = bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? "USD";
425
474
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
426
475
  "div",
@@ -454,14 +503,18 @@ function MixMatchBundle(props) {
454
503
  ),
455
504
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lb-bundle__product-info", children: [
456
505
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "lb-bundle__product-title", children: product.title }),
457
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "lb-bundle__product-price", children: (0, import_core5.formatMoney)(defaultVariant.price.amount, currency) })
506
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "lb-bundle__product-price", children: (0, import_core6.formatMoney)(defaultVariant.price.amount, currency) })
458
507
  ] }),
459
508
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "lb-bundle__product-actions", children: selected ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "lb-bundle__quantity-control", children: [
460
509
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
461
510
  "button",
462
511
  {
463
512
  "aria-label": `Decrease ${product.title}`,
464
- onClick: () => updateQuantity(product.id, defaultVariant.id, selected.quantity - 1),
513
+ onClick: () => updateQuantity(
514
+ product.id,
515
+ defaultVariant.id,
516
+ selected.quantity - 1
517
+ ),
465
518
  children: "\u2212"
466
519
  }
467
520
  ),
@@ -470,7 +523,11 @@ function MixMatchBundle(props) {
470
523
  "button",
471
524
  {
472
525
  "aria-label": `Increase ${product.title}`,
473
- onClick: () => updateQuantity(product.id, defaultVariant.id, selected.quantity + 1),
526
+ onClick: () => updateQuantity(
527
+ product.id,
528
+ defaultVariant.id,
529
+ selected.quantity + 1
530
+ ),
474
531
  children: "+"
475
532
  }
476
533
  )
@@ -495,9 +552,9 @@ function MixMatchBundle(props) {
495
552
  {
496
553
  className: "lb-bundle__cta",
497
554
  onClick: handleAddToCart,
498
- disabled: cartLoading || !validation.valid,
499
- "aria-busy": cartLoading,
500
- children: cartLoading ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${totalQuantity} Items to Cart`
555
+ disabled: addingToCart || !validation.valid,
556
+ "aria-busy": addingToCart,
557
+ children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${totalQuantity} Items to Cart`
501
558
  }
502
559
  )
503
560
  ]
@@ -506,35 +563,25 @@ function MixMatchBundle(props) {
506
563
  }
507
564
 
508
565
  // src/components/VolumeBundle.tsx
509
- var import_react6 = require("react");
510
- var import_core6 = require("@lime-bundles/core");
566
+ var import_react5 = require("react");
567
+ var import_core7 = require("@lime-bundles/core");
511
568
  var import_jsx_runtime3 = require("react/jsx-runtime");
512
569
  function VolumeBundle(props) {
513
570
  const {
514
571
  shopDomain,
515
572
  storefrontAccessToken,
516
573
  bundleGid,
517
- cartId,
518
- cartApi,
519
574
  appUrl,
520
575
  analyticsEnabled,
521
576
  onAddToCart,
522
577
  onError,
523
578
  className
524
579
  } = props;
525
- const { bundle, loading, error: loadError } = useBundleData({
580
+ const result = useBundleData({
526
581
  shopDomain,
527
582
  storefrontAccessToken,
528
583
  bundleGid
529
584
  });
530
- const { addToCart, loading: cartLoading, error: cartError } = useCart({
531
- shopDomain,
532
- storefrontAccessToken,
533
- bundleGid,
534
- bundleType: "volume",
535
- cartId,
536
- cartApi
537
- });
538
585
  const { elementRef, trackAddToCart } = useAnalytics({
539
586
  shopDomain,
540
587
  appUrl: appUrl ?? `https://${shopDomain}`,
@@ -542,40 +589,78 @@ function VolumeBundle(props) {
542
589
  bundleType: "volume",
543
590
  enabled: analyticsEnabled !== false
544
591
  });
545
- const [quantity, setQuantity] = (0, import_react6.useState)(1);
592
+ const [quantity, setQuantity] = (0, import_react5.useState)(1);
593
+ const [addingToCart, setAddingToCart] = (0, import_react5.useState)(false);
594
+ const [cartError, setCartError] = (0, import_react5.useState)(null);
595
+ const bundle = result.status === "success" && result.bundle.bundleType === "volume" ? result.bundle : null;
596
+ (0, import_react5.useEffect)(() => {
597
+ if (result.status === "error") {
598
+ onError?.(result.error);
599
+ return;
600
+ }
601
+ if (result.status === "success" && result.bundle.bundleType !== "volume") {
602
+ onError?.(
603
+ new Error(
604
+ `VolumeBundle: expected bundleType="volume", got "${result.bundle.bundleType}"`
605
+ )
606
+ );
607
+ }
608
+ }, [result, onError]);
546
609
  const product = bundle?.products[0];
547
610
  const basePrice = product ? parseFloat(product.priceRange.minVariantPrice.amount) : 0;
548
611
  const currency = product?.priceRange.minVariantPrice.currencyCode ?? "USD";
549
- const tierSavings = bundle ? (0, import_core6.calculateTierSavings)(bundle.volumeTiers, basePrice, quantity) : [];
550
- const activeTier = bundle ? (0, import_core6.getActiveTier)(bundle.volumeTiers, quantity) : null;
551
- const handleAddToCart = (0, import_react6.useCallback)(async () => {
612
+ const tierSavings = bundle ? (0, import_core7.calculateTierSavings)(bundle.volumeTiers, basePrice, quantity) : [];
613
+ const activeTier = bundle ? (0, import_core7.getActiveTier)(bundle.volumeTiers, quantity) : null;
614
+ const handleAddToCart = (0, import_react5.useCallback)(async () => {
552
615
  if (!bundle || !product) return;
553
616
  const variant = product.variants.nodes.find((v) => v.availableForSale);
554
617
  if (!variant) return;
555
- const items = [
556
- { variantId: variant.id, quantity }
618
+ const lines = [
619
+ {
620
+ merchandiseId: variant.id,
621
+ quantity,
622
+ attributes: [
623
+ { key: "_lime_bundle_gid", value: bundle.id },
624
+ { key: "_lime_bundle_type", value: bundle.bundleType }
625
+ ]
626
+ }
557
627
  ];
558
- const result = await addToCart(items);
559
- if (result.success) {
628
+ setAddingToCart(true);
629
+ setCartError(null);
630
+ try {
631
+ await onAddToCart(lines);
560
632
  const unitPrice = activeTier ? basePrice * (1 - activeTier.discountValue / 100) : basePrice;
561
633
  trackAddToCart({
562
634
  productId: product.id,
563
635
  quantity,
564
636
  totalPrice: Math.round(unitPrice * quantity * 100) / 100
565
637
  });
566
- onAddToCart?.(items);
638
+ } catch (err) {
639
+ const error = err instanceof Error ? err : new Error(String(err));
640
+ setCartError(error.message);
641
+ onError?.(error);
642
+ } finally {
643
+ setAddingToCart(false);
567
644
  }
568
- }, [bundle, product, quantity, activeTier, basePrice, addToCart, trackAddToCart, onAddToCart]);
569
- if (loading) {
645
+ }, [
646
+ bundle,
647
+ product,
648
+ quantity,
649
+ activeTier,
650
+ basePrice,
651
+ onAddToCart,
652
+ onError,
653
+ trackAddToCart
654
+ ]);
655
+ if (result.status === "loading") {
570
656
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `lb-bundle lb-bundle--loading ${className ?? ""}`, children: [
571
657
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lb-skeleton lb-skeleton--title" }),
572
658
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lb-skeleton lb-skeleton--tiers" })
573
659
  ] });
574
660
  }
575
- if (loadError || !bundle || !product) {
576
- if (onError) onError(loadError ?? "Bundle not found");
577
- return null;
578
- }
661
+ if (result.status === "error") return null;
662
+ if (!bundle) return null;
663
+ if (!product) return null;
579
664
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
580
665
  "div",
581
666
  {
@@ -598,35 +683,43 @@ function VolumeBundle(props) {
598
683
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "lb-bundle__product-info", children: [
599
684
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "lb-bundle__product-title", children: product.title }),
600
685
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "lb-bundle__product-price", children: [
601
- (0, import_core6.formatMoney)(basePrice, currency),
686
+ (0, import_core7.formatMoney)(basePrice, currency),
602
687
  " each"
603
688
  ] })
604
689
  ] })
605
690
  ] }),
606
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "lb-bundle__tiers", role: "table", "aria-label": "Volume discounts", children: tierSavings.map((ts) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
691
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
607
692
  "div",
608
693
  {
609
- className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""}`,
610
- role: "row",
611
- children: [
612
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-quantity", role: "cell", children: [
613
- ts.tier.minQuantity,
614
- "+ items"
615
- ] }),
616
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-price", role: "cell", children: [
617
- (0, import_core6.formatMoney)(ts.unitPrice, currency),
618
- " each"
619
- ] }),
620
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-savings", role: "cell", children: [
621
- "Save ",
622
- ts.savingsPercent.toFixed(0),
623
- "%"
624
- ] }),
625
- ts.tier.label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "lb-bundle__tier-label", role: "cell", children: ts.tier.label })
626
- ]
627
- },
628
- ts.tier.minQuantity
629
- )) }),
694
+ className: "lb-bundle__tiers",
695
+ role: "table",
696
+ "aria-label": "Volume discounts",
697
+ children: tierSavings.map((ts) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
698
+ "div",
699
+ {
700
+ className: `lb-bundle__tier ${ts.isActive ? "lb-bundle__tier--active" : ""}`,
701
+ role: "row",
702
+ children: [
703
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-quantity", role: "cell", children: [
704
+ ts.tier.minQuantity,
705
+ "+ items"
706
+ ] }),
707
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-price", role: "cell", children: [
708
+ (0, import_core7.formatMoney)(ts.unitPrice, currency),
709
+ " each"
710
+ ] }),
711
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "lb-bundle__tier-savings", role: "cell", children: [
712
+ "Save ",
713
+ ts.savingsPercent.toFixed(0),
714
+ "%"
715
+ ] }),
716
+ ts.tier.label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "lb-bundle__tier-label", role: "cell", children: ts.tier.label })
717
+ ]
718
+ },
719
+ ts.tier.minQuantity
720
+ ))
721
+ }
722
+ ),
630
723
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "lb-bundle__quantity-selector", children: [
631
724
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: `lb-qty-${bundle.id}`, children: "Quantity" }),
632
725
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "lb-bundle__quantity-control", children: [
@@ -668,22 +761,28 @@ function VolumeBundle(props) {
668
761
  {
669
762
  className: "lb-bundle__cta",
670
763
  onClick: handleAddToCart,
671
- disabled: cartLoading,
672
- "aria-busy": cartLoading,
673
- children: cartLoading ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`
764
+ disabled: addingToCart,
765
+ "aria-busy": addingToCart,
766
+ children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`
674
767
  }
675
768
  )
676
769
  ]
677
770
  }
678
771
  );
679
772
  }
773
+
774
+ // src/index.ts
775
+ var import_core8 = require("@lime-bundles/core");
680
776
  // Annotate the CommonJS export names for ESM import in node:
681
777
  0 && (module.exports = {
778
+ BundleParseError,
682
779
  FixedBundle,
683
780
  MixMatchBundle,
781
+ StorefrontApiError,
684
782
  VolumeBundle,
783
+ fetchBundleData,
784
+ fetchShopCustomCss,
685
785
  useAnalytics,
686
- useBundleData,
687
- useCart
786
+ useBundleData
688
787
  });
689
788
  //# sourceMappingURL=index.cjs.map