@akinon/pz-theme 2.0.161-beta.0 → 2.0.161-rc.22

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,6 +9,7 @@ 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';
12
13
  import { BlockRendererProps } from './block-renderer-registry';
13
14
  import {
14
15
  DesignerPlaceholder,
@@ -24,7 +25,7 @@ import {
24
25
  useOrderActionRules
25
26
  } from '../utils/order-action-rules';
26
27
 
27
- interface OrderItemLike {
28
+ type OrderItemLike = {
28
29
  id?: number;
29
30
  price?: string;
30
31
  quantity?: string | number;
@@ -43,7 +44,7 @@ interface OrderItemLike {
43
44
  absolute_url?: string;
44
45
  attributes?: Record<string, unknown>;
45
46
  } | null;
46
- }
47
+ };
47
48
 
48
49
  interface AddressLike {
49
50
  first_name?: string;
@@ -408,7 +409,7 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
408
409
  * currencyText, currencyPosition.
409
410
  * Parts: detail, header, number, date, header-total, status,
410
411
  * invoice-link, cancel-link, return-link, list, item, image, name, quantity,
411
- * color, size, item-status, item-tracking, item-return-code,
412
+ * color, size, item-status, item-tracking, item-return-code, item-actions,
412
413
  * item-return-method, item-return-carrier, item-pickup-date, price, shipment,
413
414
  * delivery, tracking, addresses, address, address-label, totals, total-row,
414
415
  * total-label, total-value, grand-total, payment-info, payment-info-label.
@@ -955,6 +956,21 @@ const OrderDetailBlock = ({
955
956
  showPickupDate && returnDetails.pickupDate
956
957
  ? formatPickupDate(returnDetails.pickupDate, locale)
957
958
  : '';
959
+ const lineActions = renderOrderLineActions({
960
+ order,
961
+ line,
962
+ isDesigner
963
+ });
964
+ const showLineMeta =
965
+ showShipmentStatus &&
966
+ Boolean(
967
+ item.status?.label ||
968
+ trackingValue ||
969
+ returnCodeValue ||
970
+ returnMethodValue ||
971
+ returnCarrierValue ||
972
+ pickupDateValue
973
+ );
958
974
 
959
975
  return (
960
976
  <li
@@ -1070,98 +1086,100 @@ const OrderDetailBlock = ({
1070
1086
  </div>
1071
1087
  </div>
1072
1088
 
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
- )}
1089
+ {(showLineMeta || lineActions) && (
1090
+ <div
1091
+ style={{
1092
+ display: 'flex',
1093
+ flexDirection: 'column',
1094
+ alignItems: isMobile ? 'flex-start' : 'flex-end',
1095
+ textAlign: isMobile ? 'left' : 'right',
1096
+ gap: '4px',
1097
+ flex: isMobile ? '1 1 100%' : '0 0 auto'
1098
+ }}
1099
+ >
1100
+ {lineActions && (
1101
+ <div
1102
+ data-order-detail-item-actions=""
1103
+ {...partAttrs('item-actions')}
1104
+ >
1105
+ {lineActions}
1106
+ </div>
1107
+ )}
1108
+ {showLineMeta && item.status?.label && (
1109
+ <span
1110
+ data-order-detail-item-status=""
1111
+ data-order-status-value={item.status.value}
1112
+ {...partAttrs('item-status')}
1113
+ style={{ display: 'inline-block' }}
1114
+ >
1115
+ {item.status.label}
1116
+ </span>
1117
+ )}
1118
+ {/* Label and value in their own spans, so a
1119
+ composition can colour them apart. */}
1120
+ {showLineMeta && returnMethodValue && (
1121
+ <span
1122
+ data-order-detail-item-return-method=""
1123
+ {...partAttrs('item-return-method')}
1124
+ style={{ opacity: 0.8 }}
1125
+ >
1126
+ <span data-label="">{returnMethodLabel}</span>{' '}
1127
+ <span data-value="">{returnMethodValue}</span>
1128
+ </span>
1129
+ )}
1130
+ {showLineMeta && returnCarrierValue && (
1131
+ <span
1132
+ data-order-detail-item-return-carrier=""
1133
+ {...partAttrs('item-return-carrier')}
1134
+ style={{ opacity: 0.8 }}
1135
+ >
1136
+ <span data-label="">{returnCarrierLabel}</span>{' '}
1137
+ <span data-value="">{returnCarrierValue}</span>
1138
+ </span>
1139
+ )}
1140
+ {showLineMeta && pickupDateValue && (
1141
+ <span
1142
+ data-order-detail-item-pickup-date=""
1143
+ {...partAttrs('item-pickup-date')}
1144
+ style={{ opacity: 0.8 }}
1145
+ >
1146
+ <span data-label="">{pickupDateLabel}</span>{' '}
1147
+ <span data-value="">{pickupDateValue}</span>
1148
+ </span>
1149
+ )}
1150
+ {showLineMeta && trackingValue && (
1151
+ <span
1152
+ data-order-detail-item-tracking=""
1153
+ {...partAttrs('item-tracking')}
1154
+ style={{ opacity: 0.8 }}
1155
+ >
1156
+ {lineTrackingLabel}{' '}
1157
+ {item.tracking_url ? (
1158
+ <a
1159
+ href={isDesigner ? '#' : item.tracking_url}
1160
+ onClick={guard}
1161
+ target={isDesigner ? undefined : '_blank'}
1162
+ rel="noreferrer"
1163
+ style={{ color: 'inherit' }}
1164
+ >
1165
+ {item.tracking_number || item.tracking_url}
1166
+ </a>
1167
+ ) : (
1168
+ trackingValue
1169
+ )}
1170
+ </span>
1171
+ )}
1172
+ {showLineMeta && returnCodeValue && (
1173
+ <span
1174
+ data-order-detail-item-return-code=""
1175
+ {...partAttrs('item-return-code')}
1176
+ style={{ opacity: 0.8 }}
1177
+ >
1178
+ {returnCodeLabel} {returnCodeValue}
1179
+ </span>
1180
+ )}
1181
+ </div>
1182
+ )}
1165
1183
  </div>
1166
1184
  </li>
1167
1185
  );
@@ -0,0 +1,45 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { GroupedOrderLine, OrderLineLike } from './product-block-utils';
3
+
4
+ export interface OrderLineActionsOrder {
5
+ id?: number;
6
+ number?: string;
7
+ created_date?: string;
8
+ }
9
+
10
+ export type OrderLineActionsItem = OrderLineLike & {
11
+ status?: { value?: string; label?: string };
12
+ [key: string]: unknown;
13
+ };
14
+
15
+ export interface OrderLineActionsContext {
16
+ order: OrderLineActionsOrder;
17
+ line: GroupedOrderLine<OrderLineActionsItem>;
18
+ isDesigner: boolean;
19
+ }
20
+
21
+ export type OrderLineActionsRenderer = (
22
+ context: OrderLineActionsContext
23
+ ) => ReactNode;
24
+
25
+ let renderer: OrderLineActionsRenderer | null = null;
26
+
27
+ export const registerOrderLineActions = (
28
+ fn: OrderLineActionsRenderer
29
+ ): (() => void) => {
30
+ renderer = fn;
31
+ return () => {
32
+ if (renderer === fn) renderer = null;
33
+ };
34
+ };
35
+
36
+ export const renderOrderLineActions = (
37
+ context: OrderLineActionsContext
38
+ ): ReactNode => {
39
+ if (!renderer) return null;
40
+ try {
41
+ return renderer(context) ?? null;
42
+ } catch {
43
+ return null;
44
+ }
45
+ };
@@ -13,7 +13,14 @@ import { readToggle } from './product-block-utils';
13
13
  import { BlockRendererProps } from './block-renderer-registry';
14
14
 
15
15
  interface OrderItemLike {
16
- product?: { image?: string; name?: string } | null;
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;
17
24
  }
18
25
 
19
26
  interface OrderLike {
@@ -248,7 +255,23 @@ const OrderListBlock = ({
248
255
  maxImages
249
256
  ),
250
257
  url: hrefFor(order),
251
- href: 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
+ )
252
275
  }));
253
276
 
254
277
  // A designer who marked a child as the row layout gets that layout repeated
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+
5
+ export interface ThemeRowScopeValue {
6
+ listType: string;
7
+ row: Record<string, unknown>;
8
+ index: number;
9
+ }
10
+
11
+ const ThemeRowScopeContext = React.createContext<ThemeRowScopeValue | null>(
12
+ null
13
+ );
14
+
15
+ export const ThemeRowScopeProvider = ({
16
+ listType,
17
+ row,
18
+ index,
19
+ children
20
+ }: ThemeRowScopeValue & { children: React.ReactNode }) => {
21
+ const value = React.useMemo(
22
+ () => ({ listType, row, index }),
23
+ [listType, row, index]
24
+ );
25
+
26
+ return (
27
+ <ThemeRowScopeContext.Provider value={value}>
28
+ {children}
29
+ </ThemeRowScopeContext.Provider>
30
+ );
31
+ };
32
+
33
+ /** Non-null while a repeated row of any list renders; null everywhere else. */
34
+ export const useThemeRowScope = (): ThemeRowScopeValue | null =>
35
+ React.useContext(ThemeRowScopeContext);
@@ -7,6 +7,7 @@ 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';
10
11
 
11
12
  const TextBlock = ({
12
13
  block,
@@ -14,7 +15,7 @@ const TextBlock = ({
14
15
  }: BlockRendererProps) => {
15
16
  const { locale } = useLocalization();
16
17
  const localize = useLocalizeHref();
17
- const defaultLocale = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || 'en';
18
+ const { defaultLocale } = useValueLocale();
18
19
  const themeSettings = useThemeSettingsContext();
19
20
 
20
21
  const getLocalizedContent = (): string => {
@@ -30,7 +31,10 @@ const TextBlock = ({
30
31
 
31
32
  if (typeof value === 'object' && value !== null) {
32
33
  return (
33
- value[locale] || value[defaultLocale] || Object.values(value)[0] || ''
34
+ value[locale] ||
35
+ (defaultLocale && value[defaultLocale]) ||
36
+ Object.values(value)[0] ||
37
+ ''
34
38
  );
35
39
  }
36
40
 
@@ -41,7 +41,7 @@ const orderPayload = {
41
41
  orderitem_set: [
42
42
  {
43
43
  price: '649.95',
44
- status: { label: 'Shipped' },
44
+ status: { value: '500', label: 'Shipped' },
45
45
  product: {
46
46
  name: 'Spring Sneaker',
47
47
  image: '/i.jpg',
@@ -63,6 +63,19 @@ 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');
66
79
  });
67
80
 
68
81
  it('does not publish the identity fields the raw payload carries', () => {
@@ -59,6 +59,9 @@ 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
+
62
65
  const humanizeSlug = (value: string) =>
63
66
  value
64
67
  .split('-')
@@ -479,6 +482,7 @@ export const buildOrderPageContext = ({
479
482
  unitPrice: toStringValue(item.price),
480
483
  quantity: 1,
481
484
  status: toStringValue(toRecord(item.status).label),
485
+ statusValue: toCodeValue(toRecord(item.status).value),
482
486
  currency
483
487
  };
484
488
  });