@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
@@ -7,6 +7,9 @@
7
7
  import { serverLogger } from "../../../monitoring/index.js";
8
8
  import { offerRepository } from "../repository/offer.repository.js";
9
9
  import { productRepository } from "../../products/repository/products.repository.js";
10
+ import { categoriesRepository } from "../../categories/repository/categories.repository.js";
11
+ import { resolveParcel } from "../../../_internal/shared/features/shipping/parcel.js";
12
+ import { checkDestinations, assertAllServiceable, } from "../../../_internal/server/features/shipping/book-split.js";
10
13
  import { ProductStatusValues } from "../../products/schemas/index.js";
11
14
  import { sendNotification } from "../../admin/actions/notification-actions.js";
12
15
  import { userRepository } from "../../auth/repository/user.repository.js";
@@ -184,6 +187,59 @@ export async function respondToOffer(userId, input) {
184
187
  throw new ValidationError(ERROR_MESSAGES.OFFER.EXPIRED);
185
188
  let updated;
186
189
  if (action === "accept") {
190
+ /*
191
+ * ── S5a: a coordinated offer must be SHIPPABLE before it is binding ──
192
+ *
193
+ * Accepting locks a price and starts the buyer's checkout deadline, so it
194
+ * is the last moment at which "the courier cannot reach one of these
195
+ * addresses" costs nobody anything. After it, the coordinator has paid
196
+ * and the only remedies left are a refund and an apology.
197
+ *
198
+ * 🛑 Deliberately NOT at booking time, which is the obvious place and the
199
+ * wrong one: by then the money is taken, and a four-destination order
200
+ * that fails on the fourth has three parcels the carrier already accepted
201
+ * and no rollback. `assertAllServiceable` still guards the booking path —
202
+ * this is the check that keeps it from ever being reached in that state.
203
+ *
204
+ * 🛑 Costs NOTHING on an ordinary offer. `coordination` is absent on
205
+ * every non-coordinated one, so this whole block is skipped — a check
206
+ * that charges every seller for a feature most never use is how Rule #6's
207
+ * round-trip budget gets spent by accident.
208
+ *
209
+ * 🛑 The weight comes from `resolveParcel`, NOT from a literal. That
210
+ * module's whole argument is that an item weight systematically
211
+ * under-estimates a packed box (a bey is ~30 g loose and ~300 g boxed),
212
+ * and a hard-coded `1` here would be a fourth, worse tier beside the
213
+ * three it defines. The shipment tier is deliberately absent — nothing is
214
+ * packed yet at accept time, which is exactly the case tier two and three
215
+ * exist for.
216
+ *
217
+ * 🛑 **No resolvable weight SKIPS the check** rather than guessing one.
218
+ * The carrier is being asked "can you reach this pincode with a parcel of
219
+ * this size", so an invented weight asks a question about a parcel that
220
+ * does not exist and may refuse a destination that is perfectly
221
+ * reachable. Not checking is honest; the booking path still has
222
+ * `assertAllServiceable`, and a listing with no weight at any tier has a
223
+ * data problem this is not the place to report.
224
+ */
225
+ const members = offer.coordination?.members ?? [];
226
+ if (members.length > 0) {
227
+ const offerProduct = await productRepository.findById(offer.productId);
228
+ /*
229
+ * 🛑 `categorySlugs[0]` is the LEAF, and `findById` is the right lookup
230
+ * for it. Products carry the full ancestor chain `[leaf, …, root]` and
231
+ * categories are pure slugs (`id === slug`), so this is one get rather
232
+ * than the `where("slug","==")` query `findBySlugAndType` would run —
233
+ * and the leaf is the tier whose packed default is actually about this
234
+ * kind of item. A root's default would be the average of the catalogue.
235
+ */
236
+ const leafSlug = offerProduct?.categorySlugs?.[0];
237
+ const leaf = leafSlug ? await categoriesRepository.findById(leafSlug) : null;
238
+ const parcel = resolveParcel(null, offerProduct, leaf?.productDefaults ?? null);
239
+ if (parcel) {
240
+ assertAllServiceable(await checkDestinations(members, parcel.weightG / 1000));
241
+ }
242
+ }
187
243
  updated = await offerRepository.accept(offerId, offer.offerAmount, sellerNote, new Date(Date.now() + OFFER_CHECKOUT_WINDOW_MS));
188
244
  }
189
245
  else if (action === "decline") {
@@ -181,7 +181,7 @@ export declare function listSellerPayouts(userId: string, params?: {
181
181
  page?: number;
182
182
  pageSize?: number;
183
183
  }): Promise<{
184
- items: import("../../payments").PayoutDocument[];
184
+ items: import("../../payments").StorePayoutDocument[];
185
185
  total: number;
186
186
  page: number;
187
187
  pageSize: number;
@@ -299,6 +299,8 @@ export async function requestPayout(userId, userName, userEmail, input) {
299
299
  if (earnings.netAmount <= 0 || earnings.eligibleOrders.length === 0)
300
300
  throw new ValidationError("No eligible earnings available for payout.");
301
301
  const payout = await payoutRepository.createPayout({
302
+ /* A seller requesting their own earnings — a liability settlement. */
303
+ payeeType: "store",
302
304
  storeId: userId,
303
305
  sellerName: userName,
304
306
  sellerEmail: userEmail,
@@ -391,6 +393,8 @@ export async function bulkSellerOrder(userId, userRole, userDisplayName, userEma
391
393
  // The store-funded points, both ends — see the preview path above.
392
394
  ordersPointsBorneByStore(eligible));
393
395
  const payoutDoc = await payoutRepository.createPayout({
396
+ /* A seller requesting their own earnings — a liability settlement. */
397
+ payeeType: "store",
394
398
  storeId: userId,
395
399
  sellerName: userDisplayName,
396
400
  sellerEmail: userEmail,
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export declare function DispatchForm({ orderId,
3
+ /** Already dispatched? Then there is nothing to book and nothing to record. */
4
+ trackingNumber,
5
+ /** Set by `bookShipment`; a booked parcel must not be re-booked. */
6
+ platformBooked, onDispatched, }: {
7
+ orderId: string;
8
+ trackingNumber?: string;
9
+ platformBooked?: boolean;
10
+ onDispatched: () => void;
11
+ }): React.JSX.Element;
@@ -0,0 +1,190 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ /*
4
+ * WHY: Bug 0e — a seller could not express "book this through us". The write
5
+ * shape said `method: z.literal("custom")`, so there was no option to
6
+ * offer; `platformBookedShipment`, which `order-math.ts` gates shipping
7
+ * reimbursement on, had ZERO writers.
8
+ *
9
+ * The schema now carries a discriminated union and the server does the
10
+ * booking. This is the control that reaches it.
11
+ *
12
+ * WHAT: One dispatch panel with two branches — post it yourself, or book a
13
+ * pickup on the platform's carrier account.
14
+ *
15
+ * EXPORTS: DispatchForm
16
+ *
17
+ * @tag domain:seller,orders,shipping
18
+ * @tag layer:component
19
+ * @tag pattern:form
20
+ * @tag access:client
21
+ * @tag consumers:SellerOrderDetailPanel
22
+ * @tag sideEffects:network
23
+ */
24
+ import React, { useState } from "react";
25
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
26
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
27
+ import { apiClient, ApiClientError } from "../../../http/index.js";
28
+ import { useApiMutation } from "../../../client/api/useApiMutation.js";
29
+ import { useQuery } from "@tanstack/react-query";
30
+ import { normalizeError } from "../../../errors/normalize.js";
31
+ import { toUserMessage } from "../../../errors/error-display-map.js";
32
+ import { Button } from "../../../ui/components/Button.js";
33
+ import { Div } from "../../../ui/components/Div.js";
34
+ import { Row } from "../../../ui/components/Layout.js";
35
+ import { Stack } from "../../../ui/components/Layout.js";
36
+ import { Text } from "../../../ui/components/Typography.js";
37
+ import { Form } from "../../../ui/components/Form.js";
38
+ import { FieldInput } from "../../../ui/forms/FieldInput.js";
39
+ import { FieldSelect } from "../../../ui/forms/FieldSelect.js";
40
+ import { shipOrderSchema } from "../schemas/order-forms.js";
41
+ import { applyZodIssues, rootLevelMessages } from "../../../ui/forms/apply-zod-issues.js";
42
+ /**
43
+ * Box presets, in centimetres and kilograms.
44
+ *
45
+ * 🛑 A PRESET rather than four number inputs, and that is the whole UX
46
+ * argument: a seller packing a Beyblade does not know the parcel's
47
+ * dimensions, and a carrier rejects a wrong one with an opaque error at
48
+ * pickup time rather than at booking time. Four free fields produce four
49
+ * chances to guess. "Custom" exists for the genuinely odd parcel.
50
+ */
51
+ const BOX_PRESETS = [
52
+ { id: "small", label: "Small — single top (0.3 kg)", weight: 0.3, length: 20, width: 15, height: 10 },
53
+ { id: "medium", label: "Medium — a few items (1 kg)", weight: 1, length: 30, width: 25, height: 15 },
54
+ { id: "large", label: "Large — stadium or bundle (3 kg)", weight: 3, length: 45, width: 35, height: 25 },
55
+ ];
56
+ export function DispatchForm({ orderId,
57
+ /** Already dispatched? Then there is nothing to book and nothing to record. */
58
+ trackingNumber,
59
+ /** Set by `bookShipment`; a booked parcel must not be re-booked. */
60
+ platformBooked, onDispatched, }) {
61
+ const [method, setMethod] = useState("custom");
62
+ const [carrier, setCarrier] = useState("");
63
+ const [tracking, setTracking] = useState("");
64
+ const [trackingUrl, setTrackingUrl] = useState("");
65
+ const [pickupId, setPickupId] = useState("");
66
+ const [boxId, setBoxId] = useState(BOX_PRESETS[1].id);
67
+ const [error, setError] = useState(null);
68
+ /*
69
+ * 🛑 Fetched ONLY when the platform branch is open. The custom branch is
70
+ * the common case and needs no address at all, so loading this on mount
71
+ * would be a request on every order panel open for a field most sellers
72
+ * never reach (Rule #6).
73
+ */
74
+ /*
75
+ * 🛑 `toUserMessage(code)` on failure, never the server's own `error`
76
+ * string — an unmapped code would print server internals into the panel,
77
+ * which is literally how a Node `MODULE_NOT_FOUND` with a require stack
78
+ * reached a buyer-facing modal (Root Cause #86). `ApiClientError` carries
79
+ * the stable code, so the mapping survives the move off raw `fetch`.
80
+ */
81
+ /*
82
+ * 🛑 A REF, because `setFieldError` comes from `<Form>`'s render-prop and
83
+ * therefore does not exist in component scope where the mutation is
84
+ * declared. The mutation needs a STABLE sink — passing the render-prop
85
+ * helper directly is impossible, and re-creating the mutation per render to
86
+ * close over it would reset its pending state mid-request.
87
+ *
88
+ * It is assigned inside the render-prop below, so by the time any submit
89
+ * can fire it is populated. The `?.` is not defensive decoration: the first
90
+ * render completes before `<Form>` calls its child.
91
+ */
92
+ const fieldErrorSink = React.useRef(null);
93
+ const shipMutation = useApiMutation({
94
+ /*
95
+ * 🛑 Passed so the server's own per-field `issues` land on the fields
96
+ * they name. The route parses the same `shipOrderSchema` this form does,
97
+ * so a rejection it produces that the client parse did not is real news
98
+ * — a carrier the courier does not recognise, a pickup address that
99
+ * stopped being serviceable — and collapsing that into one generic
100
+ * sentence hides which input to fix.
101
+ */
102
+ setFieldError: (field, message) => fieldErrorSink.current?.(field, message),
103
+ mutationFn: (payload) => apiClient.post(SELLER_ENDPOINTS.ORDER_SHIP(orderId), payload),
104
+ onSuccess: () => onDispatched(),
105
+ onError: (err) => {
106
+ void normalizeError(err);
107
+ setError(toUserMessage(err instanceof ApiClientError ? err.code : undefined));
108
+ },
109
+ });
110
+ /*
111
+ * 🛑 `enabled` is what keeps this request off the common path. The custom
112
+ * branch needs no address at all, so loading pickups on mount would fire a
113
+ * request on every order panel a seller opens for a field most of them
114
+ * never reach (Rule #6). `useQuery` also dedupes and caches it, which the
115
+ * hand-rolled effect could not — it refetched on every branch toggle.
116
+ */
117
+ const pickupsQuery = useQuery({
118
+ queryKey: ["store", "addresses", "pickup"],
119
+ enabled: method === "platform",
120
+ queryFn: () => apiClient.get(SELLER_ENDPOINTS.STORE_ADDRESSES),
121
+ });
122
+ const pickups = Array.isArray(pickupsQuery.data?.items)
123
+ ? pickupsQuery.data.items
124
+ : [];
125
+ /*
126
+ * 🛑 A FAILED request and an empty list must not render the same. An empty
127
+ * dropdown reads as "this store has no pickup addresses" — a fact about the
128
+ * store — when it may be a network error, and the two need different
129
+ * actions from the seller.
130
+ */
131
+ const pickupsFailed = pickupsQuery.isError;
132
+ React.useEffect(() => {
133
+ if (pickups.length === 1 && !pickupId)
134
+ setPickupId(pickups[0].id);
135
+ }, [pickups, pickupId]);
136
+ if (trackingNumber) {
137
+ return (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Dispatched" }), _jsx(Text, { size: "xs", color: "muted", children: platformBooked
138
+ ? `Booked through LetItRip · ${trackingNumber}`
139
+ : `Posted by you · ${trackingNumber}` })] }));
140
+ }
141
+ const submit = async (
142
+ /* The `<Form>` render-prop's own signature — `applyZodIssues` clears a
143
+ * field by passing `null`, so a `string`-only sink is not assignable. */
144
+ setFieldError, clearErrors) => {
145
+ clearErrors();
146
+ setError(null);
147
+ const box = BOX_PRESETS.find((b) => b.id === boxId) ?? BOX_PRESETS[1];
148
+ const payload = method === "platform"
149
+ ? {
150
+ method: "platform",
151
+ pickupAddressId: pickupId,
152
+ dimensions: {
153
+ weight: box.weight,
154
+ length: box.length,
155
+ width: box.width,
156
+ height: box.height,
157
+ },
158
+ }
159
+ : {
160
+ method: "custom",
161
+ shippingCarrier: carrier.trim(),
162
+ trackingNumber: tracking.trim(),
163
+ trackingUrl: trackingUrl.trim(),
164
+ };
165
+ /*
166
+ * Parsed against the SAME schema the route and the action use, so the
167
+ * branch's required fields are stated once. `applyZodIssues` can only
168
+ * attach path-bearing issues, so root-level refinements are surfaced
169
+ * separately or a refused save shows nothing at all.
170
+ */
171
+ const parsed = shipOrderSchema.safeParse(payload);
172
+ if (!parsed.success) {
173
+ applyZodIssues(parsed.error.issues, setFieldError);
174
+ const root = rootLevelMessages(parsed.error.issues);
175
+ if (root.length > 0)
176
+ setError(root.join(" "));
177
+ return;
178
+ }
179
+ shipMutation.mutate(parsed.data);
180
+ };
181
+ return (_jsxs(Stack, { gap: "3", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Dispatch" }), _jsxs(Row, { gap: "sm", children: [_jsx(Button, { size: "sm", variant: method === "custom" ? "primary" : "outline", "aria-pressed": method === "custom", onClick: () => setMethod("custom"), children: "I posted it" }), _jsx(Button, { size: "sm", variant: method === "platform" ? "primary" : "outline", "aria-pressed": method === "platform", onClick: () => setMethod("platform"), children: "Book through LetItRip" })] }), _jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => {
182
+ fieldErrorSink.current = setFieldError;
183
+ return (_jsxs(Stack, { gap: "3", children: [method === "custom" ? (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "shippingCarrier", label: "Carrier", required: true, value: carrier, onChange: setCarrier }), _jsx(FieldInput, { name: "trackingNumber", label: "Tracking number", required: true, value: tracking, onChange: setTracking }), _jsx(FieldInput, { name: "trackingUrl", label: "Tracking URL", required: true, type: "url", value: trackingUrl, onChange: setTrackingUrl })] })) : (_jsxs(_Fragment, { children: [pickupsFailed ? (_jsx(Text, { size: "xs", color: "error", role: "alert", children: "Could not load your pickup addresses. Reopen this panel to try again." })) : pickups.length === 0 ? (_jsx(Text, { size: "xs", color: "muted", children: "Loading your pickup addresses\u2026" })) : (_jsx(FieldSelect, { name: "pickupAddressId", label: "Collect from", required: true, value: pickupId, onChange: setPickupId, options: pickups.map((p) => ({
184
+ value: p.id,
185
+ label: [p.label, p.city, p.postalCode].filter(Boolean).join(" · ") || p.id,
186
+ })) })), _jsx(FieldSelect, { name: "box", label: "Parcel size", required: true, value: boxId, onChange: setBoxId, options: BOX_PRESETS.map((b) => ({ value: b.id, label: b.label })) }), _jsx(Text, { size: "xs", color: "muted", children: "The courier is booked on the platform's account and the tracking id comes back from them \u2014 you do not enter one." })] })), error && (_jsx(Div, { textSize: "xs", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", role: "alert", children: error })), _jsx(Button, { type: "submit", action: method === "platform"
187
+ ? ACTIONS.STORE["dispatch-platform"]
188
+ : ACTIONS.STORE["dispatch-custom"], isLoading: shipMutation.isPending, onClick: () => void submit(setFieldError, clearErrors) })] }));
189
+ } })] }));
190
+ }
@@ -5,6 +5,7 @@ import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges.js";
5
5
  import { OrderProvenanceCard } from "../../orders/components/OrderProvenanceCard.js";
6
6
  import { groupOrderItemsByLine } from "../../orders/utils/bundle-grouping.js";
7
7
  import { normalizeError } from "../../../errors/normalize.js";
8
+ import { formatCurrency } from "../../../utils/number.formatter.js";
8
9
  // The defining module, not a barrel (Root Cause #18). Maps a server error CODE
9
10
  // to user-facing copy; it can never return server text, which is the point.
10
11
  import { toUserMessage } from "../../../errors/error-display-map.js";
@@ -27,6 +28,7 @@ import { SectionForm, useSectionFormNav, buildSectionsFromSchema, visibleValues,
27
28
  import { useFormShellState, FormShellContext } from "../../../ui/forms/index.js";
28
29
  import { applyZodIssues } from "../../../ui/forms/apply-zod-issues.js";
29
30
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
31
+ import { DispatchForm } from "./DispatchForm.js";
30
32
  import { PhysicalLocationModal } from "./PhysicalLocationModal.js";
31
33
  import { ROUTES } from "../../../constants/index.js";
32
34
  import { toRecordArray, toCurrency, toStringValue } from "../hooks/useSellerListingData.js";
@@ -37,6 +39,13 @@ import { MediaImage } from "../../media/MediaImage.js";
37
39
  const __O = {
38
40
  yAuto: "overflow-y-auto",
39
41
  };
42
+ /*
43
+ * The hairline that separates each panel section inside the order drawer.
44
+ * Named because the drawer gained its fourth section with D55's balance
45
+ * collector, and four copies of one border declaration is four places a
46
+ * future token rename has to find.
47
+ */
48
+ const SECTION_DIVIDER = "border-t border-[var(--appkit-color-border)]";
40
49
  const DEFAULT_SORT = "-createdAt";
41
50
  const SORT_OPTIONS = [
42
51
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
@@ -96,6 +105,69 @@ const EMI_INSTALLMENT_BADGE_VARIANT = {
96
105
  pending: "warning",
97
106
  overdue: "danger",
98
107
  };
108
+ /**
109
+ * D55 — the seller's half of the pre-order balance.
110
+ *
111
+ * ## 🛑 Opening is an EVENT, and the control has to say so
112
+ *
113
+ * The balance has existed on the order since checkout. What the seller is
114
+ * declaring here is that the GOODS HAVE ARRIVED — a pre-order's premise is
115
+ * that the item does not exist yet, so asking for the balance before then is
116
+ * asking a buyer to fund inventory. The button's label and its blurb both
117
+ * name that, rather than saying "request payment", which would read as a
118
+ * nudge the seller could send at any time.
119
+ *
120
+ * ## Why no Cancel and no confirm dialog
121
+ *
122
+ * Opening takes money NOWHERE and reverses by simply not chasing it — the
123
+ * worst case is a buyer told a week early. A confirmation step on a
124
+ * non-destructive, idempotent action is the dialog people learn to click
125
+ * through, which is what makes the ones on real deletions worthless.
126
+ *
127
+ * ## The states are the seller's, not the buyer's
128
+ *
129
+ * Once opened the seller cannot act again, so every later state is read-only
130
+ * here: "with the buyer", "proof submitted, we are checking it", "paid". The
131
+ * verification is staff-only and deliberately NOT offered — a seller
132
+ * approving payment to themselves is not a review.
133
+ */
134
+ function SellerBalanceBlock({ orderId, apiBase, balance, balanceDue, currency, onOpened, }) {
135
+ const [opening, setOpening] = React.useState(false);
136
+ const [error, setError] = React.useState(null);
137
+ if (balance) {
138
+ const label = balance.status === "paid"
139
+ ? "Balance paid"
140
+ : balance.status === "submitted"
141
+ ? "Balance proof under review"
142
+ : balance.status === "rejected"
143
+ ? "Balance proof rejected — the buyer has been asked to resend"
144
+ : "Balance open — awaiting the buyer";
145
+ return (_jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Pre-order balance" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [formatCurrency(balance.amount, currency), " \u2014 ", label, balance.transactionId ? ` · Ref ${balance.transactionId}` : ""] })] }));
146
+ }
147
+ const handleOpen = async () => {
148
+ setOpening(true);
149
+ setError(null);
150
+ try {
151
+ const res = await fetch(`${apiBase}/${orderId}/open-balance`, {
152
+ method: "POST",
153
+ credentials: "include",
154
+ });
155
+ if (!res.ok) {
156
+ setError("Could not open the balance. Try again, or contact support if it persists.");
157
+ return;
158
+ }
159
+ onOpened();
160
+ }
161
+ catch (err) {
162
+ void normalizeError(err);
163
+ setError("Could not reach the server. Check your connection and try again.");
164
+ }
165
+ finally {
166
+ setOpening(false);
167
+ }
168
+ };
169
+ return (_jsxs(Div, { children: [_jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: "Pre-order balance" }), _jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [formatCurrency(balanceDue, currency), " is still owed on this order. Open it once the stock is physically with you \u2014 the buyer is notified and gets a week to pay, and the parcel ships when it clears."] }), _jsx(Row, { className: "mt-2", gap: "3", children: _jsx(Button, { variant: "primary", size: "sm", disabled: opening, onClick: handleOpen, children: opening ? "Opening…" : "Stock has arrived — open balance" }) }), error && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: error }))] }));
170
+ }
99
171
  /**
100
172
  * Fetches + renders one order's full detail content (items, address,
101
173
  * payment, EMI, status/tracking update form). Shared by the seller orders
@@ -299,11 +371,31 @@ export function SellerOrderDetailPanel({ orderId, apiBase, onClose, }) {
299
371
  const addrLine = typeof rawAddr === "string" && rawAddr.trim()
300
372
  ? rawAddr.trim()
301
373
  : [addr.addressLine1, addr.city, addr.state, addr.pincode].filter(Boolean).join(", ");
302
- return (_jsxs(_Fragment, { children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsx(Stack, { gap: "none", children: _jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status ?? ""] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: toRelativeDate(order.createdAt) })] }), _jsx(OrderProvenanceCard, { sourceContext: order.sourceContext, currency: order.currency }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: groupOrderItemsByLine(order.items ?? []).map((group, gi) => group.kind === "single" ? (sellerOrderItemRow(group.item, `s-${group.index}`)) : (_jsxs(Div, { children: [_jsxs(Row, { paddingY: "y-xs", padding: "x-sm", align: "center", gap: "sm", surface: "muted", children: [_jsx(Text, { size: "xs", weight: "semibold", children: group.groupTitle ?? "Grouped purchase" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: `${group.memberCount} ${group.memberCount === 1 ? "item" : "items"} · one line` })] }), _jsx(Div, { className: "pl-4", children: group.items.map(({ item, index }) => sellerOrderItemRow(item, `m-${index}`)) })] }, `g-${gi}`))) })] })), _jsx(OrderAddonBadges, { order: order, variant: "detail" }), order.giftWrapAddon && (_jsxs(Div, { className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)]", surface: "subtle", padding: "inline", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "\uD83C\uDF81 Gift wrap requested" }), order.giftWrapMessage && (_jsxs(Text, { size: "sm", className: "mt-1 text-[var(--appkit-color-text-muted)]", children: ["\u201C", order.giftWrapMessage, "\u201D"] }))] })), _jsxs(Row, { surface: "muted", padding: "inline", align: "center", justify: "between", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Total" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-primary)]", weight: "bold", children: toCurrency(order.totalAmount ?? order.totalPrice ?? 0) })] }), addrLine && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Shipping address" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [String(addr.fullName ?? ""), addrLine].filter(Boolean).join(" · ") })] })), order.paymentMethod && (_jsxs(Div, { children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-1", gap: "3", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Payment" }), isManualPaymentMethod(order.paymentMethod) && (_jsx(Badge, { variant: sellerPaymentBadge(order).variant, children: sellerPaymentBadge(order).label }))] }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", transform: "capitalize", children: order.paymentMethod }), isManualPaymentMethod(order.paymentMethod) && order.paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", className: "mt-1", children: ["UTR: ", order.paymentTransactionId] }))] })), order.emiEnabled && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-md", children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: ["EMI plan ", order.emiTenureMonths ? `· ${order.emiTenureMonths} months` : ""] }), _jsx(Badge, { variant: order.emiComplete ? "success" : "warning", children: order.emiComplete ? "Fully paid" : "In progress" })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsx(Text, { size: "xs", color: "muted", children: "Token collected" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiTokenAmount ?? 0) })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-3", children: [_jsx(Text, { size: "xs", color: "muted", children: "Remaining balance" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiRemainingBalance ?? 0) })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.emiInstallments ?? []).map((inst) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["Installment ", inst.index, " \u00B7 ", toCurrency(inst.amount)] }), _jsx(Text, { size: "xs", color: "muted", children: inst.status === "paid" && inst.paidAt
374
+ return (_jsxs(_Fragment, { children: [loading && (_jsx(Row, { align: "center", justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })), fetchError && (_jsx(Div, { textSize: "sm", className: "mx-4 mt-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "lg", children: fetchError })), order && !loading && (_jsx(Stack, { gap: "none", children: _jsxs(Stack, { className: `flex-1 ${__O.yAuto}`, gap: "5", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", children: [_jsx(Badge, { variant: STATUS_BADGE_VARIANT[order.status ?? ""] ?? "default", children: order.status ?? "Unknown" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: toRelativeDate(order.createdAt) })] }), _jsx(OrderProvenanceCard, { sourceContext: order.sourceContext, currency: order.currency }), (order.items ?? []).length > 0 && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text)] mb-2", weight: "semibold", children: "Items" }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: groupOrderItemsByLine(order.items ?? []).map((group, gi) => group.kind === "single" ? (sellerOrderItemRow(group.item, `s-${group.index}`)) : (_jsxs(Div, { children: [_jsxs(Row, { paddingY: "y-xs", padding: "x-sm", align: "center", gap: "sm", surface: "muted", children: [_jsx(Text, { size: "xs", weight: "semibold", children: group.groupTitle ?? "Grouped purchase" }), _jsx(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: `${group.memberCount} ${group.memberCount === 1 ? "item" : "items"} · one line` })] }), _jsx(Div, { paddingX: "l-md", children: group.items.map(({ item, index }) => sellerOrderItemRow(item, `m-${index}`)) })] }, `g-${gi}`))) })] })), _jsx(OrderAddonBadges, { order: order, variant: "detail" }), order.giftWrapAddon && (_jsxs(Div, { className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)]", surface: "subtle", padding: "inline", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "\uD83C\uDF81 Gift wrap requested" }), order.giftWrapMessage && (_jsxs(Text, { size: "sm", className: "mt-1 text-[var(--appkit-color-text-muted)]", children: ["\u201C", order.giftWrapMessage, "\u201D"] }))] })), _jsxs(Row, { surface: "muted", padding: "inline", align: "center", justify: "between", rounded: "lg", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Total" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-primary)]", weight: "bold", children: toCurrency(order.totalAmount ?? order.totalPrice ?? 0) })] }), addrLine && (_jsxs(Div, { children: [_jsx(Text, { size: "sm", className: "mb-1", weight: "semibold", children: "Shipping address" }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", children: [String(addr.fullName ?? ""), addrLine].filter(Boolean).join(" · ") })] })), order.paymentMethod && (_jsxs(Div, { children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-1", gap: "3", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Payment" }), isManualPaymentMethod(order.paymentMethod) && (_jsx(Badge, { variant: sellerPaymentBadge(order).variant, children: sellerPaymentBadge(order).label }))] }), _jsx(Text, { size: "sm", className: "text-[var(--appkit-color-text-muted)]", transform: "capitalize", children: order.paymentMethod }), isManualPaymentMethod(order.paymentMethod) && order.paymentTransactionId && (_jsxs(Text, { size: "xs", color: "muted", className: "mt-1", children: ["UTR: ", order.paymentTransactionId] }))] })), order.emiEnabled && (_jsxs(Div, { className: SECTION_DIVIDER, padding: "t-md", children: [_jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsxs(Text, { size: "sm", weight: "semibold", children: ["EMI plan ", order.emiTenureMonths ? `· ${order.emiTenureMonths} months` : ""] }), _jsx(Badge, { variant: order.emiComplete ? "success" : "warning", children: order.emiComplete ? "Fully paid" : "In progress" })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-2", children: [_jsx(Text, { size: "xs", color: "muted", children: "Token collected" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiTokenAmount ?? 0) })] }), _jsxs(Row, { align: "center", justify: "between", className: "mb-3", children: [_jsx(Text, { size: "xs", color: "muted", children: "Remaining balance" }), _jsx(Text, { size: "xs", weight: "medium", children: toCurrency(order.emiRemainingBalance ?? 0) })] }), _jsx(Div, { className: "divide-y divide-[var(--appkit-color-border)] border border-[var(--appkit-color-border)]", rounded: "lg", children: (order.emiInstallments ?? []).map((inst) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Div, { className: "min-w-0", children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["Installment ", inst.index, " \u00B7 ", toCurrency(inst.amount)] }), _jsx(Text, { size: "xs", color: "muted", children: inst.status === "paid" && inst.paidAt
303
375
  ? `Paid ${toRelativeDate(inst.paidAt)}`
304
376
  : inst.dueDate
305
377
  ? `Due ${toRelativeDate(inst.dueDate)}`
306
- : "" })] }), _jsxs(Row, { align: "center", gap: "sm", className: "shrink-0", children: [_jsx(Badge, { variant: EMI_INSTALLMENT_BADGE_VARIANT[inst.status] ?? "default", children: inst.status }), inst.status !== "paid" && (_jsx(Button, { action: ACTIONS.STORE["mark-installment-paid"], size: "sm", variant: "outline", isLoading: markingPaidIndex === inst.index, disabled: markingPaidIndex !== null, onClick: () => handleMarkInstallmentPaid(inst.index), children: "Mark paid" }))] })] }, inst.index))) }), emiError && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: emiError }))] })), _jsxs(Stack, { className: "border-t border-[var(--appkit-color-border)]", padding: "t-md", gap: "3", children: [_jsxs(FormShellContext.Provider, { value: form.shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: draft, onChange: (partial) => setDraft((d) => ({ ...d, ...partial })), onSubmit: () => {
378
+ : "" })] }), _jsxs(Row, { align: "center", gap: "sm", className: "shrink-0", children: [_jsx(Badge, { variant: EMI_INSTALLMENT_BADGE_VARIANT[inst.status] ?? "default", children: inst.status }), inst.status !== "paid" && (_jsx(Button, { action: ACTIONS.STORE["mark-installment-paid"], size: "sm", variant: "outline", isLoading: markingPaidIndex === inst.index, disabled: markingPaidIndex !== null, onClick: () => handleMarkInstallmentPaid(inst.index), children: "Mark paid" }))] })] }, inst.index))) }), emiError && (_jsx(Div, { textSize: "xs", className: "mt-2 border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: emiError }))] })), order.sourceContext?.path === "pre-order" &&
379
+ order.sourceContext.balanceDue > 0 && (_jsx(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: _jsx(SellerBalanceBlock, { orderId: orderId, apiBase: apiBase, balance: order.balance, balanceDue: order.sourceContext.balanceDue, currency: order.currency, onOpened: () => {
380
+ showToast("Balance opened — the buyer has been notified", "success");
381
+ void fetch(`${apiBase}/${orderId}`)
382
+ .then((r) => r.json())
383
+ .then((json) => setOrder((json?.data ?? json)))
384
+ .catch((err) => void normalizeError(err));
385
+ } }) })), _jsx(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: _jsx(DispatchForm, { orderId: orderId, trackingNumber: order.trackingNumber, platformBooked: order.platformBookedShipment, onDispatched: () => {
386
+ showToast("Dispatch recorded", "success");
387
+ /*
388
+ * Re-read rather than patching local state: the platform
389
+ * branch's tracking id is produced by the CARRIER, so there
390
+ * is no value here to optimistically write. On that branch
391
+ * the job is still running, so this first re-read correctly
392
+ * shows no tracking number yet.
393
+ */
394
+ void fetch(`${apiBase}/${orderId}`)
395
+ .then((r) => r.json())
396
+ .then((json) => setOrder((json?.data ?? json)))
397
+ .catch((err) => void normalizeError(err));
398
+ } }) }), _jsxs(Stack, { className: SECTION_DIVIDER, padding: "t-md", gap: "3", children: [_jsxs(FormShellContext.Provider, { value: form.shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: draft, onChange: (partial) => setDraft((d) => ({ ...d, ...partial })), onSubmit: () => {
307
399
  // SectionForm scrolls to the first erroring section and then
308
400
  // submits regardless, so the guard lives here.
309
401
  form.clearErrors();
@@ -5,6 +5,7 @@ export { SellerListingTypeView, SELLER_LISTING_TYPE_SPECS } from "./SellerListin
5
5
  export type { SellerListingTypeViewProps, SellerListingTypeSpec, SellerListingTypeKey, SellerListingRow } from "./SellerListingTypeView";
6
6
  export type { SellerProductsViewProps } from "./SellerProductsView";
7
7
  export { PrintCenterView } from "./PrintCenterView";
8
+ export { DispatchForm } from "./DispatchForm";
8
9
  export { SellerOrdersView, SellerOrderDetailPanel } from "./SellerOrdersView";
9
10
  export type { SellerOrdersViewProps } from "./SellerOrdersView";
10
11
  export { SellerOffersView } from "./SellerOffersView";
@@ -2,6 +2,7 @@ export { SellerDashboardView } from "./SellerDashboardView.js";
2
2
  export { SellerProductsView } from "./SellerProductsView.js";
3
3
  export { SellerListingTypeView, SELLER_LISTING_TYPE_SPECS } from "./SellerListingTypeView.js";
4
4
  export { PrintCenterView } from "./PrintCenterView.js";
5
+ export { DispatchForm } from "./DispatchForm.js";
5
6
  export { SellerOrdersView, SellerOrderDetailPanel } from "./SellerOrdersView.js";
6
7
  export { SellerOffersView } from "./SellerOffersView.js";
7
8
  export { SellerCouponsView } from "./SellerCouponsView.js";
@@ -247,9 +247,10 @@ class OfferRepository extends BaseRepository {
247
247
  trigger: ctx?.trigger ?? "offerRepository.updateStatus",
248
248
  reason: ctx?.reason,
249
249
  note: ctx?.note,
250
- // buyerName/buyerEmail live on this document. `encryptPiiFields` is a
251
- // flat top-level loop and never descends into arrays, so without this
252
- // they would persist in PLAINTEXT inside statusHistory.
250
+ // buyerName/buyerEmail live on this document. `encryptPiiFields` covers
251
+ // only NAMED paths, and nothing declares them under
252
+ // `statusHistory[].changes` — so without this scrub they persist in
253
+ // PLAINTEXT inside the history.
253
254
  piiFields: OFFER_PII_FIELDS,
254
255
  historyField: OFFER_FIELDS.STATUS_HISTORY,
255
256
  truncatedField: OFFER_FIELDS.STATUS_HISTORY_TRUNCATED,
@@ -5,6 +5,7 @@ import { type GenerateOfferIdInput } from "../../../utils/id-generators";
5
5
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
6
6
  import type { StatusChangeEntry } from "../../../_internal/shared/history/index";
7
7
  import type { Encrypted, Plaintext } from "../../../security/encrypted";
8
+ import type { OfferCoordination } from "../../../_internal/shared/features/offers/coordination";
8
9
  export type OfferStatus = "pending" | "accepted" | "declined" | "countered" | "expired" | "withdrawn" | "paid";
9
10
  /** Runtime-accessible offer status values — use instead of bare string literals. */
10
11
  export declare const OfferStatusValues: {
@@ -50,6 +51,25 @@ export interface OfferDocument extends BaseDocument {
50
51
  acceptedAt?: Date;
51
52
  checkoutDeadline?: Date;
52
53
  respondedAt?: Date;
54
+ /**
55
+ * Present when this offer is a COORDINATED purchase — several people
56
+ * chipping in on one listing.
57
+ *
58
+ * 🛑 A field on the offer, not a new document and not a new lifecycle.
59
+ * Acceptance produces an ordinary `offer-accepted` order; `unitPriceFor`
60
+ * already honours `lockedPrice`, so tax, payout base, refund ceiling and
61
+ * the invoice all follow with no new money term. See
62
+ * `_internal/shared/features/offers/coordination.ts` for the full list of
63
+ * things this deliberately does NOT add.
64
+ *
65
+ * 🛑 No member NAME is stored in here. `encryptPiiFields` CAN reach into an
66
+ * array (`coordination.members[].name`) — the claim that it cannot was
67
+ * wrong — but it covers only what is NAMED, and the near-miss `"name"`
68
+ * reads as covering it while storing plaintext. Members are uids; names
69
+ * resolve at render time, which removes the field rather than classifying
70
+ * it. See `coordination.ts`.
71
+ */
72
+ coordination?: OfferCoordination;
53
73
  /** Set by `markPaid` once the buyer's order exists — makes "paid" auditable. */
54
74
  paidOrderId?: string;
55
75
  paidAt?: Date;
@@ -93,8 +113,9 @@ export interface OfferDocument extends BaseDocument {
93
113
  /**
94
114
  * Fields whose changes are recorded in an offer's `statusHistory`.
95
115
  *
96
- * Excludes `buyerName`/`buyerEmail` (PII — `encryptPiiFields` never descends
97
- * into arrays, so they would persist in plaintext inside the history),
116
+ * Excludes `buyerName`/`buyerEmail` (PII — nothing declares them under
117
+ * `statusHistory[].changes`, and `encryptPiiFields` covers only NAMED paths,
118
+ * so they would persist in plaintext inside the history),
98
119
  * `buyerNote`/`sellerNote` (they ride in the entry's `note`), and
99
120
  * `updatedAt`/`respondedAt` (noise that would burn the FIFO cap).
100
121
  */
@@ -15,8 +15,9 @@ export const OfferStatusValues = {
15
15
  /**
16
16
  * Fields whose changes are recorded in an offer's `statusHistory`.
17
17
  *
18
- * Excludes `buyerName`/`buyerEmail` (PII — `encryptPiiFields` never descends
19
- * into arrays, so they would persist in plaintext inside the history),
18
+ * Excludes `buyerName`/`buyerEmail` (PII — nothing declares them under
19
+ * `statusHistory[].changes`, and `encryptPiiFields` covers only NAMED paths,
20
+ * so they would persist in plaintext inside the history),
20
21
  * `buyerNote`/`sellerNote` (they ride in the entry's `note`), and
21
22
  * `updatedAt`/`respondedAt` (noise that would burn the FIFO cap).
22
23
  */
@@ -72,13 +72,13 @@ export declare const sellerStoreSchema: z.ZodObject<{
72
72
  totalReviews: z.ZodNumber;
73
73
  averageRating: z.ZodOptional<z.ZodNumber>;
74
74
  }, "strip", z.ZodTypeAny, {
75
- itemsSold: number;
76
75
  totalProducts: number;
76
+ itemsSold: number;
77
77
  totalReviews: number;
78
78
  averageRating?: number | undefined;
79
79
  }, {
80
- itemsSold: number;
81
80
  totalProducts: number;
81
+ itemsSold: number;
82
82
  totalReviews: number;
83
83
  averageRating?: number | undefined;
84
84
  }>>;
@@ -96,8 +96,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
96
96
  location?: string | undefined;
97
97
  returnPolicy?: string | undefined;
98
98
  stats?: {
99
- itemsSold: number;
100
99
  totalProducts: number;
100
+ itemsSold: number;
101
101
  totalReviews: number;
102
102
  averageRating?: number | undefined;
103
103
  } | undefined;
@@ -128,8 +128,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
128
128
  location?: string | undefined;
129
129
  returnPolicy?: string | undefined;
130
130
  stats?: {
131
- itemsSold: number;
132
131
  totalProducts: number;
132
+ itemsSold: number;
133
133
  totalReviews: number;
134
134
  averageRating?: number | undefined;
135
135
  } | undefined;