@akinon/pz-theme 2.0.168 → 2.0.169-rc.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 +54 -0
- package/package.json +4 -4
- package/src/__tests__/editor-locale-bridge.test.tsx +32 -1
- package/src/blocks/__tests__/order-line-actions.test.tsx +54 -0
- package/src/blocks/__tests__/text-block.test.tsx +34 -6
- package/src/blocks/order-detail-block.tsx +113 -95
- package/src/blocks/order-line-actions.ts +45 -0
- package/src/blocks/text-block.tsx +6 -2
- package/src/custom/README.md +100 -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__/sanitize-rich-text.test.ts +80 -0
- package/src/utils/sanitize-rich-text.ts +215 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# @akinon/pz-theme
|
|
2
2
|
|
|
3
|
+
## 2.0.169-rc.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e3c2bab2: ZERO-5521: restore the text block's useValueLocale import and the /api/auth no-store header rule that the automatic RC sync dropped
|
|
8
|
+
- 8e040731e: ZERO-5235: let brands hang actions on order detail lines
|
|
9
|
+
- c761b9fd4: ZERO-5296: fall back to the storefront default language in the text block
|
|
10
|
+
- 17a598dae: ZERO-5296: report the storefront default language to the editor
|
|
11
|
+
- 3b05a407d: ZERO-5232: allow richtext settings on custom blocks
|
|
12
|
+
- Updated dependencies [86187b1c2]
|
|
13
|
+
- Updated dependencies [08390a87]
|
|
14
|
+
- Updated dependencies [0cf9ea239]
|
|
15
|
+
- Updated dependencies [324f97d55]
|
|
16
|
+
- Updated dependencies [4c3465ae]
|
|
17
|
+
- Updated dependencies [51ea06888]
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- Updated dependencies [549da545]
|
|
20
|
+
- Updated dependencies [2fea41430]
|
|
21
|
+
- Updated dependencies [b55acb768]
|
|
22
|
+
- Updated dependencies [760258c1c]
|
|
23
|
+
- Updated dependencies [143be2b9d]
|
|
24
|
+
- Updated dependencies [7889b08fe]
|
|
25
|
+
- Updated dependencies [9f8cd3bc5]
|
|
26
|
+
- Updated dependencies [d635d345e]
|
|
27
|
+
- Updated dependencies [bfafa3f49]
|
|
28
|
+
- Updated dependencies [d860c917]
|
|
29
|
+
- Updated dependencies [ac6cada1c]
|
|
30
|
+
- Updated dependencies [e90c0e02]
|
|
31
|
+
- Updated dependencies [dc7f9ca5]
|
|
32
|
+
- Updated dependencies [57d7eb305]
|
|
33
|
+
- Updated dependencies [0145dc3f]
|
|
34
|
+
- Updated dependencies [d99a6a7d5]
|
|
35
|
+
- Updated dependencies [92086b29]
|
|
36
|
+
- Updated dependencies [227b74086]
|
|
37
|
+
- Updated dependencies [9db81a714]
|
|
38
|
+
- Updated dependencies [e3c2bab2]
|
|
39
|
+
- Updated dependencies [591e345e1]
|
|
40
|
+
- Updated dependencies [4de5303c5]
|
|
41
|
+
- Updated dependencies [95b139dc1]
|
|
42
|
+
- Updated dependencies [1d00f2d06]
|
|
43
|
+
- Updated dependencies [4ac7b2a1e]
|
|
44
|
+
- Updated dependencies [4998a9631]
|
|
45
|
+
- Updated dependencies [804d2bd6c]
|
|
46
|
+
- Updated dependencies [3909d3224]
|
|
47
|
+
- Updated dependencies [6a3d8a631]
|
|
48
|
+
- Updated dependencies [e18836b20]
|
|
49
|
+
- Updated dependencies [b1111d7b1]
|
|
50
|
+
- Updated dependencies [1595d458e]
|
|
51
|
+
- Updated dependencies [dca128112]
|
|
52
|
+
- Updated dependencies [9a7b0937]
|
|
53
|
+
- Updated dependencies [8853822c]
|
|
54
|
+
- @akinon/next@2.0.169-rc.0
|
|
55
|
+
- @akinon/theme-contract@2.0.169-rc.0
|
|
56
|
+
|
|
3
57
|
## 2.0.168
|
|
4
58
|
|
|
5
59
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akinon/pz-theme",
|
|
3
3
|
"description": "Theme package for Project Zero Next — ThemePlaceholder system and theme editor infrastructure",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.169-rc.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/index.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "jest"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
|
-
"@akinon/next": "2.0.
|
|
11
|
+
"@akinon/next": "2.0.169-rc.0",
|
|
12
12
|
"react": "^18.0.0 || ^19.0.0",
|
|
13
13
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@akinon/theme-contract": "2.0.
|
|
16
|
+
"@akinon/theme-contract": "2.0.169-rc.0",
|
|
17
17
|
"clsx": "2.1.1",
|
|
18
18
|
"lottie-web": "^5.13.0",
|
|
19
19
|
"react-multi-carousel": "2.8.4",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"zod": "4.4.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@akinon/next": "2.0.
|
|
24
|
+
"@akinon/next": "2.0.169-rc.0",
|
|
25
25
|
"@testing-library/jest-dom": "5.16.4",
|
|
26
26
|
"@testing-library/react": "16.3.2",
|
|
27
27
|
"@testing-library/user-event": "14.1.1",
|
|
@@ -28,6 +28,7 @@ const SETTINGS_LOCALES = [
|
|
|
28
28
|
const mockLocalization = {
|
|
29
29
|
locale: 'tr',
|
|
30
30
|
locales: SETTINGS_LOCALES as Array<Record<string, unknown>>,
|
|
31
|
+
defaultLocaleValue: 'tr',
|
|
31
32
|
setLocale: jest.fn()
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -106,6 +107,7 @@ beforeEach(() => {
|
|
|
106
107
|
postMessage.mockClear();
|
|
107
108
|
mockLocalization.locale = 'tr';
|
|
108
109
|
mockLocalization.locales = SETTINGS_LOCALES;
|
|
110
|
+
mockLocalization.defaultLocaleValue = 'tr';
|
|
109
111
|
mockLocalization.setLocale = jest.fn();
|
|
110
112
|
delete (window as { __iframeReadySent?: boolean }).__iframeReadySent;
|
|
111
113
|
});
|
|
@@ -144,7 +146,8 @@ describe('inside the editor', () => {
|
|
|
144
146
|
{ value: 'en', label: 'English' },
|
|
145
147
|
{ value: 'tr', label: 'Türkçe' },
|
|
146
148
|
{ value: 'de', label: 'de' }
|
|
147
|
-
]
|
|
149
|
+
],
|
|
150
|
+
defaultLocale: 'tr'
|
|
148
151
|
}
|
|
149
152
|
})
|
|
150
153
|
]);
|
|
@@ -154,6 +157,34 @@ describe('inside the editor', () => {
|
|
|
154
157
|
expect(targetOrigin).toBe(EDITOR_ORIGIN);
|
|
155
158
|
});
|
|
156
159
|
|
|
160
|
+
it('posts the languages before the document reports ready', () => {
|
|
161
|
+
// The editor holds every per-language write until IFRAME_READY and takes
|
|
162
|
+
// "ready without languages" to mean they are never coming, so the order
|
|
163
|
+
// of the two frames is part of the contract.
|
|
164
|
+
render(placeholder('header'));
|
|
165
|
+
|
|
166
|
+
const types = postMessage.mock.calls.map(([message]) => message?.type);
|
|
167
|
+
expect(types).toContain('LOCALE_UPDATE');
|
|
168
|
+
expect(types).toContain('IFRAME_READY');
|
|
169
|
+
expect(types.indexOf('LOCALE_UPDATE')).toBeLessThan(
|
|
170
|
+
types.indexOf('IFRAME_READY')
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("posts the brand's default language, whichever it is", () => {
|
|
175
|
+
// The editor's per-language text controls take the plain-string form to
|
|
176
|
+
// mean this language; left to guess, the editor assumed English.
|
|
177
|
+
mockLocalization.defaultLocaleValue = 'de';
|
|
178
|
+
|
|
179
|
+
render(<EditorLocaleBridge />);
|
|
180
|
+
|
|
181
|
+
expect(localeFrames()).toEqual([
|
|
182
|
+
expect.objectContaining({
|
|
183
|
+
data: expect.objectContaining({ locale: 'tr', defaultLocale: 'de' })
|
|
184
|
+
})
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
|
|
157
188
|
it("follows the editor's switch exactly once, with several placeholders mounted", () => {
|
|
158
189
|
render(
|
|
159
190
|
<>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
registerOrderLineActions,
|
|
4
|
+
renderOrderLineActions
|
|
5
|
+
} from '../order-line-actions';
|
|
6
|
+
|
|
7
|
+
const context = {
|
|
8
|
+
order: { id: 7, number: '20920397829', created_date: '2026-10-01' },
|
|
9
|
+
line: {
|
|
10
|
+
key: '1',
|
|
11
|
+
name: 'Product',
|
|
12
|
+
image: '',
|
|
13
|
+
href: '',
|
|
14
|
+
price: '100',
|
|
15
|
+
total: 100,
|
|
16
|
+
quantity: 1,
|
|
17
|
+
item: { id: 1, status: { value: '550', label: 'Delivered' } }
|
|
18
|
+
},
|
|
19
|
+
isDesigner: false
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('order line actions registry', () => {
|
|
23
|
+
it('renders nothing until a brand registers', () => {
|
|
24
|
+
expect(renderOrderLineActions(context)).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('hands the line and order to the registered renderer', () => {
|
|
28
|
+
const renderer = jest.fn(() => <span>action</span>);
|
|
29
|
+
const unregister = registerOrderLineActions(renderer);
|
|
30
|
+
|
|
31
|
+
expect(renderOrderLineActions(context)).toEqual(<span>action</span>);
|
|
32
|
+
expect(renderer).toHaveBeenCalledWith(context);
|
|
33
|
+
|
|
34
|
+
unregister();
|
|
35
|
+
expect(renderOrderLineActions(context)).toBeNull();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('swallows a throwing renderer so a brand bug cannot break the line', () => {
|
|
39
|
+
const unregister = registerOrderLineActions(() => {
|
|
40
|
+
throw new Error('boom');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(renderOrderLineActions(context)).toBeNull();
|
|
44
|
+
unregister();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('only forgets the renderer it was asked to forget', () => {
|
|
48
|
+
const first = registerOrderLineActions(() => 'first');
|
|
49
|
+
registerOrderLineActions(() => 'second');
|
|
50
|
+
|
|
51
|
+
first();
|
|
52
|
+
expect(renderOrderLineActions(context)).toBe('second');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -2,9 +2,31 @@ import React from 'react';
|
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import TextBlock from '../text-block';
|
|
4
4
|
|
|
5
|
-
// The
|
|
6
|
-
//
|
|
7
|
-
//
|
|
5
|
+
// The page's language and the storefront's default, as the localization
|
|
6
|
+
// provider reports them. Most cases run on an English-default store showing
|
|
7
|
+
// English; the fallback case moves both.
|
|
8
|
+
const mockLocalization = { locale: 'en-us', defaultLocaleValue: 'en-us' };
|
|
9
|
+
|
|
10
|
+
jest.mock('@akinon/next/hooks', () => ({
|
|
11
|
+
__esModule: true,
|
|
12
|
+
useLocalization: () => ({
|
|
13
|
+
t: (path: string) => path,
|
|
14
|
+
setLocale: () => {},
|
|
15
|
+
setCurrency: () => {},
|
|
16
|
+
locale: mockLocalization.locale,
|
|
17
|
+
currency: 'USD',
|
|
18
|
+
locales: [{ value: 'en-us' }, { value: 'tr-tr' }],
|
|
19
|
+
currencies: ['USD'],
|
|
20
|
+
defaultLocaleValue: mockLocalization.defaultLocaleValue,
|
|
21
|
+
defaultCurrencyCode: 'USD',
|
|
22
|
+
localeUrlStrategy: 'hide-default-locale'
|
|
23
|
+
})
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
mockLocalization.locale = 'en-us';
|
|
28
|
+
mockLocalization.defaultLocaleValue = 'en-us';
|
|
29
|
+
});
|
|
8
30
|
|
|
9
31
|
const renderBlock = (value: unknown, styles?: Record<string, unknown>) => {
|
|
10
32
|
const block: any = { id: 'b1', type: 'text', value, styles };
|
|
@@ -34,10 +56,16 @@ describe('TextBlock value-shape resolution', () => {
|
|
|
34
56
|
);
|
|
35
57
|
});
|
|
36
58
|
|
|
37
|
-
it(
|
|
38
|
-
|
|
59
|
+
it("falls back to the storefront's default language when the active one is absent", () => {
|
|
60
|
+
// A Turkish-default store showing German. The entry under `en` is what
|
|
61
|
+
// the block used to show: its default was read from an env variable
|
|
62
|
+
// that falls back to English, not from the storefront (ZERO-5296).
|
|
63
|
+
mockLocalization.locale = 'de-de';
|
|
64
|
+
mockLocalization.defaultLocaleValue = 'tr-tr';
|
|
65
|
+
|
|
66
|
+
const { container } = renderBlock({ en: 'EN', 'tr-tr': 'TR' });
|
|
39
67
|
expect((container.querySelector('div') as HTMLDivElement).innerHTML).toBe(
|
|
40
|
-
'
|
|
68
|
+
'TR'
|
|
41
69
|
);
|
|
42
70
|
});
|
|
43
71
|
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
ITEM_TEMPLATE_ROLE,
|
|
14
14
|
ItemTemplateList
|
|
15
15
|
} from './item-template';
|
|
16
|
+
import { renderOrderLineActions } from './order-line-actions';
|
|
16
17
|
import { BlockRendererProps } from './block-renderer-registry';
|
|
17
18
|
import type { Block } from '../theme-block';
|
|
18
19
|
import {
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
useOrderActionRules
|
|
30
31
|
} from '../utils/order-action-rules';
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
type OrderItemLike = {
|
|
33
34
|
id?: number;
|
|
34
35
|
price?: string;
|
|
35
36
|
quantity?: string | number;
|
|
@@ -51,7 +52,7 @@ interface OrderItemLike {
|
|
|
51
52
|
absolute_url?: string;
|
|
52
53
|
attributes?: Record<string, unknown>;
|
|
53
54
|
} | null;
|
|
54
|
-
}
|
|
55
|
+
};
|
|
55
56
|
|
|
56
57
|
interface AddressLike {
|
|
57
58
|
first_name?: string;
|
|
@@ -430,7 +431,7 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
430
431
|
* currencyText, currencyPosition.
|
|
431
432
|
* Parts: detail, header, number, date, header-total, status,
|
|
432
433
|
* invoice-link, cancel-link, return-link, list, item, image, name, quantity,
|
|
433
|
-
* color, size, item-status, item-tracking, item-return-code,
|
|
434
|
+
* color, size, item-status, item-tracking, item-return-code, item-actions,
|
|
434
435
|
* item-return-method, item-return-carrier, item-pickup-date, price, shipment,
|
|
435
436
|
* delivery, tracking, addresses, address, address-label, totals, total-row,
|
|
436
437
|
* total-label, total-value, grand-total, payment-info, payment-info-label.
|
|
@@ -1131,6 +1132,21 @@ const OrderDetailBlock = ({
|
|
|
1131
1132
|
showPickupDate && returnDetails.pickupDate
|
|
1132
1133
|
? formatPickupDate(returnDetails.pickupDate, locale)
|
|
1133
1134
|
: '';
|
|
1135
|
+
const lineActions = renderOrderLineActions({
|
|
1136
|
+
order,
|
|
1137
|
+
line,
|
|
1138
|
+
isDesigner
|
|
1139
|
+
});
|
|
1140
|
+
const showLineMeta =
|
|
1141
|
+
showShipmentStatus &&
|
|
1142
|
+
Boolean(
|
|
1143
|
+
item.status?.label ||
|
|
1144
|
+
trackingValue ||
|
|
1145
|
+
returnCodeValue ||
|
|
1146
|
+
returnMethodValue ||
|
|
1147
|
+
returnCarrierValue ||
|
|
1148
|
+
pickupDateValue
|
|
1149
|
+
);
|
|
1134
1150
|
|
|
1135
1151
|
return (
|
|
1136
1152
|
<li
|
|
@@ -1246,98 +1262,100 @@ const OrderDetailBlock = ({
|
|
|
1246
1262
|
</div>
|
|
1247
1263
|
</div>
|
|
1248
1264
|
|
|
1249
|
-
{
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
</span>
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
</span>
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
</span>
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1265
|
+
{(showLineMeta || lineActions) && (
|
|
1266
|
+
<div
|
|
1267
|
+
style={{
|
|
1268
|
+
display: 'flex',
|
|
1269
|
+
flexDirection: 'column',
|
|
1270
|
+
alignItems: isMobile ? 'flex-start' : 'flex-end',
|
|
1271
|
+
textAlign: isMobile ? 'left' : 'right',
|
|
1272
|
+
gap: '4px',
|
|
1273
|
+
flex: isMobile ? '1 1 100%' : '0 0 auto'
|
|
1274
|
+
}}
|
|
1275
|
+
>
|
|
1276
|
+
{lineActions && (
|
|
1277
|
+
<div
|
|
1278
|
+
data-order-detail-item-actions=""
|
|
1279
|
+
{...partAttrs('item-actions')}
|
|
1280
|
+
>
|
|
1281
|
+
{lineActions}
|
|
1282
|
+
</div>
|
|
1283
|
+
)}
|
|
1284
|
+
{showLineMeta && item.status?.label && (
|
|
1285
|
+
<span
|
|
1286
|
+
data-order-detail-item-status=""
|
|
1287
|
+
data-order-status-value={item.status.value}
|
|
1288
|
+
{...partAttrs('item-status')}
|
|
1289
|
+
style={{ display: 'inline-block' }}
|
|
1290
|
+
>
|
|
1291
|
+
{item.status.label}
|
|
1292
|
+
</span>
|
|
1293
|
+
)}
|
|
1294
|
+
{/* Label and value in their own spans, so a
|
|
1295
|
+
composition can colour them apart. */}
|
|
1296
|
+
{showLineMeta && returnMethodValue && (
|
|
1297
|
+
<span
|
|
1298
|
+
data-order-detail-item-return-method=""
|
|
1299
|
+
{...partAttrs('item-return-method')}
|
|
1300
|
+
style={{ opacity: 0.8 }}
|
|
1301
|
+
>
|
|
1302
|
+
<span data-label="">{returnMethodLabel}</span>{' '}
|
|
1303
|
+
<span data-value="">{returnMethodValue}</span>
|
|
1304
|
+
</span>
|
|
1305
|
+
)}
|
|
1306
|
+
{showLineMeta && returnCarrierValue && (
|
|
1307
|
+
<span
|
|
1308
|
+
data-order-detail-item-return-carrier=""
|
|
1309
|
+
{...partAttrs('item-return-carrier')}
|
|
1310
|
+
style={{ opacity: 0.8 }}
|
|
1311
|
+
>
|
|
1312
|
+
<span data-label="">{returnCarrierLabel}</span>{' '}
|
|
1313
|
+
<span data-value="">{returnCarrierValue}</span>
|
|
1314
|
+
</span>
|
|
1315
|
+
)}
|
|
1316
|
+
{showLineMeta && pickupDateValue && (
|
|
1317
|
+
<span
|
|
1318
|
+
data-order-detail-item-pickup-date=""
|
|
1319
|
+
{...partAttrs('item-pickup-date')}
|
|
1320
|
+
style={{ opacity: 0.8 }}
|
|
1321
|
+
>
|
|
1322
|
+
<span data-label="">{pickupDateLabel}</span>{' '}
|
|
1323
|
+
<span data-value="">{pickupDateValue}</span>
|
|
1324
|
+
</span>
|
|
1325
|
+
)}
|
|
1326
|
+
{showLineMeta && trackingValue && (
|
|
1327
|
+
<span
|
|
1328
|
+
data-order-detail-item-tracking=""
|
|
1329
|
+
{...partAttrs('item-tracking')}
|
|
1330
|
+
style={{ opacity: 0.8 }}
|
|
1331
|
+
>
|
|
1332
|
+
{lineTrackingLabel}{' '}
|
|
1333
|
+
{item.tracking_url ? (
|
|
1334
|
+
<a
|
|
1335
|
+
href={isDesigner ? '#' : item.tracking_url}
|
|
1336
|
+
onClick={guard}
|
|
1337
|
+
target={isDesigner ? undefined : '_blank'}
|
|
1338
|
+
rel="noreferrer"
|
|
1339
|
+
style={{ color: 'inherit' }}
|
|
1340
|
+
>
|
|
1341
|
+
{item.tracking_number || item.tracking_url}
|
|
1342
|
+
</a>
|
|
1343
|
+
) : (
|
|
1344
|
+
trackingValue
|
|
1345
|
+
)}
|
|
1346
|
+
</span>
|
|
1347
|
+
)}
|
|
1348
|
+
{showLineMeta && returnCodeValue && (
|
|
1349
|
+
<span
|
|
1350
|
+
data-order-detail-item-return-code=""
|
|
1351
|
+
{...partAttrs('item-return-code')}
|
|
1352
|
+
style={{ opacity: 0.8 }}
|
|
1353
|
+
>
|
|
1354
|
+
{returnCodeLabel} {returnCodeValue}
|
|
1355
|
+
</span>
|
|
1356
|
+
)}
|
|
1357
|
+
</div>
|
|
1358
|
+
)}
|
|
1341
1359
|
</div>
|
|
1342
1360
|
</li>
|
|
1343
1361
|
);
|
|
@@ -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
|
+
};
|
|
@@ -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
|
// Rich-text element defaults from the theme settings.
|
|
11
12
|
import {
|
|
12
13
|
THEME_RICH_TEXT_ATTRIBUTE,
|
|
@@ -19,7 +20,7 @@ const TextBlock = ({
|
|
|
19
20
|
}: BlockRendererProps) => {
|
|
20
21
|
const { locale } = useLocalization();
|
|
21
22
|
const localize = useLocalizeHref();
|
|
22
|
-
const defaultLocale =
|
|
23
|
+
const { defaultLocale } = useValueLocale();
|
|
23
24
|
const themeSettings = useThemeSettingsContext();
|
|
24
25
|
|
|
25
26
|
const getLocalizedContent = (): string => {
|
|
@@ -35,7 +36,10 @@ const TextBlock = ({
|
|
|
35
36
|
|
|
36
37
|
if (typeof value === 'object' && value !== null) {
|
|
37
38
|
return (
|
|
38
|
-
value[locale] ||
|
|
39
|
+
value[locale] ||
|
|
40
|
+
(defaultLocale && value[defaultLocale]) ||
|
|
41
|
+
Object.values(value)[0] ||
|
|
42
|
+
''
|
|
39
43
|
);
|
|
40
44
|
}
|
|
41
45
|
|