@propeller-commerce/propeller-v2-react-ui 0.22.0 → 0.23.1
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 +54 -0
- package/dist/index.cjs +85 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -3
- package/dist/index.d.ts +40 -3
- package/dist/index.js +85 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,60 @@ 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.1] - 2026-09-24
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **The language fallback never ran, so a machine listed in one language still
|
|
16
|
+
opened empty.** 0.23.0 added the fallback but only advanced it on a *thrown*
|
|
17
|
+
error. The API answers a wrong-language slug with a **partial** response —
|
|
18
|
+
`machine: null` alongside `SPARE_PARTS_MACHINE_NOT_FOUND_ERROR` — and the
|
|
19
|
+
SDK's `runOperation` returns that data rather than throwing unless the client
|
|
20
|
+
opts into `throwOnPartialErrors`, which no boilerplate does. So `getMachine`
|
|
21
|
+
resolved to `null`, the first candidate language was accepted as a hit, the
|
|
22
|
+
remaining languages were never tried and `notFound` was never set: one
|
|
23
|
+
request in the tree language per page, and the same empty listing as before.
|
|
24
|
+
A `null` machine now counts as "not found in this language" and the loop
|
|
25
|
+
continues, so the fallback reaches the language the slug was authored in and
|
|
26
|
+
a slug that resolves nowhere sets `notFound`. (PWP-993)
|
|
27
|
+
|
|
28
|
+
## [0.23.0] - 2026-09-24
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **A machine listed in one language could not be opened.** 0.21.0 stopped the
|
|
33
|
+
listing from narrowing to a single language, so an NL-only installation
|
|
34
|
+
appears in an EN tree and is linked by its NL slug — but `machine(slug:)`
|
|
35
|
+
resolves a slug **only** in the language it was authored in, so following
|
|
36
|
+
that link asked for `machine(slug: "<NL slug>", language: "EN")` and got
|
|
37
|
+
`SPARE_PARTS_MACHINE_NOT_FOUND_ERROR`. The page then showed a title derived
|
|
38
|
+
from the slug, no parts and no error: the bug moved one click deeper instead
|
|
39
|
+
of going away. `useSpareParts` now tries the tree language, then the
|
|
40
|
+
storefront language, then any locales given in the new `machineLanguages`
|
|
41
|
+
option, and upper-cases each (the API matches case exactly — `nl` misses
|
|
42
|
+
where `NL` hits). A slug that resolves in none of them sets the new
|
|
43
|
+
`notFound` flag, and `MachineGrid` renders "This machine could not be found."
|
|
44
|
+
instead of an empty listing. A fully-translated shop still costs exactly one
|
|
45
|
+
request, and a non-"not found" failure is never retried or reported as a
|
|
46
|
+
missing machine. (PWP-993)
|
|
47
|
+
|
|
48
|
+
- **"Qty in machine" and "Search parts…" stayed English in a translated shop.**
|
|
49
|
+
Both were read from `toolbarLabels`, which is forwarded verbatim to
|
|
50
|
+
`GridToolbar` — so they belonged to no toolbar dictionary, and a shop that
|
|
51
|
+
translated its `GridToolbar` keys properly still got English. They now come
|
|
52
|
+
from `machineCardLabels` (the grid's own label bag, alongside `loading`,
|
|
53
|
+
`noMachines` and the new `machineNotFound`), falling back to `toolbarLabels`
|
|
54
|
+
so hosts that already set them keep working. (PWP-995a)
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- `MachineGrid.machineLanguages` / `useSpareParts.machineLanguages` — the
|
|
59
|
+
locales to try when a machine slug does not resolve in the tree language.
|
|
60
|
+
- `useSpareParts().notFound` — the slug resolved in none of the candidate
|
|
61
|
+
languages, as distinct from "resolved but has no parts".
|
|
62
|
+
- `machineCardLabels.machineNotFound` — copy for that state.
|
|
63
|
+
|
|
64
|
+
|
|
11
65
|
## [0.22.0] - 2026-09-24
|
|
12
66
|
|
|
13
67
|
### Fixed
|
package/dist/index.cjs
CHANGED
|
@@ -2782,6 +2782,41 @@ function useQuickOrder(options) {
|
|
|
2782
2782
|
);
|
|
2783
2783
|
return { submitting, error, searchProducts, submit };
|
|
2784
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
|
+
const machine = await fetchOne(language);
|
|
2811
|
+
if (machine !== null && machine !== void 0) return { machine, language };
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
if (!isMachineNotFound(error)) throw error;
|
|
2814
|
+
}
|
|
2815
|
+
}
|
|
2816
|
+
return null;
|
|
2817
|
+
}
|
|
2818
|
+
|
|
2819
|
+
// src/composables/react/useSpareParts.ts
|
|
2785
2820
|
var inflightFetches3 = /* @__PURE__ */ new Set();
|
|
2786
2821
|
var STOREFRONT_STATUSES = [
|
|
2787
2822
|
propellerSdkV2.ProductStatus.A,
|
|
@@ -2798,6 +2833,8 @@ function useSpareParts(options) {
|
|
|
2798
2833
|
const [itemsFound, setItemsFound] = React6.useState(0);
|
|
2799
2834
|
const [internalLoading, setInternalLoading] = React6.useState(false);
|
|
2800
2835
|
const [internalPage, setInternalPage] = React6.useState(1);
|
|
2836
|
+
const [notFound, setNotFound] = React6.useState(false);
|
|
2837
|
+
const machineLanguagesKey = (options.machineLanguages ?? []).join(",");
|
|
2801
2838
|
const currentPage = options.page ?? internalPage;
|
|
2802
2839
|
const [totalPages, setTotalPages] = React6.useState(1);
|
|
2803
2840
|
const fetchIdRef = React6.useRef(0);
|
|
@@ -2863,17 +2900,35 @@ function useSpareParts(options) {
|
|
|
2863
2900
|
const filterAvailableAttributeInput = {
|
|
2864
2901
|
isSearchable: true
|
|
2865
2902
|
};
|
|
2866
|
-
const
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2903
|
+
const resolved = await resolveMachineAcrossLanguages(
|
|
2904
|
+
machineLanguageCandidates(
|
|
2905
|
+
options.machineLanguage,
|
|
2906
|
+
options.language,
|
|
2907
|
+
options.machineLanguages
|
|
2908
|
+
),
|
|
2909
|
+
(language) => service.getMachine({
|
|
2910
|
+
slug: options.slug,
|
|
2911
|
+
// The machine tree's language, NOT the parts' — see `machineLanguage`.
|
|
2912
|
+
language,
|
|
2913
|
+
sparePartsMachineProductSearchInput,
|
|
2914
|
+
filterAvailableAttributeInput,
|
|
2915
|
+
priceCalculateProductInput,
|
|
2916
|
+
imageSearchFilters: options.configuration?.imageSearchFiltersGrid,
|
|
2917
|
+
imageVariantFilters: options.configuration?.imageVariantFiltersMedium
|
|
2918
|
+
})
|
|
2919
|
+
);
|
|
2876
2920
|
if (thisId !== fetchIdRef.current) return;
|
|
2921
|
+
if (!resolved) {
|
|
2922
|
+
setNotFound(true);
|
|
2923
|
+
setInternalParts([]);
|
|
2924
|
+
setChildMachines([]);
|
|
2925
|
+
setItemsFound(0);
|
|
2926
|
+
options.onItemsFoundChange?.(0);
|
|
2927
|
+
setTotalPages(1);
|
|
2928
|
+
return;
|
|
2929
|
+
}
|
|
2930
|
+
setNotFound(false);
|
|
2931
|
+
const machine = resolved.machine;
|
|
2877
2932
|
const partsResponse = machine?.sparePartProducts;
|
|
2878
2933
|
const items = partsResponse?.items ?? [];
|
|
2879
2934
|
setInternalParts(items);
|
|
@@ -2915,6 +2970,7 @@ function useSpareParts(options) {
|
|
|
2915
2970
|
options.term,
|
|
2916
2971
|
options.language,
|
|
2917
2972
|
options.machineLanguage,
|
|
2973
|
+
machineLanguagesKey,
|
|
2918
2974
|
options.taxZone,
|
|
2919
2975
|
options.companyId,
|
|
2920
2976
|
options.textFilters,
|
|
@@ -2935,6 +2991,7 @@ function useSpareParts(options) {
|
|
|
2935
2991
|
options.term,
|
|
2936
2992
|
options.language,
|
|
2937
2993
|
options.machineLanguage,
|
|
2994
|
+
machineLanguagesKey,
|
|
2938
2995
|
options.companyId,
|
|
2939
2996
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2940
2997
|
JSON.stringify(options.textFilters ?? []),
|
|
@@ -2960,7 +3017,8 @@ function useSpareParts(options) {
|
|
|
2960
3017
|
currentPage,
|
|
2961
3018
|
totalPages,
|
|
2962
3019
|
fetchParts,
|
|
2963
|
-
goToPage
|
|
3020
|
+
goToPage,
|
|
3021
|
+
notFound: !isControlled && notFound
|
|
2964
3022
|
};
|
|
2965
3023
|
}
|
|
2966
3024
|
var inflightFetches4 = /* @__PURE__ */ new Set();
|
|
@@ -10504,13 +10562,14 @@ function MachineGrid(rawProps) {
|
|
|
10504
10562
|
// node query threads through useSpareParts.
|
|
10505
10563
|
imageVariantFilters: configuration?.imageVariantFiltersMedium
|
|
10506
10564
|
});
|
|
10507
|
-
const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages } = useSpareParts({
|
|
10565
|
+
const { displayParts, childMachines, isLoading: partsLoading, currentPage, totalPages, notFound } = useSpareParts({
|
|
10508
10566
|
graphqlClient: props.graphqlClient,
|
|
10509
10567
|
// Idle at the root (no slug).
|
|
10510
10568
|
slug: isRoot ? void 0 : currentSlug,
|
|
10511
10569
|
term: term || void 0,
|
|
10512
10570
|
language,
|
|
10513
10571
|
machineLanguage,
|
|
10572
|
+
machineLanguages: rawProps.machineLanguages,
|
|
10514
10573
|
taxZone: props.taxZone,
|
|
10515
10574
|
user: props.user,
|
|
10516
10575
|
companyId: props.companyId,
|
|
@@ -10534,6 +10593,7 @@ function MachineGrid(rawProps) {
|
|
|
10534
10593
|
onMachineChange: setMachine
|
|
10535
10594
|
});
|
|
10536
10595
|
const machineName = machine ? propellerV2CoreUi.getLocalizedValue(machine.name, machineLanguage) : slugToLabel(currentSlug);
|
|
10596
|
+
const machineLabel = (key, fallback) => rawProps.machineCardLabels?.[key] ?? rawProps.toolbarLabels?.[key] ?? fallback;
|
|
10537
10597
|
const partProducts = React6.useMemo(
|
|
10538
10598
|
() => displayParts.map((p) => p.product).filter(Boolean),
|
|
10539
10599
|
[displayParts]
|
|
@@ -10608,6 +10668,12 @@ function MachineGrid(rawProps) {
|
|
|
10608
10668
|
] });
|
|
10609
10669
|
}
|
|
10610
10670
|
const hasParts = itemsFound > 0 || partProducts.length > 0;
|
|
10671
|
+
if (notFound && !partsLoading) {
|
|
10672
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rawProps.className, children: [
|
|
10673
|
+
/* @__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" }) }) }) }),
|
|
10674
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "py-12 text-center text-foreground-subtle", children: machineLabel("machineNotFound", "This machine could not be found.") })
|
|
10675
|
+
] });
|
|
10676
|
+
}
|
|
10611
10677
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: rawProps.className, children: [
|
|
10612
10678
|
/* @__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: [
|
|
10613
10679
|
/* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: basePath, className: "hover:text-primary", children: rawProps.rootTitle ?? "Machines" }) }),
|
|
@@ -10660,7 +10726,9 @@ function MachineGrid(rawProps) {
|
|
|
10660
10726
|
{
|
|
10661
10727
|
defaultValue: term,
|
|
10662
10728
|
onSubmit: submitSearch,
|
|
10663
|
-
|
|
10729
|
+
searchLabel: machineLabel("searchParts", "Search parts\u2026"),
|
|
10730
|
+
searchAriaLabel: machineLabel("searchParts", "Search parts"),
|
|
10731
|
+
buttonLabel: propellerV2CoreUi.getLabel(rawProps.toolbarLabels, "search", "Search")
|
|
10664
10732
|
},
|
|
10665
10733
|
term
|
|
10666
10734
|
),
|
|
@@ -10711,7 +10779,7 @@ function MachineGrid(rawProps) {
|
|
|
10711
10779
|
const qty = quantityBySku.get(product.sku);
|
|
10712
10780
|
if (!qty) return null;
|
|
10713
10781
|
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "propeller-spare-part__quantity text-sm text-foreground-subtle", children: [
|
|
10714
|
-
|
|
10782
|
+
machineLabel("quantityInMachine", "Qty in machine"),
|
|
10715
10783
|
": ",
|
|
10716
10784
|
qty
|
|
10717
10785
|
] });
|
|
@@ -10743,8 +10811,8 @@ function MachinePartsSearch(props) {
|
|
|
10743
10811
|
onKeyDown: (e) => {
|
|
10744
10812
|
if (e.key === "Enter") submit();
|
|
10745
10813
|
},
|
|
10746
|
-
placeholder: props.
|
|
10747
|
-
"aria-label": props.
|
|
10814
|
+
placeholder: props.searchLabel,
|
|
10815
|
+
"aria-label": props.searchAriaLabel,
|
|
10748
10816
|
className: "w-full rounded border border-border bg-background px-3 py-2"
|
|
10749
10817
|
}
|
|
10750
10818
|
),
|
|
@@ -10754,7 +10822,7 @@ function MachinePartsSearch(props) {
|
|
|
10754
10822
|
type: "button",
|
|
10755
10823
|
onClick: submit,
|
|
10756
10824
|
className: "rounded bg-primary px-4 py-2 text-primary-foreground",
|
|
10757
|
-
children: props.
|
|
10825
|
+
children: props.buttonLabel
|
|
10758
10826
|
}
|
|
10759
10827
|
)
|
|
10760
10828
|
] });
|