@fluid-app/cart-ui 0.1.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 +150 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +39 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +122 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +42 -0
- package/src/styles/cart.css +3 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
let react = require("react");
|
|
25
|
+
react = __toESM(react);
|
|
26
|
+
let next_script = require("next/script");
|
|
27
|
+
next_script = __toESM(next_script);
|
|
28
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
29
|
+
let _fluid_app_ui_primitives = require("@fluid-app/ui-primitives");
|
|
30
|
+
let lucide_react = require("lucide-react");
|
|
31
|
+
let react_dom = require("react-dom");
|
|
32
|
+
//#region src/components/cart-script.tsx
|
|
33
|
+
function CartScript({ subdomain, authJwt }) {
|
|
34
|
+
if (!subdomain) return null;
|
|
35
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(next_script.default, {
|
|
36
|
+
id: "fluid-cdn-script",
|
|
37
|
+
src: "https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js",
|
|
38
|
+
"data-fluid-shop": subdomain,
|
|
39
|
+
crossOrigin: "anonymous",
|
|
40
|
+
"data-auth-jwt": authJwt,
|
|
41
|
+
type: "module"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/components/cart-widget.tsx
|
|
46
|
+
function CartWidget() {
|
|
47
|
+
return react.default.createElement("fluid-cart-widget", {
|
|
48
|
+
"data-fluid-widget": "true",
|
|
49
|
+
"hide-widget": "true",
|
|
50
|
+
"is-primary": "true"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/components/cart-button.tsx
|
|
55
|
+
const MAX_SDK_POLL_ATTEMPTS = 50;
|
|
56
|
+
function CartButton({ onCheckout }) {
|
|
57
|
+
const navigateToCheckout = (0, react.useCallback)(() => {
|
|
58
|
+
if (!window.FluidCommerceSDK) {
|
|
59
|
+
console.error("FluidCommerceSDK not available");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();
|
|
64
|
+
if (!checkoutUrl) {
|
|
65
|
+
console.error("No checkout URL available");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
onCheckout?.(checkoutUrl);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("Error getting checkout URL:", error);
|
|
71
|
+
}
|
|
72
|
+
}, [onCheckout]);
|
|
73
|
+
(0, react.useEffect)(() => {
|
|
74
|
+
let timeoutId = null;
|
|
75
|
+
let attempts = 0;
|
|
76
|
+
let cancelled = false;
|
|
77
|
+
const setupSDK = () => {
|
|
78
|
+
if (cancelled) return;
|
|
79
|
+
if (window.FluidCommerceSDK) window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);
|
|
80
|
+
else if (attempts < MAX_SDK_POLL_ATTEMPTS) {
|
|
81
|
+
attempts++;
|
|
82
|
+
timeoutId = setTimeout(setupSDK, 100);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
setupSDK();
|
|
86
|
+
return () => {
|
|
87
|
+
cancelled = true;
|
|
88
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
89
|
+
};
|
|
90
|
+
}, [navigateToCheckout]);
|
|
91
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_fluid_app_ui_primitives.Button, {
|
|
92
|
+
className: "bg-primary text-primary-foreground hover:bg-primary-700 relative flex items-center gap-4 rounded-sm px-4 py-1.5",
|
|
93
|
+
onClick: () => {
|
|
94
|
+
window.fluidCart?.open();
|
|
95
|
+
},
|
|
96
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
97
|
+
className: "relative",
|
|
98
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ShoppingCart, { className: "size-5" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
99
|
+
id: "fluid-cart-count",
|
|
100
|
+
className: "bg-primary-foreground text-primary absolute -top-1 -right-2 flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-bold",
|
|
101
|
+
children: "0"
|
|
102
|
+
})]
|
|
103
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
104
|
+
className: "",
|
|
105
|
+
children: "Cart"
|
|
106
|
+
})]
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/components/shop-container.tsx
|
|
111
|
+
function ShopContainer({ children, className = "", cartScript, cartWidget }) {
|
|
112
|
+
const containerRef = (0, react.useRef)(null);
|
|
113
|
+
const [portalContainer, setPortalContainer] = (0, react.useState)(null);
|
|
114
|
+
(0, react.useEffect)(() => {
|
|
115
|
+
const currentContainer = containerRef.current;
|
|
116
|
+
if (!currentContainer) return;
|
|
117
|
+
const reactContentWrapper = document.createElement("div");
|
|
118
|
+
reactContentWrapper.id = "react-content-wrapper";
|
|
119
|
+
reactContentWrapper.style.cssText = `
|
|
120
|
+
position: relative;
|
|
121
|
+
`;
|
|
122
|
+
reactContentWrapper.className = "min-h-full";
|
|
123
|
+
currentContainer.appendChild(reactContentWrapper);
|
|
124
|
+
setPortalContainer(reactContentWrapper);
|
|
125
|
+
return () => {
|
|
126
|
+
if (currentContainer && reactContentWrapper) try {
|
|
127
|
+
currentContainer.removeChild(reactContentWrapper);
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.warn("Failed to cleanup isolated container:", e);
|
|
130
|
+
}
|
|
131
|
+
setPortalContainer(null);
|
|
132
|
+
};
|
|
133
|
+
}, []);
|
|
134
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
135
|
+
ref: containerRef,
|
|
136
|
+
className: `isolated-shop-wrapper ${className} h-full`,
|
|
137
|
+
children: portalContainer && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
138
|
+
cartScript,
|
|
139
|
+
cartWidget,
|
|
140
|
+
children
|
|
141
|
+
] }), portalContainer)
|
|
142
|
+
}) });
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
exports.CartButton = CartButton;
|
|
146
|
+
exports.CartScript = CartScript;
|
|
147
|
+
exports.CartWidget = CartWidget;
|
|
148
|
+
exports.ShopContainer = ShopContainer;
|
|
149
|
+
|
|
150
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["Script","React","Button","ShoppingCart"],"sources":["../src/components/cart-script.tsx","../src/components/cart-widget.tsx","../src/components/cart-button.tsx","../src/components/shop-container.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport Script from \"next/script\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n}\n\nexport default function CartScript({\n subdomain,\n authJwt,\n}: CartScriptProps): React.ReactNode {\n if (!subdomain) {\n return null;\n }\n\n return (\n <Script\n id=\"fluid-cdn-script\"\n src=\"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\"\n data-fluid-shop={subdomain}\n crossOrigin=\"anonymous\"\n data-auth-jwt={authJwt}\n type=\"module\"\n ></Script>\n );\n}\n","import React from \"react\";\n\nexport default function CartWidget(): React.ReactNode {\n return React.createElement(\"fluid-cart-widget\", {\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback } from \"react\";\n\ninterface CartButtonProps {\n onCheckout?: (checkoutUrl: string) => void;\n}\n\nconst MAX_SDK_POLL_ATTEMPTS = 50; // 5 seconds at 100ms intervals\n\nexport function CartButton({ onCheckout }: CartButtonProps): React.ReactNode {\n const navigateToCheckout = useCallback(() => {\n if (!window.FluidCommerceSDK) {\n console.error(\"FluidCommerceSDK not available\");\n return;\n }\n\n try {\n const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();\n if (!checkoutUrl) {\n console.error(\"No checkout URL available\");\n return;\n }\n onCheckout?.(checkoutUrl);\n } catch (error) {\n console.error(\"Error getting checkout URL:\", error);\n }\n }, [onCheckout]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let attempts = 0;\n let cancelled = false;\n\n const setupSDK = () => {\n if (cancelled) return;\n if (window.FluidCommerceSDK) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n } else if (attempts < MAX_SDK_POLL_ATTEMPTS) {\n attempts++;\n timeoutId = setTimeout(setupSDK, 100);\n }\n };\n\n setupSDK();\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [navigateToCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary-700 relative flex items-center gap-4 rounded-sm px-4 py-1.5\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-5\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n 0\n </span>\n </div>\n <span className=\"\">Cart</span>\n </Button>\n );\n}\n","\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface ShopContainerProps {\n children: React.ReactNode;\n className?: string;\n cartScript?: React.ReactNode;\n cartWidget?: React.ReactNode;\n}\n\nexport default function ShopContainer({\n children,\n className = \"\",\n cartScript,\n cartWidget,\n}: ShopContainerProps): React.ReactNode {\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(\n null,\n );\n\n useEffect(() => {\n const currentContainer = containerRef.current;\n if (!currentContainer) return;\n\n const reactContentWrapper = document.createElement(\"div\");\n reactContentWrapper.id = \"react-content-wrapper\";\n reactContentWrapper.style.cssText = `\n position: relative;\n `;\n reactContentWrapper.className = \"min-h-full\";\n\n currentContainer.appendChild(reactContentWrapper);\n\n setPortalContainer(reactContentWrapper);\n\n return () => {\n if (currentContainer && reactContentWrapper) {\n try {\n currentContainer.removeChild(reactContentWrapper);\n } catch (e) {\n console.warn(\"Failed to cleanup isolated container:\", e);\n }\n }\n setPortalContainer(null);\n };\n }, []);\n\n return (\n <>\n <div\n ref={containerRef}\n className={`isolated-shop-wrapper ${className} h-full`}\n >\n {portalContainer &&\n createPortal(\n <>\n {cartScript}\n {cartWidget}\n {children}\n </>,\n portalContainer,\n )}\n </div>\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,SAAwB,WAAW,EACjC,WACA,WACmC;AACnC,KAAI,CAAC,UACH,QAAO;AAGT,QACE,iBAAA,GAAA,kBAAA,KAACA,YAAAA,SAAD;EACE,IAAG;EACH,KAAI;EACJ,mBAAiB;EACjB,aAAY;EACZ,iBAAe;EACf,MAAK;EACG,CAAA;;;;ACxBd,SAAwB,aAA8B;AACpD,QAAOC,MAAAA,QAAM,cAAc,qBAAqB;EAC9C,qBAAqB;EACrB,eAAe;EACf,cAAc;EACf,CAAC;;;;ACGJ,MAAM,wBAAwB;AAE9B,SAAgB,WAAW,EAAE,cAAgD;CAC3E,MAAM,sBAAA,GAAA,MAAA,mBAAuC;AAC3C,MAAI,CAAC,OAAO,kBAAkB;AAC5B,WAAQ,MAAM,iCAAiC;AAC/C;;AAGF,MAAI;GACF,MAAM,cAAc,OAAO,iBAAiB,gBAAgB;AAC5D,OAAI,CAAC,aAAa;AAChB,YAAQ,MAAM,4BAA4B;AAC1C;;AAEF,gBAAa,YAAY;WAClB,OAAO;AACd,WAAQ,MAAM,+BAA+B,MAAM;;IAEpD,CAAC,WAAW,CAAC;AAEhB,EAAA,GAAA,MAAA,iBAAgB;EACd,IAAI,YAAkD;EACtD,IAAI,WAAW;EACf,IAAI,YAAY;EAEhB,MAAM,iBAAiB;AACrB,OAAI,UAAW;AACf,OAAI,OAAO,iBACT,QAAO,iBAAiB,cAAc,mBAAmB;YAChD,WAAW,uBAAuB;AAC3C;AACA,gBAAY,WAAW,UAAU,IAAI;;;AAIzC,YAAU;AACV,eAAa;AACX,eAAY;AACZ,OAAI,UAAW,cAAa,UAAU;;IAEvC,CAAC,mBAAmB,CAAC;AAExB,QACE,iBAAA,GAAA,kBAAA,MAACC,yBAAAA,QAAD;EACE,WAAU;EACV,eAAe;AACb,UAAO,WAAW,MAAM;;YAH5B,CAME,iBAAA,GAAA,kBAAA,MAAC,OAAD;GAAK,WAAU;aAAf,CACE,iBAAA,GAAA,kBAAA,KAACC,aAAAA,cAAD,EAAc,WAAU,UAAW,CAAA,EACnC,iBAAA,GAAA,kBAAA,KAAC,QAAD;IACE,IAAG;IACH,WAAU;cACX;IAEM,CAAA,CACH;MACN,iBAAA,GAAA,kBAAA,KAAC,QAAD;GAAM,WAAU;aAAG;GAAW,CAAA,CACvB;;;;;AC1Db,SAAwB,cAAc,EACpC,UACA,YAAY,IACZ,YACA,cACsC;CACtC,MAAM,gBAAA,GAAA,MAAA,QAAsC,KAAK;CACjD,MAAM,CAAC,iBAAiB,uBAAA,GAAA,MAAA,UACtB,KACD;AAED,EAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,mBAAmB,aAAa;AACtC,MAAI,CAAC,iBAAkB;EAEvB,MAAM,sBAAsB,SAAS,cAAc,MAAM;AACzD,sBAAoB,KAAK;AACzB,sBAAoB,MAAM,UAAU;;;AAGpC,sBAAoB,YAAY;AAEhC,mBAAiB,YAAY,oBAAoB;AAEjD,qBAAmB,oBAAoB;AAEvC,eAAa;AACX,OAAI,oBAAoB,oBACtB,KAAI;AACF,qBAAiB,YAAY,oBAAoB;YAC1C,GAAG;AACV,YAAQ,KAAK,yCAAyC,EAAE;;AAG5D,sBAAmB,KAAK;;IAEzB,EAAE,CAAC;AAEN,QACE,iBAAA,GAAA,kBAAA,KAAA,kBAAA,UAAA,EAAA,UACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,KAAK;EACL,WAAW,yBAAyB,UAAU;YAE7C,oBAAA,GAAA,UAAA,cAEG,iBAAA,GAAA,kBAAA,MAAA,kBAAA,UAAA,EAAA,UAAA;GACG;GACA;GACA;GACA,EAAA,CAAA,EACH,gBACD;EACC,CAAA,EACL,CAAA"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/components/cart-script.d.ts
|
|
4
|
+
interface CartScriptProps {
|
|
5
|
+
subdomain: string;
|
|
6
|
+
authJwt?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function CartScript({
|
|
9
|
+
subdomain,
|
|
10
|
+
authJwt
|
|
11
|
+
}: CartScriptProps): React.ReactNode;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/components/cart-widget.d.ts
|
|
14
|
+
declare function CartWidget(): React.ReactNode;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/components/cart-button.d.ts
|
|
17
|
+
interface CartButtonProps {
|
|
18
|
+
onCheckout?: (checkoutUrl: string) => void;
|
|
19
|
+
}
|
|
20
|
+
declare function CartButton({
|
|
21
|
+
onCheckout
|
|
22
|
+
}: CartButtonProps): React.ReactNode;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/components/shop-container.d.ts
|
|
25
|
+
interface ShopContainerProps {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
className?: string;
|
|
28
|
+
cartScript?: React.ReactNode;
|
|
29
|
+
cartWidget?: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
declare function ShopContainer({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
cartScript,
|
|
35
|
+
cartWidget
|
|
36
|
+
}: ShopContainerProps): React.ReactNode;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { CartButton, CartScript, CartWidget, ShopContainer };
|
|
39
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/components/cart-script.tsx","../src/components/cart-widget.tsx","../src/components/cart-button.tsx","../src/components/shop-container.tsx"],"mappings":";;;UAKU,eAAA;EACR,SAAA;EACA,OAAA;AAAA;AAAA,iBAGsB,UAAA,CAAA;EACtB,SAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;iBCXH,UAAA,CAAA,GAAc,KAAA,CAAM,SAAA;;;UCIlC,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UCPzD,kBAAA;EACR,QAAA,EAAU,KAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,KAAA,CAAM,SAAA;EACnB,UAAA,GAAa,KAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,KAAA,CAAM,SAAA"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
//#region src/components/cart-script.d.ts
|
|
4
|
+
interface CartScriptProps {
|
|
5
|
+
subdomain: string;
|
|
6
|
+
authJwt?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function CartScript({
|
|
9
|
+
subdomain,
|
|
10
|
+
authJwt
|
|
11
|
+
}: CartScriptProps): React.ReactNode;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/components/cart-widget.d.ts
|
|
14
|
+
declare function CartWidget(): React.ReactNode;
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/components/cart-button.d.ts
|
|
17
|
+
interface CartButtonProps {
|
|
18
|
+
onCheckout?: (checkoutUrl: string) => void;
|
|
19
|
+
}
|
|
20
|
+
declare function CartButton({
|
|
21
|
+
onCheckout
|
|
22
|
+
}: CartButtonProps): React.ReactNode;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/components/shop-container.d.ts
|
|
25
|
+
interface ShopContainerProps {
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
className?: string;
|
|
28
|
+
cartScript?: React.ReactNode;
|
|
29
|
+
cartWidget?: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
declare function ShopContainer({
|
|
32
|
+
children,
|
|
33
|
+
className,
|
|
34
|
+
cartScript,
|
|
35
|
+
cartWidget
|
|
36
|
+
}: ShopContainerProps): React.ReactNode;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { CartButton, CartScript, CartWidget, ShopContainer };
|
|
39
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/components/cart-script.tsx","../src/components/cart-widget.tsx","../src/components/cart-button.tsx","../src/components/shop-container.tsx"],"mappings":";;;UAKU,eAAA;EACR,SAAA;EACA,OAAA;AAAA;AAAA,iBAGsB,UAAA,CAAA;EACtB,SAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;iBCXH,UAAA,CAAA,GAAc,KAAA,CAAM,SAAA;;;UCIlC,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UCPzD,kBAAA;EACR,QAAA,EAAU,KAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,KAAA,CAAM,SAAA;EACnB,UAAA,GAAa,KAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,KAAA,CAAM,SAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React, { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import Script from "next/script";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { Button } from "@fluid-app/ui-primitives";
|
|
5
|
+
import { ShoppingCart } from "lucide-react";
|
|
6
|
+
import { createPortal } from "react-dom";
|
|
7
|
+
//#region src/components/cart-script.tsx
|
|
8
|
+
function CartScript({ subdomain, authJwt }) {
|
|
9
|
+
if (!subdomain) return null;
|
|
10
|
+
return /* @__PURE__ */ jsx(Script, {
|
|
11
|
+
id: "fluid-cdn-script",
|
|
12
|
+
src: "https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js",
|
|
13
|
+
"data-fluid-shop": subdomain,
|
|
14
|
+
crossOrigin: "anonymous",
|
|
15
|
+
"data-auth-jwt": authJwt,
|
|
16
|
+
type: "module"
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/components/cart-widget.tsx
|
|
21
|
+
function CartWidget() {
|
|
22
|
+
return React.createElement("fluid-cart-widget", {
|
|
23
|
+
"data-fluid-widget": "true",
|
|
24
|
+
"hide-widget": "true",
|
|
25
|
+
"is-primary": "true"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/components/cart-button.tsx
|
|
30
|
+
const MAX_SDK_POLL_ATTEMPTS = 50;
|
|
31
|
+
function CartButton({ onCheckout }) {
|
|
32
|
+
const navigateToCheckout = useCallback(() => {
|
|
33
|
+
if (!window.FluidCommerceSDK) {
|
|
34
|
+
console.error("FluidCommerceSDK not available");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();
|
|
39
|
+
if (!checkoutUrl) {
|
|
40
|
+
console.error("No checkout URL available");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
onCheckout?.(checkoutUrl);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Error getting checkout URL:", error);
|
|
46
|
+
}
|
|
47
|
+
}, [onCheckout]);
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
let timeoutId = null;
|
|
50
|
+
let attempts = 0;
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
const setupSDK = () => {
|
|
53
|
+
if (cancelled) return;
|
|
54
|
+
if (window.FluidCommerceSDK) window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);
|
|
55
|
+
else if (attempts < MAX_SDK_POLL_ATTEMPTS) {
|
|
56
|
+
attempts++;
|
|
57
|
+
timeoutId = setTimeout(setupSDK, 100);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
setupSDK();
|
|
61
|
+
return () => {
|
|
62
|
+
cancelled = true;
|
|
63
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
64
|
+
};
|
|
65
|
+
}, [navigateToCheckout]);
|
|
66
|
+
return /* @__PURE__ */ jsxs(Button, {
|
|
67
|
+
className: "bg-primary text-primary-foreground hover:bg-primary-700 relative flex items-center gap-4 rounded-sm px-4 py-1.5",
|
|
68
|
+
onClick: () => {
|
|
69
|
+
window.fluidCart?.open();
|
|
70
|
+
},
|
|
71
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
72
|
+
className: "relative",
|
|
73
|
+
children: [/* @__PURE__ */ jsx(ShoppingCart, { className: "size-5" }), /* @__PURE__ */ jsx("span", {
|
|
74
|
+
id: "fluid-cart-count",
|
|
75
|
+
className: "bg-primary-foreground text-primary absolute -top-1 -right-2 flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-bold",
|
|
76
|
+
children: "0"
|
|
77
|
+
})]
|
|
78
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
79
|
+
className: "",
|
|
80
|
+
children: "Cart"
|
|
81
|
+
})]
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/components/shop-container.tsx
|
|
86
|
+
function ShopContainer({ children, className = "", cartScript, cartWidget }) {
|
|
87
|
+
const containerRef = useRef(null);
|
|
88
|
+
const [portalContainer, setPortalContainer] = useState(null);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const currentContainer = containerRef.current;
|
|
91
|
+
if (!currentContainer) return;
|
|
92
|
+
const reactContentWrapper = document.createElement("div");
|
|
93
|
+
reactContentWrapper.id = "react-content-wrapper";
|
|
94
|
+
reactContentWrapper.style.cssText = `
|
|
95
|
+
position: relative;
|
|
96
|
+
`;
|
|
97
|
+
reactContentWrapper.className = "min-h-full";
|
|
98
|
+
currentContainer.appendChild(reactContentWrapper);
|
|
99
|
+
setPortalContainer(reactContentWrapper);
|
|
100
|
+
return () => {
|
|
101
|
+
if (currentContainer && reactContentWrapper) try {
|
|
102
|
+
currentContainer.removeChild(reactContentWrapper);
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.warn("Failed to cleanup isolated container:", e);
|
|
105
|
+
}
|
|
106
|
+
setPortalContainer(null);
|
|
107
|
+
};
|
|
108
|
+
}, []);
|
|
109
|
+
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx("div", {
|
|
110
|
+
ref: containerRef,
|
|
111
|
+
className: `isolated-shop-wrapper ${className} h-full`,
|
|
112
|
+
children: portalContainer && createPortal(/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
113
|
+
cartScript,
|
|
114
|
+
cartWidget,
|
|
115
|
+
children
|
|
116
|
+
] }), portalContainer)
|
|
117
|
+
}) });
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
export { CartButton, CartScript, CartWidget, ShopContainer };
|
|
121
|
+
|
|
122
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/components/cart-script.tsx","../src/components/cart-widget.tsx","../src/components/cart-button.tsx","../src/components/shop-container.tsx"],"sourcesContent":["\"use client\";\n\nimport React from \"react\";\nimport Script from \"next/script\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n}\n\nexport default function CartScript({\n subdomain,\n authJwt,\n}: CartScriptProps): React.ReactNode {\n if (!subdomain) {\n return null;\n }\n\n return (\n <Script\n id=\"fluid-cdn-script\"\n src=\"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\"\n data-fluid-shop={subdomain}\n crossOrigin=\"anonymous\"\n data-auth-jwt={authJwt}\n type=\"module\"\n ></Script>\n );\n}\n","import React from \"react\";\n\nexport default function CartWidget(): React.ReactNode {\n return React.createElement(\"fluid-cart-widget\", {\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback } from \"react\";\n\ninterface CartButtonProps {\n onCheckout?: (checkoutUrl: string) => void;\n}\n\nconst MAX_SDK_POLL_ATTEMPTS = 50; // 5 seconds at 100ms intervals\n\nexport function CartButton({ onCheckout }: CartButtonProps): React.ReactNode {\n const navigateToCheckout = useCallback(() => {\n if (!window.FluidCommerceSDK) {\n console.error(\"FluidCommerceSDK not available\");\n return;\n }\n\n try {\n const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();\n if (!checkoutUrl) {\n console.error(\"No checkout URL available\");\n return;\n }\n onCheckout?.(checkoutUrl);\n } catch (error) {\n console.error(\"Error getting checkout URL:\", error);\n }\n }, [onCheckout]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let attempts = 0;\n let cancelled = false;\n\n const setupSDK = () => {\n if (cancelled) return;\n if (window.FluidCommerceSDK) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n } else if (attempts < MAX_SDK_POLL_ATTEMPTS) {\n attempts++;\n timeoutId = setTimeout(setupSDK, 100);\n }\n };\n\n setupSDK();\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [navigateToCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary-700 relative flex items-center gap-4 rounded-sm px-4 py-1.5\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-5\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex h-4 w-4 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n 0\n </span>\n </div>\n <span className=\"\">Cart</span>\n </Button>\n );\n}\n","\"use client\";\n\nimport React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface ShopContainerProps {\n children: React.ReactNode;\n className?: string;\n cartScript?: React.ReactNode;\n cartWidget?: React.ReactNode;\n}\n\nexport default function ShopContainer({\n children,\n className = \"\",\n cartScript,\n cartWidget,\n}: ShopContainerProps): React.ReactNode {\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(\n null,\n );\n\n useEffect(() => {\n const currentContainer = containerRef.current;\n if (!currentContainer) return;\n\n const reactContentWrapper = document.createElement(\"div\");\n reactContentWrapper.id = \"react-content-wrapper\";\n reactContentWrapper.style.cssText = `\n position: relative;\n `;\n reactContentWrapper.className = \"min-h-full\";\n\n currentContainer.appendChild(reactContentWrapper);\n\n setPortalContainer(reactContentWrapper);\n\n return () => {\n if (currentContainer && reactContentWrapper) {\n try {\n currentContainer.removeChild(reactContentWrapper);\n } catch (e) {\n console.warn(\"Failed to cleanup isolated container:\", e);\n }\n }\n setPortalContainer(null);\n };\n }, []);\n\n return (\n <>\n <div\n ref={containerRef}\n className={`isolated-shop-wrapper ${className} h-full`}\n >\n {portalContainer &&\n createPortal(\n <>\n {cartScript}\n {cartWidget}\n {children}\n </>,\n portalContainer,\n )}\n </div>\n </>\n );\n}\n"],"mappings":";;;;;;;AAUA,SAAwB,WAAW,EACjC,WACA,WACmC;AACnC,KAAI,CAAC,UACH,QAAO;AAGT,QACE,oBAAC,QAAD;EACE,IAAG;EACH,KAAI;EACJ,mBAAiB;EACjB,aAAY;EACZ,iBAAe;EACf,MAAK;EACG,CAAA;;;;ACxBd,SAAwB,aAA8B;AACpD,QAAO,MAAM,cAAc,qBAAqB;EAC9C,qBAAqB;EACrB,eAAe;EACf,cAAc;EACf,CAAC;;;;ACGJ,MAAM,wBAAwB;AAE9B,SAAgB,WAAW,EAAE,cAAgD;CAC3E,MAAM,qBAAqB,kBAAkB;AAC3C,MAAI,CAAC,OAAO,kBAAkB;AAC5B,WAAQ,MAAM,iCAAiC;AAC/C;;AAGF,MAAI;GACF,MAAM,cAAc,OAAO,iBAAiB,gBAAgB;AAC5D,OAAI,CAAC,aAAa;AAChB,YAAQ,MAAM,4BAA4B;AAC1C;;AAEF,gBAAa,YAAY;WAClB,OAAO;AACd,WAAQ,MAAM,+BAA+B,MAAM;;IAEpD,CAAC,WAAW,CAAC;AAEhB,iBAAgB;EACd,IAAI,YAAkD;EACtD,IAAI,WAAW;EACf,IAAI,YAAY;EAEhB,MAAM,iBAAiB;AACrB,OAAI,UAAW;AACf,OAAI,OAAO,iBACT,QAAO,iBAAiB,cAAc,mBAAmB;YAChD,WAAW,uBAAuB;AAC3C;AACA,gBAAY,WAAW,UAAU,IAAI;;;AAIzC,YAAU;AACV,eAAa;AACX,eAAY;AACZ,OAAI,UAAW,cAAa,UAAU;;IAEvC,CAAC,mBAAmB,CAAC;AAExB,QACE,qBAAC,QAAD;EACE,WAAU;EACV,eAAe;AACb,UAAO,WAAW,MAAM;;YAH5B,CAME,qBAAC,OAAD;GAAK,WAAU;aAAf,CACE,oBAAC,cAAD,EAAc,WAAU,UAAW,CAAA,EACnC,oBAAC,QAAD;IACE,IAAG;IACH,WAAU;cACX;IAEM,CAAA,CACH;MACN,oBAAC,QAAD;GAAM,WAAU;aAAG;GAAW,CAAA,CACvB;;;;;AC1Db,SAAwB,cAAc,EACpC,UACA,YAAY,IACZ,YACA,cACsC;CACtC,MAAM,eAAe,OAAuB,KAAK;CACjD,MAAM,CAAC,iBAAiB,sBAAsB,SAC5C,KACD;AAED,iBAAgB;EACd,MAAM,mBAAmB,aAAa;AACtC,MAAI,CAAC,iBAAkB;EAEvB,MAAM,sBAAsB,SAAS,cAAc,MAAM;AACzD,sBAAoB,KAAK;AACzB,sBAAoB,MAAM,UAAU;;;AAGpC,sBAAoB,YAAY;AAEhC,mBAAiB,YAAY,oBAAoB;AAEjD,qBAAmB,oBAAoB;AAEvC,eAAa;AACX,OAAI,oBAAoB,oBACtB,KAAI;AACF,qBAAiB,YAAY,oBAAoB;YAC1C,GAAG;AACV,YAAQ,KAAK,yCAAyC,EAAE;;AAG5D,sBAAmB,KAAK;;IAEzB,EAAE,CAAC;AAEN,QACE,oBAAA,UAAA,EAAA,UACE,oBAAC,OAAD;EACE,KAAK;EACL,WAAW,yBAAyB,UAAU;YAE7C,mBACC,aACE,qBAAA,UAAA,EAAA,UAAA;GACG;GACA;GACA;GACA,EAAA,CAAA,EACH,gBACD;EACC,CAAA,EACL,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluid-app/cart-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"src/styles"
|
|
7
|
+
],
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./styles/cart.css": "./src/styles/cart.css"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^24",
|
|
19
|
+
"@types/react": "^19.2.13",
|
|
20
|
+
"@types/react-dom": "^19.2.3",
|
|
21
|
+
"lucide-react": "^0.575.0",
|
|
22
|
+
"tailwindcss": "^4.1.8",
|
|
23
|
+
"tsdown": "^0.21.0",
|
|
24
|
+
"typescript": "^5",
|
|
25
|
+
"@fluid-app/typescript-config": "0.0.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"lucide-react": ">=0.400.0",
|
|
29
|
+
"next": ">=15.0.0",
|
|
30
|
+
"react": ">=18.0.0",
|
|
31
|
+
"react-dom": ">=18.0.0",
|
|
32
|
+
"@fluid-app/ui-primitives": "^0.1.10"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"transit": "tsgo --emitDeclarationOnly",
|
|
36
|
+
"build": "tsdown",
|
|
37
|
+
"dev": "tsdown --watch",
|
|
38
|
+
"lint": "oxlint --deny-warnings",
|
|
39
|
+
"lint:fix": "oxlint --fix --deny-warnings",
|
|
40
|
+
"typecheck": "tsgo --noEmit"
|
|
41
|
+
}
|
|
42
|
+
}
|