@akinon/pz-theme 2.0.68-beta.0 → 2.0.68

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,12 +1,15 @@
1
1
  # @akinon/pz-theme
2
2
 
3
- ## 2.0.68-beta.0
3
+ ## 2.0.68
4
4
 
5
5
  ### Patch Changes
6
6
 
7
- - Updated dependencies [cbbbfd75]
8
- - @akinon/next@2.0.68-beta.0
9
- - @akinon/theme-contract@2.0.68-beta.0
7
+ - 3b8382c: ZERO-4833: Add line-level tracking and return code display to order-detail-block, make the order number heading tag configurable
8
+
9
+ Item rows can now show per-line tracking info (`showLineTracking`/`lineTrackingLabel`) and the active cancellation's return code (`showReturnCode`/`returnCodeLabel`). The order number can render as `h1`–`h3` via `numberTag` when it's the page's main heading. Removed the unused `packageLabel` and `quantityPrefix` properties in favor of the existing label/tag conventions used elsewhere in the block.
10
+
11
+ - @akinon/next@2.0.68
12
+ - @akinon/theme-contract@2.0.68
10
13
 
11
14
  ## 2.0.67
12
15
 
package/package.json CHANGED
@@ -1,26 +1,26 @@
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.68-beta.0",
4
+ "version": "2.0.68",
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.68-beta.0",
11
+ "@akinon/next": "2.0.68",
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.68-beta.0",
16
+ "@akinon/theme-contract": "2.0.68",
17
17
  "clsx": "2.1.1",
18
18
  "lottie-web": "^5.13.0",
19
19
  "tailwind-merge": "^2.5.4",
20
20
  "zod": "4.4.1"
21
21
  },
22
22
  "devDependencies": {
23
- "@akinon/next": "2.0.68-beta.0",
23
+ "@akinon/next": "2.0.68",
24
24
  "@testing-library/jest-dom": "5.16.4",
25
25
  "@testing-library/react": "16.3.2",
26
26
  "@testing-library/user-event": "14.1.1",
@@ -25,6 +25,7 @@ interface OrderItemLike {
25
25
  tracking_number?: string;
26
26
  estimated_delivery_date?: string;
27
27
  price_currency?: { value?: string; label?: string };
28
+ active_cancellation_request?: unknown;
28
29
  product?: {
29
30
  pk?: number;
30
31
  name?: string;
@@ -90,6 +91,11 @@ const ORDER_PARAM_KEYS = [
90
91
  'order'
91
92
  ];
92
93
 
94
+ // Same allow-list-then-cast pattern as product-title-block's heading tag —
95
+ // span keeps every existing composition's layout unchanged; a designer
96
+ // opts into h1/h2/h3 when the order number is the page's main heading.
97
+ const NUMBER_TAGS = ['span', 'div', 'h1', 'h2', 'h3'];
98
+
93
99
  const firstString = (
94
100
  read: (key: string) => unknown,
95
101
  keys: string[]
@@ -146,8 +152,13 @@ const DESIGNER_SAMPLE_ORDER: OrderLike = {
146
152
  price: '2299.50',
147
153
  quantity: 1,
148
154
  total_amount: '2299.50',
149
- status: { value: '10', label: 'Approved' },
155
+ status: { value: '80', label: 'Cancelled' },
150
156
  price_currency: { value: 'try', label: 'TRY' },
157
+ tracking_number: '1Z999AA10123456784',
158
+ tracking_url: '#',
159
+ active_cancellation_request: [
160
+ { easy_return: { code: '12345678912345' } }
161
+ ],
151
162
  product: {
152
163
  pk: 1,
153
164
  name: 'Sample Product',
@@ -224,6 +235,19 @@ const resolveAttribute = (
224
235
  return typeof value === 'string' ? value : '';
225
236
  };
226
237
 
238
+ /**
239
+ * Shape varies by pz version — some payloads carry the active cancellation
240
+ * request as a single object, others as a one-item array — so both are
241
+ * probed rather than assuming either.
242
+ */
243
+ const resolveReturnCode = (value: unknown): string => {
244
+ const entry = Array.isArray(value) ? value[0] : value;
245
+ const code = (entry as { easy_return?: { code?: string } } | null | undefined)
246
+ ?.easy_return?.code;
247
+
248
+ return typeof code === 'string' ? code : '';
249
+ };
250
+
227
251
  const formatAddress = (address: AddressLike | null | undefined): string[] => {
228
252
  if (!address) return [];
229
253
  const name = [address.first_name, address.last_name]
@@ -248,22 +272,23 @@ const formatAddress = (address: AddressLike | null | undefined): string[] => {
248
272
  * every caption and the styling.
249
273
  * A designer can mark one child as the row layout for the order lines; without
250
274
  * one the built-in line renders.
251
- * Properties: packageLabel, numberLabel, dateLabel, headerTotalLabel,
275
+ * Properties: numberLabel, numberTag, dateLabel, headerTotalLabel,
252
276
  * showInvoiceLink, invoiceLinkLabel, showCancelLink, cancelLinkLabel,
253
277
  * cancelHrefBase, showShipmentStatus, showItems, showImage, imageWidth,
254
- * imageHeight, showQuantity, quantityLabel, quantityPrefix, showColor,
278
+ * imageHeight, showQuantity, quantityLabel, showColor,
255
279
  * 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,
280
+ * showLineTracking, lineTrackingLabel, showReturnCode,
281
+ * returnCodeLabel, showShippingAddress, shippingAddressLabel,
282
+ * showBillingAddress, billingAddressLabel, showTotals, showPieceCount,
283
+ * pieceCountLabel, showDiscountItems, showInstallmentInfo,
284
+ * installmentInfoLabel, subtotalLabel, discountLabel, shippingLabel,
285
+ * freeShippingLabel, totalLabel, trackingLabel,
261
286
  * showPaymentInfo, paymentInfoLabel.
262
- * Parts: detail, header, package-label, number, date, header-total, status,
287
+ * Parts: detail, header, number, date, header-total, status,
263
288
  * 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.
289
+ * item-status, item-tracking, item-return-code, price, shipment,
290
+ * delivery, tracking, addresses, address, address-label, totals, total-row,
291
+ * total-label, total-value, grand-total, payment-info, payment-info-label.
267
292
  */
268
293
  const OrderDetailBlock = ({
269
294
  block,
@@ -310,7 +335,6 @@ const OrderDetailBlock = ({
310
335
  String(
311
336
  getResponsiveValue(block.properties?.[key], currentBreakpoint, fallback)
312
337
  );
313
- const quantityPrefix = text('quantityPrefix', 'x');
314
338
  const quantityLabel = text('quantityLabel', '');
315
339
  const subtotalLabel = text('subtotalLabel', 'Subtotal');
316
340
  const discountLabel = text('discountLabel', 'Discount');
@@ -319,11 +343,16 @@ const OrderDetailBlock = ({
319
343
  const totalLabel = text('totalLabel', 'Total');
320
344
  const trackingLabel = text('trackingLabel', 'Track shipment');
321
345
 
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', '');
346
+ // Header: empty keeps the original bare header (no separate labelled
347
+ // total line, no invoice/cancel links) — every one of these is an opt-in
348
+ // addition, same as the order-list block's labels.
326
349
  const numberLabel = text('numberLabel', '');
350
+ const numberTagValue = String(
351
+ getResponsiveValue(block.properties?.numberTag, currentBreakpoint, 'span')
352
+ );
353
+ const NumberTag = (
354
+ NUMBER_TAGS.includes(numberTagValue) ? numberTagValue : 'span'
355
+ ) as React.ElementType;
327
356
  const dateLabel = text('dateLabel', '');
328
357
  const headerTotalLabel = text('headerTotalLabel', '');
329
358
  const showInvoiceLink = readToggle(
@@ -355,6 +384,19 @@ const OrderDetailBlock = ({
355
384
  const sizeLabel = text('sizeLabel', 'Size:');
356
385
  const sizeAttributeKey = text('sizeAttributeKey', '');
357
386
 
387
+ const showLineTracking = readToggle(
388
+ block.properties?.showLineTracking,
389
+ currentBreakpoint,
390
+ false
391
+ );
392
+ const lineTrackingLabel = text('lineTrackingLabel', 'Tracking:');
393
+ const showReturnCode = readToggle(
394
+ block.properties?.showReturnCode,
395
+ currentBreakpoint,
396
+ false
397
+ );
398
+ const returnCodeLabel = text('returnCodeLabel', 'Return code:');
399
+
358
400
  const showShippingAddress = readToggle(
359
401
  block.properties?.showShippingAddress,
360
402
  currentBreakpoint,
@@ -453,6 +495,13 @@ const OrderDetailBlock = ({
453
495
  gap: '12px'
454
496
  };
455
497
 
498
+ // Below the header, every block lines its content up under the item rows'
499
+ // text half rather than the page's own left edge — the image half stays
500
+ // empty air beside them, same as the reference layout.
501
+ const halfShift: React.CSSProperties = isMobile
502
+ ? {}
503
+ : { width: '50%', marginLeft: '50%' };
504
+
456
505
  const lineRows = groupedItems.map((line) => {
457
506
  const lineCurrency = line.item.price_currency?.value || currency;
458
507
 
@@ -572,23 +621,14 @@ const OrderDetailBlock = ({
572
621
  }}
573
622
  >
574
623
  <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
- )}
584
- <span
624
+ <NumberTag
585
625
  data-order-detail-number=""
586
626
  {...partAttrs('number')}
587
627
  style={{ fontWeight: 600 }}
588
628
  >
589
629
  {numberLabel ? `${numberLabel} ` : ''}
590
630
  {order.number || orderKey}
591
- </span>
631
+ </NumberTag>
592
632
  </div>
593
633
 
594
634
  <div
@@ -700,6 +740,12 @@ const OrderDetailBlock = ({
700
740
  const sizeValue = showSize
701
741
  ? resolveAttribute(item.product?.attributes, sizeAttributeKey)
702
742
  : '';
743
+ const trackingValue = showLineTracking
744
+ ? item.tracking_number || item.tracking_url || ''
745
+ : '';
746
+ const returnCodeValue = showReturnCode
747
+ ? resolveReturnCode(item.active_cancellation_request)
748
+ : '';
703
749
 
704
750
  return (
705
751
  <li
@@ -708,6 +754,7 @@ const OrderDetailBlock = ({
708
754
  {...partAttrs('item')}
709
755
  style={{
710
756
  display: 'flex',
757
+ flexWrap: isMobile ? 'wrap' : 'nowrap',
711
758
  alignItems: 'flex-start',
712
759
  gap: '12px',
713
760
  paddingBottom: '16px',
@@ -716,90 +763,161 @@ const OrderDetailBlock = ({
716
763
  }}
717
764
  >
718
765
  {showImage && item.product?.image && (
719
- <NextLink
720
- href={isDesigner ? '#' : href || '#'}
721
- onClick={guard}
722
- style={{ flex: '0 0 auto', display: 'block' }}
766
+ <div
767
+ style={{
768
+ flex: isMobile ? '1 1 auto' : '1 1 50%',
769
+ display: 'flex',
770
+ justifyContent: isMobile ? 'flex-start' : 'flex-end'
771
+ }}
723
772
  >
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>
773
+ <NextLink
774
+ href={isDesigner ? '#' : href || '#'}
775
+ onClick={guard}
776
+ style={{ display: 'block' }}
777
+ >
778
+ {/* Plain img: order line thumbnails come from arbitrary
779
+ commerce CDNs that the brand next.config may not allow. */}
780
+ {/* eslint-disable-next-line @next/next/no-img-element */}
781
+ <img
782
+ src={item.product.image}
783
+ alt={name}
784
+ width={imageWidth}
785
+ height={imageHeight}
786
+ {...partAttrs('image')}
787
+ style={{ objectFit: 'cover', display: 'block' }}
788
+ />
789
+ </NextLink>
790
+ </div>
736
791
  )}
737
792
 
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' }}
793
+ <div
794
+ style={{
795
+ flex: isMobile ? '1 1 100%' : '1 1 50%',
796
+ display: 'flex',
797
+ flexWrap: 'wrap',
798
+ justifyContent: 'space-between',
799
+ gap: '12px',
800
+ minWidth: 0
801
+ }}
802
+ >
803
+ <div style={{ flex: '1 1 auto', minWidth: 0 }}>
804
+ <NextLink
805
+ href={isDesigner ? '#' : href || '#'}
806
+ onClick={guard}
807
+ data-order-detail-item-name=""
808
+ {...partAttrs('name')}
809
+ style={{ textDecoration: 'none' }}
754
810
  >
755
- {colorLabel} {colorValue}
756
- </div>
757
- )}
811
+ {name}
812
+ </NextLink>
758
813
 
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
- )}
814
+ {showColor && colorValue && (
815
+ <div
816
+ data-order-detail-item-color=""
817
+ {...partAttrs('color')}
818
+ style={{ marginTop: '4px' }}
819
+ >
820
+ {colorLabel} {colorValue}
821
+ </div>
822
+ )}
768
823
 
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
- )}
824
+ {showSize && sizeValue && (
825
+ <div
826
+ data-order-detail-item-size=""
827
+ {...partAttrs('size')}
828
+ style={{ marginTop: '4px' }}
829
+ >
830
+ {sizeLabel} {sizeValue}
831
+ </div>
832
+ )}
833
+
834
+ {showQuantity && (
835
+ <div
836
+ data-order-detail-item-quantity=""
837
+ {...partAttrs('quantity')}
838
+ style={{ marginTop: '4px', opacity: 0.8 }}
839
+ >
840
+ {quantityLabel
841
+ ? `${quantityLabel} ${line.quantity}`
842
+ : line.quantity}
843
+ </div>
844
+ )}
780
845
 
781
- {showShipmentStatus && item.status?.label && (
782
846
  <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 }}
847
+ data-order-detail-item-price=""
848
+ {...partAttrs('price')}
849
+ style={{ marginTop: '4px', fontWeight: 600 }}
787
850
  >
788
- {item.status.label}
851
+ {/* The row now stands for every unit of the product, so
852
+ the group's summed line total is what it must price —
853
+ the single line's total_amount would understate an
854
+ "x3" row. For an ungrouped row this is the value
855
+ printed before. */}
856
+ {formatMoney(line.total, {
857
+ locale,
858
+ currency: lineCurrency
859
+ })}
789
860
  </div>
790
- )}
791
- </div>
861
+ </div>
792
862
 
793
- <span data-order-detail-item-price="" {...partAttrs('price')}>
794
- {/* The row now stands for every unit of the product, so the
795
- group's summed line total is what it must price — the
796
- single line's total_amount would understate an "x3" row.
797
- For an ungrouped row this is the value printed before. */}
798
- {formatMoney(line.total, {
799
- locale,
800
- currency: lineCurrency
801
- })}
802
- </span>
863
+ {showShipmentStatus &&
864
+ (item.status?.label ||
865
+ trackingValue ||
866
+ returnCodeValue) && (
867
+ <div
868
+ style={{
869
+ display: 'flex',
870
+ flexDirection: 'column',
871
+ alignItems: isMobile ? 'flex-start' : 'flex-end',
872
+ textAlign: isMobile ? 'left' : 'right',
873
+ gap: '4px',
874
+ flex: isMobile ? '1 1 100%' : '0 0 auto'
875
+ }}
876
+ >
877
+ {item.status?.label && (
878
+ <span
879
+ data-order-detail-item-status=""
880
+ data-order-status-value={item.status.value}
881
+ {...partAttrs('item-status')}
882
+ style={{ display: 'inline-block' }}
883
+ >
884
+ {item.status.label}
885
+ </span>
886
+ )}
887
+ {trackingValue && (
888
+ <span
889
+ data-order-detail-item-tracking=""
890
+ {...partAttrs('item-tracking')}
891
+ style={{ opacity: 0.8 }}
892
+ >
893
+ {lineTrackingLabel}{' '}
894
+ {item.tracking_url ? (
895
+ <a
896
+ href={isDesigner ? '#' : item.tracking_url}
897
+ onClick={guard}
898
+ target={isDesigner ? undefined : '_blank'}
899
+ rel="noreferrer"
900
+ style={{ color: 'inherit' }}
901
+ >
902
+ {item.tracking_number || item.tracking_url}
903
+ </a>
904
+ ) : (
905
+ trackingValue
906
+ )}
907
+ </span>
908
+ )}
909
+ {returnCodeValue && (
910
+ <span
911
+ data-order-detail-item-return-code=""
912
+ {...partAttrs('item-return-code')}
913
+ style={{ opacity: 0.8 }}
914
+ >
915
+ {returnCodeLabel} {returnCodeValue}
916
+ </span>
917
+ )}
918
+ </div>
919
+ )}
920
+ </div>
803
921
  </li>
804
922
  );
805
923
  })}
@@ -810,7 +928,12 @@ const OrderDetailBlock = ({
810
928
  <section
811
929
  data-order-detail-shipment=""
812
930
  {...partAttrs('shipment')}
813
- style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
931
+ style={{
932
+ display: 'flex',
933
+ flexDirection: 'column',
934
+ gap: '4px',
935
+ ...halfShift
936
+ }}
814
937
  >
815
938
  {items.some((item) => item.estimated_delivery_date) && (
816
939
  <span
@@ -854,7 +977,8 @@ const OrderDetailBlock = ({
854
977
  style={{
855
978
  display: 'flex',
856
979
  flexDirection: isMobile ? 'column' : 'row',
857
- gap: isMobile ? '16px' : '40px'
980
+ gap: isMobile ? '16px' : '40px',
981
+ ...halfShift
858
982
  }}
859
983
  >
860
984
  {showShippingAddress &&
@@ -872,7 +996,12 @@ const OrderDetailBlock = ({
872
996
  <section
873
997
  data-order-detail-totals=""
874
998
  {...partAttrs('totals')}
875
- style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}
999
+ style={{
1000
+ display: 'flex',
1001
+ flexDirection: 'column',
1002
+ gap: '8px',
1003
+ ...halfShift
1004
+ }}
876
1005
  >
877
1006
  {showPieceCount &&
878
1007
  totalRow(
@@ -923,7 +1052,12 @@ const OrderDetailBlock = ({
923
1052
  <section
924
1053
  data-order-detail-payment=""
925
1054
  {...partAttrs('payment-info')}
926
- style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}
1055
+ style={{
1056
+ display: 'flex',
1057
+ flexDirection: 'column',
1058
+ gap: '4px',
1059
+ ...halfShift
1060
+ }}
927
1061
  >
928
1062
  <div
929
1063
  {...partAttrs('payment-info-label')}