@mohasinac/appkit 4.30.18 → 4.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (557) hide show
  1. package/dist/_internal/client/features/digital-code/CodeRevealPanel.js +5 -2
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +3 -3
  3. package/dist/_internal/client/features/layout/useSidebarSearch.d.ts +21 -5
  4. package/dist/_internal/client/features/layout/useSidebarSearch.js +27 -6
  5. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +23 -3
  6. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +106 -86
  7. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +16 -5
  8. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -40
  9. package/dist/_internal/client/features/maintenance/views/AnalysisRunnerView.js +5 -2
  10. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +11 -3
  11. package/dist/_internal/client/hand-mode/HandModeProvider.js +2 -0
  12. package/dist/_internal/server/features/auctions/actions.js +14 -3
  13. package/dist/_internal/server/features/auctions/service.js +1 -1
  14. package/dist/_internal/server/features/auth/permissions.d.ts +2 -0
  15. package/dist/_internal/server/features/auth/permissions.js +3 -2
  16. package/dist/_internal/server/features/blog/data.js +2 -2
  17. package/dist/_internal/server/features/blog/service.js +3 -3
  18. package/dist/_internal/server/features/brands/data.js +1 -1
  19. package/dist/_internal/server/features/brands/service.js +3 -3
  20. package/dist/_internal/server/features/bundles/actions.js +6 -1
  21. package/dist/_internal/server/features/bundles/data.js +21 -4
  22. package/dist/_internal/server/features/cart/data.js +3 -1
  23. package/dist/_internal/server/features/cart/service.js +13 -2
  24. package/dist/_internal/server/features/categories/data.js +26 -6
  25. package/dist/_internal/server/features/classified/index.d.ts +0 -1
  26. package/dist/_internal/server/features/classified/index.js +0 -1
  27. package/dist/_internal/server/features/digital-code/index.d.ts +0 -1
  28. package/dist/_internal/server/features/digital-code/index.js +0 -1
  29. package/dist/_internal/server/features/events/actions.js +17 -4
  30. package/dist/_internal/server/features/events/data.js +1 -1
  31. package/dist/_internal/server/features/events/service.js +1 -1
  32. package/dist/_internal/server/features/grouped/data.js +7 -2
  33. package/dist/_internal/server/features/item-requests/data.js +1 -1
  34. package/dist/_internal/server/features/live/index.d.ts +0 -1
  35. package/dist/_internal/server/features/live/index.js +0 -1
  36. package/dist/_internal/server/features/lottery/actions.js +2 -2
  37. package/dist/_internal/server/features/lottery/data.js +1 -1
  38. package/dist/_internal/server/features/orders/actions.js +7 -7
  39. package/dist/_internal/server/features/orders/data.d.ts +1 -1
  40. package/dist/_internal/server/features/orders/data.js +14 -2
  41. package/dist/_internal/server/features/orders/service.js +1 -1
  42. package/dist/_internal/server/features/pre-orders/service.js +1 -1
  43. package/dist/_internal/server/features/prize-draws/actions.js +1 -1
  44. package/dist/_internal/server/features/products/data.js +21 -6
  45. package/dist/_internal/server/features/products/list-public.d.ts +9 -0
  46. package/dist/_internal/server/features/products/list-public.js +95 -7
  47. package/dist/_internal/server/features/products/service.js +1 -1
  48. package/dist/_internal/server/features/promotions/actions.js +1 -1
  49. package/dist/_internal/server/features/promotions/data.js +1 -1
  50. package/dist/_internal/server/features/promotions/service.js +1 -1
  51. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  52. package/dist/_internal/server/features/reviews/data.js +6 -1
  53. package/dist/_internal/server/features/reviews/og-data.js +1 -1
  54. package/dist/_internal/server/features/shared/listing-data-factory.js +7 -2
  55. package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
  56. package/dist/_internal/server/features/site-settings/adapters.js +8 -5
  57. package/dist/_internal/server/features/stores/data.js +1 -1
  58. package/dist/_internal/server/features/sublisting-categories/data.js +1 -1
  59. package/dist/_internal/server/features/tester/visibility.d.ts +2 -1
  60. package/dist/_internal/server/features/tester/visibility.js +2 -2
  61. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  62. package/dist/_internal/server/functions/scheduled.js +27 -1
  63. package/dist/_internal/server/jobs/core/auctionSettlement.js +6 -1
  64. package/dist/_internal/server/jobs/core/hardBanCascade.js +4 -0
  65. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  66. package/dist/_internal/server/jobs/core/index.js +2 -0
  67. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +7 -15
  68. package/dist/_internal/server/jobs/core/pageViewPrune.d.ts +10 -0
  69. package/dist/_internal/server/jobs/core/pageViewPrune.js +44 -0
  70. package/dist/_internal/server/jobs/core/pageViewRollup.d.ts +2 -0
  71. package/dist/_internal/server/jobs/core/pageViewRollup.js +87 -0
  72. package/dist/_internal/server/jobs/core/revenueRollup.js +7 -1
  73. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  74. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  75. package/dist/_internal/server/jobs/handlers/pageViewPrune.d.ts +2 -0
  76. package/dist/_internal/server/jobs/handlers/pageViewPrune.js +2 -0
  77. package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +2 -0
  78. package/dist/_internal/server/jobs/handlers/pageViewRollup.js +2 -0
  79. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  80. package/dist/_internal/shared/actions/action-registry.js +69 -22
  81. package/dist/_internal/shared/checkout/rules/classified.rule.d.ts +9 -4
  82. package/dist/_internal/shared/checkout/rules/classified.rule.js +7 -2
  83. package/dist/_internal/shared/features/layout/matchesNavQuery.d.ts +14 -0
  84. package/dist/_internal/shared/features/layout/matchesNavQuery.js +72 -0
  85. package/dist/_internal/shared/features/layout/types.d.ts +37 -1
  86. package/dist/_internal/shared/features/layout/types.js +19 -1
  87. package/dist/_internal/shared/features/notifications/action-url.d.ts +9 -1
  88. package/dist/_internal/shared/features/notifications/action-url.js +47 -0
  89. package/dist/_internal/shared/features/offers/config.d.ts +65 -0
  90. package/dist/_internal/shared/features/offers/config.js +73 -2
  91. package/dist/_internal/shared/features/orders/schema.d.ts +19 -1
  92. package/dist/_internal/shared/features/orders/schema.js +22 -2
  93. package/dist/_internal/shared/features/site-settings/types.d.ts +12 -3
  94. package/dist/_internal/shared/listing-types/capabilities.d.ts +8 -2
  95. package/dist/_internal/shared/listing-types/capabilities.js +6 -1
  96. package/dist/_internal/shared/listing-types/classified/ctas.js +6 -1
  97. package/dist/_internal/shared/listing-types/feature-flags.d.ts +10 -5
  98. package/dist/_internal/shared/listing-types/feature-flags.js +11 -6
  99. package/dist/client/api/useApiQuery.js +17 -6
  100. package/dist/client.d.ts +38 -20
  101. package/dist/client.js +39 -14
  102. package/dist/configs/next.js +10 -2
  103. package/dist/constants/api-endpoints.d.ts +18 -54
  104. package/dist/constants/api-endpoints.js +6 -26
  105. package/dist/constants/field-names.d.ts +6 -24
  106. package/dist/constants/field-names.js +10 -27
  107. package/dist/constants/geo/countries.d.ts +50 -0
  108. package/dist/constants/geo/countries.js +129 -0
  109. package/dist/constants/geo/subdivisions.d.ts +13 -0
  110. package/dist/constants/geo/subdivisions.js +104 -0
  111. package/dist/constants/index.d.ts +2 -2
  112. package/dist/constants/index.js +2 -2
  113. package/dist/constants/table-keys.d.ts +24 -0
  114. package/dist/constants/table-keys.js +28 -0
  115. package/dist/contracts/client-realtime.d.ts +6 -0
  116. package/dist/contracts/extend.d.ts +0 -9
  117. package/dist/contracts/index.d.ts +1 -1
  118. package/dist/contracts/realtime-session.d.ts +12 -12
  119. package/dist/contracts/realtime-session.js +79 -77
  120. package/dist/core/browser.d.ts +1 -1
  121. package/dist/core/browser.js +1 -1
  122. package/dist/core/index.d.ts +1 -1
  123. package/dist/core/index.js +1 -1
  124. package/dist/core/site-config.d.ts +0 -3
  125. package/dist/core/site-config.js +0 -3
  126. package/dist/errors/index.d.ts +1 -1
  127. package/dist/errors/index.js +1 -1
  128. package/dist/errors/messages.d.ts +0 -8
  129. package/dist/errors/messages.js +0 -9
  130. package/dist/features/about/components/DeveloperView.js +7 -2
  131. package/dist/features/about/components/PolicyPageView.js +9 -1
  132. package/dist/features/about/components/PublicProfileView.js +23 -5
  133. package/dist/features/about/schemas/index.d.ts +8 -8
  134. package/dist/features/account/components/AddressFilters.js +32 -11
  135. package/dist/features/account/components/AddressForm.d.ts +26 -25
  136. package/dist/features/account/components/AddressForm.js +81 -103
  137. package/dist/features/account/components/AddressesIndexListing.js +8 -4
  138. package/dist/features/account/components/NotificationBell.js +4 -3
  139. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  140. package/dist/features/account/components/UserOffersPanel.js +1 -1
  141. package/dist/features/account/components/UserSidebar.d.ts +3 -4
  142. package/dist/features/account/components/UserSupportView.js +89 -58
  143. package/dist/features/account/components/index.d.ts +1 -7
  144. package/dist/features/account/components/index.js +0 -3
  145. package/dist/features/account/hooks/useAddresses.d.ts +4 -3
  146. package/dist/features/account/hooks/useAddresses.js +4 -6
  147. package/dist/features/account/hooks/useCollapsedSections.d.ts +51 -10
  148. package/dist/features/account/hooks/useCollapsedSections.js +211 -38
  149. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  150. package/dist/features/account/index.d.ts +0 -3
  151. package/dist/features/account/index.js +0 -3
  152. package/dist/features/account/schemas/index.d.ts +0 -92
  153. package/dist/features/account/schemas/index.js +42 -20
  154. package/dist/features/addresses/components/AddressLocationFields.d.ts +13 -0
  155. package/dist/features/addresses/components/AddressLocationFields.js +41 -0
  156. package/dist/features/addresses/hooks/usePostalLookup.d.ts +17 -0
  157. package/dist/features/addresses/hooks/usePostalLookup.js +133 -0
  158. package/dist/features/addresses/schemas/address-form.d.ts +423 -0
  159. package/dist/features/addresses/schemas/address-form.js +212 -0
  160. package/dist/features/addresses/schemas/index.d.ts +1 -0
  161. package/dist/features/addresses/schemas/index.js +8 -0
  162. package/dist/features/admin/actions/admin-read-actions.d.ts +1 -1
  163. package/dist/features/admin/actions/index.d.ts +0 -1
  164. package/dist/features/admin/actions/index.js +0 -1
  165. package/dist/features/admin/actions/notification-actions.js +1 -41
  166. package/dist/features/admin/components/AdminActionIndexView.d.ts +32 -0
  167. package/dist/features/admin/components/AdminActionIndexView.js +128 -0
  168. package/dist/features/admin/components/AdminAdEditorView.js +159 -163
  169. package/dist/features/admin/components/AdminAddressEditorView.js +97 -55
  170. package/dist/features/admin/components/AdminBrandEditorView.d.ts +15 -0
  171. package/dist/features/admin/components/AdminBrandEditorView.js +113 -77
  172. package/dist/features/admin/components/AdminBundleEditorView.d.ts +19 -9
  173. package/dist/features/admin/components/AdminBundleEditorView.js +136 -168
  174. package/dist/features/admin/components/AdminCategoryEditorView.js +120 -43
  175. package/dist/features/admin/components/AdminCouponEditorView.js +145 -109
  176. package/dist/features/admin/components/AdminEmployeeEditorView.js +125 -56
  177. package/dist/features/admin/components/AdminFaqEditorView.js +112 -58
  178. package/dist/features/admin/components/AdminFeatureEditorView.js +108 -68
  179. package/dist/features/admin/components/AdminFulfillmentView.js +5 -2
  180. package/dist/features/admin/components/AdminMediaView.js +3 -3
  181. package/dist/features/admin/components/AdminNavEditorView.js +69 -44
  182. package/dist/features/admin/components/AdminOrderEditorView.js +47 -10
  183. package/dist/features/admin/components/AdminPrizeDrawsView.js +16 -2
  184. package/dist/features/admin/components/AdminSidebar.d.ts +11 -7
  185. package/dist/features/admin/components/AdminSiteSettingsView.js +37 -37
  186. package/dist/features/admin/components/AdminStoreEditorView.js +75 -52
  187. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +71 -30
  188. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +95 -65
  189. package/dist/features/admin/components/BrandQuickCreateForm.d.ts +10 -0
  190. package/dist/features/admin/components/BrandQuickCreateForm.js +49 -26
  191. package/dist/features/admin/components/CategoryQuickCreateForm.d.ts +11 -0
  192. package/dist/features/admin/components/CategoryQuickCreateForm.js +48 -26
  193. package/dist/features/admin/components/index.d.ts +2 -2
  194. package/dist/features/admin/components/index.js +1 -1
  195. package/dist/features/admin/constants/site-settings-tabs.d.ts +120 -0
  196. package/dist/features/admin/constants/site-settings-tabs.js +152 -0
  197. package/dist/features/admin/index.d.ts +0 -3
  198. package/dist/features/admin/index.js +0 -2
  199. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +35 -0
  200. package/dist/features/admin/repository/analytics-rollup.repository.js +30 -0
  201. package/dist/features/admin/schemas/admin-editor-forms.d.ts +56 -57
  202. package/dist/features/admin/schemas/admin-editor-forms.js +97 -54
  203. package/dist/features/admin/schemas/admin-ops-forms.d.ts +57 -5
  204. package/dist/features/admin/schemas/admin-ops-forms.js +104 -20
  205. package/dist/features/admin/schemas/admin-user-form.js +7 -0
  206. package/dist/features/admin/schemas/firestore.d.ts +48 -95
  207. package/dist/features/admin/schemas/firestore.js +15 -151
  208. package/dist/features/admin/schemas/nav-item-form.js +9 -2
  209. package/dist/features/admin/server.d.ts +0 -4
  210. package/dist/features/admin/server.js +0 -2
  211. package/dist/features/admin/types/index.d.ts +0 -7
  212. package/dist/features/analytics/repository/page-views.repository.d.ts +14 -0
  213. package/dist/features/analytics/repository/page-views.repository.js +24 -0
  214. package/dist/features/analytics/types.d.ts +1 -1
  215. package/dist/features/analytics/types.js +13 -0
  216. package/dist/features/auctions/components/AuctionDetailPageView.js +6 -2
  217. package/dist/features/auctions/components/MarketplaceAuctionCard.js +3 -1
  218. package/dist/features/auth/actions/index.d.ts +0 -1
  219. package/dist/features/auth/actions/index.js +0 -1
  220. package/dist/features/auth/actions/profile-actions.js +4 -3
  221. package/dist/features/auth/actions/realtime-token-actions.js +1 -12
  222. package/dist/features/auth/auth-helpers.js +5 -7
  223. package/dist/features/auth/components/ForgotPasswordView.js +1 -1
  224. package/dist/features/auth/components/Guards.js +2 -1
  225. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  226. package/dist/features/auth/components/LoginForm.js +2 -4
  227. package/dist/features/auth/components/RegisterForm.js +62 -19
  228. package/dist/features/auth/components/ResetPasswordView.js +1 -1
  229. package/dist/features/auth/hooks/useAuth.js +3 -0
  230. package/dist/features/auth/permissions/constants.d.ts +27 -1
  231. package/dist/features/auth/role-predicates.d.ts +75 -7
  232. package/dist/features/auth/role-predicates.js +28 -2
  233. package/dist/features/auth/schemas/firestore.d.ts +22 -0
  234. package/dist/features/auth/schemas/index.d.ts +1 -1
  235. package/dist/features/auth/schemas/index.js +44 -8
  236. package/dist/features/auth/types/index.d.ts +14 -1
  237. package/dist/features/blog/api/route.js +10 -2
  238. package/dist/features/blog/components/BlogIndexListing.js +1 -1
  239. package/dist/features/blog/hooks/useBlog.js +2 -2
  240. package/dist/features/blog/repository/blog.repository.js +3 -1
  241. package/dist/features/blog/schemas/index.d.ts +4 -0
  242. package/dist/features/blog/schemas/index.js +2 -0
  243. package/dist/features/blog/types/index.d.ts +1 -1
  244. package/dist/features/cart/actions/cart-actions.js +13 -4
  245. package/dist/features/cart/utils/pending-ops.js +1 -1
  246. package/dist/features/catalogue/components/CatalogueItemEditorView.d.ts +22 -0
  247. package/dist/features/catalogue/components/CatalogueItemEditorView.js +112 -52
  248. package/dist/features/catalogue/schemas/validation.d.ts +4 -4
  249. package/dist/features/catalogue/schemas/validation.js +47 -10
  250. package/dist/features/categories/components/BrandDetailPageView.js +28 -12
  251. package/dist/features/categories/components/CategoryDetailPageView.js +37 -20
  252. package/dist/features/categories/repository/categories.repository.js +3 -1
  253. package/dist/features/categories/schemas/bundle-form.d.ts +142 -0
  254. package/dist/features/categories/schemas/bundle-form.js +149 -0
  255. package/dist/features/checkout/actions/checkout-value-otp-actions.js +10 -1
  256. package/dist/features/classified/components/ClassifiedContactSellerPanel.js +1 -1
  257. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +5 -4
  258. package/dist/features/classified/components/ClassifiedDetailPageView.js +47 -18
  259. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  260. package/dist/features/consultation/schemas/index.js +34 -10
  261. package/dist/features/corporate/schemas/index.js +36 -15
  262. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -1
  263. package/dist/features/events/components/EventBanner.js +5 -2
  264. package/dist/features/events/components/EventOfferCard.js +3 -1
  265. package/dist/features/events/components/EventPollWidget.js +1 -1
  266. package/dist/features/events/components/SpinWheelView.js +3 -2
  267. package/dist/features/filters/FilterPanel.js +3 -3
  268. package/dist/features/filters/RangeFilter.d.ts +10 -0
  269. package/dist/features/filters/RangeFilter.js +41 -4
  270. package/dist/features/grouped/components/GroupedListingEditorView.js +2 -1
  271. package/dist/features/history/hooks/useHistory.js +29 -27
  272. package/dist/features/homepage/components/AdSlot.js +3 -1
  273. package/dist/features/homepage/components/GoogleReviewsSection.js +8 -2
  274. package/dist/features/homepage/components/MarketplaceHomepageView.js +58 -15
  275. package/dist/features/homepage/components/SocialFeedSection.js +4 -1
  276. package/dist/features/homepage/hooks/useBlogArticles.js +1 -1
  277. package/dist/features/homepage/lib/live-stats.js +8 -1
  278. package/dist/features/homepage/repository/carousel.repository.js +2 -1
  279. package/dist/features/layout/BottomActions.js +84 -11
  280. package/dist/features/layout/BottomActionsContext.d.ts +1 -6
  281. package/dist/features/layout/BottomActionsContext.js +1 -1
  282. package/dist/features/layout/hooks/useBottomActions.js +2 -2
  283. package/dist/features/layout/hooks/useFormBottomActions.d.ts +12 -1
  284. package/dist/features/layout/hooks/useFormBottomActions.js +12 -6
  285. package/dist/features/live/components/LiveItemDetailPageView.js +4 -1
  286. package/dist/features/lottery/schemas/config-write.d.ts +9 -5
  287. package/dist/features/lottery/schemas/config-write.js +69 -9
  288. package/dist/features/media/finalize.js +3 -1
  289. package/dist/features/media/modals/ImageEditor.js +1 -1
  290. package/dist/features/media/modals/VideoThumbnailSelector.js +5 -2
  291. package/dist/features/media/modals/VideoTrimModal.js +5 -2
  292. package/dist/features/media/upload/MediaUploadField.js +2 -2
  293. package/dist/features/messages/hooks/useConversation.js +18 -4
  294. package/dist/features/messages/hooks/useConversations.js +25 -8
  295. package/dist/features/orders/components/RefundRequestView.js +5 -2
  296. package/dist/features/orders/schemas/buyer-forms.d.ts +6 -3
  297. package/dist/features/orders/schemas/buyer-forms.js +31 -3
  298. package/dist/features/orders/schemas/index.d.ts +2 -2
  299. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -1
  300. package/dist/features/pre-orders/components/PreOrderActionsClient.js +1 -1
  301. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -1
  302. package/dist/features/products/components/GroupSettingsPanel.js +3 -1
  303. package/dist/features/products/components/MakeOfferButton.d.ts +13 -2
  304. package/dist/features/products/components/MakeOfferButton.js +33 -11
  305. package/dist/features/products/components/PrizeDrawDetailPageView.js +4 -1
  306. package/dist/features/products/components/ProductDetailActions.js +0 -1
  307. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -3
  308. package/dist/features/products/components/ProductDetailPageView.js +16 -7
  309. package/dist/features/products/components/ShareButton.js +3 -1
  310. package/dist/features/products/constants/action-defs.d.ts +1 -2
  311. package/dist/features/products/constants/action-defs.js +40 -43
  312. package/dist/features/products/repository/loadProductFeatures.js +10 -2
  313. package/dist/features/products/repository/products.repository.d.ts +5 -1
  314. package/dist/features/products/repository/products.repository.js +25 -2
  315. package/dist/features/products/schemas/firestore.d.ts +7 -3
  316. package/dist/features/products/schemas/product-features.validators.js +3 -0
  317. package/dist/features/promotions/components/CouponCard.js +11 -8
  318. package/dist/features/reviews/actions/review-actions.js +8 -1
  319. package/dist/features/reviews/components/ReviewDetailPageView.js +13 -3
  320. package/dist/features/reviews/components/ReviewDetailShell.js +3 -1
  321. package/dist/features/scams/actions/scam-actions.d.ts +9 -1
  322. package/dist/features/scams/actions/scam-actions.js +52 -12
  323. package/dist/features/scams/components/SellerTrustBadge.d.ts +1 -1
  324. package/dist/features/scams/components/SellerTrustBadge.js +13 -0
  325. package/dist/features/scams/repository/scammer.repository.js +2 -1
  326. package/dist/features/scams/schemas/firestore.d.ts +15 -1
  327. package/dist/features/scams/schemas/report-form.d.ts +5 -5
  328. package/dist/features/scams/schemas/report-form.js +18 -3
  329. package/dist/features/search/action-index/derive.d.ts +49 -0
  330. package/dist/features/search/action-index/derive.js +121 -0
  331. package/dist/features/search/action-index/repository.d.ts +42 -0
  332. package/dist/features/search/action-index/repository.js +100 -0
  333. package/dist/features/search/action-index/settings-entries.d.ts +41 -0
  334. package/dist/features/search/action-index/settings-entries.js +268 -0
  335. package/dist/features/search/action-index/types.d.ts +81 -0
  336. package/dist/features/search/action-index/types.js +102 -0
  337. package/dist/features/search/action-index/validate-entry.d.ts +34 -0
  338. package/dist/features/search/action-index/validate-entry.js +114 -0
  339. package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
  340. package/dist/features/search/hooks/useNavSuggestions.js +23 -3
  341. package/dist/features/search/repository/search.repository.js +3 -1
  342. package/dist/features/seller/actions/offer-actions.js +43 -19
  343. package/dist/features/seller/actions/seller-actions.js +29 -5
  344. package/dist/features/seller/components/CategoryChipPicker.d.ts +9 -0
  345. package/dist/features/seller/components/CategoryChipPicker.js +12 -0
  346. package/dist/features/seller/components/FulfillmentView.js +5 -2
  347. package/dist/features/seller/components/PhysicalLocationModal.js +1 -1
  348. package/dist/features/seller/components/PrintCenterView.js +3 -1
  349. package/dist/features/seller/components/SellerAddressesView.js +5 -1
  350. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +83 -43
  351. package/dist/features/seller/components/SellerArtView.d.ts +14 -7
  352. package/dist/features/seller/components/SellerArtView.js +4 -119
  353. package/dist/features/seller/components/SellerBidsView.js +1 -1
  354. package/dist/features/seller/components/SellerClassifiedView.d.ts +14 -7
  355. package/dist/features/seller/components/SellerClassifiedView.js +4 -124
  356. package/dist/features/seller/components/SellerCouponEditorView.d.ts +22 -1
  357. package/dist/features/seller/components/SellerCouponEditorView.js +91 -45
  358. package/dist/features/seller/components/SellerCouponsView.js +1 -1
  359. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +14 -7
  360. package/dist/features/seller/components/SellerDigitalCodesView.js +4 -126
  361. package/dist/features/seller/components/SellerGoogleReviewsView.js +2 -2
  362. package/dist/features/seller/components/SellerListingTypeView.d.ts +66 -0
  363. package/dist/features/seller/components/SellerListingTypeView.js +277 -0
  364. package/dist/features/seller/components/SellerLiveView.d.ts +14 -7
  365. package/dist/features/seller/components/SellerLiveView.js +4 -126
  366. package/dist/features/seller/components/SellerOrdersView.js +3 -3
  367. package/dist/features/seller/components/SellerPayoutMethodsView.js +2 -2
  368. package/dist/features/seller/components/SellerProductShell.d.ts +5 -0
  369. package/dist/features/seller/components/SellerProductShell.js +15 -5
  370. package/dist/features/seller/components/SellerProductsView.js +24 -10
  371. package/dist/features/seller/components/SellerReviewsView.js +47 -16
  372. package/dist/features/seller/components/SellerShippingConfigsView.js +5 -4
  373. package/dist/features/seller/components/SellerSidebar.d.ts +3 -6
  374. package/dist/features/seller/components/SellerStickersView.d.ts +14 -7
  375. package/dist/features/seller/components/SellerStickersView.js +4 -119
  376. package/dist/features/seller/components/SellerStoreCategoriesView.js +3 -2
  377. package/dist/features/seller/components/SellerSupportView.d.ts +31 -0
  378. package/dist/features/seller/components/SellerSupportView.js +100 -0
  379. package/dist/features/seller/components/index.d.ts +4 -8
  380. package/dist/features/seller/components/index.js +2 -4
  381. package/dist/features/seller/index.d.ts +0 -1
  382. package/dist/features/seller/index.js +0 -1
  383. package/dist/features/seller/schemas/coupon-form.d.ts +214 -0
  384. package/dist/features/seller/schemas/coupon-form.js +155 -23
  385. package/dist/features/seller/schemas/offer-forms.d.ts +16 -7
  386. package/dist/features/seller/schemas/offer-forms.js +28 -12
  387. package/dist/features/shell/FormShell.js +2 -2
  388. package/dist/features/shell/QuickFormDrawer.js +2 -2
  389. package/dist/features/shell/SectionForm.d.ts +19 -1
  390. package/dist/features/shell/SectionForm.js +104 -17
  391. package/dist/features/shell/build-sections.d.ts +23 -0
  392. package/dist/features/shell/build-sections.js +106 -8
  393. package/dist/features/shell/field-ui-meta.d.ts +44 -0
  394. package/dist/features/shell/field-ui-meta.js +14 -7
  395. package/dist/features/shell/form-sections.d.ts +46 -0
  396. package/dist/features/shell/form-sections.js +114 -0
  397. package/dist/features/shipments/components/AdminShipmentEditorView.js +87 -64
  398. package/dist/features/shipments/schemas/validation.d.ts +2 -2
  399. package/dist/features/shipments/schemas/validation.js +44 -13
  400. package/dist/features/store-extensions/schemas/custom-role-form.js +6 -2
  401. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +7 -7
  402. package/dist/features/store-extensions/schemas/payout-method-form.js +14 -0
  403. package/dist/features/stores/actions/store-query-actions.js +3 -2
  404. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -1
  405. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -1
  406. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  407. package/dist/features/stores/components/StoreDetailLayoutView.js +12 -3
  408. package/dist/features/support/repository/support.repository.d.ts +21 -0
  409. package/dist/features/support/repository/support.repository.js +63 -0
  410. package/dist/features/support/schemas/firestore.d.ts +27 -2
  411. package/dist/features/support/schemas/firestore.js +2 -0
  412. package/dist/features/support/schemas/ticket-create-form.js +7 -0
  413. package/dist/features/tester/schemas/checklist-item-form.d.ts +43 -0
  414. package/dist/features/tester/schemas/checklist-item-form.js +109 -0
  415. package/dist/features/tester/seed-data/blog-tester-seed-data.js +2 -2
  416. package/dist/features/tester/seed-data/categories-tester-seed-data.js +2 -2
  417. package/dist/features/tester/seed-data/events-tester-seed-data.js +5 -5
  418. package/dist/features/tester/seed-data/orders-tester-seed-data.js +2 -2
  419. package/dist/features/tester/seed-data/products-tester-seed-data.js +48 -49
  420. package/dist/features/tester/seed-data/stores-tester-seed-data.js +3 -3
  421. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +291 -30
  422. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +51 -14
  423. package/dist/features/wishlist/hooks/useWishlistToggle.js +8 -1
  424. package/dist/http/api-handler.js +2 -1
  425. package/dist/index.d.ts +25 -104
  426. package/dist/index.js +38 -143
  427. package/dist/jobs.d.ts +1 -1
  428. package/dist/jobs.js +1 -1
  429. package/dist/monitoring/browser.d.ts +0 -2
  430. package/dist/monitoring/browser.js +12 -1
  431. package/dist/monitoring/index.d.ts +0 -2
  432. package/dist/monitoring/index.js +0 -1
  433. package/dist/next/api/routeHandler.js +9 -4
  434. package/dist/next/index.d.ts +1 -2
  435. package/dist/next/index.js +0 -1
  436. package/dist/next/middleware/index.d.ts +0 -2
  437. package/dist/next/middleware/index.js +0 -1
  438. package/dist/next/routing/route-map.d.ts +8 -7
  439. package/dist/next/routing/route-map.js +10 -3
  440. package/dist/providers/auth-firebase/session.js +3 -1
  441. package/dist/providers/db-firebase/admin-app-lite.js +8 -8
  442. package/dist/providers/db-firebase/admin.js +27 -13
  443. package/dist/providers/db-firebase/browser-stub.d.ts +1 -11
  444. package/dist/providers/db-firebase/browser-stub.js +6 -11
  445. package/dist/providers/db-firebase/index.d.ts +0 -1
  446. package/dist/providers/db-firebase/index.js +0 -1
  447. package/dist/providers/db-firebase/rtdb-paths.d.ts +15 -6
  448. package/dist/providers/db-firebase/rtdb-paths.js +15 -6
  449. package/dist/providers/db-firebase/rtdb-url.d.ts +27 -0
  450. package/dist/providers/db-firebase/rtdb-url.js +50 -0
  451. package/dist/providers/db-firebase/sieve.d.ts +22 -0
  452. package/dist/providers/firebase-client/realtime.d.ts +20 -0
  453. package/dist/providers/firebase-client/realtime.js +34 -1
  454. package/dist/providers.d.ts +1 -1
  455. package/dist/providers.js +1 -1
  456. package/dist/react/hooks/use-action-dispatch.js +8 -2
  457. package/dist/react/hooks/useFormStatePreservation.js +3 -1
  458. package/dist/react/hooks/useListingTypeFlags.d.ts +1 -1
  459. package/dist/react/hooks/useListingTypeFlags.js +1 -1
  460. package/dist/react/hooks/usePendingFilters.js +5 -3
  461. package/dist/react/hooks/usePendingTable.js +15 -3
  462. package/dist/react/hooks/useRealtimeEvent.js +20 -9
  463. package/dist/repositories/index.d.ts +0 -1
  464. package/dist/repositories/index.js +0 -1
  465. package/dist/schemas/registry.d.ts +197 -232
  466. package/dist/schemas/registry.js +5 -6
  467. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  468. package/dist/security/authorization.d.ts +9 -0
  469. package/dist/security/authorization.js +16 -2
  470. package/dist/security/browser.d.ts +1 -3
  471. package/dist/security/browser.js +5 -2
  472. package/dist/security/index.d.ts +1 -3
  473. package/dist/security/index.js +19 -2
  474. package/dist/security/pii-encrypt.js +16 -0
  475. package/dist/security/pii-schemas.d.ts +0 -14
  476. package/dist/security/pii-schemas.js +7 -15
  477. package/dist/seed/_helpers/category-forest.js +2 -2
  478. package/dist/seed/_helpers/media.d.ts +19 -0
  479. package/dist/seed/_helpers/media.js +75 -0
  480. package/dist/seed/blog-posts-seed-data.js +17 -17
  481. package/dist/seed/carousel-slides-seed-data.js +7 -7
  482. package/dist/seed/cart-seed-data.js +13 -13
  483. package/dist/seed/categories-seed-data.js +10 -10
  484. package/dist/seed/events-seed-data.js +24 -24
  485. package/dist/seed/factories/product.factory.js +4 -4
  486. package/dist/seed/factories/store.factory.js +3 -3
  487. package/dist/seed/factories/user.factory.js +2 -2
  488. package/dist/seed/grouped-listings-seed-data.js +11 -11
  489. package/dist/seed/homepage-sections-seed-data.js +2 -2
  490. package/dist/seed/index.d.ts +0 -1
  491. package/dist/seed/index.js +0 -1
  492. package/dist/seed/manifest.d.ts +1 -1
  493. package/dist/seed/manifest.js +0 -5
  494. package/dist/seed/notifications-seed-data.js +20 -1
  495. package/dist/seed/orders-seed-data.js +2 -2
  496. package/dist/seed/products-art-seed-data.js +16 -16
  497. package/dist/seed/products-auctions-seed-data.js +34 -34
  498. package/dist/seed/products-classifieds-seed-data.js +35 -37
  499. package/dist/seed/products-digital-codes-seed-data.js +20 -20
  500. package/dist/seed/products-live-items-seed-data.js +11 -11
  501. package/dist/seed/products-preorders-seed-data.js +13 -13
  502. package/dist/seed/products-prize-draws-seed-data.js +43 -43
  503. package/dist/seed/products-standard-seed-data.js +54 -54
  504. package/dist/seed/products-stickers-seed-data.js +15 -15
  505. package/dist/seed/reviews-seed-data.js +4 -4
  506. package/dist/seed/scammers-seed-data.js +3 -3
  507. package/dist/seed/site-settings-seed-data.js +11 -24
  508. package/dist/seed/store-extensions-seed-data.js +10 -10
  509. package/dist/seed/stores-seed-data.js +11 -11
  510. package/dist/seed/support-tickets-seed-data.d.ts +14 -0
  511. package/dist/seed/support-tickets-seed-data.js +20 -1
  512. package/dist/seed/users-seed-data.js +20 -20
  513. package/dist/server-entry.d.ts +0 -1
  514. package/dist/server-entry.js +0 -1
  515. package/dist/server.d.ts +9 -6
  516. package/dist/server.js +7 -13
  517. package/dist/styles.css +248 -68
  518. package/dist/tailwind-utilities.css +1 -1
  519. package/dist/tokens/themes/default-dark.js +2 -2
  520. package/dist/tokens/themes/default-light.js +2 -2
  521. package/dist/tokens/tokens.css +24 -4
  522. package/dist/ui/components/ActionRow.d.ts +50 -0
  523. package/dist/ui/components/ActionRow.js +53 -0
  524. package/dist/ui/components/ActionRow.style.css +145 -0
  525. package/dist/ui/components/Drawer.d.ts +10 -0
  526. package/dist/ui/components/Drawer.js +2 -1
  527. package/dist/ui/components/FilterDrawer.js +4 -1
  528. package/dist/ui/components/FilterDrawer.style.css +6 -12
  529. package/dist/ui/components/FormActionBar.js +14 -1
  530. package/dist/ui/components/FormActionBar.style.css +12 -11
  531. package/dist/ui/components/ListingLayout.js +8 -1
  532. package/dist/ui/components/ListingLayout.style.css +6 -13
  533. package/dist/ui/components/Modal.d.ts +15 -1
  534. package/dist/ui/components/Modal.js +17 -3
  535. package/dist/ui/components/QuickCreateModal.js +2 -2
  536. package/dist/ui/components/QuickCreateModal.style.css +78 -84
  537. package/dist/ui/components/RecordDetailModal.js +2 -2
  538. package/dist/ui/components/SideDrawer.d.ts +8 -0
  539. package/dist/ui/components/SideDrawer.js +2 -1
  540. package/dist/ui/components/SideDrawer.style.css +6 -3
  541. package/dist/ui/components/SideModal.d.ts +11 -1
  542. package/dist/ui/components/SideModal.js +3 -2
  543. package/dist/ui/components/SideModal.style.css +14 -0
  544. package/dist/ui/components/Typography.style.css +33 -24
  545. package/dist/ui/components/index.style.css +1 -0
  546. package/dist/ui/components/toolbar-collapse.js +1 -2
  547. package/dist/ui/forms/FormShell.style.css +260 -311
  548. package/dist/ui/index.d.ts +2 -0
  549. package/dist/ui/index.js +1 -0
  550. package/dist/utils/index.d.ts +0 -1
  551. package/dist/utils/index.js +0 -1
  552. package/dist/validation/index.d.ts +1 -1
  553. package/dist/validation/index.js +1 -1
  554. package/dist/validation/schemas.d.ts +0 -19
  555. package/dist/validation/schemas.js +8 -19
  556. package/package.json +1 -1
  557. package/scripts/seed-cli.mjs +3 -6
@@ -8,6 +8,7 @@ import { productRepository } from "../../../../repositories";
8
8
  import { reviewRepository } from "../../../../repositories";
9
9
  import { getAdminDb } from "../../../../providers/db-firebase";
10
10
  import { normalizeError } from "../../../../errors/normalize";
11
+ import { safeRead } from "../../../../errors/safe-read";
11
12
  import { serverLogger } from "../../../../monitoring/server-logger";
12
13
  import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firestore";
13
14
  import { PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
@@ -23,15 +24,29 @@ import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/confi
23
24
  // ---------------------------------------------------------------------------
24
25
  /** Fetch a single product by slug or id, deduped per request. */
25
26
  export const getProductForDetail = cache(async (slugOrId) => {
26
- const product = (await productRepository.findByIdOrSlug(slugOrId).catch(() => undefined)) ?? null;
27
- // Fire-and-forget viewCount increment — must not affect response time
28
- if (product)
29
- void productRepository.incrementViewCount(product.id);
30
- return product;
27
+ /*
28
+ * 🛑 No view-count write here — deleted 2026-08-31.
29
+ *
30
+ * This fired `incrementViewCount` on every render, which was two defects at
31
+ * once. It is a Firestore WRITE on the render path (Rule #6), and product
32
+ * and category detail were being counted TWICE by two systems that key
33
+ * differently: this one on the document by id, and the client
34
+ * `PageViewTracker` into `pageViews` by slug. They can never reconcile —
35
+ * they disagree on bots, on prefetch, and on JS-disabled clients — so the
36
+ * two numbers were guaranteed to drift and neither could be trusted.
37
+ *
38
+ * `pageViews` is the single counter now. `viewCount` on the document stays
39
+ * for historical rows; nothing increments it.
40
+ */
41
+ return (await productRepository.findByIdOrSlug(slugOrId)) ?? null;
31
42
  });
32
43
  /** Fetch the first page of approved reviews for a product, deduped per request. */
33
44
  export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
34
- return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
45
+ return safeRead(() => reviewRepository.findApprovedByProduct(productId, limit), {
46
+ route: "/products",
47
+ key: "products.getReviewsForProduct",
48
+ fallback: [],
49
+ });
35
50
  });
36
51
  /** Firestore review doc → client Review shape. Shared by every listing-type detail page's reviews tab. */
37
52
  export function toReview(doc) {
@@ -157,6 +157,15 @@ export interface PublicProductListOptions {
157
157
  */
158
158
  now?: Date;
159
159
  }
160
+ /** Every field path a per-type facet can filter on. Read by the wiring audit. */
161
+ export declare const TYPE_FACET_FIELD_PATHS: string[];
162
+ /**
163
+ * The ONE sort a per-type facet query is fetched at, so each facet needs one
164
+ * composite index instead of one per offered sort. The user's chosen sort is
165
+ * applied in memory afterwards. Changing this string invalidates all 8 indexes
166
+ * — the audit derives its expected shapes from it, so it will say so.
167
+ */
168
+ export declare const FACET_FETCH_SORT: string;
160
169
  /**
161
170
  * Read the shared public-listing query params. SSR views hand in Next's
162
171
  * `searchParams` record; the API route hands in `url.searchParams`. Both produce
@@ -104,6 +104,16 @@ function num(raw) {
104
104
  * declared these keys in their FILTER_KEYS (so they counted toward the
105
105
  * filter badge) and rendered the controls, but never put them on the wire.
106
106
  */
107
+ /*
108
+ * Facet key → the Firestore field path it filters.
109
+ *
110
+ * 🛑 Every value here MUST have an entry in the products `SIEVE_FIELDS`, and
111
+ * a `(status, listingType, <field>, createdAt DESC)` composite index. Miss the
112
+ * first and sievejs drops the clause silently; miss the second and the query
113
+ * throws FAILED_PRECONDITION, which `runQuery` logs and turns into an empty
114
+ * grid. Both read to the user as "this filter matches nothing".
115
+ * `scripts/audit-type-facet-wiring.mjs` checks all three ends.
116
+ */
107
117
  const TYPE_FACET_FIELD = {
108
118
  [TABLE_KEYS.CITY]: "classified.meetupArea.city",
109
119
  [TABLE_KEYS.NEGOTIABLE]: "classified.negotiable",
@@ -112,7 +122,17 @@ const TYPE_FACET_FIELD = {
112
122
  [TABLE_KEYS.SPECIES]: "liveItem.species",
113
123
  [TABLE_KEYS.LIVE_SEX]: "liveItem.sex",
114
124
  [TABLE_KEYS.JURISDICTION]: "liveItem.jurisdictionAllowed",
125
+ [TABLE_KEYS.LIVE_TRANSPORT_METHOD]: "liveItem.transport.method",
115
126
  };
127
+ /** Every field path a per-type facet can filter on. Read by the wiring audit. */
128
+ export const TYPE_FACET_FIELD_PATHS = Object.values(TYPE_FACET_FIELD);
129
+ /**
130
+ * The ONE sort a per-type facet query is fetched at, so each facet needs one
131
+ * composite index instead of one per offered sort. The user's chosen sort is
132
+ * applied in memory afterwards. Changing this string invalidates all 8 indexes
133
+ * — the audit derives its expected shapes from it, so it will say so.
134
+ */
135
+ export const FACET_FETCH_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
116
136
  /** Read every known per-type facet present in the params. */
117
137
  function readTypeFacets(get) {
118
138
  const out = {};
@@ -287,12 +307,21 @@ function buildFirestoreSafeFilters(input) {
287
307
  // Search now travels as `query.search` (see PublicProductQuery) and becomes an
288
308
  // `array-contains` on `searchTxt` inside the repository, which is the only
289
309
  // shape Firestore can serve for token matching.
290
- // `array-contains-any` isn't supported by the Sieve Firebase adapter, so only a
291
- // single feature can be pushed down; multi-select falls through to the caller.
310
+ /*
311
+ * `array-contains-any` isn't supported by the Sieve Firebase adapter, so one
312
+ * value pushes down as `array-contains` and several cannot.
313
+ *
314
+ * "Falls through to the caller" was the old comment here, and no caller ever
315
+ * caught it — a two-tag selection filtered NOTHING while the chips stayed lit
316
+ * and the badge counted them. The multi-value case now runs as a per-row
317
+ * predicate over the bounded window (see `hasArrayMultiSelect` below), which
318
+ * is the same trade the per-type facets make. A partial pushdown is not an
319
+ * option: `array-contains` on the first value would return a strict SUBSET
320
+ * for OR semantics, quietly hiding rows that match only the second.
321
+ */
292
322
  if (input.features?.length === 1) {
293
323
  parts.push(sieveFilter(PRODUCT_FIELDS.FEATURES, SIEVE_OP.CONTAINS, input.features[0]));
294
324
  }
295
- // `tags` is an array field, so the same one-value limit applies.
296
325
  if (input.tags?.length === 1) {
297
326
  parts.push(sieveFilter(PRODUCT_FIELDS.TAGS, SIEVE_OP.CONTAINS, input.tags[0]));
298
327
  }
@@ -504,7 +533,29 @@ export async function listPublicProducts(input, opts) {
504
533
  // ── Path A ("all") and the dense "available" case share one query. The only
505
534
  // difference is whether a per-row predicate runs afterwards.
506
535
  const filters = sieveAnd(safeFilters, ...dateClauses);
507
- const inMemory = wantAvailable || (hasDateRange && !canPushDate);
536
+ /*
537
+ * A per-type facet IS pushed down — it is a sparse equality, which is the
538
+ * side of the availability asymmetry that belongs in the query. What is not
539
+ * tractable is the SORT cross-product: `(status, listingType, <facet>,
540
+ * <sort>)` over 8 facets and the 7 publicly-offered sorts is 56 composite
541
+ * indexes, and every sort added later costs 8 more.
542
+ *
543
+ * So the facet goes down and the SORT comes back up: fetch a bounded window
544
+ * ordered by the one sort we index for every facet, then re-order in memory.
545
+ * 56 indexes become 8, a new sort costs none, and `truncated` keeps the
546
+ * pager honest — the same trade `fetchSorts` already makes for a date range
547
+ * that cannot ride its own sort.
548
+ *
549
+ * Before this, none of the 8 had a public index at all: every facet on
550
+ * /classified, /live and /digital-codes threw FAILED_PRECONDITION, which
551
+ * `runQuery` logs and returns as null, i.e. an empty grid. The store-scoped
552
+ * copies were indexed for 3 of the 7 sorts, so those pages worked until
553
+ * someone chose "Name: A–Z".
554
+ */
555
+ const hasTypeFacet = Object.keys(input.typeFacets ?? {}).length > 0;
556
+ /** Multi-value `tags`/`features` — no `array-contains-any`, so per-row. */
557
+ const hasArrayMultiSelect = (input.tags?.length ?? 0) > 1 || (input.features?.length ?? 0) > 1;
558
+ const inMemory = wantAvailable || hasTypeFacet || hasArrayMultiSelect || (hasDateRange && !canPushDate);
508
559
  // When a date range stays in memory, fetch in whichever direction front-loads
509
560
  // the rows that will PASS it: `dateFrom` (">=", still live) wants the most
510
561
  // future dates first; `dateTo` ("<=", ends by) wants the earliest first.
@@ -514,7 +565,9 @@ export async function listPublicProducts(input, opts) {
514
565
  ? input.dateFrom
515
566
  ? sortBy(dateField, "DESC")
516
567
  : sortBy(dateField, "ASC")
517
- : sorts;
568
+ : hasTypeFacet
569
+ ? FACET_FETCH_SORT
570
+ : sorts;
518
571
  const result = await runQuery(executor, {
519
572
  filters,
520
573
  sorts: fetchSorts,
@@ -534,9 +587,24 @@ export async function listPublicProducts(input, opts) {
534
587
  let truncated = false;
535
588
  if (inMemory) {
536
589
  truncated = items.length >= PUBLIC_PRODUCT_MAX_PAGE_SIZE;
590
+ /** OR semantics, matching the single-value `array-contains` pushdown. */
591
+ const matchesAny = (raw, wanted) => {
592
+ if (!Array.isArray(raw))
593
+ return false;
594
+ return wanted.some((w) => raw.includes(w));
595
+ };
537
596
  const filtered = items.filter((item) => {
538
597
  if (wantAvailable && !isListingRowAvailable(item, now))
539
598
  return false;
599
+ if (hasArrayMultiSelect) {
600
+ if ((input.tags?.length ?? 0) > 1 && !matchesAny(item[PRODUCT_FIELDS.TAGS], input.tags)) {
601
+ return false;
602
+ }
603
+ if ((input.features?.length ?? 0) > 1 &&
604
+ !matchesAny(item[PRODUCT_FIELDS.FEATURES], input.features)) {
605
+ return false;
606
+ }
607
+ }
540
608
  if (hasDateRange && !canPushDate && dateField) {
541
609
  const raw = item[dateField];
542
610
  if (!raw)
@@ -638,10 +706,30 @@ export async function listStoreProducts(storeId, listingTypes, searchParams = {}
638
706
  storeId,
639
707
  });
640
708
  }
709
+ /**
710
+ * Comparable primitive for one row value.
711
+ *
712
+ * 🛑 The `Date` branch is load-bearing. Without it a Date falls through to
713
+ * `String(value)` — `"Wed Aug 30 2026 …"` — and a lexical compare of those
714
+ * orders by WEEKDAY NAME. Every in-memory date sort was wrong: the "Sold &
715
+ * Ended" tab ordered by `createdAt`/`auctionEndDate`, and now every per-type
716
+ * facet under "Oldest First". A Firestore Timestamp arrives here as a Date via
717
+ * `mapDoc`, so the ISO-string case is only for a row that has already been
718
+ * serialised; both are handled.
719
+ */
720
+ function sortKey(value) {
721
+ if (value == null)
722
+ return null;
723
+ if (value instanceof Date)
724
+ return value.getTime();
725
+ if (typeof value === "number" || typeof value === "string")
726
+ return value;
727
+ return String(value);
728
+ }
641
729
  function sortRows(rows, field, desc) {
642
730
  return [...rows].sort((a, b) => {
643
- const av = a[field];
644
- const bv = b[field];
731
+ const av = sortKey(a[field]);
732
+ const bv = sortKey(b[field]);
645
733
  if (av == null && bv == null)
646
734
  return 0;
647
735
  if (av == null)
@@ -12,7 +12,7 @@ const ALLOWED_STATUS_TRANSITIONS = {
12
12
  };
13
13
  /** Assert a product exists and belongs to the given seller. Throws on failure. */
14
14
  export async function assertProductOwnership(productId, sellerId) {
15
- const product = await productRepository.findById(productId).catch(() => null);
15
+ const product = await productRepository.findById(productId);
16
16
  if (!product)
17
17
  throw new ProductNotFoundError(productId);
18
18
  if (product.storeId !== sellerId)
@@ -33,7 +33,7 @@ export async function createCouponAction(input) {
33
33
  export async function updateCouponAction(couponId, input) {
34
34
  return wrapAction(async () => {
35
35
  await requireRoleUser(["admin", "seller"]);
36
- const coupon = await couponsRepository.findById(couponId).catch(() => null);
36
+ const coupon = await couponsRepository.findById(couponId);
37
37
  if (!coupon)
38
38
  throw new CouponNotFoundError(couponId);
39
39
  const parsed = updateCouponSchema.safeParse(input);
@@ -1,5 +1,5 @@
1
1
  import { cache } from "react";
2
2
  import { couponsRepository } from "../../../../repositories";
3
3
  export const getCouponByCode = cache(async (code) => {
4
- return (await couponsRepository.getCouponByCode(code).catch(() => undefined)) ?? null;
4
+ return (await couponsRepository.getCouponByCode(code)) ?? null;
5
5
  });
@@ -2,7 +2,7 @@ import { couponsRepository } from "../../../../repositories";
2
2
  import { roundRupees } from "../../../../utils/number.formatter";
3
3
  import { CouponNotFoundError, CouponExpiredError, CouponUsageLimitError, CouponPerUserLimitError, CouponMinPurchaseError, CouponScopeError, } from "../../../shared/features/promotions/errors";
4
4
  export async function validateCoupon(input, userId) {
5
- const coupon = await couponsRepository.getCouponByCode(input.code).catch(() => null);
5
+ const coupon = await couponsRepository.getCouponByCode(input.code);
6
6
  if (!coupon)
7
7
  throw new CouponNotFoundError(input.code);
8
8
  if (!coupon.validity?.isActive)
@@ -1,4 +1,4 @@
1
- export declare const getReviewsForProduct: (productId: string, limit?: number) => Promise<never[] | import("../../../..").ReviewDocument[]>;
1
+ export declare const getReviewsForProduct: (productId: string, limit?: number) => Promise<import("../../../..").ReviewDocument[]>;
2
2
  export declare const getReviewsForStore: (storeId: string, page?: number, pageSize?: number) => Promise<import("../../../..").FirebaseSieveResult<import("../../../..").ReviewDocument> | {
3
3
  items: never[];
4
4
  total: number;
@@ -1,8 +1,13 @@
1
1
  import { cache } from "react";
2
2
  import { reviewRepository, orderRepository } from "../../../../repositories";
3
+ import { safeRead } from "../../../../errors/safe-read";
3
4
  import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/config";
4
5
  export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
5
- return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
6
+ return safeRead(() => reviewRepository.findApprovedByProduct(productId, limit), {
7
+ route: "/products",
8
+ key: "reviews.getReviewsForProduct",
9
+ fallback: [],
10
+ });
6
11
  });
7
12
  export const getReviewsForStore = cache(async (storeId, page = 1, pageSize = 20) => {
8
13
  return reviewRepository.listForStore(storeId, { page, pageSize }).catch(() => ({
@@ -3,5 +3,5 @@ import { reviewRepository } from "../../../../repositories";
3
3
  export const getReviewById = cache(async (id) => {
4
4
  if (!id)
5
5
  return null;
6
- return reviewRepository.findById(id).catch(() => null);
6
+ return reviewRepository.findById(id);
7
7
  });
@@ -1,5 +1,6 @@
1
1
  import { cache } from "react";
2
2
  import { productRepository } from "../../../../repositories";
3
+ import { safeRead } from "../../../../errors/safe-read";
3
4
  import { loadProductFeaturesForStore } from "../../../../features/products/repository/loadProductFeatures";
4
5
  // Returns a React.cache()-wrapped fetch gated on one listing type.
5
6
  // The guard prevents cross-type URL collisions (e.g. /auctions/classified-slug returning wrong product).
@@ -7,7 +8,7 @@ export function makeGetListingForDetail(type) {
7
8
  return cache(async (slugOrId) => {
8
9
  if (!slugOrId)
9
10
  return null;
10
- const product = await productRepository.findByIdOrSlug(slugOrId).catch(() => undefined);
11
+ const product = await productRepository.findByIdOrSlug(slugOrId);
11
12
  if (!product || product.listingType !== type)
12
13
  return null;
13
14
  return product;
@@ -15,7 +16,11 @@ export function makeGetListingForDetail(type) {
15
16
  }
16
17
  // Product-feature badges are keyed by store, not listing type.
17
18
  // Per-type data.ts files re-export this under a type-specific name.
18
- export const getProductFeaturesForStore = cache(async (storeId) => loadProductFeaturesForStore(storeId).catch(() => []));
19
+ export const getProductFeaturesForStore = cache(async (storeId) => safeRead(() => loadProductFeaturesForStore(storeId), {
20
+ route: "/products",
21
+ key: "products.getProductFeaturesForStore",
22
+ fallback: [],
23
+ }));
19
24
  // Returns a React.cache()-wrapped function for store SSR first-page data.
20
25
  // Export names in stores/data.ts stay identical so consumers need no changes.
21
26
  export function makeGetStoreListingsInitial(type, pageSize) {
@@ -13,7 +13,7 @@
13
13
  * `legalPages`), which meant everything else was public by default and
14
14
  * edge-cached for 300s. That shipped `gst.gstin` + the registered legal name
15
15
  * and address, all 25 `commissions.*` fields, `laborRate.hourlyRate`,
16
- * `emi.surchargeSellerSharePercent`, `featureFlags.adminCheckoutBypass`, and —
16
+ * `emi.surchargeSellerSharePercent`, `payment.adminCheckoutBypass`, and —
17
17
  * because they weren't even declared on the interface —
18
18
  * `adSettings.providerCredentials` (which the ADMIN ads endpoint bothers to
19
19
  * mask) plus every draft/paused/scheduled ad in `adSettings.inventory`.
@@ -37,7 +37,7 @@ import type { PublicSiteSettings } from "../../../shared/features/site-settings/
37
37
  * was traced to a call site; anything without one belongs in the private list,
38
38
  * because an unused public field is pure attack surface.
39
39
  */
40
- export declare const PUBLIC_SITE_SETTINGS_FIELDS: readonly ["contact", "payment", "featureFlags", "notificationChannels", "announcementBar", "navConfig", "actionConfig", "background", "watermark"];
40
+ export declare const PUBLIC_SITE_SETTINGS_FIELDS: readonly ["contact", "payment", "listings", "notificationChannels", "announcementBar", "navConfig", "actionConfig", "background", "watermark"];
41
41
  /** Everything else, with the reason it stays server-side. */
42
42
  export declare const PRIVATE_SITE_SETTINGS_FIELDS: readonly ["id", "createdAt", "updatedAt", "credentials", "emailSettings", "legalPages", "commissions", "emi", "gst", "laborRate", "adSettings", "integrations", "platformLimits", "auctionConfig", "disabledRoutes", "siteName", "tagline", "motto", "logo", "favicon", "seo", "socialLinks", "features", "shipping", "returns", "faq", "aboutContent", "navbarConfig", "footerConfig", "theme", "featuredResults"];
43
43
  export interface ToPublicSiteSettingsOptions {
@@ -6,7 +6,7 @@
6
6
  export const PUBLIC_SITE_SETTINGS_FIELDS = [
7
7
  "contact", // user/orders/[id]/payment — contact.whatsappNumber (narrowed: no address/upiVpa)
8
8
  "payment", // CheckoutRouteClient — payment.otpCheckoutThreshold (narrowed)
9
- "featureFlags", // useListingTypeFlags — featureFlags.listingTypes (narrowed: no adminCheckoutBypass)
9
+ "listings", // useListingTypeFlags — narrowed to listings.listingTypes; categoryTypes has no client reader
10
10
  "notificationChannels", // NotificationPreferencesPanel + CheckoutRouteClient (whatsapp.otpEnabled)
11
11
  "announcementBar", // AdminSiteView; also the homepage banner
12
12
  "navConfig", // NavbarWithSettings, DashboardLayoutClient
@@ -51,8 +51,11 @@ export const PRIVATE_SITE_SETTINGS_FIELDS = [
51
51
  export function toPublicSiteSettings(doc, opts) {
52
52
  return {
53
53
  // Explicit sub-literals, never spreads. These two are the ones that carry
54
- // real weight: `contact` drops `address` + `upiVpa`, and `featureFlags`
55
- // drops `adminCheckoutBypass` — the flag that turns off payment and OTP.
54
+ // real weight: `contact` drops `address` + `upiVpa`, and `payment` emits
55
+ // ONLY `otpCheckoutThreshold` — it also holds `adminCheckoutBypass` (the
56
+ // setting that skips OTP and payment capture) and `smsVerification`, both
57
+ // of which must never reach a client. `listings` is narrowed too:
58
+ // `categoryTypes` has no client reader.
56
59
  contact: {
57
60
  email: doc.contact?.email ?? "",
58
61
  phone: doc.contact?.phone ?? "",
@@ -61,8 +64,8 @@ export function toPublicSiteSettings(doc, opts) {
61
64
  payment: {
62
65
  otpCheckoutThreshold: doc.payment?.otpCheckoutThreshold,
63
66
  },
64
- featureFlags: {
65
- listingTypes: doc.featureFlags?.listingTypes,
67
+ listings: {
68
+ listingTypes: doc.listings?.listingTypes,
66
69
  },
67
70
  notificationChannels: doc.notificationChannels,
68
71
  announcementBar: doc.announcementBar,
@@ -5,7 +5,7 @@ import { STORES_PRODUCTS_PAGE_SIZE, STORES_SITEMAP_LIMIT, } from "../../../share
5
5
  import { makeGetStoreListingsInitial } from "../shared/listing-data-factory";
6
6
  /** Full store document by slug — deduped per request via React.cache(). */
7
7
  export const getStoreForDetail = cache(async (slug) => {
8
- return (await storeRepository.findBySlug(slug).catch(() => undefined)) ?? null;
8
+ return (await storeRepository.findBySlug(slug)) ?? null;
9
9
  });
10
10
  // Collapse of three near-identical 14-line functions via factory — export names unchanged.
11
11
  export const listStoreProductsInitial = makeGetStoreListingsInitial("standard", STORES_PRODUCTS_PAGE_SIZE);
@@ -1,5 +1,5 @@
1
1
  import { cache } from "react";
2
2
  import { categoriesRepository } from "../../../../repositories";
3
3
  export const getSublistingCategoryForDetail = cache(async (slug) => {
4
- return categoriesRepository.findBySlugAndType(slug, "sublisting").catch(() => null);
4
+ return categoriesRepository.findBySlugAndType(slug, "sublisting");
5
5
  });
@@ -2,8 +2,9 @@ export interface TestDataFlagged {
2
2
  isTestData?: boolean;
3
3
  }
4
4
  export interface ViewerLike {
5
- isTester?: boolean;
6
5
  role?: string | null;
6
+ /** @deprecated Legacy flag; read via isTesterUser during the migration window. */
7
+ isTester?: boolean;
7
8
  }
8
9
  /** True when the viewer is allowed to see isTestData:true documents. */
9
10
  export declare function canViewTestData(viewer: ViewerLike | null | undefined): boolean;
@@ -7,10 +7,10 @@
7
7
  * (a `!=` clause) would silently exclude every pre-existing document that doesn't have
8
8
  * the field set at all, which is worse than the problem it solves.
9
9
  */
10
- import { isAdminUser } from "../../../../features/auth/role-predicates";
10
+ import { isAdminUser, isTesterUser } from "../../../../features/auth/role-predicates";
11
11
  /** True when the viewer is allowed to see isTestData:true documents. */
12
12
  export function canViewTestData(viewer) {
13
- return Boolean(viewer?.isTester) || isAdminUser(viewer);
13
+ return isTesterUser(viewer) || isAdminUser(viewer);
14
14
  }
15
15
  /** Strips isTestData:true items from a list unless the viewer is a tester or admin. */
16
16
  export function filterTestDataForViewer(items, viewer) {
@@ -7,6 +7,8 @@ export declare const couponExpiry: import("./types").ScheduledFunctionDefinition
7
7
  export declare const offerExpiry: import("./types").ScheduledFunctionDefinition;
8
8
  export declare const productStatsSync: import("./types").ScheduledFunctionDefinition;
9
9
  export declare const revenueRollup: import("./types").ScheduledFunctionDefinition;
10
+ export declare const pageViewRollup: import("./types").ScheduledFunctionDefinition;
11
+ export declare const pageViewPrune: import("./types").ScheduledFunctionDefinition;
10
12
  export declare const dailyStatusDigest: import("./types").ScheduledFunctionDefinition;
11
13
  export declare const dailyDataCleanup: import("./types").ScheduledFunctionDefinition;
12
14
  export declare const countersReconcile: import("./types").ScheduledFunctionDefinition;
@@ -25,4 +27,4 @@ export declare const prizeDrawExpiryReveal: import("./types").ScheduledFunctionD
25
27
  export declare const bundleStockSync: import("./types").ScheduledFunctionDefinition;
26
28
  export declare const emiInstallmentReminder: import("./types").ScheduledFunctionDefinition;
27
29
  export declare const catalogueImageStalenessReminder: import("./types").ScheduledFunctionDefinition;
28
- export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
30
+ export declare const SCHEDULED_FUNCTIONS: readonly [import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition, import("./types").ScheduledFunctionDefinition];
@@ -1,6 +1,6 @@
1
1
  // Scheduled function definitions (Cloud Scheduler triggers).
2
2
  // Cron syntax accepts both Firebase shorthand and standard cron expressions.
3
- import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, testerSandboxRefreshHandler, } from "../jobs/handlers";
3
+ import { auctionSettlementHandler, autoPayoutEligibilityHandler, bundleStockSyncHandler, cartPruneHandler, cleanupRtdbEventsHandler, countersReconcileHandler, couponExpiryHandler, dailyDataCleanupHandler, draftPruneHandler, emiInstallmentReminderHandler, catalogueImageStalenessReminderHandler, mediaTmpCleanupHandler, notificationPruneHandler, offerExpiryHandler, payoutBatchHandler, paymentWindowTimeoutHandler, hardBanReinstatementHandler, paymentReviewAutoApproveHandler, pendingOrderTimeoutHandler, positionsReconcileHandler, prizeDrawExpiryRevealHandler, productStatsSyncHandler, revenueRollupHandler, pageViewRollupHandler, pageViewPruneHandler, dailyStatusDigestHandler, weeklyPayoutEligibilityHandler, testerSandboxCleanupHandler, testerSandboxRefreshHandler, } from "../jobs/handlers";
4
4
  import { defineFunction } from "./define";
5
5
  const REGION = "asia-south1";
6
6
  const EVERY_5_MIN = "every 5 minutes";
@@ -67,6 +67,30 @@ export const revenueRollup = defineFunction({
67
67
  handler: revenueRollupHandler,
68
68
  options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
69
69
  });
70
+ /*
71
+ * The rollup runs BEFORE the prune, and both before the retention boundary
72
+ * moves. `pageViews` had neither until 2026-08-31: it wrote one document per
73
+ * entity per day, forever, and was the only unbounded writer among 27 scheduled
74
+ * functions.
75
+ *
76
+ * Order matters — the prune deletes the day-buckets the rollup reads, so a
77
+ * prune that ran first would delete views nobody had counted yet. 01:45 then
78
+ * 02:15, either side of nothing else.
79
+ */
80
+ export const pageViewRollup = defineFunction({
81
+ name: "pageViewRollup",
82
+ description: "Fold new page-view day-buckets into analytics/pageViewRollup so the prune below cannot delete uncounted views (daily 01:45 UTC).",
83
+ trigger: { kind: "schedule", cron: "45 1 * * *", timeZone: "UTC" },
84
+ handler: pageViewRollupHandler,
85
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
86
+ });
87
+ export const pageViewPrune = defineFunction({
88
+ name: "pageViewPrune",
89
+ description: "Delete page-view day-buckets past the 90-day retention window; lifetime totals survive in the rollup (daily 02:15 UTC).",
90
+ trigger: { kind: "schedule", cron: "15 2 * * *", timeZone: "UTC" },
91
+ handler: pageViewPruneHandler,
92
+ options: { region: REGION, timeoutSeconds: 300, memory: "256MiB", maxInstances: 1 },
93
+ });
70
94
  export const dailyStatusDigest = defineFunction({
71
95
  name: "dailyStatusDigest",
72
96
  description: "Email the previous 24h order summary + platform status to the team every morning at 10:00 IST.",
@@ -203,6 +227,8 @@ export const SCHEDULED_FUNCTIONS = [
203
227
  offerExpiry,
204
228
  productStatsSync,
205
229
  revenueRollup,
230
+ pageViewRollup,
231
+ pageViewPrune,
206
232
  dailyStatusDigest,
207
233
  dailyDataCleanup,
208
234
  countersReconcile,
@@ -1,6 +1,7 @@
1
1
  import { bidRepository, cartRepository, productRepository, storeRepository, } from "../../../../repositories";
2
2
  import { sendNotification } from "../../../../features/admin/actions/notification-actions";
3
3
  import { normalizeError } from "../../../../errors/normalize";
4
+ import { safeRead } from "../../../../errors/safe-read";
4
5
  import { AUCTION_MESSAGES, BID_MESSAGES } from "../handlers/messages";
5
6
  import { ROUTES } from "../../../../next/routing/route-map";
6
7
  import { CART_LANE, LANE_CHECKOUT_WINDOW_MS } from "../../../shared/checkout/lanes";
@@ -94,7 +95,11 @@ async function settleAuction(ctx, product) {
94
95
  productRepository.updateStatusInBatch(batch, product.id, "archived");
95
96
  await batch.commit();
96
97
  const reserveStore = product.storeId
97
- ? await storeRepository.findById(product.storeId).catch(() => null)
98
+ ? await safeRead(() => storeRepository.findById(product.storeId), {
99
+ route: "job:auctionSettlement",
100
+ key: "auctionSettlement.reserveNotMet.store",
101
+ fallback: null,
102
+ })
98
103
  : null;
99
104
  if (reserveStore?.ownerId) {
100
105
  await sendNotification({
@@ -79,6 +79,10 @@ export async function runHardBanCascade(input, ctx) {
79
79
  // ban to take effect. Left unguarded so a failure here marks the whole job
80
80
  // failed (matches the original route's behavior of surfacing a 500).
81
81
  await userRepository.update(uid, {
82
+ // BOTH fields: every server-side guard reads `disabled`, and until
83
+ // 2026-08-29 no ban path wrote it — so a hard ban blocked a fresh login and
84
+ // left every existing session fully authenticated.
85
+ disabled: true,
82
86
  isDisabled: true,
83
87
  hardBanReason: reason,
84
88
  hardBannedAt: new Date(),
@@ -12,6 +12,8 @@ export { runCouponExpiry } from "./couponExpiry";
12
12
  export { runOfferExpiry } from "./offerExpiry";
13
13
  export { runCartPrune } from "./cartPrune";
14
14
  export { runNotificationPrune } from "./notificationPrune";
15
+ export { runPageViewRollup } from "./pageViewRollup";
16
+ export { runPageViewPrune, PAGE_VIEW_TTL_DAYS } from "./pageViewPrune";
15
17
  export { runDailyDataCleanup } from "./dailyDataCleanup";
16
18
  export { runCleanupRtdbEvents } from "./cleanupRtdbEvents";
17
19
  export { runMediaTmpCleanup } from "./mediaTmpCleanup";
@@ -13,6 +13,8 @@ export { runCouponExpiry } from "./couponExpiry";
13
13
  export { runOfferExpiry } from "./offerExpiry";
14
14
  export { runCartPrune } from "./cartPrune";
15
15
  export { runNotificationPrune } from "./notificationPrune";
16
+ export { runPageViewRollup } from "./pageViewRollup";
17
+ export { runPageViewPrune, PAGE_VIEW_TTL_DAYS } from "./pageViewPrune";
16
18
  export { runDailyDataCleanup } from "./dailyDataCleanup";
17
19
  export { runCleanupRtdbEvents } from "./cleanupRtdbEvents";
18
20
  export { runMediaTmpCleanup } from "./mediaTmpCleanup";
@@ -1,5 +1,3 @@
1
- import { normalizeError } from "../../../../errors/normalize";
2
- import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
3
1
  import { decryptPii } from "../../../../security/index";
4
2
  import { sendNotification } from "../../../../features/admin/actions/notification-actions";
5
3
  import { ORDER_MESSAGES } from "../handlers/messages";
@@ -58,19 +56,13 @@ export async function handleOrderStatusChange(input, ctx) {
58
56
  userEmail,
59
57
  orderWhatsappAddonPaid: after.whatsappNotifyAddon === true,
60
58
  });
61
- try {
62
- await getAdminRealtimeDb().ref(`notifications/${after.userId}`).push({
63
- type: config.type,
64
- title: config.title,
65
- message: messageText,
66
- timestamp: Date.now(),
67
- read: false,
68
- });
69
- }
70
- catch (rtdbError) {
71
- void normalizeError(rtdbError);
72
- ctx.logger.error("Realtime DB push failed (non-fatal)", rtdbError);
73
- }
59
+ // The RTDB mirror that used to live here has been removed. `sendNotification`
60
+ // above is the real path (in-app + email + WhatsApp); the extra
61
+ // `notifications/{uid}` push was a duplicate that NOTHING read — no client
62
+ // subscription to that node has ever existed — and nothing pruned, so it
63
+ // accumulated one node per order transition per user forever. Its rule's
64
+ // `.type` validator also only allowed `info|success|warning|error`, none of
65
+ // which are the values written here (`order_confirmed`, `order_shipped`, …).
74
66
  ctx.logger.info(`Order ${orderId} status → ${newStatus}`, { userId: after.userId });
75
67
  }
76
68
  catch (error) {
@@ -0,0 +1,10 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ /**
3
+ * 90 days.
4
+ *
5
+ * Long enough for a quarter-over-quarter comparison in the admin report, which
6
+ * is the longest range that screen offers, and short enough that the collection
7
+ * reaches a steady state instead of growing forever.
8
+ */
9
+ export declare const PAGE_VIEW_TTL_DAYS = 90;
10
+ export declare function runPageViewPrune(ctx: JobContext): Promise<void>;