@akinon/pz-theme 2.0.157-rc.13 → 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
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
# @akinon/pz-theme
|
|
2
2
|
|
|
3
|
-
## 2.0.
|
|
4
|
-
|
|
5
|
-
### Patch Changes
|
|
6
|
-
|
|
7
|
-
- Updated dependencies [ac6cada1]
|
|
8
|
-
- @akinon/next@2.0.157-rc.13
|
|
9
|
-
- @akinon/theme-contract@2.0.157-rc.13
|
|
10
|
-
|
|
11
|
-
## 2.0.157-rc.12
|
|
3
|
+
## 2.0.158-rc.14
|
|
12
4
|
|
|
13
5
|
### Patch Changes
|
|
14
6
|
|
|
@@ -17,7 +9,6 @@
|
|
|
17
9
|
- 17a598da: ZERO-5296: report the storefront default language to the editor
|
|
18
10
|
- 3b05a407: ZERO-5232: allow richtext settings on custom blocks
|
|
19
11
|
- ad5240c4: ZERO-5254: add custom render and theme editor support to pz-rate-review
|
|
20
|
-
- Updated dependencies [219164a3]
|
|
21
12
|
- Updated dependencies [86187b1c2]
|
|
22
13
|
- Updated dependencies [0cf9ea239]
|
|
23
14
|
- Updated dependencies [97d29893]
|
|
@@ -31,6 +22,7 @@
|
|
|
31
22
|
- Updated dependencies [7889b08fe]
|
|
32
23
|
- Updated dependencies [9f8cd3bc5]
|
|
33
24
|
- Updated dependencies [bfafa3f49]
|
|
25
|
+
- Updated dependencies [ac6cada1]
|
|
34
26
|
- Updated dependencies [57d7eb305]
|
|
35
27
|
- Updated dependencies [d99a6a7d5]
|
|
36
28
|
- Updated dependencies [9db81a714]
|
|
@@ -47,9 +39,18 @@
|
|
|
47
39
|
- Updated dependencies [b1111d7b1]
|
|
48
40
|
- Updated dependencies [1595d458e]
|
|
49
41
|
- Updated dependencies [dca128112]
|
|
42
|
+
- @akinon/next@2.0.158-rc.14
|
|
43
|
+
- @akinon/theme-contract@2.0.158-rc.14
|
|
44
|
+
|
|
45
|
+
## 2.0.157
|
|
46
|
+
|
|
47
|
+
### Patch Changes
|
|
48
|
+
|
|
49
|
+
- 3002c2cc: ZERO-5302: The order detail's return and cancel links offer what commerce allows and what a storefront's order-action rules let through (`registerOrderActionRules`: which lines a return may start from, whether an order may be cancelled). With no rules registered, commerce's `is_refundable` and `is_cancellable` flags decide; the block no longer limits returns to delivered lines on its own.
|
|
50
|
+
- Updated dependencies [219164a3]
|
|
50
51
|
- Updated dependencies [9c778307]
|
|
51
|
-
- @akinon/next@2.0.157
|
|
52
|
-
- @akinon/theme-contract@2.0.157
|
|
52
|
+
- @akinon/next@2.0.157
|
|
53
|
+
- @akinon/theme-contract@2.0.157
|
|
53
54
|
|
|
54
55
|
## 2.0.156
|
|
55
56
|
|
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.158-rc.14",
|
|
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.158-rc.14",
|
|
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.158-rc.14",
|
|
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.158-rc.14",
|
|
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",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render } from '@testing-library/react';
|
|
3
3
|
import OrderDetailBlock from '../order-detail-block';
|
|
4
|
+
import {
|
|
5
|
+
__resetOrderActionRulesForTests,
|
|
6
|
+
registerOrderActionRules
|
|
7
|
+
} from '../../utils/order-action-rules';
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* ZERO-5183 — the order detail is where an easy return starts and where a
|
|
@@ -64,6 +68,8 @@ const texts = (root: ParentNode, part: string) =>
|
|
|
64
68
|
(e.textContent || '').replace(/\s+/g, ' ').trim()
|
|
65
69
|
);
|
|
66
70
|
|
|
71
|
+
afterEach(() => __resetOrderActionRulesForTests());
|
|
72
|
+
|
|
67
73
|
beforeEach(() => {
|
|
68
74
|
order = {
|
|
69
75
|
id: 42,
|
|
@@ -164,7 +170,7 @@ describe('order detail — easy returns', () => {
|
|
|
164
170
|
expect(container.querySelector('[data-part="return-link"]')).toBeNull();
|
|
165
171
|
});
|
|
166
172
|
|
|
167
|
-
it('links
|
|
173
|
+
it('links a refundable line that has no request yet', () => {
|
|
168
174
|
// As commerce answers after a return: the order and its lines stay
|
|
169
175
|
// refundable while the request is open.
|
|
170
176
|
const flagged: Record<string, unknown>[] = (
|
|
@@ -194,15 +200,74 @@ describe('order detail — easy returns', () => {
|
|
|
194
200
|
};
|
|
195
201
|
expect(returnLink()).toBeNull();
|
|
196
202
|
|
|
197
|
-
// Commerce calls a shipped line refundable too
|
|
203
|
+
// Commerce calls a shipped line refundable too, and with no rule from
|
|
204
|
+
// the brand that is what decides (ZERO-5302).
|
|
198
205
|
order = {
|
|
199
206
|
...order,
|
|
200
207
|
orderitem_set: flagged.map((item) =>
|
|
201
|
-
item.id === 3
|
|
202
|
-
? { ...item, status: { value: '500', label: 'Gönderildi' } }
|
|
203
|
-
: item
|
|
208
|
+
item.id === 3 ? { ...item, status: { value: '500' } } : item
|
|
204
209
|
)
|
|
205
210
|
};
|
|
211
|
+
expect(returnLink()).not.toBeNull();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("asks the brand's rules about a line commerce lets go back (ZERO-5302)", () => {
|
|
215
|
+
const asked: string[] = [];
|
|
216
|
+
registerOrderActionRules({
|
|
217
|
+
returnLine: (item) => {
|
|
218
|
+
asked.push(String(item.status?.value));
|
|
219
|
+
return item.status?.value === '550';
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const returnLink = () =>
|
|
223
|
+
render(
|
|
224
|
+
<OrderDetailBlock
|
|
225
|
+
block={blockOf(ALL_ON) as never}
|
|
226
|
+
placeholderId="p1"
|
|
227
|
+
sectionId="s1"
|
|
228
|
+
isDesigner={false}
|
|
229
|
+
/>
|
|
230
|
+
).container.querySelector('[data-part="return-link"]');
|
|
231
|
+
const withLine3 = (status: string) => ({
|
|
232
|
+
...order,
|
|
233
|
+
orderitem_set: (order?.orderitem_set as Record<string, unknown>[]).map(
|
|
234
|
+
(item) =>
|
|
235
|
+
item.id === 3
|
|
236
|
+
? { ...item, is_refundable: true, status: { value: status } }
|
|
237
|
+
: { ...item, is_refundable: false }
|
|
238
|
+
)
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
order = withLine3('550');
|
|
242
|
+
expect(returnLink()).not.toBeNull();
|
|
243
|
+
|
|
244
|
+
order = withLine3('500');
|
|
206
245
|
expect(returnLink()).toBeNull();
|
|
246
|
+
// Only the line commerce lets go back is put to the brand.
|
|
247
|
+
expect(new Set(asked)).toEqual(new Set(['550', '500']));
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('withholds the cancel link when the brand switches cancellation off (ZERO-5302)', () => {
|
|
251
|
+
order = { ...order, is_cancellable: true };
|
|
252
|
+
const cancelLink = () =>
|
|
253
|
+
render(
|
|
254
|
+
<OrderDetailBlock
|
|
255
|
+
block={
|
|
256
|
+
blockOf({
|
|
257
|
+
...ALL_ON,
|
|
258
|
+
showCancelLink: { desktop: true },
|
|
259
|
+
cancelLinkLabel: { desktop: 'Cancel' }
|
|
260
|
+
}) as never
|
|
261
|
+
}
|
|
262
|
+
placeholderId="p1"
|
|
263
|
+
sectionId="s1"
|
|
264
|
+
isDesigner={false}
|
|
265
|
+
/>
|
|
266
|
+
).container.querySelector('[data-part="cancel-link"]');
|
|
267
|
+
|
|
268
|
+
expect(cancelLink()).not.toBeNull();
|
|
269
|
+
|
|
270
|
+
registerOrderActionRules({ cancelOrder: () => false });
|
|
271
|
+
expect(cancelLink()).toBeNull();
|
|
207
272
|
});
|
|
208
273
|
});
|
|
@@ -16,6 +16,14 @@ import {
|
|
|
16
16
|
groupOrderItems,
|
|
17
17
|
readToggle
|
|
18
18
|
} from './product-block-utils';
|
|
19
|
+
import {
|
|
20
|
+
allowsLineReturn,
|
|
21
|
+
allowsOrderCancel,
|
|
22
|
+
type OrderActionLine,
|
|
23
|
+
type OrderActionOrder,
|
|
24
|
+
type OrderActionRules,
|
|
25
|
+
useOrderActionRules
|
|
26
|
+
} from '../utils/order-action-rules';
|
|
19
27
|
|
|
20
28
|
type OrderItemLike = {
|
|
21
29
|
id?: number;
|
|
@@ -118,31 +126,39 @@ const firstString = (
|
|
|
118
126
|
const hasOpenRequest = (request: unknown): boolean =>
|
|
119
127
|
Array.isArray(request) ? request.length > 0 : Boolean(request);
|
|
120
128
|
|
|
121
|
-
/** Commerce's "Teslim edildi". */
|
|
122
|
-
const DELIVERED = '550';
|
|
123
|
-
|
|
124
129
|
/**
|
|
125
|
-
* Whether the order still has a line to return:
|
|
126
|
-
* no request open on it
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
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.
|
|
131
138
|
*/
|
|
132
|
-
const hasReturnableLine = (
|
|
139
|
+
const hasReturnableLine = (
|
|
140
|
+
order: OrderLike,
|
|
141
|
+
rules: OrderActionRules
|
|
142
|
+
): boolean => {
|
|
133
143
|
if (!order.is_refundable) return false;
|
|
134
144
|
|
|
135
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
|
+
);
|
|
136
152
|
|
|
137
153
|
if (!lines.some((item) => item && 'is_refundable' in item)) {
|
|
138
|
-
return
|
|
154
|
+
return lines.length === 0 ? allows(null) : lines.some(allows);
|
|
139
155
|
}
|
|
140
156
|
|
|
141
157
|
return lines.some(
|
|
142
158
|
(item) =>
|
|
143
|
-
String(item?.status?.value ?? '') === DELIVERED &&
|
|
144
159
|
item?.is_refundable &&
|
|
145
|
-
!hasOpenRequest(item.active_cancellation_request)
|
|
160
|
+
!hasOpenRequest(item.active_cancellation_request) &&
|
|
161
|
+
allows(item)
|
|
146
162
|
);
|
|
147
163
|
};
|
|
148
164
|
|
|
@@ -373,9 +389,9 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
|
373
389
|
* order key is read from the URL; the editor owns which sections are exposed,
|
|
374
390
|
* every caption and the styling.
|
|
375
391
|
* A designer can mark one child as the row layout for the order lines; without
|
|
376
|
-
* one the built-in line renders.
|
|
377
|
-
*
|
|
378
|
-
*
|
|
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).
|
|
379
395
|
* Properties: numberLabel, numberTag, dateLabel, headerTotalLabel,
|
|
380
396
|
* showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
|
|
381
397
|
* showReturnLink, returnLinkLabel, cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
|
|
@@ -591,6 +607,8 @@ const OrderDetailBlock = ({
|
|
|
591
607
|
// Not gated on the session: brands expose the same endpoint for anonymous
|
|
592
608
|
// order tracking. Without a key there is nothing to ask for at all.
|
|
593
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();
|
|
594
612
|
|
|
595
613
|
const liveOrder = (data ?? null) as OrderLike | null;
|
|
596
614
|
// See DESIGNER_SAMPLE_ORDER's own comment: the preview route's order id is
|
|
@@ -624,11 +642,16 @@ const OrderDetailBlock = ({
|
|
|
624
642
|
const money = (value: unknown) =>
|
|
625
643
|
formatMoney(value, { locale, currency, ...moneyStyle });
|
|
626
644
|
|
|
627
|
-
// The sample order
|
|
645
|
+
// The sample order shows both links for styling, whatever the brand's
|
|
646
|
+
// rules would make of it.
|
|
628
647
|
const offersReturn =
|
|
629
648
|
order === DESIGNER_SAMPLE_ORDER
|
|
630
649
|
? Boolean(order.is_refundable)
|
|
631
|
-
: 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));
|
|
632
655
|
|
|
633
656
|
// Navigating inside the designer iframe would drop the author out of the page
|
|
634
657
|
// they are editing, so every link is inert there.
|
|
@@ -833,7 +856,7 @@ const OrderDetailBlock = ({
|
|
|
833
856
|
→ {invoiceLinkLabel}
|
|
834
857
|
</a>
|
|
835
858
|
)}
|
|
836
|
-
{showCancelLink &&
|
|
859
|
+
{showCancelLink && offersCancel && cancelHref && (
|
|
837
860
|
<NextLink
|
|
838
861
|
href={isDesigner ? '#' : cancelHref}
|
|
839
862
|
onClick={guard}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { act, render, screen } from '@testing-library/react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
__resetOrderActionRulesForTests,
|
|
6
|
+
allowsLineReturn,
|
|
7
|
+
allowsOrderCancel,
|
|
8
|
+
registerOrderActionRules,
|
|
9
|
+
useOrderActionRules
|
|
10
|
+
} from '../order-action-rules';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The brand's say over the order actions a storefront offers (ZERO-5302):
|
|
14
|
+
* asked only about what commerce already allows; with no rule, commerce's
|
|
15
|
+
* flags decide.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const line = (status: string) => ({ id: 1, status: { value: status } });
|
|
19
|
+
const order = { id: 7, number: 'O-7' };
|
|
20
|
+
|
|
21
|
+
afterEach(() => __resetOrderActionRulesForTests());
|
|
22
|
+
|
|
23
|
+
describe('order action rules', () => {
|
|
24
|
+
it('leaves the decision to commerce when no brand registered rules', () => {
|
|
25
|
+
expect(allowsLineReturn({}, line('500'), order)).toBe(true);
|
|
26
|
+
expect(allowsOrderCancel({}, order)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('lets a brand narrow returns and cancellations', () => {
|
|
30
|
+
const rules = {
|
|
31
|
+
returnLine: (item: { status?: { value?: string } | null }) =>
|
|
32
|
+
item.status?.value === '550',
|
|
33
|
+
cancelOrder: () => false
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
expect(allowsLineReturn(rules, line('550'), order)).toBe(true);
|
|
37
|
+
expect(allowsLineReturn(rules, line('500'), order)).toBe(false);
|
|
38
|
+
expect(allowsOrderCancel(rules, order)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('gives a rule that throws no say', () => {
|
|
42
|
+
const rules = {
|
|
43
|
+
returnLine: () => {
|
|
44
|
+
throw new Error('brand bug');
|
|
45
|
+
},
|
|
46
|
+
cancelOrder: () => {
|
|
47
|
+
throw new Error('brand bug');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
expect(allowsLineReturn(rules, line('550'), order)).toBe(true);
|
|
52
|
+
expect(allowsOrderCancel(rules, order)).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('renders a reader again when the rules are registered or dropped', () => {
|
|
56
|
+
const Reader = () => {
|
|
57
|
+
const rules = useOrderActionRules();
|
|
58
|
+
return (
|
|
59
|
+
<span data-testid="reader">
|
|
60
|
+
{allowsLineReturn(rules, line('500'), order) ? 'offers' : 'withholds'}
|
|
61
|
+
</span>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
render(<Reader />);
|
|
65
|
+
expect(screen.getByTestId('reader')).toHaveTextContent('offers');
|
|
66
|
+
|
|
67
|
+
let unregister = () => {};
|
|
68
|
+
act(() => {
|
|
69
|
+
unregister = registerOrderActionRules({ returnLine: () => false });
|
|
70
|
+
});
|
|
71
|
+
expect(screen.getByTestId('reader')).toHaveTextContent('withholds');
|
|
72
|
+
|
|
73
|
+
act(() => unregister());
|
|
74
|
+
expect(screen.getByTestId('reader')).toHaveTextContent('offers');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('keeps rules registered later when an older registration unregisters', () => {
|
|
78
|
+
const first = registerOrderActionRules({ returnLine: () => false });
|
|
79
|
+
const second = { returnLine: () => true };
|
|
80
|
+
registerOrderActionRules(second);
|
|
81
|
+
|
|
82
|
+
first();
|
|
83
|
+
|
|
84
|
+
expect(allowsLineReturn(second, line('500'), order)).toBe(true);
|
|
85
|
+
const Reader = () => {
|
|
86
|
+
const rules = useOrderActionRules();
|
|
87
|
+
return (
|
|
88
|
+
<span data-testid="current">
|
|
89
|
+
{rules === second ? 'second' : 'other'}
|
|
90
|
+
</span>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
render(<Reader />);
|
|
94
|
+
expect(screen.getByTestId('current')).toHaveTextContent('second');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Brand hook for the order actions a storefront offers (ZERO-5302).
|
|
5
|
+
*
|
|
6
|
+
* Commerce says what it allows: a line it will take back is flagged
|
|
7
|
+
* `is_refundable`, an order it will cancel `is_cancellable`. Whether the
|
|
8
|
+
* storefront offers that return or that cancellation is a brand decision on
|
|
9
|
+
* top — from which statuses, for how long after the order, whether the action
|
|
10
|
+
* is switched on at all — so the order blocks never learn it. A brand
|
|
11
|
+
* registers its rules from its own code (the pattern of the search-term
|
|
12
|
+
* transform), and the blocks ask them only about what commerce already
|
|
13
|
+
* allows. No registration, or a rule left out, and commerce's flags decide.
|
|
14
|
+
* A rule that throws has no say: a brand bug must not take the page down.
|
|
15
|
+
*
|
|
16
|
+
* Rules may be registered again when what they depend on arrives (a brand
|
|
17
|
+
* reading its settings from a widget, say); every block reading them through
|
|
18
|
+
* `useOrderActionRules` renders again.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface OrderActionLine {
|
|
22
|
+
id?: number | string;
|
|
23
|
+
status?: { value?: string; label?: string } | null;
|
|
24
|
+
created_date?: string | null;
|
|
25
|
+
is_refundable?: boolean;
|
|
26
|
+
is_cancellable?: boolean;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OrderActionOrder {
|
|
31
|
+
id?: number | string;
|
|
32
|
+
number?: string;
|
|
33
|
+
created_date?: string | null;
|
|
34
|
+
status?: { value?: string; label?: string } | null;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OrderActionRules {
|
|
39
|
+
/**
|
|
40
|
+
* Whether a return may start from this line. Asked only about a line
|
|
41
|
+
* commerce flags refundable and that has no request open on it.
|
|
42
|
+
*/
|
|
43
|
+
returnLine?: (line: OrderActionLine, order: OrderActionOrder) => boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Whether the order may be cancelled from the storefront. Asked only about
|
|
46
|
+
* an order commerce flags cancellable.
|
|
47
|
+
*/
|
|
48
|
+
cancelOrder?: (order: OrderActionOrder) => boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const NO_RULES: OrderActionRules = {};
|
|
52
|
+
|
|
53
|
+
let current: OrderActionRules = NO_RULES;
|
|
54
|
+
const listeners = new Set<() => void>();
|
|
55
|
+
|
|
56
|
+
const notify = () => listeners.forEach((listener) => listener());
|
|
57
|
+
|
|
58
|
+
/** Register the brand's rules. Returns an unregister function. */
|
|
59
|
+
export const registerOrderActionRules = (
|
|
60
|
+
rules: OrderActionRules
|
|
61
|
+
): (() => void) => {
|
|
62
|
+
current = rules;
|
|
63
|
+
notify();
|
|
64
|
+
return () => {
|
|
65
|
+
if (current !== rules) return;
|
|
66
|
+
current = NO_RULES;
|
|
67
|
+
notify();
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const subscribe = (listener: () => void) => {
|
|
72
|
+
listeners.add(listener);
|
|
73
|
+
return () => {
|
|
74
|
+
listeners.delete(listener);
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const getSnapshot = () => current;
|
|
79
|
+
const getServerSnapshot = () => NO_RULES;
|
|
80
|
+
|
|
81
|
+
/** The rules in force; the caller renders again when they are replaced. */
|
|
82
|
+
export const useOrderActionRules = (): OrderActionRules =>
|
|
83
|
+
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
84
|
+
|
|
85
|
+
/** Whether the brand lets a return start from a line commerce allows. */
|
|
86
|
+
export const allowsLineReturn = (
|
|
87
|
+
rules: OrderActionRules,
|
|
88
|
+
line: OrderActionLine,
|
|
89
|
+
order: OrderActionOrder
|
|
90
|
+
): boolean => {
|
|
91
|
+
if (!rules.returnLine) return true;
|
|
92
|
+
try {
|
|
93
|
+
return rules.returnLine(line, order) !== false;
|
|
94
|
+
} catch {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Whether the brand lets an order commerce allows be cancelled. */
|
|
100
|
+
export const allowsOrderCancel = (
|
|
101
|
+
rules: OrderActionRules,
|
|
102
|
+
order: OrderActionOrder
|
|
103
|
+
): boolean => {
|
|
104
|
+
if (!rules.cancelOrder) return true;
|
|
105
|
+
try {
|
|
106
|
+
return rules.cancelOrder(order) !== false;
|
|
107
|
+
} catch {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Test-only: drop the registered rules between cases. */
|
|
113
|
+
export const __resetOrderActionRulesForTests = (): void => {
|
|
114
|
+
current = NO_RULES;
|
|
115
|
+
};
|