@propeller-commerce/propeller-v2-react-ui 0.18.0 → 0.19.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/dist/pure.d.cts CHANGED
@@ -252,9 +252,22 @@ interface OrderItemCardProps {
252
252
  * This component is RSC-safe (no hooks, no context), so the host has to pass
253
253
  * it — there is nothing to read it from. Without it a non-default storefront
254
254
  * renders default-language names and, worse, default-language slugs in the
255
- * links (PWP-940).
255
+ * links.
256
256
  */
257
257
  language?: string;
258
+ /**
259
+ * URL builders from the host's configuration. Supply them and the item link
260
+ * is generated the same way every other link on the storefront is — which is
261
+ * what carries the locale prefix. Without it the component falls back to a
262
+ * bare `/product/:id/:slug`, and on a prefixed storefront that link drops the
263
+ * visitor onto the default language.
264
+ */
265
+ configuration?: {
266
+ urls?: {
267
+ getProductUrl?: (product: Product, language?: string) => string;
268
+ getClusterUrl?: (cluster: Cluster, language?: string) => string;
269
+ };
270
+ };
258
271
  /** Should the item title be a link to the PDP */
259
272
  titleLinkable?: boolean;
260
273
  /** Display a small thumbnail of the order item */
@@ -286,6 +299,15 @@ interface OrderItemCardProps {
286
299
  /**
287
300
  * Renders a single order line item as a table row (with optional indented
288
301
  * child-item rows), showing image, SKU, quantity, discount and price.
302
+ *
303
+ * `orderItem.product` is optional and really can be absent: a product that is
304
+ * hidden, withdrawn or deleted from the catalog still appears on the orders and
305
+ * quotes it was sold on, but the API returns no product record for it. Every
306
+ * product-derived value below therefore falls back to the order item, which
307
+ * carries its own snapshot of the line — name, sku, quantity and prices — taken
308
+ * when the order was placed. Such a row renders as plain text: the placeholder
309
+ * thumbnail, the untranslated `orderItem.name` (there are no localized names to
310
+ * resolve), and no PDP link (there are no slugs to build one from).
289
311
  */
290
312
  declare function OrderItemCard(props: OrderItemCardProps): React.JSX.Element;
291
313
 
@@ -297,6 +319,12 @@ declare function OrderItemCard(props: OrderItemCardProps): React.JSX.Element;
297
319
  */
298
320
 
299
321
  interface OrderSummaryProps {
322
+ /**
323
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
324
+ * rendered in — without it they fall back to Dutch separators regardless of
325
+ * the language the shopper is reading.
326
+ */
327
+ language?: string;
300
328
  /** The order object from propeller-sdk-v2 */
301
329
  order: any;
302
330
  /** The CSS class for the order summary container */
@@ -360,6 +388,12 @@ declare function OrderSummary(props: OrderSummaryProps): React.JSX.Element;
360
388
  */
361
389
 
362
390
  interface OrderTotalsProps {
391
+ /**
392
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
393
+ * rendered in — without it they fall back to Dutch separators regardless of
394
+ * the language the shopper is reading.
395
+ */
396
+ language?: string;
363
397
  /** The order/quote used to populate the summary data */
364
398
  order: Order;
365
399
  /** Order summary block title */
@@ -397,6 +431,12 @@ declare function OrderTotals(props: OrderTotalsProps): React.JSX.Element;
397
431
  */
398
432
 
399
433
  interface ProductBulkPricesProps {
434
+ /**
435
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
436
+ * rendered in — without it they fall back to Dutch separators regardless of
437
+ * the language the shopper is reading.
438
+ */
439
+ language?: string;
400
440
  /**
401
441
  * Bulk price tiers from the product.
402
442
  * Obtain from `product.bulkPrices`.
@@ -473,6 +513,12 @@ declare function ProductDownloads(props: ProductDownloadsProps): React.JSX.Eleme
473
513
  */
474
514
 
475
515
  interface ProductPriceProps {
516
+ /**
517
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
518
+ * rendered in — without it they fall back to Dutch separators regardless of
519
+ * the language the shopper is reading.
520
+ */
521
+ language?: string;
476
522
  /**
477
523
  * ProductPrice object from the product.
478
524
  * Obtain from `product.price`.
package/dist/pure.d.ts CHANGED
@@ -252,9 +252,22 @@ interface OrderItemCardProps {
252
252
  * This component is RSC-safe (no hooks, no context), so the host has to pass
253
253
  * it — there is nothing to read it from. Without it a non-default storefront
254
254
  * renders default-language names and, worse, default-language slugs in the
255
- * links (PWP-940).
255
+ * links.
256
256
  */
257
257
  language?: string;
258
+ /**
259
+ * URL builders from the host's configuration. Supply them and the item link
260
+ * is generated the same way every other link on the storefront is — which is
261
+ * what carries the locale prefix. Without it the component falls back to a
262
+ * bare `/product/:id/:slug`, and on a prefixed storefront that link drops the
263
+ * visitor onto the default language.
264
+ */
265
+ configuration?: {
266
+ urls?: {
267
+ getProductUrl?: (product: Product, language?: string) => string;
268
+ getClusterUrl?: (cluster: Cluster, language?: string) => string;
269
+ };
270
+ };
258
271
  /** Should the item title be a link to the PDP */
259
272
  titleLinkable?: boolean;
260
273
  /** Display a small thumbnail of the order item */
@@ -286,6 +299,15 @@ interface OrderItemCardProps {
286
299
  /**
287
300
  * Renders a single order line item as a table row (with optional indented
288
301
  * child-item rows), showing image, SKU, quantity, discount and price.
302
+ *
303
+ * `orderItem.product` is optional and really can be absent: a product that is
304
+ * hidden, withdrawn or deleted from the catalog still appears on the orders and
305
+ * quotes it was sold on, but the API returns no product record for it. Every
306
+ * product-derived value below therefore falls back to the order item, which
307
+ * carries its own snapshot of the line — name, sku, quantity and prices — taken
308
+ * when the order was placed. Such a row renders as plain text: the placeholder
309
+ * thumbnail, the untranslated `orderItem.name` (there are no localized names to
310
+ * resolve), and no PDP link (there are no slugs to build one from).
289
311
  */
290
312
  declare function OrderItemCard(props: OrderItemCardProps): React.JSX.Element;
291
313
 
@@ -297,6 +319,12 @@ declare function OrderItemCard(props: OrderItemCardProps): React.JSX.Element;
297
319
  */
298
320
 
299
321
  interface OrderSummaryProps {
322
+ /**
323
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
324
+ * rendered in — without it they fall back to Dutch separators regardless of
325
+ * the language the shopper is reading.
326
+ */
327
+ language?: string;
300
328
  /** The order object from propeller-sdk-v2 */
301
329
  order: any;
302
330
  /** The CSS class for the order summary container */
@@ -360,6 +388,12 @@ declare function OrderSummary(props: OrderSummaryProps): React.JSX.Element;
360
388
  */
361
389
 
362
390
  interface OrderTotalsProps {
391
+ /**
392
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
393
+ * rendered in — without it they fall back to Dutch separators regardless of
394
+ * the language the shopper is reading.
395
+ */
396
+ language?: string;
363
397
  /** The order/quote used to populate the summary data */
364
398
  order: Order;
365
399
  /** Order summary block title */
@@ -397,6 +431,12 @@ declare function OrderTotals(props: OrderTotalsProps): React.JSX.Element;
397
431
  */
398
432
 
399
433
  interface ProductBulkPricesProps {
434
+ /**
435
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
436
+ * rendered in — without it they fall back to Dutch separators regardless of
437
+ * the language the shopper is reading.
438
+ */
439
+ language?: string;
400
440
  /**
401
441
  * Bulk price tiers from the product.
402
442
  * Obtain from `product.bulkPrices`.
@@ -473,6 +513,12 @@ declare function ProductDownloads(props: ProductDownloadsProps): React.JSX.Eleme
473
513
  */
474
514
 
475
515
  interface ProductPriceProps {
516
+ /**
517
+ * Storefront language ('EN', 'NL', …). Decides the number format prices are
518
+ * rendered in — without it they fall back to Dutch separators regardless of
519
+ * the language the shopper is reading.
520
+ */
521
+ language?: string;
476
522
  /**
477
523
  * ProductPrice object from the product.
478
524
  * Obtain from `product.price`.
package/dist/pure.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildClusterJsonLd, safeJsonStringify, buildItemListJsonLd, buildProductJsonLd, getLabel, getLocalizedValue, isContentHidden, formatPrice, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
1
+ import { buildClusterJsonLd, safeJsonStringify, buildItemListJsonLd, buildProductJsonLd, getLabel, getLocalizedValue, isContentHidden, formatPrice, localeForLanguage, getCountryName } from '@propeller-commerce/propeller-v2-core-ui';
2
2
  import { clsx } from 'clsx';
3
3
  import { twMerge } from 'tailwind-merge';
4
4
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
@@ -247,20 +247,21 @@ var ItemStock_default = React.memo(ItemStock);
247
247
  function getProductName(orderItem, language) {
248
248
  return getLocalizedValue(orderItem?.product?.names, language, "") || orderItem?.name || "Unknown Product";
249
249
  }
250
- function getClusterUrl(orderItem, language) {
250
+ function getClusterUrl(orderItem, language, urls) {
251
251
  const cluster = orderItem?.product?.cluster;
252
252
  if (!cluster) return "";
253
253
  const id = cluster.clusterId;
254
254
  const slug = getLocalizedValue(cluster.slugs, language, "");
255
255
  if (!id || !slug) return "";
256
- return `/cluster/${id}/${slug}`;
256
+ return urls?.getClusterUrl?.(cluster, language) || `/cluster/${id}/${slug}`;
257
257
  }
258
- function getProductUrl(orderItem, language) {
259
- const cu = getClusterUrl(orderItem, language);
258
+ function getProductUrl(orderItem, language, urls) {
259
+ const cu = getClusterUrl(orderItem, language, urls);
260
260
  if (cu) return cu;
261
- const id = orderItem?.product?.productId;
262
- const slug = getLocalizedValue(orderItem?.product?.slugs, language, "");
263
- if (id && slug) return `/product/${id}/${slug}`;
261
+ const product = orderItem?.product;
262
+ const id = product?.productId;
263
+ const slug = getLocalizedValue(product?.slugs, language, "");
264
+ if (id && slug) return urls?.getProductUrl?.(product, language) || `/product/${id}/${slug}`;
264
265
  return "";
265
266
  }
266
267
  function OrderItemCard(props) {
@@ -277,7 +278,7 @@ function OrderItemCard(props) {
277
278
  const productName = getProductName(item, props.language);
278
279
  const productSku = item?.product?.sku || item?.sku || "";
279
280
  const productImage = item?.product?.media?.images?.items?.[0]?.imageVariants?.[0]?.url || "";
280
- const productUrl = getProductUrl(item, props.language);
281
+ const productUrl = getProductUrl(item, props.language, props.configuration?.urls);
281
282
  const quantity = item?.quantity || 0;
282
283
  const priceTotal = item?.priceTotal || 0;
283
284
  const discount = item?.discount || 0;
@@ -290,7 +291,7 @@ function OrderItemCard(props) {
290
291
  return props.formatPrice(price);
291
292
  }
292
293
  if (!price && price !== 0) return "-";
293
- return formatPrice(price, { symbol: props.currency ?? "\u20AC" });
294
+ return formatPrice(price, { symbol: props.currency ?? "\u20AC", locale: localeForLanguage(props.language) });
294
295
  }
295
296
  const discountDisplay = discountPercentage > 0 ? `${formatItemPrice(discount)} (${discountPercentage.toFixed(2).replace(".", ",")}%)` : formatItemPrice(discount);
296
297
  return /* @__PURE__ */ jsxs("tbody", { className: "propeller-order-item-card", children: [
@@ -369,11 +370,11 @@ function OrderItemCard(props) {
369
370
  className: isChildItem ? "propeller-order-item-card__cell propeller-order-item-card__cell--price px-6 py-2 text-right whitespace-nowrap text-sm text-muted-foreground" : "propeller-order-item-card__cell propeller-order-item-card__cell--price px-6 py-4 text-right whitespace-nowrap",
370
371
  children: (() => {
371
372
  const PriceImpl = props.priceComponent;
372
- if (PriceImpl) {
373
+ if (PriceImpl && item?.product?.price) {
373
374
  return /* @__PURE__ */ jsx(
374
375
  PriceImpl,
375
376
  {
376
- price: item?.product?.price,
377
+ price: item.product.price,
377
378
  currency: props.currency,
378
379
  labels: props.labels
379
380
  }
@@ -431,7 +432,7 @@ function OrderSummary(props) {
431
432
  if (props.formatPrice) {
432
433
  return props.formatPrice(price);
433
434
  }
434
- return formatPrice(price || 0, { symbol: props.currency ?? "\u20AC" });
435
+ return formatPrice(price || 0, { symbol: props.currency ?? "\u20AC", locale: localeForLanguage(props.language) });
435
436
  }
436
437
  function numericDate(dateString) {
437
438
  const d = new Date(dateString);
@@ -539,7 +540,7 @@ function OrderTotals(props) {
539
540
  const postageData = props.order?.postageData;
540
541
  function formatItemPrice(price) {
541
542
  if (props.formatPrice) return props.formatPrice(price);
542
- return formatPrice(price || 0, { symbol: props.currency ?? "\u20AC" });
543
+ return formatPrice(price || 0, { symbol: props.currency ?? "\u20AC", locale: localeForLanguage(props.language) });
543
544
  }
544
545
  const subtotal = total?.gross || 0;
545
546
  const hasDiscount = !!total && total.discountType !== OrderDiscountType.N && total.discountValue > 0;
@@ -667,7 +668,7 @@ function ProductBulkPrices(props) {
667
668
  function getPrice(tier) {
668
669
  const value = includeTax ? tier.net : tier.gross;
669
670
  if (value === null || value === void 0) return "";
670
- return formatPrice(value, { symbol: props.currency ?? "\u20AC" });
671
+ return formatPrice(value, { symbol: props.currency ?? "\u20AC", locale: localeForLanguage(props.language) });
671
672
  }
672
673
  function getQuantityLabel(tier, index) {
673
674
  const discount = tier.discount;
@@ -678,13 +679,14 @@ function ProductBulkPrices(props) {
678
679
  if (nextQty) return `${qty}\u2013${nextQty - 1}`;
679
680
  return `${qty}+`;
680
681
  }
682
+ const title = props.labels?.title ?? "Volume pricing";
681
683
  return /* @__PURE__ */ jsx(Fragment, { children: !isHidden && hasItems ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(
682
684
  "div",
683
685
  {
684
686
  className: cn(`propeller-product-bulk-prices ${props.className || ""}`),
685
687
  "data-include-tax": includeTax ? "true" : "false",
686
688
  children: [
687
- getLabel(props.labels, "title", "Volume pricing") ? /* @__PURE__ */ jsx("h3", { className: "propeller-product-bulk-prices__title text-base font-semibold text-foreground mb-3", children: getLabel(props.labels, "title", "Volume pricing") }) : null,
689
+ title ? /* @__PURE__ */ jsx("h3", { className: "propeller-product-bulk-prices__title text-base font-semibold text-foreground mb-3", children: title }) : null,
688
690
  /* @__PURE__ */ jsx("div", { className: "propeller-product-bulk-prices__table-wrapper overflow-hidden rounded-container border border-border", children: /* @__PURE__ */ jsxs("table", { className: "propeller-product-bulk-prices__table w-full text-sm", children: [
689
691
  /* @__PURE__ */ jsx("thead", { className: "propeller-product-bulk-prices__thead bg-muted/50", children: /* @__PURE__ */ jsxs("tr", { children: [
690
692
  /* @__PURE__ */ jsx("th", { className: "propeller-product-bulk-prices__th propeller-product-bulk-prices__th--quantity px-4 py-2 text-left font-medium text-muted-foreground", children: getLabel(props.labels, "quantityFrom", "Qty from") }),
@@ -781,7 +783,7 @@ function ProductPriceDisplay(props) {
781
783
  }
782
784
  function formatPrice5(value) {
783
785
  if (value === null || value === void 0) return "";
784
- return formatPrice(value, { symbol: props.currency ?? "\u20AC" });
786
+ return formatPrice(value, { symbol: props.currency ?? "\u20AC", locale: localeForLanguage(props.language) });
785
787
  }
786
788
  function getOptionsTotal(useNet) {
787
789
  const options = props.options || [];