@lime-bundles/react 1.0.0 → 2.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/README.md +54 -60
- package/dist/index.cjs +126 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +125 -4
- package/dist/index.js.map +1 -1
- package/docs/README.md +81 -0
- package/docs/css-variables.md +158 -0
- package/docs/hydrogen.md +185 -0
- package/docs/react-nextjs.md +379 -0
- package/docs/web-component.md +282 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @lime-bundles/react
|
|
2
2
|
|
|
3
|
-
React components and hooks for Lime Bundles on Hydrogen, Next.js, Vite, Remix, or any React renderer.
|
|
3
|
+
React components and hooks for the **Lime Bundles Shopify app**. Use on Hydrogen, Next.js, Vite, Remix, or any React renderer. If you're not a merchant using Lime Bundles, this package probably isn't what you're looking for.
|
|
4
|
+
|
|
5
|
+
Bring your own cart via an `onAddToCart` callback; the SDK handles data fetching, rendering, and analytics.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -10,10 +12,10 @@ npm install @lime-bundles/react @lime-bundles/core
|
|
|
10
12
|
|
|
11
13
|
Peer dependencies: `react >=18`, `react-dom >=18`.
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## Drop-in components
|
|
14
16
|
|
|
15
17
|
```tsx
|
|
16
|
-
import { FixedBundle
|
|
18
|
+
import { FixedBundle } from "@lime-bundles/react";
|
|
17
19
|
|
|
18
20
|
<FixedBundle
|
|
19
21
|
shopDomain="my-shop.myshopify.com"
|
|
@@ -26,107 +28,99 @@ import { FixedBundle, VolumeBundle, MixMatchBundle } from "@lime-bundles/react";
|
|
|
26
28
|
/>
|
|
27
29
|
```
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
Same props for `<VolumeBundle>` and `<MixMatchBundle>`.
|
|
32
|
+
|
|
33
|
+
### Props: `BundleComponentProps`
|
|
30
34
|
|
|
31
35
|
| Prop | Type | Required | Notes |
|
|
32
36
|
|---|---|:-:|---|
|
|
33
|
-
| `shopDomain` | `string` | ✓ | Your
|
|
34
|
-
| `storefrontAccessToken` | `string` | ✓ |
|
|
37
|
+
| `shopDomain` | `string` | ✓ | Your `*.myshopify.com` domain. |
|
|
38
|
+
| `storefrontAccessToken` | `string` | ✓ | Generated in `/app/settings/headless`. |
|
|
35
39
|
| `bundleGid` | `string` | ✓ | Bundle metaobject GID. |
|
|
36
|
-
| `onAddToCart` | `(lines: CartLineInput[]) => Promise<void>` | ✓ | Must return a Promise
|
|
37
|
-
| `appUrl` | `string` | | Required only if
|
|
38
|
-
| `analyticsEnabled` | `boolean` | | Default `true`.
|
|
39
|
-
| `onError` | `(error: Error) => void` | | Fires on
|
|
40
|
-
| `locale` | `string` | | BCP-47
|
|
40
|
+
| `onAddToCart` | `(lines: CartLineInput[]) => Promise<void>` | ✓ | Must return a Promise; components await it for loading states. |
|
|
41
|
+
| `appUrl` | `string` | | Required only if analytics are enabled. |
|
|
42
|
+
| `analyticsEnabled` | `boolean` | | Default `true`. `false` suppresses impression + add-to-cart POSTs. |
|
|
43
|
+
| `onError` | `(error: Error) => void` | | Fires on fetch, parse, or cart failure. |
|
|
44
|
+
| `locale` | `string` | | BCP-47 tag forwarded to the Storefront API. |
|
|
41
45
|
| `className` | `string` | | Applied to the outer wrapper. |
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
`CartLineInput` matches Shopify Hydrogen's shape. A structural-compat test guards the contract across versions. Always preserve `attributes[]` so `_lime_bundle_gid` reaches Shopify; the `orders/create` webhook keys off it for purchase attribution.
|
|
44
48
|
|
|
45
49
|
## Hooks
|
|
46
50
|
|
|
47
|
-
### `useBundleData`
|
|
51
|
+
### `useBundleData({ shopDomain, storefrontAccessToken, bundleGid })`
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
Fetches a single bundle by GID. Discriminated-union result.
|
|
50
54
|
|
|
51
55
|
```tsx
|
|
52
56
|
import { useBundleData } from "@lime-bundles/react";
|
|
53
57
|
|
|
54
58
|
function MyBundle({ bundleGid }: { bundleGid: string }) {
|
|
55
|
-
const result = useBundleData({
|
|
56
|
-
shopDomain: "my-shop.myshopify.com",
|
|
57
|
-
storefrontAccessToken: TOKEN,
|
|
58
|
-
bundleGid,
|
|
59
|
-
});
|
|
60
|
-
|
|
59
|
+
const result = useBundleData({ shopDomain, storefrontAccessToken, bundleGid });
|
|
61
60
|
if (result.status === "loading") return <Skeleton />;
|
|
62
61
|
if (result.status === "error") return <Error message={result.error.message} />;
|
|
63
|
-
|
|
64
|
-
// result.status === "success": result.bundle is a ParsedBundle
|
|
65
62
|
return <pre>{JSON.stringify(result.bundle, null, 2)}</pre>;
|
|
66
63
|
}
|
|
67
64
|
```
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
### `useBundlesForProduct({ shopDomain, storefrontAccessToken, productHandle })`
|
|
67
|
+
|
|
68
|
+
Fetches every active bundle configured against a product. Equivalent to the web component's auto-detect mode. Inactive and out-of-schedule bundles are filtered out.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useBundlesForProduct } from "@lime-bundles/react";
|
|
72
|
+
|
|
73
|
+
const result = useBundlesForProduct({ shopDomain, storefrontAccessToken, productHandle });
|
|
74
|
+
if (result.status === "success") {
|
|
75
|
+
return result.bundles.map((b) => <FixedBundle key={b.id} bundleGid={b.id} {...cartProps} />);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
Both hooks auto-fetch merchant custom CSS and inject it as a scoped `<style>` tag. Unmount / dependency change cancels the in-flight request.
|
|
72
80
|
|
|
73
|
-
## Async
|
|
81
|
+
## Async fetchers for SSR / RSC
|
|
74
82
|
|
|
75
|
-
|
|
83
|
+
For Hydrogen loaders, Next.js Server Components, or `getServerSideProps`: skip the hook and use the async helpers directly.
|
|
76
84
|
|
|
77
85
|
```ts
|
|
78
|
-
import { fetchBundleData } from "@lime-bundles/react";
|
|
86
|
+
import { fetchBundleData, fetchBundlesForProduct } from "@lime-bundles/react";
|
|
79
87
|
|
|
80
88
|
const bundle = await fetchBundleData({
|
|
81
|
-
shopDomain
|
|
82
|
-
storefrontAccessToken
|
|
83
|
-
bundleGid
|
|
84
|
-
buyerIp: request.headers.get("x-forwarded-for")?.split(",")[0].trim()
|
|
89
|
+
shopDomain,
|
|
90
|
+
storefrontAccessToken,
|
|
91
|
+
bundleGid,
|
|
92
|
+
buyerIp: request.headers.get("x-forwarded-for")?.split(",")[0].trim(),
|
|
85
93
|
});
|
|
86
94
|
```
|
|
87
95
|
|
|
88
|
-
`buyerIp` is
|
|
89
|
-
|
|
90
|
-
Throws:
|
|
91
|
-
- `StorefrontApiError`: network, HTTP, or GraphQL errors.
|
|
92
|
-
- `BundleParseError`: metaobject missing, invalid type, inactive, not started, or expired. `error.reason` is a typed literal.
|
|
93
|
-
|
|
94
|
-
## The cart contract
|
|
96
|
+
`buyerIp` is mandatory on SSR. Shopify returns 430 Security Rejection for server-originated traffic without it.
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
## Build your own UI
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
type CartLineInput = {
|
|
100
|
-
merchandiseId: string; // variant GID
|
|
101
|
-
quantity: number;
|
|
102
|
-
attributes: Array<{ key: string; value: string }>;
|
|
103
|
-
};
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Always preserve `attributes[].{key: "_lime_bundle_gid"}` on its way to Shopify. The `orders/create` webhook uses it for purchase attribution.
|
|
100
|
+
Everything the built-in components use is re-exported for DIY consumers: pricing math, CSS-variable flatteners, image CDN transforms, countdown formatter, A/B merge, etc. See the [Build your own UI](./docs/react-nextjs.md#build-your-own-ui-diy-path) doc for the full primitives list.
|
|
107
101
|
|
|
108
102
|
## Styling
|
|
109
103
|
|
|
110
|
-
|
|
104
|
+
Merchant widget styling is applied automatically when you use `<FixedBundle>` / `<VolumeBundle>` / `<MixMatchBundle>`. For DIY UI, apply the config yourself:
|
|
111
105
|
|
|
112
106
|
```tsx
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
import { applyWidgetConfigVars } from "@lime-bundles/react";
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (ref.current) applyWidgetConfigVars(ref.current, bundle.widgetConfig);
|
|
111
|
+
}, [bundle.widgetConfig]);
|
|
116
112
|
```
|
|
117
113
|
|
|
118
|
-
Full reference: [css-variables.md](
|
|
114
|
+
Full CSS variable reference: [css-variables.md](./docs/css-variables.md).
|
|
119
115
|
|
|
120
|
-
##
|
|
116
|
+
## Version policy
|
|
121
117
|
|
|
122
|
-
|
|
118
|
+
`BundleComponentProps`, `UseBundleDataResult`, and `UseBundlesForProductResult` are locked at v2.0.0. All three packages (`core`, `react`, `widget`) bump majors together.
|
|
123
119
|
|
|
124
120
|
## License
|
|
125
121
|
|
|
126
|
-
MIT.
|
|
122
|
+
MIT.
|
|
127
123
|
|
|
128
|
-
##
|
|
124
|
+
## Support
|
|
129
125
|
|
|
130
|
-
|
|
131
|
-
- [React / Next.js guide](https://github.com/lime-app-dev/lime-bundles-app/blob/main/docs/headless/react-nextjs.md)
|
|
132
|
-
- [Report issues](https://github.com/lime-app-dev/lime-bundles-app/issues)
|
|
126
|
+
Merchant support and bug reports: email via the Lime Bundles listing on the Shopify App Store.
|
package/dist/index.cjs
CHANGED
|
@@ -20,15 +20,47 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
|
|
23
|
+
BUNDLES_FOR_PRODUCT_QUERY: () => import_core10.BUNDLES_FOR_PRODUCT_QUERY,
|
|
24
|
+
BUNDLE_METAOBJECT_QUERY: () => import_core10.BUNDLE_METAOBJECT_QUERY,
|
|
25
|
+
BundleParseError: () => import_core9.BundleParseError,
|
|
24
26
|
FixedBundle: () => FixedBundle,
|
|
25
27
|
MixMatchBundle: () => MixMatchBundle,
|
|
26
|
-
|
|
28
|
+
SHOP_CUSTOM_CSS_QUERY: () => import_core10.SHOP_CUSTOM_CSS_QUERY,
|
|
29
|
+
StorefrontApiError: () => import_core9.StorefrontApiError,
|
|
27
30
|
VolumeBundle: () => VolumeBundle,
|
|
31
|
+
WIDGET_CONFIG_DEFAULTS: () => import_core10.WIDGET_CONFIG_DEFAULTS,
|
|
32
|
+
applyABVariantB: () => import_core10.applyABVariantB,
|
|
33
|
+
applyWidgetConfigVars: () => import_core10.applyWidgetConfigVars,
|
|
34
|
+
calculateDiscount: () => import_core10.calculateDiscount,
|
|
35
|
+
calculateTierSavings: () => import_core10.calculateTierSavings,
|
|
36
|
+
computeBundleSaleCents: () => import_core10.computeBundleSaleCents,
|
|
37
|
+
computeFixedPricing: () => import_core10.computeFixedPricing,
|
|
38
|
+
createStorefrontClient: () => import_core10.createStorefrontClient,
|
|
28
39
|
fetchBundleData: () => fetchBundleData,
|
|
40
|
+
fetchBundlesForProduct: () => import_core10.fetchBundlesForProduct,
|
|
29
41
|
fetchShopCustomCss: () => fetchShopCustomCss,
|
|
42
|
+
formatCents: () => import_core10.formatCents,
|
|
43
|
+
formatCountdown: () => import_core10.formatCountdown,
|
|
44
|
+
formatMoney: () => import_core10.formatMoney,
|
|
45
|
+
getABTestAssignment: () => import_core10.getABTestAssignment,
|
|
46
|
+
getActiveTier: () => import_core10.getActiveTier,
|
|
47
|
+
hasConsent: () => import_core10.hasConsent,
|
|
48
|
+
injectCustomCss: () => import_core10.injectCustomCss,
|
|
49
|
+
mergeWidgetConfig: () => import_core10.mergeWidgetConfig,
|
|
50
|
+
observeImpression: () => import_core10.observeImpression,
|
|
51
|
+
parseCents: () => import_core10.parseCents,
|
|
52
|
+
parseMetaobjectBundle: () => import_core10.parseMetaobjectBundle,
|
|
53
|
+
parseMetaobjectBundleStrict: () => import_core10.parseMetaobjectBundleStrict,
|
|
54
|
+
percentageDiscountUnit: () => import_core10.percentageDiscountUnit,
|
|
55
|
+
reportAddToCart: () => import_core10.reportAddToCart,
|
|
56
|
+
reportImpression: () => import_core10.reportImpression,
|
|
57
|
+
sanitizeCustomCss: () => import_core10.sanitizeCustomCss,
|
|
58
|
+
setConsent: () => import_core10.setConsent,
|
|
59
|
+
transformImageUrl: () => import_core10.transformImageUrl,
|
|
30
60
|
useAnalytics: () => useAnalytics,
|
|
31
|
-
useBundleData: () => useBundleData
|
|
61
|
+
useBundleData: () => useBundleData,
|
|
62
|
+
useBundlesForProduct: () => useBundlesForProduct,
|
|
63
|
+
validateQuantity: () => import_core10.validateQuantity
|
|
32
64
|
});
|
|
33
65
|
module.exports = __toCommonJS(index_exports);
|
|
34
66
|
|
|
@@ -328,7 +360,7 @@ function FixedBundle(props) {
|
|
|
328
360
|
onClick: handleAddToCart,
|
|
329
361
|
disabled: addingToCart,
|
|
330
362
|
"aria-busy": addingToCart,
|
|
331
|
-
children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? "Add Bundle to Cart"
|
|
363
|
+
children: addingToCart ? "Adding..." : bundle.widgetConfig.cta.ctaText ?? "Add Bundle to Cart"
|
|
332
364
|
}
|
|
333
365
|
)
|
|
334
366
|
]
|
|
@@ -554,7 +586,7 @@ function MixMatchBundle(props) {
|
|
|
554
586
|
onClick: handleAddToCart,
|
|
555
587
|
disabled: addingToCart || !validation.valid,
|
|
556
588
|
"aria-busy": addingToCart,
|
|
557
|
-
children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${totalQuantity} Items to Cart`
|
|
589
|
+
children: addingToCart ? "Adding..." : bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`
|
|
558
590
|
}
|
|
559
591
|
)
|
|
560
592
|
]
|
|
@@ -763,7 +795,7 @@ function VolumeBundle(props) {
|
|
|
763
795
|
onClick: handleAddToCart,
|
|
764
796
|
disabled: addingToCart,
|
|
765
797
|
"aria-busy": addingToCart,
|
|
766
|
-
children: addingToCart ? "Adding..." : bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`
|
|
798
|
+
children: addingToCart ? "Adding..." : bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`
|
|
767
799
|
}
|
|
768
800
|
)
|
|
769
801
|
]
|
|
@@ -771,18 +803,104 @@ function VolumeBundle(props) {
|
|
|
771
803
|
);
|
|
772
804
|
}
|
|
773
805
|
|
|
774
|
-
// src/
|
|
806
|
+
// src/hooks/useBundlesForProduct.ts
|
|
807
|
+
var import_react6 = require("react");
|
|
775
808
|
var import_core8 = require("@lime-bundles/core");
|
|
809
|
+
var INITIAL_STATE2 = {
|
|
810
|
+
status: "loading",
|
|
811
|
+
bundles: null,
|
|
812
|
+
error: null
|
|
813
|
+
};
|
|
814
|
+
function useBundlesForProduct(options) {
|
|
815
|
+
const [state, setState] = (0, import_react6.useState)(
|
|
816
|
+
INITIAL_STATE2
|
|
817
|
+
);
|
|
818
|
+
(0, import_react6.useEffect)(() => {
|
|
819
|
+
const controller = new AbortController();
|
|
820
|
+
setState(INITIAL_STATE2);
|
|
821
|
+
(0, import_core8.fetchBundlesForProduct)({
|
|
822
|
+
shopDomain: options.shopDomain,
|
|
823
|
+
storefrontAccessToken: options.storefrontAccessToken,
|
|
824
|
+
productHandle: options.productHandle,
|
|
825
|
+
buyerIp: options.buyerIp,
|
|
826
|
+
signal: controller.signal
|
|
827
|
+
}).then((bundles) => {
|
|
828
|
+
if (controller.signal.aborted) return;
|
|
829
|
+
setState({ status: "success", bundles, error: null });
|
|
830
|
+
}).catch((err) => {
|
|
831
|
+
if (controller.signal.aborted) return;
|
|
832
|
+
setState({
|
|
833
|
+
status: "error",
|
|
834
|
+
bundles: null,
|
|
835
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
836
|
+
});
|
|
837
|
+
});
|
|
838
|
+
fetchShopCustomCss({
|
|
839
|
+
shopDomain: options.shopDomain,
|
|
840
|
+
storefrontAccessToken: options.storefrontAccessToken,
|
|
841
|
+
signal: controller.signal
|
|
842
|
+
}).then((css) => {
|
|
843
|
+
if (controller.signal.aborted) return;
|
|
844
|
+
(0, import_core8.injectCustomCss)(options.shopDomain, css);
|
|
845
|
+
}).catch(() => {
|
|
846
|
+
});
|
|
847
|
+
return () => {
|
|
848
|
+
controller.abort();
|
|
849
|
+
};
|
|
850
|
+
}, [
|
|
851
|
+
options.shopDomain,
|
|
852
|
+
options.storefrontAccessToken,
|
|
853
|
+
options.productHandle,
|
|
854
|
+
options.buyerIp
|
|
855
|
+
]);
|
|
856
|
+
return state;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/index.ts
|
|
860
|
+
var import_core9 = require("@lime-bundles/core");
|
|
861
|
+
var import_core10 = require("@lime-bundles/core");
|
|
776
862
|
// Annotate the CommonJS export names for ESM import in node:
|
|
777
863
|
0 && (module.exports = {
|
|
864
|
+
BUNDLES_FOR_PRODUCT_QUERY,
|
|
865
|
+
BUNDLE_METAOBJECT_QUERY,
|
|
778
866
|
BundleParseError,
|
|
779
867
|
FixedBundle,
|
|
780
868
|
MixMatchBundle,
|
|
869
|
+
SHOP_CUSTOM_CSS_QUERY,
|
|
781
870
|
StorefrontApiError,
|
|
782
871
|
VolumeBundle,
|
|
872
|
+
WIDGET_CONFIG_DEFAULTS,
|
|
873
|
+
applyABVariantB,
|
|
874
|
+
applyWidgetConfigVars,
|
|
875
|
+
calculateDiscount,
|
|
876
|
+
calculateTierSavings,
|
|
877
|
+
computeBundleSaleCents,
|
|
878
|
+
computeFixedPricing,
|
|
879
|
+
createStorefrontClient,
|
|
783
880
|
fetchBundleData,
|
|
881
|
+
fetchBundlesForProduct,
|
|
784
882
|
fetchShopCustomCss,
|
|
883
|
+
formatCents,
|
|
884
|
+
formatCountdown,
|
|
885
|
+
formatMoney,
|
|
886
|
+
getABTestAssignment,
|
|
887
|
+
getActiveTier,
|
|
888
|
+
hasConsent,
|
|
889
|
+
injectCustomCss,
|
|
890
|
+
mergeWidgetConfig,
|
|
891
|
+
observeImpression,
|
|
892
|
+
parseCents,
|
|
893
|
+
parseMetaobjectBundle,
|
|
894
|
+
parseMetaobjectBundleStrict,
|
|
895
|
+
percentageDiscountUnit,
|
|
896
|
+
reportAddToCart,
|
|
897
|
+
reportImpression,
|
|
898
|
+
sanitizeCustomCss,
|
|
899
|
+
setConsent,
|
|
900
|
+
transformImageUrl,
|
|
785
901
|
useAnalytics,
|
|
786
|
-
useBundleData
|
|
902
|
+
useBundleData,
|
|
903
|
+
useBundlesForProduct,
|
|
904
|
+
validateQuantity
|
|
787
905
|
});
|
|
788
906
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx"],"sourcesContent":["// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n","/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundle.products.reduce((sum, p) => {\n const price = parseFloat(p.priceRange.minVariantPrice.amount);\n return (\n sum +\n calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n )\n );\n }, 0);\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: 1,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n productId: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId: variant.id, quantity: 1 });\n }\n return next;\n });\n },\n [],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(bundle.volumeTiers, basePrice, quantity)\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const unitPrice = activeTier\n ? basePrice * (1 - activeTier.discountValue / 100)\n : basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n {ts.tier.label && (\n <span className=\"lb-bundle__tier-label\" role=\"cell\">\n {ts.tier.label}\n </span>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_core","import_core","import_core","import_react","import_core","import_react","import_core","import_jsx_runtime","import_react","import_core","import_jsx_runtime","import_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/components/FixedBundle.tsx","../src/hooks/useBundleData.ts","../src/fetchBundleData.ts","../src/fetchShopCustomCss.ts","../src/hooks/useAnalytics.ts","../src/components/MixMatchBundle.tsx","../src/components/VolumeBundle.tsx","../src/hooks/useBundlesForProduct.ts"],"sourcesContent":["// Bundle components — BYO-cart via `onAddToCart`.\nexport { FixedBundle } from \"./components/FixedBundle\";\nexport { MixMatchBundle } from \"./components/MixMatchBundle\";\nexport { VolumeBundle } from \"./components/VolumeBundle\";\nexport type { BundleComponentProps } from \"./components/types\";\n\n// Data fetching\nexport {\n fetchBundleData,\n type FetchBundleDataOptions,\n} from \"./fetchBundleData\";\nexport {\n fetchShopCustomCss,\n type FetchShopCustomCssOptions,\n} from \"./fetchShopCustomCss\";\nexport {\n useBundleData,\n type UseBundleDataOptions,\n type UseBundleDataResult,\n} from \"./hooks/useBundleData\";\nexport {\n useBundlesForProduct,\n type UseBundlesForProductOptions,\n type UseBundlesForProductResult,\n} from \"./hooks/useBundlesForProduct\";\nexport { useAnalytics } from \"./hooks/useAnalytics\";\n\n// Re-export core types consumers commonly need.\nexport type {\n ParsedBundle,\n FixedBundleData,\n VolumeBundleData,\n MixMatchBundleData,\n BundleType,\n Product,\n ProductVariant,\n CartLineInput,\n WidgetConfig,\n HeaderConfig,\n LayoutConfig,\n ProductListConfig,\n PricingConfig,\n SavingsBarConfig,\n CtaConfig,\n CountdownConfig,\n PopularBadgeConfig,\n VolumeTier,\n DiscountConfig,\n ABVariantOverrides,\n PricingRow,\n FixedBundlePricing,\n ImageTransform,\n} from \"@lime-bundles/core\";\nexport { BundleParseError, StorefrontApiError } from \"@lime-bundles/core\";\n\n// DIY widget primitives — every helper a developer needs to build their\n// own bundle UI with the same data, math, and theming the first-party\n// widget uses. All live in `@lime-bundles/core`; importing from the\n// React package is shorthand for consumers who already pulled `react`.\nexport {\n // Bundle config + variant merging\n WIDGET_CONFIG_DEFAULTS,\n mergeWidgetConfig,\n applyWidgetConfigVars,\n applyABVariantB,\n // Pricing (integer cents, matches Shopify Discount Function rounding)\n parseCents,\n formatCents,\n percentageDiscountUnit,\n computeFixedPricing,\n computeBundleSaleCents,\n // Volume-tier calculation\n calculateTierSavings,\n getActiveTier,\n // Mix-match quantity validation\n validateQuantity,\n // Image / countdown / money helpers\n transformImageUrl,\n formatCountdown,\n formatMoney,\n calculateDiscount,\n // Analytics (optional — FixedBundle / VolumeBundle / MixMatchBundle\n // auto-fire these unless analyticsEnabled is set to false)\n reportImpression,\n reportAddToCart,\n observeImpression,\n // A/B testing\n getABTestAssignment,\n setConsent,\n hasConsent,\n // CSS injection + sanitizer\n injectCustomCss,\n sanitizeCustomCss,\n // Custom-storefront data fetching\n createStorefrontClient,\n fetchBundlesForProduct,\n parseMetaobjectBundle,\n parseMetaobjectBundleStrict,\n BUNDLE_METAOBJECT_QUERY,\n BUNDLES_FOR_PRODUCT_QUERY,\n SHOP_CUSTOM_CSS_QUERY,\n} from \"@lime-bundles/core\";\n","/**\n * Fixed bundle component — pre-selected products with a shared discount.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateDiscount,\n type CartLineInput,\n type FixedBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function FixedBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"fixed\",\n enabled: analyticsEnabled !== false,\n });\n\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"fixed\"\n ? (result.bundle as FixedBundleData)\n : null;\n\n // Surface load/narrowing errors via onError — in a useEffect so we don't\n // fire side effects during render.\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"fixed\"\n ) {\n onError?.(\n new Error(\n `FixedBundle: expected bundleType=\"fixed\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle) return;\n\n const lines: CartLineInput[] = bundle.products\n .filter((p) => p.variants.nodes.some((v) => v.availableForSale))\n .map((p) => {\n const variant = p.variants.nodes.find((v) => v.availableForSale)!;\n return {\n merchandiseId: variant.id,\n quantity: 1,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n };\n });\n\n if (lines.length === 0) return;\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const totalPrice = bundle.products.reduce((sum, p) => {\n const price = parseFloat(p.priceRange.minVariantPrice.amount);\n return (\n sum +\n calculateDiscount(\n price,\n bundle.discountConfig.discountType,\n bundle.discountConfig.discountValue,\n )\n );\n }, 0);\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: 1,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [bundle, onAddToCart, onError, trackAddToCart]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n // Bundle loaded but wasn't a \"fixed\" variant — render nothing; the\n // useEffect above has already invoked onError with a descriptive message.\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--fixed ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <div className=\"lb-bundle__products\">\n {bundle.products.map((product) => (\n <div key={product.id} className=\"lb-bundle__product\" part=\"product\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(product.priceRange.minVariantPrice.amount, currency)}\n </p>\n </div>\n </div>\n ))}\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? \"Add Bundle to Cart\")}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundleData` with loading / error\n * state + abort-on-unmount.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundleData` directly and pass the result to a client\n * component as a prop — the hook's only value-add over the async\n * function is React state management.\n *\n * `UseBundleDataResult` is a discriminated union locked at v1.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport { injectCustomCss, type ParsedBundle } from \"@lime-bundles/core\";\nimport { fetchBundleData } from \"../fetchBundleData\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n}\n\nexport type UseBundleDataResult =\n | { status: \"loading\"; bundle: null; error: null }\n | { status: \"success\"; bundle: ParsedBundle; error: null }\n | { status: \"error\"; bundle: null; error: Error };\n\nconst INITIAL_STATE: UseBundleDataResult = {\n status: \"loading\",\n bundle: null,\n error: null,\n};\n\nexport function useBundleData(\n options: UseBundleDataOptions,\n): UseBundleDataResult {\n const [state, setState] = useState<UseBundleDataResult>(INITIAL_STATE);\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundleData({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n bundleGid: options.bundleGid,\n signal: controller.signal,\n })\n .then((bundle) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundle, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundle: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch — best-effort, failures never block\n // bundle rendering.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.bundleGid,\n ]);\n\n return state;\n}\n","/**\n * Framework-agnostic async fetcher for bundle data.\n *\n * Primary path for Next.js App Router / RSC and Hydrogen loaders — call\n * this in a server-side context and pass the result as a prop to a\n * `\"use client\"` component. `useBundleData` is NOT needed in that\n * component because the data is already in hand.\n *\n * Always forward the end-buyer's IP via `buyerIp` when calling from SSR:\n * Shopify may return 430 Security Rejection for server-originated traffic\n * without it.\n *\n * Throws:\n * - `StorefrontApiError` on network / HTTP / GraphQL errors\n * - `BundleParseError` when the metaobject is missing, invalid type,\n * inactive, not yet started, or expired\n */\nimport {\n createStorefrontClient,\n BUNDLE_METAOBJECT_QUERY,\n parseMetaobjectBundleStrict,\n BundleParseError,\n type ParsedBundle,\n type BundleMetaobjectResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchBundleDataOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n bundleGid: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR. */\n buyerIp?: string;\n /** Abort the in-flight request (used by `useBundleData` to cancel on unmount). */\n signal?: AbortSignal;\n /** Override the Storefront API version pinned in `createStorefrontClient`. */\n apiVersion?: string;\n}\n\nexport async function fetchBundleData(\n options: FetchBundleDataOptions,\n): Promise<ParsedBundle> {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n apiVersion: options.apiVersion,\n });\n\n const data = await client.query<BundleMetaobjectResponse>(\n BUNDLE_METAOBJECT_QUERY,\n { id: options.bundleGid },\n { signal: options.signal },\n );\n\n if (!data.metaobject) {\n throw new BundleParseError(\n `Bundle not found: ${options.bundleGid}`,\n \"not_found\",\n );\n }\n\n return parseMetaobjectBundleStrict(\n data.metaobject.id,\n data.metaobject.fields,\n );\n}\n","/**\n * Best-effort fetch for the shop's `$app:custom_css` metafield.\n *\n * Returns the raw CSS string (still needs sanitization before injection —\n * see `injectCustomCss` from core) or null if the metafield is unset or\n * the request fails.\n *\n * **Per-shop in-memory cache.** Multiple `<FixedBundle>` / `<VolumeBundle>`\n * / `<MixMatchBundle>` components on the same page share one network\n * request — the first caller fetches, concurrent callers await the\n * same promise, and resolved values are cached so subsequent mounts\n * return instantly. Failures are NOT cached so the next caller retries.\n *\n * The cache lives for the life of the JS module (typically one page\n * load). Merchants updating their CSS see changes on the next page\n * load. The cache is exposed as `__clearShopCssCache` for test use only.\n */\nimport {\n createStorefrontClient,\n SHOP_CUSTOM_CSS_QUERY,\n type ShopCustomCssResponse,\n} from \"@lime-bundles/core\";\n\nexport interface FetchShopCustomCssOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n buyerIp?: string;\n signal?: AbortSignal;\n}\n\n// Cache map: key = shopDomain, value = pending or resolved promise.\n// Failures delete their own entry so retries get a fresh fetch.\nconst cache = new Map<string, Promise<string | null>>();\n\nexport async function fetchShopCustomCss(\n options: FetchShopCustomCssOptions,\n): Promise<string | null> {\n const cached = cache.get(options.shopDomain);\n if (cached) return cached;\n\n const promise = (async () => {\n const client = createStorefrontClient({\n shopDomain: options.shopDomain,\n accessToken: options.storefrontAccessToken,\n buyerIp: options.buyerIp,\n });\n\n const data = await client.query<ShopCustomCssResponse>(\n SHOP_CUSTOM_CSS_QUERY,\n undefined,\n { signal: options.signal },\n );\n\n return data.shop?.metafield?.value ?? null;\n })();\n\n // Cache the promise synchronously so concurrent callers dedupe.\n cache.set(options.shopDomain, promise);\n\n // Evict on failure so the next caller can retry.\n promise.catch(() => {\n if (cache.get(options.shopDomain) === promise) {\n cache.delete(options.shopDomain);\n }\n });\n\n return promise;\n}\n\n/**\n * Test-only helper. Clears the per-shop CSS cache so each test starts\n * from a known state. Do not call from application code.\n */\nexport function __clearShopCssCache(): void {\n cache.clear();\n}\n","/**\n * Hook for analytics event reporting (impressions + add-to-cart).\n * Impressions fire once per mount using IntersectionObserver.\n */\nimport { useEffect, useMemo, useRef, useCallback, useState } from \"react\";\nimport {\n reportImpression,\n reportAddToCart,\n observeImpression,\n type AnalyticsConfig,\n} from \"@lime-bundles/core\";\n\nexport interface UseAnalyticsOptions {\n shopDomain: string;\n appUrl: string;\n bundleGid: string;\n bundleType: string;\n enabled?: boolean;\n abTestId?: string;\n abVariant?: string;\n}\n\nexport function useAnalytics(options: UseAnalyticsOptions) {\n const impressionFiredRef = useRef(false);\n // Callback ref + state so the observer effect re-runs when the host element\n // is attached (e.g. after a `loading` branch swaps in the real DOM node).\n const [element, setElement] = useState<HTMLElement | null>(null);\n const elementRef = useCallback((el: HTMLElement | null) => {\n setElement(el);\n }, []);\n\n const config: AnalyticsConfig = useMemo(\n () => ({ shopDomain: options.shopDomain, appUrl: options.appUrl }),\n [options.shopDomain, options.appUrl],\n );\n\n // Reset the fired flag when bundle identity changes so a reused component\n // (e.g. via navigation) can report a fresh impression.\n useEffect(() => {\n impressionFiredRef.current = false;\n }, [options.bundleGid, options.abVariant]);\n\n // Set up impression observer once the element is attached\n useEffect(() => {\n if (options.enabled === false) return;\n if (impressionFiredRef.current) return;\n if (!element) return;\n\n const cleanup = observeImpression(element, () => {\n if (impressionFiredRef.current) return;\n impressionFiredRef.current = true;\n\n reportImpression(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n });\n\n return cleanup;\n }, [\n element,\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ]);\n\n const trackAddToCart = useCallback(\n (event: {\n productId: string;\n quantity: number;\n totalPrice: number;\n }) => {\n if (options.enabled === false) return;\n\n reportAddToCart(config, {\n bundleGid: options.bundleGid,\n bundleType: options.bundleType,\n productId: event.productId,\n quantity: event.quantity,\n totalPrice: event.totalPrice,\n abTestId: options.abTestId,\n abVariant: options.abVariant,\n });\n },\n [\n config,\n options.enabled,\n options.bundleGid,\n options.bundleType,\n options.abTestId,\n options.abVariant,\n ],\n );\n\n return { elementRef, trackAddToCart };\n}\n","/**\n * Mix-and-match bundle component — customer picks products from a pool.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n validateQuantity,\n type CartLineInput,\n type MixMatchBundleData,\n type ProductVariant,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\ninterface SelectedItem {\n productId: string;\n variantId: string;\n quantity: number;\n}\n\nexport function MixMatchBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"mix_match\",\n enabled: analyticsEnabled !== false,\n });\n\n const [selections, setSelections] = useState<Map<string, SelectedItem>>(\n new Map(),\n );\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"mix_match\"\n ? (result.bundle as MixMatchBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"mix_match\"\n ) {\n onError?.(\n new Error(\n `MixMatchBundle: expected bundleType=\"mix_match\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const totalQuantity = Array.from(selections.values()).reduce(\n (sum, s) => sum + s.quantity,\n 0,\n );\n\n const validation = bundle\n ? validateQuantity(totalQuantity, bundle.minQuantity, bundle.maxQuantity)\n : { valid: false, totalQuantity: 0, message: null };\n\n const toggleProduct = useCallback(\n (productId: string, variant: ProductVariant) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variant.id}`;\n if (next.has(key)) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId: variant.id, quantity: 1 });\n }\n return next;\n });\n },\n [],\n );\n\n const updateQuantity = useCallback(\n (productId: string, variantId: string, quantity: number) => {\n setSelections((prev) => {\n const next = new Map(prev);\n const key = `${productId}:${variantId}`;\n if (quantity <= 0) {\n next.delete(key);\n } else {\n next.set(key, { productId, variantId, quantity });\n }\n return next;\n });\n },\n [],\n );\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !validation.valid) return;\n\n const lines: CartLineInput[] = Array.from(selections.values()).map((s) => ({\n merchandiseId: s.variantId,\n quantity: s.quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n }));\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n\n const totalPrice = lines.reduce((sum, line) => {\n const product = bundle.products.find((p) =>\n p.variants.nodes.some((v) => v.id === line.merchandiseId),\n );\n const variant = product?.variants.nodes.find(\n (v) => v.id === line.merchandiseId,\n );\n return (\n sum + parseFloat(variant?.price.amount ?? \"0\") * line.quantity\n );\n }, 0);\n\n trackAddToCart({\n productId: bundle.products[0]?.id ?? \"\",\n quantity: totalQuantity,\n totalPrice: Math.round(totalPrice * 100) / 100,\n });\n\n setSelections(new Map());\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n selections,\n validation.valid,\n onAddToCart,\n onError,\n trackAddToCart,\n totalQuantity,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--products\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n\n const currency =\n bundle.products[0]?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--mix-match ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n {bundle.discountLabel && (\n <span className=\"lb-bundle__discount-badge\">{bundle.discountLabel}</span>\n )}\n\n <p className=\"lb-bundle__instructions\">\n {bundle.minQuantity && bundle.maxQuantity\n ? `Select ${bundle.minQuantity}–${bundle.maxQuantity} items`\n : bundle.minQuantity\n ? `Select at least ${bundle.minQuantity} items`\n : bundle.maxQuantity\n ? `Select up to ${bundle.maxQuantity} items`\n : \"Select your items\"}\n </p>\n\n <div className=\"lb-bundle__products lb-bundle__products--selectable\">\n {bundle.products.map((product) => {\n const defaultVariant =\n product.variants.nodes.find((v) => v.availableForSale) ??\n product.variants.nodes[0];\n if (!defaultVariant) return null;\n\n const key = `${product.id}:${defaultVariant.id}`;\n const selected = selections.get(key);\n\n return (\n <div\n key={product.id}\n className={`lb-bundle__product lb-bundle__product--selectable ${selected ? \"lb-bundle__product--selected\" : \"\"}`}\n >\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(defaultVariant.price.amount, currency)}\n </p>\n </div>\n <div className=\"lb-bundle__product-actions\">\n {selected ? (\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label={`Decrease ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity - 1,\n )\n }\n >\n −\n </button>\n <span>{selected.quantity}</span>\n <button\n aria-label={`Increase ${product.title}`}\n onClick={() =>\n updateQuantity(\n product.id,\n defaultVariant.id,\n selected.quantity + 1,\n )\n }\n >\n +\n </button>\n </div>\n ) : (\n <button\n className=\"lb-bundle__select-btn\"\n onClick={() => toggleProduct(product.id, defaultVariant)}\n disabled={!defaultVariant.availableForSale}\n >\n {defaultVariant.availableForSale ? \"Select\" : \"Sold out\"}\n </button>\n )}\n </div>\n </div>\n );\n })}\n </div>\n\n {validation.message && (\n <p className=\"lb-bundle__validation\" role=\"status\">\n {validation.message}\n </p>\n )}\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart || !validation.valid}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${totalQuantity} Items to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Volume bundle component — quantity selector with tier calculator.\n */\nimport { useCallback, useEffect, useState } from \"react\";\nimport {\n formatMoney,\n calculateTierSavings,\n getActiveTier,\n type CartLineInput,\n type VolumeBundleData,\n} from \"@lime-bundles/core\";\nimport { useBundleData } from \"../hooks/useBundleData\";\nimport { useAnalytics } from \"../hooks/useAnalytics\";\nimport type { BundleComponentProps } from \"./types\";\n\nexport function VolumeBundle(props: BundleComponentProps) {\n const {\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n appUrl,\n analyticsEnabled,\n onAddToCart,\n onError,\n className,\n } = props;\n\n const result = useBundleData({\n shopDomain,\n storefrontAccessToken,\n bundleGid,\n });\n\n const { elementRef, trackAddToCart } = useAnalytics({\n shopDomain,\n appUrl: appUrl ?? `https://${shopDomain}`,\n bundleGid,\n bundleType: \"volume\",\n enabled: analyticsEnabled !== false,\n });\n\n const [quantity, setQuantity] = useState(1);\n const [addingToCart, setAddingToCart] = useState(false);\n const [cartError, setCartError] = useState<string | null>(null);\n\n const bundle =\n result.status === \"success\" && result.bundle.bundleType === \"volume\"\n ? (result.bundle as VolumeBundleData)\n : null;\n\n useEffect(() => {\n if (result.status === \"error\") {\n onError?.(result.error);\n return;\n }\n if (\n result.status === \"success\" &&\n result.bundle.bundleType !== \"volume\"\n ) {\n onError?.(\n new Error(\n `VolumeBundle: expected bundleType=\"volume\", got \"${result.bundle.bundleType}\"`,\n ),\n );\n }\n }, [result, onError]);\n\n const product = bundle?.products[0];\n const basePrice = product\n ? parseFloat(product.priceRange.minVariantPrice.amount)\n : 0;\n const currency = product?.priceRange.minVariantPrice.currencyCode ?? \"USD\";\n\n const tierSavings = bundle\n ? calculateTierSavings(bundle.volumeTiers, basePrice, quantity)\n : [];\n const activeTier = bundle\n ? getActiveTier(bundle.volumeTiers, quantity)\n : null;\n\n const handleAddToCart = useCallback(async () => {\n if (!bundle || !product) return;\n\n const variant = product.variants.nodes.find((v) => v.availableForSale);\n if (!variant) return;\n\n const lines: CartLineInput[] = [\n {\n merchandiseId: variant.id,\n quantity,\n attributes: [\n { key: \"_lime_bundle_gid\", value: bundle.id },\n { key: \"_lime_bundle_type\", value: bundle.bundleType },\n ],\n },\n ];\n\n setAddingToCart(true);\n setCartError(null);\n try {\n await onAddToCart(lines);\n const unitPrice = activeTier\n ? basePrice * (1 - activeTier.discountValue / 100)\n : basePrice;\n trackAddToCart({\n productId: product.id,\n quantity,\n totalPrice: Math.round(unitPrice * quantity * 100) / 100,\n });\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n setCartError(error.message);\n onError?.(error);\n } finally {\n setAddingToCart(false);\n }\n }, [\n bundle,\n product,\n quantity,\n activeTier,\n basePrice,\n onAddToCart,\n onError,\n trackAddToCart,\n ]);\n\n if (result.status === \"loading\") {\n return (\n <div className={`lb-bundle lb-bundle--loading ${className ?? \"\"}`}>\n <div className=\"lb-skeleton lb-skeleton--title\" />\n <div className=\"lb-skeleton lb-skeleton--tiers\" />\n </div>\n );\n }\n\n if (result.status === \"error\") return null;\n if (!bundle) return null;\n if (!product) return null;\n\n return (\n <div\n ref={elementRef}\n className={`lb-bundle lb-bundle--volume ${className ?? \"\"}`}\n role=\"region\"\n aria-label={bundle.title}\n >\n <h3 className=\"lb-bundle__title\">{bundle.title}</h3>\n\n <div className=\"lb-bundle__product lb-bundle__product--volume\">\n {product.featuredImage && (\n <img\n src={product.featuredImage.url}\n alt={product.featuredImage.altText ?? product.title}\n className=\"lb-bundle__product-image\"\n loading=\"lazy\"\n />\n )}\n <div className=\"lb-bundle__product-info\">\n <p className=\"lb-bundle__product-title\">{product.title}</p>\n <p className=\"lb-bundle__product-price\">\n {formatMoney(basePrice, currency)} each\n </p>\n </div>\n </div>\n\n <div\n className=\"lb-bundle__tiers\"\n role=\"table\"\n aria-label=\"Volume discounts\"\n >\n {tierSavings.map((ts) => (\n <div\n key={ts.tier.minQuantity}\n className={`lb-bundle__tier ${ts.isActive ? \"lb-bundle__tier--active\" : \"\"}`}\n role=\"row\"\n >\n <span className=\"lb-bundle__tier-quantity\" role=\"cell\">\n {ts.tier.minQuantity}+ items\n </span>\n <span className=\"lb-bundle__tier-price\" role=\"cell\">\n {formatMoney(ts.unitPrice, currency)} each\n </span>\n <span className=\"lb-bundle__tier-savings\" role=\"cell\">\n Save {ts.savingsPercent.toFixed(0)}%\n </span>\n {ts.tier.label && (\n <span className=\"lb-bundle__tier-label\" role=\"cell\">\n {ts.tier.label}\n </span>\n )}\n </div>\n ))}\n </div>\n\n <div className=\"lb-bundle__quantity-selector\">\n <label htmlFor={`lb-qty-${bundle.id}`}>Quantity</label>\n <div className=\"lb-bundle__quantity-control\">\n <button\n aria-label=\"Decrease quantity\"\n onClick={() => setQuantity((q) => Math.max(1, q - 1))}\n >\n −\n </button>\n <input\n id={`lb-qty-${bundle.id}`}\n type=\"number\"\n min={1}\n value={quantity}\n onChange={(e) => {\n const val = parseInt(e.target.value, 10);\n if (!isNaN(val) && val > 0) setQuantity(val);\n }}\n className=\"lb-bundle__quantity-input\"\n />\n <button\n aria-label=\"Increase quantity\"\n onClick={() => setQuantity((q) => q + 1)}\n >\n +\n </button>\n </div>\n </div>\n\n {cartError && (\n <p className=\"lb-bundle__error\" role=\"alert\">\n {cartError}\n </p>\n )}\n\n <button\n className=\"lb-bundle__cta\"\n onClick={handleAddToCart}\n disabled={addingToCart}\n aria-busy={addingToCart}\n >\n {addingToCart\n ? \"Adding...\"\n : (bundle.widgetConfig.cta.ctaText ?? `Add ${quantity} to Cart`)}\n </button>\n </div>\n );\n}\n","/**\n * Client-side React hook wrapping `fetchBundlesForProduct` — the\n * React-SDK analogue of the web component's auto-detect-by-current-product\n * mode. The web component reads the handle from `location.pathname`; a\n * React app already has it on the router, so the hook takes it as an\n * explicit `productHandle` option.\n *\n * Returns every active bundle configured against the product. Inactive,\n * draft, scheduled-for-later, and expired bundles are filtered out by\n * the core helper — consumers don't have to guard.\n *\n * For SSR / RSC contexts (Hydrogen loaders, Next.js server components),\n * prefer `fetchBundlesForProduct` directly and pass the list to a client\n * component as a prop. This hook's only value-add over the async\n * function is React state management + abort-on-unmount.\n *\n * `UseBundlesForProductResult` is a discriminated union locked at v2.0.0:\n * narrowing via `status` is safe. Widening or adding states is a\n * breaking change.\n */\nimport { useState, useEffect } from \"react\";\nimport {\n fetchBundlesForProduct,\n injectCustomCss,\n type ParsedBundle,\n} from \"@lime-bundles/core\";\nimport { fetchShopCustomCss } from \"../fetchShopCustomCss\";\n\nexport interface UseBundlesForProductOptions {\n shopDomain: string;\n storefrontAccessToken: string;\n productHandle: string;\n /** Forwarded as `Shopify-Storefront-Buyer-IP` — required for SSR paths. */\n buyerIp?: string;\n}\n\nexport type UseBundlesForProductResult =\n | { status: \"loading\"; bundles: null; error: null }\n | { status: \"success\"; bundles: ParsedBundle[]; error: null }\n | { status: \"error\"; bundles: null; error: Error };\n\nconst INITIAL_STATE: UseBundlesForProductResult = {\n status: \"loading\",\n bundles: null,\n error: null,\n};\n\nexport function useBundlesForProduct(\n options: UseBundlesForProductOptions,\n): UseBundlesForProductResult {\n const [state, setState] = useState<UseBundlesForProductResult>(\n INITIAL_STATE,\n );\n\n useEffect(() => {\n const controller = new AbortController();\n setState(INITIAL_STATE);\n\n fetchBundlesForProduct({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n productHandle: options.productHandle,\n buyerIp: options.buyerIp,\n signal: controller.signal,\n })\n .then((bundles) => {\n if (controller.signal.aborted) return;\n setState({ status: \"success\", bundles, error: null });\n })\n .catch((err: unknown) => {\n if (controller.signal.aborted) return;\n setState({\n status: \"error\",\n bundles: null,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n });\n\n // Fire-and-forget custom CSS fetch so storefronts using this hook get\n // merchant styling automatically — same behaviour as `useBundleData`.\n fetchShopCustomCss({\n shopDomain: options.shopDomain,\n storefrontAccessToken: options.storefrontAccessToken,\n signal: controller.signal,\n })\n .then((css) => {\n if (controller.signal.aborted) return;\n injectCustomCss(options.shopDomain, css);\n })\n .catch(() => {\n // Silent — no merchant CSS is fine.\n });\n\n return () => {\n controller.abort();\n };\n }, [\n options.shopDomain,\n options.storefrontAccessToken,\n options.productHandle,\n options.buyerIp,\n ]);\n\n return state;\n}\n"],"mappings":"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","names":["import_react","import_core","import_core","import_core","import_react","import_core","import_react","import_core","import_jsx_runtime","import_react","import_core","import_jsx_runtime","import_react","import_core","INITIAL_STATE","import_core"]}
|