@propeller-commerce/propeller-v2-react-ui 0.21.0 → 0.23.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/CHANGELOG.md CHANGED
@@ -8,6 +8,84 @@ once it reaches 1.0. Until then (the `0.x` line) the public API may change
8
8
  between minor versions; breaking changes are called out below and in
9
9
  [MIGRATION.md](./MIGRATION.md).
10
10
 
11
+ ## [0.23.0] - 2026-09-24
12
+
13
+ ### Fixed
14
+
15
+ - **A machine listed in one language could not be opened.** 0.21.0 stopped the
16
+ listing from narrowing to a single language, so an NL-only installation
17
+ appears in an EN tree and is linked by its NL slug — but `machine(slug:)`
18
+ resolves a slug **only** in the language it was authored in, so following
19
+ that link asked for `machine(slug: "<NL slug>", language: "EN")` and got
20
+ `SPARE_PARTS_MACHINE_NOT_FOUND_ERROR`. The page then showed a title derived
21
+ from the slug, no parts and no error: the bug moved one click deeper instead
22
+ of going away. `useSpareParts` now tries the tree language, then the
23
+ storefront language, then any locales given in the new `machineLanguages`
24
+ option, and upper-cases each (the API matches case exactly — `nl` misses
25
+ where `NL` hits). A slug that resolves in none of them sets the new
26
+ `notFound` flag, and `MachineGrid` renders "This machine could not be found."
27
+ instead of an empty listing. A fully-translated shop still costs exactly one
28
+ request, and a non-"not found" failure is never retried or reported as a
29
+ missing machine. (PWP-993)
30
+
31
+ - **"Qty in machine" and "Search parts…" stayed English in a translated shop.**
32
+ Both were read from `toolbarLabels`, which is forwarded verbatim to
33
+ `GridToolbar` — so they belonged to no toolbar dictionary, and a shop that
34
+ translated its `GridToolbar` keys properly still got English. They now come
35
+ from `machineCardLabels` (the grid's own label bag, alongside `loading`,
36
+ `noMachines` and the new `machineNotFound`), falling back to `toolbarLabels`
37
+ so hosts that already set them keep working. (PWP-995a)
38
+
39
+ ### Added
40
+
41
+ - `MachineGrid.machineLanguages` / `useSpareParts.machineLanguages` — the
42
+ locales to try when a machine slug does not resolve in the tree language.
43
+ - `useSpareParts().notFound` — the slug resolved in none of the candidate
44
+ languages, as distinct from "resolved but has no parts".
45
+ - `machineCardLabels.machineNotFound` — copy for that state.
46
+
47
+
48
+ ## [0.22.0] - 2026-09-24
49
+
50
+ ### Fixed
51
+
52
+ - **Media language is matched case-insensitively.** The API lowercases
53
+ `documents[].language` while leaving `alt[].language` as uploaded, so
54
+ `ProductDownloads` matched the label but never the file and served
55
+ `documents[0]` — the Dutch PDF under an English label. `ProductVideos` had
56
+ the identical comparison and worked only by data luck. Also fixed in
57
+ `useProductSearch.filterByLanguage`, where a case mismatch dropped **every**
58
+ product from the grid, and in `DefaultProductImage`. (PWP-984)
59
+ - **`checkoutAllowed` is usable, and fails closed.** It read the raw `companyId`
60
+ option rather than the resolved one, so a contact acting for their own company
61
+ matched no authorization config and every cart reported as within limit; and it
62
+ evaluated its own cart state, which stays null when the consumer only seeds a
63
+ `cartId`. The hook now hydrates a seeded cart, and reports `false` while that
64
+ is in flight rather than `true`. (PWP-988)
65
+ - **The place-order button is gated on the authorization limit.** An over-limit
66
+ purchaser opening checkout directly got an ungated button and a backend
67
+ rejection; `CartOverview` now shows an `authorizationRequired` message
68
+ instead, using the same predicate as the cart summary. (PWP-988)
69
+ - **Semi-closed surfaces no longer flash their logged-out state** for a signed-in
70
+ visitor while the profile loads. Every `isContentHidden` call site passes the
71
+ provider’s `isAuthenticated` (core-ui 0.9.0+). (PWP-989)
72
+ - **The cart sidebar slides again.** The overflow fix named only `transform` in
73
+ its transition, but Tailwind v4 emits `translate-x-*` as the `translate`
74
+ property, so the panel jumped. (PWP-1002)
75
+ - **Header triggers inherit their colour.** `AccountIconAndMenu` and
76
+ `CompanySwitcher` hardcoded `text-white`, which broke light headers;
77
+ `text-foreground` then broke dark ones. Both now use `text-inherit`, with the
78
+ hover tint derived from `currentColor`. (PWP-1005)
79
+
80
+ ### Added
81
+
82
+ - `CompanySwitcher` accepts `triggerClassName`; it had no class prop at all.
83
+ - `ProductInfo` accepts `cartId`, `createCart` and `onCartCreated`, and forwards
84
+ them to both the injected and the default add-to-cart. It declared none of
85
+ them, so the PDP’s add-to-cart always failed for a visitor without a cart.
86
+ `ProductCard` forwards the same to an injected component. (PWP-986)
87
+ - `CartOverview` resolves `user` and `companyId` from `PropellerProvider`.
88
+
11
89
  ## [0.21.0] - 2026-09-23
12
90
 
13
91
  `MachineGrid` stops losing rows, and becomes usable in a translated or
package/dist/index.cjs CHANGED
@@ -767,10 +767,29 @@ function useCart(options) {
767
767
  const [loading, setLoading] = React6.useState(false);
768
768
  const [error, setError] = React6.useState(null);
769
769
  const notesTimers = React6.useRef({});
770
- const checkoutAllowed = React6.useMemo(
771
- () => propellerV2CoreUi.isCheckoutAllowed(user, companyId, cart),
772
- [user, companyId, cart]
773
- );
770
+ React6.useEffect(() => {
771
+ if (!graphqlClient || !cartId || cart) return;
772
+ let cancelled = false;
773
+ (async () => {
774
+ try {
775
+ const fetched = await propellerV2CoreUi.createServices(graphqlClient).cart.getCart({
776
+ cartId,
777
+ imageSearchFilters: imageSearchFilters(),
778
+ imageVariantFilters: imageVariantFilters(),
779
+ language
780
+ });
781
+ if (!cancelled && fetched) setCart(fetched);
782
+ } catch {
783
+ }
784
+ })();
785
+ return () => {
786
+ cancelled = true;
787
+ };
788
+ }, [graphqlClient, cartId, cart, language, imageSearchFilters, imageVariantFilters]);
789
+ const checkoutAllowed = React6.useMemo(() => {
790
+ if (cartId && !cart) return false;
791
+ return propellerV2CoreUi.isCheckoutAllowed(user, resolvedCompanyId, cart);
792
+ }, [user, resolvedCompanyId, cart, cartId]);
774
793
  function getMinQuantity(product) {
775
794
  const min = product?.minimumQuantity;
776
795
  return min && min > 0 ? min : 1;
@@ -1591,7 +1610,8 @@ var INFRA_KEYS = [
1591
1610
  "includeTax",
1592
1611
  "currency",
1593
1612
  "configuration",
1594
- "portalMode"
1613
+ "portalMode",
1614
+ "isAuthenticated"
1595
1615
  ];
1596
1616
  function useInfraProps(props) {
1597
1617
  const ctx = usePropellerContext();
@@ -2268,10 +2288,11 @@ function useProductSearch(options) {
2268
2288
  const isLoading = !isControlled && internalLoading;
2269
2289
  function filterByLanguage(products, lang) {
2270
2290
  if (!lang) return products;
2291
+ const target = lang.toUpperCase();
2271
2292
  return products.filter((p) => {
2272
2293
  const names = p.names || p.names || [];
2273
2294
  if (!names || names.length === 0) return true;
2274
- return names.some((n) => n.language === lang);
2295
+ return names.some((n) => (n.language || "").toUpperCase() === target);
2275
2296
  });
2276
2297
  }
2277
2298
  const fetchProducts = React6.useCallback(async () => {
@@ -2761,6 +2782,40 @@ function useQuickOrder(options) {
2761
2782
  );
2762
2783
  return { submitting, error, searchProducts, submit };
2763
2784
  }
2785
+
2786
+ // src/composables/shared/utils/machineLanguage.ts
2787
+ var MACHINE_NOT_FOUND = "SPARE_PARTS_MACHINE_NOT_FOUND_ERROR";
2788
+ function isMachineNotFound(error) {
2789
+ const entries = error?.errors;
2790
+ if (!Array.isArray(entries)) return false;
2791
+ return entries.some(
2792
+ (e) => e?.extensions?.code === MACHINE_NOT_FOUND || /no machine found for slug and language/i.test(e?.message ?? "")
2793
+ );
2794
+ }
2795
+ function machineLanguageCandidates(machineLanguage, language, extra) {
2796
+ const ordered = [machineLanguage, language, ...extra ?? []];
2797
+ const seen = /* @__PURE__ */ new Set();
2798
+ const out = [];
2799
+ for (const raw of ordered) {
2800
+ const value = raw?.trim().toUpperCase();
2801
+ if (!value || seen.has(value)) continue;
2802
+ seen.add(value);
2803
+ out.push(value);
2804
+ }
2805
+ return out;
2806
+ }
2807
+ async function resolveMachineAcrossLanguages(candidates, fetchOne) {
2808
+ for (const language of candidates) {
2809
+ try {
2810
+ return { machine: await fetchOne(language), language };
2811
+ } catch (error) {
2812
+ if (!isMachineNotFound(error)) throw error;
2813
+ }
2814
+ }
2815
+ return null;
2816
+ }
2817
+
2818
+ // src/composables/react/useSpareParts.ts
2764
2819
  var inflightFetches3 = /* @__PURE__ */ new Set();
2765
2820
  var STOREFRONT_STATUSES = [
2766
2821
  propellerSdkV2.ProductStatus.A,
@@ -2777,6 +2832,8 @@ function useSpareParts(options) {
2777
2832
  const [itemsFound, setItemsFound] = React6.useState(0);
2778
2833
  const [internalLoading, setInternalLoading] = React6.useState(false);
2779
2834
  const [internalPage, setInternalPage] = React6.useState(1);
2835
+ const [notFound, setNotFound] = React6.useState(false);
2836
+ const machineLanguagesKey = (options.machineLanguages ?? []).join(",");
2780
2837
  const currentPage = options.page ?? internalPage;
2781
2838
  const [totalPages, setTotalPages] = React6.useState(1);
2782
2839
  const fetchIdRef = React6.useRef(0);
@@ -2842,17 +2899,35 @@ function useSpareParts(options) {
2842
2899
  const filterAvailableAttributeInput = {
2843
2900
  isSearchable: true
2844
2901
  };
2845
- const machine = await service.getMachine({
2846
- slug: options.slug,
2847
- // The machine tree's language, NOT the parts' — see `machineLanguage`.
2848
- language: options.machineLanguage ?? options.language,
2849
- sparePartsMachineProductSearchInput,
2850
- filterAvailableAttributeInput,
2851
- priceCalculateProductInput,
2852
- imageSearchFilters: options.configuration?.imageSearchFiltersGrid,
2853
- imageVariantFilters: options.configuration?.imageVariantFiltersMedium
2854
- });
2902
+ const resolved = await resolveMachineAcrossLanguages(
2903
+ machineLanguageCandidates(
2904
+ options.machineLanguage,
2905
+ options.language,
2906
+ options.machineLanguages
2907
+ ),
2908
+ (language) => service.getMachine({
2909
+ slug: options.slug,
2910
+ // The machine tree's language, NOT the parts' — see `machineLanguage`.
2911
+ language,
2912
+ sparePartsMachineProductSearchInput,
2913
+ filterAvailableAttributeInput,
2914
+ priceCalculateProductInput,
2915
+ imageSearchFilters: options.configuration?.imageSearchFiltersGrid,
2916
+ imageVariantFilters: options.configuration?.imageVariantFiltersMedium
2917
+ })
2918
+ );
2855
2919
  if (thisId !== fetchIdRef.current) return;
2920
+ if (!resolved) {
2921
+ setNotFound(true);
2922
+ setInternalParts([]);
2923
+ setChildMachines([]);
2924
+ setItemsFound(0);
2925
+ options.onItemsFoundChange?.(0);
2926
+ setTotalPages(1);
2927
+ return;
2928
+ }
2929
+ setNotFound(false);
2930
+ const machine = resolved.machine;
2856
2931
  const partsResponse = machine?.sparePartProducts;
2857
2932
  const items = partsResponse?.items ?? [];
2858
2933
  setInternalParts(items);
@@ -2894,6 +2969,7 @@ function useSpareParts(options) {
2894
2969
  options.term,
2895
2970
  options.language,
2896
2971
  options.machineLanguage,
2972
+ machineLanguagesKey,
2897
2973
  options.taxZone,
2898
2974
  options.companyId,
2899
2975
  options.textFilters,
@@ -2914,6 +2990,7 @@ function useSpareParts(options) {
2914
2990
  options.term,
2915
2991
  options.language,
2916
2992
  options.machineLanguage,
2993
+ machineLanguagesKey,
2917
2994
  options.companyId,
2918
2995
  // eslint-disable-next-line react-hooks/exhaustive-deps
2919
2996
  JSON.stringify(options.textFilters ?? []),
@@ -2939,7 +3016,8 @@ function useSpareParts(options) {
2939
3016
  currentPage,
2940
3017
  totalPages,
2941
3018
  fetchParts,
2942
- goToPage
3019
+ goToPage,
3020
+ notFound: !isControlled && notFound
2943
3021
  };
2944
3022
  }
2945
3023
  var inflightFetches4 = /* @__PURE__ */ new Set();
@@ -4228,7 +4306,7 @@ function AccountIconAndMenu(rawProps) {
4228
4306
  onClick: (event) => handleIconClick(),
4229
4307
  "aria-label": propellerV2CoreUi.getLabel(props.labels, "accountLabel", "Account"),
4230
4308
  "data-open": menuOpen ? "true" : "false",
4231
- 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 : ""}`),
4309
+ 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 : ""}`),
4232
4310
  children: [
4233
4311
  /* @__PURE__ */ jsxRuntime.jsx(
4234
4312
  "svg",
@@ -7640,7 +7718,8 @@ function CartIconAndSidebar(rawProps) {
7640
7718
  ] });
7641
7719
  }
7642
7720
  var CartIconAndSidebar_default = CartIconAndSidebar;
7643
- function CartOverview(props) {
7721
+ function CartOverview(rawProps) {
7722
+ const props = useInfraProps(rawProps);
7644
7723
  const [reference, setReference] = React6.useState("");
7645
7724
  const [notes, setNotes] = React6.useState("");
7646
7725
  const [termsAccepted, setTermsAccepted] = React6.useState(false);
@@ -7650,6 +7729,7 @@ function CartOverview(props) {
7650
7729
  const showReference = props.showReference !== false;
7651
7730
  const showTermsAndConditions = props.showTermsAndConditions !== false;
7652
7731
  const showPurchaseButton = props.showPurchaseButton !== false;
7732
+ const overLimit = propellerV2CoreUi.isOverAuthorizationLimit(props.user, props.companyId, props.cart);
7653
7733
  const invoiceAddress = props.cart?.invoiceAddress;
7654
7734
  const deliveryAddress = props.cart?.deliveryAddress;
7655
7735
  const getCountryName = (code) => propellerV2CoreUi.getCountryName(code, props.countries);
@@ -7789,7 +7869,12 @@ function CartOverview(props) {
7789
7869
  ] });
7790
7870
  })() })
7791
7871
  ] }) : null,
7792
- showPurchaseButton ? /* @__PURE__ */ jsxRuntime.jsxs(
7872
+ showPurchaseButton && overLimit ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "propeller-cart-overview__authorization-required text-sm text-muted-foreground mt-2", children: propellerV2CoreUi.getLabel(
7873
+ props.labels,
7874
+ "authorizationRequired",
7875
+ "This order exceeds your authorization limit. Request authorization from the cart to continue."
7876
+ ) }) : null,
7877
+ showPurchaseButton && !overLimit ? /* @__PURE__ */ jsxRuntime.jsxs(
7793
7878
  "button",
7794
7879
  {
7795
7880
  type: "button",
@@ -8164,6 +8249,7 @@ var RESOLVE_SPEC = {
8164
8249
  currency: { infra: "currency", default: "\u20AC" },
8165
8250
  user: { infra: "user", default: null },
8166
8251
  portalMode: { infra: "portalMode" },
8252
+ isAuthenticated: { infra: "isAuthenticated" },
8167
8253
  columns: { grid: "columns", default: 3 },
8168
8254
  showPrice: { grid: "showPrice" },
8169
8255
  showStock: { grid: "showStock" },
@@ -8207,7 +8293,7 @@ function ClusterCard(rawProps) {
8207
8293
  const textLabelValues = resolveAttributeValues(cluster, props.textLabels).map((value) => ({
8208
8294
  value
8209
8295
  }));
8210
- const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8296
+ const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8211
8297
  const defaultProductInventory = contentHidden ? void 0 : cluster?.defaultProduct?.inventory;
8212
8298
  const useTax = props.includeTax !== void 0 ? !!props.includeTax : false;
8213
8299
  const priceObj = contentHidden ? void 0 : cluster?.defaultProduct?.price;
@@ -8634,7 +8720,7 @@ function LoginToOrderButton(props) {
8634
8720
  var LoginToOrderButton_default = LoginToOrderButton;
8635
8721
  function ProductPriceDisplay(props) {
8636
8722
  function isHidden2() {
8637
- return propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8723
+ return propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8638
8724
  }
8639
8725
  function formatPrice17(value) {
8640
8726
  if (value === null || value === void 0) return "";
@@ -8726,6 +8812,7 @@ var RESOLVE_SPEC2 = {
8726
8812
  language: { infra: "language", default: "NL" },
8727
8813
  includeTax: { infra: "includeTax" },
8728
8814
  portalMode: { infra: "portalMode" },
8815
+ isAuthenticated: { infra: "isAuthenticated" },
8729
8816
  configuration: { infra: "configuration" },
8730
8817
  // Same entry ClusterCard has always had. Without it a shop priced in
8731
8818
  // anything but euros had no route to the cards at all: the grid resolved the
@@ -8784,7 +8871,7 @@ function ProductCardRoot(rawProps) {
8784
8871
  const language = props.language || "NL";
8785
8872
  const product = props.product;
8786
8873
  const isRow = props.columns === 1;
8787
- const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
8874
+ const contentHidden = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
8788
8875
  const showStock = !!props.showStock && !contentHidden;
8789
8876
  const showCta = props.allowAddToCart !== false || contentHidden;
8790
8877
  const name = getProductName(product, language);
@@ -9081,6 +9168,7 @@ function ProductCardPrice(props) {
9081
9168
  currency: resolved.currency,
9082
9169
  language: resolved.language,
9083
9170
  portalMode: resolved.portalMode,
9171
+ isAuthenticated: resolved.isAuthenticated,
9084
9172
  user: resolved.user,
9085
9173
  showLoginPrompt: false
9086
9174
  }
@@ -9088,7 +9176,7 @@ function ProductCardPrice(props) {
9088
9176
  }
9089
9177
  function ProductCardAddToCart(props) {
9090
9178
  const { product, resolved } = useProductCard();
9091
- if (propellerV2CoreUi.isContentHidden(resolved.portalMode, resolved.user)) {
9179
+ if (propellerV2CoreUi.isContentHidden(resolved.portalMode, resolved.user, resolved.isAuthenticated)) {
9092
9180
  return /* @__PURE__ */ jsxRuntime.jsx(
9093
9181
  LoginToOrderButton_default,
9094
9182
  {
@@ -9109,9 +9197,13 @@ function ProductCardAddToCart(props) {
9109
9197
  allowIncrDecr: resolved.allowIncrDecr,
9110
9198
  showModal: resolved.showModal,
9111
9199
  enableStockValidation: resolved.enableStockValidation,
9200
+ onAddToCart: resolved.onAddToCart,
9112
9201
  afterAddToCart: resolved.afterAddToCart,
9113
9202
  onProceedToCheckout: resolved.onProceedToCheckout,
9114
9203
  onRequestQuoteClick: resolved.onRequestQuoteClick,
9204
+ createCart: resolved.createCart,
9205
+ onCartCreated: resolved.onCartCreated,
9206
+ includeTax: resolved.includeTax,
9115
9207
  labels: resolved.addToCartLabels
9116
9208
  }
9117
9209
  );
@@ -9234,7 +9326,7 @@ function ProductGrid(rawProps) {
9234
9326
  }
9235
9327
  function showAddToCart() {
9236
9328
  const allow = props.allowAddToCart !== false;
9237
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user) && allow;
9329
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
9238
9330
  }
9239
9331
  function getSkeletonItems() {
9240
9332
  const cols = props.columns || 3;
@@ -9512,7 +9604,7 @@ function GridFilters(rawProps) {
9512
9604
  }
9513
9605
  function showAvailability() {
9514
9606
  if (!props.showAvailabilityFilter) return false;
9515
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
9607
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
9516
9608
  }
9517
9609
  function getFilterName(filter) {
9518
9610
  return filter?.attributeDescription?.name || "";
@@ -10067,7 +10159,7 @@ function GridToolbar(rawProps) {
10067
10159
  return badges;
10068
10160
  }
10069
10161
  function isPriceSortDisabled() {
10070
- return propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
10162
+ return propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10071
10163
  }
10072
10164
  function getAvailabilityLabel() {
10073
10165
  const qty = props.minStock;
@@ -10469,13 +10561,14 @@ function MachineGrid(rawProps) {
10469
10561
  // node query threads through useSpareParts.
10470
10562
  imageVariantFilters: configuration?.imageVariantFiltersMedium
10471
10563
  });
10472
- const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages } = useSpareParts({
10564
+ const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages, notFound } = useSpareParts({
10473
10565
  graphqlClient: props.graphqlClient,
10474
10566
  // Idle at the root (no slug).
10475
10567
  slug: isRoot ? void 0 : currentSlug,
10476
10568
  term: term || void 0,
10477
10569
  language,
10478
10570
  machineLanguage,
10571
+ machineLanguages: rawProps.machineLanguages,
10479
10572
  taxZone: props.taxZone,
10480
10573
  user: props.user,
10481
10574
  companyId: props.companyId,
@@ -10499,6 +10592,7 @@ function MachineGrid(rawProps) {
10499
10592
  onMachineChange: setMachine
10500
10593
  });
10501
10594
  const machineName = machine ? propellerV2CoreUi.getLocalizedValue(machine.name, machineLanguage) : slugToLabel(currentSlug);
10595
+ const machineLabel = (key, fallback) => rawProps.machineCardLabels?.[key] ?? rawProps.toolbarLabels?.[key] ?? fallback;
10502
10596
  const partProducts = React6.useMemo(
10503
10597
  () => displayParts.map((p) => p.product).filter(Boolean),
10504
10598
  [displayParts]
@@ -10573,6 +10667,12 @@ function MachineGrid(rawProps) {
10573
10667
  ] });
10574
10668
  }
10575
10669
  const hasParts = itemsFound > 0 || partProducts.length > 0;
10670
+ if (notFound && !partsLoading) {
10671
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rawProps.className, children: [
10672
+ /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Breadcrumb", className: "propeller-breadcrumbs mb-6", children: /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "flex flex-wrap items-center gap-2 text-sm text-foreground-subtle", children: /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: basePath, className: "hover:text-primary", children: rawProps.rootTitle ?? "Machines" }) }) }) }),
10673
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-12 text-center text-foreground-subtle", children: machineLabel("machineNotFound", "This machine could not be found.") })
10674
+ ] });
10675
+ }
10576
10676
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rawProps.className, children: [
10577
10677
  /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Breadcrumb", className: "propeller-breadcrumbs mb-6", children: /* @__PURE__ */ jsxRuntime.jsxs("ol", { className: "flex flex-wrap items-center gap-2 text-sm text-foreground-subtle", children: [
10578
10678
  /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: basePath, className: "hover:text-primary", children: rawProps.rootTitle ?? "Machines" }) }),
@@ -10625,7 +10725,9 @@ function MachineGrid(rawProps) {
10625
10725
  {
10626
10726
  defaultValue: term,
10627
10727
  onSubmit: submitSearch,
10628
- labels: rawProps.toolbarLabels
10728
+ searchLabel: machineLabel("searchParts", "Search parts\u2026"),
10729
+ searchAriaLabel: machineLabel("searchParts", "Search parts"),
10730
+ buttonLabel: propellerV2CoreUi.getLabel(rawProps.toolbarLabels, "search", "Search")
10629
10731
  },
10630
10732
  term
10631
10733
  ),
@@ -10676,7 +10778,7 @@ function MachineGrid(rawProps) {
10676
10778
  const qty = quantityBySku.get(product.sku);
10677
10779
  if (!qty) return null;
10678
10780
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "propeller-spare-part__quantity text-sm text-foreground-subtle", children: [
10679
- rawProps.toolbarLabels?.quantityInMachine ?? "Qty in machine",
10781
+ machineLabel("quantityInMachine", "Qty in machine"),
10680
10782
  ": ",
10681
10783
  qty
10682
10784
  ] });
@@ -10708,8 +10810,8 @@ function MachinePartsSearch(props) {
10708
10810
  onKeyDown: (e) => {
10709
10811
  if (e.key === "Enter") submit();
10710
10812
  },
10711
- placeholder: props.labels?.searchParts ?? "Search parts\u2026",
10712
- "aria-label": props.labels?.searchParts ?? "Search parts",
10813
+ placeholder: props.searchLabel,
10814
+ "aria-label": props.searchAriaLabel,
10713
10815
  className: "w-full rounded border border-border bg-background px-3 py-2"
10714
10816
  }
10715
10817
  ),
@@ -10719,7 +10821,7 @@ function MachinePartsSearch(props) {
10719
10821
  type: "button",
10720
10822
  onClick: submit,
10721
10823
  className: "rounded bg-primary px-4 py-2 text-primary-foreground",
10722
- children: props.labels?.search ?? "Search"
10824
+ children: props.buttonLabel
10723
10825
  }
10724
10826
  )
10725
10827
  ] });
@@ -10920,7 +11022,7 @@ function ClusterOptions(rawProps) {
10920
11022
  const language = props.language;
10921
11023
  const options = props.options || [];
10922
11024
  const sel = selectedProductIds;
10923
- const hidePrices = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
11025
+ const hidePrices = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
10924
11026
  return options.filter((option) => option.hidden !== propellerSdkV2.YesNo.Y).map((option) => {
10925
11027
  const idStr = option.id.toString();
10926
11028
  const selectedProductId = sel[idStr] || "";
@@ -11104,7 +11206,11 @@ function CompanySwitcher(props) {
11104
11206
  type: "button",
11105
11207
  "aria-haspopup": "listbox",
11106
11208
  "aria-label": propellerV2CoreUi.getLabel(props.labels, "switchCompanyAriaLabel", "Switch company"),
11107
- 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",
11209
+ className: cn(
11210
+ // `text-inherit`: this sits in the host's header, which may be dark or
11211
+ // light. See the note on AccountIconAndMenu's trigger.
11212
+ `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 : ""}`
11213
+ ),
11108
11214
  onClick: (event) => toggleDropdown(),
11109
11215
  "aria-expanded": isOpen,
11110
11216
  children: [
@@ -15079,7 +15185,7 @@ function PriceToggle(props) {
15079
15185
  var PriceToggle_default = PriceToggle;
15080
15186
  function ProductBulkPrices(props) {
15081
15187
  const includeTax = !!props.includeTax;
15082
- const isHidden2 = propellerV2CoreUi.isContentHidden(props.portalMode, props.user);
15188
+ const isHidden2 = propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated);
15083
15189
  function getTierQuantity(tier) {
15084
15190
  const discount = tier.discount;
15085
15191
  return discount?.quantityFrom ?? tier.quantity ?? null;
@@ -15626,14 +15732,10 @@ function ProductDownloads(props) {
15626
15732
  const hasItems = items.length > 0;
15627
15733
  const lang = props.language || "NL";
15628
15734
  function getDocumentUrl(doc) {
15629
- const docs = doc.documents || [];
15630
- const match = docs.find((d) => d.language === lang);
15631
- return match?.originalUrl || docs?.[0]?.originalUrl || "";
15735
+ return propellerV2CoreUi.resolveLanguageEntry(doc.documents, lang)?.originalUrl || "";
15632
15736
  }
15633
15737
  function getDocumentName(doc) {
15634
- const alts = doc.alt || [];
15635
- const match = alts.find((a) => a.language === lang);
15636
- return match?.value || alts?.[0]?.value || "Download";
15738
+ return propellerV2CoreUi.getLanguageString(doc.alt, lang, "Download");
15637
15739
  }
15638
15740
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn(`propeller-product-downloads ${props.className || ""}`), children: [
15639
15741
  hasItems ? /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "propeller-product-downloads__title text-base font-semibold text-foreground mb-3", children: propellerV2CoreUi.getLabel(props.labels, "title", "Downloads") }) : null,
@@ -15839,12 +15941,14 @@ function ProductGallery(props) {
15839
15941
  var ProductGallery_default = ProductGallery;
15840
15942
  function pickImageUrl(mediaItems, target) {
15841
15943
  if (!mediaItems || mediaItems.length === 0) return null;
15944
+ const targetUpper = (target || "").toUpperCase();
15945
+ const langMatches = (value) => (value || "").toUpperCase() === targetUpper;
15842
15946
  for (const m of mediaItems) {
15843
- const match = m.imageVariants?.find((v) => v.language === target && v.url);
15947
+ const match = m.imageVariants?.find((v) => langMatches(v.language) && v.url);
15844
15948
  if (match?.url) return match.url;
15845
15949
  }
15846
15950
  for (const m of mediaItems) {
15847
- const match = m.images?.find((i) => i.language === target && i.originalUrl);
15951
+ const match = m.images?.find((i) => langMatches(i.language) && i.originalUrl);
15848
15952
  if (match?.originalUrl) return match.originalUrl;
15849
15953
  }
15850
15954
  for (const m of mediaItems) {
@@ -16172,6 +16276,7 @@ function ProductInfoPrice(props = {}) {
16172
16276
  currency: derived.currency,
16173
16277
  taxZone: derived.taxZone,
16174
16278
  portalMode: derived.portalMode,
16279
+ isAuthenticated: resolved.isAuthenticated,
16175
16280
  user: derived.user,
16176
16281
  labels: derived.labels,
16177
16282
  className: props.className
@@ -16181,7 +16286,7 @@ function ProductInfoPrice(props = {}) {
16181
16286
  function ProductInfoStock(props = {}) {
16182
16287
  const { product, resolved, derived } = useProductInfoContext();
16183
16288
  if (!product.inventory) return null;
16184
- if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user)) return null;
16289
+ if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) return null;
16185
16290
  const Injected = resolved.stockComponent;
16186
16291
  if (Injected) {
16187
16292
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -16208,7 +16313,7 @@ function ProductInfoStock(props = {}) {
16208
16313
  }
16209
16314
  function ProductInfoAddToCart(props = {}) {
16210
16315
  const { product, resolved, derived } = useProductInfoContext();
16211
- if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user)) {
16316
+ if (propellerV2CoreUi.isContentHidden(derived.portalMode, resolved.user, resolved.isAuthenticated)) {
16212
16317
  return /* @__PURE__ */ jsxRuntime.jsx(
16213
16318
  LoginToOrderButton_default,
16214
16319
  {
@@ -16220,7 +16325,18 @@ function ProductInfoAddToCart(props = {}) {
16220
16325
  }
16221
16326
  const Injected = resolved.addToCartComponent;
16222
16327
  if (Injected) {
16223
- return /* @__PURE__ */ jsxRuntime.jsx(Injected, { product, labels: derived.labels, className: props.className });
16328
+ return /* @__PURE__ */ jsxRuntime.jsx(
16329
+ Injected,
16330
+ {
16331
+ product,
16332
+ cartId: resolved.cartId,
16333
+ createCart: resolved.createCart,
16334
+ onCartCreated: resolved.onCartCreated,
16335
+ includeTax: derived.useTax,
16336
+ labels: derived.labels,
16337
+ className: props.className
16338
+ }
16339
+ );
16224
16340
  }
16225
16341
  return /* @__PURE__ */ jsxRuntime.jsx(
16226
16342
  AddToCart_default,
@@ -16229,6 +16345,9 @@ function ProductInfoAddToCart(props = {}) {
16229
16345
  user: derived.user,
16230
16346
  currency: derived.currency,
16231
16347
  product,
16348
+ cartId: resolved.cartId,
16349
+ createCart: resolved.createCart,
16350
+ onCartCreated: resolved.onCartCreated,
16232
16351
  configuration: resolved.configuration,
16233
16352
  companyId: resolved.companyId,
16234
16353
  includeTax: derived.useTax,
@@ -16282,6 +16401,7 @@ function ProductInfoBulkPrices(props = {}) {
16282
16401
  bulkPrices: product.bulkPrices,
16283
16402
  includeTax: derived.useTax,
16284
16403
  portalMode: derived.portalMode,
16404
+ isAuthenticated: resolved.isAuthenticated,
16285
16405
  user: derived.user,
16286
16406
  taxZone: derived.taxZone,
16287
16407
  currency: derived.currency,
@@ -16444,7 +16564,7 @@ function ProductSlider(rawProps) {
16444
16564
  }
16445
16565
  function showAddToCart() {
16446
16566
  const allow = props.allowAddToCart !== false;
16447
- return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user) && allow;
16567
+ return !propellerV2CoreUi.isContentHidden(props.portalMode, props.user, props.isAuthenticated) && allow;
16448
16568
  }
16449
16569
  function isCluster(item) {
16450
16570
  return "clusterId" in item && !("productId" in item);
@@ -16748,14 +16868,10 @@ function ProductVideos(props) {
16748
16868
  const hasItems = items.length > 0;
16749
16869
  const lang = props.language || "NL";
16750
16870
  function getVideoUri(video) {
16751
- const vids = video.videos || [];
16752
- const match = vids.find((v) => v.language === lang);
16753
- return match?.uri || vids?.[0]?.uri || "";
16871
+ return propellerV2CoreUi.getLanguageUri(video.videos, lang, "");
16754
16872
  }
16755
16873
  function getVideoTitle(video) {
16756
- const alts = video.alt || [];
16757
- const match = alts.find((a) => a.language === lang);
16758
- return match?.value || alts?.[0]?.value || "Video";
16874
+ return propellerV2CoreUi.getLanguageString(video.alt, lang, "Video");
16759
16875
  }
16760
16876
  function isEmbeddable(uri) {
16761
16877
  return uri.includes("youtube.com") || uri.includes("youtu.be") || uri.includes("vimeo.com");