@akinon/pz-theme 2.0.157 → 2.0.158-rc.14
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 +44 -0
- package/package.json +4 -4
- package/src/__tests__/editor-locale-bridge.test.tsx +32 -1
- package/src/__tests__/row-scope.test.tsx +118 -0
- package/src/blocks/__tests__/order-detail-return.test.tsx +70 -5
- package/src/blocks/__tests__/order-line-actions.test.tsx +54 -0
- package/src/blocks/__tests__/order-list-rows.test.tsx +112 -0
- package/src/blocks/__tests__/text-block.test.tsx +34 -6
- package/src/blocks/item-template.tsx +25 -19
- package/src/blocks/order-detail-block.tsx +155 -112
- package/src/blocks/order-line-actions.ts +45 -0
- package/src/blocks/order-list-block.tsx +25 -2
- package/src/blocks/row-scope.tsx +35 -0
- package/src/blocks/text-block.tsx +6 -2
- package/src/chrome/__tests__/page-context-builders.test.ts +14 -1
- package/src/chrome/page-context.ts +4 -0
- package/src/custom/README.md +120 -219
- package/src/custom/__tests__/use-theme-setting.test.tsx +97 -0
- package/src/custom/collect.ts +4 -9
- package/src/custom/property-types.ts +15 -15
- package/src/custom/use-theme-setting.ts +21 -5
- package/src/editor-locale-bridge.tsx +39 -11
- package/src/theme-placeholder-wrapper.tsx +3 -0
- package/src/utils/__tests__/order-action-rules.test.tsx +96 -0
- package/src/utils/__tests__/sanitize-rich-text.test.ts +80 -0
- package/src/utils/order-action-rules.ts +115 -0
- package/src/utils/sanitize-rich-text.ts +215 -0
|
@@ -9,14 +9,23 @@ 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,
|
|
15
16
|
groupOrderItems,
|
|
16
17
|
readToggle
|
|
17
18
|
} from './product-block-utils';
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
import {
|
|
20
|
+
allowsLineReturn,
|
|
21
|
+
allowsOrderCancel,
|
|
22
|
+
type OrderActionLine,
|
|
23
|
+
type OrderActionOrder,
|
|
24
|
+
type OrderActionRules,
|
|
25
|
+
useOrderActionRules
|
|
26
|
+
} from '../utils/order-action-rules';
|
|
27
|
+
|
|
28
|
+
type OrderItemLike = {
|
|
20
29
|
id?: number;
|
|
21
30
|
price?: string;
|
|
22
31
|
quantity?: string | number;
|
|
@@ -35,7 +44,7 @@ interface OrderItemLike {
|
|
|
35
44
|
absolute_url?: string;
|
|
36
45
|
attributes?: Record<string, unknown>;
|
|
37
46
|
} | null;
|
|
38
|
-
}
|
|
47
|
+
};
|
|
39
48
|
|
|
40
49
|
interface AddressLike {
|
|
41
50
|
first_name?: string;
|
|
@@ -117,31 +126,39 @@ const firstString = (
|
|
|
117
126
|
const hasOpenRequest = (request: unknown): boolean =>
|
|
118
127
|
Array.isArray(request) ? request.length > 0 : Boolean(request);
|
|
119
128
|
|
|
120
|
-
/** Commerce's "Teslim edildi". */
|
|
121
|
-
const DELIVERED = '550';
|
|
122
|
-
|
|
123
129
|
/**
|
|
124
|
-
* Whether the order still has a line to return:
|
|
125
|
-
* no request open on it
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
+
* Whether the order still has a line to return: one commerce flags
|
|
131
|
+
* refundable, with no request open on it, that the brand's rules let a return
|
|
132
|
+
* start from (`registerOrderActionRules`: from which statuses, for how long
|
|
133
|
+
* after the order — ZERO-5302). Commerce keeps `is_refundable` true on the
|
|
134
|
+
* order and the line while a return request is open, so the order flag alone
|
|
135
|
+
* would offer a return again after the shopper has asked. Lines that carry no
|
|
136
|
+
* flag at all (an older payload) go by the order's flag, still through the
|
|
137
|
+
* brand's rules.
|
|
130
138
|
*/
|
|
131
|
-
const hasReturnableLine = (
|
|
139
|
+
const hasReturnableLine = (
|
|
140
|
+
order: OrderLike,
|
|
141
|
+
rules: OrderActionRules
|
|
142
|
+
): boolean => {
|
|
132
143
|
if (!order.is_refundable) return false;
|
|
133
144
|
|
|
134
145
|
const lines = order.orderitem_set ?? [];
|
|
146
|
+
const allows = (item: OrderItemLike | null | undefined) =>
|
|
147
|
+
allowsLineReturn(
|
|
148
|
+
rules,
|
|
149
|
+
(item ?? {}) as OrderActionLine,
|
|
150
|
+
order as OrderActionOrder
|
|
151
|
+
);
|
|
135
152
|
|
|
136
153
|
if (!lines.some((item) => item && 'is_refundable' in item)) {
|
|
137
|
-
return
|
|
154
|
+
return lines.length === 0 ? allows(null) : lines.some(allows);
|
|
138
155
|
}
|
|
139
156
|
|
|
140
157
|
return lines.some(
|
|
141
158
|
(item) =>
|
|
142
|
-
String(item?.status?.value ?? '') === DELIVERED &&
|
|
143
159
|
item?.is_refundable &&
|
|
144
|
-
!hasOpenRequest(item.active_cancellation_request)
|
|
160
|
+
!hasOpenRequest(item.active_cancellation_request) &&
|
|
161
|
+
allows(item)
|
|
145
162
|
);
|
|
146
163
|
};
|
|
147
164
|
|
|
@@ -373,6 +390,8 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
373
390
|
* every caption and the styling.
|
|
374
391
|
* A designer can mark one child as the row layout for the order lines; without
|
|
375
392
|
* one the built-in line renders.
|
|
393
|
+
* The cancel and return links offer what commerce allows and the brand's
|
|
394
|
+
* `registerOrderActionRules` lets through (ZERO-5302).
|
|
376
395
|
* Properties: numberLabel, numberTag, dateLabel, headerTotalLabel,
|
|
377
396
|
* showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
|
|
378
397
|
* showReturnLink, returnLinkLabel, cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
|
|
@@ -390,7 +409,7 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
390
409
|
* currencyText, currencyPosition.
|
|
391
410
|
* Parts: detail, header, number, date, header-total, status,
|
|
392
411
|
* invoice-link, cancel-link, return-link, list, item, image, name, quantity,
|
|
393
|
-
* color, size, item-status, item-tracking, item-return-code,
|
|
412
|
+
* color, size, item-status, item-tracking, item-return-code, item-actions,
|
|
394
413
|
* item-return-method, item-return-carrier, item-pickup-date, price, shipment,
|
|
395
414
|
* delivery, tracking, addresses, address, address-label, totals, total-row,
|
|
396
415
|
* total-label, total-value, grand-total, payment-info, payment-info-label.
|
|
@@ -588,6 +607,8 @@ const OrderDetailBlock = ({
|
|
|
588
607
|
// Not gated on the session: brands expose the same endpoint for anonymous
|
|
589
608
|
// order tracking. Without a key there is nothing to ask for at all.
|
|
590
609
|
const { data, isLoading } = useGetOrderQuery(orderKey, { skip: !orderKey });
|
|
610
|
+
// The brand's say over the return and cancel links (ZERO-5302).
|
|
611
|
+
const actionRules = useOrderActionRules();
|
|
591
612
|
|
|
592
613
|
const liveOrder = (data ?? null) as OrderLike | null;
|
|
593
614
|
// See DESIGNER_SAMPLE_ORDER's own comment: the preview route's order id is
|
|
@@ -621,11 +642,16 @@ const OrderDetailBlock = ({
|
|
|
621
642
|
const money = (value: unknown) =>
|
|
622
643
|
formatMoney(value, { locale, currency, ...moneyStyle });
|
|
623
644
|
|
|
624
|
-
// The sample order
|
|
645
|
+
// The sample order shows both links for styling, whatever the brand's
|
|
646
|
+
// rules would make of it.
|
|
625
647
|
const offersReturn =
|
|
626
648
|
order === DESIGNER_SAMPLE_ORDER
|
|
627
649
|
? Boolean(order.is_refundable)
|
|
628
|
-
: hasReturnableLine(order);
|
|
650
|
+
: hasReturnableLine(order, actionRules);
|
|
651
|
+
const offersCancel =
|
|
652
|
+
Boolean(order.is_cancellable) &&
|
|
653
|
+
(order === DESIGNER_SAMPLE_ORDER ||
|
|
654
|
+
allowsOrderCancel(actionRules, order as OrderActionOrder));
|
|
629
655
|
|
|
630
656
|
// Navigating inside the designer iframe would drop the author out of the page
|
|
631
657
|
// they are editing, so every link is inert there.
|
|
@@ -830,7 +856,7 @@ const OrderDetailBlock = ({
|
|
|
830
856
|
→ {invoiceLinkLabel}
|
|
831
857
|
</a>
|
|
832
858
|
)}
|
|
833
|
-
{showCancelLink &&
|
|
859
|
+
{showCancelLink && offersCancel && cancelHref && (
|
|
834
860
|
<NextLink
|
|
835
861
|
href={isDesigner ? '#' : cancelHref}
|
|
836
862
|
onClick={guard}
|
|
@@ -930,6 +956,21 @@ const OrderDetailBlock = ({
|
|
|
930
956
|
showPickupDate && returnDetails.pickupDate
|
|
931
957
|
? formatPickupDate(returnDetails.pickupDate, locale)
|
|
932
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
|
+
);
|
|
933
974
|
|
|
934
975
|
return (
|
|
935
976
|
<li
|
|
@@ -1045,98 +1086,100 @@ const OrderDetailBlock = ({
|
|
|
1045
1086
|
</div>
|
|
1046
1087
|
</div>
|
|
1047
1088
|
|
|
1048
|
-
{
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
</span>
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
</span>
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
</span>
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
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
|
+
)}
|
|
1140
1183
|
</div>
|
|
1141
1184
|
</li>
|
|
1142
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
|
-
|
|
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 =
|
|
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] ||
|
|
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
|
});
|