@propeller-commerce/propeller-v2-react-ui 0.8.0 → 0.10.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 +27 -0
- package/dist/index.cjs +92 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +93 -33
- package/dist/index.js.map +1 -1
- package/dist/pure.cjs +4 -4
- package/dist/pure.cjs.map +1 -1
- package/dist/pure.d.cts +2 -2
- package/dist/pure.d.ts +2 -2
- package/dist/pure.js +4 -4
- package/dist/pure.js.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,33 @@ 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.10.0] - 2026-07-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Magic-token authentication in `useAuth`.** `magicLogin(token)` exchanges a
|
|
16
|
+
backend-issued magic token for a session (via
|
|
17
|
+
`MagicTokenService.magicTokenLogin`), sets the in-memory Bearer header — never
|
|
18
|
+
`setAccessToken` — and loads the viewer, mirroring `login()`. This is the
|
|
19
|
+
passwordless / punchout deep-link handoff. `createMagicToken(input)` issues a
|
|
20
|
+
magic token for a contact/customer (`MagicTokenCreateInput`, authenticated).
|
|
21
|
+
Both return the standard `Result` and are exposed on `UseAuthReturn`. No new
|
|
22
|
+
runtime dependency — the SDK's `MagicTokenService` ships in 0.14.0.
|
|
23
|
+
|
|
24
|
+
## [0.9.0] - 2026-07-29
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Align with `@propeller-commerce/propeller-sdk-v2` 0.14.0**, which removed the
|
|
29
|
+
entire deprecated schema surface. Category reads move to the plural localized
|
|
30
|
+
arrays — `Breadcrumbs` (`category.names` / `slugs`), `CategoryDescription`
|
|
31
|
+
(`descriptions`), `CategoryShortDescription` (`shortDescriptions`) — and the
|
|
32
|
+
hand-written menu query in `useMenu` now selects `names(language:)` /
|
|
33
|
+
`slugs(language:)`. `useClusterConfigurator` / `useProductInfo` read
|
|
34
|
+
`ClusterConfigSetting.attributeName` and `uuid` (were `name` / `id`); the
|
|
35
|
+
public `ConfiguredSetting.id` is now a `string`. Bumped the SDK dev dependency
|
|
36
|
+
to `^0.14.0` and `propeller-v2-core-ui` to `^0.4.0`; the runtime peer stays `*`.
|
|
37
|
+
|
|
11
38
|
## [0.8.0] - 2026-07-28
|
|
12
39
|
|
|
13
40
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -307,6 +307,66 @@ function useAuth(options) {
|
|
|
307
307
|
setLoading(false);
|
|
308
308
|
}
|
|
309
309
|
}, [graphqlClient, language, onAuthHeaderUpdate, configuration]);
|
|
310
|
+
const magicLogin = React6.useCallback(async (token) => {
|
|
311
|
+
setLoading(true);
|
|
312
|
+
setError(null);
|
|
313
|
+
try {
|
|
314
|
+
const loginResult = await new propellerSdkV2.MagicTokenService(graphqlClient).magicTokenLogin(token);
|
|
315
|
+
const session = loginResult?.session;
|
|
316
|
+
const accessToken = session?.accessToken;
|
|
317
|
+
const refreshToken = session?.refreshToken;
|
|
318
|
+
const expiresAt = session?.expirationTime;
|
|
319
|
+
if (accessToken) {
|
|
320
|
+
const cfg = graphqlClient.getConfig();
|
|
321
|
+
graphqlClient.updateConfig({
|
|
322
|
+
headers: { ...cfg.headers, Authorization: `Bearer ${accessToken}` }
|
|
323
|
+
});
|
|
324
|
+
onAuthHeaderUpdate?.(accessToken);
|
|
325
|
+
}
|
|
326
|
+
const userService = propellerV2CoreUi.createServices(graphqlClient).user;
|
|
327
|
+
const viewerInput = {
|
|
328
|
+
...configuration?.contactTrackAttributes?.length && {
|
|
329
|
+
contactAttributesInput: { attributeDescription: { names: configuration.contactTrackAttributes } }
|
|
330
|
+
},
|
|
331
|
+
...configuration?.customerTrackAttributes?.length && {
|
|
332
|
+
customerAttributesInput: { attributeDescription: { names: configuration.customerTrackAttributes } }
|
|
333
|
+
},
|
|
334
|
+
...configuration?.companyTrackAttributes?.length && {
|
|
335
|
+
companyAttributesInput: { attributeDescription: { names: configuration.companyTrackAttributes } }
|
|
336
|
+
},
|
|
337
|
+
// See isViewerSearchInput — an empty array `[]` is truthy but 400s the viewer.
|
|
338
|
+
...isViewerSearchInput(configuration?.contactPAConfigInput) && {
|
|
339
|
+
contactPAConfigInput: configuration.contactPAConfigInput
|
|
340
|
+
},
|
|
341
|
+
...isViewerSearchInput(configuration?.contactCompaniesSearchInput) && {
|
|
342
|
+
contactCompaniesSearchInput: configuration.contactCompaniesSearchInput
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
const viewer = await userService.getViewer(viewerInput);
|
|
346
|
+
const user = viewer;
|
|
347
|
+
return propellerV2CoreUi.ok({ user, accessToken, refreshToken, expiresAt });
|
|
348
|
+
} catch (e) {
|
|
349
|
+
const msg = e instanceof Error ? e.message : "Magic-token login failed";
|
|
350
|
+
setError(msg);
|
|
351
|
+
return propellerV2CoreUi.err(msg);
|
|
352
|
+
} finally {
|
|
353
|
+
setLoading(false);
|
|
354
|
+
}
|
|
355
|
+
}, [graphqlClient, onAuthHeaderUpdate, configuration]);
|
|
356
|
+
const createMagicToken = React6.useCallback(async (input) => {
|
|
357
|
+
setLoading(true);
|
|
358
|
+
setError(null);
|
|
359
|
+
try {
|
|
360
|
+
const token = await new propellerSdkV2.MagicTokenService(graphqlClient).createMagicToken(input);
|
|
361
|
+
return propellerV2CoreUi.ok(token);
|
|
362
|
+
} catch (e) {
|
|
363
|
+
const msg = e instanceof Error ? e.message : "Failed to create magic token";
|
|
364
|
+
setError(msg);
|
|
365
|
+
return propellerV2CoreUi.err(msg);
|
|
366
|
+
} finally {
|
|
367
|
+
setLoading(false);
|
|
368
|
+
}
|
|
369
|
+
}, [graphqlClient]);
|
|
310
370
|
const registerContact = React6.useCallback(async (input, preferredLanguage = language, autoLogin = true) => {
|
|
311
371
|
setLoading(true);
|
|
312
372
|
setError(null);
|
|
@@ -553,7 +613,7 @@ function useAuth(options) {
|
|
|
553
613
|
setLoading(false);
|
|
554
614
|
}
|
|
555
615
|
}, [graphqlClient]);
|
|
556
|
-
return { loading, error, login, registerContact, registerCustomer, forgotPassword };
|
|
616
|
+
return { loading, error, login, magicLogin, createMagicToken, registerContact, registerCustomer, forgotPassword };
|
|
557
617
|
}
|
|
558
618
|
async function initCart(config) {
|
|
559
619
|
const {
|
|
@@ -1127,7 +1187,7 @@ function useClusterConfigurator(options) {
|
|
|
1127
1187
|
const previousSelections = {};
|
|
1128
1188
|
for (let i = 0; i < settingIndex; i++) {
|
|
1129
1189
|
const prev = sortedSettings[i];
|
|
1130
|
-
if (selections[prev.
|
|
1190
|
+
if (selections[prev.attributeName]) previousSelections[prev.attributeName] = selections[prev.attributeName];
|
|
1131
1191
|
}
|
|
1132
1192
|
const matching = propellerV2CoreUi.filterProductsBySelections(products, previousSelections);
|
|
1133
1193
|
return propellerV2CoreUi.collectAttributeValues(matching, attributeName);
|
|
@@ -1136,43 +1196,43 @@ function useClusterConfigurator(options) {
|
|
|
1136
1196
|
const sortedSettings = getSortedSettings();
|
|
1137
1197
|
const sel = selectedAttributes;
|
|
1138
1198
|
return sortedSettings.map((setting, index) => {
|
|
1139
|
-
const availableValues = getAvailableValuesForIndexWithSelections(setting.
|
|
1140
|
-
const selectedValue = sel[setting.
|
|
1141
|
-
const isPreviousMissing = index > 0 && sortedSettings.slice(0, index).some((prev) => !sel[prev.
|
|
1199
|
+
const availableValues = getAvailableValuesForIndexWithSelections(setting.attributeName, index, sel);
|
|
1200
|
+
const selectedValue = sel[setting.attributeName] || "";
|
|
1201
|
+
const isPreviousMissing = index > 0 && sortedSettings.slice(0, index).some((prev) => !sel[prev.attributeName]);
|
|
1142
1202
|
const isDisabled = availableValues.length === 0 || isPreviousMissing;
|
|
1143
|
-
let displayName = setting.
|
|
1203
|
+
let displayName = setting.attributeName;
|
|
1144
1204
|
let attributeType;
|
|
1145
1205
|
for (const product of products) {
|
|
1146
1206
|
const items = product.attributes?.items;
|
|
1147
1207
|
if (!items) continue;
|
|
1148
|
-
const match = items.find((a) => propellerV2CoreUi.attributeNameMatches(a, setting.
|
|
1208
|
+
const match = items.find((a) => propellerV2CoreUi.attributeNameMatches(a, setting.attributeName));
|
|
1149
1209
|
if (!match) continue;
|
|
1150
|
-
displayName = propellerV2CoreUi.getAttributeDisplayName(match, language) || setting.
|
|
1210
|
+
displayName = propellerV2CoreUi.getAttributeDisplayName(match, language) || setting.attributeName;
|
|
1151
1211
|
attributeType = match.attributeDescription?.type ?? void 0;
|
|
1152
1212
|
break;
|
|
1153
1213
|
}
|
|
1154
|
-
return { id: setting.
|
|
1214
|
+
return { id: setting.uuid, name: setting.attributeName, displayType: setting.displayType, attributeType, priority: setting.priority, displayName, availableValues, selectedValue, disabled: isDisabled };
|
|
1155
1215
|
});
|
|
1156
1216
|
}, [products, config, selectedAttributes, language]);
|
|
1157
1217
|
const handleAttributeSelect = React6.useCallback(
|
|
1158
1218
|
(settingName, value) => {
|
|
1159
1219
|
const sortedSettings = getSortedSettings();
|
|
1160
|
-
const changedIndex = sortedSettings.findIndex((s) => s.
|
|
1220
|
+
const changedIndex = sortedSettings.findIndex((s) => s.attributeName === settingName);
|
|
1161
1221
|
if (changedIndex < 0) return;
|
|
1162
1222
|
const newSelections = { ...selectedAttributes };
|
|
1163
1223
|
newSelections[settingName] = value;
|
|
1164
|
-
for (let i = changedIndex + 1; i < sortedSettings.length; i++) delete newSelections[sortedSettings[i].
|
|
1224
|
+
for (let i = changedIndex + 1; i < sortedSettings.length; i++) delete newSelections[sortedSettings[i].attributeName];
|
|
1165
1225
|
for (let i = changedIndex + 1; i < sortedSettings.length; i++) {
|
|
1166
1226
|
const next = sortedSettings[i];
|
|
1167
|
-
const available = getAvailableValuesForIndexWithSelections(next.
|
|
1227
|
+
const available = getAvailableValuesForIndexWithSelections(next.attributeName, i, newSelections);
|
|
1168
1228
|
if (available.length > 0) {
|
|
1169
|
-
newSelections[next.
|
|
1229
|
+
newSelections[next.attributeName] = available[0];
|
|
1170
1230
|
} else {
|
|
1171
1231
|
break;
|
|
1172
1232
|
}
|
|
1173
1233
|
}
|
|
1174
1234
|
setSelectedAttributes(newSelections);
|
|
1175
|
-
const allSelected = sortedSettings.every((s) => !!newSelections[s.
|
|
1235
|
+
const allSelected = sortedSettings.every((s) => !!newSelections[s.attributeName]);
|
|
1176
1236
|
if (allSelected) {
|
|
1177
1237
|
const matching = propellerV2CoreUi.filterProductsBySelections(products, newSelections);
|
|
1178
1238
|
if (matching.length > 0 && onConfigurationChange) onConfigurationChange(matching[0]);
|
|
@@ -1188,15 +1248,15 @@ function useClusterConfigurator(options) {
|
|
|
1188
1248
|
if (!attrItems) return;
|
|
1189
1249
|
const initial = {};
|
|
1190
1250
|
for (const setting of sortedSettings) {
|
|
1191
|
-
const match = attrItems.find((a) => propellerV2CoreUi.attributeNameMatches(a, setting.
|
|
1251
|
+
const match = attrItems.find((a) => propellerV2CoreUi.attributeNameMatches(a, setting.attributeName));
|
|
1192
1252
|
if (match) {
|
|
1193
1253
|
const values = propellerV2CoreUi.extractAttributeValues(match);
|
|
1194
|
-
if (values.length) initial[setting.
|
|
1254
|
+
if (values.length) initial[setting.attributeName] = values[0];
|
|
1195
1255
|
}
|
|
1196
1256
|
}
|
|
1197
1257
|
if (!Object.keys(initial).length) return;
|
|
1198
1258
|
setSelectedAttributes(initial);
|
|
1199
|
-
const allSelected = sortedSettings.every((s) => !!initial[s.
|
|
1259
|
+
const allSelected = sortedSettings.every((s) => !!initial[s.attributeName]);
|
|
1200
1260
|
if (allSelected && onConfigurationChange) {
|
|
1201
1261
|
const matching = propellerV2CoreUi.filterProductsBySelections(products, initial);
|
|
1202
1262
|
if (matching.length > 0) onConfigurationChange(matching[0]);
|
|
@@ -1532,15 +1592,15 @@ function buildCategoriesQuery(depth) {
|
|
|
1532
1592
|
categories {
|
|
1533
1593
|
categoryId
|
|
1534
1594
|
hidden
|
|
1535
|
-
|
|
1536
|
-
|
|
1595
|
+
names(language: $language) { value language }
|
|
1596
|
+
slugs(language: $language) { value }
|
|
1537
1597
|
${buildCategoriesQuery(depth - 1)}
|
|
1538
1598
|
}
|
|
1539
1599
|
`;
|
|
1540
1600
|
}
|
|
1541
1601
|
function mapCategory(raw, language) {
|
|
1542
|
-
const nameEntry = raw.
|
|
1543
|
-
const slugEntry = raw.
|
|
1602
|
+
const nameEntry = raw.names?.find((n) => n.language === language) ?? raw.names?.[0];
|
|
1603
|
+
const slugEntry = raw.slugs?.[0];
|
|
1544
1604
|
return {
|
|
1545
1605
|
categoryId: raw.categoryId,
|
|
1546
1606
|
name: nameEntry?.value ?? "",
|
|
@@ -1620,8 +1680,8 @@ function useMenu(options) {
|
|
|
1620
1680
|
category(categoryId: $categoryId) {
|
|
1621
1681
|
categoryId
|
|
1622
1682
|
hidden
|
|
1623
|
-
|
|
1624
|
-
|
|
1683
|
+
names(language: $language) { value language }
|
|
1684
|
+
slugs(language: $language) { value }
|
|
1625
1685
|
${buildCategoriesQuery(depth)}
|
|
1626
1686
|
}
|
|
1627
1687
|
}
|
|
@@ -2080,7 +2140,7 @@ function useProductInfo(options) {
|
|
|
2080
2140
|
const service = propellerV2CoreUi.createServices(graphqlClient).cluster;
|
|
2081
2141
|
const clusterConfig = await service.getClusterConfig(clusterId);
|
|
2082
2142
|
const attributeNames = (clusterConfig?.config?.settings ?? []).map(
|
|
2083
|
-
(setting) => setting.
|
|
2143
|
+
(setting) => setting.attributeName
|
|
2084
2144
|
);
|
|
2085
2145
|
const variables = {
|
|
2086
2146
|
clusterId,
|
|
@@ -6175,8 +6235,8 @@ function Breadcrumbs(props) {
|
|
|
6175
6235
|
}
|
|
6176
6236
|
const displayItems = props.showCurrent === false && items.length > 0 ? items.slice(0, items.length - 1) : items;
|
|
6177
6237
|
function getCategoryName(cat) {
|
|
6178
|
-
const match = cat.
|
|
6179
|
-
return match?.value || cat.
|
|
6238
|
+
const match = cat.names?.find((n) => n.language === lang);
|
|
6239
|
+
return match?.value || cat.names?.[0]?.value || "";
|
|
6180
6240
|
}
|
|
6181
6241
|
function getCategoryUrl(cat, index) {
|
|
6182
6242
|
if (props.getUrl) return props.getUrl(cat, index);
|
|
@@ -6184,7 +6244,7 @@ function Breadcrumbs(props) {
|
|
|
6184
6244
|
if (urls && typeof urls.getCategoryUrl === "function") {
|
|
6185
6245
|
return urls.getCategoryUrl(cat, props.language);
|
|
6186
6246
|
}
|
|
6187
|
-
const slug = cat.
|
|
6247
|
+
const slug = cat.slugs?.find((s) => s.language === lang)?.value || cat.slugs?.[0]?.value || "";
|
|
6188
6248
|
return `/category/${cat.categoryId}/${slug}`;
|
|
6189
6249
|
}
|
|
6190
6250
|
function isCurrentItem(index) {
|
|
@@ -7874,7 +7934,7 @@ function CartSummary(rawProps) {
|
|
|
7874
7934
|
var CartSummary_default = CartSummary;
|
|
7875
7935
|
function CategoryDescription(props) {
|
|
7876
7936
|
const [expanded, setExpanded] = React6.useState(false);
|
|
7877
|
-
const match = props.category?.
|
|
7937
|
+
const match = props.category?.descriptions?.find(
|
|
7878
7938
|
(d) => d.language === props.language
|
|
7879
7939
|
);
|
|
7880
7940
|
const html = match?.value || "";
|
|
@@ -7915,7 +7975,7 @@ function CategoryDescription(props) {
|
|
|
7915
7975
|
}
|
|
7916
7976
|
var CategoryDescription_default = CategoryDescription;
|
|
7917
7977
|
function CategoryShortDescription(props) {
|
|
7918
|
-
const match = props.category?.
|
|
7978
|
+
const match = props.category?.shortDescriptions?.find(
|
|
7919
7979
|
(d) => d.language === props.language
|
|
7920
7980
|
);
|
|
7921
7981
|
const html = match?.value || "";
|
|
@@ -12808,7 +12868,7 @@ function Menu(rawProps) {
|
|
|
12808
12868
|
const lang = props.language || "NL";
|
|
12809
12869
|
const category = {
|
|
12810
12870
|
categoryId: cat.categoryId,
|
|
12811
|
-
|
|
12871
|
+
slugs: [{ value: cat.slug, language: lang }]
|
|
12812
12872
|
};
|
|
12813
12873
|
if (props.getUrl) return props.getUrl(category);
|
|
12814
12874
|
return props.configuration.urls.getCategoryUrl(category, lang);
|
|
@@ -12822,8 +12882,8 @@ function Menu(rawProps) {
|
|
|
12822
12882
|
const lang = props.language || "NL";
|
|
12823
12883
|
props.onMenuItemClick({
|
|
12824
12884
|
categoryId: cat.categoryId,
|
|
12825
|
-
|
|
12826
|
-
|
|
12885
|
+
names: [{ value: cat.name, language: lang }],
|
|
12886
|
+
slugs: [{ value: cat.slug, language: lang }]
|
|
12827
12887
|
});
|
|
12828
12888
|
}
|
|
12829
12889
|
}
|