@akinon/pz-theme 2.0.37-rc.0 → 2.0.38-beta.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 +15 -31
- package/package.json +4 -4
- package/src/__tests__/__snapshots__/facet-filters-block.test.tsx.snap +11 -0
- package/src/__tests__/custom-css.test.ts +181 -0
- package/src/__tests__/designer-chrome-clipping.test.tsx +497 -0
- package/src/__tests__/facet-filters-block.test.tsx +707 -0
- package/src/__tests__/item-template.test.tsx +184 -0
- package/src/__tests__/listing-controls.test.tsx +961 -0
- package/src/__tests__/part-style-scope.test.tsx +229 -0
- package/src/__tests__/part-styles.test.ts +311 -0
- package/src/__tests__/registry-parity.test.ts +42 -0
- package/src/__tests__/row-visibility.test.ts +672 -0
- package/src/__tests__/sort-select-page-cursor.test.tsx +336 -0
- package/src/__tests__/theme-block-html-id.test.tsx +167 -0
- package/src/blocks/__tests__/__snapshots__/product-gallery-block.test.tsx.snap +15 -0
- package/src/blocks/__tests__/accordion-block-state.test.tsx +201 -0
- package/src/blocks/__tests__/facet-filters-block.test.tsx +190 -0
- package/src/blocks/__tests__/listing-context-card-model.test.tsx +230 -0
- package/src/blocks/__tests__/listing-lookup-hardening.test.ts +80 -0
- package/src/blocks/__tests__/product-gallery-block.test.tsx +974 -0
- package/src/blocks/__tests__/product-grid-composition.test.tsx +570 -0
- package/src/blocks/__tests__/product-grid-lookups.test.tsx +230 -0
- package/src/blocks/__tests__/product-price-block.test.tsx +248 -0
- package/src/blocks/__tests__/product-recommendations-block.test.tsx +702 -0
- package/src/blocks/__tests__/share-block.test.tsx +392 -0
- package/src/blocks/__tests__/store-locator-block.test.tsx +691 -0
- package/src/blocks/__tests__/variant-selector-block.test.tsx +817 -0
- package/src/blocks/__tests__/variant-selector-golden.json +20 -0
- package/src/blocks/account-menu-block.tsx +264 -0
- package/src/blocks/add-to-cart-block.tsx +417 -0
- package/src/blocks/address-list-block.tsx +348 -0
- package/src/blocks/applied-filters-block.tsx +288 -0
- package/src/blocks/auth-form-block.tsx +295 -0
- package/src/blocks/basket-items-block.tsx +457 -0
- package/src/blocks/basket-summary-block.tsx +404 -0
- package/src/blocks/block-renderer-registry.tsx +94 -0
- package/src/blocks/change-password-form-block.tsx +240 -0
- package/src/blocks/checkout-steps-block.tsx +288 -0
- package/src/blocks/checkout-summary-block.tsx +449 -0
- package/src/blocks/countdown-block.tsx +182 -0
- package/src/blocks/coupon-list-block.tsx +292 -0
- package/src/blocks/facet-filters-block.tsx +1686 -0
- package/src/blocks/faq-accordion-block.tsx +358 -0
- package/src/blocks/favorite-button-block.tsx +252 -0
- package/src/blocks/favourites-grid-block.tsx +510 -0
- package/src/blocks/forgot-password-block.tsx +155 -0
- package/src/blocks/item-template-utils.ts +131 -0
- package/src/blocks/item-template.tsx +164 -0
- package/src/blocks/listing-breadcrumb-block.tsx +175 -0
- package/src/blocks/listing-context.ts +850 -0
- package/src/blocks/listing-pagination-block.tsx +923 -0
- package/src/blocks/listing-result-count-block.tsx +126 -0
- package/src/blocks/listing-title-block.tsx +115 -0
- package/src/blocks/oauth-buttons-block.tsx +205 -0
- package/src/blocks/order-detail-block.tsx +474 -0
- package/src/blocks/order-list-block.tsx +274 -0
- package/src/blocks/order-success-block.tsx +641 -0
- package/src/blocks/order-tracking-block.tsx +533 -0
- package/src/blocks/product-attributes-block.tsx +307 -0
- package/src/blocks/product-block-utils.tsx +639 -0
- package/src/blocks/product-breadcrumb-block.tsx +215 -0
- package/src/blocks/product-description-block.tsx +169 -0
- package/src/blocks/product-gallery-block.tsx +1523 -0
- package/src/blocks/product-grid-block.tsx +966 -0
- package/src/blocks/product-price-block.tsx +373 -0
- package/src/blocks/product-recommendations-block.tsx +1433 -0
- package/src/blocks/product-title-block.tsx +129 -0
- package/src/blocks/profile-form-block.tsx +297 -0
- package/src/blocks/recently-viewed-store.ts +201 -0
- package/src/blocks/register-form-block.tsx +362 -0
- package/src/blocks/share-block.tsx +667 -0
- package/src/blocks/size-guide-block.tsx +313 -0
- package/src/blocks/sort-select-block.tsx +636 -0
- package/src/blocks/stock-status-block.tsx +168 -0
- package/src/blocks/store-locator-block.tsx +1184 -0
- package/src/blocks/variant-selector-block.tsx +1110 -0
- package/src/sections/__tests__/tablet-raw-bucket-readers.test.tsx +186 -0
- package/src/utils/__tests__/part-ownership-guard.test.tsx +29 -4
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import NextLink from 'next/link';
|
|
5
|
+
import { useParams, useSearchParams } from 'next/navigation';
|
|
6
|
+
import { useGetOrderQuery } from '@akinon/next/data/client/account';
|
|
7
|
+
import { useLocalization } from '@akinon/next/hooks';
|
|
8
|
+
import { getResponsiveValue, partAttrs } from '../utils';
|
|
9
|
+
import { formatDate, formatMoney } from './commerce-format';
|
|
10
|
+
import { hasItemTemplate, ItemTemplateList } from './item-template';
|
|
11
|
+
import { BlockRendererProps } from './block-renderer-registry';
|
|
12
|
+
import {
|
|
13
|
+
DesignerPlaceholder,
|
|
14
|
+
groupOrderItems,
|
|
15
|
+
readToggle
|
|
16
|
+
} from './product-block-utils';
|
|
17
|
+
|
|
18
|
+
interface OrderItemLike {
|
|
19
|
+
id?: number;
|
|
20
|
+
price?: string;
|
|
21
|
+
quantity?: string | number;
|
|
22
|
+
total_amount?: string;
|
|
23
|
+
status?: { value?: string; label?: string };
|
|
24
|
+
tracking_url?: string;
|
|
25
|
+
tracking_number?: string;
|
|
26
|
+
estimated_delivery_date?: string;
|
|
27
|
+
price_currency?: { value?: string; label?: string };
|
|
28
|
+
product?: {
|
|
29
|
+
pk?: number;
|
|
30
|
+
name?: string;
|
|
31
|
+
image?: string;
|
|
32
|
+
absolute_url?: string;
|
|
33
|
+
} | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface OrderLike {
|
|
37
|
+
number?: string;
|
|
38
|
+
created_date?: string;
|
|
39
|
+
amount?: string;
|
|
40
|
+
amount_without_discount?: string;
|
|
41
|
+
discount_amount?: string;
|
|
42
|
+
shipping_amount?: string;
|
|
43
|
+
tracking_url?: string;
|
|
44
|
+
tracking_number?: string;
|
|
45
|
+
status?: { value?: string; label?: string };
|
|
46
|
+
currency?: { value?: string; label?: string };
|
|
47
|
+
orderitem_set?: OrderItemLike[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The order key lives in the route on account pages (`/account/orders/[id]`)
|
|
52
|
+
* but in the query string on the anonymous tracking page, and brands name the
|
|
53
|
+
* segment differently. Both sources are probed so the block works wherever it
|
|
54
|
+
* is dropped, without asking the author to configure a data path.
|
|
55
|
+
*
|
|
56
|
+
* `pk` is in the list because it is the segment name pz uses for every other
|
|
57
|
+
* detail route (product/[pk], category/[pk], flat-page/[pk]), so an order route
|
|
58
|
+
* copied from those reads its key from `pk` rather than from `id`.
|
|
59
|
+
*/
|
|
60
|
+
const ORDER_PARAM_KEYS = [
|
|
61
|
+
'id',
|
|
62
|
+
'pk',
|
|
63
|
+
'orderId',
|
|
64
|
+
'orderNumber',
|
|
65
|
+
'number',
|
|
66
|
+
'order'
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const firstString = (
|
|
70
|
+
read: (key: string) => unknown,
|
|
71
|
+
keys: string[]
|
|
72
|
+
): string => {
|
|
73
|
+
for (const key of keys) {
|
|
74
|
+
const value = read(key);
|
|
75
|
+
const text = Array.isArray(value) ? value[0] : value;
|
|
76
|
+
|
|
77
|
+
if (typeof text === 'string' && text.trim()) return text.trim();
|
|
78
|
+
if (typeof text === 'number') return String(text);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return '';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/** Positive integers only; anything else falls back to the shipped size. */
|
|
85
|
+
const readSize = (value: unknown, fallback: number): number => {
|
|
86
|
+
const numeric = Math.round(Number(value));
|
|
87
|
+
|
|
88
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Functional account block: one order's lines, totals, status and shipment
|
|
93
|
+
* info. Order CONTENT stays commerce-driven (account order service) and the
|
|
94
|
+
* order key is read from the URL; the editor owns which sections are exposed,
|
|
95
|
+
* every caption and the styling.
|
|
96
|
+
* A designer can mark one child as the row layout for the order lines; without
|
|
97
|
+
* one the built-in line renders.
|
|
98
|
+
* Properties: showShipmentStatus, showItems, showImage, imageWidth,
|
|
99
|
+
* imageHeight, showQuantity, showTotals, quantityPrefix, subtotalLabel,
|
|
100
|
+
* discountLabel, shippingLabel, totalLabel, trackingLabel.
|
|
101
|
+
* Parts: detail, header, number, date, status, list, item, image, name,
|
|
102
|
+
* quantity, item-status, price, shipment, delivery, tracking, totals,
|
|
103
|
+
* total-row, total-label, total-value, grand-total.
|
|
104
|
+
*/
|
|
105
|
+
const OrderDetailBlock = ({
|
|
106
|
+
block,
|
|
107
|
+
placeholderId,
|
|
108
|
+
sectionId,
|
|
109
|
+
selectedBlockId = null,
|
|
110
|
+
currentBreakpoint = 'desktop',
|
|
111
|
+
isDesigner = false
|
|
112
|
+
}: BlockRendererProps) => {
|
|
113
|
+
const showShipmentStatus = readToggle(
|
|
114
|
+
block.properties?.showShipmentStatus,
|
|
115
|
+
currentBreakpoint,
|
|
116
|
+
true
|
|
117
|
+
);
|
|
118
|
+
const showItems = readToggle(
|
|
119
|
+
block.properties?.showItems,
|
|
120
|
+
currentBreakpoint,
|
|
121
|
+
true
|
|
122
|
+
);
|
|
123
|
+
const showImage = readToggle(
|
|
124
|
+
block.properties?.showImage,
|
|
125
|
+
currentBreakpoint,
|
|
126
|
+
true
|
|
127
|
+
);
|
|
128
|
+
const showQuantity = readToggle(
|
|
129
|
+
block.properties?.showQuantity,
|
|
130
|
+
currentBreakpoint,
|
|
131
|
+
true
|
|
132
|
+
);
|
|
133
|
+
const showTotals = readToggle(
|
|
134
|
+
block.properties?.showTotals,
|
|
135
|
+
currentBreakpoint,
|
|
136
|
+
true
|
|
137
|
+
);
|
|
138
|
+
const imageWidth = readSize(
|
|
139
|
+
getResponsiveValue(block.properties?.imageWidth, currentBreakpoint, 72),
|
|
140
|
+
72
|
|
141
|
+
);
|
|
142
|
+
const imageHeight = readSize(
|
|
143
|
+
getResponsiveValue(block.properties?.imageHeight, currentBreakpoint, 96),
|
|
144
|
+
96
|
|
145
|
+
);
|
|
146
|
+
const text = (key: string, fallback: string) =>
|
|
147
|
+
String(
|
|
148
|
+
getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
|
|
149
|
+
);
|
|
150
|
+
const quantityPrefix = text('quantityPrefix', 'x');
|
|
151
|
+
const subtotalLabel = text('subtotalLabel', 'Subtotal');
|
|
152
|
+
const discountLabel = text('discountLabel', 'Discount');
|
|
153
|
+
const shippingLabel = text('shippingLabel', 'Shipping');
|
|
154
|
+
const totalLabel = text('totalLabel', 'Total');
|
|
155
|
+
const trackingLabel = text('trackingLabel', 'Track shipment');
|
|
156
|
+
|
|
157
|
+
const { locale, currency: localeCurrency } = useLocalization();
|
|
158
|
+
const params = useParams();
|
|
159
|
+
const searchParams = useSearchParams();
|
|
160
|
+
|
|
161
|
+
const orderKey =
|
|
162
|
+
firstString((key) => (params as Record<string, unknown>)?.[key], ORDER_PARAM_KEYS) ||
|
|
163
|
+
firstString((key) => searchParams?.get(key), ORDER_PARAM_KEYS);
|
|
164
|
+
|
|
165
|
+
// Not gated on the session: brands expose the same endpoint for anonymous
|
|
166
|
+
// order tracking. Without a key there is nothing to ask for at all.
|
|
167
|
+
const { data, isLoading } = useGetOrderQuery(orderKey, { skip: !orderKey });
|
|
168
|
+
|
|
169
|
+
const order = (data ?? null) as OrderLike | null;
|
|
170
|
+
const items = order?.orderitem_set ?? [];
|
|
171
|
+
// pz writes one order line per unit; the same shared grouping the thank-you
|
|
172
|
+
// page uses keeps a three-unit order from listing as three "x1" rows here.
|
|
173
|
+
// The raw lines are still used for the shipment section, which reads the
|
|
174
|
+
// per-line delivery estimate.
|
|
175
|
+
const groupedItems = groupOrderItems(items);
|
|
176
|
+
|
|
177
|
+
if (!order) {
|
|
178
|
+
return isDesigner ? (
|
|
179
|
+
<DesignerPlaceholder
|
|
180
|
+
label={
|
|
181
|
+
isLoading
|
|
182
|
+
? 'Order detail (loading)'
|
|
183
|
+
: orderKey
|
|
184
|
+
? 'Order detail (order not found)'
|
|
185
|
+
: 'Order detail (no order in URL)'
|
|
186
|
+
}
|
|
187
|
+
attribute="data-order-detail"
|
|
188
|
+
/>
|
|
189
|
+
) : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The order carries the currency it was placed in; the storefront currency is
|
|
193
|
+
// only the fallback for payloads that omit it.
|
|
194
|
+
const currency = order.currency?.value || localeCurrency;
|
|
195
|
+
const money = (value: unknown) => formatMoney(value, { locale, currency });
|
|
196
|
+
|
|
197
|
+
// Navigating inside the designer iframe would drop the author out of the page
|
|
198
|
+
// they are editing, so every link is inert there.
|
|
199
|
+
const guard = (event: React.MouseEvent) => {
|
|
200
|
+
if (isDesigner) event.preventDefault();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const rowStyle: React.CSSProperties = {
|
|
204
|
+
display: 'flex',
|
|
205
|
+
alignItems: 'center',
|
|
206
|
+
justifyContent: 'space-between',
|
|
207
|
+
gap: '12px'
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const lineRows = groupedItems.map((line) => {
|
|
211
|
+
const lineCurrency = line.item.price_currency?.value || currency;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
key: line.key,
|
|
215
|
+
name: line.name,
|
|
216
|
+
image: line.image,
|
|
217
|
+
url: line.href,
|
|
218
|
+
href: line.href,
|
|
219
|
+
quantity: line.quantity,
|
|
220
|
+
price: formatMoney(line.price, { locale, currency: lineCurrency }),
|
|
221
|
+
total: formatMoney(line.total, { locale, currency: lineCurrency }),
|
|
222
|
+
status: line.item.status?.label || '',
|
|
223
|
+
statusValue: line.item.status?.value || ''
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// A designer who marked a child as the row layout gets that layout repeated
|
|
228
|
+
// per order line; otherwise the built-in line renders unchanged.
|
|
229
|
+
const customLines = hasItemTemplate(block, currentBreakpoint) ? (
|
|
230
|
+
<ItemTemplateList
|
|
231
|
+
block={block}
|
|
232
|
+
items={lineRows}
|
|
233
|
+
placeholderId={placeholderId}
|
|
234
|
+
sectionId={sectionId}
|
|
235
|
+
isDesigner={isDesigner}
|
|
236
|
+
selectedBlockId={selectedBlockId}
|
|
237
|
+
currentBreakpoint={currentBreakpoint}
|
|
238
|
+
/>
|
|
239
|
+
) : null;
|
|
240
|
+
|
|
241
|
+
const totalRow = (
|
|
242
|
+
label: string,
|
|
243
|
+
value: string,
|
|
244
|
+
attribute: string,
|
|
245
|
+
part = 'total-row'
|
|
246
|
+
) =>
|
|
247
|
+
value ? (
|
|
248
|
+
<div {...{ [attribute]: '' }} {...partAttrs(part)} style={rowStyle}>
|
|
249
|
+
<span {...partAttrs('total-label')}>{label}</span>
|
|
250
|
+
<span {...partAttrs('total-value')}>{value}</span>
|
|
251
|
+
</div>
|
|
252
|
+
) : null;
|
|
253
|
+
|
|
254
|
+
return (
|
|
255
|
+
<div
|
|
256
|
+
data-order-detail=""
|
|
257
|
+
{...partAttrs('detail')}
|
|
258
|
+
style={{
|
|
259
|
+
display: 'flex',
|
|
260
|
+
flexDirection: 'column',
|
|
261
|
+
gap: 'var(--order-detail-gap, 24px)'
|
|
262
|
+
}}
|
|
263
|
+
>
|
|
264
|
+
<header
|
|
265
|
+
data-order-detail-header=""
|
|
266
|
+
{...partAttrs('header')}
|
|
267
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
|
|
268
|
+
>
|
|
269
|
+
<span
|
|
270
|
+
data-order-detail-number=""
|
|
271
|
+
{...partAttrs('number')}
|
|
272
|
+
style={{ fontWeight: 600 }}
|
|
273
|
+
>
|
|
274
|
+
{order.number || orderKey}
|
|
275
|
+
</span>
|
|
276
|
+
<span
|
|
277
|
+
data-order-detail-date=""
|
|
278
|
+
{...partAttrs('date')}
|
|
279
|
+
style={{ opacity: 0.8 }}
|
|
280
|
+
>
|
|
281
|
+
{formatDate(order.created_date, locale)}
|
|
282
|
+
</span>
|
|
283
|
+
{order.status?.label && (
|
|
284
|
+
<span
|
|
285
|
+
data-order-detail-status=""
|
|
286
|
+
// The raw status code is published so scoped custom CSS can paint
|
|
287
|
+
// one status differently from the rest.
|
|
288
|
+
data-order-status-value={order.status.value}
|
|
289
|
+
{...partAttrs('status')}
|
|
290
|
+
>
|
|
291
|
+
{order.status.label}
|
|
292
|
+
</span>
|
|
293
|
+
)}
|
|
294
|
+
</header>
|
|
295
|
+
|
|
296
|
+
{showItems && (groupedItems.length > 0 || customLines) && (
|
|
297
|
+
<ul
|
|
298
|
+
data-order-detail-items=""
|
|
299
|
+
{...partAttrs('list')}
|
|
300
|
+
style={{
|
|
301
|
+
display: 'flex',
|
|
302
|
+
flexDirection: 'column',
|
|
303
|
+
gap: '16px',
|
|
304
|
+
listStyle: 'none',
|
|
305
|
+
margin: 0,
|
|
306
|
+
padding: 0
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
{customLines ??
|
|
310
|
+
groupedItems.map((line) => {
|
|
311
|
+
// Status, currency and thumbnails are identical across the lines of
|
|
312
|
+
// a group, so the first line represents the whole row.
|
|
313
|
+
const item = line.item;
|
|
314
|
+
const name = line.name;
|
|
315
|
+
const href = line.href;
|
|
316
|
+
const lineCurrency = item.price_currency?.value || currency;
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<li
|
|
320
|
+
key={line.key}
|
|
321
|
+
data-order-detail-item=""
|
|
322
|
+
{...partAttrs('item')}
|
|
323
|
+
style={{
|
|
324
|
+
display: 'flex',
|
|
325
|
+
alignItems: 'flex-start',
|
|
326
|
+
gap: '12px',
|
|
327
|
+
paddingBottom: '16px',
|
|
328
|
+
borderBottom: '1px solid var(--order-detail-border, #e5e7eb)'
|
|
329
|
+
}}
|
|
330
|
+
>
|
|
331
|
+
{showImage && item.product?.image && (
|
|
332
|
+
<NextLink
|
|
333
|
+
href={isDesigner ? '#' : href || '#'}
|
|
334
|
+
onClick={guard}
|
|
335
|
+
style={{ flex: '0 0 auto', display: 'block' }}
|
|
336
|
+
>
|
|
337
|
+
{/* Plain img: order line thumbnails come from arbitrary
|
|
338
|
+
commerce CDNs that the brand next.config may not allow. */}
|
|
339
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
340
|
+
<img
|
|
341
|
+
src={item.product.image}
|
|
342
|
+
alt={name}
|
|
343
|
+
width={imageWidth}
|
|
344
|
+
height={imageHeight}
|
|
345
|
+
{...partAttrs('image')}
|
|
346
|
+
style={{ objectFit: 'cover', display: 'block' }}
|
|
347
|
+
/>
|
|
348
|
+
</NextLink>
|
|
349
|
+
)}
|
|
350
|
+
|
|
351
|
+
<div style={{ flex: '1 1 auto', minWidth: 0 }}>
|
|
352
|
+
<NextLink
|
|
353
|
+
href={isDesigner ? '#' : href || '#'}
|
|
354
|
+
onClick={guard}
|
|
355
|
+
data-order-detail-item-name=""
|
|
356
|
+
{...partAttrs('name')}
|
|
357
|
+
style={{ color: 'inherit', textDecoration: 'none' }}
|
|
358
|
+
>
|
|
359
|
+
{name}
|
|
360
|
+
</NextLink>
|
|
361
|
+
|
|
362
|
+
{showQuantity && (
|
|
363
|
+
<div
|
|
364
|
+
data-order-detail-item-quantity=""
|
|
365
|
+
{...partAttrs('quantity')}
|
|
366
|
+
style={{ marginTop: '4px', opacity: 0.8 }}
|
|
367
|
+
>
|
|
368
|
+
{`${quantityPrefix}${line.quantity}`}
|
|
369
|
+
</div>
|
|
370
|
+
)}
|
|
371
|
+
|
|
372
|
+
{showShipmentStatus && item.status?.label && (
|
|
373
|
+
<div
|
|
374
|
+
data-order-detail-item-status=""
|
|
375
|
+
data-order-status-value={item.status.value}
|
|
376
|
+
{...partAttrs('item-status')}
|
|
377
|
+
style={{ marginTop: '4px', opacity: 0.8 }}
|
|
378
|
+
>
|
|
379
|
+
{item.status.label}
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
<span data-order-detail-item-price="" {...partAttrs('price')}>
|
|
385
|
+
{/* The row now stands for every unit of the product, so the
|
|
386
|
+
group's summed line total is what it must price — the
|
|
387
|
+
single line's total_amount would understate an "x3" row.
|
|
388
|
+
For an ungrouped row this is the value printed before. */}
|
|
389
|
+
{formatMoney(line.total, {
|
|
390
|
+
locale,
|
|
391
|
+
currency: lineCurrency
|
|
392
|
+
})}
|
|
393
|
+
</span>
|
|
394
|
+
</li>
|
|
395
|
+
);
|
|
396
|
+
})}
|
|
397
|
+
</ul>
|
|
398
|
+
)}
|
|
399
|
+
|
|
400
|
+
{showShipmentStatus && (
|
|
401
|
+
<section
|
|
402
|
+
data-order-detail-shipment=""
|
|
403
|
+
{...partAttrs('shipment')}
|
|
404
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
|
|
405
|
+
>
|
|
406
|
+
{items.some((item) => item.estimated_delivery_date) && (
|
|
407
|
+
<span
|
|
408
|
+
data-order-detail-estimated-delivery=""
|
|
409
|
+
{...partAttrs('delivery')}
|
|
410
|
+
>
|
|
411
|
+
{formatDate(
|
|
412
|
+
items.find((item) => item.estimated_delivery_date)
|
|
413
|
+
?.estimated_delivery_date,
|
|
414
|
+
locale
|
|
415
|
+
)}
|
|
416
|
+
</span>
|
|
417
|
+
)}
|
|
418
|
+
|
|
419
|
+
{(order.tracking_number || order.tracking_url) && (
|
|
420
|
+
<span data-order-detail-tracking="" {...partAttrs('tracking')}>
|
|
421
|
+
{order.tracking_url ? (
|
|
422
|
+
// Carrier tracking is an external URL, so it stays a plain
|
|
423
|
+
// anchor — but it is guarded exactly like the internal links.
|
|
424
|
+
<a
|
|
425
|
+
href={isDesigner ? '#' : order.tracking_url}
|
|
426
|
+
onClick={guard}
|
|
427
|
+
target={isDesigner ? undefined : '_blank'}
|
|
428
|
+
rel="noreferrer"
|
|
429
|
+
style={{ color: 'inherit' }}
|
|
430
|
+
>
|
|
431
|
+
{order.tracking_number || trackingLabel}
|
|
432
|
+
</a>
|
|
433
|
+
) : (
|
|
434
|
+
order.tracking_number
|
|
435
|
+
)}
|
|
436
|
+
</span>
|
|
437
|
+
)}
|
|
438
|
+
</section>
|
|
439
|
+
)}
|
|
440
|
+
|
|
441
|
+
{showTotals && (
|
|
442
|
+
<section
|
|
443
|
+
data-order-detail-totals=""
|
|
444
|
+
{...partAttrs('totals')}
|
|
445
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}
|
|
446
|
+
>
|
|
447
|
+
{totalRow(
|
|
448
|
+
subtotalLabel,
|
|
449
|
+
money(order.amount_without_discount),
|
|
450
|
+
'data-order-detail-subtotal'
|
|
451
|
+
)}
|
|
452
|
+
{totalRow(
|
|
453
|
+
discountLabel,
|
|
454
|
+
money(order.discount_amount),
|
|
455
|
+
'data-order-detail-discount'
|
|
456
|
+
)}
|
|
457
|
+
{totalRow(
|
|
458
|
+
shippingLabel,
|
|
459
|
+
money(order.shipping_amount),
|
|
460
|
+
'data-order-detail-shipping'
|
|
461
|
+
)}
|
|
462
|
+
{totalRow(
|
|
463
|
+
totalLabel,
|
|
464
|
+
money(order.amount),
|
|
465
|
+
'data-order-detail-total',
|
|
466
|
+
'grand-total'
|
|
467
|
+
)}
|
|
468
|
+
</section>
|
|
469
|
+
)}
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
export default OrderDetailBlock;
|