@mohasinac/appkit 4.62.0 → 4.62.1

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.
Files changed (204) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
  3. package/dist/_internal/server/features/checkout/actions.js +542 -26
  4. package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
  5. package/dist/_internal/server/features/invoicing/allocate.js +162 -0
  6. package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
  7. package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
  8. package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
  9. package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
  10. package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
  11. package/dist/_internal/server/features/invoicing/issue.js +202 -0
  12. package/dist/_internal/server/features/orders/actions.d.ts +8 -1
  13. package/dist/_internal/server/features/orders/actions.js +81 -8
  14. package/dist/_internal/server/features/orders/adapters.js +33 -0
  15. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
  16. package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
  17. package/dist/_internal/server/features/points/award.js +46 -4
  18. package/dist/_internal/server/features/refunds/actions.js +26 -0
  19. package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
  20. package/dist/_internal/server/features/shipping/courier-block.js +239 -0
  21. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  22. package/dist/_internal/server/features/site-settings/adapters.js +15 -0
  23. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  24. package/dist/_internal/server/features/stores/adapters.js +51 -0
  25. package/dist/_internal/server/functions/scheduled.js +38 -1
  26. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
  27. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
  28. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
  29. package/dist/_internal/server/jobs/core/index.d.ts +2 -1
  30. package/dist/_internal/server/jobs/core/index.js +2 -1
  31. package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
  32. package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
  33. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
  34. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
  35. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
  36. package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
  37. package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
  38. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
  39. package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
  40. package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
  41. package/dist/_internal/shared/actions/action-registry.js +28 -0
  42. package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
  43. package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
  44. package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
  45. package/dist/_internal/shared/checkout/order-math.js +173 -0
  46. package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
  47. package/dist/_internal/shared/checkout/refundable-base.js +13 -1
  48. package/dist/_internal/shared/checkout/split.d.ts +87 -0
  49. package/dist/_internal/shared/checkout/split.js +100 -0
  50. package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
  51. package/dist/_internal/shared/content/disclaimers.js +217 -0
  52. package/dist/_internal/shared/content/published-values.d.ts +88 -0
  53. package/dist/_internal/shared/content/published-values.js +198 -0
  54. package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
  55. package/dist/_internal/shared/features/admin/integrations.js +253 -0
  56. package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
  57. package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
  58. package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
  59. package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
  60. package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
  61. package/dist/_internal/shared/features/points/accepts.js +43 -0
  62. package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
  63. package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
  64. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  65. package/dist/_internal/shared/fees/calculator.d.ts +33 -1
  66. package/dist/_internal/shared/fees/calculator.js +32 -2
  67. package/dist/client.d.ts +11 -0
  68. package/dist/client.js +30 -0
  69. package/dist/constants/api-endpoints.d.ts +42 -0
  70. package/dist/constants/api-endpoints.js +14 -0
  71. package/dist/constants/field-names.d.ts +1 -0
  72. package/dist/constants/field-names.js +5 -0
  73. package/dist/contracts/index.d.ts +1 -1
  74. package/dist/contracts/shipping.d.ts +45 -0
  75. package/dist/contracts/shipping.js +15 -0
  76. package/dist/features/about/components/AboutView.js +1 -1
  77. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
  78. package/dist/features/account/components/UserOrdersView.js +19 -33
  79. package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
  80. package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
  81. package/dist/features/admin/components/AdminInvoicingView.js +65 -0
  82. package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
  83. package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
  84. package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
  85. package/dist/features/admin/components/AdminOrdersView.js +67 -5
  86. package/dist/features/admin/components/OwnerQuickActions.js +24 -2
  87. package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
  88. package/dist/features/admin/constants/filter-tabs.js +6 -0
  89. package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
  90. package/dist/features/admin/hooks/useAdminListingData.js +16 -0
  91. package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
  92. package/dist/features/admin/schemas/firestore.d.ts +30 -0
  93. package/dist/features/admin/schemas/firestore.js +5 -0
  94. package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
  95. package/dist/features/audit-log/schemas/firestore.js +15 -0
  96. package/dist/features/auth/permissions/constants.js +13 -0
  97. package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
  98. package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
  99. package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
  100. package/dist/features/cart/components/PointsTenderPicker.js +15 -0
  101. package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
  102. package/dist/features/cart/components/index.d.ts +2 -0
  103. package/dist/features/cart/components/index.js +1 -0
  104. package/dist/features/cart/repository/cart.repository.d.ts +19 -0
  105. package/dist/features/cart/repository/cart.repository.js +27 -0
  106. package/dist/features/cart/schemas/firestore.d.ts +92 -0
  107. package/dist/features/contact/email.d.ts +27 -10
  108. package/dist/features/contact/email.js +20 -3
  109. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
  110. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
  111. package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
  112. package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
  113. package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
  114. package/dist/features/faq/components/FAQAccordion.js +29 -4
  115. package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
  116. package/dist/features/invoicing/schemas/firestore.js +45 -0
  117. package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
  118. package/dist/features/invoicing/utils/financial-year.js +53 -0
  119. package/dist/features/jobs/actions/enqueue-job.js +18 -1
  120. package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
  121. package/dist/features/jobs/repository/jobs.repository.js +84 -1
  122. package/dist/features/jobs/schemas/firestore.d.ts +80 -0
  123. package/dist/features/jobs/schemas/firestore.js +37 -0
  124. package/dist/features/lottery/schemas/zod.d.ts +6 -6
  125. package/dist/features/media/MediaImage.js +22 -1
  126. package/dist/features/media/upload/ImageUpload.js +1 -1
  127. package/dist/features/obligations/constants/policies.js +24 -1
  128. package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
  129. package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
  130. package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
  131. package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
  132. package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
  133. package/dist/features/orders/components/RefundRequestView.js +19 -1
  134. package/dist/features/orders/components/index.d.ts +4 -0
  135. package/dist/features/orders/components/index.js +2 -0
  136. package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
  137. package/dist/features/orders/constants/order-lane-tabs.js +88 -0
  138. package/dist/features/orders/repository/orders.repository.d.ts +57 -0
  139. package/dist/features/orders/repository/orders.repository.js +145 -0
  140. package/dist/features/orders/schemas/firestore.d.ts +196 -1
  141. package/dist/features/orders/schemas/index.d.ts +54 -6
  142. package/dist/features/orders/schemas/index.js +12 -0
  143. package/dist/features/orders/types/index.d.ts +26 -0
  144. package/dist/features/orders/utils/courier-block.d.ts +25 -0
  145. package/dist/features/orders/utils/courier-block.js +23 -0
  146. package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
  147. package/dist/features/payment-cases/schemas/firestore.js +1 -0
  148. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  149. package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
  150. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  151. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  152. package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
  153. package/dist/features/promotions/actions/coupon-stacking.js +21 -2
  154. package/dist/features/promotions/components/CouponCard.js +13 -0
  155. package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
  156. package/dist/features/promotions/constants/coupon-help.js +9 -1
  157. package/dist/features/promotions/schemas/firestore.d.ts +24 -0
  158. package/dist/features/promotions/schemas/firestore.js +4 -0
  159. package/dist/features/promotions/types/index.d.ts +19 -1
  160. package/dist/features/scams/components/ScamProfileView.js +2 -2
  161. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  162. package/dist/features/search/action-index/tab-entries.js +1 -1
  163. package/dist/features/seller/actions/seller-actions.js +33 -3
  164. package/dist/features/seller/components/SellerOrdersView.js +129 -27
  165. package/dist/features/seller/hooks/useSellerListingData.js +25 -0
  166. package/dist/features/shipments/schemas/validation.d.ts +4 -4
  167. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
  168. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
  169. package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
  170. package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
  171. package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
  172. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
  173. package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
  174. package/dist/features/stores/schemas/firestore.d.ts +71 -0
  175. package/dist/features/stores/types/index.d.ts +19 -0
  176. package/dist/features/support/schemas/index.d.ts +6 -6
  177. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  178. package/dist/index.d.ts +6 -1
  179. package/dist/index.js +14 -1
  180. package/dist/next/routing/route-map.d.ts +20 -0
  181. package/dist/next/routing/route-map.js +10 -0
  182. package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
  183. package/dist/providers/shipping-shiprocket/index.js +84 -1
  184. package/dist/schemas/registry.d.ts +47 -18
  185. package/dist/schemas/sieve.d.ts +2 -2
  186. package/dist/server-entry.d.ts +2 -2
  187. package/dist/server-entry.js +8 -1
  188. package/dist/server.d.ts +10 -1
  189. package/dist/server.js +39 -1
  190. package/dist/styles.css +64 -80
  191. package/dist/tailwind-utilities.css +1 -1
  192. package/dist/tokens/themes/default-dark.js +1 -0
  193. package/dist/tokens/themes/default-light.js +1 -0
  194. package/dist/tokens/themes/required.d.ts +1 -1
  195. package/dist/tokens/themes/required.js +1 -0
  196. package/dist/tokens/tokens.css +15 -0
  197. package/dist/ui/components/Disclaimer.d.ts +19 -0
  198. package/dist/ui/components/Disclaimer.js +74 -0
  199. package/dist/ui/components/tabs-overflow.js +64 -11
  200. package/dist/ui/index.d.ts +2 -0
  201. package/dist/ui/index.js +6 -0
  202. package/dist/ui/rich-text/RichText.style.css +48 -79
  203. package/package.json +1 -1
  204. package/scripts/seed-cli.mjs +1831 -1822
@@ -113,8 +113,29 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
113
113
  * the placeholder, the `<picture>` and the plain `<Image>` cannot disagree
114
114
  * about their dimensions — a fallback that occupies a different box from the
115
115
  * image it replaces makes a grid jump as things load.
116
+ *
117
+ * 🛑 `max-h-full` IS THE OTHER HALF OF SIZE_BOX, added 2026-10-23. SIZE_BOX
118
+ * alone fixed the auto-height parent and broke the ALREADY-SIZED one: a
119
+ * parent that is 320x128 got a 320x320 box, so `overflow-hidden` cropped it
120
+ * to the top 40% of a letterboxed square. Measured 89 of 143 call sites in
121
+ * that shape — every image preview on the review, document and site-settings
122
+ * surfaces. `max-height:100%` resolves to `none` against an auto-height
123
+ * parent, so it clamps ONLY in the broken case.
124
+ *
125
+ * Measured in a browser (box for each parent shape), not argued:
126
+ *
127
+ * parent w-full aspect +max-h-full +h-full
128
+ * 320x128 definite 320x320 ✗ 320x128 ✓ 320x128 ✓
129
+ * h128, width shrink 320x320 ✗ 320x128 ✓ 320x128 ✓
130
+ * width 200, h AUTO 200x200 ✓ 200x200 ✓ 200x784 ✗
131
+ * 64px thumb stretched
132
+ * by a flex row 64x64 ✓ 64x64 ✓ 64x90 ✗
133
+ *
134
+ * So `h-full` is NOT the fix — it wins over `aspect-ratio` whenever the
135
+ * parent's height is definite, including the height a flex/grid row hands a
136
+ * thumbnail by stretching it. That is why this is a MAX and not a height.
116
137
  */
117
- const boxClass = fill ? "h-full" : SIZE_BOX[size];
138
+ const boxClass = fill ? "h-full" : `${SIZE_BOX[size]} max-h-full`;
118
139
  // Reset error/load/retry state whenever the resolved src actually changes.
119
140
  // Without this, a component instance that hit a load failure once (e.g.
120
141
  // lost the post-upload propagation race above) latches hasError forever,
@@ -222,7 +222,7 @@ accept = IMAGE_ACCEPT_ATTR, maxSizeMB = MAX_IMAGE_BYTES / MEGABYTE, label = "Upl
222
222
  fileInputRef.current.value = "";
223
223
  onChange?.("");
224
224
  };
225
- return (_jsxs(Stack, { gap: "3", children: [label && (_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label })), _jsx(Div, { className: "relative", children: preview ? (_jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { className: `relative h-32 max-w-xs mx-auto ${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(MediaImage, { src: preview, alt: "Preview", size: "card" }), uploading && progress > 0 && (_jsx(Div, { className: "absolute inset-x-0 bottom-0", children: _jsx(Progress, { value: progress, size: "sm" }) }))] }), !uploading && (_jsxs(Row, { wrap: true, gap: "sm", children: [(enableCrop || enableAdvancedCrop) && (_jsx(Button, { type: "button", onClick: () => openEditor(preview), variant: "secondary", size: "sm", children: tMediaEditor("editImage") })), _jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "outline", size: "sm", children: tUpload("change") }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] })) : (_jsxs(_Fragment, { children: [showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: "photo", facingMode: "environment", onCapture: handleCameraCapture, onError: setError })), !showCamera && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "sm", wrap: true, children: [showFileInput && (_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), disabled: uploading, variant: "outline", size: "sm", children: uploading ? tUpload("uploading") : tUpload("clickToUpload") })), captureSource !== "file-only" && isCameraSupported && (_jsx(Button, { type: "button", onClick: () => setCaptureMode("camera"), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") })), captureSource !== "file-only" && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") }))] }), _jsxs(Text, { size: "xs", variant: "secondary", children: [accept === "image/*"
225
+ return (_jsxs(Stack, { gap: "3", children: [label && (_jsx(Label, { className: "block", color: "muted", size: "sm", weight: "medium", children: label })), _jsx(Div, { className: "relative", children: preview ? (_jsxs(Stack, { gap: "sm", children: [_jsxs(Div, { className: `relative w-full h-32 max-w-xs mx-auto ${__O.hidden}`, rounded: "lg", border: "default", children: [_jsx(MediaImage, { src: preview, alt: "Preview", size: "card" }), uploading && progress > 0 && (_jsx(Div, { className: "absolute inset-x-0 bottom-0", children: _jsx(Progress, { value: progress, size: "sm" }) }))] }), !uploading && (_jsxs(Row, { wrap: true, gap: "sm", children: [(enableCrop || enableAdvancedCrop) && (_jsx(Button, { type: "button", onClick: () => openEditor(preview), variant: "secondary", size: "sm", children: tMediaEditor("editImage") })), _jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), variant: "outline", size: "sm", children: tUpload("change") }), _jsx(Button, { type: "button", onClick: handleRemove, variant: "danger", size: "sm", children: tUpload("remove") })] }))] })) : (_jsxs(_Fragment, { children: [showCamera && isCameraSupported && (_jsx(CameraCapture, { mode: "photo", facingMode: "environment", onCapture: handleCameraCapture, onError: setError })), !showCamera && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "sm", wrap: true, children: [showFileInput && (_jsx(Button, { type: "button", onClick: () => fileInputRef.current?.click(), disabled: uploading, variant: "outline", size: "sm", children: uploading ? tUpload("uploading") : tUpload("clickToUpload") })), captureSource !== "file-only" && isCameraSupported && (_jsx(Button, { type: "button", onClick: () => setCaptureMode("camera"), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") })), captureSource !== "file-only" && !isCameraSupported && (_jsx(Button, { type: "button", onClick: () => mobileCaptureRef.current?.click(), disabled: uploading, variant: "ghost", size: "sm", children: t("switchToCamera") }))] }), _jsxs(Text, { size: "xs", variant: "secondary", children: [accept === "image/*"
226
226
  ? `JPG PNG GIF WebP`
227
227
  : accept.split(",").map((a) => a.split("/")[1]?.toUpperCase() ?? a).join(" "), " ", "\u2014 max ", maxSizeMB, "MB"] })] }))] })) }), _jsx("input", { ref: fileInputRef, type: "file", accept: accept, onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-input" }), showCamera && !isCameraSupported && (_jsx("input", { ref: mobileCaptureRef, type: "file", accept: IMAGE_ACCEPT_ATTR, capture: "environment", onChange: handleFileChange, className: "hidden", "aria-hidden": "true", "data-testid": "media-upload-capture-input" })), helperText && !error && (_jsx(Text, { variant: "secondary", size: "xs", children: helperText })), error && _jsx(Alert, { variant: "error", children: error }), uploading && (_jsxs(Row, { gap: "sm", children: [_jsx(Spinner, { size: "sm" }), _jsx(Text, { size: "sm", variant: "secondary", children: tUpload("uploadingProgress", { progress }) })] })), cropPreviewUrl && !enableAdvancedCrop && (_jsx(ImageCropModal, { isOpen: showCropModal, imageUrl: cropPreviewUrl, onClose: handleCropClose, onSave: handleCropSave })), cropPreviewUrl && enableAdvancedCrop && (_jsx(ImageEditor, { isOpen: showAdvancedEditor, imageUrl: cropPreviewUrl, onClose: handleAdvancedEditorClose, onSave: handleAdvancedEditorSave, aspectRatio: cropAspectRatio }))] }));
228
228
  }
@@ -37,6 +37,7 @@
37
37
  import { ORDER_RETURN_WINDOW_DAYS, ORDER_AUTO_CONFIRM_DAYS, } from "../../../_internal/shared/features/orders/config.js";
38
38
  import { PROTECTION_CLAIM_WINDOW_HOURS } from "../../../_internal/shared/features/shipping/protections.js";
39
39
  import { PAYMENT_WINDOW_MINUTES } from "../../orders/constants/payment-window.js";
40
+ import { hasOpenCourierBlock } from "../../orders/utils/courier-block.js";
40
41
  const isPaid = (o) => o.paymentStatus === "paid";
41
42
  const notTerminal = (o) => o.status !== "cancelled" && o.status !== "refunded";
42
43
  /**
@@ -87,7 +88,29 @@ export const OBLIGATION_POLICIES = {
87
88
  anchor: (o) => o.acceptedAt,
88
89
  duration: { value: 2, unit: "days", calendar: "business" },
89
90
  warnAt: [{ value: 8, unit: "hours", calendar: "calendar" }],
90
- precondition: (o) => isPaid(o) && notTerminal(o) && !hasDispatched(o),
91
+ /*
92
+ * 🛑 `!hasOpenCourierBlock(o)` — DO NOT REMOVE, and do not "simplify" it
93
+ * into the stored field.
94
+ *
95
+ * This is `onDue: "act"`, so firing cancels the order and refunds the
96
+ * buyer with the seller recorded as having failed to dispatch. When the
97
+ * platform's own courier wallet is empty, nothing the seller can do makes
98
+ * the booking succeed — so without this guard our funding state cancels a
99
+ * paid order and blames them for it.
100
+ *
101
+ * The engine re-evaluates `precondition` at fire time and closes
102
+ * `superseded` when it returns false, so the clock PAUSES while blocked and
103
+ * resumes when the block clears. No new mechanism, no stored timer.
104
+ *
105
+ * 🛑 This is the ONLY `onDue: "act"` policy that needs the guard, and the
106
+ * others were checked rather than assumed: `hasDispatched` is
107
+ * `Boolean(o.shippingDate)`, so `delivery-confirm` (which REQUIRES
108
+ * `hasDispatched`) and everything post-delivery can never fire on an order
109
+ * that was never booked. `seller-accept` requires `!o.acceptedAt`, and a
110
+ * booking is only attempted after acceptance. Adding the guard there too
111
+ * would be redundant and would imply nobody traced it.
112
+ */
113
+ precondition: (o) => isPaid(o) && notTerminal(o) && !hasDispatched(o) && !hasOpenCourierBlock(o),
91
114
  blockedBy: ["seller-accept"],
92
115
  onDue: "act",
93
116
  publishedCopyKey: "obligations.sellerDispatch",
@@ -32,7 +32,16 @@ function legacyFallback(order) {
32
32
  method,
33
33
  transactionId: order.paymentTransactionId ?? order.paymentId,
34
34
  proofUrl: order.paymentProofUrl,
35
- amount: order.totalPrice,
35
+ /*
36
+ * 🛑 D46/D47 — `amountDue`, falling back to `totalPrice`. This figure is
37
+ * what a MANUAL payer is told to transfer and what an admin verifies the
38
+ * proof against, so on a points-funded order `totalPrice` would ask the
39
+ * buyer for money the order does not owe — and then the admin would reject
40
+ * a correct ₹750 UTR for not matching ₹1,000.
41
+ *
42
+ * `?? totalPrice`, never `?? 0`: absent means no points were used.
43
+ */
44
+ amount: order.amountDue ?? order.totalPrice,
36
45
  verificationMethod: method === "cod" ? "cod_collection" : method === "manual" ? "manual_review" : "order_status",
37
46
  };
38
47
  }
@@ -0,0 +1,8 @@
1
+ import type { OrderSourceContext } from "../schemas/firestore";
2
+ export interface OrderProvenanceCardProps {
3
+ sourceContext?: OrderSourceContext;
4
+ /** Falls back to the configured default currency when omitted. */
5
+ currency?: string;
6
+ className?: string;
7
+ }
8
+ export declare function OrderProvenanceCard({ sourceContext, currency, className, }: OrderProvenanceCardProps): import("react").JSX.Element | null;
@@ -0,0 +1,185 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * OrderProvenanceCard — renders `OrderDocument.sourceContext`, the record of
5
+ * HOW the buyer earned the right to buy at this price.
6
+ *
7
+ * ## Why this component had to be written
8
+ *
9
+ * `sourceContext` is a six-variant discriminated union, assembled at a real
10
+ * cost — an auction pays one `findByProductSorted`, an offer one `findById` —
11
+ * written on every order by both checkout paths, mapped through to the client
12
+ * type by `orderDocumentToOrder` (adapters.ts:359) … and, until this file,
13
+ * rendered in **zero** `.tsx` files anywhere in the repo.
14
+ *
15
+ * That is Root Cause #52/#57: the data was already present and complete, and
16
+ * no surface was ever wired to present it. The buyer could not see what they
17
+ * had agreed, the seller could not see what they had accepted, and support
18
+ * could not see either.
19
+ *
20
+ * ## What it deliberately does NOT do
21
+ *
22
+ * **`standard` renders nothing.** Its only field is the list price, which the
23
+ * order total already shows — a card reading "Standard purchase" on every
24
+ * ordinary order is noise that would teach people to ignore the card on the
25
+ * orders where it matters. It is still an explicit `case`, so the switch stays
26
+ * exhaustive.
27
+ *
28
+ * **A saving is shown only where it is real.** Not every variant records a
29
+ * displaced price: `auction-buy-now` has `listPrice`, `offer-accepted` has
30
+ * `listPriceAtOffer`, and `auction-won` has only `startingBid` — which is a
31
+ * floor, not an asking price, so presenting the gap as a discount would be a
32
+ * fabrication. Where a displaced price exists it is shown only when it is
33
+ * strictly greater than what was paid.
34
+ *
35
+ * **Nothing is fetched.** It is handed the context the page already loaded; a
36
+ * component that fetched would fire one request per opened drawer per portal.
37
+ */
38
+ import { Badge, Card, DescriptionField, Heading, Row, Stack, Text } from "../../../ui/index.js";
39
+ import { formatCurrency } from "../../../utils/number.formatter.js";
40
+ import { formatDate } from "../../../utils/date.formatter.js";
41
+ /**
42
+ * A date on a `sourceContext` survives a Firestore round trip as a Timestamp,
43
+ * an ISO string or a Date depending on which path loaded it, so normalise
44
+ * rather than assuming. An unparseable value renders an em-dash — never
45
+ * `new Date()`, which would invent a timestamp for a record that has none
46
+ * (the rule `OrderStatusTimeline` already follows).
47
+ */
48
+ function dateText(value) {
49
+ if (value === null || value === undefined)
50
+ return "—";
51
+ const raw = typeof value === "object" && value !== null && "toDate" in value
52
+ ? value.toDate()
53
+ : value;
54
+ const d = raw instanceof Date ? raw : new Date(raw);
55
+ return Number.isFinite(d.getTime()) ? formatDate(d) : "—";
56
+ }
57
+ function plural(n, one, many) {
58
+ return `${n} ${n === 1 ? one : many}`;
59
+ }
60
+ function contentFor(ctx, currency) {
61
+ const money = (n) => formatCurrency(n, currency);
62
+ switch (ctx.path) {
63
+ case "standard":
64
+ // Deliberately silent — see the header. Explicit so the switch is exhaustive.
65
+ return null;
66
+ case "auction-won": {
67
+ const rows = [
68
+ { label: "Winning bid", value: money(ctx.winningBidAmount) },
69
+ { label: "Bids in the auction", value: String(ctx.bidCount) },
70
+ { label: "Starting bid", value: money(ctx.startingBid) },
71
+ ];
72
+ if (ctx.runnerUpAmount !== undefined) {
73
+ rows.push({ label: "Runner-up bid", value: money(ctx.runnerUpAmount) });
74
+ }
75
+ if (ctx.reservePrice !== undefined) {
76
+ rows.push({
77
+ label: "Reserve",
78
+ value: `${money(ctx.reservePrice)} — ${ctx.reserveMet ? "met" : "not met"}`,
79
+ });
80
+ }
81
+ rows.push({ label: "Auction ended", value: dateText(ctx.auctionEndedAt) });
82
+ return {
83
+ title: "Won at auction",
84
+ tone: "success",
85
+ summary: ctx.bidCount > 1
86
+ ? `Highest of ${plural(ctx.bidCount, "bid", "bids")}.`
87
+ : "The only bid placed.",
88
+ rows,
89
+ };
90
+ }
91
+ case "auction-buy-now": {
92
+ const rows = [
93
+ { label: "Buy Now price", value: money(ctx.buyNowPrice) },
94
+ { label: "List price", value: money(ctx.listPrice) },
95
+ {
96
+ label: "Standing bid beaten",
97
+ value: ctx.standingBidAtBuyout > 0 ? money(ctx.standingBidAtBuyout) : "none",
98
+ },
99
+ { label: "Bids at buyout", value: String(ctx.bidCount) },
100
+ { label: "Bought", value: dateText(ctx.boughtAt) },
101
+ ];
102
+ return {
103
+ title: "Bought now, ending the auction",
104
+ tone: "info",
105
+ /*
106
+ * A buyout IS a real bid (the schema's own note: recording bidCount 0
107
+ * would bake a falsehood into an immutable record), so the summary
108
+ * must not claim nobody had bid when somebody had.
109
+ */
110
+ summary: ctx.bidCount > 0
111
+ ? `Taken at the Buy Now price while ${plural(ctx.bidCount, "bid was", "bids were")} live.`
112
+ : "Taken at the Buy Now price before any bids were placed.",
113
+ rows,
114
+ saved: ctx.listPrice > ctx.buyNowPrice ? ctx.listPrice - ctx.buyNowPrice : undefined,
115
+ };
116
+ }
117
+ case "offer-accepted": {
118
+ const rows = [
119
+ { label: "Agreed price", value: money(ctx.offeredAmount) },
120
+ { label: "Asking price when offered", value: money(ctx.listPriceAtOffer) },
121
+ ];
122
+ if (ctx.counterAmount !== undefined) {
123
+ rows.push({ label: "Final counter", value: money(ctx.counterAmount) });
124
+ }
125
+ rows.push({ label: "Negotiation", value: plural(ctx.counterRounds, "round", "rounds") }, { label: "Accepted", value: dateText(ctx.acceptedAt) });
126
+ return {
127
+ title: "Price agreed by offer",
128
+ tone: "primary",
129
+ summary: ctx.counterRounds > 1
130
+ ? `Agreed after ${plural(ctx.counterRounds, "round", "rounds")} of counter-offers.`
131
+ : "Accepted outright, with no counter-offer.",
132
+ rows,
133
+ saved: ctx.listPriceAtOffer > ctx.offeredAmount
134
+ ? ctx.listPriceAtOffer - ctx.offeredAmount
135
+ : undefined,
136
+ };
137
+ }
138
+ case "pre-order":
139
+ return {
140
+ title: "Pre-order",
141
+ tone: "warning",
142
+ summary: ctx.balanceDue > 0
143
+ ? "A deposit was taken at checkout; the balance is due before dispatch."
144
+ : "Paid in full at checkout.",
145
+ rows: [
146
+ { label: "Deposit paid", value: money(ctx.depositAmount) },
147
+ { label: "Balance due", value: money(ctx.balanceDue) },
148
+ { label: "Expected delivery", value: dateText(ctx.expectedDeliveryDate) },
149
+ ],
150
+ };
151
+ case "prize-draw":
152
+ return {
153
+ title: "Prize draw entry",
154
+ tone: "info",
155
+ summary: ctx.revealMode === "instant"
156
+ ? "The result was revealed immediately."
157
+ : "The result is revealed at the scheduled draw.",
158
+ rows: [
159
+ { label: "Entries bought", value: plural(ctx.entryCount, "entry", "entries") },
160
+ { label: "Price per entry", value: money(ctx.pricePerEntry) },
161
+ { label: "Reveal", value: ctx.revealMode === "instant" ? "Instant" : "Scheduled" },
162
+ ],
163
+ };
164
+ default: {
165
+ /*
166
+ * Exhaustive. A seventh variant — the shared-purchase path S5a still
167
+ * needs — will not compile rather than silently rendering nothing, which
168
+ * is the failure mode of `default: return null` and is precisely how this
169
+ * union came to have six populated variants and no reader at all.
170
+ */
171
+ const unreachable = ctx;
172
+ return unreachable;
173
+ }
174
+ }
175
+ }
176
+ export function OrderProvenanceCard({ sourceContext, currency, className, }) {
177
+ // Every order written before W2 has no context at all — absence is normal
178
+ // and is not an error state to announce.
179
+ if (!sourceContext)
180
+ return null;
181
+ const content = contentFor(sourceContext, currency);
182
+ if (!content)
183
+ return null;
184
+ return (_jsx(Card, { variant: "outlined", padding: "md", className: className, children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Heading, { level: 3, size: "sm", children: content.title }), content.saved !== undefined ? (_jsx(Badge, { variant: "success", children: `Saved ${formatCurrency(content.saved, currency)}` })) : null] }), content.summary ? (_jsx(Text, { size: "sm", color: "muted", children: content.summary })) : null, _jsx(Row, { as: "dl", wrap: true, gap: "md", children: content.rows.map((row) => (_jsx(DescriptionField, { label: row.label, value: row.value }, row.label))) })] }) }));
185
+ }
@@ -0,0 +1,14 @@
1
+ import { type DisputeGateOrder, type DisputeKind } from "../../../_internal/shared/features/orders/dispute-reasons";
2
+ export interface RaiseDisputeFormProps {
3
+ /** The gate reads four dates off this; an `OrderDocument` satisfies it. */
4
+ order: DisputeGateOrder;
5
+ /** `GET ?kind=` / `POST` — supplied by the consumer so appkit holds no route. */
6
+ reasonsEndpoint: string;
7
+ onSubmit: (input: {
8
+ kind: DisputeKind;
9
+ reasonId: string;
10
+ detail?: string;
11
+ }) => Promise<void>;
12
+ onDone?: () => void;
13
+ }
14
+ export declare function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }: RaiseDisputeFormProps): import("react").JSX.Element;
@@ -0,0 +1,111 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /**
4
+ * The buyer's dispute form: pick a kind, pick a reason, say what happened.
5
+ *
6
+ * ## Why the kind comes first
7
+ *
8
+ * The two kinds have MUTUALLY EXCLUSIVE gates — an item problem needs the
9
+ * order to have arrived, a non-delivery needs it not to have. So the kind is
10
+ * the first choice, it scopes the reason list, and it decides which gate the
11
+ * server will apply. Offering one merged list of reasons would mean a buyer
12
+ * could pick "it never arrived" on a delivered order and have the server
13
+ * refuse them, which reads as the form being broken rather than as the wrong
14
+ * choice.
15
+ *
16
+ * ## The gate is checked HERE for the affordance and AGAIN on the server
17
+ *
18
+ * 🛑 This copy decides what to OFFER. The server decides what to ACCEPT, and
19
+ * it is the authority — `raiseOrderDisputeAction` re-runs `checkDisputeGate`
20
+ * on the real order. Showing a kind the server would refuse is the dead-end
21
+ * shape (CLAUDE.md's rule for `OrderCompletionPanel`: never offer a button the
22
+ * server will refuse), and trusting this copy alone would be a client-side
23
+ * security gate.
24
+ */
25
+ import { useCallback, useMemo, useState } from "react";
26
+ import { DISPUTE_KIND, DISPUTE_KIND_LABEL, checkDisputeGate, disputeNotReachedFilableFrom, } from "../../../_internal/shared/features/orders/dispute-reasons.js";
27
+ import { Alert, Button, Div, FieldTextarea, Form, Stack, Text, useToast } from "../../../ui/index.js";
28
+ import { PaginatedSelect } from "../../../ui/components/PaginatedSelect.js";
29
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
30
+ import { normalizeError } from "../../../errors/normalize.js";
31
+ export function RaiseDisputeForm({ order, reasonsEndpoint, onSubmit, onDone, }) {
32
+ const { showToast } = useToast();
33
+ const [kind, setKind] = useState(null);
34
+ const [reasonId, setReasonId] = useState(null);
35
+ const [detail, setDetail] = useState("");
36
+ const [isPending, setIsPending] = useState(false);
37
+ /*
38
+ * Which kinds this order can actually support, with the server's own gate.
39
+ * A kind that would be refused is shown DISABLED with the reason rather than
40
+ * hidden: "why can't I report this?" is a question the form should answer,
41
+ * and a silently missing option looks like a bug.
42
+ */
43
+ const kindStates = useMemo(() => DISPUTE_KIND.map((k) => ({
44
+ kind: k,
45
+ label: DISPUTE_KIND_LABEL[k],
46
+ gate: checkDisputeGate(k, order),
47
+ })), [order]);
48
+ const filableFrom = useMemo(() => disputeNotReachedFilableFrom(order), [order]);
49
+ /*
50
+ * 🛑 Keyed on `kind` so switching kind refetches. `PaginatedSelect` caches
51
+ * per `loadOptions` identity, so a stable callback would keep showing the
52
+ * previous kind's reasons — which is the one thing that must never happen
53
+ * here, because a mismatched reason is refused by the server.
54
+ */
55
+ const loadReasons = useCallback(async (query, page) => {
56
+ if (!kind)
57
+ return { items: [], hasMore: false };
58
+ const res = await fetch(`${reasonsEndpoint}?kind=${encodeURIComponent(kind)}`);
59
+ if (!res.ok) {
60
+ /*
61
+ * Surfaced, not swallowed into an empty list. An empty picker is
62
+ * indistinguishable from "there are no reasons", and the buyer would
63
+ * sit on a required field with nothing in it (Root Cause #59).
64
+ */
65
+ throw new Error("Could not load the list of reasons. Please try again.");
66
+ }
67
+ const json = (await res.json());
68
+ const rows = json.data?.items ?? [];
69
+ const q = query.trim().toLowerCase();
70
+ const filtered = q ? rows.filter((r) => r.label.toLowerCase().includes(q)) : rows;
71
+ return {
72
+ // `items`, not `options` — checked against `AsyncPage` rather than
73
+ // guessed; the wrong key yields an empty picker with no error.
74
+ items: filtered.map((r) => ({ value: r.id, label: r.label })),
75
+ // The route caps at 50 and the list is short by design; there is no
76
+ // second page to promise. `page` is unused for the same reason.
77
+ hasMore: false,
78
+ };
79
+ }, [kind, reasonsEndpoint]);
80
+ const chooseKind = (next) => {
81
+ setKind(next);
82
+ // Clearing is required: a reason from the previous kind would be refused.
83
+ setReasonId(null);
84
+ };
85
+ const submit = async () => {
86
+ if (!kind || !reasonId)
87
+ return;
88
+ setIsPending(true);
89
+ try {
90
+ await onSubmit({ kind, reasonId, detail: detail.trim() || undefined });
91
+ showToast("Thanks — we've recorded it and will look into it.", "success");
92
+ onDone?.();
93
+ }
94
+ catch (err) {
95
+ // The server's message names the next action; show it verbatim rather
96
+ // than replacing it with a generic failure.
97
+ showToast(normalizeError(err).message, "error");
98
+ }
99
+ finally {
100
+ setIsPending(false);
101
+ }
102
+ };
103
+ return (_jsx(Form, { onSubmit: (e) => e.preventDefault(), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "What's the problem?" }), kindStates.map((k) => (_jsxs(Div, { children: [_jsx(Button, { variant: kind === k.kind ? "primary" : "secondary", disabled: !k.gate.allowed, onClick: () => chooseKind(k.kind), children: k.label }), !k.gate.allowed ? (_jsx(Text, { size: "xs", color: "muted", children: k.gate.message })) : null] }, k.kind)))] }), kind ? (_jsxs(_Fragment, { children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Reason" }), _jsx(PaginatedSelect, { ariaLabel: "Reason for the dispute",
104
+ /*
105
+ * 🛑 `?? null`, not `?? ""`. PaginatedSelect renders its
106
+ * placeholder only on `value == null`; an empty string paints a
107
+ * BLANK trigger with no prompt, which reads as a broken control
108
+ * rather than an unmade choice.
109
+ */
110
+ value: reasonId ?? null, onChange: (v) => setReasonId(v), loadOptions: loadReasons, placeholder: "Pick the closest match\u2026" }), _jsx(FieldTextarea, { name: "detail", label: "What happened? (optional)", value: detail, onChange: setDetail, maxLength: 2000 }), kind === "not_reached" && filableFrom ? (_jsx(Alert, { variant: "info", children: `We'll chase the courier. If it turns up before we finish, let us know.` })) : null, _jsx(Button, { type: "submit", action: ACTIONS.SUPPORT["raise-dispute"], isLoading: isPending, disabled: !reasonId, onClick: submit, children: "Report this" })] })) : null] }) }));
111
+ }
@@ -28,11 +28,19 @@ import { FormShellContext, useFormShellState, FormErrorSummary, FieldTextarea, }
28
28
  import { SectionForm, useSectionFormNav } from "../../shell/index.js";
29
29
  import { z } from "zod";
30
30
  import { REFUND_COPY } from "../../../_internal/shared/features/orders/refund-copy.js";
31
+ import { resolvePublishedCopy, resolvePublishedValues, } from "../../../_internal/shared/content/published-values.js";
31
32
  import { RETURN_REASON_LABEL, selectableReturnReasons, } from "../../../_internal/shared/features/orders/return-reasons.js";
32
33
  import { formatCurrency } from "../../../utils/number.formatter.js";
33
34
  import { normalizeError } from "../../../errors/normalize.js";
34
35
  import { toUserMessage } from "../../../errors/error-display-map.js";
35
36
  const ACK_COUNT = REFUND_COPY.request.acknowledgments.length;
37
+ /*
38
+ * Phase 9 — code-derived values, resolved once. No settings argument: the only
39
+ * token in this copy is the refund timeline, which has no setting (see the 🛑
40
+ * on `refundBusinessDaysMin` in the registry), so passing settings would imply
41
+ * an editable number that does not exist.
42
+ */
43
+ const PUBLISHED_VALUES = resolvePublishedValues();
36
44
  export function RefundRequestView({ order, onSubmitRequest, isLoading = false, className = "", }) {
37
45
  /*
38
46
  * Does anything in this order carry the final-sale term? Read from the
@@ -83,7 +91,17 @@ export function RefundRequestView({ order, onSubmitRequest, isLoading = false, c
83
91
  fields: ["acknowledged"],
84
92
  render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "sm", children: [REFUND_COPY.request.acknowledgments.map((text, i) => (_jsx(Checkbox, { checked: values.acknowledged[i] ?? false, onChange: () => onChange({
85
93
  acknowledged: values.acknowledged.map((v, idx) => idx === i ? !v : v),
86
- }), label: _jsx(Text, { size: "sm", className: "leading-snug", children: text }), "aria-label": `Acknowledgment ${i + 1}`, "aria-invalid": errors.acknowledged ? "true" : undefined }, i))), errors.acknowledged && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.acknowledged }))] })),
94
+ }),
95
+ /*
96
+ * 🛑 Phase 9 — resolved, because this is an ACKNOWLEDGEMENT
97
+ * the buyer ticks. `REFUND_COPY`'s refund-timeline line now
98
+ * carries `{{refundBusinessDaysMin}}`/`Max` so it has one
99
+ * owner instead of being the third of three contradicting
100
+ * statements — and an unresolved token inside something a
101
+ * buyer is agreeing to is the strongest reason the unknown-key
102
+ * rule leaves `{{…}}` VISIBLE rather than blanking it.
103
+ */
104
+ label: _jsx(Text, { size: "sm", className: "leading-snug", children: resolvePublishedCopy(text, PUBLISHED_VALUES) }), "aria-label": `Acknowledgment ${i + 1}`, "aria-invalid": errors.acknowledged ? "true" : undefined }, i))), errors.acknowledged && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.acknowledged }))] })),
87
105
  },
88
106
  ], [order.total, order.currency, hasFinalSaleLine, allowedReasons]);
89
107
  const nav = useSectionFormNav(sections, draft);
@@ -6,6 +6,10 @@ export { OrderSiblingPayments } from "./OrderSiblingPayments";
6
6
  export type { OrderSiblingPaymentsProps } from "./OrderSiblingPayments";
7
7
  export { OrderPaymentSummary } from "./OrderPaymentSummary";
8
8
  export type { OrderPaymentSummaryProps } from "./OrderPaymentSummary";
9
+ export { OrderProvenanceCard } from "./OrderProvenanceCard";
10
+ export type { OrderProvenanceCardProps } from "./OrderProvenanceCard";
11
+ export { RaiseDisputeForm } from "./RaiseDisputeForm";
12
+ export type { RaiseDisputeFormProps } from "./RaiseDisputeForm";
9
13
  export { OrderStatusTimeline } from "./OrderStatusTimeline";
10
14
  export type { OrderStatusTimelineProps } from "./OrderStatusTimeline";
11
15
  export { RefundHistoryTable } from "./RefundHistoryTable";
@@ -3,6 +3,8 @@ export { OrderAddonBadges, } from "./OrderAddonBadges.js";
3
3
  export { OrderFilters, ORDER_FILTER_KEYS, ORDER_ADMIN_SORT_OPTIONS, ORDER_SELLER_SORT_OPTIONS, ORDER_USER_SORT_OPTIONS, getOrderFilterKeys, getOrderSortOptions, } from "./OrderFilters.js";
4
4
  export { OrderSiblingPayments } from "./OrderSiblingPayments.js";
5
5
  export { OrderPaymentSummary } from "./OrderPaymentSummary.js";
6
+ export { OrderProvenanceCard } from "./OrderProvenanceCard.js";
7
+ export { RaiseDisputeForm } from "./RaiseDisputeForm.js";
6
8
  export { OrderStatusTimeline } from "./OrderStatusTimeline.js";
7
9
  export { RefundHistoryTable } from "./RefundHistoryTable.js";
8
10
  export { RefundRequestView } from "./RefundRequestView.js";
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Order PROVENANCE tabs — the one definition, shared by the buyer's and the
3
+ * seller's order lists.
4
+ *
5
+ * ## Why this file exists
6
+ *
7
+ * The buyer's list had these tabs and the seller's did not. A seller could see
8
+ * every order and could not ask "show me the ones I accepted an offer on" or
9
+ * "show me the auction wins", which is exactly the split that changes how they
10
+ * are handled: an auction win has a forfeit deadline, an accepted offer has an
11
+ * agreed price that is not the listing price.
12
+ *
13
+ * Copying the array into the second view would have been the ninth
14
+ * hand-written enumeration of one union in this codebase (Root Cause #61), so
15
+ * the labels are a `Record<OrderType, string>` instead: a sixth lane is a
16
+ * COMPILE error here rather than a tab that silently never appears.
17
+ *
18
+ * 🛑 Three things about these ids, each of which has already cost a bug:
19
+ *
20
+ * 1. **Every id is EXACTLY a stored `OrderDocument.orderType` value.** Firestore
21
+ * `==` is byte-exact, so a tab id that is merely plausible returns zero rows
22
+ * forever with no error anywhere (Root Cause #33).
23
+ *
24
+ * 2. **`preorder` has NO HYPHEN and that is not a typo to tidy up.** `OrderType`
25
+ * spells it `preorder`; `ListingType` spells the same concept `pre-order`.
26
+ * Two unions, two spellings, and the tab must match the one actually stored.
27
+ *
28
+ * 3. **`standard` must be filtered IN MEMORY, never as a Firestore `==`.**
29
+ * Orders written before `orderType` existed carry no such field, and an
30
+ * equality excludes every document that lacks it — so the "Normal" tab would
31
+ * hide every legacy order while looking like it worked. `mergeOrderScopeFilter`
32
+ * and the server-side list both already honour this; see `ORDER_LANE_ALL` for
33
+ * the "no filter" sentinel.
34
+ */
35
+ import type { OrderType } from "../utils/order-splitter";
36
+ /**
37
+ * The "All" tab's id. An empty string rather than a word, so it can never
38
+ * collide with a real `orderType` value and so `state.orderType ? … : null`
39
+ * reads as "no filter" at every call site.
40
+ */
41
+ export declare const ORDER_LANE_ALL: "";
42
+ /**
43
+ * 🛑 A `Record<OrderType, string>`, deliberately. Omitting a lane does not
44
+ * compile, which is the whole point — the buyer's list shipped with four tabs
45
+ * for five lanes, so pre-order and prize-draw orders were reachable only by
46
+ * landing on "All" and scrolling.
47
+ */
48
+ export declare const ORDER_LANE_LABELS: Record<OrderType, string>;
49
+ /**
50
+ * The stored `orderType` values a lane filter may name, for route-side
51
+ * validation.
52
+ *
53
+ * Derived from `ORDER_LANE_LABELS`, so it cannot drift from the tabs. Both
54
+ * `/api/user/orders` and `/api/store/orders` had — or would have needed — their
55
+ * own hand-written copy of these five strings; this is the eleventh
56
+ * enumeration of one union in this codebase collapsed into the union itself
57
+ * (Root Cause #61).
58
+ *
59
+ * 🛑 Validating against it is load-bearing, not hygiene: an unrecognised value
60
+ * interpolated into a Sieve filter string is a caller-controlled field name
61
+ * reaching the query builder.
62
+ */
63
+ export declare const VALID_ORDER_TYPES: ReadonlySet<string>;
64
+ export interface OrderLaneTab {
65
+ id: string;
66
+ label: string;
67
+ }
68
+ /** `All` first, then every lane in `LANE_ORDER`. */
69
+ export declare const ORDER_LANE_TABS: readonly OrderLaneTab[];