@mohasinac/appkit 4.30.19 → 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 (417) hide show
  1. package/dist/_internal/client/features/layout/useSidebarSearch.d.ts +21 -5
  2. package/dist/_internal/client/features/layout/useSidebarSearch.js +27 -6
  3. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +23 -3
  4. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +106 -87
  5. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +16 -5
  6. package/dist/_internal/client/features/lottery/LotteryPullForm.js +65 -40
  7. package/dist/_internal/server/features/auth/permissions.d.ts +2 -0
  8. package/dist/_internal/server/features/auth/permissions.js +3 -2
  9. package/dist/_internal/server/features/categories/data.js +15 -4
  10. package/dist/_internal/server/features/classified/index.d.ts +0 -1
  11. package/dist/_internal/server/features/classified/index.js +0 -1
  12. package/dist/_internal/server/features/digital-code/index.d.ts +0 -1
  13. package/dist/_internal/server/features/digital-code/index.js +0 -1
  14. package/dist/_internal/server/features/live/index.d.ts +0 -1
  15. package/dist/_internal/server/features/live/index.js +0 -1
  16. package/dist/_internal/server/features/products/data.js +15 -5
  17. package/dist/_internal/server/features/products/list-public.d.ts +9 -0
  18. package/dist/_internal/server/features/products/list-public.js +95 -7
  19. package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
  20. package/dist/_internal/server/features/site-settings/adapters.js +8 -5
  21. package/dist/_internal/server/features/tester/visibility.d.ts +2 -1
  22. package/dist/_internal/server/features/tester/visibility.js +2 -2
  23. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  24. package/dist/_internal/server/functions/scheduled.js +27 -1
  25. package/dist/_internal/server/jobs/core/hardBanCascade.js +4 -0
  26. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/index.js +2 -0
  28. package/dist/_internal/server/jobs/core/pageViewPrune.d.ts +10 -0
  29. package/dist/_internal/server/jobs/core/pageViewPrune.js +44 -0
  30. package/dist/_internal/server/jobs/core/pageViewRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/core/pageViewRollup.js +87 -0
  32. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  33. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  34. package/dist/_internal/server/jobs/handlers/pageViewPrune.d.ts +2 -0
  35. package/dist/_internal/server/jobs/handlers/pageViewPrune.js +2 -0
  36. package/dist/_internal/server/jobs/handlers/pageViewRollup.d.ts +2 -0
  37. package/dist/_internal/server/jobs/handlers/pageViewRollup.js +2 -0
  38. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  39. package/dist/_internal/shared/actions/action-registry.js +69 -22
  40. package/dist/_internal/shared/checkout/rules/classified.rule.d.ts +9 -4
  41. package/dist/_internal/shared/checkout/rules/classified.rule.js +7 -2
  42. package/dist/_internal/shared/features/layout/matchesNavQuery.d.ts +14 -0
  43. package/dist/_internal/shared/features/layout/matchesNavQuery.js +72 -0
  44. package/dist/_internal/shared/features/layout/types.d.ts +37 -1
  45. package/dist/_internal/shared/features/layout/types.js +19 -1
  46. package/dist/_internal/shared/features/notifications/action-url.d.ts +9 -1
  47. package/dist/_internal/shared/features/notifications/action-url.js +47 -0
  48. package/dist/_internal/shared/features/offers/config.d.ts +65 -0
  49. package/dist/_internal/shared/features/offers/config.js +73 -2
  50. package/dist/_internal/shared/features/orders/schema.d.ts +19 -1
  51. package/dist/_internal/shared/features/orders/schema.js +22 -2
  52. package/dist/_internal/shared/features/site-settings/types.d.ts +12 -3
  53. package/dist/_internal/shared/listing-types/capabilities.d.ts +8 -2
  54. package/dist/_internal/shared/listing-types/capabilities.js +6 -1
  55. package/dist/_internal/shared/listing-types/classified/ctas.js +6 -1
  56. package/dist/_internal/shared/listing-types/feature-flags.d.ts +10 -5
  57. package/dist/_internal/shared/listing-types/feature-flags.js +11 -6
  58. package/dist/client.d.ts +37 -20
  59. package/dist/client.js +36 -14
  60. package/dist/configs/next.js +10 -2
  61. package/dist/constants/api-endpoints.d.ts +18 -54
  62. package/dist/constants/api-endpoints.js +6 -26
  63. package/dist/constants/field-names.d.ts +6 -24
  64. package/dist/constants/field-names.js +10 -27
  65. package/dist/constants/geo/countries.d.ts +50 -0
  66. package/dist/constants/geo/countries.js +129 -0
  67. package/dist/constants/geo/subdivisions.d.ts +13 -0
  68. package/dist/constants/geo/subdivisions.js +104 -0
  69. package/dist/constants/index.d.ts +2 -2
  70. package/dist/constants/index.js +2 -2
  71. package/dist/constants/table-keys.d.ts +24 -0
  72. package/dist/constants/table-keys.js +28 -0
  73. package/dist/contracts/extend.d.ts +0 -9
  74. package/dist/contracts/index.d.ts +1 -1
  75. package/dist/contracts/realtime-session.js +7 -0
  76. package/dist/core/browser.d.ts +1 -1
  77. package/dist/core/browser.js +1 -1
  78. package/dist/core/index.d.ts +1 -1
  79. package/dist/core/index.js +1 -1
  80. package/dist/core/site-config.d.ts +0 -3
  81. package/dist/core/site-config.js +0 -3
  82. package/dist/errors/messages.d.ts +0 -8
  83. package/dist/errors/messages.js +0 -9
  84. package/dist/features/about/schemas/index.d.ts +8 -8
  85. package/dist/features/account/components/AddressFilters.js +32 -11
  86. package/dist/features/account/components/AddressForm.d.ts +26 -25
  87. package/dist/features/account/components/AddressForm.js +81 -103
  88. package/dist/features/account/components/AddressesIndexListing.js +8 -4
  89. package/dist/features/account/components/UserSidebar.d.ts +3 -4
  90. package/dist/features/account/components/UserSupportView.js +89 -58
  91. package/dist/features/account/components/index.d.ts +1 -7
  92. package/dist/features/account/components/index.js +0 -3
  93. package/dist/features/account/hooks/useAddresses.d.ts +4 -3
  94. package/dist/features/account/hooks/useAddresses.js +4 -6
  95. package/dist/features/account/hooks/useCollapsedSections.d.ts +51 -10
  96. package/dist/features/account/hooks/useCollapsedSections.js +211 -38
  97. package/dist/features/account/hooks/useProfile.d.ts +8 -0
  98. package/dist/features/account/index.d.ts +0 -3
  99. package/dist/features/account/index.js +0 -3
  100. package/dist/features/account/schemas/index.d.ts +0 -92
  101. package/dist/features/account/schemas/index.js +42 -20
  102. package/dist/features/addresses/components/AddressLocationFields.d.ts +13 -0
  103. package/dist/features/addresses/components/AddressLocationFields.js +41 -0
  104. package/dist/features/addresses/hooks/usePostalLookup.d.ts +17 -0
  105. package/dist/features/addresses/hooks/usePostalLookup.js +133 -0
  106. package/dist/features/addresses/schemas/address-form.d.ts +423 -0
  107. package/dist/features/addresses/schemas/address-form.js +212 -0
  108. package/dist/features/addresses/schemas/index.d.ts +1 -0
  109. package/dist/features/addresses/schemas/index.js +8 -0
  110. package/dist/features/admin/actions/admin-read-actions.d.ts +1 -1
  111. package/dist/features/admin/actions/index.d.ts +0 -1
  112. package/dist/features/admin/actions/index.js +0 -1
  113. package/dist/features/admin/actions/notification-actions.js +1 -41
  114. package/dist/features/admin/components/AdminActionIndexView.d.ts +32 -0
  115. package/dist/features/admin/components/AdminActionIndexView.js +128 -0
  116. package/dist/features/admin/components/AdminAdEditorView.js +159 -163
  117. package/dist/features/admin/components/AdminAddressEditorView.js +97 -55
  118. package/dist/features/admin/components/AdminBrandEditorView.d.ts +15 -0
  119. package/dist/features/admin/components/AdminBrandEditorView.js +113 -77
  120. package/dist/features/admin/components/AdminBundleEditorView.d.ts +19 -9
  121. package/dist/features/admin/components/AdminBundleEditorView.js +136 -168
  122. package/dist/features/admin/components/AdminCategoryEditorView.js +120 -43
  123. package/dist/features/admin/components/AdminCouponEditorView.js +145 -109
  124. package/dist/features/admin/components/AdminEmployeeEditorView.js +125 -56
  125. package/dist/features/admin/components/AdminFaqEditorView.js +112 -58
  126. package/dist/features/admin/components/AdminFeatureEditorView.js +108 -68
  127. package/dist/features/admin/components/AdminNavEditorView.js +69 -44
  128. package/dist/features/admin/components/AdminOrderEditorView.js +47 -10
  129. package/dist/features/admin/components/AdminPrizeDrawsView.js +16 -2
  130. package/dist/features/admin/components/AdminSidebar.d.ts +11 -7
  131. package/dist/features/admin/components/AdminSiteSettingsView.js +37 -37
  132. package/dist/features/admin/components/AdminStoreEditorView.js +75 -52
  133. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +71 -30
  134. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +95 -65
  135. package/dist/features/admin/components/BrandQuickCreateForm.d.ts +10 -0
  136. package/dist/features/admin/components/BrandQuickCreateForm.js +49 -26
  137. package/dist/features/admin/components/CategoryQuickCreateForm.d.ts +11 -0
  138. package/dist/features/admin/components/CategoryQuickCreateForm.js +48 -26
  139. package/dist/features/admin/components/index.d.ts +2 -2
  140. package/dist/features/admin/components/index.js +1 -1
  141. package/dist/features/admin/constants/site-settings-tabs.d.ts +120 -0
  142. package/dist/features/admin/constants/site-settings-tabs.js +152 -0
  143. package/dist/features/admin/index.d.ts +0 -3
  144. package/dist/features/admin/index.js +0 -2
  145. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +35 -0
  146. package/dist/features/admin/repository/analytics-rollup.repository.js +30 -0
  147. package/dist/features/admin/schemas/admin-editor-forms.d.ts +56 -57
  148. package/dist/features/admin/schemas/admin-editor-forms.js +97 -54
  149. package/dist/features/admin/schemas/admin-ops-forms.d.ts +57 -5
  150. package/dist/features/admin/schemas/admin-ops-forms.js +104 -20
  151. package/dist/features/admin/schemas/admin-user-form.js +7 -0
  152. package/dist/features/admin/schemas/firestore.d.ts +48 -95
  153. package/dist/features/admin/schemas/firestore.js +15 -151
  154. package/dist/features/admin/schemas/nav-item-form.js +9 -2
  155. package/dist/features/admin/server.d.ts +0 -4
  156. package/dist/features/admin/server.js +0 -2
  157. package/dist/features/admin/types/index.d.ts +0 -7
  158. package/dist/features/analytics/repository/page-views.repository.d.ts +14 -0
  159. package/dist/features/analytics/repository/page-views.repository.js +24 -0
  160. package/dist/features/auth/actions/index.d.ts +0 -1
  161. package/dist/features/auth/actions/index.js +0 -1
  162. package/dist/features/auth/actions/realtime-token-actions.js +1 -12
  163. package/dist/features/auth/auth-helpers.js +5 -7
  164. package/dist/features/auth/components/Guards.js +2 -1
  165. package/dist/features/auth/components/LoginForm.d.ts +0 -2
  166. package/dist/features/auth/components/LoginForm.js +1 -3
  167. package/dist/features/auth/components/RegisterForm.js +62 -19
  168. package/dist/features/auth/permissions/constants.d.ts +27 -1
  169. package/dist/features/auth/role-predicates.d.ts +75 -7
  170. package/dist/features/auth/role-predicates.js +28 -2
  171. package/dist/features/auth/schemas/firestore.d.ts +22 -0
  172. package/dist/features/auth/schemas/index.d.ts +1 -1
  173. package/dist/features/auth/schemas/index.js +44 -8
  174. package/dist/features/auth/types/index.d.ts +14 -1
  175. package/dist/features/blog/api/route.js +10 -2
  176. package/dist/features/blog/components/BlogIndexListing.js +1 -1
  177. package/dist/features/blog/hooks/useBlog.js +2 -2
  178. package/dist/features/blog/schemas/index.d.ts +4 -0
  179. package/dist/features/blog/schemas/index.js +2 -0
  180. package/dist/features/blog/types/index.d.ts +1 -1
  181. package/dist/features/catalogue/components/CatalogueItemEditorView.d.ts +22 -0
  182. package/dist/features/catalogue/components/CatalogueItemEditorView.js +112 -52
  183. package/dist/features/catalogue/schemas/validation.d.ts +4 -4
  184. package/dist/features/catalogue/schemas/validation.js +47 -10
  185. package/dist/features/categories/schemas/bundle-form.d.ts +142 -0
  186. package/dist/features/categories/schemas/bundle-form.js +149 -0
  187. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +5 -4
  188. package/dist/features/classified/components/ClassifiedDetailPageView.js +37 -16
  189. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  190. package/dist/features/consultation/schemas/index.js +34 -10
  191. package/dist/features/corporate/schemas/index.js +36 -15
  192. package/dist/features/filters/FilterPanel.js +3 -3
  193. package/dist/features/filters/RangeFilter.d.ts +10 -0
  194. package/dist/features/filters/RangeFilter.js +41 -4
  195. package/dist/features/homepage/components/MarketplaceHomepageView.js +24 -5
  196. package/dist/features/homepage/hooks/useBlogArticles.js +1 -1
  197. package/dist/features/layout/BottomActions.js +84 -11
  198. package/dist/features/layout/BottomActionsContext.d.ts +1 -6
  199. package/dist/features/layout/BottomActionsContext.js +1 -1
  200. package/dist/features/layout/hooks/useBottomActions.js +2 -2
  201. package/dist/features/layout/hooks/useFormBottomActions.d.ts +12 -1
  202. package/dist/features/layout/hooks/useFormBottomActions.js +12 -6
  203. package/dist/features/lottery/schemas/config-write.d.ts +9 -5
  204. package/dist/features/lottery/schemas/config-write.js +69 -9
  205. package/dist/features/orders/schemas/buyer-forms.d.ts +6 -3
  206. package/dist/features/orders/schemas/buyer-forms.js +31 -3
  207. package/dist/features/orders/schemas/index.d.ts +2 -2
  208. package/dist/features/products/components/GroupSettingsPanel.js +3 -1
  209. package/dist/features/products/components/MakeOfferButton.d.ts +13 -2
  210. package/dist/features/products/components/MakeOfferButton.js +33 -11
  211. package/dist/features/products/components/ProductDetailActions.js +0 -1
  212. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -3
  213. package/dist/features/products/components/ProductDetailPageView.js +8 -5
  214. package/dist/features/products/constants/action-defs.d.ts +1 -2
  215. package/dist/features/products/constants/action-defs.js +40 -43
  216. package/dist/features/products/repository/products.repository.d.ts +5 -1
  217. package/dist/features/products/repository/products.repository.js +23 -1
  218. package/dist/features/products/schemas/firestore.d.ts +7 -3
  219. package/dist/features/products/schemas/product-features.validators.js +3 -0
  220. package/dist/features/promotions/components/CouponCard.js +8 -7
  221. package/dist/features/scams/schemas/firestore.d.ts +15 -1
  222. package/dist/features/scams/schemas/report-form.d.ts +5 -5
  223. package/dist/features/scams/schemas/report-form.js +18 -3
  224. package/dist/features/search/action-index/derive.d.ts +49 -0
  225. package/dist/features/search/action-index/derive.js +121 -0
  226. package/dist/features/search/action-index/repository.d.ts +42 -0
  227. package/dist/features/search/action-index/repository.js +100 -0
  228. package/dist/features/search/action-index/settings-entries.d.ts +41 -0
  229. package/dist/features/search/action-index/settings-entries.js +268 -0
  230. package/dist/features/search/action-index/types.d.ts +81 -0
  231. package/dist/features/search/action-index/types.js +102 -0
  232. package/dist/features/search/action-index/validate-entry.d.ts +34 -0
  233. package/dist/features/search/action-index/validate-entry.js +114 -0
  234. package/dist/features/search/hooks/useNavSuggestions.d.ts +5 -1
  235. package/dist/features/search/hooks/useNavSuggestions.js +23 -3
  236. package/dist/features/search/repository/search.repository.js +3 -1
  237. package/dist/features/seller/actions/offer-actions.js +43 -24
  238. package/dist/features/seller/actions/seller-actions.js +29 -5
  239. package/dist/features/seller/components/CategoryChipPicker.d.ts +9 -0
  240. package/dist/features/seller/components/CategoryChipPicker.js +12 -0
  241. package/dist/features/seller/components/SellerAddressesView.js +5 -1
  242. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +83 -43
  243. package/dist/features/seller/components/SellerArtView.d.ts +14 -7
  244. package/dist/features/seller/components/SellerArtView.js +4 -119
  245. package/dist/features/seller/components/SellerClassifiedView.d.ts +14 -7
  246. package/dist/features/seller/components/SellerClassifiedView.js +4 -124
  247. package/dist/features/seller/components/SellerCouponEditorView.d.ts +22 -1
  248. package/dist/features/seller/components/SellerCouponEditorView.js +91 -45
  249. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +14 -7
  250. package/dist/features/seller/components/SellerDigitalCodesView.js +4 -126
  251. package/dist/features/seller/components/SellerListingTypeView.d.ts +66 -0
  252. package/dist/features/seller/components/SellerListingTypeView.js +277 -0
  253. package/dist/features/seller/components/SellerLiveView.d.ts +14 -7
  254. package/dist/features/seller/components/SellerLiveView.js +4 -126
  255. package/dist/features/seller/components/SellerSidebar.d.ts +3 -6
  256. package/dist/features/seller/components/SellerStickersView.d.ts +14 -7
  257. package/dist/features/seller/components/SellerStickersView.js +4 -119
  258. package/dist/features/seller/components/SellerSupportView.d.ts +31 -0
  259. package/dist/features/seller/components/SellerSupportView.js +100 -0
  260. package/dist/features/seller/components/index.d.ts +4 -8
  261. package/dist/features/seller/components/index.js +2 -4
  262. package/dist/features/seller/index.d.ts +0 -1
  263. package/dist/features/seller/index.js +0 -1
  264. package/dist/features/seller/schemas/coupon-form.d.ts +214 -0
  265. package/dist/features/seller/schemas/coupon-form.js +155 -23
  266. package/dist/features/seller/schemas/offer-forms.d.ts +16 -7
  267. package/dist/features/seller/schemas/offer-forms.js +28 -12
  268. package/dist/features/shell/FormShell.js +2 -2
  269. package/dist/features/shell/QuickFormDrawer.js +2 -2
  270. package/dist/features/shell/SectionForm.d.ts +19 -1
  271. package/dist/features/shell/SectionForm.js +104 -17
  272. package/dist/features/shell/build-sections.d.ts +23 -0
  273. package/dist/features/shell/build-sections.js +106 -8
  274. package/dist/features/shell/field-ui-meta.d.ts +44 -0
  275. package/dist/features/shell/field-ui-meta.js +14 -7
  276. package/dist/features/shell/form-sections.d.ts +46 -0
  277. package/dist/features/shell/form-sections.js +114 -0
  278. package/dist/features/shipments/components/AdminShipmentEditorView.js +87 -64
  279. package/dist/features/shipments/schemas/validation.d.ts +2 -2
  280. package/dist/features/shipments/schemas/validation.js +44 -13
  281. package/dist/features/store-extensions/schemas/custom-role-form.js +6 -2
  282. package/dist/features/store-extensions/schemas/listing-template-form.d.ts +7 -7
  283. package/dist/features/store-extensions/schemas/payout-method-form.js +14 -0
  284. package/dist/features/stores/actions/store-query-actions.js +3 -2
  285. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -1
  286. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -1
  287. package/dist/features/support/repository/support.repository.d.ts +21 -0
  288. package/dist/features/support/repository/support.repository.js +63 -0
  289. package/dist/features/support/schemas/firestore.d.ts +27 -2
  290. package/dist/features/support/schemas/firestore.js +2 -0
  291. package/dist/features/support/schemas/ticket-create-form.js +7 -0
  292. package/dist/features/tester/schemas/checklist-item-form.d.ts +43 -0
  293. package/dist/features/tester/schemas/checklist-item-form.js +109 -0
  294. package/dist/features/tester/seed-data/blog-tester-seed-data.js +2 -2
  295. package/dist/features/tester/seed-data/categories-tester-seed-data.js +2 -2
  296. package/dist/features/tester/seed-data/events-tester-seed-data.js +5 -5
  297. package/dist/features/tester/seed-data/orders-tester-seed-data.js +2 -2
  298. package/dist/features/tester/seed-data/products-tester-seed-data.js +48 -49
  299. package/dist/features/tester/seed-data/stores-tester-seed-data.js +3 -3
  300. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +275 -29
  301. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +48 -12
  302. package/dist/http/api-handler.js +2 -1
  303. package/dist/index.d.ts +24 -103
  304. package/dist/index.js +33 -141
  305. package/dist/jobs.d.ts +1 -1
  306. package/dist/jobs.js +1 -1
  307. package/dist/monitoring/browser.d.ts +0 -2
  308. package/dist/monitoring/browser.js +12 -1
  309. package/dist/monitoring/index.d.ts +0 -2
  310. package/dist/monitoring/index.js +0 -1
  311. package/dist/next/api/routeHandler.js +6 -3
  312. package/dist/next/index.d.ts +1 -2
  313. package/dist/next/index.js +0 -1
  314. package/dist/next/middleware/index.d.ts +0 -2
  315. package/dist/next/middleware/index.js +0 -1
  316. package/dist/next/routing/route-map.d.ts +8 -7
  317. package/dist/next/routing/route-map.js +10 -3
  318. package/dist/providers/db-firebase/admin.js +15 -1
  319. package/dist/providers/db-firebase/sieve.d.ts +22 -0
  320. package/dist/react/hooks/useListingTypeFlags.d.ts +1 -1
  321. package/dist/react/hooks/useListingTypeFlags.js +1 -1
  322. package/dist/react/hooks/usePendingFilters.js +5 -3
  323. package/dist/react/hooks/usePendingTable.js +15 -3
  324. package/dist/repositories/index.d.ts +0 -1
  325. package/dist/repositories/index.js +0 -1
  326. package/dist/schemas/registry.d.ts +197 -232
  327. package/dist/schemas/registry.js +5 -6
  328. package/dist/schemas/webhooks/razorpay.d.ts +50 -50
  329. package/dist/security/authorization.d.ts +9 -0
  330. package/dist/security/authorization.js +16 -2
  331. package/dist/security/browser.d.ts +1 -3
  332. package/dist/security/browser.js +5 -2
  333. package/dist/security/index.d.ts +1 -3
  334. package/dist/security/index.js +19 -2
  335. package/dist/security/pii-encrypt.js +16 -0
  336. package/dist/security/pii-schemas.d.ts +0 -14
  337. package/dist/security/pii-schemas.js +7 -15
  338. package/dist/seed/_helpers/category-forest.js +2 -2
  339. package/dist/seed/_helpers/media.d.ts +19 -0
  340. package/dist/seed/_helpers/media.js +75 -0
  341. package/dist/seed/blog-posts-seed-data.js +17 -17
  342. package/dist/seed/carousel-slides-seed-data.js +7 -7
  343. package/dist/seed/cart-seed-data.js +13 -13
  344. package/dist/seed/categories-seed-data.js +10 -10
  345. package/dist/seed/events-seed-data.js +24 -24
  346. package/dist/seed/factories/product.factory.js +4 -4
  347. package/dist/seed/factories/store.factory.js +3 -3
  348. package/dist/seed/factories/user.factory.js +2 -2
  349. package/dist/seed/grouped-listings-seed-data.js +11 -11
  350. package/dist/seed/homepage-sections-seed-data.js +2 -2
  351. package/dist/seed/index.d.ts +0 -1
  352. package/dist/seed/index.js +0 -1
  353. package/dist/seed/manifest.d.ts +1 -1
  354. package/dist/seed/manifest.js +0 -5
  355. package/dist/seed/notifications-seed-data.js +20 -1
  356. package/dist/seed/orders-seed-data.js +2 -2
  357. package/dist/seed/products-art-seed-data.js +16 -16
  358. package/dist/seed/products-auctions-seed-data.js +34 -34
  359. package/dist/seed/products-classifieds-seed-data.js +35 -37
  360. package/dist/seed/products-digital-codes-seed-data.js +20 -20
  361. package/dist/seed/products-live-items-seed-data.js +11 -11
  362. package/dist/seed/products-preorders-seed-data.js +13 -13
  363. package/dist/seed/products-prize-draws-seed-data.js +43 -43
  364. package/dist/seed/products-standard-seed-data.js +54 -54
  365. package/dist/seed/products-stickers-seed-data.js +15 -15
  366. package/dist/seed/reviews-seed-data.js +4 -4
  367. package/dist/seed/scammers-seed-data.js +3 -3
  368. package/dist/seed/site-settings-seed-data.js +11 -24
  369. package/dist/seed/store-extensions-seed-data.js +10 -10
  370. package/dist/seed/stores-seed-data.js +11 -11
  371. package/dist/seed/support-tickets-seed-data.d.ts +14 -0
  372. package/dist/seed/support-tickets-seed-data.js +20 -1
  373. package/dist/seed/users-seed-data.js +20 -20
  374. package/dist/server-entry.d.ts +0 -1
  375. package/dist/server-entry.js +0 -1
  376. package/dist/server.d.ts +9 -6
  377. package/dist/server.js +7 -13
  378. package/dist/styles.css +248 -68
  379. package/dist/tailwind-utilities.css +1 -1
  380. package/dist/tokens/themes/default-dark.js +2 -2
  381. package/dist/tokens/themes/default-light.js +2 -2
  382. package/dist/tokens/tokens.css +24 -4
  383. package/dist/ui/components/ActionRow.d.ts +50 -0
  384. package/dist/ui/components/ActionRow.js +53 -0
  385. package/dist/ui/components/ActionRow.style.css +145 -0
  386. package/dist/ui/components/Drawer.d.ts +10 -0
  387. package/dist/ui/components/Drawer.js +2 -1
  388. package/dist/ui/components/FilterDrawer.js +4 -1
  389. package/dist/ui/components/FilterDrawer.style.css +6 -12
  390. package/dist/ui/components/FormActionBar.js +14 -1
  391. package/dist/ui/components/FormActionBar.style.css +12 -11
  392. package/dist/ui/components/ListingLayout.js +8 -1
  393. package/dist/ui/components/ListingLayout.style.css +6 -13
  394. package/dist/ui/components/Modal.d.ts +15 -1
  395. package/dist/ui/components/Modal.js +17 -3
  396. package/dist/ui/components/QuickCreateModal.js +2 -2
  397. package/dist/ui/components/QuickCreateModal.style.css +78 -84
  398. package/dist/ui/components/RecordDetailModal.js +2 -2
  399. package/dist/ui/components/SideDrawer.d.ts +8 -0
  400. package/dist/ui/components/SideDrawer.js +2 -1
  401. package/dist/ui/components/SideDrawer.style.css +6 -3
  402. package/dist/ui/components/SideModal.d.ts +11 -1
  403. package/dist/ui/components/SideModal.js +3 -2
  404. package/dist/ui/components/SideModal.style.css +14 -0
  405. package/dist/ui/components/Typography.style.css +33 -24
  406. package/dist/ui/components/index.style.css +1 -0
  407. package/dist/ui/forms/FormShell.style.css +260 -311
  408. package/dist/ui/index.d.ts +2 -0
  409. package/dist/ui/index.js +1 -0
  410. package/dist/utils/index.d.ts +0 -1
  411. package/dist/utils/index.js +0 -1
  412. package/dist/validation/index.d.ts +1 -1
  413. package/dist/validation/index.js +1 -1
  414. package/dist/validation/schemas.d.ts +0 -19
  415. package/dist/validation/schemas.js +8 -19
  416. package/package.json +1 -1
  417. package/scripts/seed-cli.mjs +3 -6
@@ -24,11 +24,21 @@ import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/confi
24
24
  // ---------------------------------------------------------------------------
25
25
  /** Fetch a single product by slug or id, deduped per request. */
26
26
  export const getProductForDetail = cache(async (slugOrId) => {
27
- const product = (await productRepository.findByIdOrSlug(slugOrId)) ?? null;
28
- // Fire-and-forget viewCount increment — must not affect response time
29
- if (product)
30
- void productRepository.incrementViewCount(product.id);
31
- 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;
32
42
  });
33
43
  /** Fetch the first page of approved reviews for a product, deduped per request. */
34
44
  export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
@@ -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)
@@ -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,
@@ -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,
@@ -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";
@@ -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>;
@@ -0,0 +1,44 @@
1
+ /*
2
+ * WHY: `pageViews` writes one document per entity per day and had NO prune.
3
+ * Twenty-seven scheduled functions existed — `cartPrune`,
4
+ * `notificationPrune`, `draftPrune`, `dailyDataCleanup` — and not one of
5
+ * them touched this collection, so it was the only unbounded writer in the
6
+ * system. Its size was capped by nothing except how many distinct pages
7
+ * the catalogue had, multiplied by how long the site had been running.
8
+ *
9
+ * WHAT: Delete day-buckets older than the retention window. Shaped after
10
+ * `notificationPrune`, which is the smallest correct prune in the repo.
11
+ *
12
+ * The daily rows are the raw material; `pageViewRollup` keeps the long-run
13
+ * totals, so deleting the buckets loses granularity past the window and not the
14
+ * history itself.
15
+ *
16
+ * EXPORTS: PAGE_VIEW_TTL_DAYS, runPageViewPrune
17
+ *
18
+ * @tag domain:analytics
19
+ * @tag layer:job
20
+ * @tag pattern:none
21
+ * @tag access:server-only
22
+ * @tag consumers:pageViewPrune scheduled function
23
+ * @tag sideEffects:deletes pageViews documents
24
+ */
25
+ import { pageViewsRepository } from "../../../../features/analytics/repository/page-views.repository";
26
+ import { batchDelete } from "../handlers/_helpers";
27
+ /**
28
+ * 90 days.
29
+ *
30
+ * Long enough for a quarter-over-quarter comparison in the admin report, which
31
+ * is the longest range that screen offers, and short enough that the collection
32
+ * reaches a steady state instead of growing forever.
33
+ */
34
+ export const PAGE_VIEW_TTL_DAYS = 90;
35
+ export async function runPageViewPrune(ctx) {
36
+ ctx.logger.info(`Pruning page-view buckets older than ${PAGE_VIEW_TTL_DAYS} days`);
37
+ const refs = await pageViewsRepository.getOlderThanRefs(PAGE_VIEW_TTL_DAYS);
38
+ if (refs.length === 0) {
39
+ ctx.logger.info("No stale page-view buckets found");
40
+ return;
41
+ }
42
+ const deleted = await batchDelete(ctx, refs);
43
+ ctx.logger.info("Page-view prune complete", { deleted });
44
+ }
@@ -0,0 +1,2 @@
1
+ import type { JobContext } from "../runtime/types";
2
+ export declare function runPageViewRollup(ctx: JobContext): Promise<void>;
@@ -0,0 +1,87 @@
1
+ /*
2
+ * WHY: `pageViewPrune` deletes day-buckets past the retention window. Without a
3
+ * rollup that would also delete the only record that those views ever
4
+ * happened — the admin's lifetime total would silently walk backwards as
5
+ * the prune caught up with it.
6
+ *
7
+ * WHAT: Fold each new day-bucket into a singleton total before the prune can
8
+ * reach it. Modelled on `revenueRollup`: scan → one small doc → the API
9
+ * route reads a single document instead of the collection.
10
+ *
11
+ * EXPORTS: runPageViewRollup
12
+ *
13
+ * @tag domain:analytics
14
+ * @tag layer:job
15
+ * @tag pattern:none
16
+ * @tag access:server-only
17
+ * @tag consumers:pageViewRollup scheduled function
18
+ * @tag sideEffects:writes analytics/pageViewRollup
19
+ */
20
+ import { pageViewsRepository } from "../../../../features/analytics/repository/page-views.repository";
21
+ import { pageViewRollupRepository } from "../../../../features/admin/repository/analytics-rollup.repository";
22
+ /** The earliest date this rollup will fold in on a cold start. */
23
+ const EPOCH_DATE = "2020-01-01";
24
+ function dayKey(offsetDays) {
25
+ const d = new Date();
26
+ d.setUTCDate(d.getUTCDate() + offsetDays);
27
+ return d.toISOString().slice(0, 10);
28
+ }
29
+ export async function runPageViewRollup(ctx) {
30
+ const existing = await pageViewRollupRepository.get();
31
+ /*
32
+ * 🛑 `throughDate` is what makes this job re-runnable.
33
+ *
34
+ * The rollup ACCUMULATES rather than recomputing — the day-buckets it has
35
+ * already folded in get deleted by `pageViewPrune`, so a recompute-from-
36
+ * scratch would produce a total that shrinks every time the prune runs. So it
37
+ * must only ever read buckets it has not seen, and `throughDate` is the
38
+ * boundary. Running this twice in one day is a no-op; running it after a
39
+ * missed day still catches up.
40
+ */
41
+ const from = existing?.throughDate
42
+ ? dayKey(0) === existing.throughDate
43
+ ? null // already folded in today — nothing new to read
44
+ : nextDay(existing.throughDate)
45
+ : EPOCH_DATE;
46
+ if (from === null) {
47
+ ctx.logger.info("pageViewRollup: already current", {
48
+ throughDate: existing?.throughDate ?? "",
49
+ });
50
+ return;
51
+ }
52
+ // Yesterday, not today: today's buckets are still being written to, and
53
+ // folding a partial day in would make it uncountable tomorrow.
54
+ const through = dayKey(-1);
55
+ if (from > through) {
56
+ ctx.logger.info("pageViewRollup: nothing to fold", { from, through });
57
+ return;
58
+ }
59
+ /*
60
+ * No `.catch(() => [])`. An empty range is a legitimate zero, so a swallowed
61
+ * query failure would write a confident "no views" the dashboard cannot tell
62
+ * from the real thing — and unlike `revenueRollup`, this one ACCUMULATES, so
63
+ * a bad read would also advance `throughDate` past days it never counted and
64
+ * lose them permanently. Let it throw; the runner records the failure and the
65
+ * stale-but-correct singleton stays.
66
+ */
67
+ const buckets = await pageViewsRepository.listInRange(from, through);
68
+ const byEntityType = { ...(existing?.byEntityType ?? {}) };
69
+ let totalViews = existing?.totalViews ?? 0;
70
+ for (const b of buckets) {
71
+ const n = Number(b.count) || 0;
72
+ totalViews += n;
73
+ byEntityType[b.entityType] = (byEntityType[b.entityType] ?? 0) + n;
74
+ }
75
+ await pageViewRollupRepository.set({ totalViews, byEntityType, throughDate: through });
76
+ ctx.logger.info("pageViewRollup: complete", {
77
+ from,
78
+ through,
79
+ bucketsFolded: buckets.length,
80
+ totalViews,
81
+ });
82
+ }
83
+ function nextDay(dateKey) {
84
+ const d = new Date(`${dateKey}T00:00:00.000Z`);
85
+ d.setUTCDate(d.getUTCDate() + 1);
86
+ return d.toISOString().slice(0, 10);
87
+ }
@@ -28,6 +28,8 @@ export { hardBanReinstatementHandler } from "./hardBanReinstatement";
28
28
  export { paymentReviewAutoApproveHandler } from "./paymentReviewAutoApprove";
29
29
  export { productStatsSyncHandler } from "./productStatsSync";
30
30
  export { revenueRollupHandler } from "./revenueRollup";
31
+ export { pageViewRollupHandler } from "./pageViewRollup";
32
+ export { pageViewPruneHandler } from "./pageViewPrune";
31
33
  export { dailyStatusDigestHandler } from "./dailyStatusDigest";
32
34
  export { positionsReconcileHandler } from "./positionsReconcile";
33
35
  export { payoutBatchHandler } from "./payoutBatch";
@@ -28,6 +28,8 @@ export { hardBanReinstatementHandler } from "./hardBanReinstatement";
28
28
  export { paymentReviewAutoApproveHandler } from "./paymentReviewAutoApprove";
29
29
  export { productStatsSyncHandler } from "./productStatsSync";
30
30
  export { revenueRollupHandler } from "./revenueRollup";
31
+ export { pageViewRollupHandler } from "./pageViewRollup";
32
+ export { pageViewPruneHandler } from "./pageViewPrune";
31
33
  export { dailyStatusDigestHandler } from "./dailyStatusDigest";
32
34
  export { positionsReconcileHandler } from "./positionsReconcile";
33
35
  export { payoutBatchHandler } from "./payoutBatch";
@@ -0,0 +1,2 @@
1
+ import type { ScheduleHandler } from "../runtime/types";
2
+ export declare const pageViewPruneHandler: ScheduleHandler;
@@ -0,0 +1,2 @@
1
+ import { runPageViewPrune } from "../core/pageViewPrune";
2
+ export const pageViewPruneHandler = runPageViewPrune;
@@ -0,0 +1,2 @@
1
+ import type { ScheduleHandler } from "../runtime/types";
2
+ export declare const pageViewRollupHandler: ScheduleHandler;
@@ -0,0 +1,2 @@
1
+ import { runPageViewRollup } from "../core/pageViewRollup";
2
+ export const pageViewRollupHandler = runPageViewRollup;
@@ -29,7 +29,7 @@ import type { IconKey } from "../../../ui/icons/icon-registry";
29
29
  /** What KIND of action — drives default UI affordance (link vs button vs danger). */
30
30
  export type ActionKind = "primary" | "secondary" | "danger" | "ghost" | "link" | "chip";
31
31
  /** Resource bucket — keyed top-level in the ACTIONS tree. */
32
- export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "CLASSIFIED" | "DIGITAL_CODE" | "LIVE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
32
+ export type ActionResource = "PRODUCT" | "AUCTION" | "PRE_ORDER" | "PRIZE_DRAW" | "DIGITAL_CODE" | "BUNDLE" | "GROUP" | "CATEGORY" | "BRAND" | "SUBLISTING" | "STORE" | "BLOG" | "EVENT" | "SHIPMENT" | "CATALOGUE" | "USER" | "SELLER" | "ADMIN" | "CART" | "CHECKOUT" | "NAV" | "MEDIA" | "SUPPORT" | "LOTTERY" | "TESTER";
33
33
  /** Confirmation modal config — when set, <Button action={...}> opens
34
34
  * a confirm dialog before firing the actual handler. */
35
35
  export interface ActionConfirmation {