@akinon/pz-theme 2.0.157-rc.13 → 2.0.158-beta.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.
@@ -9,15 +9,22 @@ import { getResponsiveValue, partAttrs } from '../utils';
9
9
  import { formatDate, formatMoney } from './commerce-format';
10
10
  import { readMoneyStyle } from './money-style';
11
11
  import { hasItemTemplate, ItemTemplateList } from './item-template';
12
- import { renderOrderLineActions } from './order-line-actions';
13
12
  import { BlockRendererProps } from './block-renderer-registry';
14
13
  import {
15
14
  DesignerPlaceholder,
16
15
  groupOrderItems,
17
16
  readToggle
18
17
  } from './product-block-utils';
19
-
20
- type OrderItemLike = {
18
+ import {
19
+ allowsLineReturn,
20
+ allowsOrderCancel,
21
+ type OrderActionLine,
22
+ type OrderActionOrder,
23
+ type OrderActionRules,
24
+ useOrderActionRules
25
+ } from '../utils/order-action-rules';
26
+
27
+ interface OrderItemLike {
21
28
  id?: number;
22
29
  price?: string;
23
30
  quantity?: string | number;
@@ -36,7 +43,7 @@ type OrderItemLike = {
36
43
  absolute_url?: string;
37
44
  attributes?: Record<string, unknown>;
38
45
  } | null;
39
- };
46
+ }
40
47
 
41
48
  interface AddressLike {
42
49
  first_name?: string;
@@ -118,31 +125,39 @@ const firstString = (
118
125
  const hasOpenRequest = (request: unknown): boolean =>
119
126
  Array.isArray(request) ? request.length > 0 : Boolean(request);
120
127
 
121
- /** Commerce's "Teslim edildi". */
122
- const DELIVERED = '550';
123
-
124
128
  /**
125
- * Whether the order still has a line to return: delivered, refundable, with
126
- * no request open on it. Commerce also marks a shipped line refundable, and
127
- * keeps `is_refundable` true on the order and the line while a return request
128
- * is open, so the order flag alone would offer a return before the parcel
129
- * arrives and again after the shopper has asked. Lines that carry no flag at
130
- * all (an older payload) leave the decision to the order's flag and status.
129
+ * Whether the order still has a line to return: one commerce flags
130
+ * refundable, with no request open on it, that the brand's rules let a return
131
+ * start from (`registerOrderActionRules`: from which statuses, for how long
132
+ * after the order — ZERO-5302). Commerce keeps `is_refundable` true on the
133
+ * order and the line while a return request is open, so the order flag alone
134
+ * would offer a return again after the shopper has asked. Lines that carry no
135
+ * flag at all (an older payload) go by the order's flag, still through the
136
+ * brand's rules.
131
137
  */
132
- const hasReturnableLine = (order: OrderLike): boolean => {
138
+ const hasReturnableLine = (
139
+ order: OrderLike,
140
+ rules: OrderActionRules
141
+ ): boolean => {
133
142
  if (!order.is_refundable) return false;
134
143
 
135
144
  const lines = order.orderitem_set ?? [];
145
+ const allows = (item: OrderItemLike | null | undefined) =>
146
+ allowsLineReturn(
147
+ rules,
148
+ (item ?? {}) as OrderActionLine,
149
+ order as OrderActionOrder
150
+ );
136
151
 
137
152
  if (!lines.some((item) => item && 'is_refundable' in item)) {
138
- return String(order.status?.value ?? '') === DELIVERED;
153
+ return lines.length === 0 ? allows(null) : lines.some(allows);
139
154
  }
140
155
 
141
156
  return lines.some(
142
157
  (item) =>
143
- String(item?.status?.value ?? '') === DELIVERED &&
144
158
  item?.is_refundable &&
145
- !hasOpenRequest(item.active_cancellation_request)
159
+ !hasOpenRequest(item.active_cancellation_request) &&
160
+ allows(item)
146
161
  );
147
162
  };
148
163
 
@@ -373,9 +388,9 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
373
388
  * order key is read from the URL; the editor owns which sections are exposed,
374
389
  * every caption and the styling.
375
390
  * A designer can mark one child as the row layout for the order lines; without
376
- * one the built-in line renders. A brand may hang per-line actions on the
377
- * built-in line through `registerOrderLineActions`; they render inside the
378
- * line's `item-actions` part.
391
+ * one the built-in line renders.
392
+ * The cancel and return links offer what commerce allows and the brand's
393
+ * `registerOrderActionRules` lets through (ZERO-5302).
379
394
  * Properties: numberLabel, numberTag, dateLabel, headerTotalLabel,
380
395
  * showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
381
396
  * showReturnLink, returnLinkLabel, cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
@@ -393,7 +408,7 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
393
408
  * currencyText, currencyPosition.
394
409
  * Parts: detail, header, number, date, header-total, status,
395
410
  * invoice-link, cancel-link, return-link, list, item, image, name, quantity,
396
- * color, size, item-status, item-tracking, item-return-code, item-actions,
411
+ * color, size, item-status, item-tracking, item-return-code,
397
412
  * item-return-method, item-return-carrier, item-pickup-date, price, shipment,
398
413
  * delivery, tracking, addresses, address, address-label, totals, total-row,
399
414
  * total-label, total-value, grand-total, payment-info, payment-info-label.
@@ -591,6 +606,8 @@ const OrderDetailBlock = ({
591
606
  // Not gated on the session: brands expose the same endpoint for anonymous
592
607
  // order tracking. Without a key there is nothing to ask for at all.
593
608
  const { data, isLoading } = useGetOrderQuery(orderKey, { skip: !orderKey });
609
+ // The brand's say over the return and cancel links (ZERO-5302).
610
+ const actionRules = useOrderActionRules();
594
611
 
595
612
  const liveOrder = (data ?? null) as OrderLike | null;
596
613
  // See DESIGNER_SAMPLE_ORDER's own comment: the preview route's order id is
@@ -624,11 +641,16 @@ const OrderDetailBlock = ({
624
641
  const money = (value: unknown) =>
625
642
  formatMoney(value, { locale, currency, ...moneyStyle });
626
643
 
627
- // The sample order is not a delivered one; it shows the link for styling.
644
+ // The sample order shows both links for styling, whatever the brand's
645
+ // rules would make of it.
628
646
  const offersReturn =
629
647
  order === DESIGNER_SAMPLE_ORDER
630
648
  ? Boolean(order.is_refundable)
631
- : hasReturnableLine(order);
649
+ : hasReturnableLine(order, actionRules);
650
+ const offersCancel =
651
+ Boolean(order.is_cancellable) &&
652
+ (order === DESIGNER_SAMPLE_ORDER ||
653
+ allowsOrderCancel(actionRules, order as OrderActionOrder));
632
654
 
633
655
  // Navigating inside the designer iframe would drop the author out of the page
634
656
  // they are editing, so every link is inert there.
@@ -833,7 +855,7 @@ const OrderDetailBlock = ({
833
855
  → {invoiceLinkLabel}
834
856
  </a>
835
857
  )}
836
- {showCancelLink && order.is_cancellable && cancelHref && (
858
+ {showCancelLink && offersCancel && cancelHref && (
837
859
  <NextLink
838
860
  href={isDesigner ? '#' : cancelHref}
839
861
  onClick={guard}
@@ -933,21 +955,6 @@ const OrderDetailBlock = ({
933
955
  showPickupDate && returnDetails.pickupDate
934
956
  ? formatPickupDate(returnDetails.pickupDate, locale)
935
957
  : '';
936
- const lineActions = renderOrderLineActions({
937
- order,
938
- line,
939
- isDesigner
940
- });
941
- const showLineMeta =
942
- showShipmentStatus &&
943
- Boolean(
944
- item.status?.label ||
945
- trackingValue ||
946
- returnCodeValue ||
947
- returnMethodValue ||
948
- returnCarrierValue ||
949
- pickupDateValue
950
- );
951
958
 
952
959
  return (
953
960
  <li
@@ -1063,100 +1070,98 @@ const OrderDetailBlock = ({
1063
1070
  </div>
1064
1071
  </div>
1065
1072
 
1066
- {(showLineMeta || lineActions) && (
1067
- <div
1068
- style={{
1069
- display: 'flex',
1070
- flexDirection: 'column',
1071
- alignItems: isMobile ? 'flex-start' : 'flex-end',
1072
- textAlign: isMobile ? 'left' : 'right',
1073
- gap: '4px',
1074
- flex: isMobile ? '1 1 100%' : '0 0 auto'
1075
- }}
1076
- >
1077
- {lineActions && (
1078
- <div
1079
- data-order-detail-item-actions=""
1080
- {...partAttrs('item-actions')}
1081
- >
1082
- {lineActions}
1083
- </div>
1084
- )}
1085
- {showLineMeta && item.status?.label && (
1086
- <span
1087
- data-order-detail-item-status=""
1088
- data-order-status-value={item.status.value}
1089
- {...partAttrs('item-status')}
1090
- style={{ display: 'inline-block' }}
1091
- >
1092
- {item.status.label}
1093
- </span>
1094
- )}
1095
- {/* Label and value in their own spans, so a
1096
- composition can colour them apart. */}
1097
- {showLineMeta && returnMethodValue && (
1098
- <span
1099
- data-order-detail-item-return-method=""
1100
- {...partAttrs('item-return-method')}
1101
- style={{ opacity: 0.8 }}
1102
- >
1103
- <span data-label="">{returnMethodLabel}</span>{' '}
1104
- <span data-value="">{returnMethodValue}</span>
1105
- </span>
1106
- )}
1107
- {showLineMeta && returnCarrierValue && (
1108
- <span
1109
- data-order-detail-item-return-carrier=""
1110
- {...partAttrs('item-return-carrier')}
1111
- style={{ opacity: 0.8 }}
1112
- >
1113
- <span data-label="">{returnCarrierLabel}</span>{' '}
1114
- <span data-value="">{returnCarrierValue}</span>
1115
- </span>
1116
- )}
1117
- {showLineMeta && pickupDateValue && (
1118
- <span
1119
- data-order-detail-item-pickup-date=""
1120
- {...partAttrs('item-pickup-date')}
1121
- style={{ opacity: 0.8 }}
1122
- >
1123
- <span data-label="">{pickupDateLabel}</span>{' '}
1124
- <span data-value="">{pickupDateValue}</span>
1125
- </span>
1126
- )}
1127
- {showLineMeta && trackingValue && (
1128
- <span
1129
- data-order-detail-item-tracking=""
1130
- {...partAttrs('item-tracking')}
1131
- style={{ opacity: 0.8 }}
1132
- >
1133
- {lineTrackingLabel}{' '}
1134
- {item.tracking_url ? (
1135
- <a
1136
- href={isDesigner ? '#' : item.tracking_url}
1137
- onClick={guard}
1138
- target={isDesigner ? undefined : '_blank'}
1139
- rel="noreferrer"
1140
- style={{ color: 'inherit' }}
1141
- >
1142
- {item.tracking_number || item.tracking_url}
1143
- </a>
1144
- ) : (
1145
- trackingValue
1146
- )}
1147
- </span>
1148
- )}
1149
- {showLineMeta && returnCodeValue && (
1150
- <span
1151
- data-order-detail-item-return-code=""
1152
- {...partAttrs('item-return-code')}
1153
- style={{ opacity: 0.8 }}
1154
- >
1155
- {returnCodeLabel} {returnCodeValue}
1156
- </span>
1157
- )}
1158
- </div>
1159
- )}
1073
+ {showShipmentStatus &&
1074
+ (item.status?.label ||
1075
+ trackingValue ||
1076
+ returnCodeValue ||
1077
+ returnMethodValue ||
1078
+ returnCarrierValue ||
1079
+ pickupDateValue) && (
1080
+ <div
1081
+ style={{
1082
+ display: 'flex',
1083
+ flexDirection: 'column',
1084
+ alignItems: isMobile ? 'flex-start' : 'flex-end',
1085
+ textAlign: isMobile ? 'left' : 'right',
1086
+ gap: '4px',
1087
+ flex: isMobile ? '1 1 100%' : '0 0 auto'
1088
+ }}
1089
+ >
1090
+ {item.status?.label && (
1091
+ <span
1092
+ data-order-detail-item-status=""
1093
+ data-order-status-value={item.status.value}
1094
+ {...partAttrs('item-status')}
1095
+ style={{ display: 'inline-block' }}
1096
+ >
1097
+ {item.status.label}
1098
+ </span>
1099
+ )}
1100
+ {/* Label and value in their own spans, so a
1101
+ composition can colour them apart. */}
1102
+ {returnMethodValue && (
1103
+ <span
1104
+ data-order-detail-item-return-method=""
1105
+ {...partAttrs('item-return-method')}
1106
+ style={{ opacity: 0.8 }}
1107
+ >
1108
+ <span data-label="">{returnMethodLabel}</span>{' '}
1109
+ <span data-value="">{returnMethodValue}</span>
1110
+ </span>
1111
+ )}
1112
+ {returnCarrierValue && (
1113
+ <span
1114
+ data-order-detail-item-return-carrier=""
1115
+ {...partAttrs('item-return-carrier')}
1116
+ style={{ opacity: 0.8 }}
1117
+ >
1118
+ <span data-label="">{returnCarrierLabel}</span>{' '}
1119
+ <span data-value="">{returnCarrierValue}</span>
1120
+ </span>
1121
+ )}
1122
+ {pickupDateValue && (
1123
+ <span
1124
+ data-order-detail-item-pickup-date=""
1125
+ {...partAttrs('item-pickup-date')}
1126
+ style={{ opacity: 0.8 }}
1127
+ >
1128
+ <span data-label="">{pickupDateLabel}</span>{' '}
1129
+ <span data-value="">{pickupDateValue}</span>
1130
+ </span>
1131
+ )}
1132
+ {trackingValue && (
1133
+ <span
1134
+ data-order-detail-item-tracking=""
1135
+ {...partAttrs('item-tracking')}
1136
+ style={{ opacity: 0.8 }}
1137
+ >
1138
+ {lineTrackingLabel}{' '}
1139
+ {item.tracking_url ? (
1140
+ <a
1141
+ href={isDesigner ? '#' : item.tracking_url}
1142
+ onClick={guard}
1143
+ target={isDesigner ? undefined : '_blank'}
1144
+ rel="noreferrer"
1145
+ style={{ color: 'inherit' }}
1146
+ >
1147
+ {item.tracking_number || item.tracking_url}
1148
+ </a>
1149
+ ) : (
1150
+ trackingValue
1151
+ )}
1152
+ </span>
1153
+ )}
1154
+ {returnCodeValue && (
1155
+ <span
1156
+ data-order-detail-item-return-code=""
1157
+ {...partAttrs('item-return-code')}
1158
+ style={{ opacity: 0.8 }}
1159
+ >
1160
+ {returnCodeLabel} {returnCodeValue}
1161
+ </span>
1162
+ )}
1163
+ </div>
1164
+ )}
1160
1165
  </div>
1161
1166
  </li>
1162
1167
  );
@@ -13,14 +13,7 @@ import { readToggle } from './product-block-utils';
13
13
  import { BlockRendererProps } from './block-renderer-registry';
14
14
 
15
15
  interface OrderItemLike {
16
- id?: number;
17
- status?: { value?: string | number; label?: string };
18
- product?: {
19
- image?: string;
20
- name?: string;
21
- sku?: string;
22
- base_code?: string;
23
- } | null;
16
+ product?: { image?: string; name?: string } | null;
24
17
  }
25
18
 
26
19
  interface OrderLike {
@@ -255,23 +248,7 @@ const OrderListBlock = ({
255
248
  maxImages
256
249
  ),
257
250
  url: hrefFor(order),
258
- href: hrefFor(order),
259
- createdDate: order.created_date || '',
260
- items: (Array.isArray(order.orderitem_set) ? order.orderitem_set : []).map(
261
- (item, itemIndex) => ({
262
- id: item?.id ?? itemIndex,
263
- name: item?.product?.name || '',
264
- image: item?.product?.image || '',
265
- status: item?.status?.label || '',
266
- statusValue: String(item?.status?.value ?? ''),
267
- product: {
268
- sku: item?.product?.sku || '',
269
- base_code: item?.product?.base_code || '',
270
- name: item?.product?.name || '',
271
- image: item?.product?.image || ''
272
- }
273
- })
274
- )
251
+ href: hrefFor(order)
275
252
  }));
276
253
 
277
254
  // A designer who marked a child as the row layout gets that layout repeated
@@ -7,7 +7,6 @@ import { getInnerRootInlineStyles, partAttrs } from '../utils';
7
7
  import { useThemeSettingsContext } from '../theme-settings-context';
8
8
  import { useLocalizeHref } from '../hooks/use-localize-href';
9
9
  import { localizeHtmlLinks } from '../utils/localize-href';
10
- import { useValueLocale } from '../utils/localized-value';
11
10
 
12
11
  const TextBlock = ({
13
12
  block,
@@ -15,7 +14,7 @@ const TextBlock = ({
15
14
  }: BlockRendererProps) => {
16
15
  const { locale } = useLocalization();
17
16
  const localize = useLocalizeHref();
18
- const { defaultLocale } = useValueLocale();
17
+ const defaultLocale = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || 'en';
19
18
  const themeSettings = useThemeSettingsContext();
20
19
 
21
20
  const getLocalizedContent = (): string => {
@@ -31,10 +30,7 @@ const TextBlock = ({
31
30
 
32
31
  if (typeof value === 'object' && value !== null) {
33
32
  return (
34
- value[locale] ||
35
- (defaultLocale && value[defaultLocale]) ||
36
- Object.values(value)[0] ||
37
- ''
33
+ value[locale] || value[defaultLocale] || Object.values(value)[0] || ''
38
34
  );
39
35
  }
40
36
 
@@ -41,7 +41,7 @@ const orderPayload = {
41
41
  orderitem_set: [
42
42
  {
43
43
  price: '649.95',
44
- status: { value: '500', label: 'Shipped' },
44
+ status: { label: 'Shipped' },
45
45
  product: {
46
46
  name: 'Spring Sneaker',
47
47
  image: '/i.jpg',
@@ -63,19 +63,6 @@ describe('buildOrderPageContext', () => {
63
63
  expect(context.order.currency).toBe('TRY');
64
64
  expect(context.order.items).toHaveLength(1);
65
65
  expect(context.order.items[0].title).toBe('Spring Sneaker');
66
- expect(context.order.items[0].status).toBe('Shipped');
67
- expect(context.order.items[0].statusValue).toBe('500');
68
- });
69
-
70
- it('stringifies a numeric order item status code', () => {
71
- const context = buildOrderPageContext({
72
- order: {
73
- ...orderPayload,
74
- orderitem_set: [{ status: { value: 550, label: 'Delivered' } }]
75
- }
76
- });
77
-
78
- expect(context.order.items[0].statusValue).toBe('550');
79
66
  });
80
67
 
81
68
  it('does not publish the identity fields the raw payload carries', () => {
@@ -59,9 +59,6 @@ const toRecord = (value: unknown): UnknownRecord =>
59
59
  const toStringValue = (value: unknown, fallback = ''): string =>
60
60
  typeof value === 'string' ? value : fallback;
61
61
 
62
- const toCodeValue = (value: unknown): string =>
63
- typeof value === 'number' ? String(value) : toStringValue(value);
64
-
65
62
  const humanizeSlug = (value: string) =>
66
63
  value
67
64
  .split('-')
@@ -482,7 +479,6 @@ export const buildOrderPageContext = ({
482
479
  unitPrice: toStringValue(item.price),
483
480
  quantity: 1,
484
481
  status: toStringValue(toRecord(item.status).label),
485
- statusValue: toCodeValue(toRecord(item.status).value),
486
482
  currency
487
483
  };
488
484
  });