{"version":3,"file":"SearchSort-BPaZxlhe.mjs","names":[],"sources":["../../../products/core/src/products-core-api-context.tsx","../../../products/core/src/portal-products-api-context.tsx","../../../products/core/src/hooks/use-debounce.ts","../../../products/core/src/utils/variant-subscribable.ts","../../../products/core/src/hooks/use-portal-products.ts","../../../products/core/src/hooks/use-portal-product-catalog.ts","../../../products/core/src/hooks/use-portal-product-detail.ts","../../../products/core/src/utils/subscription-plans.ts","../../../products/core/src/stores/use-product-store.ts","../../../products/core/src/stores/use-draft-store.ts","../../../products/core/src/utils/product-helpers.ts","../../../products/core/src/utils/product-price.ts","../../../products/core/src/utils/format-portal-price.ts","../../../products/core/src/utils/format-savings.ts","../../../products/core/src/utils/static-bundle.ts","../../../products/core/src/utils/bundle-subscription-price.ts","../../../products/core/src/utils/bundle-groups.ts","../../../products/core/src/utils/bundle-selection.ts","../../../products/core/src/hooks/use-bundle-selector.ts","../../../products/core/src/utils/bundle-totals.ts","../../../platform/ui-components/src/components/SearchSort.tsx"],"sourcesContent":["import { createContext, type JSX, type ReactNode, use } from \"react\";\nimport type { ProductsApi } from \"./products-api\";\n\ninterface ProductsCoreConfig {\n  api: ProductsApi;\n}\n\nconst ProductsCoreContext = createContext<ProductsCoreConfig | null>(null);\n\nexport function ProductsCoreProvider({\n  api,\n  children,\n}: ProductsCoreConfig & { children: ReactNode }): JSX.Element {\n  return (\n    <ProductsCoreContext.Provider value={{ api }}>\n      {children}\n    </ProductsCoreContext.Provider>\n  );\n}\n\nexport function useProductsApi(): ProductsApi {\n  const ctx = use(ProductsCoreContext);\n  if (!ctx) {\n    throw new Error(\n      \"useProductsApi must be used within a <ProductsCoreProvider>\",\n    );\n  }\n  return ctx.api;\n}\n","import { createContext, type JSX, type ReactNode, use } from \"react\";\nimport type { PortalProductsApi } from \"./portal-products-api\";\n\ninterface PortalProductsCoreConfig {\n  api: PortalProductsApi;\n}\n\nconst PortalProductsCoreContext =\n  createContext<PortalProductsCoreConfig | null>(null);\n\nexport function PortalProductsCoreProvider({\n  api,\n  children,\n}: PortalProductsCoreConfig & { children: ReactNode }): JSX.Element {\n  return (\n    <PortalProductsCoreContext.Provider value={{ api }}>\n      {children}\n    </PortalProductsCoreContext.Provider>\n  );\n}\n\nexport function usePortalProductsApi(): PortalProductsApi {\n  const ctx = use(PortalProductsCoreContext);\n  if (!ctx) {\n    throw new Error(\n      \"usePortalProductsApi must be used within a <PortalProductsCoreProvider>\",\n    );\n  }\n  return ctx.api;\n}\n","import { useState, useEffect } from \"react\";\n\nexport function useDebounce<T>(value: T, delay: number): T {\n  const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n  useEffect(() => {\n    const timer = setTimeout(() => {\n      setDebouncedValue(value);\n    }, delay);\n\n    return () => {\n      clearTimeout(timer);\n    };\n  }, [value, delay]);\n\n  return debouncedValue;\n}\n","/**\n * A variant is subscribable when the backend signaled it via whichever of its\n * equivalent PER-VARIANT fields the serving API populates:\n *   - admin / V2 API:    `allow_subscription` (computed `has_subscription_plans?`)\n *   - portal-tenant API: `subscription_pricing` (per-variant, per-country plan pricing)\n *\n * Both signals are per-variant, so a variant lacking usable pricing stays\n * non-subscribable even if its product offers plans (e.g. the plan isn't priced\n * in the buyer's country). Tolerating whichever field a given API populates lets\n * the portal and admin surfaces share one rule and not drift apart.\n */\nexport function isVariantSubscribable(\n  variant?: {\n    allow_subscription?: boolean;\n    subscription_pricing?: readonly unknown[];\n  } | null,\n): boolean {\n  return (\n    variant?.allow_subscription === true ||\n    (variant?.subscription_pricing?.length ?? 0) > 0\n  );\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { portalProducts } from \"../portal-products-api\";\nimport { usePortalProductsApi } from \"../portal-products-api-context\";\n\nconst portalProductKeys = {\n  all: [\"portal-products\"] as const,\n  list: (params?: portalProducts.CursorPaginationParams) =>\n    [...portalProductKeys.all, \"list\", params] as const,\n  detail: (id: string | number) =>\n    [...portalProductKeys.all, \"detail\", String(id)] as const,\n  search: (query: string, params?: portalProducts.CursorPaginationParams) =>\n    [...portalProductKeys.all, \"search\", query, params] as const,\n  media: (productId: string | number) =>\n    [...portalProductKeys.all, \"media\", String(productId)] as const,\n};\n\nexport { portalProductKeys };\n\nexport function usePortalProducts(\n  params?: portalProducts.CursorPaginationParams,\n) {\n  const api = usePortalProductsApi();\n  return useQuery({\n    queryKey: portalProductKeys.list(params),\n    queryFn: () => api.listProducts(params),\n  });\n}\n\nexport function usePortalProduct(\n  id: string | number,\n  options?: { enabled?: boolean },\n) {\n  const api = usePortalProductsApi();\n  return useQuery({\n    queryKey: portalProductKeys.detail(id),\n    queryFn: () => api.getProduct(id),\n    enabled: options?.enabled ?? true,\n  });\n}\n\nexport function usePortalProductSearch(\n  query: string,\n  params?: portalProducts.CursorPaginationParams & { enabled?: boolean },\n) {\n  const api = usePortalProductsApi();\n  const { enabled, ...paginationParams } = params ?? {};\n  return useQuery({\n    queryKey: portalProductKeys.search(query, paginationParams),\n    queryFn: () => api.searchProducts(query, paginationParams),\n    enabled: enabled ?? query.length > 0,\n  });\n}\n\nexport function usePortalProductMedia(\n  productId: string | number,\n  options?: { enabled?: boolean },\n) {\n  const api = usePortalProductsApi();\n  return useQuery({\n    queryKey: portalProductKeys.media(productId),\n    queryFn: () => api.getProductMedia(productId),\n    enabled: options?.enabled ?? true,\n  });\n}\n","import { useState, useMemo, useCallback } from \"react\";\nimport type { portalProducts } from \"../portal-products-api\";\nimport { usePortalProductsApi } from \"../portal-products-api-context\";\nimport { useDebounce } from \"./use-debounce\";\n\nexport interface UsePortalProductCatalogParams {\n  perPage?: number;\n}\n\n/** Page param is a cursor string from the BFF, or undefined for the first page. */\nexport type PortalProductPageParam = string | undefined;\n\nexport function usePortalProductCatalog({\n  perPage = 25,\n}: UsePortalProductCatalogParams = {}) {\n  const api = usePortalProductsApi();\n  const [searchTerm, setSearchTerm] = useState(\"\");\n  const debouncedSearchTerm = useDebounce(searchTerm, 300);\n  const [currentSort, setCurrentSort] = useState<string>(\"created_at_desc\");\n\n  const fetchProducts = useCallback(\n    async (\n      pageParam?: PortalProductPageParam,\n      signal?: AbortSignal,\n    ): Promise<portalProducts.PortalProductsResponse> => {\n      const params: portalProducts.CursorPaginationParams = {\n        limit: perPage,\n        sort: currentSort,\n      };\n      if (pageParam !== undefined) params.cursor = pageParam;\n      if (signal !== undefined) params.signal = signal;\n\n      if (debouncedSearchTerm) {\n        return api.searchProducts(debouncedSearchTerm, params);\n      }\n      return api.listProducts(params);\n    },\n    [api, debouncedSearchTerm, perPage, currentSort],\n  );\n\n  const getNextPageParam = useCallback(\n    (\n      lastPage: portalProducts.PortalProductsResponse,\n      _allPages: portalProducts.PortalProductsResponse[],\n      lastPageParam: PortalProductPageParam,\n    ): PortalProductPageParam => {\n      const nextCursor = lastPage.meta?.pagination?.next_cursor ?? undefined;\n      // Stop if the API returned the same cursor we just sent (prevents\n      // infinite refetch loops when the backend doesn't advance).\n      if (nextCursor != null && nextCursor === lastPageParam) {\n        return undefined;\n      }\n      return nextCursor;\n    },\n    [],\n  );\n\n  const queryKey = useMemo(\n    () => [\n      \"portal-product-catalog\",\n      debouncedSearchTerm || \"\",\n      perPage,\n      currentSort,\n    ],\n    [debouncedSearchTerm, perPage, currentSort],\n  );\n\n  return {\n    searchTerm,\n    setSearchTerm,\n    debouncedSearchTerm,\n    currentSort,\n    setCurrentSort,\n    fetchProducts,\n    getNextPageParam,\n    queryKey,\n    perPage,\n  };\n}\n","import { useMemo } from \"react\";\nimport { usePortalProduct } from \"./use-portal-products\";\n\nexport interface UsePortalProductDetailParams {\n  productId: string;\n}\n\nexport function usePortalProductDetail({\n  productId,\n}: UsePortalProductDetailParams) {\n  const {\n    data: productResponse,\n    isLoading,\n    error,\n  } = usePortalProduct(productId);\n\n  const product = productResponse?.product;\n\n  const images = useMemo(() => {\n    if (!product?.images) return [];\n    return product.images.map((img, idx) => ({\n      id: idx,\n      url: img.url ?? \"\",\n      alt: img.alt ?? null,\n    }));\n  }, [product?.images]);\n\n  return {\n    product,\n    isLoading,\n    error,\n    images,\n  };\n}\n","import type { products } from \"../types\";\n\nexport function ensureDefaultSubscriptionPlan(\n  plans: readonly products.ProductSubscriptionPlan[],\n): products.ProductSubscriptionPlan[] {\n  const activePlans = plans.filter((plan) => plan.active !== false);\n\n  if (activePlans.length === 0) {\n    return plans.map((plan) => ({ ...plan, default: false }));\n  }\n\n  const hasActiveDefault = activePlans.some((plan) => plan.default === true);\n\n  if (!hasActiveDefault) {\n    const planWithLowestId = activePlans.reduce((lowest, current) => {\n      const lowestId = lowest.subscription_plan?.id || Infinity;\n      const currentId = current.subscription_plan?.id || Infinity;\n      return currentId < lowestId ? current : lowest;\n    });\n\n    return plans.map((plan) => ({\n      ...plan,\n      default:\n        plan.subscription_plan?.id === planWithLowestId.subscription_plan?.id &&\n        plan.active !== false,\n    }));\n  }\n\n  return plans.map((plan) => ({\n    ...plan,\n    default: plan.active !== false ? (plan.default ?? false) : false,\n  }));\n}\n\nexport function plansToAttributes(\n  plans: readonly products.ProductSubscriptionPlan[],\n): products.ProductSubscriptionPlanAttribute[] {\n  return plans.map((plan) => ({\n    ...(plan.id !== undefined && { id: plan.id }),\n    subscription_plan_id: plan.subscription_plan.id,\n    default: plan.default || false,\n    active: plan.active !== false,\n  }));\n}\n","import { create, type StateCreator } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport type { products } from \"../types\";\nimport {\n  ensureDefaultSubscriptionPlan,\n  plansToAttributes,\n} from \"../utils/subscription-plans\";\n\n// Default state\nconst defaultState = {\n  title: \"\",\n  description: \"\",\n  introduction: \"\",\n  stripped: \"\",\n  feature_text: \"\",\n  sku: \"\",\n  slug: \"\",\n  canonical_url: null as string | null,\n  image_url: \"\",\n  status: \"draft\",\n  publish_at: null,\n  price: \"0\",\n  commission: 0,\n  public: true,\n  no_index: false,\n  show_reviews: true,\n  publish_to_retail_store: true,\n  publish_to_mobile_store: true,\n  publish_to_portal_shop: true,\n  publish_to_share_tab: true,\n  collection_ids: [],\n  tag_ids: [],\n  images_attributes: [],\n  product_subscription_plans_attributes: [],\n  product_subscription_plans: [],\n  variants_attributes: [],\n  bundle: false,\n  track_inventory_on_bundle_items: false,\n  product_bundles_attributes: [],\n  option_attrs: [],\n  options: [],\n  metafields_attributes: [],\n  metadata: {},\n  search_engine_optimizer_attributes: {\n    title: \"\",\n    description: \"\",\n    image_url: \"\",\n    image_path: \"\",\n    block_crawler: false,\n  },\n};\n\ntype ValidationErrors = Partial<Record<string, string>>;\n\nexport type TranslationData = {\n  title?: string;\n  introduction?: string;\n  description?: string;\n  feature_text?: string;\n};\n\ntype LanguageTranslations = Record<string, TranslationData>;\ntype TranslationLoadingState = Record<string, boolean>;\n\ninterface UpdateFieldOptions {\n  shouldValidate?: boolean;\n  shouldClearError?: boolean;\n  markDirty?: boolean;\n}\n\ntype ArrayItemType<T> = T extends (infer U)[] ? U : unknown;\n\ntype ProductStoreFields = products.UpdateProduct & {\n  product_subscription_plans: products.ProductSubscriptionPlan[];\n  track_inventory_on_bundle_items: boolean;\n  canonical_url: string | null;\n  publish_at: string | null;\n};\n\nexport type ProductStoreState = ProductStoreFields & {\n  errors: ValidationErrors;\n  isValid: boolean;\n  isDirty: boolean;\n  translations: LanguageTranslations;\n  editedTranslations: LanguageTranslations;\n  translationErrors: Record<string, Record<string, string | undefined>>;\n  translationLoading: TranslationLoadingState;\n  translationsFetched: boolean;\n\n  setProduct: (productData: products.Product) => void;\n  updateField: <K extends keyof ProductStoreFields>(\n    key: K,\n    value: ProductStoreFields[K],\n    options?: UpdateFieldOptions,\n  ) => void;\n  updatePartial: (updates: Partial<ProductStoreFields>) => void;\n  updateSlug: (slug: string, isManual?: boolean) => void;\n  updateSEO: (seo: {\n    title?: string;\n    description?: string;\n    image_url?: string;\n    image_path?: string;\n    block_crawler?: boolean;\n  }) => void;\n  updateArrayItem: <K extends keyof ProductStoreFields>(\n    arrayKey: K,\n    itemId: number | string,\n    updatedItem: ArrayItemType<ProductStoreFields[K]>,\n    idField?: string,\n  ) => void;\n  reset: () => void;\n  validateField: (field: string) => void;\n  validateRequired: () => boolean;\n  clearErrors: () => void;\n  clearFieldError: (field: string) => void;\n  markClean: () => void;\n\n  setTranslationLoading: (languageIso: string, loading: boolean) => void;\n  setTranslationData: (languageIso: string, data: TranslationData) => void;\n  updateTranslationField: (\n    languageIso: string,\n    field: keyof TranslationData,\n    value: string,\n  ) => void;\n  getOriginalTranslation: (\n    languageIso: string,\n    field: keyof TranslationData,\n  ) => string | undefined;\n  getEditedTranslation: (\n    languageIso: string,\n    field: keyof TranslationData,\n  ) => string | undefined;\n  setTranslationError: (\n    languageIso: string,\n    field: keyof TranslationData,\n    error?: string,\n  ) => void;\n  getTranslationError: (\n    languageIso: string,\n    field: keyof TranslationData,\n  ) => string | undefined;\n  getTranslation: (\n    languageIso: string,\n    field: keyof TranslationData,\n  ) => string | undefined;\n  isTranslationLoading: (languageIso: string) => boolean;\n  resetTranslations: () => void;\n  setTranslationsFetched: (fetched: boolean) => void;\n};\n\nfunction syncSubscriptionPlans(plans: products.ProductSubscriptionPlan[]): {\n  product_subscription_plans: products.ProductSubscriptionPlan[];\n  product_subscription_plans_attributes: products.ProductSubscriptionPlanAttribute[];\n} {\n  const activePlans = plans.filter((plan) => plan.active !== false);\n  const plansWithDefault =\n    activePlans.length > 0 ? ensureDefaultSubscriptionPlan(plans) : plans;\n  return {\n    product_subscription_plans: plansWithDefault,\n    product_subscription_plans_attributes: plansToAttributes(plansWithDefault),\n  };\n}\n\nconst extractFilenameFromUrl = (imageUrl: string): string => {\n  try {\n    const url = new URL(imageUrl);\n    return url.pathname.split(\"/\").pop() || \"\";\n  } catch {\n    return imageUrl.split(\"/\").pop()?.split(\"?\")[0] || \"\";\n  }\n};\n\nconst createProductStore: StateCreator<ProductStoreState> = (set, get) => ({\n  ...defaultState,\n  track_inventory_on_bundle_items: false,\n  translations: {},\n  editedTranslations: {},\n  translationErrors: {},\n  translationLoading: {},\n  translationsFetched: false,\n  errors: {},\n  isValid: false,\n  isDirty: false,\n\n  setProduct: (productData: products.Product) => {\n    const initialImagePath =\n      productData.image_path && !productData.image_path.includes(\"undefined\")\n        ? productData.image_path\n        : productData.image_url\n          ? extractFilenameFromUrl(productData.image_url)\n          : undefined;\n    const categoryId = productData.category_id\n      ? parseInt(productData.category_id)\n      : productData.category?.id;\n    const transformedData: ProductStoreFields = {\n      ...(productData.id !== undefined && { id: productData.id }),\n      title: productData.title || \"\",\n      description: productData.description || \"\",\n      introduction: productData.introduction || \"\",\n      stripped: productData.stripped || \"\",\n      feature_text: productData.feature_text || \"\",\n      sku: productData.sku || \"\",\n      slug: productData.slug || \"\",\n      canonical_url: productData.canonical_url || null,\n      custom_slug: productData.custom_slug || false,\n      image_url: productData.image_url || \"\",\n      ...(initialImagePath !== undefined && { image_path: initialImagePath }),\n      status: productData.status || \"draft\",\n      publish_at: productData.publish_at || null,\n      commission:\n        typeof productData.commission === \"string\"\n          ? parseFloat(productData.commission)\n          : productData.commission || 0,\n      public: productData.public ?? true,\n      no_index: productData.no_index ?? false,\n      show_reviews: productData.show_reviews ?? true,\n      ...(productData.publish_to_retail_store !== undefined && {\n        publish_to_retail_store: productData.publish_to_retail_store,\n      }),\n      ...(productData.publish_to_mobile_store !== undefined && {\n        publish_to_mobile_store: productData.publish_to_mobile_store,\n      }),\n      ...(productData.publish_to_portal_shop !== undefined && {\n        publish_to_portal_shop: productData.publish_to_portal_shop,\n      }),\n      ...(productData.publish_to_share_tab !== undefined && {\n        publish_to_share_tab: productData.publish_to_share_tab,\n      }),\n      ...(productData.tax_category_id && {\n        tax_category_id: productData.tax_category_id,\n      }),\n      ...(productData.international_tax_type && {\n        international_tax_type: productData.international_tax_type,\n      }),\n      ...(categoryId !== undefined && { category_id: categoryId }),\n      ...(productData.application_theme_template_id != null && {\n        application_theme_template_id:\n          productData.application_theme_template_id,\n      }),\n      collection_ids:\n        (productData.collections as Array<{ id: number }> | undefined)?.map(\n          (collection) => collection.id,\n        ) ?? [],\n      tag_ids: Array.isArray(productData.tags)\n        ? (productData.tags as Array<{ id: number } | number>)\n            .map((tag) => (typeof tag === \"number\" ? tag : tag?.id))\n            .filter(Boolean)\n        : [],\n      search_engine_optimizer_attributes: (() => {\n        const seo = productData.search_engine_optimizer;\n        const resolvedTitle = seo?.title || productData.title;\n        return {\n          ...(seo?.id !== undefined && { id: seo.id }),\n          ...(resolvedTitle && { title: resolvedTitle }),\n          description: seo?.description || \"\",\n          image_url: seo?.image_url || productData.image_url || \"\",\n          image_path: seo?.image_path || productData.image_path || \"\",\n          block_crawler: seo?.block_crawler ?? false,\n        };\n      })(),\n      images_attributes:\n        (productData.images as products.ImageAttribute[] | undefined)?.map(\n          (img) => {\n            const imagePath =\n              img.image_path && !img.image_path.includes(\"undefined\")\n                ? img.image_path\n                : img.image_url\n                  ? extractFilenameFromUrl(img.image_url)\n                  : undefined;\n            return {\n              ...(img.id !== undefined && { id: img.id }),\n              position: img.position || 0,\n              image_url: img.image_url,\n              ...(imagePath !== undefined && { image_path: imagePath }),\n              _destroy: false,\n            };\n          },\n        ) || [],\n      ...(productData.images &&\n        productData.images.length > 0 &&\n        (() => {\n          if (\n            productData.image_path &&\n            typeof productData.image_path === \"string\" &&\n            !productData.image_path.includes(\"undefined\")\n          ) {\n            return { image_path: productData.image_path };\n          }\n          const firstImage = (\n            productData.images as products.ImageAttribute[]\n          ).find((img) => img.position === 0);\n          if (\n            firstImage?.image_path &&\n            typeof firstImage.image_path === \"string\" &&\n            !firstImage.image_path.includes(\"undefined\")\n          ) {\n            return { image_path: firstImage.image_path };\n          }\n          return productData.image_url\n            ? { image_path: extractFilenameFromUrl(productData.image_url) }\n            : {};\n        })()),\n      product_subscription_plans: (() => {\n        const plans =\n          productData?.product_subscription_plans?.map(\n            (plan: products.ProductSubscriptionPlan) => ({ ...plan }),\n          ) || [];\n        return plans.length > 0 ? ensureDefaultSubscriptionPlan(plans) : plans;\n      })(),\n      product_subscription_plans_attributes: (() => {\n        const plans =\n          productData?.product_subscription_plans?.map(\n            (plan: products.ProductSubscriptionPlan) => ({ ...plan }),\n          ) || [];\n        const finalPlans =\n          plans.length > 0 ? ensureDefaultSubscriptionPlan(plans) : plans;\n        return plansToAttributes(finalPlans);\n      })(),\n      variants_attributes:\n        productData?.variants\n          ?.filter(\n            (\n              variant: products.Variant,\n            ): variant is products.Variant & { id: number } =>\n              variant.id !== null && variant.id !== undefined,\n          )\n          .map((variant) => {\n            const imagesAttributes = variant?.images?.map(\n              (image: {\n                id?: number;\n                position: number;\n                image_url: string;\n              }) => ({\n                ...(image.id !== undefined && { id: image.id }),\n                position: image.position || 0,\n                image_url: image.image_url,\n                _destroy: false,\n              }),\n            );\n            const inventoryLevelsAttributes = variant?.inventory_levels\n              ?.filter(\n                (level: products.InventoryLevel) =>\n                  (level.warehouse_id ?? level.warehouse?.id) != null,\n              )\n              .map((level: products.InventoryLevel) => ({\n                id: level.id,\n                available: level.available,\n                committed: level.committed,\n                on_hand: level.on_hand,\n                unavailable: level.unavailable,\n                warehouse_id: level.warehouse?.id || 0,\n                _destroy: false,\n              }));\n            return {\n              id: variant.id,\n              title: variant.title ?? productData?.title ?? \"Untitled Variant\",\n              option_attrs: variant.option_attrs || [],\n              ...(variant.sku && { sku: variant.sku }),\n              ...(variant.price !== undefined && { price: variant.price }),\n              track_quantity: variant.track_quantity ?? false,\n              keep_selling: variant.keep_selling ?? false,\n              bar_code: variant.bar_code ?? \"\",\n              ...(variant.limit_subscription !== undefined && {\n                limit_subscription: variant.limit_subscription,\n              }),\n              subscription_max_qty: variant.subscription_max_qty ?? 0,\n              customer_limit: variant.customer_limit ?? 0,\n              is_master: variant.is_master,\n              _destroy: false,\n              ...(imagesAttributes && { images_attributes: imagesAttributes }),\n              ...(inventoryLevelsAttributes && {\n                inventory_levels_attributes: inventoryLevelsAttributes,\n              }),\n              variant_countries_attributes: variant?.variant_countries\n                ? Object.entries(\n                    variant.variant_countries as Record<\n                      string,\n                      products.VariantCountry\n                    >,\n                  ).map(([iso, country]) => ({\n                    id: country.id ?? 0,\n                    active: country.active ?? true,\n                    ...(country.country_id !== undefined && {\n                      country_id: country.country_id,\n                    }),\n                    country_name: country.country_name ?? \"\",\n                    country_iso: iso,\n                    price: Number(country.price) || 0,\n                    subscription_price: Number(country.subscription_price) || 0,\n                    wholesale: Number(country.wholesale) || 0,\n                    wholesale_subscription_price:\n                      Number(country.wholesale_subscription_price) || 0,\n                    compare_price: Number(country.compare_price) || 0,\n                    cv: Number(country.cv) || 0,\n                    qv: Number(country.qv) || 0,\n                    pc_cv: Number(country.pc_cv) || 0,\n                    pc_qv: Number(country.pc_qv) || 0,\n                    cost_of_goods_sold: Number(country.cost_of_goods_sold) || 0,\n                    currency_code: country.currency_code || null,\n                    shipping: Number(country.shipping) || 0,\n                    ...(country.points !== undefined && {\n                      points: country.points,\n                    }),\n                  }))\n                : [],\n            };\n          }) ?? [],\n      bundle: (productData.product_bundles?.length ?? 0) > 0,\n      track_inventory_on_bundle_items:\n        productData.track_inventory_on_bundle_items ?? false,\n      product_bundles_attributes: (productData.product_bundles || []).map(\n        (bundle: products.ProductBundle) => ({\n          id: bundle.id,\n          bundled_variant_id: bundle.bundled_variant?.id || 0,\n          bundled_variant: {\n            title: bundle.bundled_variant?.title || \"\",\n            sku: bundle.bundled_variant?.sku || null,\n            price: String(bundle.bundled_variant?.price || \"0\"),\n            price_in_currency: bundle.bundled_variant?.price_in_currency || \"\",\n            ...(bundle.bundled_variant?.currency_code !== undefined && {\n              currency_code: bundle.bundled_variant.currency_code,\n            }),\n            product: {\n              id: bundle.bundled_variant?.product.id || 0,\n              title: bundle.bundled_variant?.product.title || \"\",\n              image_url: bundle.bundled_variant?.product.image_url || \"\",\n              price: bundle.bundled_variant?.product.price || \"0\",\n              price_in_currency:\n                bundle.bundled_variant?.product.price_in_currency || \"\",\n              cv: bundle.bundled_variant?.product.cv || 0,\n              qv: bundle.bundled_variant?.product.qv || 0,\n            },\n          },\n          cv: bundle.cv || 0,\n          qv: bundle.qv || 0,\n          quantity: bundle.quantity,\n          display_externally: bundle.display_externally ?? true,\n          _destroy: false,\n        }),\n      ),\n      option_attrs: productData.option_attrs || [],\n      options: productData.options || [],\n      metafields_attributes: (productData.metafields || []).map(\n        (metafield: products.Metafield) => ({\n          id: metafield.id,\n          namespace: metafield.namespace,\n          key: metafield.key,\n          value: metafield.value,\n          value_type: metafield.value_type,\n          _destroy: false,\n        }),\n      ),\n      metadata: productData.metadata || {},\n    };\n\n    set({\n      ...transformedData,\n      errors: {},\n      isValid: false,\n      isDirty: false,\n    });\n  },\n\n  updateSlug: (slug, isManual = true) => {\n    set((state: ProductStoreState) => ({\n      slug,\n      custom_slug: isManual,\n      search_engine_optimizer_attributes: {\n        ...state.search_engine_optimizer_attributes,\n        title: state.search_engine_optimizer_attributes?.title || \"\",\n        description:\n          state.search_engine_optimizer_attributes?.description || \"\",\n        image_url: state.search_engine_optimizer_attributes?.image_url || \"\",\n        image_path: state.search_engine_optimizer_attributes?.image_path || \"\",\n        block_crawler:\n          state.search_engine_optimizer_attributes?.block_crawler ?? false,\n      },\n      isDirty: true,\n    }));\n  },\n\n  updateSEO: (seo) => {\n    set((state: ProductStoreState) => ({\n      search_engine_optimizer_attributes: {\n        ...state.search_engine_optimizer_attributes,\n        title:\n          seo.title !== undefined\n            ? seo.title\n            : state.search_engine_optimizer_attributes?.title || \"\",\n        description:\n          seo.description !== undefined\n            ? seo.description\n            : state.search_engine_optimizer_attributes?.description || \"\",\n        image_url:\n          seo.image_url !== undefined\n            ? seo.image_url\n            : state.search_engine_optimizer_attributes?.image_url || \"\",\n        image_path:\n          seo.image_path !== undefined\n            ? seo.image_path\n            : state.search_engine_optimizer_attributes?.image_path || \"\",\n        block_crawler:\n          seo.block_crawler !== undefined\n            ? seo.block_crawler\n            : (state.search_engine_optimizer_attributes?.block_crawler ??\n              false),\n      },\n      isDirty: true,\n    }));\n  },\n\n  updateField: <K extends keyof ProductStoreFields>(\n    key: K,\n    value: ProductStoreFields[K],\n    options?: UpdateFieldOptions,\n  ) => {\n    const {\n      shouldValidate = false,\n      shouldClearError = true,\n      markDirty = true,\n    } = options || {};\n\n    set((state: ProductStoreState) => {\n      if (key === \"product_subscription_plans\") {\n        return {\n          ...state,\n          ...syncSubscriptionPlans(value as products.ProductSubscriptionPlan[]),\n          errors: shouldClearError\n            ? { ...state.errors, [key]: undefined }\n            : state.errors,\n          isDirty: markDirty ? true : state.isDirty,\n        };\n      }\n\n      return {\n        ...state,\n        [key]: value,\n        errors: shouldClearError\n          ? { ...state.errors, [key]: undefined }\n          : state.errors,\n        isDirty: markDirty ? true : state.isDirty,\n      };\n    });\n\n    if (shouldValidate) {\n      get().validateField(key as string);\n    }\n  },\n\n  updatePartial: (updates: Partial<ProductStoreFields>) => {\n    set((state: ProductStoreState) => {\n      if (updates.product_subscription_plans) {\n        return {\n          ...state,\n          ...updates,\n          ...syncSubscriptionPlans(updates.product_subscription_plans),\n          errors: {\n            ...state.errors,\n            ...Object.keys(updates).reduce(\n              (acc, key) => {\n                acc[key] = undefined;\n                return acc;\n              },\n              {} as Record<string, undefined>,\n            ),\n          },\n          isDirty: true,\n        };\n      }\n\n      return {\n        ...state,\n        ...updates,\n        errors: {\n          ...state.errors,\n          ...Object.keys(updates).reduce(\n            (acc, key) => {\n              acc[key] = undefined;\n              return acc;\n            },\n            {} as Record<string, undefined>,\n          ),\n        },\n        isDirty: true,\n      };\n    });\n  },\n\n  updateArrayItem: <K extends keyof ProductStoreFields>(\n    arrayKey: K,\n    itemId: number | string,\n    updatedItem: ArrayItemType<ProductStoreFields[K]>,\n    idField: string = \"id\",\n  ) => {\n    set((state: ProductStoreState) => {\n      const currentArray = Array.isArray(state[arrayKey])\n        ? (state[arrayKey] as unknown[])\n        : [];\n\n      const updatedArray = currentArray.map((item) => {\n        if (\n          typeof item === \"object\" &&\n          item !== null &&\n          idField in item &&\n          (item as Record<string, unknown>)[idField] === itemId\n        ) {\n          return updatedItem;\n        }\n        return item;\n      });\n\n      if (arrayKey === \"product_subscription_plans\") {\n        return {\n          ...state,\n          ...syncSubscriptionPlans(\n            updatedArray as products.ProductSubscriptionPlan[],\n          ),\n          errors: { ...state.errors, [arrayKey]: undefined },\n          isDirty: true,\n        };\n      }\n\n      return {\n        ...state,\n        [arrayKey]: updatedArray,\n        errors: { ...state.errors, [arrayKey]: undefined },\n        isDirty: true,\n      };\n    });\n  },\n\n  reset: () => {\n    set({\n      ...defaultState,\n      track_inventory_on_bundle_items: false,\n      custom_slug: false,\n      errors: {},\n      isValid: false,\n      isDirty: false,\n    });\n  },\n\n  // Validation methods — these provide basic field-level validation.\n  // For full schema validation (e.g. Zod), the consumer should call\n  // their own validation function against the store state.\n  validateField: (field: string) => {\n    const fieldValue = get()[field as keyof ProductStoreState];\n    const hasValue =\n      fieldValue !== undefined && fieldValue !== null && fieldValue !== \"\";\n\n    set((state: ProductStoreState) => ({\n      errors: {\n        ...state.errors,\n        [field]: hasValue ? undefined : `${field} is required`,\n      },\n      isValid:\n        hasValue &&\n        Object.keys(state.errors).every(\n          (key) => key === field || !state.errors[key],\n        ),\n    }));\n  },\n\n  validateRequired: () => {\n    // Basic validation: check that title exists\n    const state = get();\n    const errors: ValidationErrors = {};\n\n    if (!state.title) {\n      errors.title = \"Title is required\";\n    }\n\n    if (Object.keys(errors).length > 0) {\n      set({ errors, isValid: false });\n      return false;\n    }\n\n    set({ errors: {}, isValid: true });\n    return true;\n  },\n\n  clearErrors: () => {\n    set({ errors: {}, isValid: false });\n  },\n\n  clearFieldError: (field: string) => {\n    set((state: ProductStoreState) => ({\n      errors: { ...state.errors, [field]: undefined },\n    }));\n  },\n\n  markClean: () => {\n    set({ isDirty: false });\n  },\n\n  // Translation methods\n  setTranslationLoading: (languageIso: string, loading: boolean) => {\n    set((state: ProductStoreState) => ({\n      translationLoading: {\n        ...state.translationLoading,\n        [languageIso]: loading,\n      },\n    }));\n  },\n\n  setTranslationData: (languageIso: string, data: TranslationData) => {\n    set((state: ProductStoreState) => ({\n      translations: {\n        ...state.translations,\n        [languageIso]: data,\n      },\n      translationLoading: {\n        ...state.translationLoading,\n        [languageIso]: false,\n      },\n    }));\n  },\n\n  updateTranslationField: (\n    languageIso: string,\n    field: keyof TranslationData,\n    value: string,\n  ) => {\n    set((state: ProductStoreState) => {\n      const originalValue = state.translations[languageIso]?.[field];\n      let error: string | undefined;\n\n      if (\n        field === \"title\" &&\n        value === \"\" &&\n        originalValue &&\n        originalValue.trim() !== \"\"\n      ) {\n        error = \"Title is required\";\n      }\n\n      return {\n        editedTranslations: {\n          ...state.editedTranslations,\n          [languageIso]: {\n            ...state.editedTranslations[languageIso],\n            [field]: value,\n          },\n        },\n        translationErrors: {\n          ...state.translationErrors,\n          [languageIso]: {\n            ...state.translationErrors[languageIso],\n            [field]: error,\n          },\n        },\n      };\n    });\n  },\n\n  getTranslation: (languageIso: string, field: keyof TranslationData) => {\n    const state = get();\n    return (\n      state.editedTranslations[languageIso]?.[field] ??\n      state.translations[languageIso]?.[field]\n    );\n  },\n\n  getOriginalTranslation: (\n    languageIso: string,\n    field: keyof TranslationData,\n  ) => {\n    return get().translations[languageIso]?.[field];\n  },\n\n  getEditedTranslation: (languageIso: string, field: keyof TranslationData) => {\n    return get().editedTranslations[languageIso]?.[field];\n  },\n\n  setTranslationError: (\n    languageIso: string,\n    field: keyof TranslationData,\n    error?: string,\n  ) => {\n    set((state: ProductStoreState) => ({\n      translationErrors: {\n        ...state.translationErrors,\n        [languageIso]: {\n          ...state.translationErrors[languageIso],\n          [field]: error,\n        },\n      },\n    }));\n  },\n\n  getTranslationError: (languageIso: string, field: keyof TranslationData) => {\n    return get().translationErrors[languageIso]?.[field];\n  },\n\n  isTranslationLoading: (languageIso: string) => {\n    return get().translationLoading[languageIso] ?? false;\n  },\n\n  resetTranslations: () => {\n    set({\n      translations: {},\n      editedTranslations: {},\n      translationErrors: {},\n      translationLoading: {},\n      translationsFetched: false,\n    });\n  },\n\n  setTranslationsFetched: (fetched: boolean) => {\n    set({ translationsFetched: fetched });\n  },\n});\n\n// Local declare so consumers don't need @types/node while keeping the literal\n// `process.env.NODE_ENV` AST that bundlers (Webpack/Turbopack/Vite) replace.\ndeclare const process: { env?: { NODE_ENV?: string } } | undefined;\nconst isDevEnv =\n  typeof process !== \"undefined\" && process.env?.NODE_ENV === \"development\";\n\nexport const useProductStore = isDevEnv\n  ? create<ProductStoreState>()(\n      devtools(createProductStore, { name: \"product-store\" }),\n    )\n  : create<ProductStoreState>()(createProductStore);\n","import { create } from \"zustand\";\n\ninterface DraftStore {\n  draftData: unknown | null;\n  isFromSettings: boolean;\n  navigationTarget: string | null;\n\n  saveDraft: (data: unknown) => void;\n  getDraft: () => unknown | null;\n  clearDraft: () => void;\n  setFromSettings: (value: boolean) => void;\n  setNavigationTarget: (target: string | null) => void;\n  reset: () => void;\n}\n\nexport const useDraftStore = create<DraftStore>()((set, get) => ({\n  draftData: null,\n  isFromSettings: false,\n  navigationTarget: null,\n\n  saveDraft: (data: unknown) => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const formData = data as any;\n    const hasContent =\n      formData?.title ||\n      formData?.description ||\n      formData?.sku ||\n      (formData?.product_subscription_plans &&\n        formData.product_subscription_plans.length > 0);\n\n    if (hasContent) {\n      set({ draftData: data });\n    }\n  },\n\n  getDraft: () => {\n    const { draftData, isFromSettings, navigationTarget } = get();\n\n    if (!draftData) {\n      return null;\n    }\n\n    if (isFromSettings && navigationTarget) {\n      return draftData;\n    }\n\n    set({ draftData: null });\n    return null;\n  },\n\n  clearDraft: () => {\n    const { draftData } = get();\n    if (draftData) {\n      set({ draftData: null });\n    }\n  },\n\n  setFromSettings: (value: boolean) => {\n    set({ isFromSettings: value });\n  },\n\n  setNavigationTarget: (target: string | null) => {\n    set({ navigationTarget: target });\n  },\n\n  reset: () => {\n    set({ draftData: null, isFromSettings: false, navigationTarget: null });\n  },\n}));\n","import type { products } from \"../types\";\n\nexport function createSlug(title: string): string {\n  return title\n    .trim()\n    .toLowerCase()\n    .replace(/[^\\w\\s-]/g, \"\")\n    .replace(/\\s+/g, \"-\")\n    .replace(/-+/g, \"-\")\n    .replace(/^-+|-+$/g, \"\");\n}\n\nexport function stripHtmlTags(html: string): string {\n  return html\n    .replace(/<[^>]*>/g, \"\")\n    .replace(/&[^;]+;/g, \" \")\n    .trim();\n}\n\nexport function getVariantImageUrl(\n  variant?: {\n    primary_image?: string | null;\n    image_url?: string | null;\n    images?: Array<{ image_url: string }> | null;\n  } | null,\n  product?: { image_url?: string | null } | null,\n): string {\n  if (!variant) return product?.image_url || \"\";\n  if (variant.primary_image) return variant.primary_image;\n  if (variant.image_url) return variant.image_url;\n  if (Array.isArray(variant.images) && variant.images.length > 0) {\n    const firstImage = variant.images[0];\n    if (firstImage?.image_url) return firstImage.image_url;\n  }\n  return product?.image_url || \"\";\n}\n\nexport function getProductImageUrl(\n  product?: {\n    image_url?: string | null;\n    images?: Array<{ image_url: string; position?: number }> | null;\n  } | null,\n): string | null {\n  if (!product) return null;\n  if (Array.isArray(product.images) && product.images.length > 0) {\n    const sortedImages = product.images.toSorted(\n      (a, b) => (a.position ?? 0) - (b.position ?? 0),\n    );\n    const primaryImage = sortedImages[0];\n    if (primaryImage?.image_url) return primaryImage.image_url;\n  }\n  return product.image_url ?? null;\n}\n\nexport function sanitizeBundleData<\n  T extends products.CreateProduct | products.UpdateProduct,\n>(productData: T): T {\n  const activeVariants = (productData.variants_attributes || []).filter(\n    (variant) => !variant._destroy,\n  );\n  const hasMultipleVariants = activeVariants.length > 1;\n\n  const activeOptions = (productData.options || []).filter(\n    (opt) => !opt._destroy,\n  );\n  const derivedOptionAttrs = [\n    ...new Set(activeOptions.map((opt) => opt.title.toLowerCase())),\n  ];\n\n  // We spread the original data and override specific fields, preserving the\n  // runtime shape. The cast to T is safe because we only narrow/replace fields\n  // that exist on the base type.\n  if (hasMultipleVariants) {\n    return {\n      ...productData,\n      options: undefined,\n      option_attrs: derivedOptionAttrs,\n      bundle: false,\n      product_bundles_attributes: [],\n      track_inventory_on_bundle_items: false,\n    } as T;\n  }\n\n  return {\n    ...productData,\n    options: undefined,\n    option_attrs: derivedOptionAttrs,\n  } as T;\n}\n\nexport interface ApiErrorShape {\n  message?: string;\n  error_message?: string;\n  status?: number;\n  data?: unknown;\n  errors?: Record<string, string[]>;\n}\n\nfunction getErrorsFromApiError(\n  error: ApiErrorShape,\n): Record<string, string[]> | undefined {\n  if (error?.errors && typeof error.errors === \"object\") {\n    return error.errors;\n  }\n  if (error?.data && typeof error.data === \"object\") {\n    const data = error.data as Record<string, unknown>;\n    const firstKey = Object.keys(data)[0];\n    if (firstKey && Array.isArray(data[firstKey])) {\n      return data as Record<string, string[]>;\n    }\n  }\n  return undefined;\n}\n\nfunction formatFieldName(fieldName: string): string {\n  const SEO_FIELD_LABELS: Record<string, string> = {\n    \"search_engine_optimizer.title\": \"SEO Title\",\n    \"search_engine_optimizer.description\": \"SEO Description\",\n    \"search_engine_optimizer.image_url\": \"SEO Image\",\n  };\n\n  if (SEO_FIELD_LABELS[fieldName]) return SEO_FIELD_LABELS[fieldName];\n\n  const parts = fieldName.split(\".\");\n  if (parts.length > 1) {\n    return parts\n      .map((part, index) =>\n        part\n          .split(\"_\")\n          .map((word, wordIndex) => {\n            if (\n              (index === 0 && wordIndex === 0) ||\n              (index > 0 && wordIndex > 0)\n            )\n              return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();\n            return word.toLowerCase();\n          })\n          .join(\" \"),\n      )\n      .join(\" \");\n  }\n\n  return fieldName\n    .split(\"_\")\n    .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n    .join(\" \");\n}\n\nexport function getErrorMessage(\n  error: ApiErrorShape,\n  fallbackMessage: string,\n): string {\n  if (error?.message && error.message !== \"unprocessable entity\") {\n    return error.message;\n  }\n\n  const errorsObj = getErrorsFromApiError(error);\n  if (errorsObj) {\n    const firstField = Object.keys(errorsObj)[0];\n    if (firstField && errorsObj[firstField]) {\n      const fieldErrors = errorsObj[firstField];\n      if (\n        Array.isArray(fieldErrors) &&\n        fieldErrors.length > 0 &&\n        fieldErrors[0]\n      ) {\n        return `${formatFieldName(firstField)} ${fieldErrors[0]}`;\n      }\n    }\n  }\n\n  return fallbackMessage;\n}\n\nexport function isSeoError(fieldName: string): boolean {\n  return (\n    fieldName.startsWith(\"search_engine_optimizer\") || fieldName === \"slug\"\n  );\n}\n\nexport function extractSeoErrors(\n  error: ApiErrorShape,\n): Array<{ field: string; message: string }> {\n  const seoErrors: Array<{ field: string; message: string }> = [];\n  const errorsObj = getErrorsFromApiError(error);\n  if (errorsObj) {\n    for (const fieldName of Object.keys(errorsObj)) {\n      if (isSeoError(fieldName) && errorsObj[fieldName]) {\n        const fieldErrors = errorsObj[fieldName];\n        if (\n          Array.isArray(fieldErrors) &&\n          fieldErrors.length > 0 &&\n          fieldErrors[0]\n        ) {\n          seoErrors.push({\n            field: fieldName,\n            message: `${formatFieldName(fieldName)} ${fieldErrors[0].charAt(0).toUpperCase() + fieldErrors[0].slice(1)}`,\n          });\n        }\n      }\n    }\n  }\n  return seoErrors;\n}\n","import type { products } from \"../types\";\n\ntype ProductPriceInput = products.Product | products.ShopProduct;\n\nfunction stripParentheticalText(text: string | undefined): string | null {\n  if (!text) return null;\n  return text.replace(/\\s*\\([^)]*\\)/g, \"\").trim();\n}\n\nfunction isShopVariantCountry(\n  vc: products.VariantCountry | products.ShopVariantCountry | undefined,\n): vc is products.ShopVariantCountry {\n  return vc !== undefined && \"display_wholesale_subscription_price\" in vc;\n}\n\nfunction isAdminProduct(\n  product: ProductPriceInput,\n): product is products.Product {\n  return \"display_price\" in product;\n}\n\nfunction isVariantCountriesRecord(\n  vc: unknown,\n): vc is Record<string, products.VariantCountry> {\n  return vc !== null && typeof vc === \"object\" && !Array.isArray(vc);\n}\n\nexport function determineProductPrice(\n  product: ProductPriceInput,\n  countryIso: string,\n): { repPrice: string | null | undefined; price?: string | null } {\n  const { variants } = product;\n\n  // Get the first active variant for the country, or fall back to first variant\n  const selectedVariant =\n    variants?.find((v) => {\n      if (isVariantCountriesRecord(v.variant_countries)) {\n        return v.variant_countries[countryIso]?.active;\n      }\n      return false;\n    }) ||\n    variants?.[0] ||\n    null;\n\n  let variantCountry:\n    | products.VariantCountry\n    | products.ShopVariantCountry\n    | undefined;\n  if (countryIso && selectedVariant?.variant_countries) {\n    const variantCountries = selectedVariant.variant_countries;\n\n    if (Array.isArray(variantCountries)) {\n      variantCountry = variantCountries.find(\n        (v: products.ShopVariantCountry) => v?.country?.iso === countryIso,\n      );\n    } else if (isVariantCountriesRecord(variantCountries)) {\n      variantCountry = variantCountries[countryIso];\n    }\n  }\n\n  if (selectedVariant?.subscription_only)\n    return {\n      repPrice: isShopVariantCountry(variantCountry)\n        ? variantCountry.display_wholesale_subscription_price\n        : undefined,\n    };\n\n  const price = isShopVariantCountry(variantCountry)\n    ? variantCountry.display_price\n    : isAdminProduct(product)\n      ? product.display_price\n      : undefined;\n\n  const repPrice = isShopVariantCountry(variantCountry)\n    ? variantCountry.display_wholesale\n    : undefined;\n  return {\n    repPrice: stripParentheticalText(repPrice),\n    price: price === repPrice ? null : stripParentheticalText(price),\n  };\n}\n\nexport function extractPriceFromString(priceString: string): number | null {\n  if (!priceString) return null;\n  const strippedString = priceString.replace(/[^\\d.]/g, \"\");\n  return parseFloat(strippedString);\n}\n","/**\n * Format a single portal product price (a decimal string) as a localized\n * currency string. Returns null for an empty/missing price; falls back to a\n * plain concatenation when the value isn't numeric or the currency is invalid.\n */\nexport function formatPortalPrice(\n  price: string | undefined,\n  currency: string | undefined,\n): string | null {\n  if (!price) return null;\n  const numericPrice = Number(price);\n  if (Number.isNaN(numericPrice)) return `${currency ?? \"\"}${price}`;\n  try {\n    return new Intl.NumberFormat(undefined, {\n      style: \"currency\",\n      currency: currency || \"USD\",\n    }).format(numericPrice);\n  } catch {\n    return `$${price}`;\n  }\n}\n\n/**\n * Format a bundle's min–max price range. Collapses to a single price when min\n * and max are equal, and returns null when neither end resolves (the caller\n * then falls back to the single non-zero price so a bundle never renders as\n * free).\n */\nexport function formatPortalPriceRange(\n  priceRange: { min?: string; max?: string } | null | undefined,\n  currency: string | undefined,\n): string | null {\n  if (!priceRange) return null;\n  const min = formatPortalPrice(priceRange.min, currency);\n  const max = formatPortalPrice(priceRange.max, currency);\n  if (min && max) return min === max ? min : `${min} – ${max}`;\n  return min ?? max;\n}\n","/**\n * Format the per-plan savings fragment shown on Subscribe & Save chips.\n *\n * Returns the value-only string (\"10%\" or \"$5.00\") that the i18n template\n * \"Subscribe & Save {{savings}}\" wraps. Returns `null` when the inputs do\n * not represent a positive saving — callers fall back to the bare\n * \"Subscribe\" copy.\n *\n * Math basis intentionally matches the existing shop UI: savings come from\n * `wholesalePrice - wholesaleSubscriptionPrice`. The retail/wholesale UX\n * inconsistency for rep-tier shoppers exists on every other surface too\n * (Liquid themes use the same retail basis); aligning that platform-wide\n * is out of scope for this util.\n */\n\nexport type SavingsDisplayMode = \"percent\" | \"amount\";\n\nexport interface FormatSavingsInput {\n  wholesalePrice: number | undefined;\n  wholesaleSubscriptionPrice: number | undefined;\n  mode: SavingsDisplayMode;\n  currency: string;\n  locale?: string;\n}\n\nexport function formatSavings({\n  wholesalePrice,\n  wholesaleSubscriptionPrice,\n  mode,\n  currency,\n  locale,\n}: FormatSavingsInput): string | null {\n  if (\n    wholesalePrice === undefined ||\n    wholesaleSubscriptionPrice === undefined ||\n    !Number.isFinite(wholesalePrice) ||\n    !Number.isFinite(wholesaleSubscriptionPrice) ||\n    wholesalePrice <= 0\n  ) {\n    return null;\n  }\n\n  const savings = wholesalePrice - wholesaleSubscriptionPrice;\n  if (savings <= 0) return null;\n\n  if (mode === \"amount\") {\n    try {\n      return new Intl.NumberFormat(locale, {\n        style: \"currency\",\n        currency,\n      }).format(savings);\n    } catch {\n      return `${currency} ${savings.toFixed(2)}`;\n    }\n  }\n\n  const percent = Math.round((savings / wholesalePrice) * 100);\n  if (percent <= 0) return null;\n\n  if (locale !== undefined) {\n    try {\n      return new Intl.NumberFormat(locale, {\n        style: \"percent\",\n        maximumFractionDigits: 0,\n      }).format(percent / 100);\n    } catch {\n      // Fall through to the locale-less shorthand below.\n    }\n  }\n  return `${percent}%`;\n}\n","/**\n * A bundle is \"static\" when it has no customizable groups — a fixed set of\n * items with nothing for the shopper to configure (Oliabo's bundles are the\n * motivating case). A static bundle is functionally just a product, so the\n * portal can add it straight to the in-app cart instead of sending logged-in\n * shoppers to the storefront (where they lose wholesale pricing until checkout).\n *\n * Classification is server-authoritative: the backend computes\n * `has_customizations` from `Bundle#dynamic?` and returns `false` for static\n * bundles, `true` for customizable ones, and `null` for non-bundle products\n * (see the portal-tenant product API). We only treat a bundle as static when it\n * is explicitly `has_customizations === false`, so the helper degrades\n * gracefully — until the backend populates the field, every bundle reports\n * non-static and the storefront redirect is unchanged.\n */\nexport function isStaticBundle(\n  product?: {\n    is_bundle?: boolean;\n    has_customizations?: boolean | null;\n  } | null,\n): boolean {\n  return product?.is_bundle === true && product.has_customizations === false;\n}\n","/**\n * Resolve a bundle's per-plan subscription price (in the shopper's tier) from\n * the tier-aware base `price_range` and the selected plan's adjustment, per the\n * portal-tenant contract:\n *   - `percentage`   → discount the base price by `price_adjustment_amount`%.\n *   - `fixed_amount` → use the bundle's tier-aware `subscription_price_range`.\n *   - `null`         → legacy plan with no type stored; treated as `fixed_amount`.\n *\n * `basePrice` is the tier base price (for a static bundle, `price_range.min`,\n * which equals `max`). Returns the rounded subscription price, or `undefined`\n * when it can't be resolved (no plan, missing amount/range, unknown type) — the\n * caller then shows the plan without a savings figure rather than a wrong one.\n */\nexport function bundleSubscriptionPrice(\n  basePrice: number | undefined,\n  subscriptionPriceRange: { min?: string; max?: string } | null | undefined,\n  plan:\n    | {\n        price_adjustment_type?: \"percentage\" | \"fixed_amount\" | null;\n        price_adjustment_amount?: string | null;\n      }\n    | null\n    | undefined,\n): number | undefined {\n  if (!plan) return undefined;\n\n  if (plan.price_adjustment_type === \"percentage\") {\n    const percent = Number(plan.price_adjustment_amount);\n    if (\n      basePrice === undefined ||\n      !Number.isFinite(basePrice) ||\n      basePrice <= 0 ||\n      !Number.isFinite(percent) ||\n      percent <= 0 ||\n      percent >= 100\n    ) {\n      return undefined;\n    }\n    return roundToCents(basePrice * (1 - percent / 100));\n  }\n\n  // `fixed_amount` or `null` (legacy plan, treated as fixed_amount) → use the\n  // bundle's stored tier-aware subscription range.\n  if (\n    plan.price_adjustment_type === \"fixed_amount\" ||\n    plan.price_adjustment_type === null\n  ) {\n    const min = Number(subscriptionPriceRange?.min);\n    return Number.isFinite(min) && min > 0 ? roundToCents(min) : undefined;\n  }\n\n  return undefined;\n}\n\nfunction roundToCents(value: number): number {\n  return Math.round(value * 100) / 100;\n}\n","import type { portalProducts } from \"../portal-products-api\";\n\n/**\n * Bundle-group helpers for the portal dynamic-bundle configurator (CURRENT-1394).\n *\n * The port types `group_type` / `selection_type` as `string` to mirror the\n * generated client; these helpers narrow them to the domain unions and encode\n * the selection rules the UI enforces.\n *\n * Selection-rule note: `min_selections` / `max_selections` count the group's\n * total selected quantity (summed units across its items), not the number of\n * distinct items — the design mocks show a group with two items at qty 2 + 1\n * reading \"3/3\". Each item is still capped individually by its `max_quantity`,\n * and the group total is capped by `max_selections`. The server-side rules\n * (mirrored in CURRENT-1894) are the source of truth — revisit if they diverge.\n */\n\nexport type BundleGroupType = \"included\" | \"customizable\";\nexport type BundleSelectionType =\n  | \"exact\"\n  | \"min_only\"\n  | \"max_only\"\n  | \"min_and_max\";\n\nconst GROUP_TYPES: readonly BundleGroupType[] = [\"included\", \"customizable\"];\nconst SELECTION_TYPES: readonly BundleSelectionType[] = [\n  \"exact\",\n  \"min_only\",\n  \"max_only\",\n  \"min_and_max\",\n];\n\n/** A single item's selection: chosen quantity, subscribe flag, and plan. */\nexport interface BundleItemSelection {\n  quantity: number;\n  subscribe: boolean;\n  /** Chosen subscription plan id; null when not subscribed or the item has no plans. */\n  subscriptionPlanId: number | null;\n}\n\n/**\n * The plan id to seed when an item's subscription is on: the admin-configured\n * default when set, otherwise the item's first plan, else null.\n */\nexport function defaultItemPlanId(\n  item: portalProducts.BundleGroupItem,\n): number | null {\n  if (item.subscription_plan_id != null) return item.subscription_plan_id;\n  return item.subscription_plans?.[0]?.id ?? null;\n}\n\n/** A group's selections keyed by `variant_id`. */\nexport type GroupSelection = Record<number, BundleItemSelection>;\n\nexport function getGroupType(\n  group: portalProducts.BundleGroup,\n): BundleGroupType | null {\n  return GROUP_TYPES.includes(group.group_type as BundleGroupType)\n    ? (group.group_type as BundleGroupType)\n    : null;\n}\n\nexport function isIncludedGroup(group: portalProducts.BundleGroup): boolean {\n  return getGroupType(group) === \"included\";\n}\n\nexport function isCustomizableGroup(\n  group: portalProducts.BundleGroup,\n): boolean {\n  return getGroupType(group) === \"customizable\";\n}\n\nexport function getSelectionType(\n  group: portalProducts.BundleGroup,\n): BundleSelectionType | null {\n  const value = group.selection_type;\n  return value != null && SELECTION_TYPES.includes(value as BundleSelectionType)\n    ? (value as BundleSelectionType)\n    : null;\n}\n\nexport function sortGroups(\n  groups: portalProducts.BundleGroup[],\n): portalProducts.BundleGroup[] {\n  return [...groups].sort((a, b) => a.sort_order - b.sort_order);\n}\n\nexport function sortItems(\n  items: portalProducts.BundleGroupItem[],\n): portalProducts.BundleGroupItem[] {\n  return [...items].sort((a, b) => a.sort_order - b.sort_order);\n}\n\n/** Clamp a requested quantity to `[0, max_quantity]` (uncapped when max is null). */\nexport function clampQuantity(\n  item: portalProducts.BundleGroupItem,\n  quantity: number,\n): number {\n  const lower = Math.max(0, quantity);\n  return item.max_quantity == null ? lower : Math.min(lower, item.max_quantity);\n}\n\n/**\n * A default item whose configured quantity exceeds its own cap. State stays\n * valid (the default is clamped), so the UI uses this to render the blocked\n * state per the MVP catalog rather than silently hiding the misconfiguration.\n */\nexport function hasDefaultMaxCollision(\n  item: portalProducts.BundleGroupItem,\n): boolean {\n  return (\n    item.is_default === true &&\n    item.max_quantity != null &&\n    item.quantity > item.max_quantity\n  );\n}\n\n/**\n * Initial selections for a group: every item of an included group, or the\n * `is_default` items of a customizable group, each at its (clamped) quantity.\n * `subscribe` is seeded on when the item or group forces a subscription.\n *\n * Items unavailable for the shopper are skipped for every group type: an\n * unavailable item can't be purchased, and its remove control is disabled in\n * the UI, so pre-selecting it strands it (CURRENT-1394 finding #6). Skipping it\n * here also keeps this in step with `getWholeGroupSelections` (the re-pick\n * path), so a branch has the same items whether seeded on load or re-chosen.\n */\nexport function getDefaultSelectionsForGroup(\n  group: portalProducts.BundleGroup,\n): GroupSelection {\n  const forceGroup = group.force_subscriptions === true;\n  const included = isIncludedGroup(group);\n  const result: GroupSelection = {};\n  for (const item of group.bundle_group_items) {\n    if (item.available === false) continue;\n    if (!included && item.is_default !== true) continue;\n    const subscribe = forceGroup || item.force_subscription === true;\n    result[item.variant_id] = {\n      quantity: clampQuantity(item, item.quantity),\n      subscribe,\n      subscriptionPlanId: subscribe ? defaultItemPlanId(item) : null,\n    };\n  }\n  return result;\n}\n\n/**\n * The \"whole group is chosen\" selection used when a mutually-exclusive branch\n * is picked as a unit (the \"Choose this group\" affordance): every available\n * item of an included group; the `is_default` items of a customizable group,\n * or its first available item when none are marked default (so choosing the\n * branch always activates it). Unavailable items are skipped — they can't be\n * purchased. Mirrors the storefront theme's exclusive-branch fill rules.\n */\nexport function getWholeGroupSelections(\n  group: portalProducts.BundleGroup,\n): GroupSelection {\n  const forceGroup = group.force_subscriptions === true;\n  const available = group.bundle_group_items.filter(\n    (item) => item.available !== false,\n  );\n  let source: portalProducts.BundleGroupItem[];\n  if (isIncludedGroup(group)) {\n    source = available;\n  } else {\n    const defaults = available.filter((item) => item.is_default === true);\n    source = defaults.length > 0 ? defaults : available.slice(0, 1);\n  }\n  const result: GroupSelection = {};\n  for (const item of source) {\n    const subscribe = forceGroup || item.force_subscription === true;\n    result[item.variant_id] = {\n      quantity: clampQuantity(item, item.quantity) || 1,\n      subscribe,\n      subscriptionPlanId: subscribe ? defaultItemPlanId(item) : null,\n    };\n  }\n  return result;\n}\n\n/** Total selected quantity (summed units) in a group. */\nexport function selectionCount(selection?: GroupSelection): number {\n  if (!selection) return 0;\n  return Object.values(selection).reduce(\n    (sum, s) => sum + Math.max(0, s.quantity),\n    0,\n  );\n}\n\nfunction exactTarget(group: portalProducts.BundleGroup): number {\n  return group.min_selections ?? group.max_selections ?? 0;\n}\n\n/** Maximum total quantity a group accepts, or `Infinity` when unbounded. */\nexport function groupCapacity(group: portalProducts.BundleGroup): number {\n  if (isIncludedGroup(group)) return Infinity;\n  switch (getSelectionType(group)) {\n    case \"exact\":\n      return exactTarget(group);\n    case \"max_only\":\n    case \"min_and_max\":\n      return group.max_selections ?? Infinity;\n    case \"min_only\":\n    default:\n      return Infinity;\n  }\n}\n\n/** Whether the group's selection rule is currently satisfied. Included groups always are. */\nexport function isGroupComplete(\n  group: portalProducts.BundleGroup,\n  count: number,\n): boolean {\n  if (isIncludedGroup(group)) return true;\n  switch (getSelectionType(group)) {\n    case \"exact\":\n      return count === exactTarget(group);\n    case \"min_only\":\n      return count >= (group.min_selections ?? 0);\n    case \"max_only\":\n      return count <= (group.max_selections ?? Infinity);\n    case \"min_and_max\":\n      return (\n        count >= (group.min_selections ?? 0) &&\n        count <= (group.max_selections ?? Infinity)\n      );\n    default:\n      // Unknown selection type: don't block rendering or add-to-cart gating.\n      return true;\n  }\n}\n\n/** Whether another item may still be added to the group given the current count. */\nexport function canAddMoreToGroup(\n  group: portalProducts.BundleGroup,\n  count: number,\n): boolean {\n  if (isIncludedGroup(group)) return false;\n  switch (getSelectionType(group)) {\n    case \"exact\":\n      return count < exactTarget(group);\n    case \"max_only\":\n    case \"min_and_max\":\n      return count < (group.max_selections ?? Infinity);\n    case \"min_only\":\n      return true;\n    default:\n      return true;\n  }\n}\n\n/** A group that allows at most one selection (renders as a radio, replaces on pick). */\nexport function isSingleSelectGroup(\n  group: portalProducts.BundleGroup,\n): boolean {\n  if (!isCustomizableGroup(group)) return false;\n  if (group.max_selections === 1) return true;\n  return getSelectionType(group) === \"exact\" && exactTarget(group) === 1;\n}\n\n/**\n * Normalize `bundle_config.mutually_exclusive_groups` to plain arrays of group\n * `sort_order` values, accepting both the bare-pair (legacy) and `{ ids }`\n * object forms. Malformed / empty entries are dropped.\n */\nexport function resolveExclusiveSets(\n  config: portalProducts.BundleConfig | null | undefined,\n): number[][] {\n  const raw = config?.mutually_exclusive_groups;\n  if (!Array.isArray(raw)) return [];\n  const sets: number[][] = [];\n  for (const entry of raw) {\n    if (Array.isArray(entry)) {\n      if (entry.length) sets.push([...entry]);\n    } else if (entry && Array.isArray(entry.ids) && entry.ids.length) {\n      sets.push([...entry.ids]);\n    }\n  }\n  return sets;\n}\n\n/**\n * Collect the admin-declared \"default branch\" `sort_order` values from\n * `bundle_config.mutually_exclusive_groups` — the object-form entries pin which\n * member of an exclusive set should be pre-selected. Bare-pair (legacy) entries\n * carry no default and are ignored. Consumed by `initBundleSelection` to seed\n * the declared branch instead of falling back to the lowest `sort_order`.\n */\nexport function resolveExclusiveDefaults(\n  config: portalProducts.BundleConfig | null | undefined,\n): Set<number> {\n  const defaults = new Set<number>();\n  const raw = config?.mutually_exclusive_groups;\n  if (!Array.isArray(raw)) return defaults;\n  for (const entry of raw) {\n    if (!Array.isArray(entry) && entry && entry.default != null) {\n      defaults.add(entry.default);\n    }\n  }\n  return defaults;\n}\n","import type { portalProducts } from \"../portal-products-api\";\nimport {\n  clampQuantity,\n  defaultItemPlanId,\n  getDefaultSelectionsForGroup,\n  getWholeGroupSelections,\n  groupCapacity,\n  isGroupComplete,\n  isIncludedGroup,\n  isSingleSelectGroup,\n  selectionCount,\n  type BundleItemSelection,\n  type GroupSelection,\n} from \"./bundle-groups\";\n\nexport type { BundleItemSelection, GroupSelection } from \"./bundle-groups\";\n\n/** Full selection state: `groupId -> variantId -> selection`. */\nexport type BundleSelectionState = Record<number, GroupSelection>;\n\nexport type BundleSelectionAction =\n  | { type: \"toggleItem\"; groupId: number; variantId: number }\n  | {\n      type: \"setQuantity\";\n      groupId: number;\n      variantId: number;\n      quantity: number;\n    }\n  | {\n      type: \"setSubscribe\";\n      groupId: number;\n      variantId: number;\n      subscribe: boolean;\n    }\n  | {\n      type: \"setItemSubscriptionPlan\";\n      groupId: number;\n      variantId: number;\n      planId: number | null;\n    }\n  // Choose a whole group as the active branch of its mutually-exclusive set.\n  | { type: \"selectGroup\"; groupId: number };\n\nexport interface BundleSelectionContext {\n  groups: portalProducts.BundleGroup[];\n  /** Exclusive sets as group `sort_order` values (see `resolveExclusiveSets`). */\n  exclusiveSets: number[][];\n}\n\n/** The flat payload shape the cart-add endpoint accepts (CURRENT-1894). */\nexport interface BundleCartItem {\n  variant_id: number;\n  quantity: number;\n  subscription: boolean;\n  /** Chosen subscription plan id when subscribed, else null. */\n  subscription_plan_id: number | null;\n  /**\n   * The group this selection belongs to. The backend maps each bundled item to\n   * its `ProductBundleGroup` by this id to apply the group's price (and validate\n   * the group's selection rule); without it the backend falls back to an\n   * ambiguous variant-only lookup that mis-prices (raw variant price) and\n   * mis-counts a variant that appears in more than one group.\n   */\n  product_bundle_group_id: number;\n}\n\nexport function initBundleSelection(\n  groups: portalProducts.BundleGroup[],\n  exclusiveSets: number[][] = [],\n  defaultBranches: Set<number> = new Set(),\n): BundleSelectionState {\n  const state: BundleSelectionState = {};\n  for (const group of groups) {\n    state[group.id] = getDefaultSelectionsForGroup(group);\n  }\n  // Enforce mutual exclusion on the seeded defaults: within each exclusive set\n  // at most one group may start with a selection. When admin data seeds more\n  // than one member, keep a single branch and clear the rest so the initial\n  // state never violates the invariant the reducer maintains. Prefer the\n  // admin-declared default branch (`defaultBranches`, from the exclusive set's\n  // `default` sort_order); fall back to the lowest-sort_order seeded member\n  // when no default is declared or the declared branch was not itself seeded.\n  for (const set of exclusiveSets) {\n    const seeded = groups\n      .filter(\n        (group) =>\n          set.includes(group.sort_order) &&\n          Object.keys(state[group.id] ?? {}).length > 0,\n      )\n      .sort((a, b) => a.sort_order - b.sort_order);\n    const fallback = seeded[0];\n    if (seeded.length <= 1 || !fallback) continue;\n    const kept =\n      seeded.find((group) => defaultBranches.has(group.sort_order)) ?? fallback;\n    for (const group of seeded) {\n      if (group.id !== kept.id) state[group.id] = {};\n    }\n  }\n  return state;\n}\n\nfunction findGroup(\n  ctx: BundleSelectionContext,\n  groupId: number,\n): portalProducts.BundleGroup | undefined {\n  return ctx.groups.find((g) => g.id === groupId);\n}\n\nfunction findItem(\n  group: portalProducts.BundleGroup,\n  variantId: number,\n): portalProducts.BundleGroupItem | undefined {\n  return group.bundle_group_items.find((i) => i.variant_id === variantId);\n}\n\nfunction withGroup(\n  state: BundleSelectionState,\n  groupId: number,\n  selection: GroupSelection,\n): BundleSelectionState {\n  return { ...state, [groupId]: selection };\n}\n\nfunction omitVariant(\n  selection: GroupSelection,\n  variantId: number,\n): GroupSelection {\n  const rest: GroupSelection = {};\n  for (const [key, value] of Object.entries(selection)) {\n    if (Number(key) !== variantId) rest[Number(key)] = value;\n  }\n  return rest;\n}\n\n/** Clear the selections of any group that shares an exclusive set with `group`. */\nfunction clearExclusiveSiblings(\n  state: BundleSelectionState,\n  group: portalProducts.BundleGroup,\n  ctx: BundleSelectionContext,\n): BundleSelectionState {\n  const siblingSortOrders = new Set<number>();\n  for (const set of ctx.exclusiveSets) {\n    if (!set.includes(group.sort_order)) continue;\n    for (const sortOrder of set) {\n      if (sortOrder !== group.sort_order) siblingSortOrders.add(sortOrder);\n    }\n  }\n  if (siblingSortOrders.size === 0) return state;\n\n  let next = state;\n  for (const sibling of ctx.groups) {\n    if (!siblingSortOrders.has(sibling.sort_order)) continue;\n    const current = next[sibling.id];\n    if (current && Object.keys(current).length > 0) {\n      next = withGroup(next, sibling.id, {});\n    }\n  }\n  return next;\n}\n\nfunction seedSelection(\n  group: portalProducts.BundleGroup,\n  item: portalProducts.BundleGroupItem,\n): BundleItemSelection {\n  const subscribe =\n    group.force_subscriptions === true || item.force_subscription === true;\n  return {\n    quantity: clampQuantity(item, item.quantity) || 1,\n    subscribe,\n    subscriptionPlanId: subscribe ? defaultItemPlanId(item) : null,\n  };\n}\n\n/** Summed quantity of every item in the group except `variantId`. */\nfunction sumExcept(selection: GroupSelection, variantId: number): number {\n  let sum = 0;\n  for (const [key, value] of Object.entries(selection)) {\n    if (Number(key) !== variantId) sum += Math.max(0, value.quantity);\n  }\n  return sum;\n}\n\nexport function bundleSelectionReducer(\n  state: BundleSelectionState,\n  action: BundleSelectionAction,\n  ctx: BundleSelectionContext,\n): BundleSelectionState {\n  const group = findGroup(ctx, action.groupId);\n  if (!group) return state;\n\n  // Choosing a whole group as its exclusive set's active branch. Works for\n  // included groups (otherwise locked to item-level edits — this is the only\n  // way to re-pick a static branch after a sibling cleared it) and clears the\n  // sibling branches atomically so two branches can never be active at once.\n  if (action.type === \"selectGroup\") {\n    return clearExclusiveSiblings(\n      withGroup(state, action.groupId, getWholeGroupSelections(group)),\n      group,\n      ctx,\n    );\n  }\n\n  const item = findItem(group, action.variantId);\n  if (!item) return state; // unknown item ignored\n\n  const groupSelection = state[action.groupId] ?? {};\n\n  // Subscription flag/plan changes only tweak an already-selected item — allowed\n  // even for included (locked) groups, whose items are always present.\n  if (action.type === \"setSubscribe\") {\n    const existing = groupSelection[action.variantId];\n    if (!existing) return state;\n    // A forced item (group- or item-level) can't be un-subscribed by any\n    // caller — the row disables the toggle, but guard the action too so the\n    // cart payload can never carry subscription:false for a forced item.\n    const forced =\n      group.force_subscriptions === true || item.force_subscription === true;\n    if (!action.subscribe && forced) return state;\n    const subscriptionPlanId = action.subscribe\n      ? (existing.subscriptionPlanId ?? defaultItemPlanId(item))\n      : existing.subscriptionPlanId;\n    return withGroup(state, action.groupId, {\n      ...groupSelection,\n      [action.variantId]: {\n        ...existing,\n        subscribe: action.subscribe,\n        subscriptionPlanId,\n      },\n    });\n  }\n  if (action.type === \"setItemSubscriptionPlan\") {\n    const existing = groupSelection[action.variantId];\n    if (!existing) return state;\n    return withGroup(state, action.groupId, {\n      ...groupSelection,\n      [action.variantId]: { ...existing, subscriptionPlanId: action.planId },\n    });\n  }\n\n  // Remaining item-level actions add/remove/resize a pick — included groups are\n  // locked against those.\n  if (isIncludedGroup(group)) return state;\n\n  const isSelected = (groupSelection[action.variantId]?.quantity ?? 0) > 0;\n\n  switch (action.type) {\n    case \"toggleItem\": {\n      if (isSelected) {\n        return withGroup(\n          state,\n          action.groupId,\n          omitVariant(groupSelection, action.variantId),\n        );\n      }\n      const capacity = groupCapacity(group);\n      const seed = seedSelection(group, item);\n      let nextSelection: GroupSelection;\n      if (isSingleSelectGroup(group)) {\n        // Single-select replaces the group; clamp the unit to capacity.\n        nextSelection = {\n          [action.variantId]: {\n            ...seed,\n            quantity: Math.min(seed.quantity, capacity),\n          },\n        };\n      } else {\n        // Multi-select: only add what fits within the group's remaining total.\n        const room = capacity - sumExcept(groupSelection, action.variantId);\n        if (room <= 0) return state; // group is full — no-op\n        nextSelection = {\n          ...groupSelection,\n          [action.variantId]: {\n            ...seed,\n            quantity: Math.min(seed.quantity, room),\n          },\n        };\n      }\n      return clearExclusiveSiblings(\n        withGroup(state, action.groupId, nextSelection),\n        group,\n        ctx,\n      );\n    }\n\n    case \"setQuantity\": {\n      // Explicit zero (or below) removes the item; that's the only path to\n      // removal via quantity — a request that can't grow is clamped, not dropped.\n      if (action.quantity <= 0) {\n        return withGroup(\n          state,\n          action.groupId,\n          omitVariant(groupSelection, action.variantId),\n        );\n      }\n      // Selecting a new item in a single-select group replaces the group's\n      // pick (mirrors toggleItem), regardless of the current occupant — so it\n      // must run before the capacity guard below, which would otherwise no-op\n      // when the group is already full.\n      if (isSingleSelectGroup(group) && !isSelected) {\n        const quantity = Math.min(\n          clampQuantity(item, action.quantity),\n          groupCapacity(group),\n        );\n        if (quantity <= 0) return state;\n        return clearExclusiveSiblings(\n          withGroup(state, action.groupId, {\n            [action.variantId]: { ...seedSelection(group, item), quantity },\n          }),\n          group,\n          ctx,\n        );\n      }\n\n      const room =\n        groupCapacity(group) - sumExcept(groupSelection, action.variantId);\n      const quantity = Math.min(clampQuantity(item, action.quantity), room);\n      if (quantity <= 0) return state; // no group capacity left — no-op\n      const existing = groupSelection[action.variantId];\n      const next = withGroup(state, action.groupId, {\n        ...groupSelection,\n        [action.variantId]: {\n          ...(existing ?? seedSelection(group, item)),\n          quantity,\n        },\n      });\n      // A newly added item in an exclusive-set group clears its siblings, same\n      // as toggleItem; adjusting an already-selected item leaves them be.\n      return isSelected ? next : clearExclusiveSiblings(next, group, ctx);\n    }\n\n    default:\n      return state;\n  }\n}\n\nexport function selectedCountForGroup(\n  state: BundleSelectionState,\n  groupId: number,\n): number {\n  return selectionCount(state[groupId]);\n}\n\nexport function getItemSelection(\n  state: BundleSelectionState,\n  groupId: number,\n  variantId: number,\n): BundleItemSelection | undefined {\n  return state[groupId]?.[variantId];\n}\n\n/**\n * Whether a group's items may be tagged with their `product_bundle_group_id` in\n * the cart-add payload. Mirrors the backend's `taggable_group_ids`\n * (cart_bundle_validator.rb): a group is taggable only if it's customizable, or\n * it's included AND a member of a mutually-exclusive set. Pure-static included\n * groups are NOT taggable — the server reconstitutes their fixed contents from\n * the bundle definition and rejects client-tagged items for them with a 422.\n */\nfunction isGroupTaggable(\n  group: portalProducts.BundleGroup,\n  exclusiveSets: number[][],\n): boolean {\n  if (!isIncludedGroup(group)) return true;\n  return exclusiveSets.some((set) => set.includes(group.sort_order));\n}\n\n/**\n * Flatten the selection state to the cart-add payload (items with quantity > 0).\n *\n * `groups`/`exclusiveSets` let us drop items belonging to pure-static included\n * groups (see `isGroupTaggable`): the backend builds those itself and 422s on\n * any client-supplied entry tagged with their id. When a group isn't found in\n * `groups` (callers that pass none), the item is kept — only positively\n * identified pure-static included groups are filtered out.\n */\nexport function toCartItems(\n  state: BundleSelectionState,\n  groups: portalProducts.BundleGroup[] = [],\n  exclusiveSets: number[][] = [],\n): BundleCartItem[] {\n  // One entry per (group, variant), each tagged with its product_bundle_group_id.\n  // We do NOT dedupe across groups: the same variant selected in two groups is\n  // two distinct group selections, and the group id is exactly what lets the\n  // backend price + validate each against the right group. (Collapsing by\n  // variant would throw that group identity away.)\n  const groupsById = new Map(groups.map((g) => [g.id, g]));\n  const items: BundleCartItem[] = [];\n  for (const [groupId, groupSelection] of Object.entries(state)) {\n    const group = groupsById.get(Number(groupId));\n    if (group && !isGroupTaggable(group, exclusiveSets)) continue;\n    for (const [variantId, selection] of Object.entries(groupSelection)) {\n      if (selection.quantity <= 0) continue;\n      items.push({\n        variant_id: Number(variantId),\n        quantity: selection.quantity,\n        subscription: selection.subscribe,\n        subscription_plan_id: selection.subscribe\n          ? selection.subscriptionPlanId\n          : null,\n        product_bundle_group_id: Number(groupId),\n      });\n    }\n  }\n  return items;\n}\n\n/**\n * Whether every group's selection rule is satisfied for add-to-cart. A group\n * in a mutually-exclusive set is exempt from its own min/exact requirement when\n * a sibling in the same set has a selection: selecting one branch clears the\n * others, so the non-chosen branches are intentionally empty and must not block\n * completion (otherwise an exclusive bundle could never be added to cart).\n */\nexport function isSelectionComplete(\n  groups: portalProducts.BundleGroup[],\n  state: BundleSelectionState,\n  exclusiveSets: number[][] = [],\n): boolean {\n  return groups.every((group) => {\n    if (isGroupComplete(group, selectedCountForGroup(state, group.id))) {\n      return true;\n    }\n    return exclusiveSets.some(\n      (set) =>\n        set.includes(group.sort_order) &&\n        groups.some(\n          (sibling) =>\n            sibling.sort_order !== group.sort_order &&\n            set.includes(sibling.sort_order) &&\n            selectedCountForGroup(state, sibling.id) > 0,\n        ),\n    );\n  });\n}\n","import { useCallback, useMemo, useReducer } from \"react\";\nimport type { portalProducts } from \"../portal-products-api\";\nimport {\n  canAddMoreToGroup,\n  isGroupComplete,\n  resolveExclusiveDefaults,\n  resolveExclusiveSets,\n} from \"../utils/bundle-groups\";\nimport {\n  bundleSelectionReducer,\n  getItemSelection,\n  initBundleSelection,\n  isSelectionComplete,\n  selectedCountForGroup,\n  toCartItems,\n  type BundleCartItem,\n  type BundleSelectionAction,\n  type BundleSelectionState,\n} from \"../utils/bundle-selection\";\n\nexport interface UseBundleSelectorParams {\n  groups: portalProducts.BundleGroup[];\n  bundleConfig?: portalProducts.BundleConfig | null;\n}\n\nexport interface GroupSelectionStatus {\n  /** Number of distinct selected items in the group. */\n  count: number;\n  /** Whether the group's selection rule is currently satisfied. */\n  isComplete: boolean;\n  /** Whether another item may still be added. */\n  canAddMore: boolean;\n}\n\nexport interface UseBundleSelectorResult {\n  state: BundleSelectionState;\n  toggleItem: (groupId: number, variantId: number) => void;\n  setQuantity: (groupId: number, variantId: number, quantity: number) => void;\n  setSubscribe: (\n    groupId: number,\n    variantId: number,\n    subscribe: boolean,\n  ) => void;\n  /** Choose a whole group as the active branch of its mutually-exclusive set. */\n  selectGroup: (groupId: number) => void;\n  /** Set the chosen subscription plan for a selected item. */\n  setItemSubscriptionPlan: (\n    groupId: number,\n    variantId: number,\n    planId: number | null,\n  ) => void;\n  /** The chosen subscription plan id for an item, or null. */\n  getSubscriptionPlanId: (groupId: number, variantId: number) => number | null;\n  isSelected: (groupId: number, variantId: number) => boolean;\n  getQuantity: (groupId: number, variantId: number) => number;\n  getSubscribe: (groupId: number, variantId: number) => boolean;\n  groupStatus: (group: portalProducts.BundleGroup) => GroupSelectionStatus;\n  /** Flat cart-add payload for the current selections (CURRENT-1894). */\n  cartItems: BundleCartItem[];\n  /** Whether every group satisfies its selection rule. */\n  allComplete: boolean;\n}\n\n/**\n * Ephemeral per-screen selection state for a dynamic bundle. Seeds from admin\n * defaults, enforces per-group selection rules + per-item caps + mutual\n * exclusion, and exposes the flat cart payload.\n *\n * Selections initialize once from `groups`, so key the consuming component by\n * product id — switching products should remount it with fresh defaults.\n */\nexport function useBundleSelector({\n  groups,\n  bundleConfig,\n}: UseBundleSelectorParams): UseBundleSelectorResult {\n  const ctx = useMemo(\n    () => ({\n      groups,\n      exclusiveSets: resolveExclusiveSets(bundleConfig),\n      exclusiveDefaults: resolveExclusiveDefaults(bundleConfig),\n    }),\n    [groups, bundleConfig],\n  );\n\n  const [state, dispatch] = useReducer(\n    (current: BundleSelectionState, action: BundleSelectionAction) =>\n      bundleSelectionReducer(current, action, ctx),\n    groups,\n    (initialGroups) =>\n      initBundleSelection(\n        initialGroups,\n        ctx.exclusiveSets,\n        ctx.exclusiveDefaults,\n      ),\n  );\n\n  const toggleItem = useCallback(\n    (groupId: number, variantId: number) =>\n      dispatch({ type: \"toggleItem\", groupId, variantId }),\n    [],\n  );\n  const setQuantity = useCallback(\n    (groupId: number, variantId: number, quantity: number) =>\n      dispatch({ type: \"setQuantity\", groupId, variantId, quantity }),\n    [],\n  );\n  const setSubscribe = useCallback(\n    (groupId: number, variantId: number, subscribe: boolean) =>\n      dispatch({ type: \"setSubscribe\", groupId, variantId, subscribe }),\n    [],\n  );\n  const selectGroup = useCallback(\n    (groupId: number) => dispatch({ type: \"selectGroup\", groupId }),\n    [],\n  );\n  const setItemSubscriptionPlan = useCallback(\n    (groupId: number, variantId: number, planId: number | null) =>\n      dispatch({ type: \"setItemSubscriptionPlan\", groupId, variantId, planId }),\n    [],\n  );\n\n  const isSelected = useCallback(\n    (groupId: number, variantId: number) =>\n      (getItemSelection(state, groupId, variantId)?.quantity ?? 0) > 0,\n    [state],\n  );\n  const getQuantity = useCallback(\n    (groupId: number, variantId: number) =>\n      getItemSelection(state, groupId, variantId)?.quantity ?? 0,\n    [state],\n  );\n  const getSubscribe = useCallback(\n    (groupId: number, variantId: number) =>\n      getItemSelection(state, groupId, variantId)?.subscribe ?? false,\n    [state],\n  );\n  const getSubscriptionPlanId = useCallback(\n    (groupId: number, variantId: number) =>\n      getItemSelection(state, groupId, variantId)?.subscriptionPlanId ?? null,\n    [state],\n  );\n\n  const groupStatus = useCallback(\n    (group: portalProducts.BundleGroup): GroupSelectionStatus => {\n      const count = selectedCountForGroup(state, group.id);\n      return {\n        count,\n        isComplete: isGroupComplete(group, count),\n        canAddMore: canAddMoreToGroup(group, count),\n      };\n    },\n    [state],\n  );\n\n  const cartItems = useMemo(\n    () => toCartItems(state, groups, ctx.exclusiveSets),\n    [state, groups, ctx.exclusiveSets],\n  );\n  const allComplete = useMemo(\n    () => isSelectionComplete(groups, state, ctx.exclusiveSets),\n    [groups, state, ctx.exclusiveSets],\n  );\n\n  return {\n    state,\n    toggleItem,\n    setQuantity,\n    setSubscribe,\n    selectGroup,\n    setItemSubscriptionPlan,\n    getSubscriptionPlanId,\n    isSelected,\n    getQuantity,\n    getSubscribe,\n    groupStatus,\n    cartItems,\n    allComplete,\n  };\n}\n","import type { portalProducts } from \"../portal-products-api\";\nimport { bundleSubscriptionPrice } from \"./bundle-subscription-price\";\nimport { isIncludedGroup } from \"./bundle-groups\";\nimport {\n  selectedCountForGroup,\n  type BundleSelectionState,\n} from \"./bundle-selection\";\n\n/**\n * Bundle pricing for the dynamic-bundle configurator (CURRENT-1834 / -2655).\n *\n * Two modes, detected from an explicit API signal — never inferred from whether\n * a per-item `price` happens to be positive (a bundle-level bundle may still\n * expose positive per-item prices, so that inference would misprice it):\n * - **bundle-level** — `product.price_range` is present and no group overrides\n *   with its own `price`; the range is authoritative and the total is fixed\n *   regardless of selection. Recurring derives from the selected plan\n *   adjustment (same rule as static bundles).\n * - **group-/item-level** — `product.price_range` is absent, or a group carries\n *   its own `price`. The total sums the *active* groups (included groups always;\n *   a customizable group once it has a selection — which, thanks to the\n *   reducer's mutual-exclusion clearing, counts only the chosen branch of an\n *   exclusive set). A group with a fixed `price` contributes that price; a group\n *   without one contributes the sum of its selected items' prices, each priced\n *   at the item's own subscribe choice (CURRENT-2655). Recurring and earned\n *   CV/QV sum the same active groups.\n *\n * All group/product money + volume values are already tier- and country-\n * resolved server-side.\n */\n\nexport type BundlePricingMode = \"bundle\" | \"group\";\n\nexport interface BundleTotals {\n  mode: BundlePricingMode;\n  /** One-off total in the shopper's currency, or undefined when unresolved. */\n  oneOff: number | undefined;\n  /** Recurring (subscription) total, or undefined when the bundle has none. */\n  recurring: number | undefined;\n  /** Earned CV for the current configuration, or null when not applicable. */\n  cv: number | null;\n  /** Earned QV for the current configuration, or null when not applicable. */\n  qv: number | null;\n}\n\ntype SubscriptionPlanAdjustment = {\n  price_adjustment_type?: \"percentage\" | \"fixed_amount\" | null;\n  price_adjustment_amount?: string | null;\n};\n\nfunction toNumber(value: string | null | undefined): number | undefined {\n  if (value == null) return undefined;\n  const n = Number(value);\n  return Number.isFinite(n) ? n : undefined;\n}\n\n/**\n * The per-item subscription price for the chosen plan. A `percentage` plan\n * discounts the item's base `price`; `fixed_amount` (or an unknown/legacy type)\n * uses the item's server-resolved `subscription_price`, falling back to base.\n * Mirrors the contract's stated pricing rule.\n */\nfunction itemSubscriptionPrice(\n  item: portalProducts.BundleGroupItem,\n  planId: number | null,\n): number {\n  const base = toNumber(item.price) ?? 0;\n  const plan =\n    planId != null\n      ? item.subscription_plans.find((p) => p.id === planId)\n      : undefined;\n  if (plan?.price_adjustment_type === \"percentage\") {\n    const amount = toNumber(plan.price_adjustment_amount) ?? 0;\n    return Math.max(0, base * (1 - amount / 100));\n  }\n  return toNumber(item.subscription_price) ?? base;\n}\n\nfunction isActiveGroup(\n  group: portalProducts.BundleGroup,\n  state: BundleSelectionState,\n): boolean {\n  return isIncludedGroup(group) || selectedCountForGroup(state, group.id) > 0;\n}\n\n/**\n * Whether pricing comes from the groups/items rather than a fixed bundle-level\n * range. True when the product has no authoritative `price_range`, or a group\n * carries its own `price`. Keyed on this explicit signal — NOT on whether a\n * per-item `price` is positive — so a bundle-level bundle that still exposes\n * positive per-item prices is not misread as item-level (Shad review, #2).\n *\n * The UI uses this to decide where a per-item subscribe toggle actually moves\n * the Total: only in a `price`-less group of a group-/item-level bundle.\n */\nexport function isPerItemOrGroupPriced(\n  product: portalProducts.Product,\n  groups: portalProducts.BundleGroup[],\n): boolean {\n  return product.price_range == null || groups.some((g) => g.price != null);\n}\n\nexport function computeBundleTotals(\n  product: portalProducts.Product,\n  groups: portalProducts.BundleGroup[],\n  state: BundleSelectionState,\n  subscriptionPlan?: SubscriptionPlanAdjustment | null,\n  /**\n   * Whether to sum the bundle's volume. Callers that will not render CV/QV pass\n   * false so the summation never runs against suppressed values — summing\n   * blanked group volumes yields a 0, which reads as a real figure rather than\n   * an absent one. Defaults to true: the price half is unaffected either way.\n   */\n  includeVolume = true,\n): BundleTotals {\n  if (!isPerItemOrGroupPriced(product, groups)) {\n    const oneOff = toNumber(product.price_range?.min);\n    return {\n      mode: \"bundle\",\n      oneOff,\n      recurring: bundleSubscriptionPrice(\n        oneOff,\n        product.subscription_price_range,\n        subscriptionPlan,\n      ),\n      cv: includeVolume ? (toNumber(product.cv) ?? null) : null,\n      qv: includeVolume ? (toNumber(product.qv) ?? null) : null,\n    };\n  }\n\n  const active = groups.filter((g) => isActiveGroup(g, state));\n\n  // `total` reflects each item's own subscribe choice (subscribed items priced\n  // at their plan price); `recurring` is the whole-bundle-subscribed variant\n  // the bundle-level toggle shows. cv/qv stay a group-level concern.\n  let total = 0;\n  let recurring = 0;\n  let hasRecurring = false;\n  let cv = 0;\n  let qv = 0;\n  let hasVolume = false;\n\n  for (const group of active) {\n    if (group.price != null) {\n      const base = toNumber(group.price) ?? 0;\n      const sub = toNumber(group.subscription_price);\n      total += base;\n      recurring += sub ?? base;\n      if (sub != null) hasRecurring = true;\n    } else {\n      const selection = state[group.id] ?? {};\n      for (const item of group.bundle_group_items) {\n        const picked = selection[item.variant_id];\n        if (!picked || picked.quantity <= 0) continue;\n        const base = toNumber(item.price) ?? 0;\n        const subPrice = itemSubscriptionPrice(item, picked.subscriptionPlanId);\n        total += (picked.subscribe ? subPrice : base) * picked.quantity;\n        recurring += subPrice * picked.quantity;\n        if (\n          item.has_subscription_plans ||\n          item.subscription_plans.length > 0 ||\n          item.subscription_price != null\n        ) {\n          hasRecurring = true;\n        }\n      }\n    }\n    if (includeVolume) {\n      const groupCv = toNumber(group.cv);\n      if (groupCv != null) {\n        cv += groupCv;\n        hasVolume = true;\n      }\n      const groupQv = toNumber(group.qv);\n      if (groupQv != null) {\n        qv += groupQv;\n        hasVolume = true;\n      }\n    }\n  }\n\n  return {\n    mode: \"group\",\n    oneOff: total,\n    recurring: hasRecurring ? recurring : undefined,\n    cv: hasVolume ? cv : null,\n    qv: hasVolume ? qv : null,\n  };\n}\n","import type React from \"react\";\nimport { useState } from \"react\";\nimport { ArrowUpDown, Check, Search, X } from \"lucide-react\";\nimport { Input } from \"@fluid-app/ui-primitives\";\nimport { Button } from \"@fluid-app/ui-primitives\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuTrigger,\n} from \"@fluid-app/ui-primitives\";\nimport { MobileActionSheet } from \"./MobileActionSheet\";\nimport type { ResponsiveDialogPresentation } from \"./ResponsiveDialog\";\n\ntype SortOption = {\n  label: string;\n  value: string;\n};\n\ntype BaseSearchProps = {\n  /** Current search value (controlled). */\n  searchValue: string;\n  /** Called on every keystroke with the new value. */\n  onSearchChange: (value: string) => void;\n  /** Placeholder text for the search input. */\n  placeholder?: string;\n  /** Use the native-feeling action sheet for sort choices on mobile. */\n  presentation?: ResponsiveDialogPresentation;\n  /** Accessible label and mobile sheet title for the sort control. */\n  sortLabel?: string;\n  /**\n   * Accessible label for the button that clears the search.\n   *\n   * The button shows an icon and no words, so this is all a screen reader has to\n   * read. Pass a translated string; the English default is a fallback for callers\n   * with no translator, not the intended wording in a localized app.\n   */\n  clearLabel?: string;\n};\n\ntype SearchOnlyProps = BaseSearchProps & {\n  sortOptions?: never;\n  sortValue?: never;\n  onSortChange?: never;\n};\n\ntype WithSortProps = BaseSearchProps & {\n  /** Sort options to display in the dropdown. */\n  sortOptions: ReadonlyArray<SortOption>;\n  /** Currently selected sort value. */\n  sortValue: string;\n  /** Called when a sort option is selected. */\n  onSortChange: (value: string) => void;\n};\n\ntype SearchSortProps = SearchOnlyProps | WithSortProps;\n\nexport type {\n  SortOption,\n  BaseSearchProps,\n  SearchOnlyProps,\n  WithSortProps,\n  SearchSortProps,\n};\n\nexport function SearchSort({\n  searchValue,\n  onSearchChange,\n  placeholder = \"Search...\",\n  sortOptions,\n  sortValue,\n  onSortChange,\n  presentation = \"dialog\",\n  sortLabel = \"Sort options\",\n  clearLabel = \"Clear search\",\n}: SearchSortProps): React.JSX.Element {\n  const [sortOpen, setSortOpen] = useState(false);\n  const activeSort = sortOptions?.find((o) => o.value === sortValue);\n\n  return (\n    <div className=\"flex items-center gap-2\">\n      <div className=\"relative min-w-0 flex-1\">\n        <Search className=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\" />\n        <Input\n          type=\"search\"\n          value={searchValue}\n          onChange={(e) => onSearchChange(e.target.value)}\n          placeholder={placeholder}\n          className=\"pl-9 [&::-webkit-search-cancel-button]:hidden\"\n        />\n        {searchValue.length > 0 && (\n          <Button\n            variant=\"ghost\"\n            size=\"icon-xs\"\n            onClick={() => onSearchChange(\"\")}\n            aria-label={clearLabel}\n            className=\"absolute top-1/2 right-2 -translate-y-1/2\"\n          >\n            <X className=\"size-3\" />\n          </Button>\n        )}\n      </div>\n      {sortOptions &&\n        sortOptions.length > 0 &&\n        (presentation === \"bottom-sheet\" ? (\n          <>\n            <Button\n              variant=\"outline\"\n              aria-label={\n                activeSort ? `${sortLabel}: ${activeSort.label}` : sortLabel\n              }\n              onClick={() => setSortOpen(true)}\n            >\n              <ArrowUpDown className=\"size-4\" />\n              {activeSort && (\n                <span className=\"hidden sm:inline\">{activeSort.label}</span>\n              )}\n            </Button>\n            <MobileActionSheet\n              open={sortOpen}\n              onOpenChange={setSortOpen}\n              title={sortLabel}\n              actions={sortOptions.map((option) => {\n                const isActive = option.value === sortValue;\n                return {\n                  id: option.value,\n                  label: option.label,\n                  selected: isActive,\n                  onSelect: () => onSortChange(option.value),\n                };\n              })}\n            />\n          </>\n        ) : (\n          <DropdownMenu>\n            <DropdownMenuTrigger asChild>\n              <Button\n                variant=\"outline\"\n                aria-label={\n                  activeSort ? `${sortLabel}: ${activeSort.label}` : sortLabel\n                }\n              >\n                <ArrowUpDown className=\"size-4\" />\n                {activeSort && (\n                  <span className=\"hidden sm:inline\">{activeSort.label}</span>\n                )}\n              </Button>\n            </DropdownMenuTrigger>\n            <DropdownMenuContent align=\"end\">\n              {sortOptions.map((option) => {\n                const isActive = option.value === sortValue;\n                return (\n                  <DropdownMenuItem\n                    key={option.value}\n                    onClick={() => onSortChange(option.value)}\n                  >\n                    <span className=\"flex-1\">{option.label}</span>\n                    {isActive && (\n                      <Check className=\"text-muted-foreground size-4\" />\n                    )}\n                  </DropdownMenuItem>\n                );\n              })}\n            </DropdownMenuContent>\n          </DropdownMenu>\n        ))}\n    </div>\n  );\n}\n"],"mappings":"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"}