@akinon/pz-theme 2.0.154-rc.5 → 2.0.154-rc.6
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
CHANGED
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.154-rc.
|
|
4
|
+
"version": "2.0.154-rc.6",
|
|
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.154-rc.
|
|
11
|
+
"@akinon/next": "2.0.154-rc.6",
|
|
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.154-rc.
|
|
16
|
+
"@akinon/theme-contract": "2.0.154-rc.6",
|
|
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.154-rc.
|
|
24
|
+
"@akinon/next": "2.0.154-rc.6",
|
|
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",
|
|
@@ -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
|
+
});
|
|
@@ -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,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
readToggle
|
|
17
18
|
} from './product-block-utils';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
type OrderItemLike = {
|
|
20
21
|
id?: number;
|
|
21
22
|
price?: string;
|
|
22
23
|
quantity?: string | number;
|
|
@@ -34,7 +35,7 @@ interface OrderItemLike {
|
|
|
34
35
|
absolute_url?: string;
|
|
35
36
|
attributes?: Record<string, unknown>;
|
|
36
37
|
} | null;
|
|
37
|
-
}
|
|
38
|
+
};
|
|
38
39
|
|
|
39
40
|
interface AddressLike {
|
|
40
41
|
first_name?: string;
|
|
@@ -272,7 +273,9 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
272
273
|
* order key is read from the URL; the editor owns which sections are exposed,
|
|
273
274
|
* every caption and the styling.
|
|
274
275
|
* A designer can mark one child as the row layout for the order lines; without
|
|
275
|
-
* one the built-in line renders.
|
|
276
|
+
* one the built-in line renders. A brand may hang per-line actions on the
|
|
277
|
+
* built-in line through `registerOrderLineActions`; they render inside the
|
|
278
|
+
* line's `item-actions` part.
|
|
276
279
|
* Properties: numberLabel, numberTag, dateLabel, headerTotalLabel,
|
|
277
280
|
* showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
|
|
278
281
|
* cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
|
|
@@ -288,7 +291,7 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
288
291
|
* currencyText, currencyPosition.
|
|
289
292
|
* Parts: detail, header, number, date, header-total, status,
|
|
290
293
|
* invoice-link, cancel-link, list, item, image, name, quantity, color, size,
|
|
291
|
-
* item-status, item-tracking, item-return-code, price, shipment,
|
|
294
|
+
* item-status, item-tracking, item-return-code, item-actions, price, shipment,
|
|
292
295
|
* delivery, tracking, addresses, address, address-label, totals, total-row,
|
|
293
296
|
* total-label, total-value, grand-total, payment-info, payment-info-label.
|
|
294
297
|
*/
|
|
@@ -758,6 +761,14 @@ const OrderDetailBlock = ({
|
|
|
758
761
|
const returnCodeValue = showReturnCode
|
|
759
762
|
? resolveReturnCode(item.active_cancellation_request)
|
|
760
763
|
: '';
|
|
764
|
+
const lineActions = renderOrderLineActions({
|
|
765
|
+
order,
|
|
766
|
+
line,
|
|
767
|
+
isDesigner
|
|
768
|
+
});
|
|
769
|
+
const showLineMeta =
|
|
770
|
+
showShipmentStatus &&
|
|
771
|
+
Boolean(item.status?.label || trackingValue || returnCodeValue);
|
|
761
772
|
|
|
762
773
|
return (
|
|
763
774
|
<li
|
|
@@ -873,63 +884,68 @@ const OrderDetailBlock = ({
|
|
|
873
884
|
</div>
|
|
874
885
|
</div>
|
|
875
886
|
|
|
876
|
-
{
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
887
|
+
{(showLineMeta || lineActions) && (
|
|
888
|
+
<div
|
|
889
|
+
style={{
|
|
890
|
+
display: 'flex',
|
|
891
|
+
flexDirection: 'column',
|
|
892
|
+
alignItems: isMobile ? 'flex-start' : 'flex-end',
|
|
893
|
+
textAlign: isMobile ? 'left' : 'right',
|
|
894
|
+
gap: '4px',
|
|
895
|
+
flex: isMobile ? '1 1 100%' : '0 0 auto'
|
|
896
|
+
}}
|
|
897
|
+
>
|
|
898
|
+
{lineActions && (
|
|
899
|
+
<div
|
|
900
|
+
data-order-detail-item-actions=""
|
|
901
|
+
{...partAttrs('item-actions')}
|
|
902
|
+
>
|
|
903
|
+
{lineActions}
|
|
904
|
+
</div>
|
|
905
|
+
)}
|
|
906
|
+
{showLineMeta && item.status?.label && (
|
|
907
|
+
<span
|
|
908
|
+
data-order-detail-item-status=""
|
|
909
|
+
data-order-status-value={item.status.value}
|
|
910
|
+
{...partAttrs('item-status')}
|
|
911
|
+
style={{ display: 'inline-block' }}
|
|
912
|
+
>
|
|
913
|
+
{item.status.label}
|
|
914
|
+
</span>
|
|
915
|
+
)}
|
|
916
|
+
{showLineMeta && trackingValue && (
|
|
917
|
+
<span
|
|
918
|
+
data-order-detail-item-tracking=""
|
|
919
|
+
{...partAttrs('item-tracking')}
|
|
920
|
+
style={{ opacity: 0.8 }}
|
|
921
|
+
>
|
|
922
|
+
{lineTrackingLabel}{' '}
|
|
923
|
+
{item.tracking_url ? (
|
|
924
|
+
<a
|
|
925
|
+
href={isDesigner ? '#' : item.tracking_url}
|
|
926
|
+
onClick={guard}
|
|
927
|
+
target={isDesigner ? undefined : '_blank'}
|
|
928
|
+
rel="noreferrer"
|
|
929
|
+
style={{ color: 'inherit' }}
|
|
930
|
+
>
|
|
931
|
+
{item.tracking_number || item.tracking_url}
|
|
932
|
+
</a>
|
|
933
|
+
) : (
|
|
934
|
+
trackingValue
|
|
935
|
+
)}
|
|
936
|
+
</span>
|
|
937
|
+
)}
|
|
938
|
+
{showLineMeta && returnCodeValue && (
|
|
939
|
+
<span
|
|
940
|
+
data-order-detail-item-return-code=""
|
|
941
|
+
{...partAttrs('item-return-code')}
|
|
942
|
+
style={{ opacity: 0.8 }}
|
|
943
|
+
>
|
|
944
|
+
{returnCodeLabel} {returnCodeValue}
|
|
945
|
+
</span>
|
|
946
|
+
)}
|
|
947
|
+
</div>
|
|
948
|
+
)}
|
|
933
949
|
</div>
|
|
934
950
|
</li>
|
|
935
951
|
);
|
|
@@ -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
|
+
};
|