@mohasinac/appkit 4.61.0 → 4.62.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +19 -3
  3. package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +1 -1
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +1 -1
  5. package/dist/_internal/server/features/checkout/actions.d.ts +80 -0
  6. package/dist/_internal/server/features/checkout/actions.js +542 -26
  7. package/dist/_internal/server/features/invoicing/allocate.d.ts +63 -0
  8. package/dist/_internal/server/features/invoicing/allocate.js +162 -0
  9. package/dist/_internal/server/features/invoicing/credit-note.d.ts +57 -0
  10. package/dist/_internal/server/features/invoicing/credit-note.js +126 -0
  11. package/dist/_internal/server/features/invoicing/gstr1.d.ts +74 -0
  12. package/dist/_internal/server/features/invoicing/gstr1.js +251 -0
  13. package/dist/_internal/server/features/invoicing/issue.d.ts +96 -0
  14. package/dist/_internal/server/features/invoicing/issue.js +202 -0
  15. package/dist/_internal/server/features/orders/actions.d.ts +8 -1
  16. package/dist/_internal/server/features/orders/actions.js +81 -8
  17. package/dist/_internal/server/features/orders/adapters.js +33 -0
  18. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +15 -9
  19. package/dist/_internal/server/features/orders/cancellation-refund.js +15 -9
  20. package/dist/_internal/server/features/points/award.js +46 -4
  21. package/dist/_internal/server/features/refunds/actions.js +26 -0
  22. package/dist/_internal/server/features/shipping/courier-block.d.ts +69 -0
  23. package/dist/_internal/server/features/shipping/courier-block.js +239 -0
  24. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  25. package/dist/_internal/server/features/site-settings/adapters.js +15 -0
  26. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  27. package/dist/_internal/server/features/stores/adapters.js +51 -0
  28. package/dist/_internal/server/functions/scheduled.js +38 -1
  29. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +9 -2
  30. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +4 -0
  31. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +19 -2
  32. package/dist/_internal/server/jobs/core/index.d.ts +2 -1
  33. package/dist/_internal/server/jobs/core/index.js +2 -1
  34. package/dist/_internal/server/jobs/core/onJobCreated.d.ts +30 -7
  35. package/dist/_internal/server/jobs/core/onJobCreated.js +78 -45
  36. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +7 -0
  37. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +62 -0
  38. package/dist/_internal/server/jobs/core/paymentReconcile.js +87 -97
  39. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +33 -0
  40. package/dist/_internal/server/jobs/core/runJobOnce.d.ts +48 -0
  41. package/dist/_internal/server/jobs/core/runJobOnce.js +195 -0
  42. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +10 -2
  43. package/dist/_internal/server/jobs/handlers/onOrderStatusChange.js +17 -0
  44. package/dist/_internal/server/notifications/channel-health.d.ts +26 -3
  45. package/dist/_internal/shared/actions/action-registry.js +177 -9
  46. package/dist/_internal/shared/checkout/coupon-discount.d.ts +43 -1
  47. package/dist/_internal/shared/checkout/coupon-discount.js +26 -1
  48. package/dist/_internal/shared/checkout/order-math.d.ts +145 -0
  49. package/dist/_internal/shared/checkout/order-math.js +173 -0
  50. package/dist/_internal/shared/checkout/refundable-base.d.ts +25 -0
  51. package/dist/_internal/shared/checkout/refundable-base.js +13 -1
  52. package/dist/_internal/shared/checkout/split.d.ts +87 -0
  53. package/dist/_internal/shared/checkout/split.js +100 -0
  54. package/dist/_internal/shared/content/disclaimers.d.ts +88 -0
  55. package/dist/_internal/shared/content/disclaimers.js +217 -0
  56. package/dist/_internal/shared/content/published-values.d.ts +88 -0
  57. package/dist/_internal/shared/content/published-values.js +198 -0
  58. package/dist/_internal/shared/features/admin/integrations.d.ts +109 -0
  59. package/dist/_internal/shared/features/admin/integrations.js +253 -0
  60. package/dist/_internal/shared/features/orders/dispute-reasons.d.ts +113 -0
  61. package/dist/_internal/shared/features/orders/dispute-reasons.js +181 -0
  62. package/dist/_internal/shared/features/orders/refund-copy.d.ts +1 -1
  63. package/dist/_internal/shared/features/orders/refund-copy.js +18 -1
  64. package/dist/_internal/shared/features/points/accepts.d.ts +36 -0
  65. package/dist/_internal/shared/features/points/accepts.js +43 -0
  66. package/dist/_internal/shared/features/shipping/shared-purchase.d.ts +91 -0
  67. package/dist/_internal/shared/features/shipping/shared-purchase.js +108 -0
  68. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  69. package/dist/_internal/shared/fees/calculator.d.ts +33 -1
  70. package/dist/_internal/shared/fees/calculator.js +32 -2
  71. package/dist/_internal/shared/media/limits.d.ts +10 -0
  72. package/dist/_internal/shared/media/limits.js +10 -0
  73. package/dist/_internal/shared/types/action-result.js +15 -1
  74. package/dist/client/api/surface-error.js +16 -2
  75. package/dist/client/api/useApiMutation.js +17 -1
  76. package/dist/client.d.ts +12 -1
  77. package/dist/client.js +31 -1
  78. package/dist/constants/api-endpoints.d.ts +45 -0
  79. package/dist/constants/api-endpoints.js +15 -0
  80. package/dist/constants/field-names.d.ts +18 -0
  81. package/dist/constants/field-names.js +28 -0
  82. package/dist/contracts/index.d.ts +1 -1
  83. package/dist/contracts/shipping.d.ts +45 -0
  84. package/dist/contracts/shipping.js +15 -0
  85. package/dist/core/server-action.js +19 -3
  86. package/dist/errors/error-mapping.js +26 -1
  87. package/dist/features/about/components/AboutView.js +1 -1
  88. package/dist/features/account/components/BuyerAuctionsGuideView.js +4 -1
  89. package/dist/features/account/components/UserOffersPanel.js +5 -3
  90. package/dist/features/account/components/UserOrdersView.js +19 -33
  91. package/dist/features/admin/components/AdminActionIndexView.js +100 -5
  92. package/dist/features/admin/components/AdminAddressClustersView.js +21 -2
  93. package/dist/features/admin/components/AdminBlogEditorView.js +8 -8
  94. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  95. package/dist/features/admin/components/AdminCarouselView.js +96 -4
  96. package/dist/features/admin/components/AdminCouponEditorView.js +17 -0
  97. package/dist/features/admin/components/AdminFulfillmentView.js +3 -2
  98. package/dist/features/admin/components/AdminInvoicingView.d.ts +2 -0
  99. package/dist/features/admin/components/AdminInvoicingView.js +65 -0
  100. package/dist/features/admin/components/AdminMediaView.js +36 -3
  101. package/dist/features/admin/components/AdminNavigationView.js +4 -1
  102. package/dist/features/admin/components/AdminOffersView.js +2 -1
  103. package/dist/features/admin/components/AdminOrderEditorView.d.ts +18 -1
  104. package/dist/features/admin/components/AdminOrderEditorView.js +11 -2
  105. package/dist/features/admin/components/AdminOrdersGuideView.js +23 -2
  106. package/dist/features/admin/components/AdminOrdersView.js +67 -5
  107. package/dist/features/admin/components/AdminPaymentClustersView.js +19 -2
  108. package/dist/features/admin/components/AdminProductEditorView.js +2 -2
  109. package/dist/features/admin/components/AdminSectionsView.d.ts +10 -0
  110. package/dist/features/admin/components/AdminSectionsView.js +486 -224
  111. package/dist/features/admin/components/OwnerQuickActions.js +24 -2
  112. package/dist/features/admin/components/ReferenceRecordEditor.js +1 -1
  113. package/dist/features/admin/constants/filter-tabs.d.ts +6 -0
  114. package/dist/features/admin/constants/filter-tabs.js +6 -0
  115. package/dist/features/admin/constants/site-settings-tabs.d.ts +1 -1
  116. package/dist/features/admin/hooks/useAdminListingData.js +16 -0
  117. package/dist/features/admin/hooks/useOptimisticRowFlag.d.ts +33 -0
  118. package/dist/features/admin/hooks/useOptimisticRowFlag.js +87 -0
  119. package/dist/features/admin/repository/notification.repository.d.ts +15 -0
  120. package/dist/features/admin/repository/notification.repository.js +32 -0
  121. package/dist/features/admin/schemas/action-index-form.d.ts +34 -0
  122. package/dist/features/admin/schemas/action-index-form.js +115 -0
  123. package/dist/features/admin/schemas/admin-user-form.d.ts +2 -2
  124. package/dist/features/admin/schemas/firestore.d.ts +30 -0
  125. package/dist/features/admin/schemas/firestore.js +5 -0
  126. package/dist/features/audit-log/schemas/firestore.d.ts +30 -0
  127. package/dist/features/audit-log/schemas/firestore.js +15 -0
  128. package/dist/features/auth/components/RegisterForm.js +2 -1
  129. package/dist/features/auth/permissions/constants.js +13 -0
  130. package/dist/features/cart/components/CartPriceBreakdown.d.ts +13 -0
  131. package/dist/features/cart/components/CartPriceBreakdown.js +6 -2
  132. package/dist/features/cart/components/PointsTenderPicker.d.ts +61 -0
  133. package/dist/features/cart/components/PointsTenderPicker.js +15 -0
  134. package/dist/features/cart/components/StoreAddonsPicker.js +2 -1
  135. package/dist/features/cart/components/index.d.ts +2 -0
  136. package/dist/features/cart/components/index.js +1 -0
  137. package/dist/features/cart/repository/cart.repository.d.ts +19 -0
  138. package/dist/features/cart/repository/cart.repository.js +27 -0
  139. package/dist/features/cart/schemas/firestore.d.ts +92 -0
  140. package/dist/features/catalogue/components/CatalogueItemEditorView.js +3 -3
  141. package/dist/features/contact/email.d.ts +27 -10
  142. package/dist/features/contact/email.js +20 -3
  143. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.d.ts +38 -0
  144. package/dist/features/dispute-reasons/repository/dispute-reasons.repository.js +80 -0
  145. package/dist/features/dispute-reasons/schemas/firestore.d.ts +64 -0
  146. package/dist/features/dispute-reasons/schemas/firestore.js +40 -0
  147. package/dist/features/events/components/AdminEventEditorView.js +8 -6
  148. package/dist/features/faq/components/FAQAccordion.d.ts +13 -1
  149. package/dist/features/faq/components/FAQAccordion.js +29 -4
  150. package/dist/features/invoicing/schemas/firestore.d.ts +112 -0
  151. package/dist/features/invoicing/schemas/firestore.js +45 -0
  152. package/dist/features/invoicing/utils/financial-year.d.ts +43 -0
  153. package/dist/features/invoicing/utils/financial-year.js +53 -0
  154. package/dist/features/jobs/actions/enqueue-job.js +18 -1
  155. package/dist/features/jobs/repository/jobs.repository.d.ts +62 -1
  156. package/dist/features/jobs/repository/jobs.repository.js +84 -1
  157. package/dist/features/jobs/schemas/firestore.d.ts +80 -0
  158. package/dist/features/jobs/schemas/firestore.js +37 -0
  159. package/dist/features/layout/hooks/useFormBottomActions.js +20 -1
  160. package/dist/features/lottery/schemas/zod.d.ts +6 -6
  161. package/dist/features/media/MediaImage.js +22 -1
  162. package/dist/features/media/upload/ImageUpload.js +1 -1
  163. package/dist/features/obligations/constants/policies.js +24 -1
  164. package/dist/features/orders/components/EmiInstallmentsPanel.js +25 -7
  165. package/dist/features/orders/components/OrderPaymentSummary.js +10 -1
  166. package/dist/features/orders/components/OrderProvenanceCard.d.ts +8 -0
  167. package/dist/features/orders/components/OrderProvenanceCard.js +185 -0
  168. package/dist/features/orders/components/RaiseDisputeForm.d.ts +14 -0
  169. package/dist/features/orders/components/RaiseDisputeForm.js +111 -0
  170. package/dist/features/orders/components/RefundRequestView.js +22 -4
  171. package/dist/features/orders/components/index.d.ts +4 -0
  172. package/dist/features/orders/components/index.js +2 -0
  173. package/dist/features/orders/constants/order-lane-tabs.d.ts +69 -0
  174. package/dist/features/orders/constants/order-lane-tabs.js +88 -0
  175. package/dist/features/orders/repository/orders.repository.d.ts +57 -0
  176. package/dist/features/orders/repository/orders.repository.js +145 -0
  177. package/dist/features/orders/schemas/firestore.d.ts +196 -1
  178. package/dist/features/orders/schemas/index.d.ts +60 -12
  179. package/dist/features/orders/schemas/index.js +12 -0
  180. package/dist/features/orders/types/index.d.ts +26 -0
  181. package/dist/features/orders/utils/courier-block.d.ts +25 -0
  182. package/dist/features/orders/utils/courier-block.js +23 -0
  183. package/dist/features/payment-cases/schemas/firestore.d.ts +26 -1
  184. package/dist/features/payment-cases/schemas/firestore.js +1 -0
  185. package/dist/features/products/components/GroupMemberPicker.js +4 -1
  186. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -2
  187. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -1
  188. package/dist/features/products/components/ProductDetailActions.js +2 -1
  189. package/dist/features/products/components/ProductDetailPageView.d.ts +17 -1
  190. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  191. package/dist/features/products/schemas/product-features.validators.d.ts +10 -10
  192. package/dist/features/promotions/actions/coupon-stacking.d.ts +30 -3
  193. package/dist/features/promotions/actions/coupon-stacking.js +21 -2
  194. package/dist/features/promotions/components/CouponCard.js +13 -0
  195. package/dist/features/promotions/constants/coupon-help.d.ts +2 -2
  196. package/dist/features/promotions/constants/coupon-help.js +9 -1
  197. package/dist/features/promotions/schemas/firestore.d.ts +24 -0
  198. package/dist/features/promotions/schemas/firestore.js +4 -0
  199. package/dist/features/promotions/types/index.d.ts +19 -1
  200. package/dist/features/scams/components/ScamProfileView.js +2 -2
  201. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  202. package/dist/features/search/action-index/tab-entries.js +1 -1
  203. package/dist/features/seller/actions/seller-actions.js +33 -3
  204. package/dist/features/seller/components/FulfillmentView.js +3 -2
  205. package/dist/features/seller/components/SellerAddressesView.js +32 -4
  206. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +62 -3
  207. package/dist/features/seller/components/SellerBundlesView.js +53 -14
  208. package/dist/features/seller/components/SellerGroupedListingsView.js +23 -3
  209. package/dist/features/seller/components/SellerListingTypeView.js +56 -10
  210. package/dist/features/seller/components/SellerOrdersView.js +129 -27
  211. package/dist/features/seller/components/SellerPayoutMethodsView.js +33 -1
  212. package/dist/features/seller/components/SellerPayoutSettingsView.js +48 -9
  213. package/dist/features/seller/components/SellerProductsView.js +5 -1
  214. package/dist/features/seller/components/SellerShippingConfigsView.js +33 -1
  215. package/dist/features/seller/components/SellerShippingView.js +2 -1
  216. package/dist/features/seller/components/SellerStoreCategoriesView.js +33 -1
  217. package/dist/features/seller/components/SellerSupportView.js +69 -2
  218. package/dist/features/seller/hooks/useSellerListingData.js +25 -0
  219. package/dist/features/shell/SectionForm.js +20 -16
  220. package/dist/features/shell/build-sections.js +14 -1
  221. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +54 -1
  222. package/dist/features/shipments/components/AdminShipmentProjectionsView.js +3 -2
  223. package/dist/features/shipments/schemas/validation.d.ts +4 -4
  224. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.d.ts +43 -0
  225. package/dist/features/shipping-boxes/repository/shipping-boxes.repository.js +90 -0
  226. package/dist/features/shipping-boxes/schemas/firestore.d.ts +82 -0
  227. package/dist/features/shipping-boxes/schemas/firestore.js +53 -0
  228. package/dist/features/sticker-trace/components/StickerCommitPanel.js +2 -1
  229. package/dist/features/sticker-trace/components/StickerPrintPanel.js +2 -1
  230. package/dist/features/sticker-trace/components/StickerRetryPanel.js +24 -4
  231. package/dist/features/sticker-trace/components/StickerTraceStudio.js +2 -1
  232. package/dist/features/store-extensions/schemas/report-create-form.d.ts +2 -2
  233. package/dist/features/store-extensions/schemas/shipping-config-form.d.ts +2 -2
  234. package/dist/features/stores/components/StoreOrdersGuideView.js +6 -1
  235. package/dist/features/stores/schemas/firestore.d.ts +71 -0
  236. package/dist/features/stores/types/index.d.ts +19 -0
  237. package/dist/features/support/schemas/index.d.ts +6 -6
  238. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +3 -2
  239. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  240. package/dist/index.d.ts +11 -2
  241. package/dist/index.js +17 -2
  242. package/dist/next/routing/route-map.d.ts +24 -0
  243. package/dist/next/routing/route-map.js +19 -0
  244. package/dist/providers/shipping-shiprocket/index.d.ts +27 -1
  245. package/dist/providers/shipping-shiprocket/index.js +84 -1
  246. package/dist/schemas/registry.d.ts +51 -22
  247. package/dist/schemas/sieve.d.ts +5 -5
  248. package/dist/server-entry.d.ts +2 -2
  249. package/dist/server-entry.js +8 -1
  250. package/dist/server.d.ts +11 -2
  251. package/dist/server.js +40 -2
  252. package/dist/styles.css +64 -80
  253. package/dist/tailwind-utilities.css +1 -1
  254. package/dist/tokens/themes/default-dark.js +1 -0
  255. package/dist/tokens/themes/default-light.js +1 -0
  256. package/dist/tokens/themes/required.d.ts +1 -1
  257. package/dist/tokens/themes/required.js +1 -0
  258. package/dist/tokens/tokens.css +15 -0
  259. package/dist/ui/components/Checkbox.js +10 -2
  260. package/dist/ui/components/DateInput.js +8 -1
  261. package/dist/ui/components/Disclaimer.d.ts +19 -0
  262. package/dist/ui/components/Disclaimer.js +74 -0
  263. package/dist/ui/components/Input.js +22 -2
  264. package/dist/ui/components/PaginatedSelect.d.ts +8 -19
  265. package/dist/ui/components/PaginatedSelect.js +43 -9
  266. package/dist/ui/components/Radio.js +8 -1
  267. package/dist/ui/components/Select.js +10 -2
  268. package/dist/ui/components/TagInput.d.ts +8 -1
  269. package/dist/ui/components/TagInput.js +4 -3
  270. package/dist/ui/components/Textarea.js +10 -2
  271. package/dist/ui/components/tabs-overflow.js +64 -11
  272. package/dist/ui/forms/FieldCheckbox.js +1 -1
  273. package/dist/ui/forms/FormErrorList.d.ts +0 -16
  274. package/dist/ui/forms/FormErrorList.js +34 -1
  275. package/dist/ui/forms/apply-zod-issues.d.ts +19 -1
  276. package/dist/ui/forms/apply-zod-issues.js +23 -1
  277. package/dist/ui/index.d.ts +2 -0
  278. package/dist/ui/index.js +6 -0
  279. package/dist/ui/rich-text/RichText.style.css +48 -79
  280. package/package.json +1 -1
  281. package/scripts/seed-cli.mjs +1831 -1822
@@ -37,6 +37,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
37
37
  */
38
38
  import React, { useCallback, useState } from "react";
39
39
  import { Badge, ConfirmDeleteModal, RowActionMenu, Span, Text } from "../../../ui/index.js";
40
+ import { apiClient } from "../../../http/index.js";
40
41
  import { useEntityDelete } from "../../../react/hooks/useEntityDelete.js";
41
42
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
42
43
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
@@ -46,6 +47,7 @@ import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
46
47
  import { SIEVE_OP, sieveFilter } from "../../../utils/sieve-builder.js";
47
48
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
48
49
  import { toCurrency, toRecordArray, toRelativeDate, toStringValue, } from "../../admin/hooks/useAdminListingData.js";
50
+ import { useOptimisticRowFlag } from "../../admin/hooks/useOptimisticRowFlag.js";
49
51
  import { DataListingView } from "../../admin/components/DataListingView.js";
50
52
  import { ROW_ACTION_META, SELLER_BULK_ACTIONS } from "../../products/constants/action-defs.js";
51
53
  import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope.js";
@@ -204,6 +206,17 @@ export function SellerListingTypeView({ type, onCreateClick, onEditClick, onDele
204
206
  await performDelete(id);
205
207
  setDeleteTargetId(null);
206
208
  }, [performDelete]);
209
+ /*
210
+ * Optimistic status, via the shared hook — see its header for why a
211
+ * rollback must DELETE the override key rather than write back a captured
212
+ * value, and why the key is cleared on success too.
213
+ */
214
+ const statusFlag = useOptimisticRowFlag({
215
+ mutate: (id, next) => apiClient.patch(SELLER_ENDPOINTS.PRODUCT_BY_ID(id), { status: next }),
216
+ invalidate: [["seller", plugin.tabSlug]],
217
+ successMessage: (next) => next === "published" ? "Listing published." : "Listing unpublished.",
218
+ errorMessage: "Could not change that listing's status.",
219
+ });
207
220
  const handleEdit = useCallback((id) => {
208
221
  if (onEditClick)
209
222
  onEditClick(id);
@@ -233,7 +246,15 @@ export function SellerListingTypeView({ type, onCreateClick, onEditClick, onDele
233
246
  {
234
247
  key: "status",
235
248
  header: "Status",
236
- render: (row) => (_jsx(Badge, { variant: row.status === "active" ? "active" : "inactive", size: "xs", className: "capitalize", children: row.status })),
249
+ /*
250
+ * Reads the optimistic override so the badge and the row action agree
251
+ * the instant one is used — otherwise the menu would say "Unpublish"
252
+ * while the badge beside it still said published, until the refetch.
253
+ */
254
+ render: (row) => {
255
+ const status = statusFlag.resolve(row.id, row.status);
256
+ return (_jsx(Badge, { variant: status === "active" || status === "published" ? "active" : "inactive", size: "xs", className: "capitalize", children: status }));
257
+ },
237
258
  },
238
259
  textCell("createdAt", spec.createdHeader ?? "Created"),
239
260
  ];
@@ -291,15 +312,40 @@ export function SellerListingTypeView({ type, onCreateClick, onEditClick, onDele
291
312
  },
292
313
  }))
293
314
  : undefined,
294
- renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
295
- { label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
296
- {
297
- label: ACTIONS.STORE["delete-listing"].label,
298
- destructive: true,
299
- onClick: () => setDeleteTargetId(row.id),
300
- disabled: deletingId === row.id,
301
- },
302
- ] })),
315
+ renderRowActions: (row) => {
316
+ const isPublished = statusFlag.resolve(row.id, row.status) === "published";
317
+ return (_jsx(RowActionMenu, { actions: [
318
+ { label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
319
+ /*
320
+ * 🛑 This one entry lights up SIX seller pages — art, classified,
321
+ * digital codes, live, spares and stickers are each a ~25-line
322
+ * wrapper around this component.
323
+ *
324
+ * `status` was a read-only Badge on all of them, so changing one
325
+ * listing from published to draft meant opening the full editor,
326
+ * even though `PATCH /api/store/products/[id]` has always taken
327
+ * `status` and SellerProductsView already drives that exact call.
328
+ *
329
+ * `ACTIONS.STORE["publish-listing"]` / `["unpublish-listing"]`
330
+ * were defined with ZERO call sites — this is what finally wires
331
+ * them, rather than reinventing the labels inline.
332
+ */
333
+ {
334
+ label: isPublished
335
+ ? ACTIONS.STORE["unpublish-listing"].label
336
+ : ACTIONS.STORE["publish-listing"].label,
337
+ separator: true,
338
+ disabled: statusFlag.pendingId === row.id,
339
+ onClick: () => { void statusFlag.set(row.id, isPublished ? "draft" : "published"); },
340
+ },
341
+ {
342
+ label: ACTIONS.STORE["delete-listing"].label,
343
+ destructive: true,
344
+ onClick: () => setDeleteTargetId(row.id),
345
+ disabled: deletingId === row.id,
346
+ },
347
+ ] }));
348
+ },
303
349
  };
304
350
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: `Delete ${spec.deleteLabel}`, message: `Are you sure you want to delete this ${spec.deleteLabel.toLowerCase()}? This cannot be undone.`, onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
305
351
  }
@@ -2,7 +2,12 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { formatPhysicalLocation } from "../../../utils/physical-location.js";
4
4
  import { OrderAddonBadges } from "../../orders/components/OrderAddonBadges.js";
5
+ import { OrderProvenanceCard } from "../../orders/components/OrderProvenanceCard.js";
6
+ import { groupOrderItemsByLine } from "../../orders/utils/bundle-grouping.js";
5
7
  import { normalizeError } from "../../../errors/normalize.js";
8
+ // The defining module, not a barrel (Root Cause #18). Maps a server error CODE
9
+ // to user-facing copy; it can never return server text, which is the point.
10
+ import { toUserMessage } from "../../../errors/error-display-map.js";
6
11
  import { Row, SIEVE_OP, sieveFilter } from "@mohasinac/appkit/client";
7
12
  import { sortBy } from "@mohasinac/appkit/client";
8
13
  import React, { useState, useCallback } from "react";
@@ -12,6 +17,7 @@ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
12
17
  import { Badge, Button, Div, FilterChipGroup, SideDrawer, Span, Stack, Text, useToast } from "../../../ui/index.js";
13
18
  import { SELLER_ORDER_STATUS_TABS } from "../../admin/constants/filter-tabs.js";
14
19
  import { useOrderScope } from "../../orders/components/OrderScopeTabs.js";
20
+ import { ORDER_LANE_TABS } from "../../orders/constants/order-lane-tabs.js";
15
21
  import { OrderStatusValues } from "../../orders/schemas/firestore.js";
16
22
  import { isManualPaymentMethod } from "../../orders/constants/payment-window.js";
17
23
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
@@ -60,6 +66,14 @@ const UPDATE_STATUS_OPTIONS = [
60
66
  { value: "delivered", label: "Delivered" },
61
67
  { value: "cancelled", label: "Cancelled" },
62
68
  ];
69
+ /**
70
+ * One item row. Extracted verbatim when grouping landed so the single and
71
+ * grouped paths cannot render differently — two copies of this markup is how
72
+ * the field-name bug below would come back on one path only.
73
+ */
74
+ function sellerOrderItemRow(item, key) {
75
+ return (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Row, { align: "center", gap: "sm", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title ?? "Order item", size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.productTitle ?? item.title ?? item.productId ?? "Item" }), _jsxs(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: ["Qty: ", item.quantity ?? 1] })] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "medium", children: toCurrency(item.totalPrice ?? item.unitPrice ?? item.price ?? 0) })] }, key));
76
+ }
63
77
  /**
64
78
  * Manual-payment state for the seller's read-only badge. Derived from the same
65
79
  * two fields the admin queue uses (`paymentProofUrl` + `paymentReviewOutcome`)
@@ -198,8 +212,33 @@ export function SellerOrderDetailPanel({ orderId, apiBase, onClose, }) {
198
212
  body: JSON.stringify(payload),
199
213
  });
200
214
  if (!res.ok) {
201
- const d = await res.json().catch(() => ({}));
202
- throw new Error(d?.error ?? "Failed to update order");
215
+ const d = (await res.json().catch(() => ({})));
216
+ /*
217
+ * 🛑 FIELD ERRORS GO ON THE FIELD. The server already sends them.
218
+ *
219
+ * `mapToHttpError` answers a rejected body with
220
+ * `{ status: 400, code: VALIDATION_FAILED, message: "Validation
221
+ * failed", issues }` — the per-field detail is ON THE WIRE. This
222
+ * handler used to throw `d.error` and render it in the banner below,
223
+ * so the seller saw a bare "Validation failed" with nothing marked and
224
+ * no way to tell WHICH field was wrong. Rule #9 §6.
225
+ *
226
+ * `applyZodIssues` keys by the full dotted path, so a nested field
227
+ * lands on itself rather than collapsing onto `issue.path[0]`.
228
+ */
229
+ if (Array.isArray(d.issues) && d.issues.length > 0) {
230
+ applyZodIssues(d.issues, form.setFieldError);
231
+ return;
232
+ }
233
+ /*
234
+ * 🛑 NEVER the server's own message, and this is not hypothetical.
235
+ * `d.error` is server text; rendering it is how a Node
236
+ * `MODULE_NOT_FOUND` with a `/var/task/.next/...` require stack was
237
+ * once shown inside a buyer's "Place your bid" modal (Root Cause #86).
238
+ * `toUserMessage` resolves through `errors.codes.*`, terminates at
239
+ * UNKNOWN, and cannot return server internals.
240
+ */
241
+ throw new Error(toUserMessage(d.code));
203
242
  }
204
243
  const updated = await res.json();
205
244
  const next = (updated?.data ?? updated);
@@ -217,8 +256,19 @@ export function SellerOrderDetailPanel({ orderId, apiBase, onClose, }) {
217
256
  });
218
257
  }
219
258
  catch (err) {
220
- void normalizeError(err);
221
- setSaveError(err.message);
259
+ /*
260
+ * `normalizeError`'s RESULT, not an `as Error` cast. The cast asserted a
261
+ * shape nothing checked, and the only reason it happened to work is that
262
+ * every throw above is one we construct — a rejected promise from `fetch`
263
+ * or a JSON parse failure is not an `Error` by that assertion's logic,
264
+ * it just happens to be one at runtime.
265
+ *
266
+ * What reaches the banner is now bounded: either `toUserMessage(code)`
267
+ * from the throw above (mapped copy, never server text) or a browser-level
268
+ * network message. Field-level problems never get here at all — they were
269
+ * routed to the fields by `applyZodIssues`.
270
+ */
271
+ setSaveError(normalizeError(err).message);
222
272
  }
223
273
  finally {
224
274
  setSaving(false);
@@ -249,21 +299,21 @@ export function SellerOrderDetailPanel({ orderId, apiBase, onClose, }) {
249
299
  const addrLine = typeof rawAddr === "string" && rawAddr.trim()
250
300
  ? rawAddr.trim()
251
301
  : [addr.addressLine1, addr.city, addr.state, addr.pincode].filter(Boolean).join(", ");
252
- 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 && (_jsxs(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) })] }), (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: (order.items ?? []).map((item, i) => (_jsxs(Row, { paddingY: "y-xs-tall", padding: "x-sm", align: "center", justify: "between", gap: "3", children: [_jsxs(Row, { align: "center", gap: "sm", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: item.image, alt: item.title ?? "Order item", size: "thumbnail" }) }), _jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { size: "sm", className: "truncate", weight: "medium", children: item.productTitle ?? item.title ?? item.productId ?? "Item" }), _jsxs(Text, { size: "xs", className: "text-[var(--appkit-color-text-muted)]", children: ["Qty: ", item.quantity ?? 1] })] })] }), _jsx(Text, { size: "sm", className: "shrink-0", weight: "medium", children: toCurrency(item.totalPrice ?? item.unitPrice ?? item.price ?? 0) })] }, i))) })] })), _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
253
- ? `Paid ${toRelativeDate(inst.paidAt)}`
254
- : inst.dueDate
255
- ? `Due ${toRelativeDate(inst.dueDate)}`
256
- : "" })] }), _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: () => {
257
- // SectionForm scrolls to the first erroring section and then
258
- // submits regardless, so the guard lives here.
259
- form.clearErrors();
260
- const parsed = sellerOrderUpdateSchema.safeParse(visibleValues(sellerOrderUpdateSchema, draft));
261
- if (!parsed.success) {
262
- applyZodIssues(parsed.error.issues, form.setFieldError);
263
- return;
264
- }
265
- void handleSave();
266
- }, onValidationChange: () => form.validate(draft), schema: sellerOrderUpdateSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, submitLabel: "Save", cancelLabel: "Close", onCancel: onClose, isLoading: saving })] }), saveError && (_jsx(Div, { textSize: "xs", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError }))] })] }), _jsxs(Row, { border: "top", paddingY: "y-sm-tall", padding: "x-md", align: "center", justify: "end", gap: "3", children: [_jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: "Close" }), _jsx(Button, { onClick: handleSave, isLoading: saving, disabled: saving, children: "Save" })] })] }))] }));
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
303
+ ? `Paid ${toRelativeDate(inst.paidAt)}`
304
+ : inst.dueDate
305
+ ? `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: () => {
307
+ // SectionForm scrolls to the first erroring section and then
308
+ // submits regardless, so the guard lives here.
309
+ form.clearErrors();
310
+ const parsed = sellerOrderUpdateSchema.safeParse(visibleValues(sellerOrderUpdateSchema, draft));
311
+ if (!parsed.success) {
312
+ applyZodIssues(parsed.error.issues, form.setFieldError);
313
+ return;
314
+ }
315
+ void handleSave();
316
+ }, onValidationChange: () => form.validate(draft), schema: sellerOrderUpdateSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, submitLabel: "Save", cancelLabel: "Close", onCancel: onClose, isLoading: saving })] }), saveError && (_jsx(Div, { textSize: "xs", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError }))] })] }) }))] }));
267
317
  }
268
318
  function OrderDetailDrawer({ orderId, apiBase, onClose, }) {
269
319
  return (_jsx(SideDrawer, { isOpen: true, title: `Order ${orderId}`, onClose: onClose, children: _jsx(SellerOrderDetailPanel, { orderId: orderId, apiBase: apiBase, onClose: onClose }) }));
@@ -343,9 +393,7 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
343
393
  {
344
394
  key: "primary",
345
395
  header: "Order",
346
- render: (row) => (_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: row.itemImage, alt: row.itemTitle ?? "Order item", size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "semibold", children: row.itemTitle
347
- ? `${row.itemTitle}${row.itemCount > 1 ? ` +${row.itemCount - 1} more` : ""}`
348
- : row.primary }), _jsxs(Text, { size: "xs", color: "muted", children: [row.buyerName, " \u00B7 ", row.itemCount, " item", row.itemCount !== 1 ? "s" : ""] })] })] })),
396
+ render: (row) => (_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0", children: [_jsx(Div, { className: "h-10 w-10 shrink-0", rounded: "md", overflow: "hidden", children: _jsx(MediaImage, { src: row.itemImage, alt: row.itemTitle ?? "Order item", size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { className: "truncate", size: "sm", weight: "semibold", children: row.primary }), _jsxs(Text, { size: "xs", color: "muted", children: [row.buyerName, " \u00B7 ", row.itemCount, " item", row.itemCount !== 1 ? "s" : ""] })] })] })),
349
397
  },
350
398
  {
351
399
  key: "totalAmount",
@@ -392,7 +440,7 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
392
440
  commit: "debounce",
393
441
  },
394
442
  emptyLabel: "No orders yet",
395
- filterKeys: ["status"],
443
+ filterKeys: ["status", "orderType"],
396
444
  defaultSort: DEFAULT_SORT,
397
445
  queryKey: ["seller", "orders", "listing"],
398
446
  endpoint: SELLER_ENDPOINTS.ORDERS,
@@ -425,10 +473,45 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
425
473
  * below as `itemTitle`.
426
474
  */
427
475
  primary: (() => {
428
- const title = typeof firstItem.productTitle === "string" ? firstItem.productTitle : "";
476
+ /*
477
+ * Counted in LINES, not item rows, and a grouped line is named by
478
+ * its own title.
479
+ *
480
+ * This was `items[0].productTitle` + `itemsArr.length - 1`, so a
481
+ * four-member bundle read "Dranzer S +3 more" — an arbitrary
482
+ * member, implying four separate purchases. Grouping through the
483
+ * same helper the drawer now uses makes it "Starter Bundle", and a
484
+ * bundle beside one standalone item "+1 more".
485
+ */
486
+ const groups = groupOrderItemsByLine(itemsArr
487
+ .filter((entry) => Boolean(entry) && typeof entry === "object")
488
+ /*
489
+ * Narrowed, not cast: `Record<string, unknown>` does NOT
490
+ * satisfy `OrderItemForLineGrouping` — its `groupSlug` is
491
+ * `unknown`, which is not assignable to `string | undefined`.
492
+ * A cast would compile and then group on whatever junk a bad
493
+ * document carried.
494
+ */
495
+ .map((e) => ({
496
+ productTitle: typeof e.productTitle === "string" ? e.productTitle : undefined,
497
+ groupSlug: typeof e.groupSlug === "string" ? e.groupSlug : undefined,
498
+ groupTitle: typeof e.groupTitle === "string" ? e.groupTitle : undefined,
499
+ bundleCategorySlug: typeof e.bundleCategorySlug === "string" ? e.bundleCategorySlug : undefined,
500
+ bundleProductIds: Array.isArray(e.bundleProductIds)
501
+ ? e.bundleProductIds.filter((v) => typeof v === "string")
502
+ : undefined,
503
+ })));
504
+ const head = groups[0];
505
+ const extra = Math.max(0, groups.length - 1);
506
+ const title = head === undefined
507
+ ? typeof firstItem.productTitle === "string"
508
+ ? firstItem.productTitle
509
+ : ""
510
+ : head.kind === "bundle"
511
+ ? (head.groupTitle ?? "Grouped purchase")
512
+ : (head.item.productTitle ?? "");
429
513
  if (!title)
430
514
  return `Order ${toStringValue(item.id, "-")}`;
431
- const extra = itemsArr.length - 1;
432
515
  return extra > 0 ? `${title} +${extra} more` : title;
433
516
  })(),
434
517
  /*
@@ -456,10 +539,29 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
456
539
  // The scope narrows; the status chip drills down inside it. Both are
457
540
  // emitted — an explicit status always wins, since picking "Delivered"
458
541
  // while sitting on Active should show delivered orders, not nothing.
459
- buildFilters: (state) => (state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined),
542
+ /*
543
+ * Two independent axes, joined. The lane answers "how was this won", the
544
+ * status answers "where is it now" — a seller asking "which of my auction
545
+ * wins are still unshipped" should not have to choose between them.
546
+ *
547
+ * 🛑 `orderType` is a registered SELLER_SIEVE_FIELD as of this change. It
548
+ * was not, and the failure would have been invisible: `throwExceptions` is
549
+ * false, so an unallowlisted field drops its clause and the chip would have
550
+ * returned the seller's whole order list while looking like it filtered
551
+ * (Root Cause #62). `standard` is stripped from the query server-side and
552
+ * refined in memory — see `/api/store/orders`.
553
+ */
554
+ buildFilters: (state) => [
555
+ state.status && state.status !== "All"
556
+ ? sieveFilter("status", SIEVE_OP.EQ, state.status)
557
+ : null,
558
+ state.orderType ? sieveFilter("orderType", SIEVE_OP.EQ, state.orderType) : null,
559
+ ]
560
+ .filter(Boolean)
561
+ .join(",") || undefined,
460
562
  buildExtraParams: () => orderScope.extraParams,
461
563
  renderAboveContent: orderScope.renderAboveContent,
462
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
564
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(_Fragment, { children: [_jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }), _jsx(FilterChipGroup, { label: "Type", tabs: ORDER_LANE_TABS, value: pendingFilters.orderType ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, orderType: id })) })] })),
463
565
  buildBulkActions: (selection) => {
464
566
  const handlePrintPackingSlips = () => {
465
567
  const ids = selection.selectedIds.join(",");
@@ -8,6 +8,8 @@ import { Badge, ConfirmDeleteModal, Div, Row, RowActionMenu, Span, Text, useToas
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
9
9
  import { ROUTES } from "../../../constants/index.js";
10
10
  import { toRecordArray, toStringValue } from "../hooks/useSellerListingData.js";
11
+ import { useOptimisticRowFlag } from "../../admin/hooks/useOptimisticRowFlag.js";
12
+ import { apiClient } from "../../../http/index.js";
11
13
  import { DataListingView } from "../../admin/components/DataListingView.js";
12
14
  const CLS_TYPE_PILL = "inline-flex items-center rounded-full px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-semibold uppercase tracking-wide bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300";
13
15
  const DEFAULT_SORT = "-createdAt";
@@ -42,6 +44,22 @@ const COLUMNS = [
42
44
  },
43
45
  ];
44
46
  export function SellerPayoutMethodsView({ onCreateClick, onEditClick, onDelete, onSetDefault, }) {
47
+ /*
48
+ * 🛑 `isActive` was a READ-ONLY badge here, and the update schema was built
49
+ * to take it on its own — shipping-config's even says so in a comment: "a
50
+ * PATCH may legitimately carry only `isActive`". Nothing in the client had
51
+ * ever sent one, so flipping one boolean meant a full-editor round trip.
52
+ *
53
+ * The optimistic/rollback logic lives in the hook because eight listings
54
+ * grew this control at once; see its header for why rollback must DELETE
55
+ * the override key rather than write back a captured value.
56
+ */
57
+ const activeFlag = useOptimisticRowFlag({
58
+ mutate: (id, next) => apiClient.patch(SELLER_ENDPOINTS.PAYOUT_METHOD_BY_ID(id), { isActive: next }),
59
+ invalidate: [["seller", "payout-methods", "listing"]],
60
+ successMessage: (next) => (next ? "Payout method activated." : "Payout method deactivated."),
61
+ errorMessage: "Could not change that payout method's status.",
62
+ });
45
63
  const [deleteTargetId, setDeleteTargetId] = useState(null);
46
64
  const { deletingId, handleDelete: performDelete } = useEntityDelete({
47
65
  endpoint: SELLER_ENDPOINTS.PAYOUT_METHOD_BY_ID,
@@ -114,13 +132,27 @@ export function SellerPayoutMethodsView({ onCreateClick, onEditClick, onDelete,
114
132
  label: String(item.label ?? ""),
115
133
  type: String(item.type ?? ""),
116
134
  isDefault: Boolean(item.isDefault),
117
- isActive: Boolean(item.isActive),
135
+ // Resolved through the pending override so the Status badge and the
136
+ // row toggle cannot disagree between the click and the refetch.
137
+ isActive: activeFlag.resolve(toStringValue(item.id, `pm-${index}`), Boolean(item.isActive)),
118
138
  maskedIdentifier: getMaskedIdentifier(item),
119
139
  })),
120
140
  getTotal: (response, mappedRows) => (typeof response.total === "number" ? response.total : mappedRows.length),
121
141
  buildFilters: () => undefined,
122
142
  primaryAction: { label: "New Method", onClick: handleCreate },
123
143
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
144
+ /*
145
+ * 🛑 `isActive` was a read-only badge and the update schema was
146
+ * built to accept it alone. A menu entry rather than a switch here,
147
+ * to match the other actions in this menu — the menu is already
148
+ * click-stopped in all three renderers.
149
+ */
150
+ {
151
+ label: row.isActive ? "Deactivate" : "Activate",
152
+ separator: true,
153
+ disabled: activeFlag.pendingId === row.id,
154
+ onClick: () => { void activeFlag.set(row.id, !row.isActive); },
155
+ },
124
156
  ...(!row.isDefault
125
157
  ? [{ label: "Set as Default", disabled: settingDefaultId === row.id, onClick: () => handleSetDefault(row.id) }]
126
158
  : []),
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useCallback, useEffect, useMemo, useState } from "react";
4
4
  import { z } from "zod";
5
5
  import { Alert, Badge, Checkbox, Div, FormField, FormGroup, Heading, Label, Row, Span, Stack, Text, Toggle } from "../../../ui/index.js";
@@ -8,6 +8,7 @@ import { FormShellContext, useFormShellState, applyZodIssues, FormErrorSummary }
8
8
  import { SectionForm, useSectionFormNav, annotate } from "../../shell/index.js";
9
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
10
10
  import { normalizeError } from "../../../errors/normalize.js";
11
+ import { toUserMessage } from "../../../errors/error-display-map.js";
11
12
  const __P = {
12
13
  p3: "p-[var(--appkit-space-3)]",
13
14
  };
@@ -153,6 +154,20 @@ const DEFAULT_DRAFT = {
153
154
  */
154
155
  advancedListingTypesEnabled: false,
155
156
  };
157
+ /**
158
+ * One radio group for a single-value field, with ONE error message.
159
+ *
160
+ * Extracted because the payout-method and account-type groups were the same
161
+ * eleven lines twice, and because adding the error handling to both tipped
162
+ * this view past `audit-code-quality`'s component-size threshold. The error
163
+ * placement is the part worth sharing: `aria-invalid` goes on every radio so
164
+ * assistive tech marks the whole group, but the message renders ONCE
165
+ * underneath — the field is one value, so an `error` per radio would print
166
+ * and announce the same sentence for every option.
167
+ */
168
+ function RadioFieldGroup({ name, value, options, error, disabled, onSelect, renderOption, layout: Layout, }) {
169
+ return (_jsxs(_Fragment, { children: [_jsx(Layout, { gap: "sm", children: options.map((option) => (_jsxs(Label, { layout: "flex", gap: "md", className: "cursor-pointer", children: [_jsx(Checkbox, { bare: true, type: "radio", name: name, value: option, checked: value === option, onChange: () => onSelect(option), className: "accent-[var(--appkit-color-primary)]", disabled: disabled, "aria-invalid": error ? "true" : undefined }), renderOption(option)] }, option))) }), error && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: error }))] }));
170
+ }
156
171
  export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SETTINGS }) {
157
172
  const [draft, setDraft] = useState(DEFAULT_DRAFT);
158
173
  const [current, setCurrent] = useState(null);
@@ -253,7 +268,22 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
253
268
  if (Array.isArray(json?.issues) && json.issues.length > 0) {
254
269
  applyZodIssues(json.issues, setFieldError);
255
270
  }
256
- setError(json?.error ?? "Failed to save");
271
+ /*
272
+ * `json.error` is the SERVER's sentence — never shown to a user
273
+ * (Rule #9.6). Resolve the stable `code` instead; `toUserMessage`
274
+ * terminates in this fallback and can never return server text.
275
+ *
276
+ * Only a banner when the issues above attached nothing: if they did,
277
+ * the fields now carry the detail and a banner repeating a vaguer
278
+ * version of it is noise.
279
+ */
280
+ const attached = Array.isArray(json?.issues) &&
281
+ json.issues.some((i) => i?.path?.length);
282
+ if (!attached) {
283
+ setError(toUserMessage(json?.code, undefined, {
284
+ fallback: "We couldn't save your payout settings.",
285
+ }));
286
+ }
257
287
  return;
258
288
  }
259
289
  clearErrors();
@@ -263,8 +293,17 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
263
293
  setDraft((prev) => ({ ...prev, accountNumber: "" }));
264
294
  }
265
295
  catch (err) {
266
- void normalizeError(err);
267
- setError(err.message);
296
+ /*
297
+ * `normalizeError` was already being called and its result DISCARDED,
298
+ * then the raw `.message` shown anyway — a thrown value's message is
299
+ * written for a developer, and this is the path a network failure or a
300
+ * bundler fault takes (Root Cause #86 rendered a require stack this
301
+ * way). Use the normalized code; the sentence is ours.
302
+ */
303
+ const normalized = normalizeError(err);
304
+ setError(toUserMessage(normalized.code, undefined, {
305
+ fallback: "We couldn't save your payout settings. Please try again.",
306
+ }));
268
307
  }
269
308
  finally {
270
309
  setSaving(false);
@@ -283,12 +322,12 @@ export function SellerPayoutSettingsView({ apiBase = SELLER_ENDPOINTS.PAYOUT_SET
283
322
  required: true,
284
323
  quick: true,
285
324
  fields: ["method", "upiId", "accountHolderName", "accountNumber", "ifscCode", "bankName", "accountType"],
286
- render: ({ values, onChange }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Method" }), current && (_jsx(Row, { className: "mb-2", align: "center", gap: "sm", children: _jsx(Badge, { variant: current.isConfigured ? "success" : "warning", children: current.isConfigured ? "Payout configured" : "Not configured" }) })), current?.isConfigured && (_jsx(Alert, { variant: "info", children: current.method === "upi"
325
+ render: ({ values, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 3, className: "mb-2", children: "Payout Method" }), current && (_jsx(Row, { className: "mb-2", align: "center", gap: "sm", children: _jsx(Badge, { variant: current.isConfigured ? "success" : "warning", children: current.isConfigured ? "Payout configured" : "Not configured" }) })), current?.isConfigured && (_jsx(Alert, { variant: "info", children: current.method === "upi"
287
326
  ? `Current UPI: ${current.upiId}`
288
- : `Current bank: ${current.bankAccount?.bankName ?? ""} — ••••${current.bankAccount?.accountNumberMasked?.slice(-4) ?? "••••"} (${current.bankAccount?.accountType})` })), _jsx(Stack, { gap: "sm", children: ([
289
- { value: "upi", label: "UPI", desc: "Instant payouts via UPI VPA (e.g. name@upi)." },
290
- { value: "bank_transfer", label: "Bank Transfer", desc: "NEFT/RTGS to your bank account within 2–3 business days." },
291
- ]).map(({ value, label, desc }) => (_jsxs("label", { className: "flex items-center gap-[var(--appkit-space-3)] p-[var(--appkit-space-3)] rounded-lg border border-[var(--appkit-color-border)] cursor-pointer has-[:checked]:border-[var(--appkit-color-primary)] has-[:checked]:bg-[var(--appkit-color-primary)]/5", children: [_jsx(Checkbox, { bare: true, type: "radio", name: "payoutMethod", value: value, checked: values.method === value, onChange: () => onChange({ method: value }), className: "accent-[var(--appkit-color-primary)]", disabled: busy }), _jsxs(Div, { children: [_jsx(Text, { weight: "medium", children: label }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: desc })] })] }, value))) }), values.method === "upi" && (_jsx(FormField, { name: "upiId", label: "UPI ID (VPA)", type: "text", value: values.upiId, onChange: (v) => onChange({ upiId: v }), placeholder: "yourname@upi", helpText: "Ensure this VPA is registered and active.", disabled: busy })), values.method === "bank_transfer" && (_jsxs(Stack, { gap: "md", className: "mt-2", children: [_jsx(FormField, { name: "accountHolderName", label: "Account Holder Name", type: "text", value: values.accountHolderName, onChange: (v) => onChange({ accountHolderName: v }), placeholder: "Name as on bank account", disabled: busy }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "accountNumber", label: "Account Number", type: "text", value: values.accountNumber, onChange: (v) => onChange({ accountNumber: v }), placeholder: "Enter full account number", helpText: current?.bankAccount ? `Saved: ••••${current.bankAccount.accountNumberMasked?.slice(-4) ?? "••••"}` : "Stored securely, never displayed in full.", disabled: busy }), _jsx(FormField, { name: "ifscCode", label: "IFSC Code", type: "text", value: values.ifscCode, onChange: (v) => onChange({ ifscCode: v.toUpperCase() }), placeholder: "e.g. SBIN0001234", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "bankName", label: "Bank Name", type: "text", value: values.bankName, onChange: (v) => onChange({ bankName: v }), placeholder: "e.g. State Bank of India", disabled: busy }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-1.5", size: "sm", weight: "medium", children: "Account Type" }), _jsx(Row, { gap: "xs", children: ["savings", "current"].map((t) => (_jsxs(Label, { layout: "flex", gap: "md", className: "cursor-pointer", children: [_jsx(Checkbox, { bare: true, type: "radio", name: "accountType", value: t, checked: values.accountType === t, onChange: () => onChange({ accountType: t }), className: "accent-[var(--appkit-color-primary)]", disabled: busy }), _jsx(Text, { size: "sm", transform: "capitalize", children: t })] }, t))) })] })] })] }))] })),
327
+ : `Current bank: ${current.bankAccount?.bankName ?? ""} — ••••${current.bankAccount?.accountNumberMasked?.slice(-4) ?? "••••"} (${current.bankAccount?.accountType})` })), _jsxs(Stack, { gap: "sm", children: [([
328
+ { value: "upi", label: "UPI", desc: "Instant payouts via UPI VPA (e.g. name@upi)." },
329
+ { value: "bank_transfer", label: "Bank Transfer", desc: "NEFT/RTGS to your bank account within 2–3 business days." },
330
+ ]).map(({ value, label, desc }) => (_jsxs("label", { className: "flex items-center gap-[var(--appkit-space-3)] p-[var(--appkit-space-3)] rounded-lg border border-[var(--appkit-color-border)] cursor-pointer has-[:checked]:border-[var(--appkit-color-primary)] has-[:checked]:bg-[var(--appkit-color-primary)]/5", children: [_jsx(Checkbox, { bare: true, type: "radio", name: "payoutMethod", value: value, checked: values.method === value, onChange: () => onChange({ method: value }), className: "accent-[var(--appkit-color-primary)]", disabled: busy, "aria-invalid": errors.method ? "true" : undefined }), _jsxs(Div, { children: [_jsx(Text, { weight: "medium", children: label }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: desc })] })] }, value))), errors.method && (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.method }))] }), values.method === "upi" && (_jsx(FormField, { name: "upiId", label: "UPI ID (VPA)", type: "text", value: values.upiId, onChange: (v) => onChange({ upiId: v }), placeholder: "yourname@upi", helpText: "Ensure this VPA is registered and active.", disabled: busy })), values.method === "bank_transfer" && (_jsxs(Stack, { gap: "md", className: "mt-2", children: [_jsx(FormField, { name: "accountHolderName", label: "Account Holder Name", type: "text", value: values.accountHolderName, onChange: (v) => onChange({ accountHolderName: v }), placeholder: "Name as on bank account", disabled: busy }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "accountNumber", label: "Account Number", type: "text", value: values.accountNumber, onChange: (v) => onChange({ accountNumber: v }), placeholder: "Enter full account number", helpText: current?.bankAccount ? `Saved: ••••${current.bankAccount.accountNumberMasked?.slice(-4) ?? "••••"}` : "Stored securely, never displayed in full.", disabled: busy }), _jsx(FormField, { name: "ifscCode", label: "IFSC Code", type: "text", value: values.ifscCode, onChange: (v) => onChange({ ifscCode: v.toUpperCase() }), placeholder: "e.g. SBIN0001234", disabled: busy })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "bankName", label: "Bank Name", type: "text", value: values.bankName, onChange: (v) => onChange({ bankName: v }), placeholder: "e.g. State Bank of India", disabled: busy }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-1.5", size: "sm", weight: "medium", children: "Account Type" }), _jsx(RadioFieldGroup, { name: "accountType", value: values.accountType, options: ["savings", "current"], error: errors.accountType, disabled: busy, onSelect: (t) => onChange({ accountType: t }), layout: Row, renderOption: (t) => (_jsx(Text, { size: "sm", transform: "capitalize", children: t })) })] })] })] }))] })),
292
331
  },
293
332
  {
294
333
  id: "tax",
@@ -641,7 +641,11 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
641
641
  : `/products/${row.id}`,
642
642
  renderRowActions: (row) => {
643
643
  const isPublished = (statusOverrides.get(row.id) ?? row.status) === "published";
644
- return (_jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); handleEdit(row); }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void openAssembly(row, "assemble"); }, "aria-label": ACTIONS.STORE["assemble-from-parts"].ariaLabel, title: ACTIONS.STORE["assemble-from-parts"].label, disabled: assembleLoading, children: _jsx(Wrench, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void openAssembly(row, "part-out"); }, "aria-label": ACTIONS.STORE["part-out-listing"].ariaLabel, title: ACTIONS.STORE["part-out-listing"].label, disabled: assembleLoading, children: _jsx(Scissors, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleTogglePublish(row); }, "aria-label": isPublished ? "Unpublish" : "Publish", title: isPublished ? "Unpublish" : "Publish", disabled: publishingId === row.id, children: isPublished ? _jsx(EyeOff, { className: "w-4 h-4" }) : _jsx(Eye, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleDuplicate(row); }, "aria-label": "Duplicate listing", title: "Duplicate", children: "\u29C9" }), onDeleteProduct && (_jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); void handleDelete(row); }, disabled: deletingId === row.id, className: "text-[var(--appkit-color-error)] hover:bg-[var(--appkit-color-border-subtle)]", children: _jsx(Trash2, { className: "w-4 h-4" }) }))] }));
644
+ return (_jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); handleEdit(row); }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void openAssembly(row, "assemble"); }, "aria-label": ACTIONS.STORE["assemble-from-parts"].ariaLabel, title: ACTIONS.STORE["assemble-from-parts"].label, disabled: assembleLoading, children: _jsx(Wrench, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void openAssembly(row, "part-out"); }, "aria-label": ACTIONS.STORE["part-out-listing"].ariaLabel, title: ACTIONS.STORE["part-out-listing"].label, disabled: assembleLoading, children: _jsx(Scissors, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleTogglePublish(row); }, "aria-label": isPublished
645
+ ? ACTIONS.STORE["unpublish-listing"].label
646
+ : ACTIONS.STORE["publish-listing"].label, title: isPublished
647
+ ? ACTIONS.STORE["unpublish-listing"].description
648
+ : ACTIONS.STORE["publish-listing"].description, disabled: publishingId === row.id, children: isPublished ? _jsx(EyeOff, { className: "w-4 h-4" }) : _jsx(Eye, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleDuplicate(row); }, "aria-label": "Duplicate listing", title: "Duplicate", children: "\u29C9" }), onDeleteProduct && (_jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); void handleDelete(row); }, disabled: deletingId === row.id, className: "text-[var(--appkit-color-error)] hover:bg-[var(--appkit-color-border-subtle)]", children: _jsx(Trash2, { className: "w-4 h-4" }) }))] }));
645
649
  },
646
650
  };
647
651
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), setLocationOpen && (_jsx(PhysicalLocationModal, { count: selectedIdsForLocation.length, onSave: (loc) => handleSetLocation(loc, selectedIdsForLocation), onClose: () => setSetLocationOpen(false) })), setStockOpen && (_jsx(BulkStockModal, { count: selectedIdsForStock.length, busy: stockBusy, onSave: (qty) => handleSetStock(qty, selectedIdsForStock), onClose: () => setSetStockOpen(false) })), assembleFor && assembleData && (_jsx(AssemblyDrawer, { isOpen: true,