@fluid-app/cart-ui 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -32,9 +32,15 @@ let react_jsx_runtime = require("react/jsx-runtime");
32
32
  const SCRIPT_ID = "fluid-cdn-script";
33
33
  const LEAD_CAPTURE_ID = "fluid-lead-capture-suppress";
34
34
  const DEFAULT_SCRIPT_SRC = "https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js";
35
- function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug }) {
35
+ function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug, country, cookieDomain, shareGuid }) {
36
36
  const authJwtRef = (0, react.useRef)(authJwt);
37
37
  authJwtRef.current = authJwt;
38
+ const countryRef = (0, react.useRef)(country);
39
+ countryRef.current = country;
40
+ const cookieDomainRef = (0, react.useRef)(cookieDomain);
41
+ cookieDomainRef.current = cookieDomain;
42
+ const shareGuidRef = (0, react.useRef)(shareGuid);
43
+ shareGuidRef.current = shareGuid;
38
44
  (0, react.useEffect)(() => {
39
45
  if (!subdomain) return;
40
46
  if (document.getElementById(SCRIPT_ID)) return;
@@ -48,6 +54,9 @@ function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug
48
54
  else if (authJwtRef.current) script.dataset.authJwt = authJwtRef.current;
49
55
  if (apiBaseUrl) script.dataset.fluidApiBaseUrl = apiBaseUrl;
50
56
  if (debug) script.dataset.debug = "true";
57
+ if (countryRef.current) script.dataset.fluidCountry = countryRef.current;
58
+ if (cookieDomainRef.current) script.dataset.fluidCookieDomain = cookieDomainRef.current;
59
+ if (shareGuidRef.current) script.dataset.shareGuid = shareGuidRef.current;
51
60
  document.head.appendChild(script);
52
61
  const leadCapture = document.createElement("fluid-lead-capture-widget");
53
62
  leadCapture.id = LEAD_CAPTURE_ID;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["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":["import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\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 { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\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 if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\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, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAeA,MAAM,YAAY;AAClB,MAAM,kBAAkB;AACxB,MAAM,qBACJ;AAEF,SAAwB,WAAW,EACjC,WACA,SACA,SACA,WACA,YACA,SACmC;CAInC,MAAM,cAAA,GAAA,MAAA,QAAoB,QAAQ;AAClC,YAAW,UAAU;AAErB,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,CAAC,UAAW;AAGhB,MAAI,SAAS,eAAe,UAAU,CAAE;EAExC,MAAM,SAAS,SAAS,cAAc,SAAS;AAC/C,SAAO,KAAK;AACZ,SAAO,MAAM,aAAa;AAC1B,SAAO,OAAO;AACd,SAAO,cAAc;AACrB,SAAO,QAAQ,YAAY;AAC3B,MAAI,QACF,QAAO,QAAQ,UAAU;WAChB,WAAW,QACpB,QAAO,QAAQ,UAAU,WAAW;AAEtC,MAAI,WACF,QAAO,QAAQ,kBAAkB;AAEnC,MAAI,MACF,QAAO,QAAQ,QAAQ;AAEzB,WAAS,KAAK,YAAY,OAAO;EAIjC,MAAM,cAAc,SAAS,cAAc,4BAA4B;AACvE,cAAY,KAAK;AACjB,cAAY,aAAa,eAAe,OAAO;AAC/C,WAAS,KAAK,YAAY,YAAY;AAEtC,eAAa;GACX,MAAM,WAAW,SAAS,eAAe,UAAU;AACnD,OAAI,SAAU,UAAS,QAAQ;GAC/B,MAAM,sBAAsB,SAAS,eAAe,gBAAgB;AACpE,OAAI,oBAAqB,qBAAoB,QAAQ;;IAEtD;EAAC;EAAW;EAAS;EAAW;EAAY;EAAM,CAAC;AAEtD,QAAO;;;;ACnET,SAAwB,WAAW,EACjC,SACmC;CACnC,MAAM,CAAC,SAAS,eAAA,GAAA,MAAA,UAAuB,MAAM;CAC7C,MAAM,aAAA,GAAA,MAAA,QAAuC,KAAK;AAElD,EAAA,GAAA,MAAA,iBAAgB;AACd,aAAW,KAAK;IACf,EAAE,CAAC;AAEN,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,CAAC,QAAS;EACd,MAAM,KAAK,UAAU;AACrB,MAAI,CAAC,GAAI;AACT,MAAI,MACF,IAAG,aAAa,SAAS,KAAK,UAAU,MAAM,CAAC;MAE/C,IAAG,gBAAgB,QAAQ;IAE5B,CAAC,OAAO,QAAQ,CAAC;CAEpB,MAAM,SAASA,MAAAA,QAAM,cAAc,qBAAqB;EACtD,MAAM,OAA2B;AAC/B,aAAU,UAAU;;EAEtB,qBAAqB;EACrB,eAAe;EACf,cAAc;EACf,CAAC;AAIF,KAAI,QACF,SAAA,GAAA,UAAA,cAAoB,QAAQ,SAAS,KAAK;AAG5C,QAAO;;;;ACZT,MAAM,wBAAwB;AAE9B,SAAgB,WAAW,EAAE,cAAgD;CAC3E,MAAM,EAAE,OAAA,GAAA,6CAAA,qBAA0B;CAClC,MAAM,CAAC,cAAc,oBAAA,GAAA,MAAA,UAA4B,EAAE;CAEnD,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,kBAAkB;AAC3B,QAAI,WACF,QAAO,iBAAiB,cAAc,mBAAmB;IAE3D,MAAM,QAAQ,OAAO,cAAc,oBAAoB;AACvD,QAAI,SAAS,KACX,iBAAgB,MAAM;cAEf,WAAW,uBAAuB;AAC3C;AACA,gBAAY,WAAW,UAAU,IAAI;;;AAIzC,YAAU;AACV,eAAa;AACX,eAAY;AACZ,OAAI,UAAW,cAAa,UAAU;;IAEvC,CAAC,oBAAoB,WAAW,CAAC;AAEpC,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;cAET;IACI,CAAA,CACH;MACN,iBAAA,GAAA,kBAAA,KAAC,QAAD,EAAA,UAAO,EAAE,OAAO,EAAQ,CAAA,CACjB;;;;;AC1Fb,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"}
1
+ {"version":3,"file":"index.cjs","names":["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":["import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n /**\n * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with\n * before any cart operation. Renders as `data-fluid-country` on the SDK\n * script tag; the SDK reads it at init time and uses it for cart creation\n * paths so they don't fall back to locale-derived country. Requires\n * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country\n * parsing) on the CDN.\n */\n country?: string;\n /**\n * Explicit cookie domain for the FairShare SDK, rendered as\n * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes\n * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain\n * instead of inferring an apex/brand domain from the hostname.\n *\n * This is required in the portal's hub/tenant model: the SDK's hostname\n * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the\n * shared `.portal.fluid.app` level, which leaks attribution across tenants.\n * Pass the current tenant host so cookies stay isolated per company.\n * Storefronts load the SDK directly (not via this component) and keep the\n * default brand-wide behavior. Requires a web-widgets bundle that parses\n * `data-fluid-cookie-domain`.\n */\n cookieDomain?: string;\n /**\n * Fair Share attribution GUID for the logged-in member, rendered as\n * `data-share-guid` on the SDK script tag. This is the FairShare SDK's\n * highest-priority attribution override: when present the SDK attributes\n * the member's in-portal shopping to this share GUID. A portal belongs to a\n * company (not a rep), so pass the current member's company-scoped share\n * GUID (`account.share_guid`) — reps only; omit for customers so they are\n * not self-attributed.\n */\n shareGuid?: string;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n country,\n cookieDomain,\n shareGuid,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n const countryRef = useRef(country);\n countryRef.current = country;\n const cookieDomainRef = useRef(cookieDomain);\n cookieDomainRef.current = cookieDomain;\n const shareGuidRef = useRef(shareGuid);\n shareGuidRef.current = shareGuid;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n if (countryRef.current) {\n script.dataset.fluidCountry = countryRef.current;\n }\n if (cookieDomainRef.current) {\n script.dataset.fluidCookieDomain = cookieDomainRef.current;\n }\n if (shareGuidRef.current) {\n script.dataset.shareGuid = shareGuidRef.current;\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n // `authJwt`, `country`, `cookieDomain`, and `shareGuid` are intentionally\n // omitted from deps: the SDK reads data attributes only once when this\n // module script first executes.\n // Re-inserting the same module URL after those props change does not\n // re-run FairShare initialization, so treat them as inject-time values.\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\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 { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\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 if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\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, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import 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":"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"}
package/dist/index.d.cts CHANGED
@@ -12,6 +12,40 @@ interface CartScriptProps {
12
12
  apiBaseUrl?: string;
13
13
  /** Enable SDK debug logging. */
14
14
  debug?: boolean;
15
+ /**
16
+ * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with
17
+ * before any cart operation. Renders as `data-fluid-country` on the SDK
18
+ * script tag; the SDK reads it at init time and uses it for cart creation
19
+ * paths so they don't fall back to locale-derived country. Requires
20
+ * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country
21
+ * parsing) on the CDN.
22
+ */
23
+ country?: string;
24
+ /**
25
+ * Explicit cookie domain for the FairShare SDK, rendered as
26
+ * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes
27
+ * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain
28
+ * instead of inferring an apex/brand domain from the hostname.
29
+ *
30
+ * This is required in the portal's hub/tenant model: the SDK's hostname
31
+ * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the
32
+ * shared `.portal.fluid.app` level, which leaks attribution across tenants.
33
+ * Pass the current tenant host so cookies stay isolated per company.
34
+ * Storefronts load the SDK directly (not via this component) and keep the
35
+ * default brand-wide behavior. Requires a web-widgets bundle that parses
36
+ * `data-fluid-cookie-domain`.
37
+ */
38
+ cookieDomain?: string;
39
+ /**
40
+ * Fair Share attribution GUID for the logged-in member, rendered as
41
+ * `data-share-guid` on the SDK script tag. This is the FairShare SDK's
42
+ * highest-priority attribution override: when present the SDK attributes
43
+ * the member's in-portal shopping to this share GUID. A portal belongs to a
44
+ * company (not a rep), so pass the current member's company-scoped share
45
+ * GUID (`account.share_guid`) — reps only; omit for customers so they are
46
+ * not self-attributed.
47
+ */
48
+ shareGuid?: string;
15
49
  }
16
50
  declare function CartScript({
17
51
  subdomain,
@@ -19,7 +53,10 @@ declare function CartScript({
19
53
  bffMode,
20
54
  scriptSrc,
21
55
  apiBaseUrl,
22
- debug
56
+ debug,
57
+ country,
58
+ cookieDomain,
59
+ shareGuid
23
60
  }: CartScriptProps): React.ReactNode;
24
61
  //#endregion
25
62
  //#region src/components/cart-widget.d.ts
@@ -1 +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":";;;UAEU,eAAA;EACR,SAAA;EACA,OAAA;;EAEA,OAAA;;EAEA,SAAA;;EAEA,UAAA;;EAEA,KAAA;AAAA;AAAA,iBAQsB,UAAA,CAAA;EACtB,SAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UCxBjB,eAAA;EACR,KAAA,GAAQ,MAAA;AAAA;AAAA,iBAGc,UAAA,CAAA;EACtB;AAAA,GACC,eAAA,GAAkB,OAAA,CAAM,SAAA;;;;YCDf,MAAA;IACR,gBAAA;MACE,cAAA;MACA,aAAA,GAAgB,QAAA;IAAA;IAElB,YAAA;MACE,gBAAA;MACA,SAAA;MACA,oBAAA,GAAuB,OAAA;QACrB,QAAA;QACA,OAAA;MAAA,MACI,OAAA;IAAA;IAER,SAAA;MACE,IAAA;IAAA;EAAA;AAAA;AAAA,UAKI,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,OAAA,CAAM,SAAA;;;UC9BzD,kBAAA;EACR,QAAA,EAAU,OAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,OAAA,CAAM,SAAA;EACnB,UAAA,GAAa,OAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,OAAA,CAAM,SAAA"}
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":";;;UAEU,eAAA;EACR,SAAA;EACA,OAAA;;EAEA,OAAA;;EAEA,SAAA;;EAEA,UAAA;;EAEA,KAAA;;;;;;;;AAkCA;EAzBA,OAAA;;;;;;;;;;;;;;;EAeA,YAAA;;;;;;;;;;EAUA,SAAA;AAAA;AAAA,iBAQsB,UAAA,CAAA;EACtB,SAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,KAAA;EACA,OAAA;EACA,YAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UC7DjB,eAAA;EACR,KAAA,GAAQ,MAAA;AAAA;AAAA,iBAGc,UAAA,CAAA;EACtB;AAAA,GACC,eAAA,GAAkB,OAAA,CAAM,SAAA;;;;YCDf,MAAA;IACR,gBAAA;MACE,cAAA;MACA,aAAA,GAAgB,QAAA;IAAA;IAElB,YAAA;MACE,gBAAA;MACA,SAAA;MACA,oBAAA,GAAuB,OAAA;QACrB,QAAA;QACA,OAAA;MAAA,MACI,OAAA;IAAA;IAER,SAAA;MACE,IAAA;IAAA;EAAA;AAAA;AAAA,UAKI,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,OAAA,CAAM,SAAA;;;UC9BzD,kBAAA;EACR,QAAA,EAAU,OAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,OAAA,CAAM,SAAA;EACnB,UAAA,GAAa,OAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,OAAA,CAAM,SAAA"}
package/dist/index.d.mts CHANGED
@@ -12,6 +12,40 @@ interface CartScriptProps {
12
12
  apiBaseUrl?: string;
13
13
  /** Enable SDK debug logging. */
14
14
  debug?: boolean;
15
+ /**
16
+ * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with
17
+ * before any cart operation. Renders as `data-fluid-country` on the SDK
18
+ * script tag; the SDK reads it at init time and uses it for cart creation
19
+ * paths so they don't fall back to locale-derived country. Requires
20
+ * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country
21
+ * parsing) on the CDN.
22
+ */
23
+ country?: string;
24
+ /**
25
+ * Explicit cookie domain for the FairShare SDK, rendered as
26
+ * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes
27
+ * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain
28
+ * instead of inferring an apex/brand domain from the hostname.
29
+ *
30
+ * This is required in the portal's hub/tenant model: the SDK's hostname
31
+ * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the
32
+ * shared `.portal.fluid.app` level, which leaks attribution across tenants.
33
+ * Pass the current tenant host so cookies stay isolated per company.
34
+ * Storefronts load the SDK directly (not via this component) and keep the
35
+ * default brand-wide behavior. Requires a web-widgets bundle that parses
36
+ * `data-fluid-cookie-domain`.
37
+ */
38
+ cookieDomain?: string;
39
+ /**
40
+ * Fair Share attribution GUID for the logged-in member, rendered as
41
+ * `data-share-guid` on the SDK script tag. This is the FairShare SDK's
42
+ * highest-priority attribution override: when present the SDK attributes
43
+ * the member's in-portal shopping to this share GUID. A portal belongs to a
44
+ * company (not a rep), so pass the current member's company-scoped share
45
+ * GUID (`account.share_guid`) — reps only; omit for customers so they are
46
+ * not self-attributed.
47
+ */
48
+ shareGuid?: string;
15
49
  }
16
50
  declare function CartScript({
17
51
  subdomain,
@@ -19,7 +53,10 @@ declare function CartScript({
19
53
  bffMode,
20
54
  scriptSrc,
21
55
  apiBaseUrl,
22
- debug
56
+ debug,
57
+ country,
58
+ cookieDomain,
59
+ shareGuid
23
60
  }: CartScriptProps): React.ReactNode;
24
61
  //#endregion
25
62
  //#region src/components/cart-widget.d.ts
@@ -1 +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":";;;UAEU,eAAA;EACR,SAAA;EACA,OAAA;;EAEA,OAAA;;EAEA,SAAA;;EAEA,UAAA;;EAEA,KAAA;AAAA;AAAA,iBAQsB,UAAA,CAAA;EACtB,SAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UCxBjB,eAAA;EACR,KAAA,GAAQ,MAAA;AAAA;AAAA,iBAGc,UAAA,CAAA;EACtB;AAAA,GACC,eAAA,GAAkB,OAAA,CAAM,SAAA;;;;YCDf,MAAA;IACR,gBAAA;MACE,cAAA;MACA,aAAA,GAAgB,QAAA;IAAA;IAElB,YAAA;MACE,gBAAA;MACA,SAAA;MACA,oBAAA,GAAuB,OAAA;QACrB,QAAA;QACA,OAAA;MAAA,MACI,OAAA;IAAA;IAER,SAAA;MACE,IAAA;IAAA;EAAA;AAAA;AAAA,UAKI,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,OAAA,CAAM,SAAA;;;UC9BzD,kBAAA;EACR,QAAA,EAAU,OAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,OAAA,CAAM,SAAA;EACnB,UAAA,GAAa,OAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,OAAA,CAAM,SAAA"}
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":";;;UAEU,eAAA;EACR,SAAA;EACA,OAAA;;EAEA,OAAA;;EAEA,SAAA;;EAEA,UAAA;;EAEA,KAAA;;;;;;;;AAkCA;EAzBA,OAAA;;;;;;;;;;;;;;;EAeA,YAAA;;;;;;;;;;EAUA,SAAA;AAAA;AAAA,iBAQsB,UAAA,CAAA;EACtB,SAAA;EACA,OAAA;EACA,OAAA;EACA,SAAA;EACA,UAAA;EACA,KAAA;EACA,OAAA;EACA,YAAA;EACA;AAAA,GACC,eAAA,GAAkB,KAAA,CAAM,SAAA;;;UC7DjB,eAAA;EACR,KAAA,GAAQ,MAAA;AAAA;AAAA,iBAGc,UAAA,CAAA;EACtB;AAAA,GACC,eAAA,GAAkB,OAAA,CAAM,SAAA;;;;YCDf,MAAA;IACR,gBAAA;MACE,cAAA;MACA,aAAA,GAAgB,QAAA;IAAA;IAElB,YAAA;MACE,gBAAA;MACA,SAAA;MACA,oBAAA,GAAuB,OAAA;QACrB,QAAA;QACA,OAAA;MAAA,MACI,OAAA;IAAA;IAER,SAAA;MACE,IAAA;IAAA;EAAA;AAAA;AAAA,UAKI,eAAA;EACR,UAAA,IAAc,WAAA;AAAA;AAAA,iBAKA,UAAA,CAAA;EAAa;AAAA,GAAc,eAAA,GAAkB,OAAA,CAAM,SAAA;;;UC9BzD,kBAAA;EACR,QAAA,EAAU,OAAA,CAAM,SAAA;EAChB,SAAA;EACA,UAAA,GAAa,OAAA,CAAM,SAAA;EACnB,UAAA,GAAa,OAAA,CAAM,SAAA;AAAA;AAAA,iBAGG,aAAA,CAAA;EACtB,QAAA;EACA,SAAA;EACA,UAAA;EACA;AAAA,GACC,kBAAA,GAAqB,OAAA,CAAM,SAAA"}
package/dist/index.mjs CHANGED
@@ -8,9 +8,15 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
8
  const SCRIPT_ID = "fluid-cdn-script";
9
9
  const LEAD_CAPTURE_ID = "fluid-lead-capture-suppress";
10
10
  const DEFAULT_SCRIPT_SRC = "https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js";
11
- function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug }) {
11
+ function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug, country, cookieDomain, shareGuid }) {
12
12
  const authJwtRef = useRef(authJwt);
13
13
  authJwtRef.current = authJwt;
14
+ const countryRef = useRef(country);
15
+ countryRef.current = country;
16
+ const cookieDomainRef = useRef(cookieDomain);
17
+ cookieDomainRef.current = cookieDomain;
18
+ const shareGuidRef = useRef(shareGuid);
19
+ shareGuidRef.current = shareGuid;
14
20
  useEffect(() => {
15
21
  if (!subdomain) return;
16
22
  if (document.getElementById(SCRIPT_ID)) return;
@@ -24,6 +30,9 @@ function CartScript({ subdomain, authJwt, bffMode, scriptSrc, apiBaseUrl, debug
24
30
  else if (authJwtRef.current) script.dataset.authJwt = authJwtRef.current;
25
31
  if (apiBaseUrl) script.dataset.fluidApiBaseUrl = apiBaseUrl;
26
32
  if (debug) script.dataset.debug = "true";
33
+ if (countryRef.current) script.dataset.fluidCountry = countryRef.current;
34
+ if (cookieDomainRef.current) script.dataset.fluidCookieDomain = cookieDomainRef.current;
35
+ if (shareGuidRef.current) script.dataset.shareGuid = shareGuidRef.current;
27
36
  document.head.appendChild(script);
28
37
  const leadCapture = document.createElement("fluid-lead-capture-widget");
29
38
  leadCapture.id = LEAD_CAPTURE_ID;
@@ -1 +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":["import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\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 { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\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 if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\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, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import 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":"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"}
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":["import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n /**\n * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with\n * before any cart operation. Renders as `data-fluid-country` on the SDK\n * script tag; the SDK reads it at init time and uses it for cart creation\n * paths so they don't fall back to locale-derived country. Requires\n * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country\n * parsing) on the CDN.\n */\n country?: string;\n /**\n * Explicit cookie domain for the FairShare SDK, rendered as\n * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes\n * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain\n * instead of inferring an apex/brand domain from the hostname.\n *\n * This is required in the portal's hub/tenant model: the SDK's hostname\n * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the\n * shared `.portal.fluid.app` level, which leaks attribution across tenants.\n * Pass the current tenant host so cookies stay isolated per company.\n * Storefronts load the SDK directly (not via this component) and keep the\n * default brand-wide behavior. Requires a web-widgets bundle that parses\n * `data-fluid-cookie-domain`.\n */\n cookieDomain?: string;\n /**\n * Fair Share attribution GUID for the logged-in member, rendered as\n * `data-share-guid` on the SDK script tag. This is the FairShare SDK's\n * highest-priority attribution override: when present the SDK attributes\n * the member's in-portal shopping to this share GUID. A portal belongs to a\n * company (not a rep), so pass the current member's company-scoped share\n * GUID (`account.share_guid`) — reps only; omit for customers so they are\n * not self-attributed.\n */\n shareGuid?: string;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n country,\n cookieDomain,\n shareGuid,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n const countryRef = useRef(country);\n countryRef.current = country;\n const cookieDomainRef = useRef(cookieDomain);\n cookieDomainRef.current = cookieDomain;\n const shareGuidRef = useRef(shareGuid);\n shareGuidRef.current = shareGuid;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n if (countryRef.current) {\n script.dataset.fluidCountry = countryRef.current;\n }\n if (cookieDomainRef.current) {\n script.dataset.fluidCookieDomain = cookieDomainRef.current;\n }\n if (shareGuidRef.current) {\n script.dataset.shareGuid = shareGuidRef.current;\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n // `authJwt`, `country`, `cookieDomain`, and `shareGuid` are intentionally\n // omitted from deps: the SDK reads data attributes only once when this\n // module script first executes.\n // Re-inserting the same module URL after those props change does not\n // re-run FairShare initialization, so treat them as inject-time values.\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\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 { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\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 if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\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, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import 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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/cart-ui",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "files": [
5
5
  "dist",
6
6
  "src/styles"