@fluid-app/portal-sdk 0.1.456 → 0.1.458

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/{CardWidget-BbddboSI.cjs → CardWidget-8kubp1dh.cjs} +2 -2
  2. package/dist/{CardWidget-Bs0hqVjy.mjs → CardWidget-Dee9DpBH.mjs} +2 -2
  3. package/dist/{CardWidget-Bs0hqVjy.mjs.map → CardWidget-Dee9DpBH.mjs.map} +1 -1
  4. package/dist/{CardWidget-CGbAcMeq.cjs → CardWidget-YGsYcK7i.cjs} +2 -2
  5. package/dist/{CardWidget-CGbAcMeq.cjs.map → CardWidget-YGsYcK7i.cjs.map} +1 -1
  6. package/dist/{ContainerWidget-bbzSrNUJ.mjs → ContainerWidget-BVlY76wy.mjs} +2 -2
  7. package/dist/{ContainerWidget-bbzSrNUJ.mjs.map → ContainerWidget-BVlY76wy.mjs.map} +1 -1
  8. package/dist/{ContainerWidget-CRCAjF4G.cjs → ContainerWidget-Ba_Ti1Qh.cjs} +3 -3
  9. package/dist/{ContainerWidget-BOZYpAH1.cjs → ContainerWidget-DCDrjhgP.cjs} +2 -2
  10. package/dist/{ContainerWidget-BOZYpAH1.cjs.map → ContainerWidget-DCDrjhgP.cjs.map} +1 -1
  11. package/dist/{FluidProvider-CbA8KrhD.mjs → FluidProvider-C5JqeOGi.mjs} +3 -3
  12. package/dist/{FluidProvider-CbA8KrhD.mjs.map → FluidProvider-C5JqeOGi.mjs.map} +1 -1
  13. package/dist/{FluidProvider-JiGJqJQe.cjs → FluidProvider-LYDiXUkk.cjs} +3 -3
  14. package/dist/{FluidProvider-JiGJqJQe.cjs.map → FluidProvider-LYDiXUkk.cjs.map} +1 -1
  15. package/dist/{LayoutWidget-nea3CGc6.cjs → LayoutWidget-BKjkjoNO.cjs} +2 -2
  16. package/dist/{LayoutWidget-CS8fOr1e.cjs → LayoutWidget-CqIkNZNa.cjs} +2 -2
  17. package/dist/{LayoutWidget-CS8fOr1e.cjs.map → LayoutWidget-CqIkNZNa.cjs.map} +1 -1
  18. package/dist/{LayoutWidget-ENLd1G__.mjs → LayoutWidget-DkYKWC2P.mjs} +2 -2
  19. package/dist/{LayoutWidget-ENLd1G__.mjs.map → LayoutWidget-DkYKWC2P.mjs.map} +1 -1
  20. package/dist/{MessagingScreen-BeUhzBWr.cjs → MessagingScreen-BLZt8lMN.cjs} +2 -2
  21. package/dist/{MessagingScreen-BeUhzBWr.cjs.map → MessagingScreen-BLZt8lMN.cjs.map} +1 -1
  22. package/dist/{MessagingScreen-D6Bxoq0a.cjs → MessagingScreen-CoBvR0p4.cjs} +7 -7
  23. package/dist/{MessagingScreen-Dt6Rwk7_.mjs → MessagingScreen-CowNfnz0.mjs} +8 -8
  24. package/dist/{MessagingScreen-BtdVeSR5.mjs → MessagingScreen-kftHOAO7.mjs} +2 -2
  25. package/dist/{MessagingScreen-BtdVeSR5.mjs.map → MessagingScreen-kftHOAO7.mjs.map} +1 -1
  26. package/dist/{OrdersScreen-BirJq38x.cjs → OrdersScreen-B3Aa4ip-.cjs} +7 -7
  27. package/dist/{OrdersScreen-BSwESoF3.cjs → OrdersScreen-C-B6_Agy.cjs} +2 -2
  28. package/dist/{OrdersScreen-BSwESoF3.cjs.map → OrdersScreen-C-B6_Agy.cjs.map} +1 -1
  29. package/dist/{OrdersScreen-fZFg1l1W.mjs → OrdersScreen-DDQ8QXWo.mjs} +2 -2
  30. package/dist/{OrdersScreen-fZFg1l1W.mjs.map → OrdersScreen-DDQ8QXWo.mjs.map} +1 -1
  31. package/dist/{OrdersScreen-1kPmXJga.mjs → OrdersScreen-DPkq_mS2.mjs} +8 -8
  32. package/dist/{ProfileScreen-CirvTBwX.cjs → ProfileScreen-CK17yi8m.cjs} +8 -8
  33. package/dist/{ProfileScreen-Dj1pTzLz.mjs → ProfileScreen-DnlCxwh6.mjs} +3 -3
  34. package/dist/{ProfileScreen-Dj1pTzLz.mjs.map → ProfileScreen-DnlCxwh6.mjs.map} +1 -1
  35. package/dist/{ProfileScreen-BvxuA9P4.cjs → ProfileScreen-R4ulzpPu.cjs} +3 -3
  36. package/dist/{ProfileScreen-BvxuA9P4.cjs.map → ProfileScreen-R4ulzpPu.cjs.map} +1 -1
  37. package/dist/{ProfileScreen-Bqv7MNPH.mjs → ProfileScreen-ntvqBCpb.mjs} +9 -9
  38. package/dist/{ScreenRenderer-EGk5-w0h.cjs → ScreenRenderer-D0-ymcZV.cjs} +13 -4
  39. package/dist/ScreenRenderer-D0-ymcZV.cjs.map +1 -0
  40. package/dist/{ScreenRenderer-BQz-1cCn.mjs → ScreenRenderer-KJIcMne2.mjs} +13 -4
  41. package/dist/ScreenRenderer-KJIcMne2.mjs.map +1 -0
  42. package/dist/{ShopScreen-3ryiBWeh.cjs → ShopScreen-BtwvHTbN.cjs} +7 -7
  43. package/dist/{ShopScreen-CfvCqYJ9.mjs → ShopScreen-CGX6YHVV.mjs} +8 -8
  44. package/dist/{ShopScreen-BKWFrPHB.mjs → ShopScreen-CUoUECrG.mjs} +3 -3
  45. package/dist/{ShopScreen-BKWFrPHB.mjs.map → ShopScreen-CUoUECrG.mjs.map} +1 -1
  46. package/dist/{ShopScreen-C3vwiETG.cjs → ShopScreen-DS5-ICA-.cjs} +2 -2
  47. package/dist/{ShopScreen-C3vwiETG.cjs.map → ShopScreen-DS5-ICA-.cjs.map} +1 -1
  48. package/dist/{ShopWidget-CwiD88Sr.mjs → ShopWidget-DXVNRrFY.mjs} +2 -2
  49. package/dist/{ShopWidget-CwiD88Sr.mjs.map → ShopWidget-DXVNRrFY.mjs.map} +1 -1
  50. package/dist/{SubscriptionsScreen-DWB0E5Hu.cjs → SubscriptionsScreen-D04ldHXL.cjs} +3 -3
  51. package/dist/{SubscriptionsScreen-DWB0E5Hu.cjs.map → SubscriptionsScreen-D04ldHXL.cjs.map} +1 -1
  52. package/dist/{SubscriptionsScreen-Dv7PjXwb.mjs → SubscriptionsScreen-D0pn4_JR.mjs} +3 -3
  53. package/dist/{SubscriptionsScreen-Dv7PjXwb.mjs.map → SubscriptionsScreen-D0pn4_JR.mjs.map} +1 -1
  54. package/dist/{SubscriptionsScreen-COBaRHDG.cjs → SubscriptionsScreen-J4R7V7na.cjs} +8 -8
  55. package/dist/{SubscriptionsScreen-G64lYG5v.mjs → SubscriptionsScreen-qD8Mnh2A.mjs} +9 -9
  56. package/dist/{build-widget-registry-7slNHtKU.cjs → build-widget-registry-DMiPjMfG.cjs} +31 -7
  57. package/dist/build-widget-registry-DMiPjMfG.cjs.map +1 -0
  58. package/dist/{build-widget-registry-BZjLacQ8.mjs → build-widget-registry-Dveiky2q.mjs} +28 -10
  59. package/dist/build-widget-registry-Dveiky2q.mjs.map +1 -0
  60. package/dist/index.cjs +260 -26
  61. package/dist/index.cjs.map +1 -1
  62. package/dist/index.d.cts.map +1 -1
  63. package/dist/index.d.mts.map +1 -1
  64. package/dist/index.mjs +260 -26
  65. package/dist/index.mjs.map +1 -1
  66. package/dist/{use-portal-card-add-3ds-flow-tvSUKAvl.mjs → use-portal-card-add-3ds-flow--PAh5qUt.mjs} +2 -2
  67. package/dist/{use-portal-card-add-3ds-flow-tvSUKAvl.mjs.map → use-portal-card-add-3ds-flow--PAh5qUt.mjs.map} +1 -1
  68. package/dist/{use-portal-card-add-3ds-flow-C_Frj80d.cjs → use-portal-card-add-3ds-flow-DzFfku22.cjs} +2 -2
  69. package/dist/{use-portal-card-add-3ds-flow-C_Frj80d.cjs.map → use-portal-card-add-3ds-flow-DzFfku22.cjs.map} +1 -1
  70. package/dist/utils/build-remote-widget-plugin-manifests.cjs +5 -5
  71. package/dist/utils/build-remote-widget-plugin-manifests.mjs +6 -6
  72. package/package.json +9 -9
  73. package/dist/ScreenRenderer-BQz-1cCn.mjs.map +0 -1
  74. package/dist/ScreenRenderer-EGk5-w0h.cjs.map +0 -1
  75. package/dist/build-widget-registry-7slNHtKU.cjs.map +0 -1
  76. package/dist/build-widget-registry-BZjLacQ8.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ShopScreen-C3vwiETG.cjs","names":["defaultRenderImage","useShopTranslation","getProductImageUrl","isVideoUrl","formatPortalPrice","determineProductPrice","formatPortalPriceRange","getVideoThumbnailUrl","CirclePlay","Badge","Card","defaultRenderImage","useShopTranslation","isVideoUrl","ChevronLeft","ChevronRight","Dialog","DialogContent","DialogTitle","DialogDescription","DialogClose","X","Button","useShopTranslation","formatSavings","RadioGroup","RadioGroupItem","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","useShopTranslation","cn","RadioGroupItem","Button","useShopTranslation","isIncludedGroup","isSingleSelectGroup","sortItems","cn","Check","Badge","CircleCheck","TriangleAlert","RadioGroup","useShopTranslation","sortGroups","resolveExclusiveSets","cn","useShopTranslation","useBundleSelector","computeBundleTotals","isIncludedGroup","selectedCountForGroup","formatPortalPrice","Button","ShoppingCart","isVariantSubscribable","Skeleton","useShopTranslation","usePortalProductCatalog","SearchSort","DropdownMenu","DropdownMenuTrigger","Button","ArrowUpDown","DropdownMenuContent","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuRadioGroup","DropdownMenuRadioItem","usePortalProductDetail","usePortalProducts","formatPrice","isStaticBundle","formatPriceRange","bundleSubscriptionPrice","Badge","ExternalLink","ShoppingCart","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","React","useShopTranslation","Button","ShoppingCart","useFluidContext","useShopTranslation","useStore","useAccount","usePortalTenantClient","useAppNavigation","ScreenHeaderBreadcrumbs","Breadcrumb","BreadcrumbList","BreadcrumbItem","BreadcrumbPage","ScreenHeaderActions","useNavigationParent","usePortalProductDetail","BreadcrumbLink","BreadcrumbSeparator"],"sources":["../../../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-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","../../../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","../src/screens/ShopScreen.tsx"],"sourcesContent":["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 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 renderImage = defaultRenderImage,\n}: {\n product: ProductCardProduct;\n countryIso?: string;\n companyLogoUrl?: string | null;\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 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 {!isPortal && countryIso && (\n <div className=\"text-muted-foreground mt-1 text-xs\">\n CV{\" \"}\n {getVariantCountryValue(\n getSelectedVariant(product as LegacyProduct),\n countryIso,\n \"cv\",\n ) || \"-\"}{\" \"}\n | QV{\" \"}\n {getVariantCountryValue(\n getSelectedVariant(product as LegacyProduct),\n countryIso,\n \"qv\",\n ) || \"-\"}\n </div>\n )}\n\n {isPortal && product.wholesale_price != 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 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 {...(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 { ReactNode } from \"react\";\nimport { 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\";\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 (display only in this ticket). */\n forceSubscription: boolean;\n onToggle: () => void;\n onQuantityChange: (quantity: number) => void;\n renderImage?: RenderBundleImage;\n}\n\nexport function BundleGroupItem({\n item,\n control,\n selected,\n quantity,\n canAddMore,\n forceSubscription,\n onToggle,\n onQuantityChange,\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 variant (e.g. \"Small\") and show the product name beneath it;\n // when there's no 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 = variantTitle ?? label;\n const secondaryLabel = variantTitle ? label : null;\n // Accessible name leads with the variant then the product, 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 <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 {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 disabled={unavailable}\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 disabled={unavailable}\n onClick={() =>\n quantity <= 1 ? onToggle() : 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 unavailable ||\n !canAddMore ||\n (item.max_quantity != null && 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={unavailable || !canAddMore}\n onClick={onToggle}\n >\n {t(\"bundle_add\")}\n </Button>\n ))}\n </div>\n </RowTag>\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 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 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\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 <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={forceGroup || item.force_subscription === true}\n onToggle={() => onToggle(item.variant_id)}\n onQuantityChange={(quantity) =>\n onQuantityChange(item.variant_id, quantity)\n }\n {...(renderImage !== undefined && { renderImage })}\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 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 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 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\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 {...(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 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 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 );\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 })),\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 {(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 {...(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 DropdownMenu,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { ArrowUpDown, ExternalLink, ShoppingCart } from \"lucide-react\";\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 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 renderImage,\n onSelectProduct,\n cartButton,\n}: {\n companyLogoUrl?: string | null;\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\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 <div className=\"mx-auto px-2 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 <h1 className=\"text-foreground text-3xl leading-tight font-semibold tracking-tight\">\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 />\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"border-foreground/10 shadow-input size-9 shrink-0\"\n >\n <ArrowUpDown className=\"size-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-60\">\n <DropdownMenuLabel>{t(\"sort_by\")}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuRadioGroup\n value={catalog.currentSort}\n onValueChange={catalog.setCurrentSort}\n >\n {sortOptions.map((opt) => (\n <DropdownMenuRadioItem key={opt.id} value={opt.id}>\n {opt.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\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 {...(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 productId,\n renderImage,\n onSelectProduct,\n accountForPortalHeader,\n}: {\n companyLogoUrl?: string | null;\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 const displayCv = isBundle ? product.cv : selectedVariant?.cv;\n const displayQv = isBundle ? product.qv : selectedVariant?.qv;\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 {...(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 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 {product.wholesale_price != null && !hasDynamicConfig && (\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 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 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 {...(renderImage !== undefined && { renderImage })}\n onSelectProduct={handleSelectProduct}\n cartButton={cartButton}\n />\n );\n}\n","import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n /**\n * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with\n * before any cart operation. Renders as `data-fluid-country` on the SDK\n * script tag; the SDK reads it at init time and uses it for cart creation\n * paths so they don't fall back to locale-derived country. Requires\n * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country\n * parsing) on the CDN.\n */\n country?: string;\n /**\n * Explicit cookie domain for the FairShare SDK, rendered as\n * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes\n * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain\n * instead of inferring an apex/brand domain from the hostname.\n *\n * This is required in the portal's hub/tenant model: the SDK's hostname\n * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the\n * shared `.portal.fluid.app` level, which leaks attribution across tenants.\n * Pass the current tenant host so cookies stay isolated per company.\n * Storefronts load the SDK directly (not via this component) and keep the\n * default brand-wide behavior. Requires a web-widgets bundle that parses\n * `data-fluid-cookie-domain`.\n */\n cookieDomain?: string;\n /**\n * Fair Share attribution GUID for the logged-in member, rendered as\n * `data-share-guid` on the SDK script tag. This is the FairShare SDK's\n * highest-priority attribution override: when present the SDK attributes\n * the member's in-portal shopping to this share GUID. A portal belongs to a\n * company (not a rep), so pass the current member's company-scoped share\n * GUID (`account.share_guid`) — reps only; omit for customers so they are\n * not self-attributed.\n */\n shareGuid?: string;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n country,\n cookieDomain,\n shareGuid,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n const countryRef = useRef(country);\n countryRef.current = country;\n const cookieDomainRef = useRef(cookieDomain);\n cookieDomainRef.current = cookieDomain;\n const shareGuidRef = useRef(shareGuid);\n shareGuidRef.current = shareGuid;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n if (countryRef.current) {\n script.dataset.fluidCountry = countryRef.current;\n }\n if (cookieDomainRef.current) {\n script.dataset.fluidCookieDomain = cookieDomainRef.current;\n }\n if (shareGuidRef.current) {\n script.dataset.shareGuid = shareGuidRef.current;\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n // `authJwt`, `country`, `cookieDomain`, and `shareGuid` are intentionally\n // omitted from deps: the SDK reads data attributes only once when this\n // module script first executes.\n // Re-inserting the same module URL after those props change does not\n // re-run FairShare initialization, so treat them as inject-time values.\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\n\ninterface CartButtonProps {\n onCheckout?: (checkoutUrl: string) => void;\n}\n\nconst MAX_SDK_POLL_ATTEMPTS = 50; // 5 seconds at 100ms intervals\n\nexport function CartButton({ onCheckout }: CartButtonProps): React.ReactNode {\n const { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\n const navigateToCheckout = useCallback(() => {\n if (!window.FluidCommerceSDK) {\n console.error(\"FluidCommerceSDK not available\");\n return;\n }\n\n try {\n const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();\n if (!checkoutUrl) {\n console.error(\"No checkout URL available\");\n return;\n }\n onCheckout?.(checkoutUrl);\n } catch (error) {\n console.error(\"Error getting checkout URL:\", error);\n }\n }, [onCheckout]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let attempts = 0;\n let cancelled = false;\n\n const setupSDK = () => {\n if (cancelled) return;\n if (window.FluidCommerceSDK) {\n if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\n } else if (attempts < MAX_SDK_POLL_ATTEMPTS) {\n attempts++;\n timeoutId = setTimeout(setupSDK, 100);\n }\n };\n\n setupSDK();\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [navigateToCheckout, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface ShopContainerProps {\n children: React.ReactNode;\n className?: string;\n cartScript?: React.ReactNode;\n cartWidget?: React.ReactNode;\n}\n\nexport default function ShopContainer({\n children,\n className = \"\",\n cartScript,\n cartWidget,\n}: ShopContainerProps): React.ReactNode {\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(\n null,\n );\n\n useEffect(() => {\n const currentContainer = containerRef.current;\n if (!currentContainer) return;\n\n const reactContentWrapper = document.createElement(\"div\");\n reactContentWrapper.id = \"react-content-wrapper\";\n reactContentWrapper.style.cssText = `\n position: relative;\n `;\n reactContentWrapper.className = \"min-h-full\";\n\n currentContainer.appendChild(reactContentWrapper);\n\n setPortalContainer(reactContentWrapper);\n\n return () => {\n if (currentContainer && reactContentWrapper) {\n try {\n currentContainer.removeChild(reactContentWrapper);\n } catch (e) {\n console.warn(\"Failed to cleanup isolated container:\", e);\n }\n }\n setPortalContainer(null);\n };\n }, []);\n\n return (\n <>\n <div\n ref={containerRef}\n className={`isolated-shop-wrapper ${className} h-full`}\n >\n {portalContainer &&\n createPortal(\n <>\n {cartScript}\n {cartWidget}\n {children}\n </>,\n portalContainer,\n )}\n </div>\n </>\n );\n}\n","import { type ComponentProps, useCallback } from \"react\";\nimport ShopApp from \"@fluid-app/shop-ui/components/shop-app\";\nimport { portalTenant } from \"@fluid-app/portal-tenant-api-client\";\nimport {\n CartButton,\n CartScript,\n CartWidget,\n ShopContainer,\n} 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 {\n ScreenHeaderBreadcrumbs,\n ScreenHeaderActions,\n} 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 { useFluidContext } from \"../providers/FluidProvider\";\nimport { useAppNavigation } from \"../shell/AppNavigationContext\";\nimport { useNavigationParent } from \"../shell/use-navigation-parent\";\nimport { useStore } from \"../hooks/use-store\";\nimport { useAccount } from \"../hooks/use-account\";\nimport { usePortalTenantClient } from \"../providers/PortalTenantClientProvider\";\nimport { usePortalProductDetail } from \"@fluid-app/products-core\";\n\n/**\n * Extract the cart token (e.g. \"ct_abc123\") from a checkout URL such as\n * https://checkout.fluid.app/checkouts/ct_abc123?fluid_shop=oliabo — the URL\n * the FairShare SDK returns from getCheckoutUrl(). Returns null when the URL\n * has no recognizable token.\n */\nexport function parseCartToken(checkoutUrl: string): string | null {\n try {\n // Resolve against the current location so relative checkout paths\n // (e.g. \"/checkouts/ct_abc123\") parse too — new URL() alone rejects\n // non-absolute URLs, which would silently skip the claim.\n const base =\n typeof window === \"undefined\" ? undefined : window.location.href;\n const { pathname } = new URL(checkoutUrl, base);\n return /\\/checkouts\\/([^/?#]+)/.exec(pathname)?.[1] ?? null;\n } catch {\n return null;\n }\n}\n\n// Claiming the cart must never block checkout indefinitely. If the claim\n// hangs (flaky network, no client-side fetch timeout), give up after this\n// bound and proceed to checkout as if the claim failed — best-effort.\nconst CART_CLAIM_TIMEOUT_MS = 3000;\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 { config } = useFluidContext();\n const { t } = useShopTranslation();\n const { data: store } = useStore();\n const { data: account, isPending: accountPending } = useAccount();\n const client = usePortalTenantClient();\n const { currentSlug, navigate } = useAppNavigation();\n\n // Claim the FairShare-created (unclaimed) cart for the logged-in member —\n // rep OR customer — then hand off to the checkout_url the claim returns.\n //\n // CartClaimer attaches the member's customer identity for every member (and\n // buyer_rep only for rep-eligible ones, which drives wholesale pricing), and\n // the response's checkout_url carries that member's handoff JWT. Navigating\n // there makes checkout adopt the current member instead of falling back to a\n // stale per-browser cookie left by a previously logged-in account — the\n // cross-account leak in CURRENT-2180. Claiming only for reps (the old\n // behavior) left customers on the token-less SDK URL and exposed to the leak.\n //\n // Best-effort: a failed or slow claim must never block checkout, so fall back\n // to the token-less SDK URL.\n const handleCheckout = useCallback(\n async (checkoutUrl: string) => {\n const token = parseCartToken(checkoutUrl);\n let destination = checkoutUrl;\n\n if (token) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n const claimed = await Promise.race([\n portalTenant.carts_claim(client, token),\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(\"cart claim timed out\")),\n CART_CLAIM_TIMEOUT_MS,\n );\n }),\n ]);\n destination = claimed?.checkout_url ?? checkoutUrl;\n } catch (error) {\n console.error(\"Failed to claim cart:\", error);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n }\n\n window.location.assign(destination);\n },\n [client],\n );\n const countryCode = config.countryIso ?? \"US\";\n const subdomain = store?.subdomain;\n\n // Parse product ID from slug: \"shop/{productId}\"\n const parts = currentSlug.split(\"/\");\n const productId = parts[1] ?? null;\n\n // Tenant host (e.g. tacobell.portal.fluid.app), SSR-safe. Used to scope\n // FairShare cookies to this tenant so attribution stays isolated per company.\n const tenantHost =\n typeof window !== \"undefined\" ? window.location.hostname : undefined;\n\n // TODO(portal-theme): Cart widget theming requires AppShell to expose\n // resolvedTheme via context. Currently it's local state in AppShell.\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 <ScreenHeaderActions>\n <div className=\"flex items-center gap-4\">\n <CartButton onCheckout={handleCheckout} />\n </div>\n </ScreenHeaderActions>\n <div {...divProps} className={divProps.className ?? \"\"}>\n <ShopContainer\n cartScript={\n // Mount CartScript ONLY when both:\n // (a) subdomain is known (otherwise data-fluid-shop is empty), and\n // (b) the customer's account has resolved (so FluidConfigHydrator\n // has already merged account.market_country_iso (or\n // default_country_iso when market is null) into\n // config.countryIso, and countryCode is the real country\n // rather than the \"US\" fallback).\n // ES modules are cached by URL, so the SDK script only executes\n // once per page. Mounting before account resolves would seed\n // fs_country with \"US\" and a later re-render with the real\n // country would NOT re-execute the module — fluid-fairshare#443's\n // data-fluid-country seeding only works at first script init.\n subdomain && !accountPending ? (\n <CartScript\n subdomain={subdomain}\n bffMode\n scriptSrc={\n import.meta.env.DEV\n ? String(\n import.meta.env.VITE_FAIRSHARE_SDK_URL ||\n \"http://localhost:4444/index.js\",\n )\n : undefined\n }\n apiBaseUrl={\n import.meta.env.DEV\n ? String(\n import.meta.env.VITE_FAIRSHARE_API_BASE_URL ||\n \"http://api.fluid.localhost:3000\",\n )\n : undefined\n }\n debug={import.meta.env.DEV}\n country={countryCode}\n // Scope FairShare cookies to this tenant host (e.g.\n // tacobell.portal.fluid.app). Without this the SDK infers the\n // shared `.portal.fluid.app` domain and attribution leaks\n // across companies in the hub/tenant model.\n cookieDomain={tenantHost}\n // Attribute this rep's in-portal shopping to their Fair Share\n // GUID (the SDK's #1 attribution override). Null for customers\n // (`account.share_guid` is null), so they aren't self-attributed.\n shareGuid={account?.share_guid ?? undefined}\n />\n ) : null\n }\n cartWidget={<CartWidget />}\n >\n {productId && <ProductBreadcrumb productId={productId} />}\n <ShopApp\n companyLogoUrl={store?.logo_url ?? undefined}\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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"}
1
+ {"version":3,"file":"ShopScreen-DS5-ICA-.cjs","names":["defaultRenderImage","useShopTranslation","getProductImageUrl","isVideoUrl","formatPortalPrice","determineProductPrice","formatPortalPriceRange","getVideoThumbnailUrl","CirclePlay","Badge","Card","defaultRenderImage","useShopTranslation","isVideoUrl","ChevronLeft","ChevronRight","Dialog","DialogContent","DialogTitle","DialogDescription","DialogClose","X","Button","useShopTranslation","formatSavings","RadioGroup","RadioGroupItem","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","useShopTranslation","cn","RadioGroupItem","Button","useShopTranslation","isIncludedGroup","isSingleSelectGroup","sortItems","cn","Check","Badge","CircleCheck","TriangleAlert","RadioGroup","useShopTranslation","sortGroups","resolveExclusiveSets","cn","useShopTranslation","useBundleSelector","computeBundleTotals","isIncludedGroup","selectedCountForGroup","formatPortalPrice","Button","ShoppingCart","isVariantSubscribable","Skeleton","useShopTranslation","usePortalProductCatalog","SearchSort","DropdownMenu","DropdownMenuTrigger","Button","ArrowUpDown","DropdownMenuContent","DropdownMenuLabel","DropdownMenuSeparator","DropdownMenuRadioGroup","DropdownMenuRadioItem","usePortalProductDetail","usePortalProducts","formatPrice","isStaticBundle","formatPriceRange","bundleSubscriptionPrice","Badge","ExternalLink","ShoppingCart","Select","SelectTrigger","SelectValue","SelectContent","SelectItem","React","useShopTranslation","Button","ShoppingCart","useFluidContext","useShopTranslation","useStore","useAccount","usePortalTenantClient","useAppNavigation","ScreenHeaderBreadcrumbs","Breadcrumb","BreadcrumbList","BreadcrumbItem","BreadcrumbPage","ScreenHeaderActions","useNavigationParent","usePortalProductDetail","BreadcrumbLink","BreadcrumbSeparator"],"sources":["../../../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-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","../../../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","../src/screens/ShopScreen.tsx"],"sourcesContent":["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 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 renderImage = defaultRenderImage,\n}: {\n product: ProductCardProduct;\n countryIso?: string;\n companyLogoUrl?: string | null;\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 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 {!isPortal && countryIso && (\n <div className=\"text-muted-foreground mt-1 text-xs\">\n CV{\" \"}\n {getVariantCountryValue(\n getSelectedVariant(product as LegacyProduct),\n countryIso,\n \"cv\",\n ) || \"-\"}{\" \"}\n | QV{\" \"}\n {getVariantCountryValue(\n getSelectedVariant(product as LegacyProduct),\n countryIso,\n \"qv\",\n ) || \"-\"}\n </div>\n )}\n\n {isPortal && product.wholesale_price != 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 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 {...(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 { ReactNode } from \"react\";\nimport { 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\";\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 (display only in this ticket). */\n forceSubscription: boolean;\n onToggle: () => void;\n onQuantityChange: (quantity: number) => void;\n renderImage?: RenderBundleImage;\n}\n\nexport function BundleGroupItem({\n item,\n control,\n selected,\n quantity,\n canAddMore,\n forceSubscription,\n onToggle,\n onQuantityChange,\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 variant (e.g. \"Small\") and show the product name beneath it;\n // when there's no 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 = variantTitle ?? label;\n const secondaryLabel = variantTitle ? label : null;\n // Accessible name leads with the variant then the product, 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 <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 {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 disabled={unavailable}\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 disabled={unavailable}\n onClick={() =>\n quantity <= 1 ? onToggle() : 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 unavailable ||\n !canAddMore ||\n (item.max_quantity != null && 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={unavailable || !canAddMore}\n onClick={onToggle}\n >\n {t(\"bundle_add\")}\n </Button>\n ))}\n </div>\n </RowTag>\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 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 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\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 <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={forceGroup || item.force_subscription === true}\n onToggle={() => onToggle(item.variant_id)}\n onQuantityChange={(quantity) =>\n onQuantityChange(item.variant_id, quantity)\n }\n {...(renderImage !== undefined && { renderImage })}\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 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 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 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\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 {...(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 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 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 );\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 })),\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 {(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 {...(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 DropdownMenu,\n DropdownMenuContent,\n DropdownMenuLabel,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { ArrowUpDown, ExternalLink, ShoppingCart } from \"lucide-react\";\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 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 renderImage,\n onSelectProduct,\n cartButton,\n}: {\n companyLogoUrl?: string | null;\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\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 <div className=\"mx-auto px-2 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 <h1 className=\"text-foreground text-3xl leading-tight font-semibold tracking-tight\">\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 />\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"border-foreground/10 shadow-input size-9 shrink-0\"\n >\n <ArrowUpDown className=\"size-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-60\">\n <DropdownMenuLabel>{t(\"sort_by\")}</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuRadioGroup\n value={catalog.currentSort}\n onValueChange={catalog.setCurrentSort}\n >\n {sortOptions.map((opt) => (\n <DropdownMenuRadioItem key={opt.id} value={opt.id}>\n {opt.label}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\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 {...(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 productId,\n renderImage,\n onSelectProduct,\n accountForPortalHeader,\n}: {\n companyLogoUrl?: string | null;\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 const displayCv = isBundle ? product.cv : selectedVariant?.cv;\n const displayQv = isBundle ? product.qv : selectedVariant?.qv;\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 {...(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 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 {product.wholesale_price != null && !hasDynamicConfig && (\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 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 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 {...(renderImage !== undefined && { renderImage })}\n onSelectProduct={handleSelectProduct}\n cartButton={cartButton}\n />\n );\n}\n","import { useEffect, useRef } from \"react\";\n\ninterface CartScriptProps {\n subdomain: string;\n authJwt?: string;\n /** Enable BFF mode — uses portal session cookies instead of JWT for cart auth. */\n bffMode?: boolean;\n /** Override the SDK script URL (e.g. \"http://localhost:4444/index.js\" for local dev). */\n scriptSrc?: string;\n /** Override the API base URL the SDK uses (e.g. \"http://localhost:3000\" for local dev). */\n apiBaseUrl?: string;\n /** Enable SDK debug logging. */\n debug?: boolean;\n /**\n * Initial ISO 3166-1 alpha-2 country code to seed the FairShare SDK with\n * before any cart operation. Renders as `data-fluid-country` on the SDK\n * script tag; the SDK reads it at init time and uses it for cart creation\n * paths so they don't fall back to locale-derived country. Requires\n * fluid-fairshare PR #443 (web-widgets bundle with data-fluid-country\n * parsing) on the CDN.\n */\n country?: string;\n /**\n * Explicit cookie domain for the FairShare SDK, rendered as\n * `data-fluid-cookie-domain` on the SDK script tag. When set, the SDK scopes\n * its cookies (session, affiliate/attribution, locale, etc.) to exactly this domain\n * instead of inferring an apex/brand domain from the hostname.\n *\n * This is required in the portal's hub/tenant model: the SDK's hostname\n * heuristic resolves a tenant host like `tacobell.portal.fluid.app` to the\n * shared `.portal.fluid.app` level, which leaks attribution across tenants.\n * Pass the current tenant host so cookies stay isolated per company.\n * Storefronts load the SDK directly (not via this component) and keep the\n * default brand-wide behavior. Requires a web-widgets bundle that parses\n * `data-fluid-cookie-domain`.\n */\n cookieDomain?: string;\n /**\n * Fair Share attribution GUID for the logged-in member, rendered as\n * `data-share-guid` on the SDK script tag. This is the FairShare SDK's\n * highest-priority attribution override: when present the SDK attributes\n * the member's in-portal shopping to this share GUID. A portal belongs to a\n * company (not a rep), so pass the current member's company-scoped share\n * GUID (`account.share_guid`) — reps only; omit for customers so they are\n * not self-attributed.\n */\n shareGuid?: string;\n}\n\nconst SCRIPT_ID = \"fluid-cdn-script\";\nconst LEAD_CAPTURE_ID = \"fluid-lead-capture-suppress\";\nconst DEFAULT_SCRIPT_SRC =\n \"https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js\";\n\nexport default function CartScript({\n subdomain,\n authJwt,\n bffMode,\n scriptSrc,\n apiBaseUrl,\n debug,\n country,\n cookieDomain,\n shareGuid,\n}: CartScriptProps): React.ReactNode {\n // Use a ref so the script is injected once with the initial values.\n // ES modules are cached by URL — re-inserting the same script won't\n // re-execute it, so changing props after the first load has no effect.\n const authJwtRef = useRef(authJwt);\n authJwtRef.current = authJwt;\n const countryRef = useRef(country);\n countryRef.current = country;\n const cookieDomainRef = useRef(cookieDomain);\n cookieDomainRef.current = cookieDomain;\n const shareGuidRef = useRef(shareGuid);\n shareGuidRef.current = shareGuid;\n\n useEffect(() => {\n if (!subdomain) return;\n\n // Don't add a duplicate script\n if (document.getElementById(SCRIPT_ID)) return;\n\n const script = document.createElement(\"script\");\n script.id = SCRIPT_ID;\n script.src = scriptSrc ?? DEFAULT_SCRIPT_SRC;\n script.type = \"module\";\n script.crossOrigin = \"anonymous\";\n script.dataset.fluidShop = subdomain;\n if (bffMode) {\n script.dataset.bffMode = \"true\";\n } else if (authJwtRef.current) {\n script.dataset.authJwt = authJwtRef.current;\n }\n if (apiBaseUrl) {\n script.dataset.fluidApiBaseUrl = apiBaseUrl;\n }\n if (debug) {\n script.dataset.debug = \"true\";\n }\n if (countryRef.current) {\n script.dataset.fluidCountry = countryRef.current;\n }\n if (cookieDomainRef.current) {\n script.dataset.fluidCookieDomain = cookieDomainRef.current;\n }\n if (shareGuidRef.current) {\n script.dataset.shareGuid = shareGuidRef.current;\n }\n document.head.appendChild(script);\n\n // Suppress the SDK's auto-injected lead capture widget.\n // The SDK skips injection when it finds an existing element with hide-widget.\n const leadCapture = document.createElement(\"fluid-lead-capture-widget\");\n leadCapture.id = LEAD_CAPTURE_ID;\n leadCapture.setAttribute(\"hide-widget\", \"true\");\n document.body.appendChild(leadCapture);\n\n return () => {\n const existing = document.getElementById(SCRIPT_ID);\n if (existing) existing.remove();\n const existingLeadCapture = document.getElementById(LEAD_CAPTURE_ID);\n if (existingLeadCapture) existingLeadCapture.remove();\n };\n // `authJwt`, `country`, `cookieDomain`, and `shareGuid` are intentionally\n // omitted from deps: the SDK reads data attributes only once when this\n // module script first executes.\n // Re-inserting the same module URL after those props change does not\n // re-run FairShare initialization, so treat them as inject-time values.\n }, [subdomain, bffMode, scriptSrc, apiBaseUrl, debug]);\n\n return null;\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface CartWidgetProps {\n theme?: Record<string, string>;\n}\n\nexport default function CartWidget({\n theme,\n}: CartWidgetProps): React.ReactNode {\n const [mounted, setMounted] = useState(false);\n const widgetRef = useRef<HTMLElement | null>(null);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n useEffect(() => {\n if (!mounted) return;\n const el = widgetRef.current;\n if (!el) return;\n if (theme) {\n el.setAttribute(\"theme\", JSON.stringify(theme));\n } else {\n el.removeAttribute(\"theme\");\n }\n }, [theme, mounted]);\n\n const widget = React.createElement(\"fluid-cart-widget\", {\n ref: (el: HTMLElement | null) => {\n widgetRef.current = el;\n },\n \"data-fluid-widget\": \"true\",\n \"hide-widget\": \"true\",\n \"is-primary\": \"true\",\n });\n\n // Portal to document.body so the cart drawer escapes any\n // overflow-hidden / isolation stacking contexts in the layout.\n if (mounted) {\n return createPortal(widget, document.body);\n }\n\n return null;\n}\n","\"use client\";\n\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport { ShoppingCart } from \"lucide-react\";\nimport React, { useEffect, useCallback, useState } from \"react\";\nimport { useShopTranslation } from \"@fluid-app/shop-core/translation-api-context\";\n\ndeclare global {\n interface Window {\n FluidCommerceSDK?: {\n getCheckoutUrl: () => string;\n setOnCheckout: (callback: () => void) => void;\n };\n FairShareSDK?: {\n getCartItemCount: () => number;\n isBffMode: () => boolean;\n updateLocaleSettings: (options: {\n language?: string;\n country?: string;\n }) => Promise<void>;\n };\n fluidCart?: {\n open: () => void;\n };\n }\n}\n\ninterface CartButtonProps {\n onCheckout?: (checkoutUrl: string) => void;\n}\n\nconst MAX_SDK_POLL_ATTEMPTS = 50; // 5 seconds at 100ms intervals\n\nexport function CartButton({ onCheckout }: CartButtonProps): React.ReactNode {\n const { t } = useShopTranslation();\n const [initialCount, setInitialCount] = useState(0);\n\n const navigateToCheckout = useCallback(() => {\n if (!window.FluidCommerceSDK) {\n console.error(\"FluidCommerceSDK not available\");\n return;\n }\n\n try {\n const checkoutUrl = window.FluidCommerceSDK.getCheckoutUrl();\n if (!checkoutUrl) {\n console.error(\"No checkout URL available\");\n return;\n }\n onCheckout?.(checkoutUrl);\n } catch (error) {\n console.error(\"Error getting checkout URL:\", error);\n }\n }, [onCheckout]);\n\n useEffect(() => {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let attempts = 0;\n let cancelled = false;\n\n const setupSDK = () => {\n if (cancelled) return;\n if (window.FluidCommerceSDK) {\n if (onCheckout) {\n window.FluidCommerceSDK.setOnCheckout(navigateToCheckout);\n }\n const count = window.FairShareSDK?.getCartItemCount?.();\n if (count != null) {\n setInitialCount(count);\n }\n } else if (attempts < MAX_SDK_POLL_ATTEMPTS) {\n attempts++;\n timeoutId = setTimeout(setupSDK, 100);\n }\n };\n\n setupSDK();\n return () => {\n cancelled = true;\n if (timeoutId) clearTimeout(timeoutId);\n };\n }, [navigateToCheckout, onCheckout]);\n\n return (\n <Button\n className=\"bg-primary text-primary-foreground hover:bg-primary/70 relative flex h-8 items-center gap-2 rounded-sm px-3 py-1 text-xs\"\n onClick={() => {\n window.fluidCart?.open();\n }}\n >\n <div className=\"relative\">\n <ShoppingCart className=\"size-4\" />\n <span\n id=\"fluid-cart-count\"\n className=\"bg-primary-foreground text-primary absolute -top-1 -right-2 flex size-3.5 items-center justify-center rounded-full text-[8px] font-bold\"\n >\n {initialCount}\n </span>\n </div>\n <span>{t(\"cart\")}</span>\n </Button>\n );\n}\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\ninterface ShopContainerProps {\n children: React.ReactNode;\n className?: string;\n cartScript?: React.ReactNode;\n cartWidget?: React.ReactNode;\n}\n\nexport default function ShopContainer({\n children,\n className = \"\",\n cartScript,\n cartWidget,\n}: ShopContainerProps): React.ReactNode {\n const containerRef = useRef<HTMLDivElement>(null);\n const [portalContainer, setPortalContainer] = useState<HTMLDivElement | null>(\n null,\n );\n\n useEffect(() => {\n const currentContainer = containerRef.current;\n if (!currentContainer) return;\n\n const reactContentWrapper = document.createElement(\"div\");\n reactContentWrapper.id = \"react-content-wrapper\";\n reactContentWrapper.style.cssText = `\n position: relative;\n `;\n reactContentWrapper.className = \"min-h-full\";\n\n currentContainer.appendChild(reactContentWrapper);\n\n setPortalContainer(reactContentWrapper);\n\n return () => {\n if (currentContainer && reactContentWrapper) {\n try {\n currentContainer.removeChild(reactContentWrapper);\n } catch (e) {\n console.warn(\"Failed to cleanup isolated container:\", e);\n }\n }\n setPortalContainer(null);\n };\n }, []);\n\n return (\n <>\n <div\n ref={containerRef}\n className={`isolated-shop-wrapper ${className} h-full`}\n >\n {portalContainer &&\n createPortal(\n <>\n {cartScript}\n {cartWidget}\n {children}\n </>,\n portalContainer,\n )}\n </div>\n </>\n );\n}\n","import { type ComponentProps, useCallback } from \"react\";\nimport ShopApp from \"@fluid-app/shop-ui/components/shop-app\";\nimport { portalTenant } from \"@fluid-app/portal-tenant-api-client\";\nimport {\n CartButton,\n CartScript,\n CartWidget,\n ShopContainer,\n} 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 {\n ScreenHeaderBreadcrumbs,\n ScreenHeaderActions,\n} 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 { useFluidContext } from \"../providers/FluidProvider\";\nimport { useAppNavigation } from \"../shell/AppNavigationContext\";\nimport { useNavigationParent } from \"../shell/use-navigation-parent\";\nimport { useStore } from \"../hooks/use-store\";\nimport { useAccount } from \"../hooks/use-account\";\nimport { usePortalTenantClient } from \"../providers/PortalTenantClientProvider\";\nimport { usePortalProductDetail } from \"@fluid-app/products-core\";\n\n/**\n * Extract the cart token (e.g. \"ct_abc123\") from a checkout URL such as\n * https://checkout.fluid.app/checkouts/ct_abc123?fluid_shop=oliabo — the URL\n * the FairShare SDK returns from getCheckoutUrl(). Returns null when the URL\n * has no recognizable token.\n */\nexport function parseCartToken(checkoutUrl: string): string | null {\n try {\n // Resolve against the current location so relative checkout paths\n // (e.g. \"/checkouts/ct_abc123\") parse too — new URL() alone rejects\n // non-absolute URLs, which would silently skip the claim.\n const base =\n typeof window === \"undefined\" ? undefined : window.location.href;\n const { pathname } = new URL(checkoutUrl, base);\n return /\\/checkouts\\/([^/?#]+)/.exec(pathname)?.[1] ?? null;\n } catch {\n return null;\n }\n}\n\n// Claiming the cart must never block checkout indefinitely. If the claim\n// hangs (flaky network, no client-side fetch timeout), give up after this\n// bound and proceed to checkout as if the claim failed — best-effort.\nconst CART_CLAIM_TIMEOUT_MS = 3000;\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 { config } = useFluidContext();\n const { t } = useShopTranslation();\n const { data: store } = useStore();\n const { data: account, isPending: accountPending } = useAccount();\n const client = usePortalTenantClient();\n const { currentSlug, navigate } = useAppNavigation();\n\n // Claim the FairShare-created (unclaimed) cart for the logged-in member —\n // rep OR customer — then hand off to the checkout_url the claim returns.\n //\n // CartClaimer attaches the member's customer identity for every member (and\n // buyer_rep only for rep-eligible ones, which drives wholesale pricing), and\n // the response's checkout_url carries that member's handoff JWT. Navigating\n // there makes checkout adopt the current member instead of falling back to a\n // stale per-browser cookie left by a previously logged-in account — the\n // cross-account leak in CURRENT-2180. Claiming only for reps (the old\n // behavior) left customers on the token-less SDK URL and exposed to the leak.\n //\n // Best-effort: a failed or slow claim must never block checkout, so fall back\n // to the token-less SDK URL.\n const handleCheckout = useCallback(\n async (checkoutUrl: string) => {\n const token = parseCartToken(checkoutUrl);\n let destination = checkoutUrl;\n\n if (token) {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n const claimed = await Promise.race([\n portalTenant.carts_claim(client, token),\n new Promise<never>((_, reject) => {\n timeoutId = setTimeout(\n () => reject(new Error(\"cart claim timed out\")),\n CART_CLAIM_TIMEOUT_MS,\n );\n }),\n ]);\n destination = claimed?.checkout_url ?? checkoutUrl;\n } catch (error) {\n console.error(\"Failed to claim cart:\", error);\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n }\n\n window.location.assign(destination);\n },\n [client],\n );\n const countryCode = config.countryIso ?? \"US\";\n const subdomain = store?.subdomain;\n\n // Parse product ID from slug: \"shop/{productId}\"\n const parts = currentSlug.split(\"/\");\n const productId = parts[1] ?? null;\n\n // Tenant host (e.g. tacobell.portal.fluid.app), SSR-safe. Used to scope\n // FairShare cookies to this tenant so attribution stays isolated per company.\n const tenantHost =\n typeof window !== \"undefined\" ? window.location.hostname : undefined;\n\n // TODO(portal-theme): Cart widget theming requires AppShell to expose\n // resolvedTheme via context. Currently it's local state in AppShell.\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 <ScreenHeaderActions>\n <div className=\"flex items-center gap-4\">\n <CartButton onCheckout={handleCheckout} />\n </div>\n </ScreenHeaderActions>\n <div {...divProps} className={divProps.className ?? \"\"}>\n <ShopContainer\n cartScript={\n // Mount CartScript ONLY when both:\n // (a) subdomain is known (otherwise data-fluid-shop is empty), and\n // (b) the customer's account has resolved (so FluidConfigHydrator\n // has already merged account.market_country_iso (or\n // default_country_iso when market is null) into\n // config.countryIso, and countryCode is the real country\n // rather than the \"US\" fallback).\n // ES modules are cached by URL, so the SDK script only executes\n // once per page. Mounting before account resolves would seed\n // fs_country with \"US\" and a later re-render with the real\n // country would NOT re-execute the module — fluid-fairshare#443's\n // data-fluid-country seeding only works at first script init.\n subdomain && !accountPending ? (\n <CartScript\n subdomain={subdomain}\n bffMode\n scriptSrc={\n import.meta.env.DEV\n ? String(\n import.meta.env.VITE_FAIRSHARE_SDK_URL ||\n \"http://localhost:4444/index.js\",\n )\n : undefined\n }\n apiBaseUrl={\n import.meta.env.DEV\n ? String(\n import.meta.env.VITE_FAIRSHARE_API_BASE_URL ||\n \"http://api.fluid.localhost:3000\",\n )\n : undefined\n }\n debug={import.meta.env.DEV}\n country={countryCode}\n // Scope FairShare cookies to this tenant host (e.g.\n // tacobell.portal.fluid.app). Without this the SDK infers the\n // shared `.portal.fluid.app` domain and attribution leaks\n // across companies in the hub/tenant model.\n cookieDomain={tenantHost}\n // Attribute this rep's in-portal shopping to their Fair Share\n // GUID (the SDK's #1 attribution override). Null for customers\n // (`account.share_guid` is null), so they aren't self-attributed.\n shareGuid={account?.share_guid ?? undefined}\n />\n ) : null\n }\n cartWidget={<CartWidget />}\n >\n {productId && <ProductBreadcrumb productId={productId} />}\n <ShopApp\n companyLogoUrl={store?.logo_url ?? undefined}\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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"}