@propeller-commerce/propeller-v2-react-ui 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -40,6 +40,14 @@ interface PropellerDeps {
40
40
  interface PropellerScope {
41
41
  /** The authenticated user, or `null` when browsing anonymously. */
42
42
  user: Contact | Customer | null;
43
+ /**
44
+ * Whether a session exists, independent of whether `user` has loaded yet.
45
+ * Hosts paint from a cached hint before the profile arrives, so `user` is
46
+ * null for an authenticated visitor for the first frames; without this,
47
+ * semi-closed surfaces flash their logged-out state. Optional — omitting it
48
+ * keeps the previous behaviour.
49
+ */
50
+ isAuthenticated?: boolean;
43
51
  /** Active company ID for the current session; `undefined` for non-company users. */
44
52
  companyId: number | undefined;
45
53
  /** Active language code (e.g. `'NL'`). */
@@ -588,7 +596,11 @@ interface UseCartReturn {
588
596
  loading: boolean;
589
597
  /** Last error message, or `null`. */
590
598
  error: string | null;
591
- /** `false` when a B2B purchaser's authorization limit is exceeded by the cart total. */
599
+ /**
600
+ * `false` when a B2B purchaser's authorization limit is exceeded by the cart
601
+ * total, and while a seeded `cartId` is still being hydrated — it fails
602
+ * closed, so gating a checkout button on it holds rather than opens.
603
+ */
592
604
  checkoutAllowed: boolean;
593
605
  /** Resolves an existing cart or creates one via the shared `initCart` flow. */
594
606
  resolveCart: () => Promise<Cart>;
@@ -2181,8 +2193,13 @@ interface ResolveSpecEntry {
2181
2193
  */
2182
2194
  transform?: (gridValue: NonNullable<ProductGridConfig[GridKey]>) => unknown;
2183
2195
  }
2184
- /** Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}. */
2185
- type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2196
+ /**
2197
+ * Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}.
2198
+ *
2199
+ * Infra keys are spec'able too, so a component can pull a provider value it
2200
+ * only reads internally without having to publish it as its own prop.
2201
+ */
2202
+ type ResolveSpec<P> = Partial<Record<keyof P | keyof PropellerInfra, ResolveSpecEntry>>;
2186
2203
  /**
2187
2204
  * useResolvedProps — resolves props through the two-tier precedence
2188
2205
  * `explicit prop > ProductGrid context > Propeller infra > default`.
@@ -2191,7 +2208,7 @@ type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2191
2208
  * @param spec - per-key resolution rules; keys absent from the spec pass through unchanged.
2192
2209
  * @returns the props with each spec'd key resolved to its highest-precedence value.
2193
2210
  */
2194
- declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P;
2211
+ declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P & Partial<PropellerInfra>;
2195
2212
 
2196
2213
  /**
2197
2214
  * Read the SDK services bundle from `<PropellerDepsProvider>`.
@@ -3496,13 +3513,17 @@ interface CartOverviewProps {
3496
3513
  code: string;
3497
3514
  name: string;
3498
3515
  }[];
3516
+ /** Logged-in user — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3517
+ user?: Contact | Customer | null;
3518
+ /** Active company ID — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3519
+ companyId?: number;
3499
3520
  }
3500
3521
  /**
3501
3522
  * Final cart review panel: shows invoice/delivery addresses, payment, carrier
3502
3523
  * and delivery date, with optional reference, notes, terms acceptance and a
3503
3524
  * place-order button.
3504
3525
  */
3505
- declare function CartOverview(props: CartOverviewProps): React$1.JSX.Element;
3526
+ declare function CartOverview(rawProps: CartOverviewProps): React$1.JSX.Element;
3506
3527
 
3507
3528
  /**
3508
3529
  * @rsc-blocked — Client-only component: interactive state (useState/useReducer).
@@ -4195,10 +4216,11 @@ declare function ProductCardPrice(props: {
4195
4216
  /** Renders the embedded `AddToCart` control with all pass-through props from
4196
4217
  * context, or the injected `addToCartComponent` when set.
4197
4218
  *
4198
- * Note: the default `AddToCartImpl` accepts a richer prop set (graphqlClient,
4199
- * configuration, etc.) than the public `AddToCartComponentProps` contract.
4200
- * Injected components only receive the contract surface; the extra infra
4201
- * props stay default-only. */
4219
+ * Note: the default `AddToCartImpl` accepts a richer prop set than the public
4220
+ * `AddToCartComponentProps` contract. Injected components receive the whole
4221
+ * contract surface; what stays default-only is infra (graphqlClient, user,
4222
+ * configuration, …), which an injected component resolves from
4223
+ * `<PropellerProvider>` itself. */
4202
4224
  declare function ProductCardAddToCart(props: {
4203
4225
  /** Override the AddToCart wrapper class. */
4204
4226
  className?: string;
@@ -4549,6 +4571,8 @@ interface CompanySwitcherProps {
4549
4571
  /** Translated labels keyed by the slugs used inside the component (see
4550
4572
  * `getLabel` calls). Missing keys fall back to the English defaults. */
4551
4573
  labels?: Record<string, string>;
4574
+ /** Additional class name for the switcher's trigger button. */
4575
+ triggerClassName?: string;
4552
4576
  }
4553
4577
  /**
4554
4578
  * Dropdown for switching the active company of a multi-company contact.
@@ -6155,6 +6179,12 @@ interface ProductBulkPricesProps {
6155
6179
  * Defaults to 'open'.
6156
6180
  */
6157
6181
  portalMode?: string;
6182
+ /**
6183
+ * Whether a session exists, independent of whether `user` has loaded yet.
6184
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6185
+ * window in which an authenticated visitor still has a null `user`.
6186
+ */
6187
+ isAuthenticated?: boolean;
6158
6188
  /** Authenticated user — used for semi-closed visibility. */
6159
6189
  user?: Contact | Customer | null;
6160
6190
  /** Tax zone code. Defaults to 'NL'. */
@@ -6694,6 +6724,15 @@ interface ProductInfoProps {
6694
6724
  * Example: ['brand', 'color']
6695
6725
  */
6696
6726
  textLabels?: string[];
6727
+ /** Cart to add into. Omit and pass `createCart` to start one on first add. */
6728
+ cartId?: string;
6729
+ /** If true a new cart is created when no `cartId` is available. */
6730
+ createCart?: boolean;
6731
+ /**
6732
+ * Called when a new cart is created, so the host can persist `cart.cartId`.
6733
+ * WARNING: without it a new cart is created on every add.
6734
+ */
6735
+ onCartCreated?: (cart: Cart) => void;
6697
6736
  showImage?: boolean;
6698
6737
  showBadges?: boolean;
6699
6738
  showFavorite?: boolean;
@@ -6837,6 +6876,12 @@ interface ProductPriceProps {
6837
6876
  * Defaults to 'open'.
6838
6877
  */
6839
6878
  portalMode?: string;
6879
+ /**
6880
+ * Whether a session exists, independent of whether `user` has loaded yet.
6881
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6882
+ * window in which an authenticated visitor still has a null `user`.
6883
+ */
6884
+ isAuthenticated?: boolean;
6840
6885
  /** Authenticated user — used for semi-closed visibility. */
6841
6886
  user?: Contact | Customer | null;
6842
6887
  /**
package/dist/index.d.ts CHANGED
@@ -40,6 +40,14 @@ interface PropellerDeps {
40
40
  interface PropellerScope {
41
41
  /** The authenticated user, or `null` when browsing anonymously. */
42
42
  user: Contact | Customer | null;
43
+ /**
44
+ * Whether a session exists, independent of whether `user` has loaded yet.
45
+ * Hosts paint from a cached hint before the profile arrives, so `user` is
46
+ * null for an authenticated visitor for the first frames; without this,
47
+ * semi-closed surfaces flash their logged-out state. Optional — omitting it
48
+ * keeps the previous behaviour.
49
+ */
50
+ isAuthenticated?: boolean;
43
51
  /** Active company ID for the current session; `undefined` for non-company users. */
44
52
  companyId: number | undefined;
45
53
  /** Active language code (e.g. `'NL'`). */
@@ -588,7 +596,11 @@ interface UseCartReturn {
588
596
  loading: boolean;
589
597
  /** Last error message, or `null`. */
590
598
  error: string | null;
591
- /** `false` when a B2B purchaser's authorization limit is exceeded by the cart total. */
599
+ /**
600
+ * `false` when a B2B purchaser's authorization limit is exceeded by the cart
601
+ * total, and while a seeded `cartId` is still being hydrated — it fails
602
+ * closed, so gating a checkout button on it holds rather than opens.
603
+ */
592
604
  checkoutAllowed: boolean;
593
605
  /** Resolves an existing cart or creates one via the shared `initCart` flow. */
594
606
  resolveCart: () => Promise<Cart>;
@@ -2181,8 +2193,13 @@ interface ResolveSpecEntry {
2181
2193
  */
2182
2194
  transform?: (gridValue: NonNullable<ProductGridConfig[GridKey]>) => unknown;
2183
2195
  }
2184
- /** Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}. */
2185
- type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2196
+ /**
2197
+ * Per-prop resolution spec: maps each prop key to its {@link ResolveSpecEntry}.
2198
+ *
2199
+ * Infra keys are spec'able too, so a component can pull a provider value it
2200
+ * only reads internally without having to publish it as its own prop.
2201
+ */
2202
+ type ResolveSpec<P> = Partial<Record<keyof P | keyof PropellerInfra, ResolveSpecEntry>>;
2186
2203
  /**
2187
2204
  * useResolvedProps — resolves props through the two-tier precedence
2188
2205
  * `explicit prop > ProductGrid context > Propeller infra > default`.
@@ -2191,7 +2208,7 @@ type ResolveSpec<P> = Partial<Record<keyof P, ResolveSpecEntry>>;
2191
2208
  * @param spec - per-key resolution rules; keys absent from the spec pass through unchanged.
2192
2209
  * @returns the props with each spec'd key resolved to its highest-precedence value.
2193
2210
  */
2194
- declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P;
2211
+ declare function useResolvedProps<P extends object>(rawProps: P, spec: ResolveSpec<P>): P & Partial<PropellerInfra>;
2195
2212
 
2196
2213
  /**
2197
2214
  * Read the SDK services bundle from `<PropellerDepsProvider>`.
@@ -3496,13 +3513,17 @@ interface CartOverviewProps {
3496
3513
  code: string;
3497
3514
  name: string;
3498
3515
  }[];
3516
+ /** Logged-in user — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3517
+ user?: Contact | Customer | null;
3518
+ /** Active company ID — used for the purchase-authorization check. Resolved from PropellerProvider when omitted. */
3519
+ companyId?: number;
3499
3520
  }
3500
3521
  /**
3501
3522
  * Final cart review panel: shows invoice/delivery addresses, payment, carrier
3502
3523
  * and delivery date, with optional reference, notes, terms acceptance and a
3503
3524
  * place-order button.
3504
3525
  */
3505
- declare function CartOverview(props: CartOverviewProps): React$1.JSX.Element;
3526
+ declare function CartOverview(rawProps: CartOverviewProps): React$1.JSX.Element;
3506
3527
 
3507
3528
  /**
3508
3529
  * @rsc-blocked — Client-only component: interactive state (useState/useReducer).
@@ -4195,10 +4216,11 @@ declare function ProductCardPrice(props: {
4195
4216
  /** Renders the embedded `AddToCart` control with all pass-through props from
4196
4217
  * context, or the injected `addToCartComponent` when set.
4197
4218
  *
4198
- * Note: the default `AddToCartImpl` accepts a richer prop set (graphqlClient,
4199
- * configuration, etc.) than the public `AddToCartComponentProps` contract.
4200
- * Injected components only receive the contract surface; the extra infra
4201
- * props stay default-only. */
4219
+ * Note: the default `AddToCartImpl` accepts a richer prop set than the public
4220
+ * `AddToCartComponentProps` contract. Injected components receive the whole
4221
+ * contract surface; what stays default-only is infra (graphqlClient, user,
4222
+ * configuration, …), which an injected component resolves from
4223
+ * `<PropellerProvider>` itself. */
4202
4224
  declare function ProductCardAddToCart(props: {
4203
4225
  /** Override the AddToCart wrapper class. */
4204
4226
  className?: string;
@@ -4549,6 +4571,8 @@ interface CompanySwitcherProps {
4549
4571
  /** Translated labels keyed by the slugs used inside the component (see
4550
4572
  * `getLabel` calls). Missing keys fall back to the English defaults. */
4551
4573
  labels?: Record<string, string>;
4574
+ /** Additional class name for the switcher's trigger button. */
4575
+ triggerClassName?: string;
4552
4576
  }
4553
4577
  /**
4554
4578
  * Dropdown for switching the active company of a multi-company contact.
@@ -6155,6 +6179,12 @@ interface ProductBulkPricesProps {
6155
6179
  * Defaults to 'open'.
6156
6180
  */
6157
6181
  portalMode?: string;
6182
+ /**
6183
+ * Whether a session exists, independent of whether `user` has loaded yet.
6184
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6185
+ * window in which an authenticated visitor still has a null `user`.
6186
+ */
6187
+ isAuthenticated?: boolean;
6158
6188
  /** Authenticated user — used for semi-closed visibility. */
6159
6189
  user?: Contact | Customer | null;
6160
6190
  /** Tax zone code. Defaults to 'NL'. */
@@ -6694,6 +6724,15 @@ interface ProductInfoProps {
6694
6724
  * Example: ['brand', 'color']
6695
6725
  */
6696
6726
  textLabels?: string[];
6727
+ /** Cart to add into. Omit and pass `createCart` to start one on first add. */
6728
+ cartId?: string;
6729
+ /** If true a new cart is created when no `cartId` is available. */
6730
+ createCart?: boolean;
6731
+ /**
6732
+ * Called when a new cart is created, so the host can persist `cart.cartId`.
6733
+ * WARNING: without it a new cart is created on every add.
6734
+ */
6735
+ onCartCreated?: (cart: Cart) => void;
6697
6736
  showImage?: boolean;
6698
6737
  showBadges?: boolean;
6699
6738
  showFavorite?: boolean;
@@ -6837,6 +6876,12 @@ interface ProductPriceProps {
6837
6876
  * Defaults to 'open'.
6838
6877
  */
6839
6878
  portalMode?: string;
6879
+ /**
6880
+ * Whether a session exists, independent of whether `user` has loaded yet.
6881
+ * Passed down by the parent alongside `portalMode`; closes the hydration
6882
+ * window in which an authenticated visitor still has a null `user`.
6883
+ */
6884
+ isAuthenticated?: boolean;
6840
6885
  /** Authenticated user — used for semi-closed visibility. */
6841
6886
  user?: Contact | Customer | null;
6842
6887
  /**
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use client";
2
- import { getLabel, getLocalizedValue, formatPrice, localeForLanguage, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, isCheckoutAllowed, createServices, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, buildInventoryFilter, findPurchaserPac, isOverAuthorizationLimit, MIN_STOCK_THRESHOLD, getNettedBonusItems, getLanguageString, collectAttributeValues, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
2
+ import { getLabel, getLocalizedValue, formatPrice, localeForLanguage, getClusterSku, getClusterImageUrl, isContentHidden, getProductSku, getProductImageUrl, createServices, isCheckoutAllowed, err, ok, formatSurcharge, deriveUserMode, isContact, isCustomer, getAddresses, attributeNameMatches, getAttributeDisplayName, filterProductsBySelections, extractAttributeValues, buildInventoryFilter, findPurchaserPac, isOverAuthorizationLimit, MIN_STOCK_THRESHOLD, getNettedBonusItems, getLanguageString, collectAttributeValues, getCountryName, resolveLanguageEntry, getLanguageUri } from '@propeller-commerce/propeller-v2-core-ui';
3
3
  export * from '@propeller-commerce/propeller-v2-core-ui';
4
4
  import * as React6 from 'react';
5
- import { createContext, useState, useRef, useCallback, useMemo, useContext, useEffect } from 'react';
5
+ import { createContext, useState, useRef, useCallback, useEffect, useMemo, useContext } from 'react';
6
6
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
7
  import { ProductStatus, ProductSortField, SortOrder, CrossupsellType, CartStatus, CartAddressType, Gender, YesNo, AddressType, MagicTokenService, PaymentStatuses, OrderSearchFields, OrderItemClass, ProductSearchableField, machineService, PurchaseRole, AttributeType, OrderSortField, OrderType, BundleCondition, OrderDiscountType } from '@propeller-commerce/propeller-sdk-v2';
8
8
  import { clsx } from 'clsx';
@@ -747,10 +747,29 @@ function useCart(options) {
747
747
  const [loading, setLoading] = useState(false);
748
748
  const [error, setError] = useState(null);
749
749
  const notesTimers = useRef({});
750
- const checkoutAllowed = useMemo(
751
- () => isCheckoutAllowed(user, companyId, cart),
752
- [user, companyId, cart]
753
- );
750
+ useEffect(() => {
751
+ if (!graphqlClient || !cartId || cart) return;
752
+ let cancelled = false;
753
+ (async () => {
754
+ try {
755
+ const fetched = await createServices(graphqlClient).cart.getCart({
756
+ cartId,
757
+ imageSearchFilters: imageSearchFilters(),
758
+ imageVariantFilters: imageVariantFilters(),
759
+ language
760
+ });
761
+ if (!cancelled && fetched) setCart(fetched);
762
+ } catch {
763
+ }
764
+ })();
765
+ return () => {
766
+ cancelled = true;
767
+ };
768
+ }, [graphqlClient, cartId, cart, language, imageSearchFilters, imageVariantFilters]);
769
+ const checkoutAllowed = useMemo(() => {
770
+ if (cartId && !cart) return false;
771
+ return isCheckoutAllowed(user, resolvedCompanyId, cart);
772
+ }, [user, resolvedCompanyId, cart, cartId]);
754
773
  function getMinQuantity(product) {
755
774
  const min = product?.minimumQuantity;
756
775
  return min && min > 0 ? min : 1;
@@ -1571,7 +1590,8 @@ var INFRA_KEYS = [
1571
1590
  "includeTax",
1572
1591
  "currency",
1573
1592
  "configuration",
1574
- "portalMode"
1593
+ "portalMode",
1594
+ "isAuthenticated"
1575
1595
  ];
1576
1596
  function useInfraProps(props) {
1577
1597
  const ctx = usePropellerContext();
@@ -2248,10 +2268,11 @@ function useProductSearch(options) {
2248
2268
  const isLoading = !isControlled && internalLoading;
2249
2269
  function filterByLanguage(products, lang) {
2250
2270
  if (!lang) return products;
2271
+ const target = lang.toUpperCase();
2251
2272
  return products.filter((p) => {
2252
2273
  const names = p.names || p.names || [];
2253
2274
  if (!names || names.length === 0) return true;
2254
- return names.some((n) => n.language === lang);
2275
+ return names.some((n) => (n.language || "").toUpperCase() === target);
2255
2276
  });
2256
2277
  }
2257
2278
  const fetchProducts = useCallback(async () => {
@@ -4208,7 +4229,7 @@ function AccountIconAndMenu(rawProps) {
4208
4229
  onClick: (event) => handleIconClick(),
4209
4230
  "aria-label": getLabel(props.labels, "accountLabel", "Account"),
4210
4231
  "data-open": menuOpen ? "true" : "false",
4211
- className: cn(`propeller-account-menu__trigger inline-flex items-center gap-2 px-3 py-2 rounded-control text-sm font-medium transition-colors text-foreground hover:bg-surface-hover${props.iconClassName ? " " + props.iconClassName : ""}`),
4232
+ className: cn(`propeller-account-menu__trigger inline-flex items-center gap-2 px-3 py-2 rounded-control text-sm font-medium transition-colors text-inherit${props.iconClassName ? " " + props.iconClassName : ""}`),
4212
4233
  children: [
4213
4234
  /* @__PURE__ */ jsx(
4214
4235
  "svg",
@@ -7620,7 +7641,8 @@ function CartIconAndSidebar(rawProps) {
7620
7641
  ] });
7621
7642
  }
7622
7643
  var CartIconAndSidebar_default = CartIconAndSidebar;
7623
- function CartOverview(props) {
7644
+ function CartOverview(rawProps) {
7645
+ const props = useInfraProps(rawProps);
7624
7646
  const [reference, setReference] = useState("");
7625
7647
  const [notes, setNotes] = useState("");
7626
7648
  const [termsAccepted, setTermsAccepted] = useState(false);
@@ -7630,6 +7652,7 @@ function CartOverview(props) {
7630
7652
  const showReference = props.showReference !== false;
7631
7653
  const showTermsAndConditions = props.showTermsAndConditions !== false;
7632
7654
  const showPurchaseButton = props.showPurchaseButton !== false;
7655
+ const overLimit = isOverAuthorizationLimit(props.user, props.companyId, props.cart);
7633
7656
  const invoiceAddress = props.cart?.invoiceAddress;
7634
7657
  const deliveryAddress = props.cart?.deliveryAddress;
7635
7658
  const getCountryName$1 = (code) => getCountryName(code, props.countries);
@@ -7769,7 +7792,12 @@ function CartOverview(props) {
7769
7792
  ] });
7770
7793
  })() })
7771
7794
  ] }) : null,
7772
- showPurchaseButton ? /* @__PURE__ */ jsxs(
7795
+ showPurchaseButton && overLimit ? /* @__PURE__ */ jsx("p", { className: "propeller-cart-overview__authorization-required text-sm text-muted-foreground mt-2", children: getLabel(
7796
+ props.labels,
7797
+ "authorizationRequired",
7798
+ "This order exceeds your authorization limit. Request authorization from the cart to continue."
7799
+ ) }) : null,
7800
+ showPurchaseButton && !overLimit ? /* @__PURE__ */ jsxs(
7773
7801
  "button",
7774
7802
  {
7775
7803
  type: "button",
@@ -8144,6 +8172,7 @@ var RESOLVE_SPEC = {
8144
8172
  currency: { infra: "currency", default: "\u20AC" },
8145
8173
  user: { infra: "user", default: null },
8146
8174
  portalMode: { infra: "portalMode" },
8175
+ isAuthenticated: { infra: "isAuthenticated" },
8147
8176
  columns: { grid: "columns", default: 3 },
8148
8177
  showPrice: { grid: "showPrice" },
8149
8178
  showStock: { grid: "showStock" },
@@ -8187,7 +8216,7 @@ function ClusterCard(rawProps) {
8187
8216
  const textLabelValues = resolveAttributeValues(cluster, props.textLabels).map((value) => ({
8188
8217
  value
8189
8218
  }));
8190
- const contentHidden = isContentHidden(props.portalMode, props.user);
8219
+ const contentHidden = isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8191
8220
  const defaultProductInventory = contentHidden ? void 0 : cluster?.defaultProduct?.inventory;
8192
8221
  const useTax = props.includeTax !== void 0 ? !!props.includeTax : false;
8193
8222
  const priceObj = contentHidden ? void 0 : cluster?.defaultProduct?.price;
@@ -8614,7 +8643,7 @@ function LoginToOrderButton(props) {
8614
8643
  var LoginToOrderButton_default = LoginToOrderButton;
8615
8644
  function ProductPriceDisplay(props) {
8616
8645
  function isHidden2() {
8617
- return isContentHidden(props.portalMode, props.user);
8646
+ return isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8618
8647
  }
8619
8648
  function formatPrice17(value) {
8620
8649
  if (value === null || value === void 0) return "";
@@ -8706,6 +8735,7 @@ var RESOLVE_SPEC2 = {
8706
8735
  language: { infra: "language", default: "NL" },
8707
8736
  includeTax: { infra: "includeTax" },
8708
8737
  portalMode: { infra: "portalMode" },
8738
+ isAuthenticated: { infra: "isAuthenticated" },
8709
8739
  configuration: { infra: "configuration" },
8710
8740
  // Same entry ClusterCard has always had. Without it a shop priced in
8711
8741
  // anything but euros had no route to the cards at all: the grid resolved the
@@ -8764,7 +8794,7 @@ function ProductCardRoot(rawProps) {
8764
8794
  const language = props.language || "NL";
8765
8795
  const product = props.product;
8766
8796
  const isRow = props.columns === 1;
8767
- const contentHidden = isContentHidden(props.portalMode, props.user);
8797
+ const contentHidden = isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8768
8798
  const showStock = !!props.showStock && !contentHidden;
8769
8799
  const showCta = props.allowAddToCart !== false || contentHidden;
8770
8800
  const name = getProductName(product, language);
@@ -9061,6 +9091,7 @@ function ProductCardPrice(props) {
9061
9091
  currency: resolved.currency,
9062
9092
  language: resolved.language,
9063
9093
  portalMode: resolved.portalMode,
9094
+ isAuthenticated: resolved.isAuthenticated,
9064
9095
  user: resolved.user,
9065
9096
  showLoginPrompt: false
9066
9097
  }
@@ -9068,7 +9099,7 @@ function ProductCardPrice(props) {
9068
9099
  }
9069
9100
  function ProductCardAddToCart(props) {
9070
9101
  const { product, resolved } = useProductCard();
9071
- if (isContentHidden(resolved.portalMode, resolved.user)) {
9102
+ if (isContentHidden(resolved.portalMode, resolved.user, resolved.isAuthenticated)) {
9072
9103
  return /* @__PURE__ */ jsx(
9073
9104
  LoginToOrderButton_default,
9074
9105
  {
@@ -9089,9 +9120,13 @@ function ProductCardAddToCart(props) {
9089
9120
  allowIncrDecr: resolved.allowIncrDecr,
9090
9121
  showModal: resolved.showModal,
9091
9122
  enableStockValidation: resolved.enableStockValidation,
9123
+ onAddToCart: resolved.onAddToCart,
9092
9124
  afterAddToCart: resolved.afterAddToCart,
9093
9125
  onProceedToCheckout: resolved.onProceedToCheckout,
9094
9126
  onRequestQuoteClick: resolved.onRequestQuoteClick,
9127
+ createCart: resolved.createCart,
9128
+ onCartCreated: resolved.onCartCreated,
9129
+ includeTax: resolved.includeTax,
9095
9130
  labels: resolved.addToCartLabels
9096
9131
  }
9097
9132
  );
@@ -9214,7 +9249,7 @@ function ProductGrid(rawProps) {
9214
9249
  }
9215
9250
  function showAddToCart() {
9216
9251
  const allow = props.allowAddToCart !== false;
9217
- return !isContentHidden(props.portalMode, props.user) && allow;
9252
+ return !isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
9218
9253
  }
9219
9254
  function getSkeletonItems() {
9220
9255
  const cols = props.columns || 3;
@@ -9492,7 +9527,7 @@ function GridFilters(rawProps) {
9492
9527
  }
9493
9528
  function showAvailability() {
9494
9529
  if (!props.showAvailabilityFilter) return false;
9495
- return !isContentHidden(props.portalMode, props.user);
9530
+ return !isContentHidden(props.portalMode, props.user, props.isAuthenticated);
9496
9531
  }
9497
9532
  function getFilterName(filter) {
9498
9533
  return filter?.attributeDescription?.name || "";
@@ -10047,7 +10082,7 @@ function GridToolbar(rawProps) {
10047
10082
  return badges;
10048
10083
  }
10049
10084
  function isPriceSortDisabled() {
10050
- return isContentHidden(props.portalMode, props.user);
10085
+ return isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10051
10086
  }
10052
10087
  function getAvailabilityLabel() {
10053
10088
  const qty = props.minStock;
@@ -10900,7 +10935,7 @@ function ClusterOptions(rawProps) {
10900
10935
  const language = props.language;
10901
10936
  const options = props.options || [];
10902
10937
  const sel = selectedProductIds;
10903
- const hidePrices = isContentHidden(props.portalMode, props.user);
10938
+ const hidePrices = isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10904
10939
  return options.filter((option) => option.hidden !== YesNo.Y).map((option) => {
10905
10940
  const idStr = option.id.toString();
10906
10941
  const selectedProductId = sel[idStr] || "";
@@ -11084,7 +11119,11 @@ function CompanySwitcher(props) {
11084
11119
  type: "button",
11085
11120
  "aria-haspopup": "listbox",
11086
11121
  "aria-label": getLabel(props.labels, "switchCompanyAriaLabel", "Switch company"),
11087
- className: "propeller-company-switcher__trigger flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors hover:bg-white/10",
11122
+ className: cn(
11123
+ // `text-inherit`: this sits in the host's header, which may be dark or
11124
+ // light. See the note on AccountIconAndMenu's trigger.
11125
+ `propeller-company-switcher__trigger flex items-center gap-2 rounded-md px-3 py-1.5 text-sm font-medium transition-colors text-inherit${props.triggerClassName ? " " + props.triggerClassName : ""}`
11126
+ ),
11088
11127
  onClick: (event) => toggleDropdown(),
11089
11128
  "aria-expanded": isOpen,
11090
11129
  children: [
@@ -15059,7 +15098,7 @@ function PriceToggle(props) {
15059
15098
  var PriceToggle_default = PriceToggle;
15060
15099
  function ProductBulkPrices(props) {
15061
15100
  const includeTax = !!props.includeTax;
15062
- const isHidden2 = isContentHidden(props.portalMode, props.user);
15101
+ const isHidden2 = isContentHidden(props.portalMode, props.user, props.isAuthenticated);
15063
15102
  function getTierQuantity(tier) {
15064
15103
  const discount = tier.discount;
15065
15104
  return discount?.quantityFrom ?? tier.quantity ?? null;
@@ -15606,14 +15645,10 @@ function ProductDownloads(props) {
15606
15645
  const hasItems = items.length > 0;
15607
15646
  const lang = props.language || "NL";
15608
15647
  function getDocumentUrl(doc) {
15609
- const docs = doc.documents || [];
15610
- const match = docs.find((d) => d.language === lang);
15611
- return match?.originalUrl || docs?.[0]?.originalUrl || "";
15648
+ return resolveLanguageEntry(doc.documents, lang)?.originalUrl || "";
15612
15649
  }
15613
15650
  function getDocumentName(doc) {
15614
- const alts = doc.alt || [];
15615
- const match = alts.find((a) => a.language === lang);
15616
- return match?.value || alts?.[0]?.value || "Download";
15651
+ return getLanguageString(doc.alt, lang, "Download");
15617
15652
  }
15618
15653
  return /* @__PURE__ */ jsxs("div", { className: cn(`propeller-product-downloads ${props.className || ""}`), children: [
15619
15654
  hasItems ? /* @__PURE__ */ jsx("h3", { className: "propeller-product-downloads__title text-base font-semibold text-foreground mb-3", children: getLabel(props.labels, "title", "Downloads") }) : null,
@@ -15819,12 +15854,14 @@ function ProductGallery(props) {
15819
15854
  var ProductGallery_default = ProductGallery;
15820
15855
  function pickImageUrl(mediaItems, target) {
15821
15856
  if (!mediaItems || mediaItems.length === 0) return null;
15857
+ const targetUpper = (target || "").toUpperCase();
15858
+ const langMatches = (value) => (value || "").toUpperCase() === targetUpper;
15822
15859
  for (const m of mediaItems) {
15823
- const match = m.imageVariants?.find((v) => v.language === target && v.url);
15860
+ const match = m.imageVariants?.find((v) => langMatches(v.language) && v.url);
15824
15861
  if (match?.url) return match.url;
15825
15862
  }
15826
15863
  for (const m of mediaItems) {
15827
- const match = m.images?.find((i) => i.language === target && i.originalUrl);
15864
+ const match = m.images?.find((i) => langMatches(i.language) && i.originalUrl);
15828
15865
  if (match?.originalUrl) return match.originalUrl;
15829
15866
  }
15830
15867
  for (const m of mediaItems) {
@@ -16152,6 +16189,7 @@ function ProductInfoPrice(props = {}) {
16152
16189
  currency: derived.currency,
16153
16190
  taxZone: derived.taxZone,
16154
16191
  portalMode: derived.portalMode,
16192
+ isAuthenticated: resolved.isAuthenticated,
16155
16193
  user: derived.user,
16156
16194
  labels: derived.labels,
16157
16195
  className: props.className
@@ -16161,7 +16199,7 @@ function ProductInfoPrice(props = {}) {
16161
16199
  function ProductInfoStock(props = {}) {
16162
16200
  const { product, resolved, derived } = useProductInfoContext();
16163
16201
  if (!product.inventory) return null;
16164
- if (isContentHidden(derived.portalMode, resolved.user)) return null;
16202
+ if (isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) return null;
16165
16203
  const Injected = resolved.stockComponent;
16166
16204
  if (Injected) {
16167
16205
  return /* @__PURE__ */ jsx(
@@ -16188,7 +16226,7 @@ function ProductInfoStock(props = {}) {
16188
16226
  }
16189
16227
  function ProductInfoAddToCart(props = {}) {
16190
16228
  const { product, resolved, derived } = useProductInfoContext();
16191
- if (isContentHidden(derived.portalMode, resolved.user)) {
16229
+ if (isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) {
16192
16230
  return /* @__PURE__ */ jsx(
16193
16231
  LoginToOrderButton_default,
16194
16232
  {
@@ -16200,7 +16238,18 @@ function ProductInfoAddToCart(props = {}) {
16200
16238
  }
16201
16239
  const Injected = resolved.addToCartComponent;
16202
16240
  if (Injected) {
16203
- return /* @__PURE__ */ jsx(Injected, { product, labels: derived.labels, className: props.className });
16241
+ return /* @__PURE__ */ jsx(
16242
+ Injected,
16243
+ {
16244
+ product,
16245
+ cartId: resolved.cartId,
16246
+ createCart: resolved.createCart,
16247
+ onCartCreated: resolved.onCartCreated,
16248
+ includeTax: derived.useTax,
16249
+ labels: derived.labels,
16250
+ className: props.className
16251
+ }
16252
+ );
16204
16253
  }
16205
16254
  return /* @__PURE__ */ jsx(
16206
16255
  AddToCart_default,
@@ -16209,6 +16258,9 @@ function ProductInfoAddToCart(props = {}) {
16209
16258
  user: derived.user,
16210
16259
  currency: derived.currency,
16211
16260
  product,
16261
+ cartId: resolved.cartId,
16262
+ createCart: resolved.createCart,
16263
+ onCartCreated: resolved.onCartCreated,
16212
16264
  configuration: resolved.configuration,
16213
16265
  companyId: resolved.companyId,
16214
16266
  includeTax: derived.useTax,
@@ -16262,6 +16314,7 @@ function ProductInfoBulkPrices(props = {}) {
16262
16314
  bulkPrices: product.bulkPrices,
16263
16315
  includeTax: derived.useTax,
16264
16316
  portalMode: derived.portalMode,
16317
+ isAuthenticated: resolved.isAuthenticated,
16265
16318
  user: derived.user,
16266
16319
  taxZone: derived.taxZone,
16267
16320
  currency: derived.currency,
@@ -16424,7 +16477,7 @@ function ProductSlider(rawProps) {
16424
16477
  }
16425
16478
  function showAddToCart() {
16426
16479
  const allow = props.allowAddToCart !== false;
16427
- return !isContentHidden(props.portalMode, props.user) && allow;
16480
+ return !isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
16428
16481
  }
16429
16482
  function isCluster(item) {
16430
16483
  return "clusterId" in item && !("productId" in item);
@@ -16728,14 +16781,10 @@ function ProductVideos(props) {
16728
16781
  const hasItems = items.length > 0;
16729
16782
  const lang = props.language || "NL";
16730
16783
  function getVideoUri(video) {
16731
- const vids = video.videos || [];
16732
- const match = vids.find((v) => v.language === lang);
16733
- return match?.uri || vids?.[0]?.uri || "";
16784
+ return getLanguageUri(video.videos, lang, "");
16734
16785
  }
16735
16786
  function getVideoTitle(video) {
16736
- const alts = video.alt || [];
16737
- const match = alts.find((a) => a.language === lang);
16738
- return match?.value || alts?.[0]?.value || "Video";
16787
+ return getLanguageString(video.alt, lang, "Video");
16739
16788
  }
16740
16789
  function isEmbeddable(uri) {
16741
16790
  return uri.includes("youtube.com") || uri.includes("youtu.be") || uri.includes("vimeo.com");