@akinon/pz-theme 2.0.64 → 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 +8 -0
- package/package.json +4 -4
- package/src/blocks/order-detail-block.tsx +600 -124
- package/src/blocks/order-list-block.tsx +477 -101
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React from 'react';
|
|
3
|
+
import React, { useEffect, useState } from 'react';
|
|
4
4
|
import NextLink from 'next/link';
|
|
5
5
|
import { useSession } from 'next-auth/react';
|
|
6
6
|
import { useGetOrdersQuery } from '@akinon/next/data/client/account';
|
|
@@ -11,6 +11,10 @@ import { hasItemTemplate, ItemTemplateList } from './item-template';
|
|
|
11
11
|
import { readToggle } from './product-block-utils';
|
|
12
12
|
import { BlockRendererProps } from './block-renderer-registry';
|
|
13
13
|
|
|
14
|
+
interface OrderItemLike {
|
|
15
|
+
product?: { image?: string; name?: string } | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
interface OrderLike {
|
|
15
19
|
id?: number;
|
|
16
20
|
number?: string;
|
|
@@ -18,9 +22,15 @@ interface OrderLike {
|
|
|
18
22
|
created_date?: string;
|
|
19
23
|
status?: { value?: string; label?: string };
|
|
20
24
|
currency?: { value?: string; label?: string };
|
|
21
|
-
orderitem_set?:
|
|
25
|
+
orderitem_set?: OrderItemLike[];
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
const readCount = (value: unknown, fallback: number): number => {
|
|
29
|
+
const numeric = Math.round(Number(value));
|
|
30
|
+
|
|
31
|
+
return Number.isFinite(numeric) && numeric >= 0 ? numeric : fallback;
|
|
32
|
+
};
|
|
33
|
+
|
|
24
34
|
/** Newest/oldest first without mutating the array RTK Query handed us. */
|
|
25
35
|
const sortOrders = (orders: OrderLike[], sort: string): OrderLike[] => {
|
|
26
36
|
if (sort !== 'newest' && sort !== 'oldest') return orders;
|
|
@@ -42,8 +52,11 @@ const sortOrders = (orders: OrderLike[], sort: string): OrderLike[] => {
|
|
|
42
52
|
* rows are shown, which fields the row carries, where a row links to and the
|
|
43
53
|
* styling.
|
|
44
54
|
* Properties: limit, orderHrefBase, sort, showDate, showStatus, showAmount,
|
|
45
|
-
*
|
|
46
|
-
*
|
|
55
|
+
* showItemImages, imageWidth, imageHeight, imageGap, imageAlign, maxImages,
|
|
56
|
+
* numberLabel, dateLabel, totalLabel, detailLinkLabel, showTitle, title,
|
|
57
|
+
* showPagination, perPage, emptyLabel.
|
|
58
|
+
* Parts: title, list, item, images, image-overflow, link, date, total,
|
|
59
|
+
* detail-link, status, pagination, empty.
|
|
47
60
|
*/
|
|
48
61
|
const OrderListBlock = ({
|
|
49
62
|
block,
|
|
@@ -83,23 +96,108 @@ const OrderListBlock = ({
|
|
|
83
96
|
currentBreakpoint,
|
|
84
97
|
true
|
|
85
98
|
);
|
|
99
|
+
const showItemImages = readToggle(
|
|
100
|
+
block.properties?.showItemImages,
|
|
101
|
+
currentBreakpoint,
|
|
102
|
+
true
|
|
103
|
+
);
|
|
104
|
+
const imageWidth = readCount(
|
|
105
|
+
getResponsiveValue(block.properties?.imageWidth, currentBreakpoint, 64),
|
|
106
|
+
64
|
|
107
|
+
);
|
|
108
|
+
const imageHeight = readCount(
|
|
109
|
+
getResponsiveValue(block.properties?.imageHeight, currentBreakpoint, 96),
|
|
110
|
+
96
|
|
111
|
+
);
|
|
112
|
+
const imageGap = readCount(
|
|
113
|
+
getResponsiveValue(block.properties?.imageGap, currentBreakpoint, 8),
|
|
114
|
+
8
|
|
115
|
+
);
|
|
116
|
+
const imageAlignRaw = String(
|
|
117
|
+
getResponsiveValue(block.properties?.imageAlign, currentBreakpoint, 'left')
|
|
118
|
+
);
|
|
119
|
+
const imageJustify =
|
|
120
|
+
imageAlignRaw === 'center'
|
|
121
|
+
? 'center'
|
|
122
|
+
: imageAlignRaw === 'right'
|
|
123
|
+
? 'flex-end'
|
|
124
|
+
: 'flex-start';
|
|
125
|
+
const maxImages = Math.max(
|
|
126
|
+
1,
|
|
127
|
+
readCount(
|
|
128
|
+
getResponsiveValue(block.properties?.maxImages, currentBreakpoint, 3),
|
|
129
|
+
3
|
|
130
|
+
)
|
|
131
|
+
);
|
|
86
132
|
const emptyLabel = String(
|
|
87
133
|
getResponsiveValue(block.properties?.emptyLabel, currentBreakpoint, '')
|
|
88
134
|
);
|
|
135
|
+
// Empty keeps the built-in row exactly as it always rendered — a brand
|
|
136
|
+
// opts into labels and a standalone detail link by setting these.
|
|
137
|
+
const numberLabel = String(
|
|
138
|
+
getResponsiveValue(block.properties?.numberLabel, currentBreakpoint, '')
|
|
139
|
+
);
|
|
140
|
+
const dateLabel = String(
|
|
141
|
+
getResponsiveValue(block.properties?.dateLabel, currentBreakpoint, '')
|
|
142
|
+
);
|
|
143
|
+
const totalLabel = String(
|
|
144
|
+
getResponsiveValue(block.properties?.totalLabel, currentBreakpoint, '')
|
|
145
|
+
);
|
|
146
|
+
const detailLinkLabel = String(
|
|
147
|
+
getResponsiveValue(block.properties?.detailLinkLabel, currentBreakpoint, '')
|
|
148
|
+
);
|
|
149
|
+
const showTitle = readToggle(
|
|
150
|
+
block.properties?.showTitle,
|
|
151
|
+
currentBreakpoint,
|
|
152
|
+
false
|
|
153
|
+
);
|
|
154
|
+
const title = String(
|
|
155
|
+
getResponsiveValue(block.properties?.title, currentBreakpoint, '')
|
|
156
|
+
);
|
|
157
|
+
const showPagination = readToggle(
|
|
158
|
+
block.properties?.showPagination,
|
|
159
|
+
currentBreakpoint,
|
|
160
|
+
false
|
|
161
|
+
);
|
|
162
|
+
const perPage = Math.max(
|
|
163
|
+
1,
|
|
164
|
+
readCount(
|
|
165
|
+
getResponsiveValue(block.properties?.perPage, currentBreakpoint, 10),
|
|
166
|
+
10
|
|
167
|
+
)
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const isMobile = currentBreakpoint === 'mobile';
|
|
89
171
|
|
|
90
172
|
const { locale, currency: localeCurrency } = useLocalization();
|
|
91
173
|
const { status } = useSession();
|
|
92
174
|
const isAuthenticated = status === 'authenticated';
|
|
93
175
|
|
|
176
|
+
// Paged mode reads perPage/currentPage from the server; unpaged mode keeps
|
|
177
|
+
// the original single fetch capped by `limit`.
|
|
178
|
+
const [currentPage, setCurrentPage] = useState(1);
|
|
179
|
+
|
|
180
|
+
// A brand switching pageSize (or sort) mid-session should not strand the
|
|
181
|
+
// shopper on a page number that no longer exists.
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
setCurrentPage(1);
|
|
184
|
+
}, [perPage, sort, showPagination]);
|
|
185
|
+
|
|
186
|
+
const effectiveLimit = showPagination ? perPage : limit;
|
|
187
|
+
|
|
94
188
|
// Guests have no orders endpoint; skipping avoids a guaranteed 403 on every
|
|
95
189
|
// page that hosts this block.
|
|
96
190
|
const { data, isLoading } = useGetOrdersQuery(
|
|
97
|
-
{ limit, page: 1 },
|
|
191
|
+
{ limit: effectiveLimit, page: showPagination ? currentPage : 1 },
|
|
98
192
|
{ skip: !isAuthenticated }
|
|
99
193
|
);
|
|
100
194
|
|
|
101
195
|
const orders: OrderLike[] =
|
|
102
196
|
(data as { results?: OrderLike[] } | undefined)?.results ?? [];
|
|
197
|
+
const totalCount = (data as { count?: number } | undefined)?.count ?? 0;
|
|
198
|
+
const totalPages = showPagination
|
|
199
|
+
? Math.max(1, Math.ceil(totalCount / perPage))
|
|
200
|
+
: 1;
|
|
103
201
|
|
|
104
202
|
// Navigating inside the designer iframe would drop the author out of the
|
|
105
203
|
// page they are editing, so the order links are inert there.
|
|
@@ -108,9 +206,11 @@ const OrderListBlock = ({
|
|
|
108
206
|
};
|
|
109
207
|
|
|
110
208
|
const hrefFor = (order: OrderLike) =>
|
|
111
|
-
order.id
|
|
209
|
+
order.id
|
|
210
|
+
? `${orderHrefBase.replace(/\/$/, '')}/${order.id}`
|
|
211
|
+
: orderHrefBase;
|
|
112
212
|
|
|
113
|
-
const visibleOrders = sortOrders(orders, sort).slice(0,
|
|
213
|
+
const visibleOrders = sortOrders(orders, sort).slice(0, effectiveLimit);
|
|
114
214
|
|
|
115
215
|
const rows = visibleOrders.map((order, index) => ({
|
|
116
216
|
id: order.id ?? '',
|
|
@@ -133,6 +233,15 @@ const OrderListBlock = ({
|
|
|
133
233
|
itemCount: Array.isArray(order.orderitem_set)
|
|
134
234
|
? order.orderitem_set.length
|
|
135
235
|
: 0,
|
|
236
|
+
images: (Array.isArray(order.orderitem_set) ? order.orderitem_set : [])
|
|
237
|
+
.slice(0, maxImages)
|
|
238
|
+
.map((item) => item?.product?.image || '')
|
|
239
|
+
.filter(Boolean),
|
|
240
|
+
imageOverflowCount: Math.max(
|
|
241
|
+
0,
|
|
242
|
+
(Array.isArray(order.orderitem_set) ? order.orderitem_set.length : 0) -
|
|
243
|
+
maxImages
|
|
244
|
+
),
|
|
136
245
|
url: hrefFor(order),
|
|
137
246
|
href: hrefFor(order)
|
|
138
247
|
}));
|
|
@@ -151,123 +260,390 @@ const OrderListBlock = ({
|
|
|
151
260
|
/>
|
|
152
261
|
) : null;
|
|
153
262
|
|
|
263
|
+
const titleNode =
|
|
264
|
+
showTitle && title ? (
|
|
265
|
+
<div
|
|
266
|
+
data-order-list-title=""
|
|
267
|
+
{...partAttrs('title')}
|
|
268
|
+
style={{
|
|
269
|
+
width: '100%',
|
|
270
|
+
fontSize: '16px',
|
|
271
|
+
fontWeight: 600,
|
|
272
|
+
marginBottom: '16px'
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
{title}
|
|
276
|
+
</div>
|
|
277
|
+
) : null;
|
|
278
|
+
|
|
154
279
|
if (orders.length === 0 && !customRows) {
|
|
155
280
|
if (isDesigner) {
|
|
156
281
|
return (
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
282
|
+
<>
|
|
283
|
+
{titleNode}
|
|
284
|
+
<div
|
|
285
|
+
data-order-list=""
|
|
286
|
+
data-order-list-state="empty"
|
|
287
|
+
{...partAttrs('empty', { empty: true })}
|
|
288
|
+
style={{ width: '100%' }}
|
|
289
|
+
>
|
|
290
|
+
{isLoading
|
|
291
|
+
? 'Recent orders (loading)'
|
|
292
|
+
: emptyLabel || 'Recent orders (no orders)'}
|
|
293
|
+
</div>
|
|
294
|
+
</>
|
|
166
295
|
);
|
|
167
296
|
}
|
|
168
297
|
|
|
169
298
|
// The shopper-facing empty copy is only honest once the account query has
|
|
170
299
|
// resolved; before that (and without a message) the block stays absent,
|
|
171
300
|
// which is what every published composition renders today.
|
|
172
|
-
if (!isAuthenticated || isLoading || !emptyLabel) return
|
|
301
|
+
if (!isAuthenticated || isLoading || !emptyLabel) return titleNode;
|
|
173
302
|
|
|
174
303
|
return (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
304
|
+
<>
|
|
305
|
+
{titleNode}
|
|
306
|
+
<div
|
|
307
|
+
data-order-list=""
|
|
308
|
+
data-order-list-state="empty"
|
|
309
|
+
{...partAttrs('empty', { empty: true })}
|
|
310
|
+
style={{ width: '100%' }}
|
|
311
|
+
>
|
|
312
|
+
{emptyLabel}
|
|
313
|
+
</div>
|
|
314
|
+
</>
|
|
182
315
|
);
|
|
183
316
|
}
|
|
184
317
|
|
|
185
318
|
return (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
{...partAttrs('date')}
|
|
230
|
-
style={{ opacity: 0.8, marginTop: '2px' }}
|
|
231
|
-
>
|
|
232
|
-
{formatDate(order.created_date, locale)}
|
|
233
|
-
</div>
|
|
319
|
+
<>
|
|
320
|
+
{titleNode}
|
|
321
|
+
<ul
|
|
322
|
+
data-order-list=""
|
|
323
|
+
{...partAttrs('list')}
|
|
324
|
+
style={{
|
|
325
|
+
display: 'flex',
|
|
326
|
+
flexDirection: 'column',
|
|
327
|
+
width: '100%',
|
|
328
|
+
gap: 'var(--order-list-gap, 12px)',
|
|
329
|
+
listStyle: 'none',
|
|
330
|
+
margin: 0,
|
|
331
|
+
padding: 0
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
{customRows ??
|
|
335
|
+
visibleOrders.map((order, index) => {
|
|
336
|
+
const href = hrefFor(order);
|
|
337
|
+
const orderItems = Array.isArray(order.orderitem_set)
|
|
338
|
+
? order.orderitem_set
|
|
339
|
+
: [];
|
|
340
|
+
const images = orderItems
|
|
341
|
+
.slice(0, maxImages)
|
|
342
|
+
.map((item) => item?.product?.image || '')
|
|
343
|
+
.filter(Boolean);
|
|
344
|
+
const imageOverflowCount = Math.max(
|
|
345
|
+
0,
|
|
346
|
+
orderItems.length - maxImages
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// A label turns its row into a label/value grid pair; without one
|
|
350
|
+
// the value alone spans both columns, exactly as before.
|
|
351
|
+
const gridPair = (
|
|
352
|
+
label: string,
|
|
353
|
+
value: React.ReactNode,
|
|
354
|
+
partName: string,
|
|
355
|
+
dataAttr: string
|
|
356
|
+
) => (
|
|
357
|
+
<React.Fragment>
|
|
358
|
+
{label && (
|
|
359
|
+
<span style={{ fontWeight: 600, whiteSpace: 'nowrap' }}>
|
|
360
|
+
{label}
|
|
361
|
+
</span>
|
|
234
362
|
)}
|
|
235
|
-
|
|
363
|
+
<span
|
|
364
|
+
{...{ [dataAttr]: '' }}
|
|
365
|
+
{...partAttrs(partName)}
|
|
366
|
+
// No default text color: the Order Number/Date/Total style
|
|
367
|
+
// groups already expose one, and an inline default would
|
|
368
|
+
// sit in front of that panel value rather than behind it.
|
|
369
|
+
// A template asking for a muted value colour sets it as a
|
|
370
|
+
// `link|date|total/color` style, not a code default.
|
|
371
|
+
style={{
|
|
372
|
+
gridColumn: label ? 'auto' : '1 / span 2',
|
|
373
|
+
// An order number is one long unbroken digit string —
|
|
374
|
+
// without this it can't wrap at all, so shrinking the
|
|
375
|
+
// column (above) just pushes it past the row's edge.
|
|
376
|
+
overflowWrap: 'anywhere'
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
{value}
|
|
380
|
+
</span>
|
|
381
|
+
</React.Fragment>
|
|
382
|
+
);
|
|
236
383
|
|
|
237
|
-
|
|
384
|
+
return (
|
|
385
|
+
<li
|
|
386
|
+
key={order.id ?? order.number ?? index}
|
|
387
|
+
data-order-list-item=""
|
|
388
|
+
{...partAttrs('item')}
|
|
238
389
|
style={{
|
|
239
390
|
display: 'flex',
|
|
240
|
-
flexDirection: 'column',
|
|
241
|
-
alignItems: 'flex-
|
|
391
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
392
|
+
alignItems: isMobile ? 'stretch' : 'flex-start',
|
|
393
|
+
gap: isMobile ? '16px' : '24px',
|
|
394
|
+
padding: '20px 0',
|
|
395
|
+
borderBottom: '1px solid var(--order-list-border, #e5e7eb)'
|
|
242
396
|
}}
|
|
243
397
|
>
|
|
244
|
-
{
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
398
|
+
{showItemImages && images.length > 0 && (
|
|
399
|
+
<ul
|
|
400
|
+
data-order-list-images=""
|
|
401
|
+
style={{
|
|
402
|
+
display: 'flex',
|
|
403
|
+
// Where the images sit inside their half when they
|
|
404
|
+
// don't fill it — a single order's one thumbnail on
|
|
405
|
+
// an otherwise 3-image row, for instance.
|
|
406
|
+
justifyContent: imageJustify,
|
|
407
|
+
gap: `${imageGap}px`,
|
|
408
|
+
listStyle: 'none',
|
|
409
|
+
margin: 0,
|
|
410
|
+
padding: 0,
|
|
411
|
+
minWidth: 0,
|
|
412
|
+
// Grow/shrink together with the info half (below), not
|
|
413
|
+
// a rigid 50% — a host narrower than the full page (a
|
|
414
|
+
// dashboard widget next to a sidebar, say) needs both
|
|
415
|
+
// halves to give way instead of overflowing on top of
|
|
416
|
+
// each other.
|
|
417
|
+
flex: isMobile ? '0 0 auto' : '1 1 50%',
|
|
418
|
+
width: isMobile ? '100%' : undefined
|
|
419
|
+
}}
|
|
262
420
|
>
|
|
263
|
-
{
|
|
264
|
-
|
|
421
|
+
{images.map((src, imageIndex) => (
|
|
422
|
+
// Plain img: order thumbnails come from arbitrary commerce
|
|
423
|
+
// CDNs that the brand next.config may not allow. The
|
|
424
|
+
// Product Images style group targets each thumbnail
|
|
425
|
+
// (not this wrapper), so its box controls land on an
|
|
426
|
+
// element they actually make sense on.
|
|
427
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
428
|
+
<img
|
|
429
|
+
key={imageIndex}
|
|
430
|
+
{...partAttrs('images')}
|
|
431
|
+
src={src}
|
|
432
|
+
alt=""
|
|
433
|
+
width={imageWidth}
|
|
434
|
+
height={imageHeight}
|
|
435
|
+
style={{
|
|
436
|
+
objectFit: 'cover',
|
|
437
|
+
display: 'block',
|
|
438
|
+
flex: '0 0 auto'
|
|
439
|
+
}}
|
|
440
|
+
/>
|
|
441
|
+
))}
|
|
442
|
+
{imageOverflowCount > 0 && (
|
|
443
|
+
<li
|
|
444
|
+
data-order-list-image-overflow=""
|
|
445
|
+
{...partAttrs('image-overflow')}
|
|
446
|
+
style={{
|
|
447
|
+
display: 'flex',
|
|
448
|
+
alignItems: 'center',
|
|
449
|
+
justifyContent: 'center',
|
|
450
|
+
flex: '0 0 auto'
|
|
451
|
+
}}
|
|
452
|
+
>
|
|
453
|
+
+{imageOverflowCount}
|
|
454
|
+
</li>
|
|
455
|
+
)}
|
|
456
|
+
</ul>
|
|
265
457
|
)}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
458
|
+
|
|
459
|
+
{/* The other half of the row: number/date/total, the detail
|
|
460
|
+
link and the status pill. Stacks full width on mobile,
|
|
461
|
+
matches the images column's 50% on desktop. */}
|
|
462
|
+
<div
|
|
463
|
+
style={{
|
|
464
|
+
display: 'flex',
|
|
465
|
+
flexDirection: isMobile ? 'column' : 'row',
|
|
466
|
+
alignItems: isMobile ? 'stretch' : 'flex-start',
|
|
467
|
+
justifyContent: isMobile ? 'flex-start' : 'space-between',
|
|
468
|
+
// A safety net for the row/status column below, on top
|
|
469
|
+
// of the shrinking above: if there still isn't room once
|
|
470
|
+
// both halves have given up all their slack, this drops
|
|
471
|
+
// the status pill to its own line instead of it landing
|
|
472
|
+
// on top of the order number.
|
|
473
|
+
flexWrap: 'wrap',
|
|
474
|
+
gap: isMobile ? '10px' : '24px',
|
|
475
|
+
flex: isMobile ? '1 1 auto' : '1 1 50%',
|
|
476
|
+
minWidth: 0,
|
|
477
|
+
width: isMobile ? '100%' : undefined
|
|
478
|
+
}}
|
|
479
|
+
>
|
|
480
|
+
<div
|
|
481
|
+
style={{
|
|
482
|
+
minWidth: 0,
|
|
483
|
+
// The value column: 'auto' alone has no minimum-width
|
|
484
|
+
// floor, so a long unbroken value (a 16-digit order
|
|
485
|
+
// number, say) refuses to shrink and pushes the grid
|
|
486
|
+
// itself past the row's edge instead of wrapping.
|
|
487
|
+
display: 'grid',
|
|
488
|
+
gridTemplateColumns: 'auto minmax(0, auto)',
|
|
489
|
+
columnGap: '10px',
|
|
490
|
+
rowGap: '10px',
|
|
491
|
+
justifyContent: 'start'
|
|
492
|
+
}}
|
|
493
|
+
>
|
|
494
|
+
{gridPair(
|
|
495
|
+
numberLabel,
|
|
496
|
+
detailLinkLabel ? (
|
|
497
|
+
order.number || `#${order.id ?? ''}`
|
|
498
|
+
) : (
|
|
499
|
+
<NextLink
|
|
500
|
+
href={isDesigner ? '#' : href}
|
|
501
|
+
onClick={guard}
|
|
502
|
+
style={{ textDecoration: 'none' }}
|
|
503
|
+
>
|
|
504
|
+
{order.number || `#${order.id ?? ''}`}
|
|
505
|
+
</NextLink>
|
|
506
|
+
),
|
|
507
|
+
'link',
|
|
508
|
+
'data-order-list-number'
|
|
509
|
+
)}
|
|
510
|
+
{showDate &&
|
|
511
|
+
gridPair(
|
|
512
|
+
dateLabel,
|
|
513
|
+
formatDate(order.created_date, locale),
|
|
514
|
+
'date',
|
|
515
|
+
'data-order-list-date'
|
|
516
|
+
)}
|
|
517
|
+
{showAmount &&
|
|
518
|
+
totalLabel &&
|
|
519
|
+
gridPair(
|
|
520
|
+
totalLabel,
|
|
521
|
+
formatMoney(order.amount, {
|
|
522
|
+
locale,
|
|
523
|
+
currency: order.currency?.value || localeCurrency
|
|
524
|
+
}),
|
|
525
|
+
'total',
|
|
526
|
+
'data-order-list-total-inline'
|
|
527
|
+
)}
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
{detailLinkLabel && (
|
|
531
|
+
<NextLink
|
|
532
|
+
href={isDesigner ? '#' : href}
|
|
533
|
+
onClick={guard}
|
|
534
|
+
data-order-list-detail-link=""
|
|
535
|
+
{...partAttrs('detail-link')}
|
|
536
|
+
// No inline colour: the Order Detail Link style group
|
|
537
|
+
// owns that (an inline default would block it, same as
|
|
538
|
+
// the value spans above).
|
|
539
|
+
style={{
|
|
540
|
+
flex: '0 0 auto',
|
|
541
|
+
whiteSpace: 'nowrap',
|
|
542
|
+
textDecoration: 'none'
|
|
543
|
+
}}
|
|
544
|
+
>
|
|
545
|
+
→ {detailLinkLabel}
|
|
546
|
+
</NextLink>
|
|
547
|
+
)}
|
|
548
|
+
|
|
549
|
+
<div
|
|
550
|
+
style={{
|
|
551
|
+
display: 'flex',
|
|
552
|
+
flexDirection: 'column',
|
|
553
|
+
alignItems: isMobile ? 'flex-start' : 'flex-end',
|
|
554
|
+
gap: '6px',
|
|
555
|
+
flex: '0 0 auto',
|
|
556
|
+
marginLeft: !isMobile && !detailLinkLabel ? 'auto' : 0
|
|
557
|
+
}}
|
|
558
|
+
>
|
|
559
|
+
{showAmount && !totalLabel && (
|
|
560
|
+
<span data-order-list-amount="" {...partAttrs('total')}>
|
|
561
|
+
{/* The order carries the currency it was placed in; the
|
|
562
|
+
storefront currency is only the fallback. */}
|
|
563
|
+
{formatMoney(order.amount, {
|
|
564
|
+
locale,
|
|
565
|
+
currency: order.currency?.value || localeCurrency
|
|
566
|
+
})}
|
|
567
|
+
</span>
|
|
568
|
+
)}
|
|
569
|
+
{showStatus && order.status?.label && (
|
|
570
|
+
<span
|
|
571
|
+
data-order-list-status=""
|
|
572
|
+
// The raw status code is published so scoped custom CSS can
|
|
573
|
+
// paint one status differently from the rest.
|
|
574
|
+
data-order-status-value={order.status.value}
|
|
575
|
+
{...partAttrs('status')}
|
|
576
|
+
// No background/padding/radius here: the Status Pill
|
|
577
|
+
// style group already exposes exactly those (and an
|
|
578
|
+
// inline default would sit in front of a panel value,
|
|
579
|
+
// not behind it — a designer could never turn it off).
|
|
580
|
+
// A pill look is a template's `status/*` styles, not a
|
|
581
|
+
// code default.
|
|
582
|
+
style={{ display: 'inline-block', fontSize: '12px' }}
|
|
583
|
+
>
|
|
584
|
+
{order.status.label}
|
|
585
|
+
</span>
|
|
586
|
+
)}
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
</li>
|
|
590
|
+
);
|
|
591
|
+
})}
|
|
592
|
+
</ul>
|
|
593
|
+
{showPagination && totalPages > 1 && (
|
|
594
|
+
<nav
|
|
595
|
+
data-order-list-pagination=""
|
|
596
|
+
{...partAttrs('pagination')}
|
|
597
|
+
style={{
|
|
598
|
+
display: 'flex',
|
|
599
|
+
alignItems: 'center',
|
|
600
|
+
justifyContent: 'center',
|
|
601
|
+
gap: '16px',
|
|
602
|
+
marginTop: 'var(--order-list-pagination-margin, 16px)'
|
|
603
|
+
}}
|
|
604
|
+
>
|
|
605
|
+
<button
|
|
606
|
+
type="button"
|
|
607
|
+
data-order-list-pagination-prev=""
|
|
608
|
+
disabled={currentPage <= 1}
|
|
609
|
+
onClick={() => setCurrentPage((page) => Math.max(1, page - 1))}
|
|
610
|
+
style={{
|
|
611
|
+
background: 'none',
|
|
612
|
+
border: 'none',
|
|
613
|
+
cursor: currentPage <= 1 ? 'default' : 'pointer',
|
|
614
|
+
opacity: currentPage <= 1 ? 0.4 : 1,
|
|
615
|
+
padding: 0,
|
|
616
|
+
font: 'inherit',
|
|
617
|
+
color: 'inherit'
|
|
618
|
+
}}
|
|
619
|
+
>
|
|
620
|
+
‹
|
|
621
|
+
</button>
|
|
622
|
+
<span data-order-list-pagination-status="">
|
|
623
|
+
{currentPage} / {totalPages}
|
|
624
|
+
</span>
|
|
625
|
+
<button
|
|
626
|
+
type="button"
|
|
627
|
+
data-order-list-pagination-next=""
|
|
628
|
+
disabled={currentPage >= totalPages}
|
|
629
|
+
onClick={() =>
|
|
630
|
+
setCurrentPage((page) => Math.min(totalPages, page + 1))
|
|
631
|
+
}
|
|
632
|
+
style={{
|
|
633
|
+
background: 'none',
|
|
634
|
+
border: 'none',
|
|
635
|
+
cursor: currentPage >= totalPages ? 'default' : 'pointer',
|
|
636
|
+
opacity: currentPage >= totalPages ? 0.4 : 1,
|
|
637
|
+
padding: 0,
|
|
638
|
+
font: 'inherit',
|
|
639
|
+
color: 'inherit'
|
|
640
|
+
}}
|
|
641
|
+
>
|
|
642
|
+
›
|
|
643
|
+
</button>
|
|
644
|
+
</nav>
|
|
645
|
+
)}
|
|
646
|
+
</>
|
|
271
647
|
);
|
|
272
648
|
};
|
|
273
649
|
|