@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
@@ -236,6 +236,28 @@ export function OwnerQuickActions({ resource, editHref, onDelete, onClone, owner
236
236
  * πŸ›‘ `inline-flex`, never `w-full`. In normal flow this must shrink to its
237
237
  * own content β€” a full-width bar would read as a section divider and would
238
238
  * stretch across a card body it is only a corner of.
239
+ *
240
+ * πŸ›‘ `shrink-0` IS THE MOBILE FIX, and it is one word because the bug was
241
+ * one missing word.
242
+ *
243
+ * A flex item's default is `flex-shrink: 1`, so in the title/actions row
244
+ * this toolbar lives in β€” `<Row justify="between">` with an `<h1>` on the
245
+ * left β€” the heading and the toolbar compete for width, and at ~390px the
246
+ * toolbar LOSES. It compresses below its own content: the icons slide under
247
+ * each other, the padding collapses, and the 44px tap targets stop being
248
+ * 44px wide. That is the "works incorrectly on mobile" report.
249
+ *
250
+ * With `shrink-0` the toolbar keeps its intrinsic size and the HEADING gives
251
+ * way instead β€” which is right, because a heading wraps gracefully onto a
252
+ * second line and a row of hit targets does not.
253
+ *
254
+ * πŸ›‘ Note what this is NOT: the desktop fix (`d94496c42`) gave the toolbar a
255
+ * container and added an audit whose two rules are about DOM ORDER β€” must
256
+ * come after the `<h1>`, must not share the heading's row unless that row is
257
+ * `justify="between"`. Both pass here. R2's exemption actively WHITELISTS
258
+ * the layout that breaks, so the surfaces that satisfy that audit are
259
+ * precisely the ones most likely to be wrong at phone width. A source-order
260
+ * rule cannot see a width problem.
239
261
  */
240
262
  const boxClass = onDark
241
263
  ? /*
@@ -244,12 +266,12 @@ export function OwnerQuickActions({ resource, editHref, onDelete, onClone, owner
244
266
  * LIGHT background in every light theme, so a themed box on a dark hero
245
267
  * photo is a white rectangle over the artwork.
246
268
  */
247
- "inline-flex rounded-[var(--appkit-radius-lg)] bg-black/55 backdrop-blur-sm p-[var(--appkit-space-1)] shadow-md"
269
+ "inline-flex shrink-0 rounded-[var(--appkit-radius-lg)] bg-black/55 backdrop-blur-sm p-[var(--appkit-space-1)] shadow-md"
248
270
  : /*
249
271
  * Off media: the real surface tokens, so it inverts with the theme
250
272
  * instead of being a fixed near-white that disappears in dark mode.
251
273
  */
252
- "inline-flex rounded-[var(--appkit-radius-lg)] bg-[var(--appkit-color-surface-elevated)] border border-[var(--appkit-color-border)] p-[var(--appkit-space-1)] shadow-sm";
274
+ "inline-flex shrink-0 rounded-[var(--appkit-radius-lg)] bg-[var(--appkit-color-surface-elevated)] border border-[var(--appkit-color-border)] p-[var(--appkit-space-1)] shadow-sm";
253
275
  return (_jsxs(_Fragment, { children: [_jsxs(Row, { gap: "xs", align: "center", className: `${boxClass} ${className}`.trim(),
254
276
  /*
255
277
  * Stops the click reaching an ancestor `<Link>` or the card's own
@@ -64,7 +64,7 @@ export function ReferenceRecordEditor({ endpoint, noun, fields, publicHref, }) {
64
64
  catch (err) {
65
65
  const e = normalizeError(err);
66
66
  if (!cancelled)
67
- setLoadError(e.message);
67
+ setLoadError(toUserMessage(e?.code, undefined, { fallback: "Could not load that record." }));
68
68
  }
69
69
  finally {
70
70
  if (!cancelled)
@@ -457,6 +457,9 @@ export declare const ADMIN_COUPON_TYPE_TABS: readonly [{
457
457
  }, {
458
458
  readonly id: "buy_x_get_y";
459
459
  readonly label: "Buy X Get Y";
460
+ }, {
461
+ readonly id: "points_earn";
462
+ readonly label: "Points Back";
460
463
  }];
461
464
  /** Empty-sentinel variant for views (like SellerBidsView) that drive the
462
465
  * filter URL with `""` instead of `"All"` so the `"" == no filter` invariant
@@ -757,6 +760,9 @@ export declare const ADMIN_PAYMENT_CASE_KIND_TABS: readonly [{
757
760
  }, {
758
761
  readonly id: "shipping_unpriced";
759
762
  readonly label: "Shipping unpriced";
763
+ }, {
764
+ readonly id: "courier_unavailable";
765
+ readonly label: "Courier unavailable";
760
766
  }, {
761
767
  readonly id: "dead_job";
762
768
  readonly label: "Dead job";
@@ -245,6 +245,11 @@ export const ADMIN_COUPON_TYPE_TABS = [
245
245
  { id: "fixed", label: "Fixed" },
246
246
  { id: "free_shipping", label: "Free Shipping" },
247
247
  { id: "buy_x_get_y", label: "Buy X Get Y" },
248
+ // D51. `audit-filter-tab-enums`' COVERAGE rule fails on a real union value
249
+ // with no chip β€” which is what it was extended to catch after ten such gaps
250
+ // were found at once (Root Cause #72), including sellers being unable to
251
+ // filter their own `in_review` listings.
252
+ { id: "points_earn", label: "Points Back" },
248
253
  ];
249
254
  // ---------------------------------------------------------------------------
250
255
  // Seller-dashboard filter-chip tab sets
@@ -441,6 +446,7 @@ export const ADMIN_PAYMENT_CASE_KIND_TABS = [
441
446
  { id: "protection_claim", label: "Protection claim" },
442
447
  { id: "grievance", label: "Grievance" },
443
448
  { id: "shipping_unpriced", label: "Shipping unpriced" },
449
+ { id: "courier_unavailable", label: "Courier unavailable" },
444
450
  { id: "dead_job", label: "Dead job" },
445
451
  { id: "ledger_drift", label: "Points drift" },
446
452
  ];
@@ -102,7 +102,7 @@ export declare const SITE_SETTINGS_TABS: readonly [{
102
102
  readonly description: "Which listing types sellers may create, and their defaults.";
103
103
  readonly keywords: ["listing types", "auctions on", "enable", "categories"];
104
104
  }];
105
- export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "appearance" | "emi" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "legal" | "procurement")[];
105
+ export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "emi" | "legal" | "appearance" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "procurement")[];
106
106
  /**
107
107
  * The union `?tab=` is validated against.
108
108
  *
@@ -2,6 +2,9 @@
2
2
  import { useQuery } from "@tanstack/react-query";
3
3
  import { apiClient, ApiClientError } from "../../../http/index.js";
4
4
  import { formatCurrency } from "../../../utils/number.formatter.js";
5
+ // The defining module, not a barrel (Root Cause #18). `hasEncPrefix` is a pure
6
+ // string test with no crypto in its import chain, so it is client-safe.
7
+ import { hasEncPrefix } from "../../../security/pii-mask.js";
5
8
  function withQueryParams(endpoint, params) {
6
9
  const hasQuery = endpoint.includes("?");
7
10
  const query = new URLSearchParams(params).toString();
@@ -50,6 +53,19 @@ export function toRecordArray(value) {
50
53
  }
51
54
  export function toStringValue(value, fallback = "-") {
52
55
  if (typeof value === "string") {
56
+ /*
57
+ * πŸ›‘ Ciphertext is not a display value. See the twin in
58
+ * `features/seller/hooks/useSellerListingData.ts` for the full account β€”
59
+ * the short version is that `mapDoc` does not decrypt, so a route that
60
+ * forgets `revealOrders`/`maskedOrders` hands this a stored `enc:v1:…`
61
+ * blob which is a perfectly ordinary `string` and renders verbatim.
62
+ *
63
+ * `/api/admin/orders` already hit this ("measured live at 3 visible
64
+ * `enc:v1:` strings") and fixed it at the route. This is the backstop for
65
+ * the next one.
66
+ */
67
+ if (hasEncPrefix(value))
68
+ return fallback;
53
69
  return value.trim() || fallback;
54
70
  }
55
71
  if (typeof value === "number" || typeof value === "boolean") {
@@ -0,0 +1,33 @@
1
+ import { type QueryKey } from "@tanstack/react-query";
2
+ export interface UseOptimisticRowFlagOptions<T> {
3
+ /**
4
+ * Performs the write. Receives the row id and the value being set.
5
+ *
6
+ * πŸ›‘ Send ONLY the field being changed. Several of this hook's consumers sit
7
+ * over repositories that merge a `config`/nested object by READING the
8
+ * document first; omitting that field short-circuits the read, so a toggle
9
+ * costs one write and no read.
10
+ */
11
+ mutate: (id: string, next: T) => Promise<unknown>;
12
+ /**
13
+ * Query keys to invalidate. Pass every key that renders this field β€”
14
+ * several of these records are read by a PUBLIC surface under a key of its
15
+ * own, and forgetting it leaves the storefront stale until its own TTL.
16
+ */
17
+ invalidate: readonly QueryKey[];
18
+ /** Toast copy. Receives the value that was set. */
19
+ successMessage: (next: T) => string;
20
+ /** Fallback when the thrown error carries no message. */
21
+ errorMessage?: string;
22
+ }
23
+ export interface UseOptimisticRowFlagReturn<T> {
24
+ /** `overrides[id] ?? row.value` β€” the value to render. */
25
+ overrides: Record<string, T>;
26
+ /** Resolve a row's current value against any pending override. */
27
+ resolve: (id: string, serverValue: T) => T;
28
+ /** The id currently being written, for a per-row disabled state. */
29
+ pendingId: string | null;
30
+ /** Fire the change. Safe to call from an `onChange` without awaiting. */
31
+ set: (id: string, next: T) => Promise<void>;
32
+ }
33
+ export declare function useOptimisticRowFlag<T>({ mutate, invalidate, successMessage, errorMessage, }: UseOptimisticRowFlagOptions<T>): UseOptimisticRowFlagReturn<T>;
@@ -0,0 +1,87 @@
1
+ "use client";
2
+ /*
3
+ * WHY: Eight listing views grew the same per-row "flip one field" control in
4
+ * one change β€” homepage sections (`enabled`), carousel slides (`active`),
5
+ * the six seller listing-type pages (`status`), seller bundles, grouped
6
+ * listings, store categories, shipping configs and payout methods
7
+ * (`isActive`). Every one needs an optimistic override so the switch
8
+ * moves under the finger, a PATCH, an invalidate, and a rollback.
9
+ *
10
+ * That is well past the Duplication Framework's Rule of Three, and the
11
+ * bug-fix multiplier is the real argument: the rollback is subtly wrong
12
+ * in the shape everyone writes first.
13
+ *
14
+ * πŸ›‘ THE ROLLBACK BUG THIS EXISTS TO MAKE UNREPEATABLE
15
+ *
16
+ * The obvious implementation captures the previous override and writes it
17
+ * back on failure:
18
+ *
19
+ * const prev = overrides[id]; // `undefined` on a row's
20
+ * setOverrides((o) => ({...o, [id]: v})); // FIRST toggle
21
+ * catch { setOverrides((o) => ({...o, [id]: prev})); }
22
+ *
23
+ * `prev` is `undefined` the first time any given row is toggled, and writing
24
+ * `undefined` back is not the same as removing the key β€” a reader doing
25
+ * `overrides[id] ?? row.value` gets `undefined`, which for a boolean renders
26
+ * as `false`. So a FAILED toggle of a row the server holds as `true` leaves
27
+ * the UI asserting the opposite of the truth, which is worse than not having
28
+ * rolled back at all. `AdminProductsView.tsx` has this shape.
29
+ *
30
+ * The fix is to DELETE the key, which is what `clear` below does.
31
+ *
32
+ * πŸ›‘ The key is cleared on SUCCESS too, and only AFTER the invalidation has
33
+ * settled. `invalidateQueries` resolves once active refetches finish, so by
34
+ * then `mapRows` is already reading the fresh server value and there is no
35
+ * visible flip-back-then-forward. An override that is never cleared pins the
36
+ * row forever and masks every later server-side change to it.
37
+ *
38
+ * EXPORTS: useOptimisticRowFlag, UseOptimisticRowFlagOptions
39
+ *
40
+ * @tag domain:admin,seller
41
+ * @tag layer:hook
42
+ * @tag pattern:none
43
+ * @tag access:client
44
+ * @tag consumers:AdminSectionsView,AdminCarouselView,SellerListingTypeView,SellerBundlesView,SellerGroupedListingsView,SellerStoreCategoriesView,SellerShippingConfigsView,SellerPayoutMethodsView
45
+ * @tag sideEffects:network
46
+ */
47
+ import React from "react";
48
+ import { useQueryClient } from "@tanstack/react-query";
49
+ import { useToast } from "../../../ui/index.js";
50
+ import { normalizeError } from "../../../errors/normalize.js";
51
+ import { toUserMessage } from "../../../errors/error-display-map.js";
52
+ export function useOptimisticRowFlag({ mutate, invalidate, successMessage, errorMessage = "Could not save that change.", }) {
53
+ const queryClient = useQueryClient();
54
+ const { showToast } = useToast();
55
+ const [overrides, setOverrides] = React.useState({});
56
+ const [pendingId, setPendingId] = React.useState(null);
57
+ const clear = React.useCallback((id) => {
58
+ setOverrides((prev) => {
59
+ if (!(id in prev))
60
+ return prev;
61
+ const next = { ...prev };
62
+ // DELETE, never write back a captured value β€” see the header.
63
+ delete next[id];
64
+ return next;
65
+ });
66
+ }, []);
67
+ const set = React.useCallback(async (id, next) => {
68
+ setOverrides((prev) => ({ ...prev, [id]: next }));
69
+ setPendingId(id);
70
+ try {
71
+ await mutate(id, next);
72
+ await Promise.all(invalidate.map((queryKey) => queryClient.invalidateQueries({ queryKey })));
73
+ clear(id);
74
+ showToast(successMessage(next), "success");
75
+ }
76
+ catch (err) {
77
+ const normalized = normalizeError(err);
78
+ clear(id);
79
+ showToast(toUserMessage(normalized.code, undefined, { fallback: errorMessage }), "error");
80
+ }
81
+ finally {
82
+ setPendingId(null);
83
+ }
84
+ }, [mutate, invalidate, queryClient, clear, showToast, successMessage, errorMessage]);
85
+ const resolve = React.useCallback((id, serverValue) => (id in overrides ? overrides[id] : serverValue), [overrides]);
86
+ return { overrides, resolve, pendingId, set };
87
+ }
@@ -30,6 +30,21 @@ export declare class NotificationRepository extends BaseRepository<NotificationD
30
30
  * Get unread notification count for a user.
31
31
  */
32
32
  getUnreadCount(userId: string): Promise<number>;
33
+ /**
34
+ * Mark a single notification UNREAD again.
35
+ *
36
+ * πŸ›‘ `readAt` is cleared, not left behind. It is the timestamp of a read
37
+ * that the caller is now saying did not happen, and a row that is
38
+ * `isRead: false` with a `readAt` set is a contradiction the next reader has
39
+ * to resolve β€” the digest and the unread badge key on `isRead`, so they
40
+ * would disagree with anything that looked at `readAt`.
41
+ *
42
+ * There was no way to do this at all: the buyer's notifications page offered
43
+ * only Mark-read, and the PATCH route hard-coded `markAsRead(id)` without
44
+ * reading a body β€” so a notification clicked by accident was gone from the
45
+ * unread list for good.
46
+ */
47
+ markAsUnread(id: string): Promise<NotificationDocument | null>;
33
48
  /**
34
49
  * Mark a single notification as read.
35
50
  */
@@ -59,6 +59,38 @@ export class NotificationRepository extends BaseRepository {
59
59
  return 0;
60
60
  }
61
61
  }
62
+ /**
63
+ * Mark a single notification UNREAD again.
64
+ *
65
+ * πŸ›‘ `readAt` is cleared, not left behind. It is the timestamp of a read
66
+ * that the caller is now saying did not happen, and a row that is
67
+ * `isRead: false` with a `readAt` set is a contradiction the next reader has
68
+ * to resolve β€” the digest and the unread badge key on `isRead`, so they
69
+ * would disagree with anything that looked at `readAt`.
70
+ *
71
+ * There was no way to do this at all: the buyer's notifications page offered
72
+ * only Mark-read, and the PATCH route hard-coded `markAsRead(id)` without
73
+ * reading a body β€” so a notification clicked by accident was gone from the
74
+ * unread list for good.
75
+ */
76
+ async markAsUnread(id) {
77
+ try {
78
+ const now = new Date();
79
+ await this.getCollection()
80
+ .doc(id)
81
+ .update(prepareForFirestore({
82
+ [NOTIFICATION_FIELDS.IS_READ]: false,
83
+ [NOTIFICATION_FIELDS.READ_AT]: null,
84
+ [NOTIFICATION_FIELDS.UPDATED_AT]: now,
85
+ }));
86
+ return this.findById(id);
87
+ }
88
+ catch (error) {
89
+ void normalizeError(error);
90
+ serverLogger.error("Failed to mark notification unread", { id, error });
91
+ throw error;
92
+ }
93
+ }
62
94
  /**
63
95
  * Mark a single notification as read.
64
96
  */
@@ -0,0 +1,34 @@
1
+ import { z } from "zod";
2
+ import type { QuickFieldDef } from "../../shell/QuickFormDrawer";
3
+ /**
4
+ * πŸ›‘ `keywords` is a COMMA-SEPARATED STRING here, not an array.
5
+ *
6
+ * `QuickFormDrawer` has no array control β€” its field types are text / number
7
+ * / select / toggle / date / textarea / email / url. Declaring `z.string()`
8
+ * and splitting at the call site keeps the schema honest about what the form
9
+ * actually produces, rather than describing a shape no control can emit.
10
+ * `splitKeywords` below is the one place that conversion happens.
11
+ */
12
+ export declare const actionIndexOverrideSchema: z.ZodObject<{
13
+ label: z.ZodString;
14
+ description: z.ZodOptional<z.ZodString>;
15
+ keywords: z.ZodOptional<z.ZodString>;
16
+ weight: z.ZodOptional<z.ZodNumber>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ label: string;
19
+ weight?: number | undefined;
20
+ description?: string | undefined;
21
+ keywords?: string | undefined;
22
+ }, {
23
+ label: string;
24
+ weight?: number | undefined;
25
+ description?: string | undefined;
26
+ keywords?: string | undefined;
27
+ }>;
28
+ export type ActionIndexOverrideFormInput = z.infer<typeof actionIndexOverrideSchema>;
29
+ /** The drawer's fields, in the order an admin reads them. */
30
+ export declare const ACTION_INDEX_OVERRIDE_FIELDS: QuickFieldDef[];
31
+ /** The single place the comma-separated field becomes the stored array. */
32
+ export declare function splitKeywords(raw: string | undefined): string[] | undefined;
33
+ /** The reverse, for seeding the form from a stored override. */
34
+ export declare function joinKeywords(keywords: readonly string[] | undefined): string;
@@ -0,0 +1,115 @@
1
+ /*
2
+ * WHY: `/admin/action-index` told admins "Labels, descriptions and keywords
3
+ * can be edited here" while offering only a Shown toggle. The server has
4
+ * always accepted those fields β€” `ActionIndexOverride` carries `label`,
5
+ * `description`, `keywords` and `weight`, and `PATCH
6
+ * /api/admin/action-index` writes them through `setOverride`. The UI had
7
+ * simply never grown the control, so the sentence was a promise nothing
8
+ * kept.
9
+ *
10
+ * WHAT: The write contract for ONE entry override, as edited from the admin
11
+ * row action.
12
+ *
13
+ * ## What is deliberately absent
14
+ *
15
+ * `href`, `kind`, `portal` and every permission field. The view's own
16
+ * docstring draws this line and it is the right one: repointing a built-in
17
+ * route is routing, and relaxing a permission is access control. Neither
18
+ * belongs in a quick-edit drawer beside a label.
19
+ *
20
+ * `enabled` is also absent β€” it has its own control in the Shown column, and
21
+ * a second place to set it is a second source of truth for one boolean.
22
+ *
23
+ * EXPORTS:
24
+ * actionIndexOverrideSchema, ActionIndexOverrideFormInput,
25
+ * ACTION_INDEX_OVERRIDE_FIELDS
26
+ *
27
+ * @tag domain:admin,search
28
+ * @tag layer:schema
29
+ * @tag pattern:none
30
+ * @tag access:isomorphic
31
+ * @tag consumers:AdminActionIndexView
32
+ * @tag sideEffects:none
33
+ */
34
+ import { z } from "zod";
35
+ /**
36
+ * πŸ›‘ `keywords` is a COMMA-SEPARATED STRING here, not an array.
37
+ *
38
+ * `QuickFormDrawer` has no array control β€” its field types are text / number
39
+ * / select / toggle / date / textarea / email / url. Declaring `z.string()`
40
+ * and splitting at the call site keeps the schema honest about what the form
41
+ * actually produces, rather than describing a shape no control can emit.
42
+ * `splitKeywords` below is the one place that conversion happens.
43
+ */
44
+ export const actionIndexOverrideSchema = z.object({
45
+ label: z
46
+ .string()
47
+ .trim()
48
+ .min(1, "A label is required β€” it is what the sidebar and search show.")
49
+ .max(80, "Keep labels under 80 characters so they fit the sidebar."),
50
+ description: z
51
+ .string()
52
+ .trim()
53
+ .max(200, "Keep descriptions under 200 characters.")
54
+ .optional(),
55
+ keywords: z
56
+ .string()
57
+ .trim()
58
+ .max(300, "Keep the keyword list under 300 characters.")
59
+ .optional(),
60
+ /*
61
+ * Ranking, not ordering. Higher sorts first in the search and the command
62
+ * palette. Bounded because an unbounded weight is how one entry ends up
63
+ * permanently above every other and nobody remembers why.
64
+ */
65
+ weight: z.coerce
66
+ .number()
67
+ .int("Weight must be a whole number.")
68
+ .min(0, "Weight cannot be negative.")
69
+ .max(100, "Weight is capped at 100.")
70
+ .optional(),
71
+ });
72
+ /** The drawer's fields, in the order an admin reads them. */
73
+ export const ACTION_INDEX_OVERRIDE_FIELDS = [
74
+ {
75
+ name: "label",
76
+ label: "Label",
77
+ type: "text",
78
+ required: true,
79
+ helperText: "What the sidebar, header search and command palette show.",
80
+ },
81
+ {
82
+ name: "description",
83
+ label: "Description",
84
+ type: "textarea",
85
+ placeholder: "One line explaining what this entry is for",
86
+ },
87
+ {
88
+ name: "keywords",
89
+ label: "Keywords",
90
+ type: "text",
91
+ placeholder: "refunds, returns, money back",
92
+ helperText: "Comma-separated. Extra words that should find this entry in search.",
93
+ },
94
+ {
95
+ name: "weight",
96
+ label: "Weight",
97
+ type: "number",
98
+ helperText: "Higher sorts first in search results. 0–100.",
99
+ },
100
+ ];
101
+ /** The single place the comma-separated field becomes the stored array. */
102
+ export function splitKeywords(raw) {
103
+ if (raw === undefined)
104
+ return undefined;
105
+ const parts = raw
106
+ .split(",")
107
+ .map((part) => part.trim())
108
+ .filter(Boolean);
109
+ // An empty box means "no keywords", which is `[]` β€” not "leave them alone".
110
+ return parts;
111
+ }
112
+ /** The reverse, for seeding the form from a stored override. */
113
+ export function joinKeywords(keywords) {
114
+ return (keywords ?? []).join(", ");
115
+ }
@@ -82,13 +82,13 @@ export declare const lotteryPullSchema: z.ZodObject<{
82
82
  userPhone: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
83
83
  }, "strip", z.ZodTypeAny, {
84
84
  transactionId: string;
85
- sourceType: "event" | "product";
85
+ sourceType: "product" | "event";
86
86
  paymentTime: string;
87
87
  purchasedItemNumber: number;
88
88
  userPhone?: string | undefined;
89
89
  }, {
90
90
  transactionId: string;
91
- sourceType: "event" | "product";
91
+ sourceType: "product" | "event";
92
92
  paymentTime: string;
93
93
  purchasedItemNumber: number;
94
94
  userPhone?: string | undefined;
@@ -690,6 +690,36 @@ export interface SiteSettingsDocument extends BaseDocument {
690
690
  returns: {
691
691
  windowDays: number;
692
692
  };
693
+ /**
694
+ * The support integration's on/off switch.
695
+ *
696
+ * πŸ›‘ The ONLY new integration flag. The other three the admin page switches
697
+ * already have an authoritative field β€” `payment.razorpayEnabled`,
698
+ * `messaging.emailEnabled` (which `guardSend` reads), and
699
+ * `shipping.provider`. Adding a parallel `integrationSwitches.*` beside
700
+ * those would be a denormalised mirror that drifts the first time one path
701
+ * writes only one of a pair (Root Cause #42), and the page would then
702
+ * disagree with the code that enforces it. The page is a VIEW over the real
703
+ * fields; this is the one field that did not exist.
704
+ *
705
+ * πŸ›‘ Also not under `siteSettings.integrations` β€” that key is live and holds
706
+ * analytics/tag IDs (GA, FB Pixel, GTM).
707
+ *
708
+ * OFF hides the WHOLE support surface from BUYERS: no nav entry, no ticket
709
+ * list, no contact form. Staff keep full access, so an open ticket is still
710
+ * workable and the employee pay-per-ticket basis survives.
711
+ *
712
+ * πŸ›‘ A buyer mid-conversation loses sight of their thread. That is the
713
+ * owner's decision, accepted knowingly β€” and the admin row must SAY it
714
+ * rather than implying the switch only stops new tickets.
715
+ *
716
+ * Defaults to `true`: support being reachable is the safe state, and a
717
+ * settings document written before this field existed must not read as
718
+ * "support is off".
719
+ */
720
+ support: {
721
+ enabled: boolean;
722
+ };
693
723
  faq: {
694
724
  variables: {
695
725
  shippingDays: number;
@@ -377,6 +377,11 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
377
377
  returns: {
378
378
  windowDays: 7,
379
379
  },
380
+ // Reachable is the safe default, and an older settings document with no
381
+ // `support` group must not read as "support is off".
382
+ support: {
383
+ enabled: true,
384
+ },
380
385
  announcementBar: {
381
386
  enabled: true,
382
387
  message: "πŸŽ‰ Up to 15% Off on PokΓ©mon TCG this week β€” Use code SAVE15",
@@ -44,6 +44,21 @@ export declare const AdminAuditActionValues: {
44
44
  * same class as `payout_mark_paid` and `coupon_update`, which are both here.
45
45
  */
46
46
  readonly POINTS_ADJUST: "points_adjust";
47
+ /**
48
+ * Staff added a buyer-facing dispute reason.
49
+ *
50
+ * πŸ›‘ Here because the list is POLICY VOCABULARY, even though no single row
51
+ * decides money. The reasons are what buyers are offered when something goes
52
+ * wrong, so the wording shapes what gets reported and how it is triaged β€”
53
+ * and a badly-worded or leading option is the kind of thing that is only
54
+ * ever noticed later, by which time "who added this, and when" is the
55
+ * question. `createdByName` on the row answers it for the list; this answers
56
+ * it in the one place an admin looks for "what have staff done lately".
57
+ *
58
+ * Recorded only on a REAL creation. Re-adding wording that already exists
59
+ * returns the existing row, which is not an act worth logging.
60
+ */
61
+ readonly DISPUTE_REASON_CREATE: "dispute_reason_create";
47
62
  };
48
63
  export type AdminAuditAction = (typeof AdminAuditActionValues)[keyof typeof AdminAuditActionValues];
49
64
  export interface AdminAuditLogDocument extends BaseDocument {
@@ -97,5 +112,20 @@ export declare const ADMIN_AUDIT_LOG_FIELDS: {
97
112
  * same class as `payout_mark_paid` and `coupon_update`, which are both here.
98
113
  */
99
114
  readonly POINTS_ADJUST: "points_adjust";
115
+ /**
116
+ * Staff added a buyer-facing dispute reason.
117
+ *
118
+ * πŸ›‘ Here because the list is POLICY VOCABULARY, even though no single row
119
+ * decides money. The reasons are what buyers are offered when something goes
120
+ * wrong, so the wording shapes what gets reported and how it is triaged β€”
121
+ * and a badly-worded or leading option is the kind of thing that is only
122
+ * ever noticed later, by which time "who added this, and when" is the
123
+ * question. `createdByName` on the row answers it for the list; this answers
124
+ * it in the one place an admin looks for "what have staff done lately".
125
+ *
126
+ * Recorded only on a REAL creation. Re-adding wording that already exists
127
+ * returns the existing row, which is not an act worth logging.
128
+ */
129
+ readonly DISPUTE_REASON_CREATE: "dispute_reason_create";
100
130
  };
101
131
  };
@@ -42,6 +42,21 @@ export const AdminAuditActionValues = {
42
42
  * same class as `payout_mark_paid` and `coupon_update`, which are both here.
43
43
  */
44
44
  POINTS_ADJUST: "points_adjust",
45
+ /**
46
+ * Staff added a buyer-facing dispute reason.
47
+ *
48
+ * πŸ›‘ Here because the list is POLICY VOCABULARY, even though no single row
49
+ * decides money. The reasons are what buyers are offered when something goes
50
+ * wrong, so the wording shapes what gets reported and how it is triaged β€”
51
+ * and a badly-worded or leading option is the kind of thing that is only
52
+ * ever noticed later, by which time "who added this, and when" is the
53
+ * question. `createdByName` on the row answers it for the list; this answers
54
+ * it in the one place an admin looks for "what have staff done lately".
55
+ *
56
+ * Recorded only on a REAL creation. Re-adding wording that already exists
57
+ * returns the existing row, which is not an act worth logging.
58
+ */
59
+ DISPUTE_REASON_CREATE: "dispute_reason_create",
45
60
  };
46
61
  export const ADMIN_AUDIT_LOG_FIELDS = {
47
62
  ACTOR_UID: "actorUid",
@@ -11,6 +11,7 @@ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell
11
11
  import { registerSchema, registerPasswordSchema } from "../schemas/index.js";
12
12
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary.js";
13
13
  import { SectionForm, useSectionFormNav } from "../../shell/SectionForm.js";
14
+ import { toUserMessage } from "../../../errors/error-display-map.js";
14
15
  const registerClientSchema = registerSchema.extend({
15
16
  displayName: z.string().min(1, "Enter your name"),
16
17
  confirmPassword: registerPasswordSchema,
@@ -81,7 +82,7 @@ export function RegisterForm({ onSubmit, isLoading = false, error, success, labe
81
82
  * email, a rejected password β€” which is the one thing they can act on.
82
83
  */
83
84
  const normalized = normalizeError(err);
84
- setFieldError("email", normalized.message || "Registration failed");
85
+ setFieldError("email", toUserMessage(normalized.code, undefined, { fallback: "We couldn't create your account. Please check your details and try again." }));
85
86
  }
86
87
  };
87
88
  return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: values, onChange: (partial) => setValues((prev) => Object.assign({}, prev, partial)), onSubmit: handleSubmit, schema: registerClientSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: isLoading, submitLabel: isLoading
@@ -344,6 +344,19 @@ export const ROUTE_PERMISSION_MAP = {
344
344
  // Payment protection β€” admin-only, see the permission union's own πŸ›‘ note.
345
345
  "/admin/money-cases": "admin:payment-cases:read",
346
346
  "/admin/protection-claims": "admin:protection-claims:read",
347
+ /*
348
+ * Phase 7 invoicing β€” the GSTR-1 draft export.
349
+ *
350
+ * πŸ›‘ `settings:write` rather than a new permission, and that is a judgement
351
+ * worth stating. The page exports the company's tax position for a quarter
352
+ * plus every buyer's place of supply; the set of people who should see that
353
+ * is the set who can already change the GSTIN and the fee schedule, which is
354
+ * exactly what `settings:write` names. Minting
355
+ * `admin:invoicing:read` would add a permission with one route and one
356
+ * holder β€” a distinction with no decision behind it, which is how a
357
+ * permission union becomes unreadable.
358
+ */
359
+ "/admin/invoicing": "admin:settings:write",
347
360
  };
348
361
  /**
349
362
  * Every capability, as a runtime value.