@akinon/pz-theme 2.0.64-rc.0 → 2.0.65-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/blocks/__tests__/account-button-block.test.tsx +115 -0
- package/src/blocks/account-button-block.tsx +39 -3
- package/src/blocks/order-detail-block.tsx +600 -124
- package/src/blocks/order-list-block.tsx +477 -101
- package/src/chrome/__tests__/theme-config-source.test.ts +3 -0
- package/src/overlays/__tests__/modal-preview-host.test.tsx +36 -0
- package/src/overlays/__tests__/modal-registry.test.ts +37 -0
- package/src/overlays/modal-preview-host.tsx +6 -0
- package/src/overlays/modal-registry.ts +47 -2
- package/src/overlays/overlay-surface.tsx +76 -6
- package/src/overlays/theme-modal-mount.tsx +6 -0
|
@@ -30,10 +30,26 @@ interface OrderItemLike {
|
|
|
30
30
|
name?: string;
|
|
31
31
|
image?: string;
|
|
32
32
|
absolute_url?: string;
|
|
33
|
+
attributes?: Record<string, unknown>;
|
|
33
34
|
} | null;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
interface AddressLike {
|
|
38
|
+
first_name?: string;
|
|
39
|
+
last_name?: string;
|
|
40
|
+
line?: string;
|
|
41
|
+
township?: { name?: string } | null;
|
|
42
|
+
district?: { name?: string } | null;
|
|
43
|
+
city?: { name?: string } | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface DiscountItemLike {
|
|
47
|
+
name?: string;
|
|
48
|
+
amount?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
interface OrderLike {
|
|
52
|
+
id?: number;
|
|
37
53
|
number?: string;
|
|
38
54
|
created_date?: string;
|
|
39
55
|
amount?: string;
|
|
@@ -45,6 +61,14 @@ interface OrderLike {
|
|
|
45
61
|
status?: { value?: string; label?: string };
|
|
46
62
|
currency?: { value?: string; label?: string };
|
|
47
63
|
orderitem_set?: OrderItemLike[];
|
|
64
|
+
discountitem_set?: DiscountItemLike[];
|
|
65
|
+
is_cancellable?: boolean;
|
|
66
|
+
e_archive_url?: string | null;
|
|
67
|
+
shipping_address?: AddressLike | null;
|
|
68
|
+
billing_address?: AddressLike | null;
|
|
69
|
+
bank?: { name?: string } | null;
|
|
70
|
+
payment_option?: { name?: string } | null;
|
|
71
|
+
installment_count?: number;
|
|
48
72
|
}
|
|
49
73
|
|
|
50
74
|
/**
|
|
@@ -88,6 +112,135 @@ const readSize = (value: unknown, fallback: number): number => {
|
|
|
88
112
|
return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
|
|
89
113
|
};
|
|
90
114
|
|
|
115
|
+
/**
|
|
116
|
+
* pz-dynamic-widget's page picker points the designer at a fixed, almost
|
|
117
|
+
* certainly non-existent order id (`PREVIEW_ORDER_PK`) so the route mounts
|
|
118
|
+
* without depending on the preview session having a real order — deliberate,
|
|
119
|
+
* per that file's own comment. The trade-off lands here: without a sample
|
|
120
|
+
* object, every section below (header, lines, addresses, totals, payment)
|
|
121
|
+
* would sit behind one "order not found" box, unstylable. This mirrors
|
|
122
|
+
* `DESIGNER_SAMPLE_PRODUCT`'s role for item-template rows, just for the
|
|
123
|
+
* whole order. Never reached outside `isDesigner`, so real shoppers never
|
|
124
|
+
* see it. The color/size attribute keys are duplicated under a few common
|
|
125
|
+
* names so a brand's configured key is more likely to resolve to something
|
|
126
|
+
* out of the box.
|
|
127
|
+
*/
|
|
128
|
+
const DESIGNER_SAMPLE_ORDER: OrderLike = {
|
|
129
|
+
// Never 0 — `order.id ? ... : ''` further down would read a real falsy
|
|
130
|
+
// zero as "no id" and hide the cancellation link.
|
|
131
|
+
id: 999999,
|
|
132
|
+
number: '20920397829',
|
|
133
|
+
created_date: '2026-01-15T10:00:00Z',
|
|
134
|
+
amount: '4099.20',
|
|
135
|
+
amount_without_discount: '4599.00',
|
|
136
|
+
discount_amount: '3497.00',
|
|
137
|
+
shipping_amount: '0.00',
|
|
138
|
+
status: { value: '10', label: 'Approved' },
|
|
139
|
+
currency: { value: 'try', label: 'TRY' },
|
|
140
|
+
is_cancellable: true,
|
|
141
|
+
e_archive_url: '#',
|
|
142
|
+
installment_count: 3,
|
|
143
|
+
orderitem_set: [
|
|
144
|
+
{
|
|
145
|
+
id: 1,
|
|
146
|
+
price: '2299.50',
|
|
147
|
+
quantity: 1,
|
|
148
|
+
total_amount: '2299.50',
|
|
149
|
+
status: { value: '10', label: 'Approved' },
|
|
150
|
+
price_currency: { value: 'try', label: 'TRY' },
|
|
151
|
+
product: {
|
|
152
|
+
pk: 1,
|
|
153
|
+
name: 'Sample Product',
|
|
154
|
+
image: 'https://placehold.co/220x300',
|
|
155
|
+
absolute_url: '#',
|
|
156
|
+
attributes: {
|
|
157
|
+
color: 'Navy',
|
|
158
|
+
size: 'M',
|
|
159
|
+
filterable_color: 'Navy',
|
|
160
|
+
integration_renk_detay: 'Navy',
|
|
161
|
+
filtrenebilir_beden: 'M'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
id: 2,
|
|
167
|
+
price: '2299.50',
|
|
168
|
+
quantity: 1,
|
|
169
|
+
total_amount: '2299.50',
|
|
170
|
+
status: { value: '10', label: 'Approved' },
|
|
171
|
+
price_currency: { value: 'try', label: 'TRY' },
|
|
172
|
+
product: {
|
|
173
|
+
pk: 2,
|
|
174
|
+
name: 'Sample Product 2',
|
|
175
|
+
image: 'https://placehold.co/220x300',
|
|
176
|
+
absolute_url: '#',
|
|
177
|
+
attributes: {
|
|
178
|
+
color: 'Black',
|
|
179
|
+
size: 'L',
|
|
180
|
+
filterable_color: 'Black',
|
|
181
|
+
integration_renk_detay: 'Black',
|
|
182
|
+
filtrenebilir_beden: 'L'
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
discountitem_set: [
|
|
188
|
+
{ name: 'Basket discount', amount: '-2499.00' },
|
|
189
|
+
{ name: 'Promo code', amount: '-998.00' }
|
|
190
|
+
],
|
|
191
|
+
shipping_address: {
|
|
192
|
+
first_name: 'Jane',
|
|
193
|
+
last_name: 'Doe',
|
|
194
|
+
line: '123 Sample Street',
|
|
195
|
+
district: { name: 'Downtown' },
|
|
196
|
+
township: { name: 'Central' },
|
|
197
|
+
city: { name: 'Istanbul' }
|
|
198
|
+
},
|
|
199
|
+
billing_address: {
|
|
200
|
+
first_name: 'Jane',
|
|
201
|
+
last_name: 'Doe',
|
|
202
|
+
line: '123 Sample Street',
|
|
203
|
+
district: { name: 'Downtown' },
|
|
204
|
+
township: { name: 'Central' },
|
|
205
|
+
city: { name: 'Istanbul' }
|
|
206
|
+
},
|
|
207
|
+
bank: { name: 'Sample Bank' },
|
|
208
|
+
payment_option: { name: 'Credit Card' }
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Attribute names are brand-specific (a raw commerce feed, not a fixed
|
|
213
|
+
* schema), so color/size read from a designer-configured key rather than a
|
|
214
|
+
* hardcoded one — the alternative is a block that only ever works for the
|
|
215
|
+
* one brand whose attribute names happened to match.
|
|
216
|
+
*/
|
|
217
|
+
const resolveAttribute = (
|
|
218
|
+
attributes: Record<string, unknown> | undefined | null,
|
|
219
|
+
key: string
|
|
220
|
+
): string => {
|
|
221
|
+
if (!key || !attributes) return '';
|
|
222
|
+
const value = attributes[key];
|
|
223
|
+
|
|
224
|
+
return typeof value === 'string' ? value : '';
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const formatAddress = (address: AddressLike | null | undefined): string[] => {
|
|
228
|
+
if (!address) return [];
|
|
229
|
+
const name = [address.first_name, address.last_name]
|
|
230
|
+
.filter(Boolean)
|
|
231
|
+
.join(' ');
|
|
232
|
+
const locality = [
|
|
233
|
+
address.district?.name,
|
|
234
|
+
address.township?.name,
|
|
235
|
+
address.city?.name
|
|
236
|
+
]
|
|
237
|
+
.filter(Boolean)
|
|
238
|
+
.join(' / ');
|
|
239
|
+
const lineWithLocality = [address.line, locality].filter(Boolean).join(' ');
|
|
240
|
+
|
|
241
|
+
return [name, lineWithLocality].filter(Boolean);
|
|
242
|
+
};
|
|
243
|
+
|
|
91
244
|
/**
|
|
92
245
|
* Functional account block: one order's lines, totals, status and shipment
|
|
93
246
|
* info. Order CONTENT stays commerce-driven (account order service) and the
|
|
@@ -95,12 +248,22 @@ const readSize = (value: unknown, fallback: number): number => {
|
|
|
95
248
|
* every caption and the styling.
|
|
96
249
|
* A designer can mark one child as the row layout for the order lines; without
|
|
97
250
|
* one the built-in line renders.
|
|
98
|
-
* Properties:
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
251
|
+
* Properties: packageLabel, numberLabel, dateLabel, headerTotalLabel,
|
|
252
|
+
* showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
|
|
253
|
+
* cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
|
|
254
|
+
* imageHeight, showQuantity, quantityLabel, quantityPrefix, showColor,
|
|
255
|
+
* colorLabel, colorAttributeKey, showSize, sizeLabel, sizeAttributeKey,
|
|
256
|
+
* showShippingAddress, shippingAddressLabel, showBillingAddress,
|
|
257
|
+
* billingAddressLabel, showTotals, showPieceCount, pieceCountLabel,
|
|
258
|
+
* showDiscountItems, showInstallmentInfo, installmentInfoLabel,
|
|
259
|
+
* subtotalLabel, discountLabel, shippingLabel, freeShippingLabel, totalLabel,
|
|
260
|
+
* trackingLabel,
|
|
261
|
+
* showPaymentInfo, paymentInfoLabel.
|
|
262
|
+
* Parts: detail, header, package-label, number, date, header-total, status,
|
|
263
|
+
* invoice-link, cancel-link, list, item, image, name, quantity, color, size,
|
|
264
|
+
* item-status, price, shipment, delivery, tracking, addresses, address,
|
|
265
|
+
* address-label, totals, total-row, total-label, total-value, grand-total,
|
|
266
|
+
* payment-info, payment-info-label.
|
|
104
267
|
*/
|
|
105
268
|
const OrderDetailBlock = ({
|
|
106
269
|
block,
|
|
@@ -148,25 +311,110 @@ const OrderDetailBlock = ({
|
|
|
148
311
|
getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
|
|
149
312
|
);
|
|
150
313
|
const quantityPrefix = text('quantityPrefix', 'x');
|
|
314
|
+
const quantityLabel = text('quantityLabel', '');
|
|
151
315
|
const subtotalLabel = text('subtotalLabel', 'Subtotal');
|
|
152
316
|
const discountLabel = text('discountLabel', 'Discount');
|
|
153
317
|
const shippingLabel = text('shippingLabel', 'Shipping');
|
|
318
|
+
const freeShippingLabel = text('freeShippingLabel', 'Free');
|
|
154
319
|
const totalLabel = text('totalLabel', 'Total');
|
|
155
320
|
const trackingLabel = text('trackingLabel', 'Track shipment');
|
|
156
321
|
|
|
322
|
+
// Header: empty keeps the original bare header (no package tag, no
|
|
323
|
+
// separate labelled total line, no invoice/cancel links) — every one of
|
|
324
|
+
// these is an opt-in addition, same as the order-list block's labels.
|
|
325
|
+
const packageLabel = text('packageLabel', '');
|
|
326
|
+
const numberLabel = text('numberLabel', '');
|
|
327
|
+
const dateLabel = text('dateLabel', '');
|
|
328
|
+
const headerTotalLabel = text('headerTotalLabel', '');
|
|
329
|
+
const showInvoiceLink = readToggle(
|
|
330
|
+
block.properties?.showInvoiceLink,
|
|
331
|
+
currentBreakpoint,
|
|
332
|
+
false
|
|
333
|
+
);
|
|
334
|
+
const invoiceLinkLabel = text('invoiceLinkLabel', 'Invoice');
|
|
335
|
+
const showCancelLink = readToggle(
|
|
336
|
+
block.properties?.showCancelLink,
|
|
337
|
+
currentBreakpoint,
|
|
338
|
+
false
|
|
339
|
+
);
|
|
340
|
+
const cancelLinkLabel = text('cancelLinkLabel', 'Easy cancellation');
|
|
341
|
+
const cancelHrefBase = text('cancelHrefBase', '/account/orders');
|
|
342
|
+
|
|
343
|
+
const showColor = readToggle(
|
|
344
|
+
block.properties?.showColor,
|
|
345
|
+
currentBreakpoint,
|
|
346
|
+
false
|
|
347
|
+
);
|
|
348
|
+
const colorLabel = text('colorLabel', 'Color:');
|
|
349
|
+
const colorAttributeKey = text('colorAttributeKey', '');
|
|
350
|
+
const showSize = readToggle(
|
|
351
|
+
block.properties?.showSize,
|
|
352
|
+
currentBreakpoint,
|
|
353
|
+
false
|
|
354
|
+
);
|
|
355
|
+
const sizeLabel = text('sizeLabel', 'Size:');
|
|
356
|
+
const sizeAttributeKey = text('sizeAttributeKey', '');
|
|
357
|
+
|
|
358
|
+
const showShippingAddress = readToggle(
|
|
359
|
+
block.properties?.showShippingAddress,
|
|
360
|
+
currentBreakpoint,
|
|
361
|
+
false
|
|
362
|
+
);
|
|
363
|
+
const shippingAddressLabel = text('shippingAddressLabel', 'Delivery address');
|
|
364
|
+
const showBillingAddress = readToggle(
|
|
365
|
+
block.properties?.showBillingAddress,
|
|
366
|
+
currentBreakpoint,
|
|
367
|
+
false
|
|
368
|
+
);
|
|
369
|
+
const billingAddressLabel = text('billingAddressLabel', 'Billing address');
|
|
370
|
+
|
|
371
|
+
const showPieceCount = readToggle(
|
|
372
|
+
block.properties?.showPieceCount,
|
|
373
|
+
currentBreakpoint,
|
|
374
|
+
false
|
|
375
|
+
);
|
|
376
|
+
const pieceCountLabel = text('pieceCountLabel', 'pieces');
|
|
377
|
+
const showDiscountItems = readToggle(
|
|
378
|
+
block.properties?.showDiscountItems,
|
|
379
|
+
currentBreakpoint,
|
|
380
|
+
false
|
|
381
|
+
);
|
|
382
|
+
const showInstallmentInfo = readToggle(
|
|
383
|
+
block.properties?.showInstallmentInfo,
|
|
384
|
+
currentBreakpoint,
|
|
385
|
+
false
|
|
386
|
+
);
|
|
387
|
+
const installmentInfoLabel = text('installmentInfoLabel', 'Installments');
|
|
388
|
+
|
|
389
|
+
const showPaymentInfo = readToggle(
|
|
390
|
+
block.properties?.showPaymentInfo,
|
|
391
|
+
currentBreakpoint,
|
|
392
|
+
false
|
|
393
|
+
);
|
|
394
|
+
const paymentInfoLabel = text('paymentInfoLabel', 'Payment information');
|
|
395
|
+
|
|
396
|
+
const isMobile = currentBreakpoint === 'mobile';
|
|
397
|
+
|
|
157
398
|
const { locale, currency: localeCurrency } = useLocalization();
|
|
158
399
|
const params = useParams();
|
|
159
400
|
const searchParams = useSearchParams();
|
|
160
401
|
|
|
161
402
|
const orderKey =
|
|
162
|
-
firstString(
|
|
163
|
-
|
|
403
|
+
firstString(
|
|
404
|
+
(key) => (params as Record<string, unknown>)?.[key],
|
|
405
|
+
ORDER_PARAM_KEYS
|
|
406
|
+
) || firstString((key) => searchParams?.get(key), ORDER_PARAM_KEYS);
|
|
164
407
|
|
|
165
408
|
// Not gated on the session: brands expose the same endpoint for anonymous
|
|
166
409
|
// order tracking. Without a key there is nothing to ask for at all.
|
|
167
410
|
const { data, isLoading } = useGetOrderQuery(orderKey, { skip: !orderKey });
|
|
168
411
|
|
|
169
|
-
const
|
|
412
|
+
const liveOrder = (data ?? null) as OrderLike | null;
|
|
413
|
+
// See DESIGNER_SAMPLE_ORDER's own comment: the preview route's order id is
|
|
414
|
+
// fake by design, so a designer gets a fully populated sample instead of
|
|
415
|
+
// the section collapsing to one "not found" box the moment it's empty.
|
|
416
|
+
const order: OrderLike | null =
|
|
417
|
+
liveOrder ?? (isDesigner && !isLoading ? DESIGNER_SAMPLE_ORDER : null);
|
|
170
418
|
const items = order?.orderitem_set ?? [];
|
|
171
419
|
// pz writes one order line per unit; the same shared grouping the thank-you
|
|
172
420
|
// page uses keeps a three-unit order from listing as three "x1" rows here.
|
|
@@ -180,8 +428,6 @@ const OrderDetailBlock = ({
|
|
|
180
428
|
label={
|
|
181
429
|
isLoading
|
|
182
430
|
? 'Order detail (loading)'
|
|
183
|
-
: orderKey
|
|
184
|
-
? 'Order detail (order not found)'
|
|
185
431
|
: 'Order detail (no order in URL)'
|
|
186
432
|
}
|
|
187
433
|
attribute="data-order-detail"
|
|
@@ -251,6 +497,59 @@ const OrderDetailBlock = ({
|
|
|
251
497
|
</div>
|
|
252
498
|
) : null;
|
|
253
499
|
|
|
500
|
+
// Matches the storefront's own account-orders page: the amount before
|
|
501
|
+
// shipping is what "N pieces" prices against, not the grand total.
|
|
502
|
+
const pieceSubtotal = money(
|
|
503
|
+
String(
|
|
504
|
+
Number(order.amount_without_discount || 0) -
|
|
505
|
+
Number(order.shipping_amount || 0)
|
|
506
|
+
)
|
|
507
|
+
);
|
|
508
|
+
const cancelHref = order.id
|
|
509
|
+
? `${cancelHrefBase.replace(/\/$/, '')}/${order.id}/cancellation`
|
|
510
|
+
: '';
|
|
511
|
+
const installmentCount = Math.max(0, Number(order.installment_count) || 0);
|
|
512
|
+
const perInstallment =
|
|
513
|
+
installmentCount > 1
|
|
514
|
+
? money(String(Number(order.amount || 0) / installmentCount))
|
|
515
|
+
: '';
|
|
516
|
+
|
|
517
|
+
const addressBlock = (
|
|
518
|
+
address: AddressLike | null | undefined,
|
|
519
|
+
label: string,
|
|
520
|
+
part: string
|
|
521
|
+
) => {
|
|
522
|
+
const lines = formatAddress(address);
|
|
523
|
+
if (lines.length === 0) return null;
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<div
|
|
527
|
+
{...partAttrs('address')}
|
|
528
|
+
data-order-detail-address={part}
|
|
529
|
+
style={{ flex: isMobile ? '1 1 auto' : '0 1 50%', minWidth: 0 }}
|
|
530
|
+
>
|
|
531
|
+
<div
|
|
532
|
+
{...partAttrs('address-label')}
|
|
533
|
+
style={{ fontWeight: 600, marginBottom: '8px' }}
|
|
534
|
+
>
|
|
535
|
+
{label}
|
|
536
|
+
</div>
|
|
537
|
+
<div
|
|
538
|
+
style={{
|
|
539
|
+
display: 'flex',
|
|
540
|
+
flexDirection: 'column',
|
|
541
|
+
gap: '2px',
|
|
542
|
+
opacity: 0.8
|
|
543
|
+
}}
|
|
544
|
+
>
|
|
545
|
+
{lines.map((line, index) => (
|
|
546
|
+
<span key={index}>{line}</span>
|
|
547
|
+
))}
|
|
548
|
+
</div>
|
|
549
|
+
</div>
|
|
550
|
+
);
|
|
551
|
+
};
|
|
552
|
+
|
|
254
553
|
return (
|
|
255
554
|
<div
|
|
256
555
|
data-order-detail=""
|
|
@@ -264,136 +563,246 @@ const OrderDetailBlock = ({
|
|
|
264
563
|
<header
|
|
265
564
|
data-order-detail-header=""
|
|
266
565
|
{...partAttrs('header')}
|
|
267
|
-
style={{
|
|
566
|
+
style={{
|
|
567
|
+
display: 'flex',
|
|
568
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
569
|
+
alignItems: isMobile ? 'stretch' : 'flex-start',
|
|
570
|
+
justifyContent: 'space-between',
|
|
571
|
+
gap: isMobile ? '12px' : '24px'
|
|
572
|
+
}}
|
|
268
573
|
>
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
style={{ opacity: 0.8 }}
|
|
280
|
-
>
|
|
281
|
-
{formatDate(order.created_date, locale)}
|
|
282
|
-
</span>
|
|
283
|
-
{order.status?.label && (
|
|
574
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
575
|
+
{packageLabel && (
|
|
576
|
+
<span
|
|
577
|
+
data-order-detail-package=""
|
|
578
|
+
{...partAttrs('package-label')}
|
|
579
|
+
style={{ opacity: 0.7 }}
|
|
580
|
+
>
|
|
581
|
+
{packageLabel}
|
|
582
|
+
</span>
|
|
583
|
+
)}
|
|
284
584
|
<span
|
|
285
|
-
data-order-detail-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
data-order-status-value={order.status.value}
|
|
289
|
-
{...partAttrs('status')}
|
|
585
|
+
data-order-detail-number=""
|
|
586
|
+
{...partAttrs('number')}
|
|
587
|
+
style={{ fontWeight: 600 }}
|
|
290
588
|
>
|
|
291
|
-
{
|
|
589
|
+
{numberLabel ? `${numberLabel} ` : ''}
|
|
590
|
+
{order.number || orderKey}
|
|
292
591
|
</span>
|
|
293
|
-
|
|
294
|
-
</header>
|
|
592
|
+
</div>
|
|
295
593
|
|
|
296
|
-
|
|
297
|
-
<ul
|
|
298
|
-
data-order-detail-items=""
|
|
299
|
-
{...partAttrs('list')}
|
|
594
|
+
<div
|
|
300
595
|
style={{
|
|
301
596
|
display: 'flex',
|
|
302
|
-
flexDirection: 'column',
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
margin: 0,
|
|
306
|
-
padding: 0
|
|
597
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
598
|
+
alignItems: isMobile ? 'stretch' : 'flex-start',
|
|
599
|
+
gap: isMobile ? '8px' : '24px'
|
|
307
600
|
}}
|
|
308
601
|
>
|
|
309
|
-
{
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
602
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
603
|
+
<span data-order-detail-date="" {...partAttrs('date')}>
|
|
604
|
+
{dateLabel ? `${dateLabel} ` : ''}
|
|
605
|
+
{formatDate(order.created_date, locale)}
|
|
606
|
+
</span>
|
|
607
|
+
{headerTotalLabel && (
|
|
608
|
+
<span
|
|
609
|
+
data-order-detail-header-total=""
|
|
610
|
+
{...partAttrs('header-total')}
|
|
611
|
+
>
|
|
612
|
+
{headerTotalLabel} {money(order.amount)}
|
|
613
|
+
</span>
|
|
614
|
+
)}
|
|
615
|
+
{(showInvoiceLink || showCancelLink) && (
|
|
616
|
+
<div
|
|
323
617
|
style={{
|
|
324
618
|
display: 'flex',
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
paddingBottom: '16px',
|
|
328
|
-
borderBottom: '1px solid var(--order-detail-border, #e5e7eb)'
|
|
619
|
+
gap: '16px',
|
|
620
|
+
marginTop: '4px'
|
|
329
621
|
}}
|
|
330
622
|
>
|
|
331
|
-
{
|
|
332
|
-
<
|
|
333
|
-
href={isDesigner ? '#' :
|
|
623
|
+
{showInvoiceLink && order.e_archive_url && (
|
|
624
|
+
<a
|
|
625
|
+
href={isDesigner ? '#' : order.e_archive_url}
|
|
334
626
|
onClick={guard}
|
|
335
|
-
|
|
627
|
+
target={isDesigner ? undefined : '_blank'}
|
|
628
|
+
rel="noreferrer"
|
|
629
|
+
data-order-detail-invoice-link=""
|
|
630
|
+
{...partAttrs('invoice-link')}
|
|
631
|
+
style={{
|
|
632
|
+
color: 'inherit',
|
|
633
|
+
textDecoration: 'none',
|
|
634
|
+
whiteSpace: 'nowrap'
|
|
635
|
+
}}
|
|
336
636
|
>
|
|
337
|
-
{
|
|
338
|
-
|
|
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>
|
|
637
|
+
→ {invoiceLinkLabel}
|
|
638
|
+
</a>
|
|
349
639
|
)}
|
|
350
|
-
|
|
351
|
-
<div style={{ flex: '1 1 auto', minWidth: 0 }}>
|
|
640
|
+
{showCancelLink && order.is_cancellable && cancelHref && (
|
|
352
641
|
<NextLink
|
|
353
|
-
href={isDesigner ? '#' :
|
|
642
|
+
href={isDesigner ? '#' : cancelHref}
|
|
354
643
|
onClick={guard}
|
|
355
|
-
data-order-detail-
|
|
356
|
-
{...partAttrs('
|
|
357
|
-
style={{
|
|
644
|
+
data-order-detail-cancel-link=""
|
|
645
|
+
{...partAttrs('cancel-link')}
|
|
646
|
+
style={{
|
|
647
|
+
color: 'inherit',
|
|
648
|
+
textDecoration: 'none',
|
|
649
|
+
whiteSpace: 'nowrap'
|
|
650
|
+
}}
|
|
358
651
|
>
|
|
359
|
-
{
|
|
652
|
+
→ {cancelLinkLabel}
|
|
360
653
|
</NextLink>
|
|
654
|
+
)}
|
|
655
|
+
</div>
|
|
656
|
+
)}
|
|
657
|
+
</div>
|
|
361
658
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
659
|
+
{order.status?.label && (
|
|
660
|
+
<span
|
|
661
|
+
data-order-detail-status=""
|
|
662
|
+
// The raw status code is published so scoped custom CSS can paint
|
|
663
|
+
// one status differently from the rest.
|
|
664
|
+
data-order-status-value={order.status.value}
|
|
665
|
+
{...partAttrs('status')}
|
|
666
|
+
// No background/padding/radius here: the Status style group owns
|
|
667
|
+
// that, same reasoning as the order-list block's status pill.
|
|
668
|
+
style={{ display: 'inline-block', flex: '0 0 auto' }}
|
|
669
|
+
>
|
|
670
|
+
{order.status.label}
|
|
671
|
+
</span>
|
|
672
|
+
)}
|
|
673
|
+
</div>
|
|
674
|
+
</header>
|
|
371
675
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
676
|
+
{showItems && (groupedItems.length > 0 || customLines) && (
|
|
677
|
+
<ul
|
|
678
|
+
data-order-detail-items=""
|
|
679
|
+
{...partAttrs('list')}
|
|
680
|
+
style={{
|
|
681
|
+
display: 'flex',
|
|
682
|
+
flexDirection: 'column',
|
|
683
|
+
gap: '16px',
|
|
684
|
+
listStyle: 'none',
|
|
685
|
+
margin: 0,
|
|
686
|
+
padding: 0
|
|
687
|
+
}}
|
|
688
|
+
>
|
|
689
|
+
{customLines ??
|
|
690
|
+
groupedItems.map((line) => {
|
|
691
|
+
// Status, currency and thumbnails are identical across the lines of
|
|
692
|
+
// a group, so the first line represents the whole row.
|
|
693
|
+
const item = line.item;
|
|
694
|
+
const name = line.name;
|
|
695
|
+
const href = line.href;
|
|
696
|
+
const lineCurrency = item.price_currency?.value || currency;
|
|
697
|
+
const colorValue = showColor
|
|
698
|
+
? resolveAttribute(item.product?.attributes, colorAttributeKey)
|
|
699
|
+
: '';
|
|
700
|
+
const sizeValue = showSize
|
|
701
|
+
? resolveAttribute(item.product?.attributes, sizeAttributeKey)
|
|
702
|
+
: '';
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<li
|
|
706
|
+
key={line.key}
|
|
707
|
+
data-order-detail-item=""
|
|
708
|
+
{...partAttrs('item')}
|
|
709
|
+
style={{
|
|
710
|
+
display: 'flex',
|
|
711
|
+
alignItems: 'flex-start',
|
|
712
|
+
gap: '12px',
|
|
713
|
+
paddingBottom: '16px',
|
|
714
|
+
borderBottom:
|
|
715
|
+
'1px solid var(--order-detail-border, #e5e7eb)'
|
|
716
|
+
}}
|
|
717
|
+
>
|
|
718
|
+
{showImage && item.product?.image && (
|
|
719
|
+
<NextLink
|
|
720
|
+
href={isDesigner ? '#' : href || '#'}
|
|
721
|
+
onClick={guard}
|
|
722
|
+
style={{ flex: '0 0 auto', display: 'block' }}
|
|
378
723
|
>
|
|
379
|
-
{
|
|
380
|
-
|
|
724
|
+
{/* Plain img: order line thumbnails come from arbitrary
|
|
725
|
+
commerce CDNs that the brand next.config may not allow. */}
|
|
726
|
+
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
727
|
+
<img
|
|
728
|
+
src={item.product.image}
|
|
729
|
+
alt={name}
|
|
730
|
+
width={imageWidth}
|
|
731
|
+
height={imageHeight}
|
|
732
|
+
{...partAttrs('image')}
|
|
733
|
+
style={{ objectFit: 'cover', display: 'block' }}
|
|
734
|
+
/>
|
|
735
|
+
</NextLink>
|
|
381
736
|
)}
|
|
382
|
-
</div>
|
|
383
737
|
|
|
384
|
-
|
|
385
|
-
|
|
738
|
+
<div style={{ flex: '1 1 auto', minWidth: 0 }}>
|
|
739
|
+
<NextLink
|
|
740
|
+
href={isDesigner ? '#' : href || '#'}
|
|
741
|
+
onClick={guard}
|
|
742
|
+
data-order-detail-item-name=""
|
|
743
|
+
{...partAttrs('name')}
|
|
744
|
+
style={{ textDecoration: 'none' }}
|
|
745
|
+
>
|
|
746
|
+
{name}
|
|
747
|
+
</NextLink>
|
|
748
|
+
|
|
749
|
+
{showColor && colorValue && (
|
|
750
|
+
<div
|
|
751
|
+
data-order-detail-item-color=""
|
|
752
|
+
{...partAttrs('color')}
|
|
753
|
+
style={{ marginTop: '4px' }}
|
|
754
|
+
>
|
|
755
|
+
{colorLabel} {colorValue}
|
|
756
|
+
</div>
|
|
757
|
+
)}
|
|
758
|
+
|
|
759
|
+
{showSize && sizeValue && (
|
|
760
|
+
<div
|
|
761
|
+
data-order-detail-item-size=""
|
|
762
|
+
{...partAttrs('size')}
|
|
763
|
+
style={{ marginTop: '4px' }}
|
|
764
|
+
>
|
|
765
|
+
{sizeLabel} {sizeValue}
|
|
766
|
+
</div>
|
|
767
|
+
)}
|
|
768
|
+
|
|
769
|
+
{showQuantity && (
|
|
770
|
+
<div
|
|
771
|
+
data-order-detail-item-quantity=""
|
|
772
|
+
{...partAttrs('quantity')}
|
|
773
|
+
style={{ marginTop: '4px', opacity: 0.8 }}
|
|
774
|
+
>
|
|
775
|
+
{quantityLabel
|
|
776
|
+
? `${quantityLabel} ${line.quantity}`
|
|
777
|
+
: `${quantityPrefix}${line.quantity}`}
|
|
778
|
+
</div>
|
|
779
|
+
)}
|
|
780
|
+
|
|
781
|
+
{showShipmentStatus && item.status?.label && (
|
|
782
|
+
<div
|
|
783
|
+
data-order-detail-item-status=""
|
|
784
|
+
data-order-status-value={item.status.value}
|
|
785
|
+
{...partAttrs('item-status')}
|
|
786
|
+
style={{ marginTop: '4px', opacity: 0.8 }}
|
|
787
|
+
>
|
|
788
|
+
{item.status.label}
|
|
789
|
+
</div>
|
|
790
|
+
)}
|
|
791
|
+
</div>
|
|
792
|
+
|
|
793
|
+
<span data-order-detail-item-price="" {...partAttrs('price')}>
|
|
794
|
+
{/* The row now stands for every unit of the product, so the
|
|
386
795
|
group's summed line total is what it must price — the
|
|
387
796
|
single line's total_amount would understate an "x3" row.
|
|
388
797
|
For an ungrouped row this is the value printed before. */}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
798
|
+
{formatMoney(line.total, {
|
|
799
|
+
locale,
|
|
800
|
+
currency: lineCurrency
|
|
801
|
+
})}
|
|
802
|
+
</span>
|
|
803
|
+
</li>
|
|
804
|
+
);
|
|
805
|
+
})}
|
|
397
806
|
</ul>
|
|
398
807
|
)}
|
|
399
808
|
|
|
@@ -438,33 +847,100 @@ const OrderDetailBlock = ({
|
|
|
438
847
|
</section>
|
|
439
848
|
)}
|
|
440
849
|
|
|
850
|
+
{(showShippingAddress || showBillingAddress) && (
|
|
851
|
+
<section
|
|
852
|
+
data-order-detail-addresses=""
|
|
853
|
+
{...partAttrs('addresses')}
|
|
854
|
+
style={{
|
|
855
|
+
display: 'flex',
|
|
856
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
857
|
+
gap: isMobile ? '16px' : '40px'
|
|
858
|
+
}}
|
|
859
|
+
>
|
|
860
|
+
{showShippingAddress &&
|
|
861
|
+
addressBlock(
|
|
862
|
+
order.shipping_address,
|
|
863
|
+
shippingAddressLabel,
|
|
864
|
+
'shipping'
|
|
865
|
+
)}
|
|
866
|
+
{showBillingAddress &&
|
|
867
|
+
addressBlock(order.billing_address, billingAddressLabel, 'billing')}
|
|
868
|
+
</section>
|
|
869
|
+
)}
|
|
870
|
+
|
|
441
871
|
{showTotals && (
|
|
442
872
|
<section
|
|
443
873
|
data-order-detail-totals=""
|
|
444
874
|
{...partAttrs('totals')}
|
|
445
875
|
style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}
|
|
446
876
|
>
|
|
447
|
-
{
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
)}
|
|
877
|
+
{showPieceCount &&
|
|
878
|
+
totalRow(
|
|
879
|
+
`${groupedItems.reduce(
|
|
880
|
+
(sum, line) => sum + Number(line.quantity || 0),
|
|
881
|
+
0
|
|
882
|
+
)} ${pieceCountLabel}`,
|
|
883
|
+
pieceSubtotal,
|
|
884
|
+
'data-order-detail-piece-count'
|
|
885
|
+
)}
|
|
457
886
|
{totalRow(
|
|
458
887
|
shippingLabel,
|
|
459
|
-
|
|
888
|
+
Number(order.shipping_amount || 0) > 0
|
|
889
|
+
? money(order.shipping_amount)
|
|
890
|
+
: freeShippingLabel,
|
|
460
891
|
'data-order-detail-shipping'
|
|
461
892
|
)}
|
|
893
|
+
{showDiscountItems
|
|
894
|
+
? (order.discountitem_set || []).map((discount, index) =>
|
|
895
|
+
totalRow(
|
|
896
|
+
discount.name || discountLabel,
|
|
897
|
+
money(discount.amount),
|
|
898
|
+
`data-order-detail-discount-${index}`
|
|
899
|
+
)
|
|
900
|
+
)
|
|
901
|
+
: totalRow(
|
|
902
|
+
discountLabel,
|
|
903
|
+
money(order.discount_amount),
|
|
904
|
+
'data-order-detail-discount'
|
|
905
|
+
)}
|
|
462
906
|
{totalRow(
|
|
463
907
|
totalLabel,
|
|
464
908
|
money(order.amount),
|
|
465
909
|
'data-order-detail-total',
|
|
466
910
|
'grand-total'
|
|
467
911
|
)}
|
|
912
|
+
{showInstallmentInfo &&
|
|
913
|
+
installmentCount > 1 &&
|
|
914
|
+
totalRow(
|
|
915
|
+
installmentInfoLabel,
|
|
916
|
+
`${installmentCount} x ${perInstallment}`,
|
|
917
|
+
'data-order-detail-installments'
|
|
918
|
+
)}
|
|
919
|
+
</section>
|
|
920
|
+
)}
|
|
921
|
+
|
|
922
|
+
{showPaymentInfo && (order.bank?.name || order.payment_option?.name) && (
|
|
923
|
+
<section
|
|
924
|
+
data-order-detail-payment=""
|
|
925
|
+
{...partAttrs('payment-info')}
|
|
926
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
|
|
927
|
+
>
|
|
928
|
+
<div
|
|
929
|
+
{...partAttrs('payment-info-label')}
|
|
930
|
+
style={{ fontWeight: 600, marginBottom: '4px' }}
|
|
931
|
+
>
|
|
932
|
+
{paymentInfoLabel}
|
|
933
|
+
</div>
|
|
934
|
+
{formatAddress(order.billing_address)
|
|
935
|
+
.slice(0, 1)
|
|
936
|
+
.map((line, index) => (
|
|
937
|
+
<span key={index}>{line}</span>
|
|
938
|
+
))}
|
|
939
|
+
<span style={{ opacity: 0.8 }}>
|
|
940
|
+
{[order.bank?.name, order.payment_option?.name]
|
|
941
|
+
.filter(Boolean)
|
|
942
|
+
.join(' — ')}
|
|
943
|
+
</span>
|
|
468
944
|
</section>
|
|
469
945
|
)}
|
|
470
946
|
</div>
|