@mohasinac/appkit 4.62.1 → 4.63.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.
Files changed (261) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +8 -29
  3. package/dist/_internal/client/features/layout/filterNavItems.d.ts +28 -0
  4. package/dist/_internal/client/features/layout/filterNavItems.js +35 -0
  5. package/dist/_internal/server/features/ads/booking.d.ts +118 -0
  6. package/dist/_internal/server/features/ads/booking.js +326 -0
  7. package/dist/_internal/server/features/checkout/actions.d.ts +51 -11
  8. package/dist/_internal/server/features/checkout/actions.js +335 -80
  9. package/dist/_internal/server/features/checkout/expand-order-items.d.ts +1 -2
  10. package/dist/_internal/server/features/checkout/expand-order-items.js +32 -0
  11. package/dist/_internal/server/features/checkout/freeze-parts.d.ts +50 -0
  12. package/dist/_internal/server/features/checkout/freeze-parts.js +94 -0
  13. package/dist/_internal/server/features/checkout/value-otp-gate.d.ts +55 -0
  14. package/dist/_internal/server/features/checkout/value-otp-gate.js +60 -0
  15. package/dist/_internal/server/features/dashboard/attention.d.ts +128 -0
  16. package/dist/_internal/server/features/dashboard/attention.js +183 -0
  17. package/dist/_internal/server/features/dashboard/data.d.ts +47 -0
  18. package/dist/_internal/server/features/dashboard/data.js +82 -0
  19. package/dist/_internal/server/features/inventory/reservations.d.ts +99 -0
  20. package/dist/_internal/server/features/inventory/reservations.js +273 -0
  21. package/dist/_internal/server/features/offers/destinations.d.ts +40 -0
  22. package/dist/_internal/server/features/offers/destinations.js +65 -0
  23. package/dist/_internal/server/features/orders/adapters.js +21 -0
  24. package/dist/_internal/server/features/orders/amend-delivery.d.ts +56 -0
  25. package/dist/_internal/server/features/orders/amend-delivery.js +124 -0
  26. package/dist/_internal/server/features/orders/balance.d.ts +89 -0
  27. package/dist/_internal/server/features/orders/balance.js +256 -0
  28. package/dist/_internal/server/features/pricing/actions.d.ts +58 -0
  29. package/dist/_internal/server/features/pricing/actions.js +144 -0
  30. package/dist/_internal/server/features/shipping/book-split.d.ts +86 -0
  31. package/dist/_internal/server/features/shipping/book-split.js +130 -0
  32. package/dist/_internal/server/features/shipping/book.d.ts +61 -0
  33. package/dist/_internal/server/features/shipping/book.js +176 -0
  34. package/dist/_internal/server/features/shipping/courier-block.js +26 -1
  35. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  36. package/dist/_internal/server/features/site-settings/adapters.js +14 -0
  37. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  38. package/dist/_internal/server/features/stores/adapters.js +14 -0
  39. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  40. package/dist/_internal/server/functions/scheduled.js +57 -1
  41. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +2 -0
  42. package/dist/_internal/server/jobs/core/jobRunners.js +45 -0
  43. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +4 -1
  44. package/dist/_internal/server/jobs/core/reservationExpiry.d.ts +17 -0
  45. package/dist/_internal/server/jobs/core/reservationExpiry.js +40 -0
  46. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -0
  47. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  48. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  49. package/dist/_internal/server/jobs/handlers/reservationExpiry.d.ts +2 -0
  50. package/dist/_internal/server/jobs/handlers/reservationExpiry.js +2 -0
  51. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  52. package/dist/_internal/shared/actions/action-registry.js +268 -0
  53. package/dist/_internal/shared/checkout/order-math.d.ts +31 -2
  54. package/dist/_internal/shared/checkout/order-math.js +22 -36
  55. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  56. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  57. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +18 -0
  58. package/dist/_internal/shared/checkout/rules/payment-gate.js +25 -0
  59. package/dist/_internal/shared/checkout/split.d.ts +0 -12
  60. package/dist/_internal/shared/checkout/split.js +22 -2
  61. package/dist/_internal/shared/content/disclaimers.js +7 -7
  62. package/dist/_internal/shared/content/published-values.d.ts +6 -1
  63. package/dist/_internal/shared/content/published-values.js +8 -0
  64. package/dist/_internal/shared/features/admin/integrations.d.ts +32 -2
  65. package/dist/_internal/shared/features/admin/integrations.js +19 -9
  66. package/dist/_internal/shared/features/dashboard/hub-sections.d.ts +72 -0
  67. package/dist/_internal/shared/features/dashboard/hub-sections.js +75 -0
  68. package/dist/_internal/shared/features/emi/choice.d.ts +98 -0
  69. package/dist/_internal/shared/features/emi/choice.js +69 -0
  70. package/dist/_internal/shared/features/emi/group.d.ts +84 -0
  71. package/dist/_internal/shared/features/emi/group.js +126 -0
  72. package/dist/_internal/shared/features/ledger/posting.d.ts +107 -0
  73. package/dist/_internal/shared/features/ledger/posting.js +165 -0
  74. package/dist/_internal/shared/features/ledger/reports.d.ts +76 -0
  75. package/dist/_internal/shared/features/ledger/reports.js +127 -0
  76. package/dist/_internal/shared/features/ledger/rules.d.ts +170 -0
  77. package/dist/_internal/shared/features/ledger/rules.js +221 -0
  78. package/dist/_internal/shared/features/offers/coordination.d.ts +122 -0
  79. package/dist/_internal/shared/features/offers/coordination.js +80 -0
  80. package/dist/_internal/shared/features/orders/delivery-snapshot.d.ts +114 -0
  81. package/dist/_internal/shared/features/orders/delivery-snapshot.js +116 -0
  82. package/dist/_internal/shared/features/payouts/pay-period.d.ts +91 -0
  83. package/dist/_internal/shared/features/payouts/pay-period.js +104 -0
  84. package/dist/_internal/shared/features/points/accepts.d.ts +1 -8
  85. package/dist/_internal/shared/features/points/accepts.js +12 -6
  86. package/dist/_internal/shared/features/pricing/rate-registry.d.ts +150 -0
  87. package/dist/_internal/shared/features/pricing/rate-registry.js +246 -0
  88. package/dist/_internal/shared/features/shipping/parcel.d.ts +123 -0
  89. package/dist/_internal/shared/features/shipping/parcel.js +139 -0
  90. package/dist/_internal/shared/features/site-settings/fees.d.ts +16 -8
  91. package/dist/_internal/shared/features/site-settings/fees.js +12 -8
  92. package/dist/_internal/shared/fees/explain.d.ts +81 -0
  93. package/dist/_internal/shared/fees/explain.js +101 -0
  94. package/dist/_internal/shared/history/index.d.ts +1 -1
  95. package/dist/_internal/shared/history/index.js +1 -1
  96. package/dist/_internal/shared/history/types.d.ts +7 -1
  97. package/dist/_internal/shared/history/types.js +25 -0
  98. package/dist/client.d.ts +11 -1
  99. package/dist/client.js +13 -0
  100. package/dist/constants/api-endpoints.d.ts +72 -0
  101. package/dist/constants/api-endpoints.js +41 -0
  102. package/dist/constants/field-names.d.ts +41 -0
  103. package/dist/constants/field-names.js +47 -0
  104. package/dist/features/account/components/UserOrdersView.js +28 -2
  105. package/dist/features/admin/components/AdminIntegrationsView.d.ts +55 -0
  106. package/dist/features/admin/components/AdminIntegrationsView.js +74 -0
  107. package/dist/features/admin/components/AdminOrderEditorView.d.ts +16 -1
  108. package/dist/features/admin/components/AdminOrderEditorView.js +72 -3
  109. package/dist/features/admin/components/AdminOrdersView.js +20 -1
  110. package/dist/features/admin/schemas/firestore.d.ts +32 -0
  111. package/dist/features/ads/schemas/booking-write.d.ts +120 -0
  112. package/dist/features/ads/schemas/booking-write.js +75 -0
  113. package/dist/features/ads/schemas/creative-form.d.ts +18 -0
  114. package/dist/features/ads/schemas/creative-form.js +51 -0
  115. package/dist/features/ads/schemas/firestore.d.ts +140 -0
  116. package/dist/features/ads/schemas/firestore.js +109 -0
  117. package/dist/features/auctions/repository/bid.repository.js +3 -1
  118. package/dist/features/audit-log/schemas/firestore.d.ts +110 -0
  119. package/dist/features/audit-log/schemas/firestore.js +74 -0
  120. package/dist/features/auth/actions/profile-actions.d.ts +69 -1
  121. package/dist/features/auth/actions/profile-actions.js +62 -1
  122. package/dist/features/auth/schemas/firestore.d.ts +16 -2
  123. package/dist/features/auth/schemas/firestore.js +2 -1
  124. package/dist/features/blog/actions/blog-actions.d.ts +6 -0
  125. package/dist/features/blog/actions/blog-actions.js +17 -2
  126. package/dist/features/blog/api/route.js +4 -1
  127. package/dist/features/blog/columns/index.js +5 -0
  128. package/dist/features/blog/components/BlogIndexPageView.js +2 -1
  129. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  130. package/dist/features/blog/repository/blog.repository.js +5 -0
  131. package/dist/features/blog/schemas/blog-form.d.ts +3 -0
  132. package/dist/features/blog/schemas/blog-form.js +1 -0
  133. package/dist/features/blog/schemas/firestore.d.ts +38 -1
  134. package/dist/features/blog/schemas/firestore.js +23 -0
  135. package/dist/features/blog/schemas/index.d.ts +6 -0
  136. package/dist/features/blog/schemas/index.js +2 -0
  137. package/dist/features/blog/types/index.d.ts +2 -0
  138. package/dist/features/cart/components/CartPriceBreakdown.d.ts +12 -1
  139. package/dist/features/cart/components/CartPriceBreakdown.js +24 -5
  140. package/dist/features/cart/repository/cart.repository.d.ts +14 -0
  141. package/dist/features/cart/repository/cart.repository.js +20 -0
  142. package/dist/features/cart/schemas/firestore.d.ts +24 -0
  143. package/dist/features/dashboard/components/DashboardHubView.d.ts +83 -0
  144. package/dist/features/dashboard/components/DashboardHubView.js +56 -0
  145. package/dist/features/dashboard/components/DashboardSectionsClient.d.ts +23 -0
  146. package/dist/features/dashboard/components/DashboardSectionsClient.js +66 -0
  147. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.d.ts +55 -0
  148. package/dist/features/dispute-reasons/components/AdminDisputeReasonsView.js +175 -0
  149. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +24 -0
  150. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +28 -0
  151. package/dist/features/dispute-reasons/schemas/firestore.d.ts +3 -5
  152. package/dist/features/events/components/EventSaleBanner.js +1 -1
  153. package/dist/features/faq/components/FAQPageContent.js +2 -2
  154. package/dist/features/gateway-events/schemas/index.d.ts +2 -2
  155. package/dist/features/homepage/schemas/index.d.ts +8 -8
  156. package/dist/features/inventory/schemas/stock-reservation.d.ts +114 -0
  157. package/dist/features/inventory/schemas/stock-reservation.js +103 -0
  158. package/dist/features/layout/AppLayoutShell.d.ts +9 -1
  159. package/dist/features/layout/AppLayoutShell.js +12 -4
  160. package/dist/features/layout/BottomActions.js +2 -1
  161. package/dist/features/layout/BottomNavbar.d.ts +10 -2
  162. package/dist/features/layout/SideActionDock.js +52 -2
  163. package/dist/features/layout/TitleBarLayout.js +13 -4
  164. package/dist/features/layout/TitleBarLayout.style.css +59 -0
  165. package/dist/features/layout/useCtaReveal.d.ts +16 -1
  166. package/dist/features/layout/useCtaReveal.js +17 -26
  167. package/dist/features/ledger/repository/journal.repository.d.ts +110 -0
  168. package/dist/features/ledger/repository/journal.repository.js +312 -0
  169. package/dist/features/ledger/schemas/expenses.d.ts +87 -0
  170. package/dist/features/ledger/schemas/expenses.js +27 -0
  171. package/dist/features/ledger/schemas/firestore.d.ts +183 -0
  172. package/dist/features/ledger/schemas/firestore.js +69 -0
  173. package/dist/features/orders/components/BalancePaymentPanel.d.ts +12 -0
  174. package/dist/features/orders/components/BalancePaymentPanel.js +94 -0
  175. package/dist/features/orders/components/RaiseDisputeForm.js +19 -11
  176. package/dist/features/orders/repository/orders.repository.d.ts +28 -1
  177. package/dist/features/orders/repository/orders.repository.js +60 -6
  178. package/dist/features/orders/schemas/balance.d.ts +40 -0
  179. package/dist/features/orders/schemas/balance.js +47 -0
  180. package/dist/features/orders/schemas/firestore.d.ts +202 -0
  181. package/dist/features/orders/schemas/firestore.js +43 -0
  182. package/dist/features/orders/schemas/index.d.ts +2 -2
  183. package/dist/features/orders/types/index.d.ts +21 -0
  184. package/dist/features/payments/repository/payout.repository.js +3 -2
  185. package/dist/features/payments/schemas/firestore.d.ts +92 -1
  186. package/dist/features/payments/schemas/firestore.js +23 -0
  187. package/dist/features/pricing/components/AdminPricingView.d.ts +13 -0
  188. package/dist/features/pricing/components/AdminPricingView.js +157 -0
  189. package/dist/features/products/schemas/firestore.d.ts +26 -1
  190. package/dist/features/products/schemas/firestore.js +2 -0
  191. package/dist/features/products/schemas/index.d.ts +2 -2
  192. package/dist/features/reviews/repository/reviews.repository.d.ts +14 -0
  193. package/dist/features/reviews/repository/reviews.repository.js +16 -0
  194. package/dist/features/scams/repository/scammer.repository.js +2 -1
  195. package/dist/features/scams/schemas/firestore.d.ts +2 -1
  196. package/dist/features/scams/schemas/firestore.js +2 -1
  197. package/dist/features/seller/actions/offer-actions.js +56 -0
  198. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  199. package/dist/features/seller/actions/seller-actions.js +4 -0
  200. package/dist/features/seller/components/DispatchForm.d.ts +11 -0
  201. package/dist/features/seller/components/DispatchForm.js +190 -0
  202. package/dist/features/seller/components/SellerOrdersView.js +94 -2
  203. package/dist/features/seller/components/index.d.ts +1 -0
  204. package/dist/features/seller/components/index.js +1 -0
  205. package/dist/features/seller/repository/offer.repository.js +4 -3
  206. package/dist/features/seller/schemas/firestore.d.ts +23 -2
  207. package/dist/features/seller/schemas/firestore.js +3 -2
  208. package/dist/features/seller/schemas/index.d.ts +4 -4
  209. package/dist/features/seller/schemas/order-forms.d.ts +61 -4
  210. package/dist/features/seller/schemas/order-forms.js +41 -8
  211. package/dist/features/shipments/schemas/firestore.d.ts +25 -0
  212. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +39 -0
  213. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +55 -0
  214. package/dist/features/shipping-boxes/schemas/firestore.d.ts +2 -4
  215. package/dist/features/stores/repository/store.repository.d.ts +18 -0
  216. package/dist/features/stores/repository/store.repository.js +27 -1
  217. package/dist/features/stores/schemas/firestore.d.ts +41 -0
  218. package/dist/features/stores/schemas/firestore.js +20 -0
  219. package/dist/features/stores/schemas/index.d.ts +6 -6
  220. package/dist/features/support/repository/support.repository.js +72 -6
  221. package/dist/features/support/schemas/firestore.d.ts +17 -0
  222. package/dist/features/support/schemas/index.d.ts +2 -2
  223. package/dist/index.d.ts +5 -0
  224. package/dist/index.js +19 -0
  225. package/dist/next/routing/route-map.d.ts +92 -0
  226. package/dist/next/routing/route-map.js +53 -0
  227. package/dist/react/contexts/SessionContext.d.ts +17 -1
  228. package/dist/schemas/registry.d.ts +22 -16
  229. package/dist/security/pii-schemas.d.ts +1 -1
  230. package/dist/security/pii-schemas.js +45 -0
  231. package/dist/server.d.ts +27 -1
  232. package/dist/server.js +144 -0
  233. package/dist/styles.css +349 -1
  234. package/dist/tailwind-utilities.css +1 -1
  235. package/dist/ui/components/Button.d.ts +10 -1
  236. package/dist/ui/components/Button.js +25 -3
  237. package/dist/ui/components/Chart.d.ts +63 -0
  238. package/dist/ui/components/Chart.js +169 -0
  239. package/dist/ui/components/Chart.style.css +214 -0
  240. package/dist/ui/components/Disclaimer.js +10 -1
  241. package/dist/ui/components/OverflowRow.js +54 -2
  242. package/dist/ui/components/OverflowRow.style.css +38 -0
  243. package/dist/ui/components/RichTextEditor.js +99 -8
  244. package/dist/ui/components/chart-spec.d.ts +68 -0
  245. package/dist/ui/components/chart-spec.js +140 -0
  246. package/dist/ui/components/index.style.css +1 -0
  247. package/dist/ui/components/surface-tokens.d.ts +1 -1
  248. package/dist/ui/components/surface-tokens.js +9 -0
  249. package/dist/ui/components/toolbar-collapse.d.ts +13 -0
  250. package/dist/ui/components/toolbar-collapse.js +10 -1
  251. package/dist/ui/index.d.ts +6 -0
  252. package/dist/ui/index.js +3 -0
  253. package/dist/ui/rich-text/RichText.d.ts +10 -1
  254. package/dist/ui/rich-text/RichText.js +33 -2
  255. package/dist/ui/rich-text/RichText.style.css +35 -0
  256. package/dist/ui/rich-text/RichTextRenderer.d.ts +30 -1
  257. package/dist/ui/rich-text/RichTextRenderer.js +34 -2
  258. package/dist/ui/rich-text/chart-blocks.d.ts +62 -0
  259. package/dist/ui/rich-text/chart-blocks.js +87 -0
  260. package/package.json +1 -1
  261. package/scripts/seed-cli.mjs +3 -0
@@ -48,6 +48,49 @@ export const RefundStatusValues = {
48
48
  COMPLETED: "completed",
49
49
  REJECTED: "rejected",
50
50
  };
51
+ /** Firestore storage shape for an order line item â€" distinct from the API display model OrderItem */
52
+ /**
53
+ * The PII inside one amendment.
54
+ *
55
+ * 🛑 BARE keys, and `ORDER_PII_FIELDS` uses the `deliveryAmendments[].…`
56
+ * form for the same five fields. Two notations, two scopes, both correct:
57
+ * the `[]` form is for the whole ORDER document, where the hook has to walk
58
+ * into the array; these bare names are for ONE amendment object, where they
59
+ * are genuinely top-level.
60
+ *
61
+ * 🛑 This list was first justified as "`encryptPiiFields` CANNOT cover an
62
+ * array" — CLAUDE.md's wording, and FALSE. `encryptInArray` has always
63
+ * handled `a[].b`, in both directions. The real rule is that the encryptor
64
+ * does not infer: a field is covered only where it is NAMED, in the notation
65
+ * matching the object the list is applied to.
66
+ */
67
+ export const AMENDMENT_PII_FIELDS = [
68
+ "fullName",
69
+ "phone",
70
+ "addressLine1",
71
+ "addressLine2",
72
+ /* Encrypted on the snapshot as `deliveryLandmark`; the two must agree or the
73
+ * same datum is protected before a correction and exposed after it. */
74
+ "landmark",
75
+ ];
76
+ /**
77
+ * How much seller prose a single order line carries.
78
+ *
79
+ * 🛑 A Firestore document caps at 1 MiB and `items[]` is inline on the order.
80
+ * 2,000 characters × 20 lines is ~40 KB of description — comfortable. The
81
+ * same field unbounded is a rejected write at checkout, and the checkout is
82
+ * the one operation in the system that must never fail for a reason the buyer
83
+ * cannot act on.
84
+ */
85
+ export const ORDER_ITEM_DESCRIPTION_MAX = 2000;
86
+ /**
87
+ * How many spec rows one order line keeps.
88
+ *
89
+ * Capped by COUNT rather than characters: a spec table is rows, and half a
90
+ * row is worse than a missing one — "Weight: 4" with the unit cut off reads
91
+ * as a fact rather than as truncation.
92
+ */
93
+ export const ORDER_ITEM_SPECIFICATIONS_MAX = 30;
51
94
  export const ORDER_COLLECTION = "orders";
52
95
  /**
53
96
  * At-most-once gate for refunds. Document id = `sha256(idempotencyKey)`,
@@ -684,6 +684,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
684
684
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
685
685
  }[] | undefined;
686
686
  bundleId?: string | undefined;
687
+ orderType?: string | undefined;
687
688
  prizeWon?: {
688
689
  title: string;
689
690
  images: string[];
@@ -700,7 +701,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
700
701
  bin: string;
701
702
  } | undefined;
702
703
  offerId?: string | undefined;
703
- orderType?: string | undefined;
704
704
  isNonRefundable?: boolean | undefined;
705
705
  imageUrls?: string[] | undefined;
706
706
  refundFeeDeducted?: number | undefined;
@@ -843,6 +843,7 @@ export declare const orderFirestoreSchema: z.ZodObject<{
843
843
  prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
844
844
  }[] | undefined;
845
845
  bundleId?: string | undefined;
846
+ orderType?: string | undefined;
846
847
  prizeWon?: {
847
848
  title: string;
848
849
  images: string[];
@@ -859,7 +860,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
859
860
  bin: string;
860
861
  } | undefined;
861
862
  offerId?: string | undefined;
862
- orderType?: string | undefined;
863
863
  isNonRefundable?: boolean | undefined;
864
864
  imageUrls?: string[] | undefined;
865
865
  refundFeeDeducted?: number | undefined;
@@ -287,6 +287,27 @@ export interface Order {
287
287
  timelineTruncated?: number;
288
288
  /** How the buyer acquired the right to buy. Written once, at creation. */
289
289
  sourceContext?: OrderSourceContext;
290
+ /**
291
+ * D55 — the pre-order balance, once the seller has OPENED it for collection.
292
+ *
293
+ * 🛑 Absent until then, and that is the difference between "a balance
294
+ * exists" (`sourceContext.balanceDue > 0`, true from the moment the order
295
+ * was placed) and "a balance is payable". The buyer's panel gates on this
296
+ * field, so forgetting it in `orderDocumentToOrder` would render the
297
+ * "nothing to pay" branch on every order forever — Root Cause #57, which
298
+ * is what made the whole manual-payment flow dead for weeks.
299
+ */
300
+ balance?: {
301
+ amount: number;
302
+ method: "manual";
303
+ status: "awaiting" | "submitted" | "paid" | "rejected";
304
+ dueBy?: string;
305
+ proofUrl?: string;
306
+ transactionId?: string;
307
+ submittedAt?: string;
308
+ reviewedAt?: string;
309
+ reviewNote?: string;
310
+ };
290
311
  physicalLocation?: {
291
312
  zone: string;
292
313
  shelf: string;
@@ -220,8 +220,9 @@ export class PayoutRepository extends BaseRepository {
220
220
  trigger: ctx?.trigger ?? defaultTrigger,
221
221
  reason: ctx?.reason,
222
222
  note: ctx?.note,
223
- // `sellerEmail` / `upiId` — `encryptPiiFields` never descends into
224
- // arrays, so either reaching `changes` would persist in PLAINTEXT.
223
+ // `sellerEmail` / `upiId` — `encryptPiiFields` covers only NAMED paths,
224
+ // and nothing declares them under `statusHistory[].changes`, so either
225
+ // one reaching `changes` would persist in PLAINTEXT.
225
226
  piiFields: PAYOUT_PII_FIELDS,
226
227
  });
227
228
  return withEntry ?? patch;
@@ -5,6 +5,7 @@ import { type GeneratePayoutIdInput } from "../../../utils/id-generators";
5
5
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
6
6
  import type { StatusChangeEntry } from "../../../_internal/shared/history/types";
7
7
  import type { Encrypted, Plaintext } from "../../../security/encrypted";
8
+ import type { PayCadence } from "../../../_internal/shared/features/payouts/pay-period";
8
9
  export interface PayoutBankAccount {
9
10
  accountHolderName: string;
10
11
  accountNumberMasked: string;
@@ -45,7 +46,29 @@ export type PayoutPaymentMethod = "bank_transfer" | "upi";
45
46
  * asterisks". The distinction is which named method you called.
46
47
  */
47
48
  export type PayoutPlain = Plaintext<PayoutDocument>;
48
- export interface PayoutDocument extends BaseDocument {
49
+ /**
50
+ * 🛑 **A seller payout settles a LIABILITY. An employee payout is an EXPENSE.**
51
+ *
52
+ * One undiscriminated shape misstates both statements at once — wages vanish
53
+ * from the P&L and reappear as settled seller liabilities — so `payeeType` is
54
+ * a discriminated union rather than an optional field.
55
+ *
56
+ * ## Why `PayoutDocument` still names the STORE variant
57
+ *
58
+ * Every one of the ~30 existing readers means a seller payout, and every one
59
+ * of them is correct. Renaming the type they import would turn a safety change
60
+ * into a mechanical sweep, and the sweep is where the real mistakes go in.
61
+ * `PayoutDocument = StorePayoutDocument` keeps them compiling and correct;
62
+ * anything that must handle BOTH takes `AnyPayoutDocument` and is forced by
63
+ * `tsc` to narrow.
64
+ *
65
+ * 🛑 `payeeType` is REQUIRED here, not optional-with-a-default. That breaks
66
+ * the five sites that CONSTRUCT a payout, which is the entire point — a row
67
+ * written without it is a row the books cannot classify, and an optional
68
+ * discriminant defers that discovery to a reader rather than a writer.
69
+ */
70
+ export interface StorePayoutDocument extends BaseDocument {
71
+ payeeType: "store";
49
72
  storeId: string;
50
73
  sellerName: string;
51
74
  sellerEmail: Encrypted<string>;
@@ -92,6 +115,74 @@ export interface PayoutDocument extends BaseDocument {
92
115
  statusHistory?: StatusChangeEntry[];
93
116
  statusHistoryTruncated?: number;
94
117
  }
118
+ /**
119
+ * Wages for a period, earned by resolving support tickets.
120
+ *
121
+ * ## 🛑 `platformFee` is ABSENT, not zero
122
+ *
123
+ * A `0` would read as "we took no commission on these wages", which is a
124
+ * statement about a transaction that did not happen. Commission is not a
125
+ * concept between a platform and its own staff, and the books must be able to
126
+ * tell "not applicable" from "nil" — a nil commission is a seller payout where
127
+ * the rate happened to resolve to zero, and those post differently.
128
+ *
129
+ * The same argument removes `storeId`, `sellerName`, `sellerEmail`,
130
+ * `grossAmount`, `platformFeeRate`, `gatewayFee*`, `orderIds` and
131
+ * `refundDeductions`. A field that cannot apply is omitted so `tsc` refuses
132
+ * the read, rather than present-and-empty so a reader quietly gets a falsy
133
+ * value.
134
+ */
135
+ export interface EmployeePayoutDocument extends BaseDocument {
136
+ payeeType: "employee";
137
+ employeeUid: string;
138
+ employeeName: string;
139
+ /** PII, same treatment as a seller's. */
140
+ employeeEmail: Encrypted<string>;
141
+ amount: number;
142
+ currency: string;
143
+ status: PayoutStatus;
144
+ paymentMethod: PayoutPaymentMethod;
145
+ bankAccount?: PayoutBankAccount;
146
+ upiId?: Encrypted<string>;
147
+ /**
148
+ * The tickets this payslip is for.
149
+ *
150
+ * 🛑 Tickets **still resolved at period end**, not tickets closed during it.
151
+ * Paying per ticket closed rewards closing rather than resolving, so a
152
+ * reopened ticket does not count — see `resolvedTicketsForPeriod`.
153
+ */
154
+ ticketIds: string[];
155
+ /** Resolved by `payPeriodFor`, never computed at a call site. */
156
+ periodStart: Date;
157
+ periodEnd: Date;
158
+ payCadence: PayCadence;
159
+ notes?: string;
160
+ adminNote?: string;
161
+ requestedAt: Date;
162
+ processedAt?: Date;
163
+ transactionId?: string;
164
+ statusHistory?: StatusChangeEntry[];
165
+ statusHistoryTruncated?: number;
166
+ }
167
+ /**
168
+ * What ~30 existing readers mean, and what they should keep meaning.
169
+ *
170
+ * Deliberately an alias rather than a rename: see the header on
171
+ * `StorePayoutDocument`.
172
+ */
173
+ export type PayoutDocument = StorePayoutDocument;
174
+ /** Use this ONLY where both kinds genuinely arrive together. */
175
+ export type AnyPayoutDocument = StorePayoutDocument | EmployeePayoutDocument;
176
+ /**
177
+ * 🛑 Narrow on the DISCRIMINANT, never on the presence of `storeId`.
178
+ *
179
+ * A `"storeId" in payout` test reads as equivalent and is not: it answers a
180
+ * question about the object in hand rather than about what the row IS, so a
181
+ * partially-populated or hand-built object answers it wrongly, silently, and
182
+ * in the direction that posts wages as a settled seller liability.
183
+ */
184
+ export declare function isStorePayout(p: AnyPayoutDocument): p is StorePayoutDocument;
185
+ export declare function isEmployeePayout(p: AnyPayoutDocument): p is EmployeePayoutDocument;
95
186
  /**
96
187
  * The fields whose changes earn a timeline entry.
97
188
  *
@@ -8,6 +8,20 @@ export const PayoutStatusValues = {
8
8
  PAID: "paid",
9
9
  FAILED: "failed",
10
10
  };
11
+ /**
12
+ * 🛑 Narrow on the DISCRIMINANT, never on the presence of `storeId`.
13
+ *
14
+ * A `"storeId" in payout` test reads as equivalent and is not: it answers a
15
+ * question about the object in hand rather than about what the row IS, so a
16
+ * partially-populated or hand-built object answers it wrongly, silently, and
17
+ * in the direction that posts wages as a settled seller liability.
18
+ */
19
+ export function isStorePayout(p) {
20
+ return p.payeeType === "store";
21
+ }
22
+ export function isEmployeePayout(p) {
23
+ return p.payeeType === "employee";
24
+ }
11
25
  /**
12
26
  * The fields whose changes earn a timeline entry.
13
27
  *
@@ -36,6 +50,15 @@ export const PAYOUT_INDEXED_FIELDS = [
36
50
  ];
37
51
  export const DEFAULT_PLATFORM_FEE_RATE = 0.05;
38
52
  export const DEFAULT_PAYOUT_DATA = {
53
+ /*
54
+ * 🛑 Here as a BACKSTOP, not as the reason the field is set. Every caller
55
+ * passes `payeeType` explicitly and must keep doing so — a default that is
56
+ * the only writer makes the discriminant optional in practice, which is the
57
+ * exact failure the required field exists to prevent. This catches a row
58
+ * built by some path nobody enumerated and lands it on the correct side of
59
+ * the books rather than on neither.
60
+ */
61
+ payeeType: "store",
39
62
  status: "pending",
40
63
  platformFeeRate: DEFAULT_PLATFORM_FEE_RATE,
41
64
  orderIds: [],
@@ -0,0 +1,13 @@
1
+ import React from "react";
2
+ export interface AdminPricingViewProps {
3
+ /**
4
+ * The rate endpoint, supplied by the consumer.
5
+ *
6
+ * 🛑 REQUIRED, with no default. appkit cannot import `API_ROUTES` (that is
7
+ * consumer-side), so a default would be a path literal living in the
8
+ * library — a second place the route is written, and the one nobody updates
9
+ * when it moves. `audit-hardcoded-api-routes` blocks it, correctly.
10
+ */
11
+ endpoint: string;
12
+ }
13
+ export declare function AdminPricingView({ endpoint }: AdminPricingViewProps): React.JSX.Element;
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useState } from "react";
4
+ import { Badge, Button, Heading, Row, Stack, Text } from "../../../ui/index.js";
5
+ import { Modal } from "../../../ui/components/Modal.js";
6
+ import { useToast } from "../../../ui/components/Toast.js";
7
+ import { Form } from "../../../ui/components/Form.js";
8
+ import { FieldInput } from "../../../ui/forms/FieldInput.js";
9
+ import { FieldTextarea } from "../../../ui/forms/FieldTextarea.js";
10
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
11
+ import { apiClient } from "../../../http/ApiClient.js";
12
+ import { normalizeError } from "../../../errors/normalize.js";
13
+ import { toUserMessage } from "../../../errors/error-display-map.js";
14
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
15
+ import { RATE_TIER_ORDER, } from "../../../_internal/shared/features/pricing/rate-registry.js";
16
+ const TIER_LABEL = {
17
+ statutory: "Statutory",
18
+ policy: "Policy ceiling",
19
+ operational: "Operational",
20
+ };
21
+ const TIER_BLURB = {
22
+ statutory: "Set by law, not by us. Sourced per HSN code in Tax codes — they deliberately do not appear below.",
23
+ policy: "A ceiling the owner decides. Administrator only, and a reason is required.",
24
+ operational: "Tuned, audited and reversible by back-office staff.",
25
+ };
26
+ const TIER_TONE = {
27
+ statutory: "warning",
28
+ policy: "info",
29
+ operational: "default",
30
+ };
31
+ /**
32
+ * 🛑 Format by the descriptor's UNIT, never by guessing from the value.
33
+ *
34
+ * `2` is 2% as a commission, ₹2 as a floor and 2 days as a hold, and the three
35
+ * are side by side in one table. A bare number would make the COD ceiling and
36
+ * the COD percentage read as the same kind of thing.
37
+ */
38
+ function formatRate(value, unit) {
39
+ if (value === null)
40
+ return "—";
41
+ switch (unit) {
42
+ case "percent":
43
+ return `${value}%`;
44
+ case "rupees":
45
+ return `₹${value.toLocaleString("en-IN")}`;
46
+ case "days":
47
+ return value === 1 ? "1 day" : `${value} days`;
48
+ case "minutes":
49
+ return value === 1 ? "1 minute" : `${value} minutes`;
50
+ case "count":
51
+ return String(value);
52
+ }
53
+ }
54
+ function formatWhen(iso) {
55
+ const d = new Date(iso);
56
+ return Number.isNaN(d.getTime()) ? "—" : d.toLocaleDateString("en-IN", { dateStyle: "medium" });
57
+ }
58
+ export function AdminPricingView({ endpoint }) {
59
+ const { showToast } = useToast();
60
+ const [rates, setRates] = useState(null);
61
+ const [loadError, setLoadError] = useState(null);
62
+ const [editing, setEditing] = useState(null);
63
+ const [draftValue, setDraftValue] = useState("");
64
+ const [draftReason, setDraftReason] = useState("");
65
+ const [saving, setSaving] = useState(false);
66
+ const load = useCallback(async () => {
67
+ setLoadError(null);
68
+ try {
69
+ const payload = await apiClient.get(endpoint);
70
+ setRates(payload?.rates ?? []);
71
+ }
72
+ catch (err) {
73
+ /*
74
+ * Surfaced, never swallowed into an empty table. An empty rate list and
75
+ * a failed read look identical, and the first reads as "we charge
76
+ * nothing" (Root Cause #59/#72 — undefined must never render as zero).
77
+ *
78
+ * 🛑 A FIXED string, never `normalized.message`. The server's own text
79
+ * is how a Node `MODULE_NOT_FOUND` with a require stack ended up inside
80
+ * "Place your bid" (Root Cause #86) — and `mapToHttpError` scrubs 5xx
81
+ * messages in production anyway, so the raw value is either useless or
82
+ * dangerous. `normalizeError` still runs: it is what keeps `unknown`
83
+ * out of the catch body.
84
+ */
85
+ void normalizeError(err);
86
+ setLoadError("Could not load the rate table. Try again.");
87
+ setRates([]);
88
+ }
89
+ }, [endpoint]);
90
+ useEffect(() => {
91
+ void load();
92
+ }, [load]);
93
+ const byTier = useMemo(() => {
94
+ const groups = new Map();
95
+ for (const tier of RATE_TIER_ORDER)
96
+ groups.set(tier, []);
97
+ for (const rate of rates ?? [])
98
+ groups.get(rate.tier)?.push(rate);
99
+ return groups;
100
+ }, [rates]);
101
+ const openEditor = useCallback((rate) => {
102
+ setEditing(rate);
103
+ setDraftValue(rate.value === null ? "" : String(rate.value));
104
+ setDraftReason("");
105
+ }, []);
106
+ const submit = useCallback(async (setFieldError) => {
107
+ if (!editing)
108
+ return;
109
+ const parsed = Number(draftValue);
110
+ if (!Number.isFinite(parsed)) {
111
+ setFieldError("newValue", "Enter a number.");
112
+ return;
113
+ }
114
+ setSaving(true);
115
+ try {
116
+ await apiClient.post(endpoint, {
117
+ path: editing.path,
118
+ newValue: parsed,
119
+ ...(draftReason.trim() ? { reason: draftReason.trim() } : {}),
120
+ });
121
+ showToast(`${editing.label} updated.`, "success");
122
+ setEditing(null);
123
+ await load();
124
+ }
125
+ catch (err) {
126
+ void normalizeError(err);
127
+ /*
128
+ * 🛑 `toUserMessage`, never the server's own message — and no
129
+ * `?? normalized.message` fallback either. That fallback IS the bug:
130
+ * `toUserMessage` already terminates at `errors.codes.UNKNOWN`, so
131
+ * adding a raw-text branch for the unmapped case reintroduces exactly
132
+ * what Root Cause #86 describes — a require stack rendered inside a
133
+ * modal. The first draft of this file had it.
134
+ */
135
+ const code = err?.code;
136
+ setFieldError("newValue", toUserMessage(code ?? "UNKNOWN"));
137
+ }
138
+ finally {
139
+ setSaving(false);
140
+ }
141
+ }, [draftReason, draftValue, editing, endpoint, load, showToast]);
142
+ return (_jsxs(Stack, { gap: "loose", children: [_jsxs(Stack, { gap: "dense", children: [_jsx(Heading, { level: 1, children: "Pricing" }), _jsx(Text, { color: "muted", children: "Every rate the platform charges, what kind of number it is, and when it last moved. Changes take effect from the moment they are saved and never restate past orders." })] }), loadError ? (_jsxs(Stack, { surface: "danger-surface", padding: "md", rounded: "lg", gap: "dense", children: [_jsx(Text, { weight: "semibold", children: "The rate table could not be loaded." }), _jsx(Text, { size: "sm", children: loadError }), _jsx(Row, { children: _jsx(Button, { variant: "secondary", onClick: () => void load(), children: "Try again" }) })] })) : null, RATE_TIER_ORDER.map((tier) => {
143
+ const rows = byTier.get(tier) ?? [];
144
+ /*
145
+ * The statutory section renders with NO rows and that is the point —
146
+ * it tells a reader the tier exists and where it is set. Hiding an
147
+ * empty section would make statutory rates look like something this
148
+ * platform does not have.
149
+ */
150
+ if (rows.length === 0 && tier !== "statutory")
151
+ return null;
152
+ return (_jsxs(Stack, { gap: "comfortable", children: [_jsxs(Stack, { gap: "dense", children: [_jsxs(Row, { gap: "dense", align: "center", children: [_jsx(Heading, { level: 2, children: TIER_LABEL[tier] }), _jsx(Badge, { variant: TIER_TONE[tier], children: rows.length || "set elsewhere" })] }), _jsx(Text, { size: "sm", color: "muted", children: TIER_BLURB[tier] })] }), rows.map((rate) => (_jsxs(Row, { surface: "card", padding: "card", rounded: "lg", gap: "comfortable", align: "start", justify: "between", children: [_jsxs(Stack, { gap: "dense", children: [_jsxs(Row, { gap: "dense", align: "center", children: [_jsx(Text, { weight: "semibold", children: rate.label }), rate.sellerOnly ? _jsx(Badge, { variant: "default", children: "Seller-facing" }) : null] }), _jsx(Text, { size: "sm", color: "muted", children: rate.description }), rate.lastChange ? (_jsxs(Text, { size: "xs", color: "muted", children: [formatRate(rate.lastChange.previousValue, rate.unit), " \u2192", " ", formatRate(rate.lastChange.newValue, rate.unit), " on", " ", formatWhen(rate.lastChange.effectiveFrom), rate.lastChange.changedByName ? ` by ${rate.lastChange.changedByName}` : "", rate.lastChange.reason ? ` — ${rate.lastChange.reason}` : ""] })) : (_jsx(Text, { size: "xs", color: "muted", children: "No recorded change \u2014 this is the value it was configured with." }))] }), _jsxs(Row, { gap: "comfortable", align: "center", children: [_jsx(Text, { weight: "bold", numeric: true, children: formatRate(rate.value, rate.unit) }), _jsx(Button, { variant: "secondary", size: "sm", disabled: !rate.editable, onClick: () => openEditor(rate), children: "Change" })] })] }, rate.path)))] }, tier));
153
+ }), _jsx(Modal, { isOpen: editing !== null, onClose: () => setEditing(null), title: editing ? `Change ${editing.label}` : "Change rate", children: editing ? (_jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "comfortable", children: [_jsx(FormErrorSummary, {}), _jsx(Text, { size: "sm", color: "muted", children: editing.description }), _jsx(FieldInput, { name: "newValue", label: `New value (${editing.unit})`, required: true, value: draftValue, onChange: setDraftValue }), _jsx(FieldTextarea, { name: "reason", label: editing.tier === "policy" ? "Reason (required)" : "Reason (optional)", value: draftReason, onChange: setDraftReason }), _jsx(Text, { size: "xs", color: "muted", children: "This takes effect now. Orders, invoices and payouts already recorded keep the rate they were charged at \u2014 nothing is restated." }), _jsxs(Row, { gap: "dense", justify: "end", children: [_jsx(Button, { variant: "ghost", onClick: () => setEditing(null), children: "Cancel" }), _jsx(Button, { action: ACTIONS.ADMIN["update-pricing"], isLoading: saving, onClick: () => {
154
+ clearErrors();
155
+ void submit(setFieldError);
156
+ } })] })] })) })) : null })] }));
157
+ }
@@ -603,6 +603,31 @@ export interface ProductDocument extends BaseDocument {
603
603
  howToUse?: string[];
604
604
  allowOffers?: boolean;
605
605
  minOfferPercent?: number;
606
+ /**
607
+ * The seller permits a COORDINATED purchase on this listing.
608
+ *
609
+ * ## 🛑 A coordinated purchase is a SPECIALISED OFFER, not a listing type
610
+ *
611
+ * Owner, 2026-10-22: *"it is not a variant, it is similar to an offer, which
612
+ * the seller enables on the listing, then the customer can coordinate on it,
613
+ * it is a specialized offer."*
614
+ *
615
+ * That framing REMOVES work rather than adding it, and the removals are the
616
+ * part worth knowing — otherwise somebody rebuilds them:
617
+ *
618
+ * | Looks like it needs | Actually |
619
+ * |---|---|
620
+ * | a 7th `OrderSourceContext` variant | **nothing** — it is `offer-accepted`, and the provenance card already renders that branch |
621
+ * | an `orderType` tab | **nothing** — the Offer-wins tab already covers it |
622
+ * | a commit/settlement path | the offer lifecycle, with its chain fields, `checkoutDeadline` and lapse sweep |
623
+ * | a new money term | **none** — `unitPriceFor` already honours `lockedPrice`, so tax, payout base, refund ceiling and the invoice all follow |
624
+ *
625
+ * So this flag is deliberately shaped exactly like `allowOffers` above: same
626
+ * optional boolean, same seller control, same projection list. A listing
627
+ * with `allowOffers: false` and this `true` is coherent — the seller takes
628
+ * no solo offers but will consider a group — and nothing needs to forbid it.
629
+ */
630
+ allowCoordination?: boolean;
606
631
  customFields?: CustomField[];
607
632
  customSections?: CustomSection[];
608
633
  sublistingCategoryId?: string;
@@ -837,7 +862,7 @@ export declare const ProductStatusValues: {
837
862
  export declare const PRODUCT_COLLECTION: "products";
838
863
  export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "categoryIds", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "searchTxt", "createdAt", "barcodeId", "physicalLocation.zone"];
839
864
  export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
840
- export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "createdAt"];
865
+ export declare const PRODUCT_PUBLIC_FIELDS: readonly ["allowOffers", "minOfferPercent", "allowCoordination", "id", "title", "description", "category", "categoryIds", "categoryIdNames", "subcategory", "brand", "price", "originalPrice", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "views7d", "views30d", "unitLabel", "partGlossaryId", "colourway", "mouldVariant", "assembledFrom", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "createdAt"];
841
866
  export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "categoryIds", "subcategory", "brand", "price", "originalPrice", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "finalSale", "isLot", "lotSize", "physicalLocation", "sku", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "madeToOrder", "sourceStickerSheetId", "sourceStickerPartOrdinal", "allowShipBeforeEmiComplete", "gstRate", "hsnCode", "authenticity", "printMeta", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "groupMinMembers", "groupMaxMembers", "groupMaxUnitsPerMember", "groupRequiredMemberIds", "groupDiscountPercent", "barcodeId"];
842
867
  export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
843
868
  export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
@@ -142,6 +142,8 @@ export const PRODUCT_PUBLIC_FIELDS = [
142
142
  // the moment something starts projecting through it.
143
143
  "allowOffers",
144
144
  "minOfferPercent",
145
+ /* Same reasoning as `allowOffers`: the card renders a pill from it. */
146
+ "allowCoordination",
145
147
  "id",
146
148
  "title",
147
149
  "description",
@@ -449,10 +449,10 @@ export declare const productItemSchema: z.ZodObject<{
449
449
  wonByOrderId?: string | undefined;
450
450
  }[] | undefined;
451
451
  prizeGithubFileUrl?: string | undefined;
452
+ attributes?: Record<string, string> | undefined;
452
453
  inStock?: boolean | undefined;
453
454
  sellerAvatar?: string | undefined;
454
455
  stockCount?: number | undefined;
455
- attributes?: Record<string, string> | undefined;
456
456
  }, {
457
457
  status: "published" | "draft" | "in_review" | "archived";
458
458
  id: string;
@@ -589,10 +589,10 @@ export declare const productItemSchema: z.ZodObject<{
589
589
  wonByOrderId?: string | undefined;
590
590
  }[] | undefined;
591
591
  prizeGithubFileUrl?: string | undefined;
592
+ attributes?: Record<string, string> | undefined;
592
593
  inStock?: boolean | undefined;
593
594
  sellerAvatar?: string | undefined;
594
595
  stockCount?: number | undefined;
595
- attributes?: Record<string, string> | undefined;
596
596
  }>;
597
597
  /** Base Zod schema for list-query parameters. */
598
598
  export declare const productListParamsSchema: z.ZodObject<{
@@ -52,6 +52,20 @@ declare class ReviewRepository extends BaseRepository<ReviewDocument> {
52
52
  findApprovedByStore(storeId: string, limit?: number): Promise<ReviewDocument[]>;
53
53
  findByUser(userId: string): Promise<ReviewDocument[]>;
54
54
  findApprovedByUser(userId: string): Promise<ReviewDocument[]>;
55
+ /**
56
+ * How many reviews this account has WRITTEN.
57
+ *
58
+ * Deliberately every status and both reviewer roles — it counts the same set
59
+ * `GET /api/user/reviews` lists by default (`findByUser`), so the number in
60
+ * the sidebar and the rows on the page it links to agree by construction. A
61
+ * count that quietly excluded pending reviews would read as the author's
62
+ * work having vanished.
63
+ *
64
+ * An aggregation, not `findByUser(...).length`: that sibling downloads every
65
+ * review the account has ever written to learn how many there are, and the
66
+ * caller here runs on every session profile fetch.
67
+ */
68
+ countByUser(userId: string): Promise<number>;
55
69
  findPending(): Promise<ReviewDocument[]>;
56
70
  findByStatus(status: ReviewStatus): Promise<ReviewDocument[]>;
57
71
  /**
@@ -175,6 +175,22 @@ class ReviewRepository extends BaseRepository {
175
175
  .get();
176
176
  return snapshot.docs.map((doc) => this.mapDoc(doc));
177
177
  }
178
+ /**
179
+ * How many reviews this account has WRITTEN.
180
+ *
181
+ * Deliberately every status and both reviewer roles — it counts the same set
182
+ * `GET /api/user/reviews` lists by default (`findByUser`), so the number in
183
+ * the sidebar and the rows on the page it links to agree by construction. A
184
+ * count that quietly excluded pending reviews would read as the author's
185
+ * work having vanished.
186
+ *
187
+ * An aggregation, not `findByUser(...).length`: that sibling downloads every
188
+ * review the account has ever written to learn how many there are, and the
189
+ * caller here runs on every session profile fetch.
190
+ */
191
+ async countByUser(userId) {
192
+ return getFirestoreCount(this.getCollection().where(REVIEW_FIELDS.USER_ID, "==", userId));
193
+ }
178
194
  async findPending() {
179
195
  return this.findBy(REVIEW_FIELDS.STATUS, "pending");
180
196
  }
@@ -142,7 +142,8 @@ class ScammerRepository extends BaseRepository {
142
142
  reason: ctx?.reason,
143
143
  note: ctx?.note,
144
144
  // A scammer profile is built entirely from identifying details, and
145
- // `encryptPiiFields` never descends into arrays.
145
+ // `encryptPiiFields` covers a field only where it is NAMED. It CAN
146
+ // reach into an array via `array[].key`, but a bare key does not.
146
147
  piiFields: SCAMMER_HISTORY_PII_FIELDS,
147
148
  });
148
149
  const data = prepareForFirestore(withEntry ?? { ...input, updatedAt: new Date() });
@@ -403,6 +403,7 @@ export declare const SCAMMER_TRACKED_FIELDS: readonly ["status", "verifiedBy", "
403
403
  /**
404
404
  * PII on this document, for `withHistory`'s scrub. A scammer profile is built
405
405
  * ENTIRELY out of identifying details, so this list is long on purpose —
406
- * `encryptPiiFields` never descends into arrays, and history is an array.
406
+ * `encryptPiiFields` covers only NAMED paths, and nobody is going to declare
407
+ * `statusHistory[].changes.<key>` for every field that might land there.
407
408
  */
408
409
  export declare const SCAMMER_HISTORY_PII_FIELDS: readonly ["name", "displayName", "email", "phone", "phoneNumber", "upiId", "bankAccount", "accountNumber", "reporterEmail", "reporterName"];
@@ -248,7 +248,8 @@ export const SCAMMER_TRACKED_FIELDS = [
248
248
  /**
249
249
  * PII on this document, for `withHistory`'s scrub. A scammer profile is built
250
250
  * ENTIRELY out of identifying details, so this list is long on purpose —
251
- * `encryptPiiFields` never descends into arrays, and history is an array.
251
+ * `encryptPiiFields` covers only NAMED paths, and nobody is going to declare
252
+ * `statusHistory[].changes.<key>` for every field that might land there.
252
253
  */
253
254
  export const SCAMMER_HISTORY_PII_FIELDS = [
254
255
  "name",