{"version":3,"file":"ShopScreen-CTxedZqv.mjs","names":["defaultRenderImage","defaultRenderImage","formatPrice","formatPriceRange"],"sources":["../../../cart/ui/src/components/cart-script.tsx","../../../cart/ui/src/components/cart-widget.tsx","../../../cart/ui/src/components/cart-button.tsx","../../../cart/ui/src/components/shop-container.tsx","../../../shop/ui/src/components/product-card.tsx","../../../shop/ui/src/components/image-gallery.tsx","../../../shop/ui/src/components/quantity-selector.tsx","../../../shop/ui/src/components/purchase-options.tsx","../../../shop/ui/src/components/bundle-item-subscribe.tsx","../../../shop/ui/src/components/bundle-group-item.tsx","../../../shop/ui/src/components/bundle-group.tsx","../../../shop/ui/src/components/bundle-selector.tsx","../../../shop/ui/src/components/dynamic-bundle.tsx","../../../shop/ui/src/utils/dynamic-bundle-flag.ts","../../../shop/ui/src/utils/is-variant-unavailable.ts","../../../shop/ui/src/components/shop-app.tsx","../src/screens/ShopScreen.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\nconst CART_OPERATION_SUCCESS_EVENT = \"CART_OPERATION_SUCCESS\";\n\nexport function CartButton({ onCheckout }: CartButtonProps): React.ReactNode {\n  const { t } = useShopTranslation();\n  const [cartItemCount, setCartItemCount] = 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 syncSDKState = (): boolean => {\n      const sdk = window.FluidCommerceSDK;\n      if (!sdk) return false;\n\n      if (onCheckout) {\n        sdk.setOnCheckout(navigateToCheckout);\n      }\n      const count = window.FairShareSDK?.getCartItemCount?.();\n      if (count != null) {\n        setCartItemCount(count);\n      }\n      return true;\n    };\n\n    const setupSDK = () => {\n      if (cancelled || syncSDKState()) return;\n      if (attempts < MAX_SDK_POLL_ATTEMPTS) {\n        attempts++;\n        timeoutId = setTimeout(setupSDK, 100);\n      }\n    };\n\n    const handleCartOperationSuccess = () => {\n      if (cancelled) return;\n      if (timeoutId) {\n        clearTimeout(timeoutId);\n        timeoutId = null;\n      }\n      syncSDKState();\n    };\n\n    window.addEventListener(\n      CART_OPERATION_SUCCESS_EVENT,\n      handleCartOperationSuccess,\n    );\n    setupSDK();\n    return () => {\n      cancelled = true;\n      window.removeEventListener(\n        CART_OPERATION_SUCCESS_EVENT,\n        handleCartOperationSuccess,\n      );\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          {cartItemCount}\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","import type React from \"react\";\nimport { useState, type ReactNode } from \"react\";\nimport type { products, portalProducts } from \"@fluid-app/products-core\";\nimport {\n  determineProductPrice,\n  formatPortalPrice,\n  formatPortalPriceRange,\n  getProductImageUrl,\n} from \"@fluid-app/products-core\";\nimport { Badge, Card } from \"@fluid-app/ui-primitives\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { CirclePlay } from \"lucide-react\";\nimport { getVideoThumbnailUrl, isVideoUrl } from \"../utils/media-helpers\";\n\ntype LegacyProduct = (products.Product | products.ShopProduct) & {\n  kind?: string;\n  video_url?: string;\n};\n\nexport interface RenderImageProps {\n  src: string;\n  alt: string;\n  fill?: boolean;\n  className?: string;\n  onError?: (e: React.SyntheticEvent<HTMLImageElement>) => void;\n  unoptimized?: boolean;\n}\n\ntype TaggedPortalProduct = portalProducts.Product & {\n  readonly __portalProduct: true;\n};\n\nexport type ProductCardProduct = LegacyProduct | TaggedPortalProduct;\n\ninterface ProductCardProps {\n  product: ProductCardProduct;\n  countryIso?: string;\n  companyLogoUrl?: string | null;\n  /**\n   * Whether this viewer may see CV/QV, resolved by the consumer through the\n   * shared policy. Defaults to false — volume is opt-in.\n   */\n  canShowVolume?: boolean;\n  showShareModal?: boolean;\n  setShareModalOpen?: (open: boolean) => void;\n  setSelectedProduct?: (product: LegacyProduct) => void;\n  renderLink?: (props: { href: string; children: ReactNode }) => ReactNode;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n  onClick?: () => void;\n}\n\nexport function tagPortalProduct(\n  product: portalProducts.Product,\n): TaggedPortalProduct {\n  return { ...product, __portalProduct: true as const };\n}\n\nfunction isPortalProduct(\n  product: ProductCardProduct,\n): product is TaggedPortalProduct {\n  return \"__portalProduct\" in product && product.__portalProduct === true;\n}\n\nfunction getPortalProductCoverImage(\n  product: portalProducts.Product,\n): string | null {\n  if (product.images && product.images.length > 0) {\n    return product.images[0]?.url ?? null;\n  }\n  return null;\n}\n\nfunction defaultRenderImage({\n  src,\n  alt,\n  fill,\n  className,\n  onError,\n}: RenderImageProps): ReactNode {\n  return (\n    <img\n      src={src}\n      alt={alt}\n      className={`${fill ? \"absolute inset-0 h-full w-full\" : \"\"} ${className ?? \"\"}`}\n      onError={onError}\n    />\n  );\n}\n\nfunction ProductCardContent({\n  product,\n  countryIso,\n  companyLogoUrl,\n  canShowVolume = false,\n  renderImage = defaultRenderImage,\n}: {\n  product: ProductCardProduct;\n  countryIso?: string;\n  companyLogoUrl?: string | null;\n  /**\n   * Whether this viewer may see CV/QV, resolved by the consumer through the\n   * shared policy. Defaults to false: volume is opt-in, and shop/ui has no\n   * viewer signal of its own to fall back on.\n   */\n  canShowVolume?: boolean;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n}) {\n  const [isHovered, setIsHovered] = useState(false);\n  const { t } = useShopTranslation();\n\n  const isPortal = isPortalProduct(product);\n  const coverImage = isPortal\n    ? getPortalProductCoverImage(product)\n    : getProductImageUrl(product as Parameters<typeof getProductImageUrl>[0]);\n  const isVideo = isVideoUrl(coverImage);\n  const productName = isPortal\n    ? product.name || t(\"no_title\")\n    : (product as LegacyProduct).title || t(\"no_title\");\n\n  const isBundle = isPortal && product.is_bundle === true;\n\n  // Resolved once so the render can ask whether there is anything to show\n  // before deciding to show it. Both are null when the country has no entry.\n  const retailVariant =\n    !isPortal && canShowVolume\n      ? getSelectedVariant(product as LegacyProduct)\n      : null;\n  const retailCv =\n    retailVariant && countryIso\n      ? getVariantCountryValue(retailVariant, countryIso, \"cv\")\n      : null;\n  const retailQv =\n    retailVariant && countryIso\n      ? getVariantCountryValue(retailVariant, countryIso, \"qv\")\n      : null;\n\n  let repPrice: string | null | undefined = null;\n  let price: string | null | undefined = null;\n  if (isPortal) {\n    // wholesale_price is non-null for reps, null for customers\n    if (product.wholesale_price) {\n      repPrice = formatPortalPrice(product.wholesale_price, product.currency);\n      // Show retail as strikethrough if different from wholesale\n      const retailFormatted = formatPortalPrice(\n        product.price,\n        product.currency,\n      );\n      if (retailFormatted !== repPrice) {\n        price = retailFormatted;\n      }\n    } else {\n      repPrice = formatPortalPrice(product.price, product.currency);\n    }\n  } else if (countryIso) {\n    const prices = determineProductPrice(product, countryIso);\n    repPrice = prices.repPrice;\n    price = prices.price;\n  }\n\n  // A bundle's master-variant price is often $0, so show the resolved range.\n  // Fall back to the single (backend-guaranteed non-zero) retail price when no\n  // range — matching the detail page's fallback so card and detail never diverge.\n  const bundlePrice = isBundle\n    ? (formatPortalPriceRange(product.price_range, product.currency) ??\n      formatPortalPrice(product.price, product.currency))\n    : null;\n\n  const resolvedCoverImage =\n    isVideo && coverImage\n      ? getVideoThumbnailUrl(coverImage)\n      : coverImage ||\n        \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\";\n\n  return (\n    <>\n      {/* Image/Video container */}\n      <div\n        className=\"bg-muted/40 relative aspect-square overflow-hidden rounded-lg\"\n        onMouseEnter={() => isVideo && setIsHovered(true)}\n        onMouseLeave={() => isVideo && setIsHovered(false)}\n      >\n        {isVideo && isHovered ? (\n          <video\n            src={coverImage || \"\"}\n            className=\"absolute inset-0 h-full w-full object-cover\"\n            autoPlay\n            muted\n            loop\n            playsInline\n          />\n        ) : (\n          renderImage({\n            src: resolvedCoverImage,\n            alt: productName,\n            fill: true,\n            className:\n              \"object-cover transition-transform duration-300 hover:scale-[1.02]\",\n            onError: (e) => {\n              e.currentTarget.src =\n                companyLogoUrl ||\n                \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\";\n            },\n            unoptimized: true,\n          })\n        )}\n\n        {/* Video play indicator */}\n        {isVideo && !isHovered && (\n          <div className=\"absolute inset-0 flex items-center justify-center\">\n            <div className=\"flex size-16 items-center justify-center rounded-full bg-black/50 backdrop-blur-sm\">\n              <CirclePlay className=\"size-12 text-white\" />\n            </div>\n          </div>\n        )}\n\n        {/* Bundle badge */}\n        {isBundle && (\n          <Badge variant=\"secondary\" className=\"absolute top-2 left-2\">\n            {t(\"bundle_badge\")}\n          </Badge>\n        )}\n      </div>\n\n      {/* Product info */}\n      <div className=\"px-0.5 pt-2 pb-1\">\n        <h3 className=\"text-foreground line-clamp-1 text-sm leading-snug font-semibold\">\n          {productName}\n        </h3>\n\n        <div className=\"flex items-center gap-2\">\n          {isBundle ? (\n            bundlePrice && (\n              <span className=\"text-foreground text-sm leading-none font-semibold\">\n                {bundlePrice}\n              </span>\n            )\n          ) : (\n            <>\n              {repPrice && (\n                <span className=\"text-foreground text-sm leading-none font-semibold\">\n                  {repPrice}\n                </span>\n              )}\n              {price && (\n                <span className=\"text-muted-foreground text-sm leading-none line-through\">\n                  {price}\n                </span>\n              )}\n            </>\n          )}\n        </div>\n\n        {/* Having a wholesale price used to stand in for \"the viewer is a rep\".\n            It is a pricing fact, not a viewer fact, and it is no longer\n            consulted — the gate decides that. Presence of the values still\n            decides whether there is a pill at all: a product may carry one\n            metric without the other, so `-` fills the missing half rather\n            than the row rendering as an empty \"CV  | QV \". */}\n        {!isPortal &&\n          canShowVolume &&\n          countryIso &&\n          (retailCv != null || retailQv != null) && (\n            <div className=\"text-muted-foreground mt-1 text-xs\">\n              CV {retailCv ?? \"-\"} | QV {retailQv ?? \"-\"}\n            </div>\n          )}\n\n        {isPortal &&\n          canShowVolume &&\n          (product.cv != null || product.qv != null) && (\n            <div className=\"text-muted-foreground mt-1 text-xs\">\n              CV {product.cv ?? \"-\"} | QV {product.qv ?? \"-\"}\n            </div>\n          )}\n      </div>\n    </>\n  );\n}\n\nfunction getSelectedVariant(\n  product: LegacyProduct,\n): products.Variant | products.ShopVariant | null {\n  if (!product.variants || product.variants.length === 0) return null;\n\n  const masterVariant = product.variants.find(\n    (v: products.Variant | products.ShopVariant) => {\n      return \"is_master\" in v && v.is_master;\n    },\n  );\n  if (masterVariant) return masterVariant;\n\n  return product.variants[0] || null;\n}\n\nfunction getVariantCountryValue(\n  variant: products.Variant | products.ShopVariant | null,\n  countryIso: string,\n  field: \"cv\" | \"qv\",\n): number | null {\n  if (!variant || !variant.variant_countries) return null;\n\n  if (\n    typeof variant.variant_countries === \"object\" &&\n    !Array.isArray(variant.variant_countries)\n  ) {\n    const countryData = variant.variant_countries[countryIso] as\n      | products.VariantCountry\n      | undefined;\n    return countryData?.[field] ?? null;\n  }\n\n  if (Array.isArray(variant.variant_countries)) {\n    const countryData = variant.variant_countries.find(\n      (vc: products.ShopVariantCountry) => vc.country_iso === countryIso,\n    );\n    return countryData?.[field] ?? null;\n  }\n\n  return null;\n}\n\nexport default function ProductCard({\n  product,\n  countryIso,\n  companyLogoUrl,\n  canShowVolume = false,\n  showShareModal = false,\n  setShareModalOpen,\n  setSelectedProduct,\n  renderLink,\n  renderImage,\n  onClick,\n}: ProductCardProps): React.JSX.Element {\n  const cardContent = (\n    <ProductCardContent\n      product={product}\n      canShowVolume={canShowVolume}\n      {...(countryIso !== undefined && { countryIso })}\n      {...(companyLogoUrl !== undefined && { companyLogoUrl })}\n      {...(renderImage !== undefined && { renderImage })}\n    />\n  );\n\n  const cardClassName =\n    \"bg-transparent overflow-hidden border-0 shadow-none pt-0 gap-0\";\n\n  if (showShareModal && !isPortalProduct(product)) {\n    const handleShareClick = () => {\n      if (setSelectedProduct && setShareModalOpen) {\n        setSelectedProduct(product);\n        setShareModalOpen(true);\n      }\n    };\n    return (\n      <Card className={cardClassName}>\n        <button\n          onClick={handleShareClick}\n          className=\"group block w-full cursor-pointer text-left\"\n        >\n          {cardContent}\n        </button>\n      </Card>\n    );\n  }\n\n  if (onClick) {\n    return (\n      <Card className={cardClassName}>\n        <button\n          onClick={onClick}\n          className=\"group block w-full cursor-pointer text-left\"\n        >\n          {cardContent}\n        </button>\n      </Card>\n    );\n  }\n\n  const href = `/portal/shop/${product.id}`;\n\n  if (renderLink) {\n    return (\n      <Card className={cardClassName}>\n        {renderLink({ href, children: cardContent })}\n      </Card>\n    );\n  }\n\n  return (\n    <Card className={cardClassName}>\n      <a href={href} className=\"group block cursor-pointer\">\n        {cardContent}\n      </a>\n    </Card>\n  );\n}\n","import type React from \"react\";\nimport { type ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { ChevronLeft, ChevronRight, X } from \"lucide-react\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogTitle,\n  DialogClose,\n} from \"@fluid-app/ui-primitives\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { isVideoUrl } from \"../utils/media-helpers\";\nimport type { RenderImageProps } from \"./product-card\";\n\ninterface ImageGalleryProps {\n  images: Array<{\n    id: number;\n    image_url: string;\n    image_path: string | null;\n    position: number;\n  }>;\n  fallbackImageUrl: string;\n  productTitle: string;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n}\n\nfunction defaultRenderImage({\n  src,\n  alt,\n  fill,\n  className,\n  onError,\n}: RenderImageProps): ReactNode {\n  return (\n    <img\n      src={src}\n      alt={alt}\n      className={`${fill ? \"absolute inset-0 h-full w-full\" : \"\"} ${className ?? \"\"}`}\n      onError={onError}\n    />\n  );\n}\n\nexport default function ImageGallery({\n  images,\n  fallbackImageUrl,\n  productTitle,\n  renderImage = defaultRenderImage,\n}: ImageGalleryProps): React.JSX.Element {\n  const { t } = useShopTranslation();\n  const [currentImageIndex, setCurrentImageIndex] = useState(0);\n  const [viewerOpen, setViewerOpen] = useState(false);\n\n  const hasMultipleImages = images && images.length > 0;\n  const displayImages = useMemo(\n    () =>\n      hasMultipleImages\n        ? images.toSorted((a, b) => a.position - b.position)\n        : [{ id: 0, image_url: fallbackImageUrl, position: 0 }],\n    [images, hasMultipleImages, fallbackImageUrl],\n  );\n\n  // Reset to first image when the images array changes (e.g. variant switch)\n  useEffect(() => {\n    setCurrentImageIndex(0);\n  }, [displayImages]);\n\n  const nextImage = () => {\n    if (displayImages.length > 1) {\n      setCurrentImageIndex((prev) => (prev + 1) % displayImages.length);\n    }\n  };\n\n  const prevImage = () => {\n    if (displayImages.length > 1) {\n      setCurrentImageIndex(\n        (prev) => (prev - 1 + displayImages.length) % displayImages.length,\n      );\n    }\n  };\n\n  return (\n    <div className=\"flex flex-col gap-3 md:flex-row-reverse md:items-start\">\n      <div className=\"min-w-0 flex-1\">\n        {/* Main Image */}\n        <div className=\"bg-muted/30 group relative aspect-[3/4] max-h-[760px] overflow-hidden rounded-xl shadow-sm\">\n          {isVideoUrl(displayImages[currentImageIndex]?.image_url) ? (\n            <video\n              key={displayImages[currentImageIndex]?.id}\n              src={displayImages[currentImageIndex]?.image_url}\n              className=\"absolute inset-0 h-full w-full object-cover\"\n              controls\n              loop\n              playsInline\n            />\n          ) : (\n            <button\n              type=\"button\"\n              onClick={() => setViewerOpen(true)}\n              className=\"group absolute inset-0 z-[1] cursor-pointer border-0 bg-transparent p-0\"\n              aria-label={t(\"open_image_viewer\")}\n            >\n              {renderImage({\n                src:\n                  displayImages[currentImageIndex]?.image_url ||\n                  fallbackImageUrl,\n                alt: productTitle,\n                fill: true,\n                className: \"rounded-xl object-cover md:shadow-sm\",\n                onError: (e) => {\n                  e.currentTarget.src =\n                    \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\";\n                },\n                unoptimized: true,\n              })}\n              <span className=\"group-focus-visible:ring-ring pointer-events-none absolute inset-0 rounded-xl group-focus-visible:ring-2 group-focus-visible:ring-inset\" />\n            </button>\n          )}\n\n          {/* Navigation arrows */}\n          {displayImages.length > 1 && (\n            <>\n              <button\n                type=\"button\"\n                aria-label={t(\"previous_image\")}\n                className=\"bg-background/80 text-foreground hover:bg-background absolute top-1/2 left-3 z-10 flex size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full shadow-md transition-opacity md:opacity-0 md:group-hover:opacity-100\"\n                onClick={prevImage}\n              >\n                <ChevronLeft className=\"size-5\" />\n              </button>\n              <button\n                type=\"button\"\n                aria-label={t(\"next_image\")}\n                className=\"bg-background/80 text-foreground hover:bg-background absolute top-1/2 right-3 z-10 flex size-10 -translate-y-1/2 cursor-pointer items-center justify-center rounded-full shadow-md transition-opacity md:opacity-0 md:group-hover:opacity-100\"\n                onClick={nextImage}\n              >\n                <ChevronRight className=\"size-5\" />\n              </button>\n            </>\n          )}\n        </div>\n      </div>\n\n      {displayImages.length > 1 && (\n        <div className=\"grid grid-cols-5 gap-2 sm:grid-cols-6 md:w-14 md:grid-cols-1\">\n          {displayImages.map((image, index) => {\n            const imageUrl = image.image_url || fallbackImageUrl;\n            const isSelected = index === currentImageIndex;\n            return (\n              <button\n                type=\"button\"\n                key={`${image.id}-${imageUrl}`}\n                aria-label={t(\"go_to_image\", { index: String(index + 1) })}\n                className={`bg-background relative aspect-square overflow-hidden rounded-lg border-2 transition ${\n                  isSelected\n                    ? \"border-foreground\"\n                    : \"hover:border-border border-transparent\"\n                }`}\n                onClick={() => setCurrentImageIndex(index)}\n              >\n                {renderImage({\n                  src: imageUrl,\n                  alt: productTitle,\n                  fill: true,\n                  className: \"object-cover\",\n                  onError: (e) => {\n                    e.currentTarget.src =\n                      \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\";\n                  },\n                  unoptimized: true,\n                })}\n              </button>\n            );\n          })}\n        </div>\n      )}\n\n      {/* Image viewer dialog */}\n      <Dialog open={viewerOpen} onOpenChange={setViewerOpen}>\n        <DialogContent\n          className=\"h-[95vh] w-[95vw] max-w-none border-0 bg-transparent p-0 shadow-none sm:max-w-none\"\n          showCloseButton={false}\n          overlayClassName=\"bg-foreground/90\"\n        >\n          <DialogTitle className=\"sr-only\">{productTitle}</DialogTitle>\n          <DialogDescription className=\"sr-only\">\n            {t(\"image_viewer_description\")}\n          </DialogDescription>\n          <DialogClose\n            aria-label={t(\"close_image_viewer\")}\n            className=\"bg-foreground/60 text-background hover:bg-foreground/80 focus-visible:ring-ring focus-visible:ring-offset-background absolute top-4 right-4 z-10 flex size-10 cursor-pointer items-center justify-center rounded-full transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none\"\n          >\n            <X className=\"size-5\" />\n            <span className=\"sr-only\">{t(\"close_image_viewer\")}</span>\n          </DialogClose>\n          <div className=\"relative flex h-full w-full items-center justify-center\">\n            {renderImage({\n              src:\n                displayImages[currentImageIndex]?.image_url || fallbackImageUrl,\n              alt: productTitle,\n              fill: true,\n              className: \"object-contain\",\n              onError: (e) => {\n                e.currentTarget.src =\n                  \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\";\n              },\n              unoptimized: true,\n            })}\n          </div>\n        </DialogContent>\n      </Dialog>\n    </div>\n  );\n}\n","import type React from \"react\";\nimport { Button } from \"@fluid-app/ui-primitives\";\n\ninterface QuantitySelectorProps {\n  quantity: number;\n  setQuantity: (quantity: number) => void;\n  disabled?: boolean;\n}\n\nexport default function QuantitySelector({\n  quantity,\n  setQuantity,\n  disabled = false,\n}: QuantitySelectorProps): React.JSX.Element {\n  return (\n    <div className=\"inline-flex items-center\">\n      <div className=\"border-border bg-background inline-flex items-center rounded-full border p-1\">\n        <Button\n          variant=\"ghost\"\n          onClick={() => setQuantity(Math.max(1, quantity - 1))}\n          className=\"text-foreground size-8 rounded-full border-0 p-0 shadow-none\"\n          disabled={disabled}\n        >\n          −\n        </Button>\n        <span className=\"text-foreground min-w-9 px-2 text-center text-sm font-semibold\">\n          {quantity}\n        </span>\n        <Button\n          variant=\"ghost\"\n          onClick={() => setQuantity(quantity + 1)}\n          className=\"text-foreground size-8 rounded-full border-0 p-0 shadow-none\"\n          disabled={disabled}\n        >\n          +\n        </Button>\n      </div>\n    </div>\n  );\n}\n","import type React from \"react\";\nimport { useMemo } from \"react\";\nimport { formatSavings, type products } from \"@fluid-app/products-core\";\nimport {\n  RadioGroup,\n  RadioGroupItem,\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@fluid-app/ui-primitives\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ninterface PurchaseOptionsProps {\n  showBuyOnce: boolean;\n  showSubscribe: boolean;\n  isSubscribe: boolean;\n  onSubscribeChange: (subscribe: boolean) => void;\n  product_subscription_plans: products.ProductSubscriptionPlan[];\n  selectedSubscriptionPlan?: products.ProductSubscriptionPlan;\n  onSubscriptionPlanChange?: (plan: products.ProductSubscriptionPlan) => void;\n  wholesalePrice?: number;\n  wholesaleSubscriptionPrice?: number;\n  currency?: string;\n}\n\nexport default function PurchaseOptions({\n  showBuyOnce,\n  showSubscribe,\n  isSubscribe,\n  onSubscribeChange,\n  product_subscription_plans,\n  selectedSubscriptionPlan,\n  onSubscriptionPlanChange,\n  wholesalePrice,\n  wholesaleSubscriptionPrice,\n  currency,\n}: PurchaseOptionsProps): React.JSX.Element | null {\n  const { t, locale } = useShopTranslation();\n\n  // Find default subscription plan or use first one\n  const defaultSubscriptionPlan = useMemo(() => {\n    if (!product_subscription_plans?.length) return null;\n    return (\n      product_subscription_plans.find((plan) => plan.default) ||\n      product_subscription_plans[0]\n    );\n  }, [product_subscription_plans]);\n\n  // Use selected plan or default to the default/first plan\n  const currentSubscriptionPlan =\n    selectedSubscriptionPlan || defaultSubscriptionPlan;\n\n  const savingsMode =\n    currentSubscriptionPlan?.subscription_plan.savings_display_mode ??\n    \"percent\";\n\n  const savingsText = useMemo(\n    () =>\n      formatSavings({\n        wholesalePrice,\n        wholesaleSubscriptionPrice,\n        // When currency is unknown, skip amount formatting (which would\n        // misrender as \"$X.XX\" on a non-USD store) and fall back to percent.\n        mode: currency !== undefined ? savingsMode : \"percent\",\n        currency: currency ?? \"USD\",\n        locale,\n      }),\n    [wholesalePrice, wholesaleSubscriptionPrice, savingsMode, currency, locale],\n  );\n\n  // Format subscription plan display name\n  const formatSubscriptionPlan = (plan: products.ProductSubscriptionPlan) => {\n    const interval = plan.subscription_plan.billing_interval;\n    const unit = plan.subscription_plan.billing_interval_unit;\n    return `${interval} ${unit} (${plan.subscription_plan.name})`;\n  };\n\n  // Prepare subscription plan options for Select component\n  const subscriptionPlanOptions = useMemo(() => {\n    if (!product_subscription_plans?.length) return [];\n\n    return product_subscription_plans.map((plan) => ({\n      value: plan.subscription_plan.id.toString(),\n      label: formatSubscriptionPlan(plan),\n    }));\n  }, [product_subscription_plans]);\n\n  // Don't render if only buy once is shown and subscribe is false\n  if (showBuyOnce && !showSubscribe) {\n    return null;\n  }\n  const handleSubscriptionPlanChange = (planId: string) => {\n    const selectedPlan = product_subscription_plans?.find(\n      (plan) => plan.subscription_plan.id.toString() === planId,\n    );\n    if (selectedPlan && onSubscriptionPlanChange) {\n      onSubscriptionPlanChange(selectedPlan);\n    }\n  };\n\n  const purchaseType = isSubscribe ? \"subscribe\" : \"once\";\n\n  return (\n    <div className=\"mb-3 flex flex-col\">\n      <RadioGroup\n        value={purchaseType}\n        onValueChange={(value) => onSubscribeChange(value === \"subscribe\")}\n        className=\"gap-0 space-y-0\"\n      >\n        {showSubscribe && (\n          <div\n            onClick={() => onSubscribeChange(true)}\n            className={`cursor-pointer rounded-t-lg p-4 text-left transition-all duration-200 ${showBuyOnce ? \"border border-b-0\" : \"rounded-b-lg\"} ${isSubscribe ? \"bg-muted\" : \"bg-background\"}`}\n          >\n            <div className=\"flex items-start gap-x-3\">\n              <RadioGroupItem\n                value=\"subscribe\"\n                onClick={(e) => e.stopPropagation()}\n                className={`flex items-center justify-center [&_svg]:h-1.5 [&_svg]:w-1.5 [&_svg]:fill-white ${\n                  isSubscribe\n                    ? \"border-contrast bg-contrast text-foreground\"\n                    : \"border-border bg-background text-muted-foreground\"\n                }`}\n              />\n              <div className=\"flex-1\">\n                <div className=\"text-foreground mb-1 text-sm leading-tight font-medium\">\n                  {savingsText\n                    ? t(\"subscribe_and_save\", { savings: savingsText })\n                    : t(\"subscribe\")}\n                </div>\n\n                {/* Subscription Plan Dropdown*/}\n                {product_subscription_plans?.length > 0 && (\n                  <div\n                    className=\"bg-muted mt-3 rounded-lg p-2\"\n                    onClick={(e) => e.stopPropagation()}\n                  >\n                    <div className=\"text-foreground mb-1 text-xs font-medium\">\n                      {t(\"delivery_frequency\")}\n                    </div>\n                    <Select\n                      value={\n                        currentSubscriptionPlan?.subscription_plan.id.toString() ||\n                        \"\"\n                      }\n                      onValueChange={handleSubscriptionPlanChange}\n                      disabled={product_subscription_plans?.length === 1}\n                    >\n                      <SelectTrigger className=\"bg-background text-foreground! w-full text-xs\">\n                        <SelectValue\n                          placeholder={t(\"select_delivery_schedule\")}\n                        />\n                      </SelectTrigger>\n                      <SelectContent>\n                        {subscriptionPlanOptions.map((option) => (\n                          <SelectItem key={option.value} value={option.value}>\n                            {option.label}\n                          </SelectItem>\n                        ))}\n                      </SelectContent>\n                    </Select>\n                  </div>\n                )}\n              </div>\n            </div>\n          </div>\n        )}\n        {showBuyOnce && (\n          <div\n            onClick={() => onSubscribeChange(false)}\n            className={`cursor-pointer rounded-b-lg border p-4 text-left transition-all duration-200 ${showSubscribe ? \"border-t-0\" : \"rounded-t-lg\"} ${!isSubscribe ? \"bg-muted\" : \"bg-background\"}`}\n          >\n            <div className=\"flex items-center gap-x-3\">\n              <RadioGroupItem\n                value=\"once\"\n                onClick={(e) => e.stopPropagation()}\n                className={`flex items-center justify-center [&_svg]:h-1.5 [&_svg]:w-1.5 [&_svg]:fill-white ${\n                  !isSubscribe\n                    ? \"border-contrast bg-contrast text-foreground\"\n                    : \"border-border bg-background text-muted-foreground\"\n                }`}\n              />\n              <div className=\"text-foreground mb-1 text-sm leading-tight font-medium\">\n                {t(\"one_time_purchase\")}\n              </div>\n            </div>\n          </div>\n        )}\n      </RadioGroup>\n    </div>\n  );\n}\n","import type { portalProducts } from \"@fluid-app/products-core\";\nimport { formatPortalPrice } from \"@fluid-app/products-core\";\nimport {\n  Badge,\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n  cn,\n} from \"@fluid-app/ui-primitives\";\nimport { Check } from \"lucide-react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ninterface BundleItemSubscribeProps {\n  item: portalProducts.BundleGroupItem;\n  subscribe: boolean;\n  subscriptionPlanId: number | null;\n  /** Group- or item-level forced subscription: the row is checked and locked. */\n  forced: boolean;\n  /**\n   * Whether the item's own price drives the Total (item-level pricing). When\n   * false the row is here only to pick a forced item's plan under bundle-level\n   * pricing, so its per-item price/savings are hidden — they aren't what's\n   * charged (the fixed bundle price is) and would read as conflicting.\n   */\n  showPrice: boolean;\n  currency?: string;\n  onSubscribeChange: (subscribe: boolean) => void;\n  onPlanChange: (planId: number) => void;\n}\n\n/** Subscription price for the chosen plan (mirrors computeBundleTotals). */\nfunction planSubscriptionPrice(\n  item: portalProducts.BundleGroupItem,\n  plan: portalProducts.SubscriptionPlan | undefined,\n): number {\n  const base = Number(item.price) || 0;\n  if (plan?.price_adjustment_type === \"percentage\") {\n    const amount = Number(plan.price_adjustment_amount) || 0;\n    return Math.max(0, base * (1 - amount / 100));\n  }\n  const sub =\n    item.subscription_price != null ? Number(item.subscription_price) : NaN;\n  return Number.isFinite(sub) ? sub : base;\n}\n\n/**\n * Per-item \"Subscribe & save\" row shown beneath a selected item whose product\n * offers plans. Independent of the bundle-level subscribe toggle. A forced item\n * renders checked and locked; an optional one toggles freely. When the product\n * has more than one plan, a cadence picker appears once subscribed.\n */\nexport function BundleItemSubscribe({\n  item,\n  subscribe,\n  subscriptionPlanId,\n  forced,\n  showPrice,\n  currency,\n  onSubscribeChange,\n  onPlanChange,\n}: BundleItemSubscribeProps) {\n  const { t } = useShopTranslation();\n  const plans = item.subscription_plans;\n  // Only offer the row when the product has plans and the item allows (or is\n  // forced onto) a subscription — matches the theme's resolveItemSubscriptionMode.\n  if (plans.length === 0 || (!item.allow_subscription && !forced)) return null;\n\n  const plan = plans.find((p) => p.id === subscriptionPlanId) ?? plans[0];\n  const base = Number(item.price) || 0;\n  const subPrice = planSubscriptionPrice(item, plan);\n  const savingsPercent = base > 0 ? Math.round((1 - subPrice / base) * 100) : 0;\n  const cadence = plan\n    ? t(\"bundle_subscribe_every\", {\n        interval: plan.billing_interval ?? 1,\n        unit: plan.billing_interval_unit ?? \"month\",\n      })\n    : \"\";\n\n  return (\n    <div\n      className={cn(\n        \"mt-2 rounded-lg border p-2.5 transition-colors\",\n        subscribe ? \"border-foreground\" : \"border-border\",\n      )}\n    >\n      <button\n        type=\"button\"\n        onClick={() => !forced && onSubscribeChange(!subscribe)}\n        aria-pressed={subscribe}\n        disabled={forced}\n        className={cn(\n          \"flex w-full items-center gap-2 text-left\",\n          !forced && \"cursor-pointer\",\n        )}\n      >\n        <span\n          className={cn(\n            \"flex size-4 shrink-0 items-center justify-center rounded-full border\",\n            subscribe\n              ? \"border-foreground bg-foreground text-background\"\n              : \"border-input\",\n          )}\n        >\n          {subscribe && <Check className=\"size-2.5\" />}\n        </span>\n        <span className=\"text-foreground text-sm font-medium\">\n          {t(\"bundle_subscribe\")}\n        </span>\n        {showPrice && subscribe && savingsPercent > 0 && (\n          <Badge variant=\"secondary\" className=\"text-[10px]\">\n            {t(\"bundle_subscribe_save\", { percent: savingsPercent })}\n          </Badge>\n        )}\n        {showPrice && (\n          <span className=\"ml-auto flex items-baseline gap-1.5\">\n            <span className=\"text-foreground text-sm font-semibold\">\n              {formatPortalPrice(String(subPrice.toFixed(2)), currency)}\n            </span>\n            {subscribe && subPrice < base && (\n              <span className=\"text-muted-foreground text-xs line-through\">\n                {formatPortalPrice(item.price, currency)}\n              </span>\n            )}\n          </span>\n        )}\n      </button>\n\n      {cadence && (\n        <p className=\"text-muted-foreground mt-1 pl-6 text-xs\">{cadence}</p>\n      )}\n\n      {subscribe && plans.length > 1 && (\n        <Select\n          value={plan?.id != null ? String(plan.id) : \"\"}\n          onValueChange={(value) => onPlanChange(Number(value))}\n        >\n          <SelectTrigger className=\"mt-2 h-8 w-full text-xs\">\n            <SelectValue />\n          </SelectTrigger>\n          <SelectContent>\n            {plans.map((p) =>\n              p.id == null ? null : (\n                <SelectItem key={p.id} value={String(p.id)}>\n                  {t(\"bundle_subscribe_every\", {\n                    interval: p.billing_interval ?? 1,\n                    unit: p.billing_interval_unit ?? \"month\",\n                  })}\n                </SelectItem>\n              ),\n            )}\n          </SelectContent>\n        </Select>\n      )}\n    </div>\n  );\n}\n","import type { ReactNode } from \"react\";\nimport { Badge, Button, RadioGroupItem, cn } from \"@fluid-app/ui-primitives\";\nimport type { portalProducts } from \"@fluid-app/products-core\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { BundleItemSubscribe } from \"./bundle-item-subscribe\";\n\nexport type RenderBundleImage = (props: {\n  src: string;\n  alt: string;\n  className?: string;\n}) => ReactNode;\n\nfunction defaultRenderImage({\n  src,\n  alt,\n  className,\n}: {\n  src: string;\n  alt: string;\n  className?: string;\n}): ReactNode {\n  return <img src={src} alt={alt} className={className} />;\n}\n\n/**\n * How the item's selection control renders:\n * - `locked` — included group; shows the fixed quantity, no control.\n * - `radio` — single-select group; the radio lives in a parent RadioGroup.\n * - `add` — multi-select group; an Add button that becomes a quantity stepper.\n */\nexport type BundleItemControl = \"locked\" | \"radio\" | \"add\";\n\ninterface BundleGroupItemProps {\n  item: portalProducts.BundleGroupItem;\n  control: BundleItemControl;\n  selected: boolean;\n  quantity: number;\n  /** Whether the group can still accept more quantity (disables the stepper's \"+\"). */\n  canAddMore: boolean;\n  /** Group- or item-level forced subscription: locks the per-item subscribe row on. */\n  forceSubscription: boolean;\n  /** Whether this item is currently set to subscribe. */\n  subscribe: boolean;\n  /** The item's chosen subscription plan id, or null. */\n  subscriptionPlanId: number | null;\n  /**\n   * Whether the per-item Subscribe & save row renders here — in item-level\n   * priced groups (the item's subscription price drives the Total), or for a\n   * forced item that still needs a plan choice under bundle-level pricing.\n   */\n  subscriptionEnabled: boolean;\n  /**\n   * Whether the item's own price drives the Total (item-level pricing). Passed to\n   * the subscribe row so it hides its per-item price/savings under bundle-level\n   * pricing, where the fixed bundle price — not the item's — is what's charged.\n   */\n  subscriptionPriceDriving: boolean;\n  currency?: string;\n  onToggle: () => void;\n  onQuantityChange: (quantity: number) => void;\n  onSubscribeChange: (subscribe: boolean) => void;\n  onSubscriptionPlanChange: (planId: number) => void;\n  renderImage?: RenderBundleImage;\n}\n\nexport function BundleGroupItem({\n  item,\n  control,\n  selected,\n  quantity,\n  canAddMore,\n  forceSubscription,\n  subscribe,\n  subscriptionPlanId,\n  subscriptionEnabled,\n  subscriptionPriceDriving,\n  currency,\n  onToggle,\n  onQuantityChange,\n  onSubscribeChange,\n  onSubscriptionPlanChange,\n  renderImage = defaultRenderImage,\n}: BundleGroupItemProps) {\n  const { t } = useShopTranslation();\n  const label = item.title ?? t(\"no_title\");\n  const unavailable = item.available === false;\n  // Prefer the admin-curated per-item image; fall back to the variant/product\n  // image the API resolves.\n  const imageUrl = item.image_url ?? item.variant_image_url;\n  // Lead with the product name (e.g. \"Chick-fil-A® Deluxe Sandwich\") and show\n  // the variant (e.g. \"American Buttery white bun\") beneath it; when there's no\n  // distinct variant title, the product name leads alone.\n  const variantTitle =\n    item.variant_title && item.variant_title !== label\n      ? item.variant_title\n      : null;\n  const primaryLabel = label;\n  const secondaryLabel = variantTitle;\n  // Accessible name leads with the product then the variant, so same-product\n  // variants (e.g. Small / Medium / Large) are distinguishable to assistive\n  // tech — the product title alone would announce identically for each.\n  const accessibleLabel = secondaryLabel\n    ? `${primaryLabel}, ${secondaryLabel}`\n    : primaryLabel;\n\n  // Single-select rows are one big click target (not just the radio circle) so\n  // tapping the image or product name selects the item. Rendered as a <label>\n  // wrapping the radio, which carries its own aria-label, so the click forwards\n  // to the radio without the label text hijacking its accessible name. The Add\n  // button and locked/stepper rows keep their own affordance (a <label> would\n  // rename the Add button after the row and fight the stepper's two controls).\n  const rowClickable = !unavailable && control === \"radio\";\n  const RowTag = rowClickable ? \"label\" : \"div\";\n\n  return (\n    <div>\n      <RowTag\n        className={cn(\n          \"flex items-center gap-3 py-3\",\n          rowClickable && \"cursor-pointer\",\n          unavailable && \"opacity-50\",\n        )}\n      >\n        <div className=\"bg-muted/40 size-14 shrink-0 overflow-hidden rounded-md\">\n          {imageUrl\n            ? renderImage({\n                src: imageUrl,\n                alt: label,\n                className: \"size-full object-cover\",\n              })\n            : null}\n        </div>\n\n        <div className=\"min-w-0 flex-1\">\n          <p className=\"text-foreground truncate text-sm font-medium\">\n            {primaryLabel}\n          </p>\n          {secondaryLabel && (\n            <p className=\"text-muted-foreground truncate text-xs\">\n              {secondaryLabel}\n            </p>\n          )}\n          {forceSubscription && (\n            <p className=\"text-muted-foreground text-xs\">\n              {t(\"bundle_subscription_required\")}\n            </p>\n          )}\n        </div>\n\n        <div className=\"shrink-0\">\n          {/* Inactive / archived children still render (so the group isn't\n              mysteriously empty) but swap their control for a clear pill — a\n              disabled Add button or a locked \"×8\" would read as purchasable\n              (CURRENT-3549). Seeding skips these, so checkout stays blocked. */}\n          {unavailable ? (\n            <Badge variant=\"secondary\" className=\"text-[10px] uppercase\">\n              {t(\"bundle_item_unavailable\")}\n            </Badge>\n          ) : (\n            <>\n              {control === \"locked\" && (\n                <span className=\"text-foreground text-sm font-medium\">\n                  ×{quantity}\n                </span>\n              )}\n\n              {control === \"radio\" && (\n                <RadioGroupItem\n                  value={String(item.variant_id)}\n                  aria-label={accessibleLabel}\n                />\n              )}\n\n              {control === \"add\" &&\n                (selected ? (\n                  <div className=\"border-border bg-background inline-flex items-center rounded-full border p-1\">\n                    <Button\n                      variant=\"ghost\"\n                      className=\"text-foreground size-8 rounded-full border-0 p-0 shadow-none\"\n                      onClick={() =>\n                        quantity <= 1\n                          ? onToggle()\n                          : onQuantityChange(quantity - 1)\n                      }\n                    >\n                      −\n                    </Button>\n                    <span className=\"text-foreground min-w-9 px-2 text-center text-sm font-semibold\">\n                      {quantity}\n                    </span>\n                    <Button\n                      variant=\"ghost\"\n                      className=\"text-foreground size-8 rounded-full border-0 p-0 shadow-none\"\n                      disabled={\n                        !canAddMore ||\n                        (item.max_quantity != null &&\n                          quantity >= item.max_quantity)\n                      }\n                      onClick={() => onQuantityChange(quantity + 1)}\n                    >\n                      +\n                    </Button>\n                  </div>\n                ) : (\n                  <Button\n                    variant=\"outline\"\n                    className=\"rounded-full\"\n                    disabled={!canAddMore}\n                    onClick={onToggle}\n                  >\n                    {t(\"bundle_add\")}\n                  </Button>\n                ))}\n            </>\n          )}\n        </div>\n      </RowTag>\n      {selected && subscriptionEnabled && (\n        <BundleItemSubscribe\n          item={item}\n          subscribe={subscribe}\n          subscriptionPlanId={subscriptionPlanId}\n          forced={forceSubscription}\n          showPrice={subscriptionPriceDriving}\n          {...(currency !== undefined && { currency })}\n          onSubscribeChange={onSubscribeChange}\n          onPlanChange={onSubscriptionPlanChange}\n        />\n      )}\n    </div>\n  );\n}\n","import { Badge, RadioGroup, cn } from \"@fluid-app/ui-primitives\";\nimport { Check, CircleCheck, TriangleAlert } from \"lucide-react\";\nimport type {\n  GroupSelectionStatus,\n  portalProducts,\n} from \"@fluid-app/products-core\";\nimport {\n  isIncludedGroup,\n  isSingleSelectGroup,\n  sortItems,\n} from \"@fluid-app/products-core\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { BundleGroupItem, type RenderBundleImage } from \"./bundle-group-item\";\n\ninterface BundleGroupProps {\n  group: portalProducts.BundleGroup;\n  status: GroupSelectionStatus;\n  isSelected: (variantId: number) => boolean;\n  getQuantity: (variantId: number) => number;\n  onToggle: (variantId: number) => void;\n  onQuantityChange: (variantId: number, quantity: number) => void;\n  getSubscribe: (variantId: number) => boolean;\n  getSubscriptionPlanId: (variantId: number) => number | null;\n  onSubscribeChange: (variantId: number, subscribe: boolean) => void;\n  onSubscriptionPlanChange: (variantId: number, planId: number) => void;\n  /**\n   * Whether the bundle prices per item/group (no fixed `product.price_range`, or\n   * a group carries its own `price`) — from `isPerItemOrGroupPriced`. Gates the\n   * per-item subscribe row: it only moves the Total in a `price`-less group here.\n   */\n  perItemOrGroupPriced: boolean;\n  currency?: string;\n  renderImage?: RenderBundleImage;\n  /** True when this group is a branch of a mutually-exclusive set. */\n  exclusive?: boolean;\n  /** True when this exclusive branch currently holds the set's selection. */\n  active?: boolean;\n  /** Choose this whole group as the exclusive set's active branch. */\n  onChoose?: () => void;\n}\n\nexport function BundleGroup({\n  group,\n  status,\n  isSelected,\n  getQuantity,\n  onToggle,\n  onQuantityChange,\n  getSubscribe,\n  getSubscriptionPlanId,\n  onSubscribeChange,\n  onSubscriptionPlanChange,\n  perItemOrGroupPriced,\n  currency,\n  renderImage,\n  exclusive = false,\n  active = false,\n  onChoose,\n}: BundleGroupProps) {\n  const { t } = useShopTranslation();\n  const included = isIncludedGroup(group);\n  const singleSelect = isSingleSelectGroup(group);\n  const control = included ? \"locked\" : singleSelect ? \"radio\" : \"add\";\n  const items = sortItems(group.bundle_group_items);\n  const forceGroup = group.force_subscriptions === true;\n  // An included group inside an exclusive set is picked as a WHOLE via the\n  // choose control (radio), not per item — its items render read-only at their\n  // configured quantity (not the live selection, so a not-yet-chosen branch\n  // still shows what you'd get instead of \"×0\").\n  const wholeGroup = exclusive && included;\n  // Per-item subscription only applies where the item's own price drives the\n  // Total — a `price`-less group within a group-/item-level-priced bundle.\n  // A fixed group `price` (or a fixed bundle-level `price_range`) subscribes at\n  // the group/bundle level, so a per-item toggle there would send subscription\n  // without moving the Total.\n  const itemPriced = perItemOrGroupPriced && group.price == null;\n\n  // Denominator shown in the status chip (the group's target / cap).\n  const target = group.max_selections ?? group.min_selections ?? null;\n\n  const selectedVariantId = singleSelect\n    ? items.find((item) => isSelected(item.variant_id))?.variant_id\n    : undefined;\n\n  const renderedItems = items.map((item) => {\n    const forced = forceGroup || item.force_subscription === true;\n    // The per-item subscribe/plan row shows when the item's own price drives the\n    // Total (item-level pricing) OR the item forces a subscription. A forced\n    // item must still let the shopper pick its plan (cadence) under bundle-level\n    // pricing — that choice goes into the order payload even though the fixed\n    // Total won't move.\n    const subscriptionEnabled = itemPriced || forced;\n    return (\n      <BundleGroupItem\n        key={item.id}\n        item={item}\n        control={control}\n        selected={isSelected(item.variant_id)}\n        quantity={wholeGroup ? item.quantity : getQuantity(item.variant_id)}\n        canAddMore={status.canAddMore}\n        forceSubscription={forced}\n        subscribe={getSubscribe(item.variant_id)}\n        subscriptionPlanId={getSubscriptionPlanId(item.variant_id)}\n        subscriptionEnabled={subscriptionEnabled}\n        subscriptionPriceDriving={itemPriced}\n        {...(currency !== undefined && { currency })}\n        onToggle={() => onToggle(item.variant_id)}\n        onQuantityChange={(quantity) =>\n          onQuantityChange(item.variant_id, quantity)\n        }\n        onSubscribeChange={(subscribe) =>\n          onSubscribeChange(item.variant_id, subscribe)\n        }\n        onSubscriptionPlanChange={(planId) =>\n          onSubscriptionPlanChange(item.variant_id, planId)\n        }\n        {...(renderImage !== undefined && { renderImage })}\n      />\n    );\n  });\n\n  return (\n    <section className=\"py-2\">\n      <div className=\"flex items-start justify-between gap-3\">\n        <div className=\"flex items-center gap-2\">\n          {exclusive && (\n            // Every exclusive branch shows a selected-state check so it's clear\n            // which branch is chosen — even a customizable branch selected by\n            // picking an item. Radio semantics (not a pressed toggle): the set\n            // is a radiogroup, so clicking the already-checked branch correctly\n            // does nothing. Clicking an unchecked branch picks the whole branch\n            // (a customizable one falls back to its default / first item).\n            <button\n              type=\"button\"\n              role=\"radio\"\n              aria-checked={active}\n              onClick={active ? undefined : onChoose}\n              aria-label={t(\"bundle_choose_this_group\")}\n              className={cn(\n                \"flex size-5 shrink-0 items-center justify-center rounded-full border transition-colors\",\n                active\n                  ? \"border-foreground bg-foreground text-background\"\n                  : \"border-input cursor-pointer\",\n              )}\n            >\n              {active && <Check className=\"size-3\" />}\n            </button>\n          )}\n          <h3 className=\"text-foreground text-base font-semibold\">\n            {group.title ?? \"\"}\n          </h3>\n          {included && !wholeGroup && (\n            <Badge variant=\"secondary\" className=\"text-[10px] uppercase\">\n              {t(\"bundle_included\")}\n            </Badge>\n          )}\n        </div>\n\n        {!included && target != null && (\n          <span\n            className={cn(\n              \"flex shrink-0 items-center gap-1 text-sm font-medium\",\n              status.isComplete ? \"text-foreground\" : \"text-muted-foreground\",\n            )}\n          >\n            {status.isComplete ? (\n              <CircleCheck className=\"size-4 text-green-600\" />\n            ) : (\n              <TriangleAlert className=\"size-4 text-amber-500\" />\n            )}\n            <span>\n              {status.count}/{target}\n            </span>\n          </span>\n        )}\n      </div>\n\n      {group.description && (\n        <p className=\"text-muted-foreground mt-1 text-sm\">\n          {group.description}\n        </p>\n      )}\n\n      {singleSelect ? (\n        <RadioGroup\n          className=\"divide-border/60 mt-2 divide-y\"\n          value={selectedVariantId != null ? String(selectedVariantId) : \"\"}\n          onValueChange={(value) => onToggle(Number(value))}\n        >\n          {renderedItems}\n        </RadioGroup>\n      ) : (\n        <div className=\"divide-border/60 mt-2 divide-y\">{renderedItems}</div>\n      )}\n    </section>\n  );\n}\n","import type { MouseEvent, ReactNode } from \"react\";\nimport type {\n  portalProducts,\n  UseBundleSelectorResult,\n} from \"@fluid-app/products-core\";\nimport {\n  isPerItemOrGroupPriced,\n  resolveExclusiveSets,\n  sortGroups,\n  useBundleSelector,\n} from \"@fluid-app/products-core\";\nimport { cn } from \"@fluid-app/ui-primitives\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { BundleGroup } from \"./bundle-group\";\nimport type { RenderBundleImage } from \"./bundle-group-item\";\n\ninterface BundleSelectorViewProps {\n  product: portalProducts.Product;\n  /** Selection state, owned by the caller (lifted so the parent can price it). */\n  selector: UseBundleSelectorResult;\n  currency?: string;\n  renderImage?: RenderBundleImage;\n}\n\ninterface BundleSelectorProps {\n  product: portalProducts.Product;\n  renderImage?: RenderBundleImage;\n}\n\n/**\n * Self-owning wrapper: instantiates the selection state and renders\n * {@link BundleSelectorView}. Use this for standalone renders and tests where\n * nothing outside the component reads the selections. When the parent needs the\n * selections too (e.g. to price the bundle in CURRENT-1834), it lifts\n * `useBundleSelector` and renders `BundleSelectorView` directly instead — that\n * avoids initializing a second, unused reducer here.\n *\n * Key this component by product id so switching products remounts it with\n * fresh default selections.\n */\nexport function BundleSelector({ product, renderImage }: BundleSelectorProps) {\n  const groups = product.product_bundle_groups ?? [];\n  const bundleConfig = product.bundle_config ?? null;\n  const selector = useBundleSelector({ groups, bundleConfig });\n\n  return (\n    <BundleSelectorView\n      product={product}\n      selector={selector}\n      {...(renderImage !== undefined && { renderImage })}\n    />\n  );\n}\n\n/**\n * Presentational body: renders a dynamic bundle's configuration (CURRENT-1394)\n * — included, customizable, single-select, and mutually-exclusive groups —\n * against a caller-owned selector. Holds no selection state of its own.\n * Pricing (CURRENT-1834) and add-to-cart + validation messaging (CURRENT-1894)\n * are layered on separately.\n */\nexport function BundleSelectorView({\n  product,\n  selector,\n  currency,\n  renderImage,\n}: BundleSelectorViewProps) {\n  const { t } = useShopTranslation();\n  const groups = product.product_bundle_groups ?? [];\n  const bundleConfig = product.bundle_config ?? null;\n\n  if (groups.length === 0) return null;\n\n  const sorted = sortGroups(groups);\n  const exclusiveSets = resolveExclusiveSets(bundleConfig);\n  const perItemOrGroupPriced = isPerItemOrGroupPriced(product, groups);\n\n  // sort_order -> index of the exclusive set it belongs to.\n  const setBySortOrder = new Map<number, number>();\n  exclusiveSets.forEach((set, index) => {\n    for (const sortOrder of set) setBySortOrder.set(sortOrder, index);\n  });\n\n  const renderGroup = (\n    group: portalProducts.BundleGroup,\n    exclusiveOpts?: { active: boolean },\n  ): ReactNode => (\n    <BundleGroup\n      key={group.id}\n      group={group}\n      status={selector.groupStatus(group)}\n      isSelected={(variantId) => selector.isSelected(group.id, variantId)}\n      getQuantity={(variantId) => selector.getQuantity(group.id, variantId)}\n      onToggle={(variantId) => selector.toggleItem(group.id, variantId)}\n      onQuantityChange={(variantId, quantity) =>\n        selector.setQuantity(group.id, variantId, quantity)\n      }\n      getSubscribe={(variantId) => selector.getSubscribe(group.id, variantId)}\n      getSubscriptionPlanId={(variantId) =>\n        selector.getSubscriptionPlanId(group.id, variantId)\n      }\n      onSubscribeChange={(variantId, subscribe) =>\n        selector.setSubscribe(group.id, variantId, subscribe)\n      }\n      onSubscriptionPlanChange={(variantId, planId) =>\n        selector.setItemSubscriptionPlan(group.id, variantId, planId)\n      }\n      perItemOrGroupPriced={perItemOrGroupPriced}\n      {...(currency !== undefined && { currency })}\n      {...(exclusiveOpts && {\n        exclusive: true,\n        active: exclusiveOpts.active,\n        onChoose: () => selector.selectGroup(group.id),\n      })}\n      {...(renderImage !== undefined && { renderImage })}\n    />\n  );\n\n  const blocks: ReactNode[] = [];\n  const renderedSets = new Set<number>();\n\n  for (const group of sorted) {\n    const setIndex = setBySortOrder.get(group.sort_order);\n\n    if (setIndex === undefined) {\n      blocks.push(renderGroup(group));\n      continue;\n    }\n    if (renderedSets.has(setIndex)) continue;\n    renderedSets.add(setIndex);\n\n    const memberSortOrders = exclusiveSets[setIndex] ?? [];\n    const members = sorted.filter((candidate) =>\n      memberSortOrders.includes(candidate.sort_order),\n    );\n\n    // Each branch is its own box so the chosen one can be highlighted (the\n    // active branch gets a solid border); the customer picks exactly one.\n    blocks.push(\n      <div\n        key={`exclusive-${setIndex}`}\n        className=\"space-y-3\"\n        role=\"radiogroup\"\n        aria-label={t(\"bundle_choose_one\")}\n      >\n        <p className=\"text-muted-foreground text-xs font-medium\">\n          {t(\"bundle_choose_one\")}\n        </p>\n        {members.map((member) => {\n          const active = selector.groupStatus(member).count > 0;\n          // Clicking anywhere in an inactive branch's box picks that branch.\n          // Inner controls (item Add/stepper/radio, the header check) handle\n          // their own clicks — skip those so we don't clobber an item pick.\n          const pickBranch = (event: MouseEvent<HTMLDivElement>) => {\n            if (\n              (event.target as HTMLElement).closest(\n                'button, a, input, select, label, [role=\"radio\"]',\n              )\n            ) {\n              return;\n            }\n            selector.selectGroup(member.id);\n          };\n          return (\n            <div\n              key={member.id}\n              className={cn(\n                \"rounded-xl border p-4 transition-colors\",\n                active ? \"border-foreground\" : \"border-border cursor-pointer\",\n              )}\n              {...(!active && { onClick: pickBranch })}\n            >\n              {renderGroup(member, { active })}\n            </div>\n          );\n        })}\n      </div>,\n    );\n  }\n\n  return <div className=\"space-y-6\">{blocks}</div>;\n}\n","import { useState, type ReactNode } from \"react\";\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport type { portalProducts, products } from \"@fluid-app/products-core\";\nimport {\n  computeBundleTotals,\n  formatPortalPrice,\n  isIncludedGroup,\n  selectedCountForGroup,\n  useBundleSelector,\n} from \"@fluid-app/products-core\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { BundleSelectorView } from \"./bundle-selector\";\nimport type { RenderBundleImage } from \"./bundle-group-item\";\nimport PurchaseOptions from \"./purchase-options\";\n\ninterface DynamicBundleProps {\n  /**\n   * Whether this viewer may see CV/QV, resolved by the consumer through the\n   * shared policy. Defaults to false — volume is opt-in. The bundle total is\n   * summed client-side, so the summation is skipped when this is false rather\n   * than being computed and hidden.\n   */\n  canShowVolume?: boolean;\n  product: portalProducts.Product;\n  /** Legacy-shaped subscription plans for PurchaseOptions (empty → no toggle). */\n  subscriptionPlans: products.ProductSubscriptionPlan[];\n  /** Parent cart variant id (the bundle's master variant) for `data-fluid-add-to-cart`. */\n  cartVariantId: string;\n  /** Resolved subscription plan id for the bundle when subscribing. */\n  subscriptionPlanId?: number;\n  currency?: string;\n  renderImage?: RenderBundleImage;\n  /**\n   * Left-column media (the product image gallery) rendered above the purchase\n   * panel — per Sophie's design the Total + subscribe + add-to-cart controls\n   * sit under the product image, not beside the group list.\n   */\n  imageSlot?: ReactNode;\n  /** Left-column heading (product title + bundle badge) shown above the panel. */\n  titleSlot?: ReactNode;\n  /** Product description shown directly under the title (Sophie's subtitle). */\n  descriptionSlot?: ReactNode;\n  /** Offsets the sticky left column when the portal screen header is present. */\n  accountForPortalHeader?: boolean;\n}\n\n/**\n * Native dynamic-bundle experience for the Portal shop (CURRENT-1834 pricing +\n * subscribe toggle layered on the CURRENT-1394 configurator). Owns the shared\n * selection state so the running Total reflects the same picks the group list\n * renders; mounted only for a loaded dynamic bundle, so the selector seeds from\n * real groups. Add-to-cart submit + validation is CURRENT-1894.\n */\nexport function DynamicBundle({\n  product,\n  canShowVolume = false,\n  subscriptionPlans,\n  cartVariantId,\n  subscriptionPlanId,\n  currency,\n  renderImage,\n  imageSlot,\n  titleSlot,\n  descriptionSlot,\n  accountForPortalHeader,\n}: DynamicBundleProps) {\n  const { t } = useShopTranslation();\n  const groups = product.product_bundle_groups ?? [];\n  const bundleConfig = product.bundle_config ?? null;\n  const selector = useBundleSelector({ groups, bundleConfig });\n\n  const [userSelectedSubscribe, setUserSelectedSubscribe] = useState<\n    boolean | null\n  >(null);\n  const [selectedSubscriptionPlan, setSelectedSubscriptionPlan] = useState<\n    products.ProductSubscriptionPlan | undefined\n  >(undefined);\n\n  // Feed the shopper's plan into the Total so bundle-level pricing (no group\n  // carries a price) resolves a recurring price instead of `undefined`.\n  // PurchaseOptions falls back to the default/first plan for its own display but\n  // only emits it once the dropdown changes, so mirror that fallback here — else\n  // the Total never updates on a forced or single-plan subscription. The flat\n  // plan shape (`subscription_plan`) carries the adjustment fields the totals\n  // helper expects.\n  const activeSubscriptionPlan =\n    selectedSubscriptionPlan ??\n    subscriptionPlans.find((p) => p.default) ??\n    subscriptionPlans[0];\n  const planAdjustment = activeSubscriptionPlan?.subscription_plan;\n\n  const totals = computeBundleTotals(\n    product,\n    groups,\n    selector.state,\n    planAdjustment\n      ? {\n          price_adjustment_type:\n            planAdjustment.price_adjustment_type === \"percentage\" ||\n            planAdjustment.price_adjustment_type === \"fixed_amount\"\n              ? planAdjustment.price_adjustment_type\n              : null,\n          price_adjustment_amount:\n            planAdjustment.price_adjustment_amount == null\n              ? null\n              : String(planAdjustment.price_adjustment_amount),\n        }\n      : null,\n    canShowVolume,\n  );\n\n  // Force-subscribe: an active group (included, or customizable once picked)\n  // that forces a subscription locks the whole bundle into subscription.\n  const forceSubscribe = groups.some(\n    (g) =>\n      g.force_subscriptions === true &&\n      (isIncludedGroup(g) || selectedCountForGroup(selector.state, g.id) > 0),\n  );\n  const canSubscribe = subscriptionPlans.length > 0 || totals.recurring != null;\n  const showSubscribe = canSubscribe || forceSubscribe;\n  const isSubscribe = forceSubscribe || (userSelectedSubscribe ?? false);\n\n  const oneOffDisplay = formatPortalPrice(totals.oneOff?.toFixed(2), currency);\n  const recurringDisplay = formatPortalPrice(\n    totals.recurring?.toFixed(2),\n    currency,\n  );\n  const showRecurring = isSubscribe && recurringDisplay != null;\n\n  // Add-to-cart (CURRENT-1894): submit via the shop's data-fluid-* contract —\n  // the parent bundle variant plus the selected children as bundled_items — so\n  // the cart SDK handles the cart token, buyer context, and opening the drawer.\n  // Attributes are only bound when the configuration is complete, so the SDK\n  // can't submit an invalid selection.\n  const planId =\n    selectedSubscriptionPlan?.subscription_plan.id ?? subscriptionPlanId;\n  const canAddToCart = selector.allComplete && cartVariantId !== \"\";\n  const bundledItems = JSON.stringify(\n    selector.cartItems.map((item) => ({\n      variant_id: item.variant_id,\n      quantity: item.quantity,\n      // Group id lets the backend price + validate each item against its group\n      // rather than an ambiguous variant-only lookup (see toCartItems).\n      product_bundle_group_id: item.product_bundle_group_id,\n      // Per-item subscription: the shopper's per-item Subscribe & save choice,\n      // independent of the bundle-level subscribe toggle.\n      subscription: item.subscription,\n      subscription_plan_id: item.subscription_plan_id,\n    })),\n  );\n\n  // Per Sophie's design the purchase panel (Total + subscribe + add-to-cart)\n  // lives under the product image in the left column, while the group\n  // configurator fills the right column. The shared selector is owned here so\n  // both columns stay in sync.\n  const stickyClass = accountForPortalHeader\n    ? \"lg:sticky lg:top-20 lg:self-start\"\n    : \"lg:sticky lg:top-8 lg:self-start\";\n\n  const purchasePanel = (\n    <div className=\"space-y-5\">\n      <div>\n        <div className=\"flex items-baseline justify-between gap-3\">\n          <span className=\"text-muted-foreground text-sm font-medium\">\n            {t(\"bundle_total\")}\n          </span>\n          <span className=\"flex items-baseline gap-2\">\n            <span className=\"text-foreground text-2xl font-bold\">\n              {showRecurring ? recurringDisplay : oneOffDisplay}\n            </span>\n            {showRecurring && recurringDisplay !== oneOffDisplay && (\n              <span className=\"text-muted-foreground text-sm line-through\">\n                {oneOffDisplay}\n              </span>\n            )}\n          </span>\n        </div>\n        {/* `||`, not `&&`: in the non-per-item branch computeBundleTotals\n            resolves cv and qv independently, so a bundle configured with CV\n            but no QV still has volume worth showing. When the gate is off,\n            includeVolume nulls both and the pill drops out entirely. */}\n        {canShowVolume && (totals.cv != null || totals.qv != null) && (\n          <div className=\"text-muted-foreground bg-muted/50 mt-3 inline-flex rounded-full px-3 py-1 text-xs font-medium\">\n            {t(\"bundle_volume_summary\", {\n              cv: totals.cv ?? \"-\",\n              qv: totals.qv ?? \"-\",\n            })}\n          </div>\n        )}\n      </div>\n\n      {showSubscribe && (\n        <PurchaseOptions\n          showBuyOnce={!forceSubscribe}\n          showSubscribe={showSubscribe}\n          isSubscribe={isSubscribe}\n          onSubscribeChange={setUserSelectedSubscribe}\n          product_subscription_plans={subscriptionPlans}\n          {...(selectedSubscriptionPlan !== undefined && {\n            selectedSubscriptionPlan,\n          })}\n          onSubscriptionPlanChange={setSelectedSubscriptionPlan}\n          {...(totals.oneOff !== undefined && {\n            wholesalePrice: totals.oneOff,\n          })}\n          {...(totals.recurring !== undefined && {\n            wholesaleSubscriptionPrice: totals.recurring,\n          })}\n          {...(currency !== undefined && { currency })}\n        />\n      )}\n\n      <div className=\"pt-2\">\n        <Button\n          variant=\"default\"\n          className=\"h-12 w-full gap-2 rounded-full text-base font-semibold\"\n          disabled={!canAddToCart}\n          {...(canAddToCart && {\n            \"data-fluid-add-to-cart\": cartVariantId,\n            \"data-fluid-quantity\": 1,\n            \"data-fluid-bundled-items\": bundledItems,\n            \"data-fluid-subscribe\": isSubscribe,\n            \"data-fluid-subscription-plan-id\":\n              isSubscribe && planId != null ? String(planId) : \"\",\n            \"data-fluid-open-cart-after-add\": \"true\",\n          })}\n        >\n          <ShoppingCart className=\"size-4\" />\n          {isSubscribe ? t(\"subscribe\") : t(\"bundle_add_to_cart\")}\n        </Button>\n        {!selector.allComplete && (\n          <p className=\"text-muted-foreground mt-2 text-center text-sm\">\n            {t(\"bundle_complete_selections\")}\n          </p>\n        )}\n      </div>\n    </div>\n  );\n\n  return (\n    // Left column is bounded to the image's natural max width (570px = 760px ×\n    // 3/4, the ImageGallery aspect cap) so the product image fills the column\n    // and lines up flush with the title + Total + add-to-cart panel beneath it.\n    <div className=\"grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,570px)_minmax(0,1fr)] xl:gap-12\">\n      {/* Left: product image + title + purchase panel. */}\n      <div className={stickyClass}>\n        {imageSlot}\n        <div className={imageSlot != null ? \"mt-6 space-y-5\" : \"space-y-5\"}>\n          {(titleSlot != null || descriptionSlot != null) && (\n            <div className=\"space-y-2\">\n              {titleSlot}\n              {descriptionSlot}\n            </div>\n          )}\n          {purchasePanel}\n        </div>\n      </div>\n\n      {/* Right: the group configurator. */}\n      <div className=\"self-start\">\n        <BundleSelectorView\n          product={product}\n          selector={selector}\n          {...(currency !== undefined && { currency })}\n          {...(renderImage !== undefined && { renderImage })}\n        />\n      </div>\n    </div>\n  );\n}\n","const PARAM = \"dynamic_bundle\";\nconst STORAGE_KEY = \"fluid_dynamic_bundle\";\n\n/**\n * Temporary feature gate for the dynamic-bundle configurator.\n *\n * Off by default — dynamic (customizable) bundles link out to the storefront as\n * they do today. Turned on with `?dynamic_bundle=true` and made **sticky** via\n * `sessionStorage`, because the portal navigates by slug with\n * `history.pushState(null, \"\", targetPath)` (AppShell) which drops the query\n * string — so a bare query param would evaporate the moment you click from the\n * shop listing into a product. `?dynamic_bundle=false` clears it.\n *\n * Read once per mount (the sessionStorage write is idempotent). Remove this\n * gate — and the `?dynamic_bundle` handling — once dynamic bundles ship on by\n * default.\n */\nexport function isDynamicBundlesEnabled(): boolean {\n  if (typeof window === \"undefined\") return false;\n  try {\n    const param = new URLSearchParams(window.location.search).get(PARAM);\n    if (param === \"true\") {\n      window.sessionStorage.setItem(STORAGE_KEY, \"true\");\n      return true;\n    }\n    if (param === \"false\") {\n      window.sessionStorage.removeItem(STORAGE_KEY);\n      return false;\n    }\n    return window.sessionStorage.getItem(STORAGE_KEY) === \"true\";\n  } catch {\n    // Private-mode / storage-disabled: fall back to the URL param only.\n    return new URLSearchParams(window.location.search).get(PARAM) === \"true\";\n  }\n}\n","import {\n  isVariantSubscribable,\n  type portalProducts,\n} from \"@fluid-app/products-core\";\n\n/**\n * Decides whether a product variant should disable its purchase CTA on the\n * portal shop page.\n */\nexport function isVariantUnavailable(\n  variant: portalProducts.Variant | undefined,\n  product: portalProducts.Product | undefined,\n): boolean {\n  if (!variant) return true;\n  if (variant.available === false) return true;\n  // Subscription-only variants price via `subscription_pricing`, so `price` is\n  // often \"0\"/null. Purchasability is the shared `isVariantSubscribable` rule,\n  // which tolerates whichever subscribability signal the API populates (the\n  // portal-tenant BFF sends `subscription_pricing`, not `allow_subscription`).\n  if (variant.subscription_only) {\n    return !isVariantSubscribable(variant);\n  }\n  // Match the render path's fallback (variant.price ?? product.price) so a\n  // variant inheriting price from the product isn't blocked.\n  const rawPrice = variant.price ?? product?.price;\n  if (rawPrice == null || rawPrice === \"\") return true;\n  const numericPrice = Number(rawPrice);\n  if (Number.isNaN(numericPrice)) return true;\n  if (numericPrice <= 0) return true;\n  return false;\n}\n","import type React from \"react\";\nimport {\n  useState,\n  useMemo,\n  useEffect,\n  useRef,\n  useCallback,\n  type ReactNode,\n} from \"react\";\nimport {\n  bundleSubscriptionPrice,\n  formatPortalPrice as formatPrice,\n  formatPortalPriceRange as formatPriceRange,\n  isStaticBundle,\n  usePortalProductCatalog,\n  usePortalProductDetail,\n  usePortalProducts,\n  type PortalProductPageParam,\n  type portalProducts,\n  type products,\n} from \"@fluid-app/products-core\";\nimport { useInfiniteQuery } from \"@tanstack/react-query\";\nimport {\n  Badge,\n  Button,\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n  Skeleton,\n} from \"@fluid-app/ui-primitives\";\nimport { SearchSort } from \"@fluid-app/ui-components/components/SearchSort\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport { ExternalLink, ShoppingCart } from \"lucide-react\";\nimport { useIsMobile } from \"@fluid-app/portal-react/shell/use-mobile\";\nimport ProductCard, {\n  tagPortalProduct,\n  type RenderImageProps,\n} from \"./product-card\";\nimport ImageGallery from \"./image-gallery\";\nimport QuantitySelector from \"./quantity-selector\";\nimport PurchaseOptions from \"./purchase-options\";\nimport { DynamicBundle } from \"./dynamic-bundle\";\nimport { isDynamicBundlesEnabled } from \"../utils/dynamic-bundle-flag\";\nimport { isVariantUnavailable } from \"../utils/is-variant-unavailable\";\n\ninterface ShopAppProps {\n  companyLogoUrl?: string | null;\n  /**\n   * Whether this viewer may see CV/QV, resolved by the consumer through the\n   * shared policy. Defaults to false — volume is opt-in.\n   */\n  canShowVolume?: boolean;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n  /** When provided, controls which product detail to show (URL-driven routing) */\n  productId?: string | null;\n  /** Called when a product is selected from the listing */\n  onSelectProduct?: (productId: string) => void;\n  /** Called when user navigates back from product detail */\n  onBack?: () => void;\n  /** Optional cart button to render in the header area */\n  cartButton?: ReactNode;\n  /** Set when the portal screen header is present above the scroll content. */\n  accountForPortalHeader?: boolean;\n}\n\nconst PAGE_SIZE = 25;\nconst RELATED_PRODUCTS_LIMIT = 5;\nconst RELATED_PRODUCTS_GRID_CLASS =\n  \"grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 lg:grid-cols-5\";\n\nfunction sanitizeHtml(html: string): string {\n  const doc = new DOMParser().parseFromString(html, \"text/html\");\n  for (const el of doc.querySelectorAll(\n    \"script, iframe, object, embed, form, base, meta, link, style\",\n  )) {\n    el.remove();\n  }\n  for (const el of doc.querySelectorAll(\"*\")) {\n    for (const attr of [...el.attributes]) {\n      if (\n        attr.name.toLowerCase().startsWith(\"on\") ||\n        attr.value.toLowerCase().trim().startsWith(\"javascript:\")\n      ) {\n        el.removeAttribute(attr.name);\n      }\n    }\n  }\n  return doc.body.innerHTML;\n}\n\nconst GRID_CLASS =\n  \"grid grid-cols-2 gap-x-3 gap-y-6 sm:grid-cols-3 md:grid-cols-4 xl:grid-cols-5\";\n\nfunction SkeletonGrid({\n  count = 8,\n  className = GRID_CLASS,\n}: {\n  count?: number;\n  className?: string;\n}) {\n  return (\n    <div className={className}>\n      {Array.from({ length: count }, (_, i) => (\n        <div key={i} className=\"space-y-2\">\n          <Skeleton className=\"aspect-square w-full rounded-lg\" />\n          <Skeleton className=\"h-4 w-3/4\" />\n          <Skeleton className=\"h-4 w-1/2\" />\n          <Skeleton className=\"h-3 w-1/3\" />\n        </div>\n      ))}\n    </div>\n  );\n}\n\nfunction ProductListing({\n  companyLogoUrl,\n  canShowVolume = false,\n  renderImage,\n  onSelectProduct,\n  cartButton,\n}: {\n  companyLogoUrl?: string | null;\n  /** Resolved by the consumer through the shared policy; volume is opt-in. */\n  canShowVolume?: boolean;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n  onSelectProduct: (productId: string) => void;\n  cartButton?: ReactNode;\n}) {\n  const observerTarget = useRef<HTMLDivElement>(null);\n  const { t } = useShopTranslation();\n  const isMobile = useIsMobile();\n\n  const catalog = usePortalProductCatalog({ perPage: PAGE_SIZE });\n\n  const {\n    data,\n    isLoading,\n    isFetchingNextPage,\n    hasNextPage,\n    fetchNextPage,\n    error,\n    isFetched,\n  } = useInfiniteQuery({\n    queryKey: catalog.queryKey,\n    queryFn: ({ pageParam, signal }) =>\n      catalog.fetchProducts(pageParam, signal),\n    getNextPageParam: catalog.getNextPageParam,\n    initialPageParam: undefined as PortalProductPageParam,\n  });\n\n  const handleIntersect = useCallback(\n    (entries: IntersectionObserverEntry[]) => {\n      if (entries[0]?.isIntersecting && hasNextPage && !isFetchingNextPage) {\n        fetchNextPage();\n      }\n    },\n    [hasNextPage, isFetchingNextPage, fetchNextPage],\n  );\n\n  useEffect(() => {\n    const target = observerTarget.current;\n    if (!target) return;\n\n    const observer = new IntersectionObserver(handleIntersect, {\n      threshold: 0.1,\n      rootMargin: \"200px\",\n    });\n    observer.observe(target);\n    return () => observer.disconnect();\n  }, [handleIntersect]);\n\n  const allProducts = data?.pages.flatMap((page) => page.products) ?? [];\n\n  const sortOptions = useMemo(\n    () => [\n      { id: \"title_asc\", label: t(\"sort_title_asc\") },\n      { id: \"title_desc\", label: t(\"sort_title_desc\") },\n      { id: \"price_asc\", label: t(\"sort_price_asc\") },\n      { id: \"price_desc\", label: t(\"sort_price_desc\") },\n      { id: \"created_at_desc\", label: t(\"sort_recent\") },\n      { id: \"created_at_asc\", label: t(\"sort_oldest\") },\n    ],\n    [t],\n  );\n\n  return (\n    <div>\n      {/* Mobile only: on desktop the portal's own sticky ScreenHeader shares\n          this scrollport and would sit on top of it. */}\n      <div className=\"bg-background sticky top-0 z-10 mx-auto px-2 md:static md:bg-transparent md:px-10\">\n        {/* Search + Sort */}\n        <div className=\"flex flex-col gap-3 py-4 sm:flex-row sm:items-center sm:justify-between\">\n          {/* The portal's top bar already shows this title on mobile. */}\n          <h1 className=\"text-foreground text-3xl leading-tight font-semibold tracking-tight max-md:hidden\">\n            {t(\"shop_all\")}\n          </h1>\n          <div className=\"flex items-center gap-2 sm:ml-auto\">\n            <div className=\"w-full max-w-sm sm:w-80 md:w-96\">\n              <SearchSort\n                searchValue={catalog.searchTerm}\n                onSearchChange={catalog.setSearchTerm}\n                placeholder={t(\"search_placeholder\")}\n                sortOptions={sortOptions.map((option) => ({\n                  value: option.id,\n                  label: option.label,\n                }))}\n                sortValue={catalog.currentSort}\n                onSortChange={catalog.setCurrentSort}\n                sortLabel={t(\"sort_by\")}\n                presentation={isMobile ? \"bottom-sheet\" : \"dialog\"}\n              />\n            </div>\n            {cartButton && (\n              <div className=\"flex shrink-0 items-center gap-3\">\n                {cartButton}\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n\n      {/* Product Grid */}\n      <div className=\"mx-auto space-y-8 px-2 md:px-10 md:py-8\">\n        {isLoading ? (\n          <SkeletonGrid />\n        ) : error ? (\n          <p className=\"mx-auto my-6 rounded-lg bg-red-100 px-3 py-2 text-red-500\">\n            {t(\"error_generic\")}\n          </p>\n        ) : isFetched && allProducts.length === 0 ? (\n          <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n            <p className=\"text-muted-foreground text-sm\">\n              {catalog.searchTerm\n                ? t(\"no_search_results\", { term: catalog.searchTerm })\n                : t(\"no_products\")}\n            </p>\n          </div>\n        ) : (\n          <>\n            <div className={GRID_CLASS}>\n              {allProducts.map((product) => (\n                <ProductCard\n                  key={product.id}\n                  product={tagPortalProduct(product)}\n                  canShowVolume={canShowVolume}\n                  {...(companyLogoUrl !== undefined && { companyLogoUrl })}\n                  {...(renderImage !== undefined && { renderImage })}\n                  onClick={() => onSelectProduct(String(product.id))}\n                />\n              ))}\n            </div>\n            <div ref={observerTarget} />\n            {isFetchingNextPage && <SkeletonGrid count={4} />}\n          </>\n        )}\n      </div>\n    </div>\n  );\n}\n\ninterface OptionGroup {\n  optionId: number;\n  name: string;\n  values: { id: number; name: string }[];\n}\n\n/**\n * Build the variant options map by grouping option_values across all variants.\n * Master variants can represent real selectable options in portal product\n * responses, so include them when they carry option_values.\n * Groups by option_id, collecting unique value entries.\n */\nfunction buildOptionGroups(variants: portalProducts.Variant[]): OptionGroup[] {\n  const groupMap = new Map<\n    number,\n    { name: string; values: Map<number, string> }\n  >();\n\n  for (const variant of variants) {\n    for (const ov of variant.option_values ?? []) {\n      if (ov.option_id == null || ov.id == null) continue;\n      if (!groupMap.has(ov.option_id)) {\n        groupMap.set(ov.option_id, {\n          name: ov.option_name ?? `Option ${ov.option_id}`,\n          values: new Map(),\n        });\n      }\n      groupMap.get(ov.option_id)!.values.set(ov.id, ov.name ?? String(ov.id));\n    }\n  }\n\n  return [...groupMap.entries()].map(([optionId, group]) => ({\n    optionId,\n    name: group.name,\n    values: [...group.values.entries()].map(([id, name]) => ({ id, name })),\n  }));\n}\n\n/**\n * Find the variant matching a set of selected option value IDs.\n * selections is a map of optionId → selected value id.\n */\nfunction findVariantBySelections(\n  variants: portalProducts.Variant[],\n  selections: Record<number, number>,\n): portalProducts.Variant | undefined {\n  const entries = Object.entries(selections).map(\n    ([k, v]) => [Number(k), v] as const,\n  );\n  if (entries.length === 0) return undefined;\n  const matchingVariants = variants.filter((v) =>\n    entries.every(([optionId, valueId]) =>\n      v.option_values?.some(\n        (ov) => ov.option_id === optionId && ov.id === valueId,\n      ),\n    ),\n  );\n  // When duplicate option values exist, prefer the concrete non-master\n  // variant. A master variant is still returned when it is the only match.\n  return (\n    matchingVariants.find((v) => v.is_master !== true) ?? matchingVariants[0]\n  );\n}\n\n/**\n * Map BFF flat subscription plans to the legacy ProductSubscriptionPlan shape\n * expected by the PurchaseOptions component.\n */\nfunction mapToLegacySubscriptionPlans(\n  plans: portalProducts.SubscriptionPlan[],\n): products.ProductSubscriptionPlan[] {\n  return plans.map((plan, idx) => ({\n    ...(plan.id !== undefined && { id: plan.id }),\n    default: idx === 0,\n    products_count: null,\n    subscribers_count: null,\n    active: true,\n    subscription_plan: {\n      id: plan.id ?? 0,\n      name: plan.name ?? \"\",\n      billing_interval: plan.billing_interval ?? 1,\n      billing_interval_unit: plan.billing_interval_unit ?? \"month\",\n      billing_frequency_in_words:\n        plan.billing_frequency ??\n        `${plan.billing_interval} ${plan.billing_interval_unit}`,\n      active: true,\n      // Intentionally null on the legacy shape: bundle subscription pricing\n      // reads price_adjustment_type/amount from the raw BFF plan\n      // (selectedBundlePlan), not this mapped legacy plan.\n      price_adjustment_amount: null,\n      price_adjustment_type: null,\n      ...(plan.savings_display_mode !== undefined && {\n        savings_display_mode: plan.savings_display_mode,\n      }),\n    },\n  }));\n}\n\nfunction ProductDetail({\n  companyLogoUrl,\n  canShowVolume = false,\n  productId,\n  renderImage,\n  onSelectProduct,\n  accountForPortalHeader,\n}: {\n  companyLogoUrl?: string | null;\n  /** Resolved by the consumer through the shared policy; volume is opt-in. */\n  canShowVolume?: boolean;\n  productId: string;\n  renderImage?: (props: RenderImageProps) => ReactNode;\n  onSelectProduct: (productId: string) => void;\n  accountForPortalHeader?: boolean;\n}) {\n  const [quantity, setQuantity] = useState(1);\n  // selections: optionId → selected value id\n  const [selections, setSelections] = useState<Record<number, number>>({});\n  const [userSelectedSubscribe, setUserSelectedSubscribe] = useState<\n    boolean | null\n  >(null);\n  const [selectedSubscriptionPlan, setSelectedSubscriptionPlan] = useState<\n    products.ProductSubscriptionPlan | undefined\n  >(undefined);\n\n  const { t } = useShopTranslation();\n\n  // Temporary gate: the dynamic-bundle configurator is dark unless\n  // ?dynamic_bundle=true (sticky per session). Off → dynamic bundles link out\n  // to the storefront as before. Read once per mount. See dynamic-bundle-flag.\n  const dynamicBundlesEnabled = useMemo(() => isDynamicBundlesEnabled(), []);\n\n  const { product, isLoading, error, images } = usePortalProductDetail({\n    productId,\n  });\n  const relatedProductsParams = useMemo(\n    () => ({ limit: RELATED_PRODUCTS_LIMIT + 1, sort: \"created_at_desc\" }),\n    [],\n  );\n  const { data: relatedProductsData, isLoading: isRelatedProductsLoading } =\n    usePortalProducts(relatedProductsParams);\n\n  const variants = useMemo(() => product?.variants ?? [], [product?.variants]);\n  const subscriptionPlans = useMemo(\n    () => product?.subscription_plans ?? [],\n    [product?.subscription_plans],\n  );\n\n  // Default to the master variant, or the first available variant\n  const masterVariant = useMemo(\n    () => variants.find((v) => v.is_master) ?? variants[0],\n    [variants],\n  );\n\n  // Build option groups from all variants with option_values\n  const optionGroups = useMemo(() => buildOptionGroups(variants), [variants]);\n\n  // Title-based fallback: products that distinguish variants by `title` only\n  // (no Options/OptionValues set up) — list every variant by title.\n  const showVariantChoices = optionGroups.length === 0 && variants.length > 1;\n  const [selectedVariantId, setSelectedVariantId] = useState<number | null>(\n    null,\n  );\n\n  // Initialise selections from the master variant when it carries option values,\n  // otherwise fall back to the first variant with option values.\n  useEffect(() => {\n    if (optionGroups.length === 0) return;\n    const source = masterVariant?.option_values?.length\n      ? masterVariant\n      : variants.find((v) => v.option_values?.length);\n    if (!source?.option_values?.length) return;\n    const defaults: Record<number, number> = {};\n    for (const ov of source.option_values) {\n      if (ov.option_id != null && ov.id != null) {\n        defaults[ov.option_id] = ov.id;\n      }\n    }\n    setSelections(defaults);\n  }, [optionGroups, variants, masterVariant]);\n\n  // Default the title-based picker to the master variant\n  useEffect(() => {\n    if (!showVariantChoices) return;\n    if (selectedVariantId != null) return;\n    if (masterVariant?.id == null) return;\n    setSelectedVariantId(masterVariant.id);\n  }, [showVariantChoices, selectedVariantId, masterVariant?.id]);\n\n  // Resolve the currently selected variant\n  const selectedVariant = useMemo(() => {\n    if (showVariantChoices) {\n      return variants.find((v) => v.id === selectedVariantId) ?? masterVariant;\n    }\n    if (optionGroups.length === 0) return masterVariant;\n    return findVariantBySelections(variants, selections) ?? masterVariant;\n  }, [\n    variants,\n    optionGroups,\n    selections,\n    masterVariant,\n    showVariantChoices,\n    selectedVariantId,\n  ]);\n\n  // Map BFF subscription plans → legacy ProductSubscriptionPlan shape for PurchaseOptions\n  const legacySubscriptionPlans = useMemo(\n    () => mapToLegacySubscriptionPlans(subscriptionPlans),\n    [subscriptionPlans],\n  );\n\n  // Derive subscription state from variant, falling back to subscribe-by-default\n  // when the product offers subscription plans — matches the front-facing storefront.\n  const showSubscribe = legacySubscriptionPlans.length > 0;\n  const showBuyOnce = selectedVariant?.subscription_only !== true;\n  const isSubscribe =\n    selectedVariant?.subscription_only === true ||\n    (userSelectedSubscribe ?? showSubscribe);\n\n  // Prefer variant-specific images when available, fall back to product-level images\n  const galleryImages = useMemo(() => {\n    const variantImages = selectedVariant?.images;\n    if (variantImages && variantImages.length > 0) {\n      return variantImages.map((img, idx) => ({\n        id: idx,\n        image_url: img.url ?? \"\",\n        image_path: null as string | null,\n        position: idx,\n      }));\n    }\n    return images.map((img, idx) => ({\n      id: img.id ?? idx,\n      image_url: img.url,\n      image_path: null as string | null,\n      position: idx,\n    }));\n  }, [selectedVariant?.images, images]);\n\n  const coverImage = galleryImages[0]?.image_url ?? null;\n  const relatedProducts = useMemo(\n    () =>\n      (relatedProductsData?.products ?? [])\n        .filter((relatedProduct) => String(relatedProduct.id) !== productId)\n        .slice(0, RELATED_PRODUCTS_LIMIT),\n    [productId, relatedProductsData?.products],\n  );\n\n  // Pricing: use selected variant's subscription_pricing for wholesale comparison\n  const selectedPricing = selectedVariant?.subscription_pricing?.find(\n    (sp) =>\n      sp.plan_id ===\n      (selectedSubscriptionPlan?.subscription_plan.id ??\n        legacySubscriptionPlans[0]?.subscription_plan.id),\n  );\n  const wholesalePrice = selectedVariant?.wholesale_price\n    ? Number(selectedVariant.wholesale_price)\n    : undefined;\n  const wholesaleSubscriptionPrice = selectedPricing?.wholesale_price\n    ? Number(selectedPricing.wholesale_price)\n    : undefined;\n\n  // Display prices\n  // price = retail price, wholesale_price = rep/logged-in price\n  // subscription_pricing.wholesale_price = subscription wholesale price\n  const currency = selectedVariant?.currency ?? product?.currency;\n  const displayPrice = formatPrice(\n    selectedVariant?.price ?? product?.price,\n    currency,\n  );\n  const displayWholesalePrice = formatPrice(\n    selectedVariant?.wholesale_price ??\n      selectedVariant?.price ??\n      product?.price,\n    currency,\n  );\n  const displayWholesaleSubscriptionPrice = formatPrice(\n    selectedPricing?.wholesale_price ??\n      selectedPricing?.price ??\n      selectedVariant?.wholesale_price ??\n      undefined,\n    currency,\n  );\n\n  // Resolve subscription plan ID for the cart SDK\n  const resolvedPlanId =\n    selectedSubscriptionPlan?.subscription_plan.id ??\n    legacySubscriptionPlans.find((p) => p.default)?.subscription_plan.id ??\n    legacySubscriptionPlans[0]?.subscription_plan.id;\n\n  // Cart data attributes — SDK expects variant ID, not product ID\n  const cartVariantId = String(selectedVariant?.id ?? product?.id ?? \"\");\n\n  if (isLoading) {\n    return (\n      <div className=\"mx-auto max-w-7xl py-8 pr-4 pl-0 md:pr-6 lg:pr-8 lg:pl-0\">\n        <div className=\"grid grid-cols-1 gap-5 lg:grid-cols-2\">\n          <Skeleton className=\"aspect-square w-full rounded-lg\" />\n          <div className=\"space-y-4 pl-2 lg:pl-20\">\n            <Skeleton className=\"h-8 w-3/4\" />\n            <Skeleton className=\"h-5 w-1/4\" />\n            <Skeleton className=\"h-20 w-full\" />\n            <Skeleton className=\"h-10 w-1/2\" />\n            <Skeleton className=\"h-10 w-full\" />\n          </div>\n        </div>\n      </div>\n    );\n  }\n\n  if (error) {\n    return (\n      <div className=\"flex min-h-[400px] items-center justify-center\">\n        <div className=\"text-center\">\n          <h3 className=\"text-foreground mb-2 text-lg font-medium\">\n            {t(\"error_loading\")}\n          </h3>\n          <p className=\"text-muted-foreground\">{t(\"error_generic\")}</p>\n        </div>\n      </div>\n    );\n  }\n\n  if (!product) {\n    return (\n      <div className=\"flex min-h-[400px] items-center justify-center\">\n        <div className=\"text-center\">\n          <h3 className=\"text-foreground mb-2 text-lg font-medium\">\n            {t(\"product_not_found\")}\n          </h3>\n          <p className=\"text-muted-foreground\">\n            {t(\"product_not_found_description\")}\n          </p>\n        </div>\n      </div>\n    );\n  }\n\n  const title = product.name || t(\"product_fallback_name\");\n  const isBundle = product.is_bundle === true;\n  const bundleUrl = isBundle && product.shop_link ? product.shop_link : null;\n  // A static bundle (no customizable groups) is functionally just a product, so\n  // keep logged-in shoppers in-app with wholesale pricing by adding it straight\n  // to the cart instead of bouncing to the storefront. Gate on a real variant\n  // ID (the cart SDK expects a variant, not a product ID — `cartVariantId`\n  // falls back to `product.id`): without a master variant we can't add it\n  // reliably, so fall back to the storefront link below.\n  const staticBundle =\n    isBundle && isStaticBundle(product) && selectedVariant?.id != null;\n  // A dynamic (customizable) bundle with group data renders the native\n  // configurator, which owns its own Total + subscribe toggle + CV/QV\n  // (CURRENT-1834), so the generic bundle price/CV badges are suppressed below.\n  // Gated behind ?dynamic_bundle=true — when off, this is false so dynamic\n  // bundles fall through to the storefront redirect and the generic price/CV\n  // badges render as before.\n  const hasDynamicConfig =\n    dynamicBundlesEnabled &&\n    isBundle &&\n    !staticBundle &&\n    (product.product_bundle_groups?.length ?? 0) > 0;\n  // Derived only when the gate says show, so a suppressed value never becomes\n  // a rendered placeholder.\n  const displayCv = canShowVolume\n    ? isBundle\n      ? product.cv\n      : selectedVariant?.cv\n    : undefined;\n  const displayQv = canShowVolume\n    ? isBundle\n      ? product.qv\n      : selectedVariant?.qv\n    : undefined;\n  // `price_range` is tier-aware (the backend resolves wholesale for reps,\n  // retail for customers), so display it directly — no client-side\n  // wholesale/retail strikethrough. A static bundle has min === max, which\n  // formatPriceRange collapses to a single price.\n  const bundlePriceDisplay = isBundle\n    ? (formatPriceRange(product.price_range, currency) ??\n      formatPrice(product.price, currency))\n    : null;\n  // Static-bundle subscription price: the tier base is price_range.min (== max\n  // for a static bundle); the per-plan subscription price is derived from the\n  // selected plan's adjustment (percentage off the base, or the tier-aware\n  // subscription_price_range). The plan's $0 master variant carries no usable\n  // subscription price, so we compute it from the bundle payload instead.\n  const bundleBasePrice =\n    product.price_range?.min != null\n      ? Number(product.price_range.min)\n      : undefined;\n  const selectedBundlePlan =\n    subscriptionPlans.find((p) => p.id === resolvedPlanId) ??\n    subscriptionPlans[0];\n  const bundleSubPrice = staticBundle\n    ? bundleSubscriptionPrice(\n        bundleBasePrice,\n        product.subscription_price_range,\n        selectedBundlePlan,\n      )\n    : undefined;\n  const staticBundleSubDisplay =\n    bundleSubPrice !== undefined\n      ? formatPrice(bundleSubPrice.toFixed(2), currency)\n      : null;\n  const isUnavailable =\n    !isBundle && isVariantUnavailable(selectedVariant, product);\n\n  const imageGallery = (\n    <ImageGallery\n      images={galleryImages}\n      fallbackImageUrl={\n        coverImage ??\n        \"https://ik.imagekit.io/fluid/tr:w-1500,h-1500,cm-pad_resize,bg-FFFFFF/980191006/images/WJHL8V/WeCommerce_Logotype-Black_NcO-MzatB.png\"\n      }\n      productTitle={title}\n      {...(renderImage !== undefined && { renderImage })}\n    />\n  );\n\n  const productTitle = (\n    <div className=\"flex flex-wrap items-start gap-2\">\n      <h1 className=\"text-foreground flex-1 text-4xl leading-tight font-bold tracking-normal\">\n        {title}\n      </h1>\n      {isBundle && <Badge variant=\"secondary\">{t(\"bundle_badge\")}</Badge>}\n    </div>\n  );\n\n  const productDescription = (\n    <div className=\"border-border/70 mt-8 border-t pt-6\">\n      <h3 className=\"text-foreground mb-2 text-lg font-bold\">\n        {t(\"product_description\")}\n      </h3>\n      <div\n        className=\"text-muted-foreground [&_a]:text-foreground space-y-3 text-sm leading-6 [&_a]:underline [&_li]:ml-4 [&_li]:list-disc\"\n        dangerouslySetInnerHTML={{\n          __html: sanitizeHtml(product.description ?? \"\"),\n        }}\n      />\n    </div>\n  );\n\n  // Dynamic-bundle subtitle: the product description shown directly under the\n  // title (Sophie's design), without the labeled \"Product Description\" section\n  // heading the standard layout uses at the bottom.\n  const bundleDescription = product.description ? (\n    <div\n      className=\"text-muted-foreground [&_a]:text-foreground space-y-3 text-sm leading-6 [&_a]:underline [&_li]:ml-4 [&_li]:list-disc\"\n      dangerouslySetInnerHTML={{\n        __html: sanitizeHtml(product.description),\n      }}\n    />\n  ) : null;\n\n  const relatedProductsSection =\n    isRelatedProductsLoading || relatedProducts.length > 0 ? (\n      <section className=\"border-border/70 mt-12 border-t pt-8\">\n        <h2 className=\"text-foreground mb-5 text-2xl font-bold\">\n          {t(\"more_to_shop\")}\n        </h2>\n        {isRelatedProductsLoading ? (\n          <SkeletonGrid\n            count={RELATED_PRODUCTS_LIMIT}\n            className={RELATED_PRODUCTS_GRID_CLASS}\n          />\n        ) : (\n          <div className={RELATED_PRODUCTS_GRID_CLASS}>\n            {relatedProducts.map((relatedProduct) => (\n              <ProductCard\n                key={relatedProduct.id}\n                product={tagPortalProduct(relatedProduct)}\n                canShowVolume={canShowVolume}\n                {...(companyLogoUrl !== undefined && { companyLogoUrl })}\n                {...(renderImage !== undefined && { renderImage })}\n                onClick={() => onSelectProduct(String(relatedProduct.id))}\n              />\n            ))}\n          </div>\n        )}\n      </section>\n    ) : null;\n\n  // Dynamic (customizable) bundle: Sophie's design puts the purchase panel\n  // (Total + subscribe + add-to-cart) under the product image on the left and\n  // the group configurator on the right — a different split from the standard\n  // product layout, so DynamicBundle owns its own two-column grid here.\n  if (hasDynamicConfig) {\n    return (\n      <div className=\"pb-8 md:pl-8\">\n        <div className=\"mx-auto max-w-7xl px-4 py-8 md:pr-6 md:pl-0 lg:pr-8 lg:pl-0\">\n          <DynamicBundle\n            product={product}\n            canShowVolume={canShowVolume}\n            subscriptionPlans={legacySubscriptionPlans}\n            cartVariantId={cartVariantId}\n            {...(resolvedPlanId != null && {\n              subscriptionPlanId: resolvedPlanId,\n            })}\n            {...(currency !== undefined && { currency })}\n            imageSlot={imageGallery}\n            titleSlot={productTitle}\n            descriptionSlot={bundleDescription}\n            {...(accountForPortalHeader !== undefined && {\n              accountForPortalHeader,\n            })}\n          />\n          {relatedProductsSection}\n        </div>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"pb-8 md:pl-8\">\n      <div className=\"mx-auto max-w-7xl px-4 py-8 md:pr-6 md:pl-0 lg:pr-8 lg:pl-0\">\n        <div className=\"grid grid-cols-1 gap-8 lg:grid-cols-[minmax(0,1.12fr)_minmax(320px,0.88fr)] xl:gap-12\">\n          {/* Image Gallery */}\n          <div\n            className={\n              accountForPortalHeader\n                ? \"lg:sticky lg:top-20 lg:self-start\"\n                : \"lg:sticky lg:top-8 lg:self-start\"\n            }\n          >\n            {imageGallery}\n          </div>\n\n          {/* Product Info */}\n          <div className=\"self-start lg:pt-4\">\n            {productTitle}\n\n            {/* Customizable bundle without group data: show the price range.\n                With group data, DynamicBundle renders the running Total. */}\n            {isBundle &&\n              !staticBundle &&\n              !hasDynamicConfig &&\n              bundlePriceDisplay && (\n                <div className=\"mt-5 mb-2 flex items-center gap-2\">\n                  <span className=\"text-foreground text-xl font-semibold\">\n                    {bundlePriceDisplay}\n                  </span>\n                </div>\n              )}\n\n            {/* Static bundle: show the tier-resolved price_range directly. The\n                backend already resolved the tier (wholesale for reps, retail\n                for customers), so — unlike a normal product — there is no\n                separate retail value to strike through in buy-once mode\n                (bundle price/wholesale_price are the $0 master-variant\n                placeholder). When subscribed, show the discounted subscription\n                price with the base price struck through (the subscribe\n                savings). */}\n            {staticBundle && bundlePriceDisplay && (\n              <div className=\"mt-5 mb-2 flex items-center gap-2\">\n                <span className=\"text-foreground text-xl font-semibold\">\n                  {isSubscribe && staticBundleSubDisplay\n                    ? staticBundleSubDisplay\n                    : bundlePriceDisplay}\n                </span>\n                {isSubscribe &&\n                  staticBundleSubDisplay &&\n                  staticBundleSubDisplay !== bundlePriceDisplay && (\n                    <span className=\"text-muted-foreground text-sm line-through\">\n                      {bundlePriceDisplay}\n                    </span>\n                  )}\n              </div>\n            )}\n\n            {/* Price — matches admin exactly */}\n            {!isBundle && !isUnavailable && (\n              <div className=\"mt-5 mb-2 flex items-center gap-2\">\n                <span className=\"text-foreground text-xl font-semibold\">\n                  {isSubscribe\n                    ? displayWholesaleSubscriptionPrice\n                    : displayWholesalePrice}\n                </span>\n                {((isSubscribe &&\n                  displayWholesaleSubscriptionPrice !==\n                    displayWholesalePrice) ||\n                  (!isSubscribe && displayWholesalePrice !== displayPrice)) && (\n                  <span className=\"text-muted-foreground text-sm line-through\">\n                    {isSubscribe ? displayWholesalePrice : displayPrice}\n                  </span>\n                )}\n              </div>\n            )}\n\n            {/* The gate decides whether this viewer may see volume; presence\n                decides whether there is any to show. A product may carry one\n                metric without the other, so `-` still stands in for the\n                missing half rather than dropping the pill. */}\n            {canShowVolume &&\n              !hasDynamicConfig &&\n              (displayCv != null || displayQv != null) && (\n                <div className=\"text-muted-foreground bg-muted/50 mt-3 mb-5 inline-flex rounded-full px-3 py-1 text-xs font-medium\">\n                  CV {displayCv ?? \"-\"} | QV {displayQv ?? \"-\"}\n                </div>\n              )}\n\n            {isBundle && !staticBundle ? (\n              <div className=\"pt-4\">\n                {/* Customizable bundle without the native configurator (flag off\n                    or no bundle-group data): fall back to the storefront\n                    redirect. The dynamic configurator returns earlier. */}\n                <Button\n                  variant=\"default\"\n                  className=\"h-12 w-full rounded-full text-base font-semibold\"\n                  disabled={!bundleUrl}\n                  onClick={() => {\n                    if (bundleUrl)\n                      window.open(bundleUrl, \"_blank\", \"noopener,noreferrer\");\n                  }}\n                >\n                  {t(\"purchase_bundle\")}\n                  <ExternalLink className=\"size-4\" />\n                </Button>\n                {!bundleUrl && (\n                  <p className=\"text-muted-foreground mt-2 text-center text-xs\">\n                    {t(\"bundle_unavailable\")}\n                  </p>\n                )}\n              </div>\n            ) : staticBundle ? (\n              <div className=\"pt-4\">\n                {/* Static bundle: buy in-app like a product, including\n                    subscription when the bundle offers plans. Pricing is the\n                    tier-aware bundle total: base = price_range, subscription =\n                    bundleSubPrice (computed from the plan adjustment), so\n                    PurchaseOptions shows the real \"save X%\". */}\n                <PurchaseOptions\n                  showBuyOnce={showBuyOnce}\n                  showSubscribe={showSubscribe}\n                  isSubscribe={isSubscribe}\n                  onSubscribeChange={setUserSelectedSubscribe}\n                  {...(bundleBasePrice !== undefined && {\n                    wholesalePrice: bundleBasePrice,\n                  })}\n                  {...(bundleSubPrice !== undefined && {\n                    wholesaleSubscriptionPrice: bundleSubPrice,\n                  })}\n                  {...(currency !== undefined && { currency })}\n                  product_subscription_plans={legacySubscriptionPlans}\n                  {...(selectedSubscriptionPlan !== undefined && {\n                    selectedSubscriptionPlan,\n                  })}\n                  onSubscriptionPlanChange={setSelectedSubscriptionPlan}\n                />\n                <div className=\"mt-5 space-y-4\">\n                  <div>\n                    <h3 className=\"text-foreground mb-2 text-sm font-semibold\">\n                      {t(\"quantity\")}\n                    </h3>\n                    <QuantitySelector\n                      quantity={quantity}\n                      setQuantity={setQuantity}\n                      disabled={isUnavailable}\n                    />\n                  </div>\n                  <Button\n                    variant=\"default\"\n                    className=\"h-12 w-full rounded-full text-base font-semibold\"\n                    disabled={isUnavailable}\n                    {...(!isUnavailable && {\n                      \"data-fluid-add-to-cart\": cartVariantId,\n                      \"data-fluid-quantity\": quantity,\n                      \"data-fluid-subscribe\": isSubscribe,\n                      \"data-fluid-subscription-plan-id\": isSubscribe\n                        ? String(resolvedPlanId ?? \"\")\n                        : \"\",\n                      \"data-fluid-open-cart-after-add\": \"true\",\n                    })}\n                  >\n                    <ShoppingCart className=\"size-4\" />\n                    {isSubscribe ? t(\"subscribe\") : t(\"add_to_cart\")}\n                  </Button>\n                </div>\n              </div>\n            ) : (\n              <>\n                {/* Purchase Options — matches admin PurchaseOptions component */}\n                <PurchaseOptions\n                  showBuyOnce={showBuyOnce}\n                  showSubscribe={showSubscribe}\n                  isSubscribe={isSubscribe}\n                  onSubscribeChange={setUserSelectedSubscribe}\n                  {...(wholesalePrice !== undefined && { wholesalePrice })}\n                  {...(wholesaleSubscriptionPrice !== undefined && {\n                    wholesaleSubscriptionPrice,\n                  })}\n                  {...(currency !== undefined && { currency })}\n                  product_subscription_plans={legacySubscriptionPlans}\n                  {...(selectedSubscriptionPlan !== undefined && {\n                    selectedSubscriptionPlan,\n                  })}\n                  onSubscriptionPlanChange={setSelectedSubscriptionPlan}\n                />\n\n                {/* Variant Options — matches admin layout */}\n                {optionGroups.length > 0 && (\n                  <div className=\"mb-4 pt-4\">\n                    {optionGroups.map((group) => (\n                      <div\n                        key={group.optionId}\n                        className=\"mb-3 flex items-center\"\n                      >\n                        <h3 className=\"text-md text-foreground w-24 font-bold\">\n                          {group.name.charAt(0).toUpperCase() +\n                            group.name.slice(1)}\n                        </h3>\n                        <Select\n                          value={String(selections[group.optionId] ?? \"\")}\n                          onValueChange={(value) =>\n                            setSelections((prev) => ({\n                              ...prev,\n                              [group.optionId]: Number(value),\n                            }))\n                          }\n                        >\n                          <SelectTrigger className=\"w-48 max-w-full\">\n                            <SelectValue\n                              placeholder={t(\"select_option\", {\n                                name: group.name,\n                              })}\n                            />\n                          </SelectTrigger>\n                          <SelectContent position=\"popper\" className=\"max-h-60\">\n                            {group.values.map((v) => (\n                              <SelectItem key={v.id} value={String(v.id)}>\n                                {v.name}\n                              </SelectItem>\n                            ))}\n                          </SelectContent>\n                        </Select>\n                      </div>\n                    ))}\n                  </div>\n                )}\n\n                {/* Title-based variant picker — for products without Options */}\n                {showVariantChoices && (\n                  <div className=\"mb-4 pt-4\">\n                    <div className=\"mb-3 flex items-center\">\n                      <h3 className=\"text-md text-foreground w-24 font-bold\">\n                        {t(\"variant_label\")}\n                      </h3>\n                      <Select\n                        value={\n                          selectedVariantId != null\n                            ? String(selectedVariantId)\n                            : \"\"\n                        }\n                        onValueChange={(value) =>\n                          setSelectedVariantId(Number(value))\n                        }\n                      >\n                        <SelectTrigger className=\"w-48 max-w-full\">\n                          <SelectValue placeholder={t(\"select_variant\")} />\n                        </SelectTrigger>\n                        <SelectContent position=\"popper\" className=\"max-h-60\">\n                          {variants.map((v, idx) =>\n                            v.id == null ? null : (\n                              <SelectItem key={v.id} value={String(v.id)}>\n                                {v.title ||\n                                  t(\"variant_fallback_name\", {\n                                    index: String(idx + 1),\n                                  })}\n                              </SelectItem>\n                            ),\n                          )}\n                        </SelectContent>\n                      </Select>\n                    </div>\n                  </div>\n                )}\n\n                {/* Unavailable product message */}\n                {isUnavailable && (\n                  <div className=\"text-muted-foreground text-sm\">\n                    {t(\"product_unavailable\")}\n                  </div>\n                )}\n\n                {/* Quantity and Add to Cart — matches admin. data-fluid-* attrs are stripped when unavailable so the cart SDK can't bind to a disabled button. */}\n                <div className=\"mt-5 space-y-4\">\n                  <div>\n                    <h3 className=\"text-foreground mb-2 text-sm font-semibold\">\n                      {t(\"quantity\")}\n                    </h3>\n                    <QuantitySelector\n                      quantity={quantity}\n                      setQuantity={setQuantity}\n                      disabled={isUnavailable}\n                    />\n                  </div>\n\n                  <Button\n                    variant=\"default\"\n                    className=\"h-12 w-full rounded-full text-base font-semibold\"\n                    disabled={isUnavailable}\n                    {...(!isUnavailable && {\n                      \"data-fluid-add-to-cart\": cartVariantId,\n                      \"data-fluid-quantity\": quantity,\n                      \"data-fluid-subscribe\": isSubscribe,\n                      \"data-fluid-subscription-plan-id\": isSubscribe\n                        ? String(resolvedPlanId ?? \"\")\n                        : \"\",\n                      \"data-fluid-open-cart-after-add\": \"true\",\n                    })}\n                  >\n                    <ShoppingCart className=\"size-4\" />\n                    {isSubscribe ? t(\"subscribe\") : t(\"add_to_cart\")}\n                  </Button>\n                </div>\n              </>\n            )}\n\n            {productDescription}\n          </div>\n        </div>\n\n        {relatedProductsSection}\n      </div>\n    </div>\n  );\n}\n\nexport default function ShopApp({\n  companyLogoUrl,\n  canShowVolume = false,\n  renderImage,\n  productId: controlledProductId,\n  onSelectProduct: onSelectProductProp,\n  onBack: _onBack,\n  cartButton,\n  accountForPortalHeader,\n}: ShopAppProps): React.JSX.Element {\n  // Internal state used only when navigation is not controlled externally\n  const [internalProductId, setInternalProductId] = useState<string | null>(\n    null,\n  );\n\n  const isControlled = controlledProductId !== undefined;\n  const activeProductId = isControlled\n    ? controlledProductId\n    : internalProductId;\n\n  const handleSelectProduct = onSelectProductProp ?? setInternalProductId;\n\n  if (activeProductId) {\n    return (\n      <ProductDetail\n        key={activeProductId}\n        {...(companyLogoUrl !== undefined && { companyLogoUrl })}\n        canShowVolume={canShowVolume}\n        productId={activeProductId}\n        {...(renderImage !== undefined && { renderImage })}\n        onSelectProduct={handleSelectProduct}\n        {...(accountForPortalHeader !== undefined && {\n          accountForPortalHeader,\n        })}\n      />\n    );\n  }\n\n  return (\n    <ProductListing\n      {...(companyLogoUrl !== undefined && { companyLogoUrl })}\n      canShowVolume={canShowVolume}\n      {...(renderImage !== undefined && { renderImage })}\n      onSelectProduct={handleSelectProduct}\n      cartButton={cartButton}\n    />\n  );\n}\n","import { type ComponentProps } from \"react\";\nimport ShopApp from \"@fluid-app/shop-ui/components/shop-app\";\nimport { ShopContainer } from \"@fluid-app/cart-ui\";\nimport {\n  Breadcrumb,\n  BreadcrumbList,\n  BreadcrumbItem,\n  BreadcrumbLink,\n  BreadcrumbPage,\n  BreadcrumbSeparator,\n} from \"@fluid-app/ui-primitives\";\nimport { ScreenHeaderBreadcrumbs } from \"@fluid-app/portal-react/shell/ScreenHeaderContext\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\nimport type {\n  BackgroundValue,\n  BorderRadiusOptions,\n  ColorOptions,\n  PaddingOptions,\n} from \"../types\";\nimport type { WidgetPropertySchema } from \"../registries/property-schema-types\";\nimport { useAppNavigation } from \"../shell/AppNavigationContext\";\nimport { useNavigationParent } from \"../shell/use-navigation-parent\";\nimport { useStore } from \"../hooks/use-store\";\nimport { useCanShowVolume } from \"@fluid-app/portal-react/hooks/use-can-show-volume\";\nimport { usePortalProductDetail } from \"@fluid-app/products-core\";\n\ntype ShopScreenProps = ComponentProps<\"div\"> & {\n  background?: BackgroundValue;\n  textColor?: ColorOptions;\n  accentColor?: ColorOptions;\n  padding?: PaddingOptions;\n  borderRadius?: BorderRadiusOptions;\n};\n\nexport function ShopScreen(props: ShopScreenProps): React.JSX.Element {\n  return <ShopScreenContent {...props} />;\n}\n\nfunction ShopScreenContent({\n  /* eslint-disable @typescript-eslint/no-unused-vars -- destructured to exclude from divProps spread */\n  background,\n  textColor,\n  accentColor,\n  padding,\n  borderRadius,\n  /* eslint-enable @typescript-eslint/no-unused-vars */\n  ...divProps\n}: ShopScreenProps): React.JSX.Element {\n  const { t } = useShopTranslation();\n  const { data: store } = useStore();\n  // The portal shop is where an entitled rep expects to see volume, so the\n  // screen resolves the gate and hands shop-ui the answer. shop-ui defaults to\n  // false and has no viewer signal of its own — omitting this suppresses volume\n  // for reps rather than leaking it, but suppressing it is still wrong.\n  const canShowVolume = useCanShowVolume();\n  const { currentSlug, navigate } = useAppNavigation();\n\n  // Parse product ID from slug: \"shop/{productId}\"\n  const parts = currentSlug.split(\"/\");\n  const productId = parts[1] ?? null;\n\n  return (\n    <>\n      {!productId && (\n        <ScreenHeaderBreadcrumbs>\n          <Breadcrumb>\n            <BreadcrumbList className=\"text-lg\">\n              <BreadcrumbItem>\n                <BreadcrumbPage className=\"font-semibold\">\n                  {t(\"breadcrumb\")}\n                </BreadcrumbPage>\n              </BreadcrumbItem>\n            </BreadcrumbList>\n          </Breadcrumb>\n        </ScreenHeaderBreadcrumbs>\n      )}\n      <div {...divProps} className={divProps.className ?? \"\"}>\n        <ShopContainer>\n          {productId && <ProductBreadcrumb productId={productId} />}\n          <ShopApp\n            companyLogoUrl={store?.logo_url ?? undefined}\n            canShowVolume={canShowVolume}\n            productId={productId}\n            accountForPortalHeader\n            onSelectProduct={(id) => navigate(`shop/${id}`)}\n            onBack={() => navigate(\"shop\")}\n          />\n        </ShopContainer>\n      </div>\n    </>\n  );\n}\n\nexport const shopScreenPropertySchema: WidgetPropertySchema = {\n  widgetType: \"ShopScreen\",\n  displayName: \"Shop Screen\",\n  tabsConfig: [{ id: \"styling\", label: \"Styling\" }],\n  fields: [],\n} as const satisfies WidgetPropertySchema;\n\n/**\n * Renders inside PortalProductsCoreProvider to set breadcrumbs with the\n * product name: \"Shop > {product name}\". Overrides the parent's \"Shop\"\n * breadcrumb once product data loads.\n */\nfunction ProductBreadcrumb({ productId }: { productId: string }) {\n  const { navigate } = useAppNavigation();\n  const parentBreadcrumb = useNavigationParent();\n  const { t } = useShopTranslation();\n  const { product } = usePortalProductDetail({ productId });\n  const productName = product?.name ?? t(\"product_fallback_name\");\n\n  const parentLabel = parentBreadcrumb?.label ?? t(\"breadcrumb\");\n  const onParentClick = parentBreadcrumb?.onClick ?? (() => navigate(\"shop\"));\n\n  return (\n    <ScreenHeaderBreadcrumbs>\n      <Breadcrumb>\n        <BreadcrumbList className=\"text-lg\">\n          <BreadcrumbItem>\n            <BreadcrumbLink\n              onClick={onParentClick}\n              className=\"cursor-pointer font-semibold\"\n            >\n              {parentLabel}\n            </BreadcrumbLink>\n          </BreadcrumbItem>\n          <BreadcrumbSeparator />\n          <BreadcrumbItem>\n            <BreadcrumbPage className=\"font-semibold\">\n              {productName}\n            </BreadcrumbPage>\n          </BreadcrumbItem>\n        </BreadcrumbList>\n      </Breadcrumb>\n    </ScreenHeaderBreadcrumbs>\n  );\n}\n"],"mappings":"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"}