@mohasinac/appkit 4.30.19 → 4.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +7 -2
  313. package/dist/next/index.js +36 -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
@@ -13,22 +13,90 @@ type RoleCarrier = {
13
13
  role?: string | null;
14
14
  } | null | undefined;
15
15
  export declare const isAdminUser: (input: RoleCarrier) => boolean;
16
+ /**
17
+ * True for sellers AND testers.
18
+ *
19
+ * A QA tester owns a real store — `becomeSeller`/`createStore` auto-approve it —
20
+ * so every seller-facing surface must admit them. Making `tester` a peer role
21
+ * of `seller` rather than a boolean is what removed the `isTester` flag; this
22
+ * predicate is where that peerage is expressed.
23
+ */
16
24
  export declare const isSellerUser: (input: RoleCarrier) => boolean;
25
+ /** Strictly the `seller` role — for the rare case that must EXCLUDE testers. */
26
+ export declare const isStrictSellerUser: (input: RoleCarrier) => boolean;
17
27
  export declare const isModeratorUser: (input: RoleCarrier) => boolean;
18
28
  export declare const isEmployeeUser: (input: RoleCarrier) => boolean;
19
29
  export declare const isBuyerUser: (input: RoleCarrier) => boolean;
20
30
  /**
21
- * True for real admins OR a tester explicitly flagged to test admin areas
22
- * (`isTester && canTestAdmin`). Used at the two RBAC chokepoints — the API route
23
- * guard (`createRouteHandler`) and the admin layout guard (`makeAdminSectionLayout`)
24
- * — so a flagged tester gets real read/write access to `/admin/**` without changing
25
- * their `role`. Callers must supply `isTester`/`canTestAdmin` from a live Firestore
26
- * read; these are never present in the session-cookie JWT claims (only `role` is).
31
+ * ── Tester identity: role first, legacy booleans second ──────────────────────
32
+ *
33
+ * `isTester` / `canTestAdmin` are being replaced by the `tester` role and the
34
+ * `tester:admin-surfaces` permission. Both predicates below read the NEW shape
35
+ * first and fall back to the OLD one, which is what makes the code safe to ship
36
+ * before the data migration: a tester whose document still says
37
+ * `{ role: "seller", isTester: true }` keeps working unchanged, and one already
38
+ * migrated to `{ role: "tester" }` works too.
39
+ *
40
+ * ⛔ Do NOT delete the fallbacks until the backfill has run and been verified.
41
+ * Removing them early locks every existing tester out of the Tester Hub and out
42
+ * of the auto-approved store the QA program depends on.
27
43
  */
28
- type EffectiveAdminCarrier = {
44
+ type TesterCarrier = {
29
45
  role?: string | null;
30
46
  isTester?: boolean;
31
47
  canTestAdmin?: boolean;
48
+ permissions?: readonly string[] | null;
32
49
  } | null | undefined;
50
+ /** The `tester` role, or the legacy `isTester` flag. */
51
+ export declare const isTesterUser: (input: TesterCarrier) => boolean;
52
+ /**
53
+ * May this tester reach `/admin/**`?
54
+ *
55
+ * The `tester:admin-surfaces` permission, or the legacy `canTestAdmin` flag.
56
+ * Granted through the same `permissions[]` array employees already use, so it
57
+ * is one grant mechanism rather than a bespoke boolean — and revoking it is a
58
+ * permission change, which the admin audit log already records.
59
+ */
60
+ export declare const canTestAdminSurfaces: (input: TesterCarrier) => boolean;
61
+ /**
62
+ * True for real admins OR a tester cleared for admin surfaces. Used at the two
63
+ * RBAC chokepoints — the API route guard (`createRouteHandler`) and the admin
64
+ * layout guard (`makeAdminSectionLayout`) — so a cleared tester gets real
65
+ * read/write access to `/admin/**` without holding the admin role.
66
+ *
67
+ * Callers must supply the role/permissions from a live Firestore read; none of
68
+ * these are present in the session-cookie JWT claims (only `role` is).
69
+ */
70
+ type EffectiveAdminCarrier = TesterCarrier;
33
71
  export declare const isEffectiveAdminUser: (input: EffectiveAdminCarrier) => boolean;
72
+ /**
73
+ * ── Is this account blocked? ─────────────────────────────────────────────────
74
+ *
75
+ * 🛑 There are TWO ban fields on `UserDocument` and, until 2026-08-29, no ban
76
+ * path wrote the one every guard reads.
77
+ *
78
+ * `disabled` — required, and read by EVERY server-side guard:
79
+ * auth-server, api-handler, authorization.requireActiveUser,
80
+ * Guards.requireActiveAccount, SessionContext.
81
+ * Its only writers, `userRepository.disable()/enable()`, had
82
+ * ZERO callers.
83
+ * `isDisabled` — optional, and what `hardBanCascade` and the bulk-suspend
84
+ * route actually set, alongside the Firebase Auth record.
85
+ *
86
+ * The consequence was that **no ban terminated an existing session.** A hard
87
+ * ban set Firebase Auth `disabled: true`, which blocks a fresh login — and
88
+ * nothing else. Every authenticated request from a session cookie the user
89
+ * already held kept passing, because each guard consulted a Firestore field
90
+ * the ban never touched. Silent, and invisible to anyone testing a ban by
91
+ * logging out first.
92
+ *
93
+ * This predicate reads BOTH so already-banned accounts are enforced from the
94
+ * moment it ships, without waiting on a backfill. Every write path now sets
95
+ * both fields too; `isDisabled` is removed once the data is reconciled.
96
+ */
97
+ type BanCarrier = {
98
+ disabled?: boolean | null;
99
+ isDisabled?: boolean | null;
100
+ } | null | undefined;
101
+ export declare const isAccountDisabled: (input: BanCarrier) => boolean;
34
102
  export {};
@@ -2,8 +2,34 @@ function normalizeRole(input) {
2
2
  return (input?.role ?? "user");
3
3
  }
4
4
  export const isAdminUser = (input) => normalizeRole(input) === "admin";
5
- export const isSellerUser = (input) => normalizeRole(input) === "seller";
5
+ /**
6
+ * True for sellers AND testers.
7
+ *
8
+ * A QA tester owns a real store — `becomeSeller`/`createStore` auto-approve it —
9
+ * so every seller-facing surface must admit them. Making `tester` a peer role
10
+ * of `seller` rather than a boolean is what removed the `isTester` flag; this
11
+ * predicate is where that peerage is expressed.
12
+ */
13
+ export const isSellerUser = (input) => {
14
+ const role = normalizeRole(input);
15
+ return role === "seller" || role === "tester";
16
+ };
17
+ /** Strictly the `seller` role — for the rare case that must EXCLUDE testers. */
18
+ export const isStrictSellerUser = (input) => normalizeRole(input) === "seller";
6
19
  export const isModeratorUser = (input) => normalizeRole(input) === "moderator";
7
20
  export const isEmployeeUser = (input) => normalizeRole(input) === "employee";
8
21
  export const isBuyerUser = (input) => normalizeRole(input) === "user";
9
- export const isEffectiveAdminUser = (input) => isAdminUser(input) || Boolean(input?.isTester && input?.canTestAdmin);
22
+ /** The `tester` role, or the legacy `isTester` flag. */
23
+ export const isTesterUser = (input) => normalizeRole(input) === "tester" || input?.isTester === true;
24
+ /**
25
+ * May this tester reach `/admin/**`?
26
+ *
27
+ * The `tester:admin-surfaces` permission, or the legacy `canTestAdmin` flag.
28
+ * Granted through the same `permissions[]` array employees already use, so it
29
+ * is one grant mechanism rather than a bespoke boolean — and revoking it is a
30
+ * permission change, which the admin audit log already records.
31
+ */
32
+ export const canTestAdminSurfaces = (input) => Boolean(input?.permissions?.includes("tester:admin-surfaces")) ||
33
+ input?.canTestAdmin === true;
34
+ export const isEffectiveAdminUser = (input) => isAdminUser(input) || (isTesterUser(input) && canTestAdminSurfaces(input));
35
+ export const isAccountDisabled = (input) => input?.disabled === true || input?.isDisabled === true;
@@ -140,6 +140,28 @@ export interface UserDocument extends BaseDocument {
140
140
  * stay unique). Absent/missing IDs default to collapsed for a section the
141
141
  * user has never interacted with. See useCollapsedSections. */
142
142
  collapsedSections?: string[];
143
+ /**
144
+ * Which sections are OPEN, namespaced per surface: `scope -> openIds[]`.
145
+ *
146
+ * Supersedes `collapsedSections`, and the polarity flip is the point.
147
+ * Storing what is COLLAPSED cannot express "section 1 open by default"
148
+ * without a sentinel, and an unseen scope has to be guessed at; storing
149
+ * what is OPEN makes a never-seen scope default correctly *by absence*.
150
+ *
151
+ * Namespaced because the flat list was global: every page's ids shared one
152
+ * array and were kept apart only by a naming convention. The scope key is
153
+ * `"{portal}:{surface}"` — deliberately NOT a hash of the section ids,
154
+ * which would change the moment a section is added and silently reset
155
+ * every user's layout.
156
+ *
157
+ * `collapsedSections` is still written during the migration window; see
158
+ * `useSectionState`'s one-way dual-read.
159
+ */
160
+ sectionState?: Record<string, string[]>;
161
+ /** Preferred density for list surfaces that offer a view switch.
162
+ * Was written by the route, the API type and useDataViewMode while being
163
+ * absent from this interface — declared 2026-08-29. */
164
+ dataViewMode?: "table" | "grid" | "list";
143
165
  /** Which side CTA panels/drawers/sidebars dock to and where close buttons
144
166
  * sit. Absent/"right" = default (right-docked). "left" = left-hand mode. */
145
167
  handMode?: "left" | "right";
@@ -26,9 +26,9 @@ export declare const loginSchema: z.ZodObject<{
26
26
  */
27
27
  export declare const registerPasswordSchema: z.ZodString;
28
28
  export declare const registerSchema: z.ZodObject<{
29
+ displayName: z.ZodOptional<z.ZodString>;
29
30
  email: z.ZodString;
30
31
  password: z.ZodString;
31
- displayName: z.ZodOptional<z.ZodString>;
32
32
  }, "strip", z.ZodTypeAny, {
33
33
  email: string;
34
34
  password: string;
@@ -1,7 +1,20 @@
1
1
  export * from "./firestore";
2
2
  import { z } from "zod";
3
3
  import { ERROR_MESSAGES } from "../../../errors/messages";
4
+ import { annotate } from "../../shell/field-ui-meta";
4
5
  // --- Form schemas -------------------------------------------------------------
6
+ /*
7
+ * The `annotate()` calls below add UI metadata to a WeakMap keyed on the schema
8
+ * object; they return the same schema and change nothing about parsing. That is
9
+ * what makes them safe on `registerSchema`, which the register ROUTE also
10
+ * parses — the server never reads the registry.
11
+ *
12
+ * Every auth form is a single required section. That is not a section in any
13
+ * interesting sense, and it is not meant to be: `sectionRequired` renders the
14
+ * panel open and non-collapsible, so the page looks exactly as it does today
15
+ * and gains the pinned mobile action bar, which is the whole reason to put a
16
+ * short form through <SectionForm> at all.
17
+ */
5
18
  /**
6
19
  * Login form schema — use with react-hook-form + zodResolver.
7
20
  *
@@ -10,8 +23,13 @@ import { ERROR_MESSAGES } from "../../../errors/messages";
10
23
  * const form = useForm({ resolver: zodResolver(loginSchema) });
11
24
  */
12
25
  export const loginSchema = z.object({
13
- email: z.string().email(),
14
- password: z.string().min(6),
26
+ email: annotate(z.string().email(), {
27
+ section: "basics", sectionLabel: "Sign in", sectionRequired: true,
28
+ order: 1, row: "full", inputType: "email", label: "Email",
29
+ }),
30
+ password: annotate(z.string().min(6), {
31
+ section: "basics", order: 2, row: "full", inputType: "password", label: "Password",
32
+ }),
15
33
  });
16
34
  /**
17
35
  * Matches the server-side complexity rule enforced in
@@ -27,16 +45,34 @@ export const registerPasswordSchema = z
27
45
  .regex(/[a-z]/, ERROR_MESSAGES.PASSWORD.NO_LOWERCASE)
28
46
  .regex(/[0-9]/, ERROR_MESSAGES.PASSWORD.NO_NUMBER);
29
47
  export const registerSchema = z.object({
30
- email: z.string().email(),
31
- password: registerPasswordSchema,
32
- displayName: z.string().min(1).optional(),
48
+ displayName: annotate(z.string().min(1).optional(), {
49
+ section: "basics", sectionLabel: "Create your account", sectionRequired: true,
50
+ order: 1, row: "full", label: "Display name",
51
+ }),
52
+ email: annotate(z.string().email(), {
53
+ section: "basics", order: 2, row: "full", inputType: "email", label: "Email",
54
+ }),
55
+ password: annotate(registerPasswordSchema, {
56
+ section: "basics", order: 3, row: "full", inputType: "password", label: "Password",
57
+ help: "At least 8 characters, with an uppercase letter, a lowercase letter and a number.",
58
+ }),
33
59
  });
34
60
  export const forgotPasswordSchema = z.object({
35
- email: z.string().email(),
61
+ email: annotate(z.string().email(), {
62
+ section: "basics", sectionLabel: "Reset your password", sectionRequired: true,
63
+ order: 1, row: "full", inputType: "email", label: "Email",
64
+ }),
36
65
  });
37
66
  export const resetPasswordSchema = z.object({
38
- token: z.string().min(1),
39
- password: z.string().min(6),
67
+ // Carried in the reset link, never typed. `derived` keeps it out of the
68
+ // rendered form; the schema still requires it, because the SUBMIT needs it.
69
+ token: annotate(z.string().min(1), {
70
+ section: "basics", order: 1, derived: true, tier: "t2-server",
71
+ }),
72
+ password: annotate(z.string().min(6), {
73
+ section: "basics", sectionLabel: "Choose a new password", sectionRequired: true,
74
+ order: 2, row: "full", inputType: "password", label: "New password",
75
+ }),
40
76
  });
41
77
  // --- Auth user schema ---------------------------------------------------------
42
78
  export const userRoleSchema = z.enum([
@@ -1,4 +1,17 @@
1
- export type UserRole = "user" | "seller" | "moderator" | "employee" | "admin";
1
+ /**
2
+ * `tester` sits at the SAME level as `seller` (see ROLE_HIERARCHY in
3
+ * security/authorization.ts) because a QA tester owns a real store and must
4
+ * pass every seller gate — the Tester QA Program depends on it.
5
+ *
6
+ * It replaces the `isTester` boolean. The companion `canTestAdmin` boolean
7
+ * became the `tester:admin-surfaces` permission rather than a second role,
8
+ * because this hierarchy is LINEAR: there is no level that means
9
+ * "seller-level, plus read admin" without also meaning admin.
10
+ *
11
+ * Both booleans are still read during the migration window — see
12
+ * `isTesterUser` / `canTestAdminSurfaces` in ./role-predicates.
13
+ */
14
+ export type UserRole = "user" | "seller" | "tester" | "moderator" | "employee" | "admin";
2
15
  export interface AuthUser {
3
16
  id: string;
4
17
  email?: string;
@@ -25,7 +25,15 @@ export async function GET(request) {
25
25
  try {
26
26
  const url = new URL(request.url);
27
27
  const page = numParam(url, "page", 1);
28
- const perPage = numParam(url, "perPage", 20);
28
+ /*
29
+ * `pageSize` is canonical; `perPage` is still read because this is a public
30
+ * GET and a link built before 2026-08-31 carries the old spelling. Ignoring
31
+ * it would silently serve the default page size with a 200 — the shape of
32
+ * failure this codebase keeps paying for.
33
+ */
34
+ const pageSize = url.searchParams.has("perPage")
35
+ ? numParam(url, "perPage", 20)
36
+ : numParam(url, "pageSize", 20);
29
37
  const sort = param(url, "sort") ?? "-publishedAt";
30
38
  const parts = [];
31
39
  const category = param(url, "category");
@@ -52,7 +60,7 @@ export async function GET(request) {
52
60
  // `blogRepository.listAll` rather than the generic `db.getRepository(...)`:
53
61
  // only the feature repository knows how to push a searchTxt clause down and
54
62
  // AND-refine the remaining terms.
55
- const result = await blogRepository.listAll({ filters, sorts: sort, page, pageSize: perPage }, q ? { search: q } : undefined);
63
+ const result = await blogRepository.listAll({ filters, sorts: sort, page, pageSize }, q ? { search: q } : undefined);
56
64
  const meta = {
57
65
  total: result.total,
58
66
  page: result.page,
@@ -106,7 +106,7 @@ export function BlogIndexListing({ initialData }) {
106
106
  tags: table.get(TABLE_KEYS.TAGS) || undefined,
107
107
  sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
108
108
  page: table.getNumber(TABLE_KEYS.PAGE, 1),
109
- perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, PAGE_SIZE),
109
+ pageSize: table.getNumber(TABLE_KEYS.PAGE_SIZE, PAGE_SIZE),
110
110
  };
111
111
  const { posts, totalPages, isLoading } = useBlogPosts(params, { initialData });
112
112
  const currentPage = table.getNumber("page", 1);
@@ -11,8 +11,8 @@ export function useBlogPosts(params = {}, opts) {
11
11
  searchParams.set("tags", params.tags);
12
12
  if (params.page)
13
13
  searchParams.set("page", String(params.page));
14
- if (params.perPage)
15
- searchParams.set("perPage", String(params.perPage));
14
+ if (params.pageSize)
15
+ searchParams.set("pageSize", String(params.pageSize));
16
16
  if (params.sort)
17
17
  searchParams.set("sort", params.sort);
18
18
  if (params.featured !== undefined)
@@ -458,6 +458,8 @@ export declare const blogListParamsSchema: z.ZodObject<{
458
458
  tags: z.ZodOptional<z.ZodString>;
459
459
  q: z.ZodOptional<z.ZodString>;
460
460
  page: z.ZodOptional<z.ZodNumber>;
461
+ pageSize: z.ZodOptional<z.ZodNumber>;
462
+ /** Accepted, never emitted — a link built before the 2026-08-31 rename. */
461
463
  perPage: z.ZodOptional<z.ZodNumber>;
462
464
  sort: z.ZodOptional<z.ZodString>;
463
465
  featured: z.ZodOptional<z.ZodBoolean>;
@@ -466,6 +468,7 @@ export declare const blogListParamsSchema: z.ZodObject<{
466
468
  page?: number | undefined;
467
469
  q?: string | undefined;
468
470
  perPage?: number | undefined;
471
+ pageSize?: number | undefined;
469
472
  category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
470
473
  featured?: boolean | undefined;
471
474
  tags?: string | undefined;
@@ -474,6 +477,7 @@ export declare const blogListParamsSchema: z.ZodObject<{
474
477
  page?: number | undefined;
475
478
  q?: string | undefined;
476
479
  perPage?: number | undefined;
480
+ pageSize?: number | undefined;
477
481
  category?: "news" | "tips" | "guides" | "updates" | "community" | undefined;
478
482
  featured?: boolean | undefined;
479
483
  tags?: string | undefined;
@@ -90,6 +90,8 @@ export const blogListParamsSchema = z.object({
90
90
  tags: z.string().optional(),
91
91
  q: z.string().optional(),
92
92
  page: z.coerce.number().optional(),
93
+ pageSize: z.coerce.number().optional(),
94
+ /** Accepted, never emitted — a link built before the 2026-08-31 rename. */
93
95
  perPage: z.coerce.number().optional(),
94
96
  sort: z.string().optional(),
95
97
  featured: z.coerce.boolean().optional(),
@@ -43,7 +43,7 @@ export interface BlogListParams {
43
43
  tags?: string;
44
44
  q?: string;
45
45
  page?: number;
46
- perPage?: number;
46
+ pageSize?: number;
47
47
  sort?: string;
48
48
  featured?: boolean;
49
49
  }
@@ -1,3 +1,25 @@
1
+ /**
2
+ * Add or edit an item in a user's personal catalogue.
3
+ *
4
+ * Fields come from `createCatalogueItemSchema`'s annotations. Three need a
5
+ * `renderers` entry: the photo list (a real uploader, not the generator's
6
+ * disabled placeholder), and the two taxonomy selects, which are searchable
7
+ * because 47 categories is well past the 5-option threshold.
8
+ *
9
+ * ## `categorySlugs` and `brandSlug` were declared, read, and never collected
10
+ *
11
+ * Both are inherited from `ProductDraftFields` and both are read by
12
+ * `product-from-item.ts`, which builds the marketplace listing when an item is
13
+ * promoted. The hand-rolled form had no control for either, so every promoted
14
+ * listing carried `categorySlugs: []` — and per the Category Tree rules a
15
+ * product with an empty chain is invisible on every category and brand page it
16
+ * should appear under. A schema field with a reader and no control is the
17
+ * inverse of a dead control, and just as silent.
18
+ *
19
+ * `mainImage` is `t1-derive` — the first photo, not a field anyone types. It
20
+ * renders as a read-only preview rather than a text box that could be desynced
21
+ * from the gallery it is supposed to mirror.
22
+ */
1
23
  import React from "react";
2
24
  import type { CatalogueItemDocument } from "../schemas/firestore";
3
25
  export interface CatalogueItemEditorViewProps {
@@ -1,60 +1,120 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Add or edit an item in a user's personal catalogue.
5
+ *
6
+ * Fields come from `createCatalogueItemSchema`'s annotations. Three need a
7
+ * `renderers` entry: the photo list (a real uploader, not the generator's
8
+ * disabled placeholder), and the two taxonomy selects, which are searchable
9
+ * because 47 categories is well past the 5-option threshold.
10
+ *
11
+ * ## `categorySlugs` and `brandSlug` were declared, read, and never collected
12
+ *
13
+ * Both are inherited from `ProductDraftFields` and both are read by
14
+ * `product-from-item.ts`, which builds the marketplace listing when an item is
15
+ * promoted. The hand-rolled form had no control for either, so every promoted
16
+ * listing carried `categorySlugs: []` — and per the Category Tree rules a
17
+ * product with an empty chain is invisible on every category and brand page it
18
+ * should appear under. A schema field with a reader and no control is the
19
+ * inverse of a dead control, and just as silent.
20
+ *
21
+ * `mainImage` is `t1-derive` — the first photo, not a field anyone types. It
22
+ * renders as a read-only preview rather than a text box that could be desynced
23
+ * from the gallery it is supposed to mirror.
24
+ */
3
25
  import React from "react";
4
26
  import { useQueryClient } from "@tanstack/react-query";
5
27
  import { useApiMutation } from "@mohasinac/appkit/client";
6
- import { Alert, Button, FieldInput, FieldSelect, FieldTextarea, Form, Grid, Row, Span, Stack, Toggle } from "../../../ui";
7
- import { FormErrorSummary } from "../../../ui/forms";
8
- import { MediaUploadList } from "../../media/upload/MediaUploadList";
9
- import { useMediaUpload } from "../../media";
28
+ import { ACCOUNT_ENDPOINTS, BRAND_ENDPOINTS, CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints";
10
29
  import { apiClient } from "../../../http";
11
- import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
30
+ import { Alert, PaginatedSelect, Stack } from "../../../ui";
31
+ import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
32
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
33
+ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
34
+ import { buildSectionsFromSchema } from "../../shell/build-sections";
35
+ import { SectionForm, useSectionFormNav } from "../../shell/SectionForm";
36
+ import { useMediaUpload } from "../../media";
37
+ import { MediaUploadList } from "../../media/upload/MediaUploadList";
12
38
  import { createCatalogueItemSchema } from "../schemas/validation";
13
- const CONDITION_OPTIONS = [
14
- { label: "New", value: "new" },
15
- { label: "Like new", value: "like_new" },
16
- { label: "Good", value: "good" },
17
- { label: "Fair", value: "fair" },
18
- { label: "Poor", value: "poor" },
19
- { label: "Used", value: "used" },
20
- { label: "Refurbished", value: "refurbished" },
21
- ];
39
+ /** PUBLIC endpoints — this is a buyer surface; the admin ones would 403. */
40
+ async function loadOptionsFrom(endpoint, query, page) {
41
+ const params = new URLSearchParams({ page: String(page), pageSize: "25" });
42
+ if (query)
43
+ params.set("q", query);
44
+ const res = await apiClient.get(`${endpoint}?${params.toString()}`);
45
+ const data = res ?? {};
46
+ return {
47
+ items: (data.items ?? []).map((c) => ({ value: c.id, label: c.name })),
48
+ hasMore: data.hasMore ?? false,
49
+ };
50
+ }
51
+ const loadCategoryOptions = (q, p) => loadOptionsFrom(CATEGORY_ENDPOINTS.LIST, q, p);
52
+ const loadBrandOptions = (q, p) => loadOptionsFrom(BRAND_ENDPOINTS.LIST, q, p);
22
53
  export function CatalogueItemEditorView({ item, onSaved }) {
23
54
  const isCreate = !item;
24
55
  const queryClient = useQueryClient();
25
56
  const { upload } = useMediaUpload();
26
57
  const uploadIndexRef = React.useRef(0);
27
- const [title, setTitle] = React.useState(item?.title ?? "");
28
- const [description, setDescription] = React.useState(item?.description ?? "");
29
- const [condition, setCondition] = React.useState(item?.condition ?? "");
30
- const [price, setPrice] = React.useState(item?.price ? String(item.price) : "0");
31
- const [quantity, setQuantity] = React.useState(String(item?.quantity ?? 1));
32
- const [visibility, setVisibility] = React.useState(item?.visibility ?? "public");
33
- const [images, setImages] = React.useState(item?.images ?? []);
34
- const galleryFields = images.map((url) => ({ url, type: "image" }));
35
- const handleUpload = async (file) => {
58
+ const [form, setForm] = React.useState({
59
+ title: item?.title ?? "",
60
+ description: item?.description ?? "",
61
+ condition: item?.condition ?? "",
62
+ quantity: item?.quantity ?? 1,
63
+ price: item?.price ?? 0,
64
+ images: item?.images ?? [],
65
+ mainImage: item?.mainImage ?? item?.images?.[0] ?? "",
66
+ categorySlugs: item?.categorySlugs ?? [],
67
+ brandSlug: item?.brandSlug ?? "",
68
+ visibility: item?.visibility ?? "public",
69
+ });
70
+ const handleUpload = React.useCallback(async (file) => {
36
71
  uploadIndexRef.current += 1;
37
72
  return upload(file, "catalogue", true, {
38
73
  type: "catalogue-image",
39
- item: title || "item",
74
+ item: form.title || "item",
40
75
  index: uploadIndexRef.current,
41
76
  });
42
- };
77
+ }, [upload, form.title]);
78
+ const sections = React.useMemo(() => buildSectionsFromSchema(createCatalogueItemSchema, {
79
+ renderers: {
80
+ images: ({ values, onChange }) => (_jsx(MediaUploadList, { label: "Photos", value: values.images.map((url) => ({ url, type: "image" })), onChange: (fields) => {
81
+ const urls = fields.map((f) => f.url);
82
+ // `mainImage` is derived, so it moves with the gallery rather
83
+ // than pointing at a photo the user has since removed.
84
+ onChange({ images: urls, mainImage: urls[0] ?? "" });
85
+ }, onUpload: handleUpload, accept: "image/*", maxItems: 8, helperText: "Photos older than 30 days must be refreshed before this item can be listed." })),
86
+ categorySlugs: ({ values, onChange }) => (
87
+ // Explicit `<string>`: with `multiple`, `value` is `V[]`, so an
88
+ // inferred V reads the array itself as the option type.
89
+ _jsx(PaginatedSelect, { multiple: true, value: values.categorySlugs, onChange: (v) => onChange({ categorySlugs: v ?? [] }), loadOptions: loadCategoryOptions, placeholder: "Search categories\u2026", searchPlaceholder: "Type a category name\u2026", noResultsText: "No categories found", ariaLabel: "Categories" })),
90
+ brandSlug: ({ values, onChange }) => (_jsx(PaginatedSelect, { value: values.brandSlug || null, onChange: (v) => onChange({ brandSlug: v ?? "" }), loadOptions: loadBrandOptions, placeholder: "Search brands\u2026", searchPlaceholder: "Type a brand name\u2026", noResultsText: "No brands found", ariaLabel: "Brand" })),
91
+ },
92
+ }), [handleUpload]);
93
+ const nav = useSectionFormNav(sections, form, { scope: "user:catalogue-item" });
94
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState(createCatalogueItemSchema, {
95
+ sections: nav.sectionMeta,
96
+ onGoToSection: nav.goToSection,
97
+ fieldToSectionIndex: nav.fieldToSectionIndex,
98
+ });
43
99
  const saveMutation = useApiMutation({
44
100
  successMessage: isCreate ? "Added to your catalogue" : "Catalogue item updated",
101
+ errorMessage: isCreate ? "Could not add this item." : "Could not save your changes.",
45
102
  mutationFn: async () => {
46
103
  const payload = {
47
- title,
48
- description: description || undefined,
49
- condition: condition || undefined,
50
- price: Math.round(Number(price) * 100) / 100,
51
- quantity: Number(quantity),
52
- visibility,
53
- images,
54
- mainImage: images[0],
104
+ title: form.title,
105
+ description: form.description || undefined,
106
+ condition: form.condition || undefined,
107
+ price: Math.round(Number(form.price) * 100) / 100,
108
+ quantity: Number(form.quantity),
109
+ visibility: form.visibility,
110
+ images: form.images,
111
+ mainImage: form.images[0],
112
+ categorySlugs: form.categorySlugs,
113
+ brandSlug: form.brandSlug || undefined,
55
114
  };
56
- if (isCreate)
115
+ if (isCreate) {
57
116
  return apiClient.post(ACCOUNT_ENDPOINTS.CATALOGUE, payload);
117
+ }
58
118
  return apiClient.patch(ACCOUNT_ENDPOINTS.CATALOGUE_BY_ID(item.id), payload);
59
119
  },
60
120
  onSuccess: (saved) => {
@@ -62,21 +122,21 @@ export function CatalogueItemEditorView({ item, onSaved }) {
62
122
  onSaved?.(saved.id);
63
123
  },
64
124
  });
65
- return (_jsx(Form, { schema: createCatalogueItemSchema, spacing: "md", children: ({ setFieldError, markSubmitAttempted }) => (_jsxs(Stack, { gap: "md", children: [item?.listingStatus && item.listingStatus !== "not_listed" && (_jsxs(Alert, { variant: "info", children: ["This item is ", item.listingStatus.replace(/_/g, " "), item.listingStatus === "rejected" && item.rejectionReason ? `: ${item.rejectionReason}` : "."] })), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(FieldInput, { name: "title", label: "Title", required: true, value: title, onChange: setTitle }), _jsx(FieldSelect, { name: "condition", label: "Condition", value: condition, onChange: setCondition, options: CONDITION_OPTIONS }), _jsx(FieldInput, { name: "price", label: "Estimated resale price (\u20B9)", type: "number", min: "0", value: price, onChange: setPrice }), _jsx(FieldInput, { name: "quantity", label: "Quantity", type: "number", min: "1", value: quantity, onChange: setQuantity })] }), _jsx(FieldTextarea, { name: "description", label: "Description", value: description, onChange: setDescription, rows: 3 }), _jsx(MediaUploadList, { label: "Photos", value: galleryFields, onChange: (fields) => setImages(fields.map((f) => f.url)), onUpload: handleUpload, accept: "image/*", maxItems: 8, helperText: "Photos older than 30 days must be refreshed before this item can be listed." }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(Toggle, { checked: visibility === "public", onChange: (checked) => setVisibility(checked ? "public" : "private"), size: "sm" }), _jsx(Span, { children: visibility === "public" ? "Public — visible on your profile" : "Private — only you can see it" })] }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "button", isLoading: saveMutation.isPending, onClick: () => {
66
- // type="button" — no native submit, so unhide the summary here.
67
- markSubmitAttempted();
68
- const parsed = createCatalogueItemSchema.safeParse({
69
- title,
70
- images,
71
- price: Math.round(Number(price) * 100) / 100,
72
- quantity: Number(quantity),
73
- visibility,
74
- });
75
- if (!parsed.success) {
76
- for (const issue of parsed.error.issues)
77
- setFieldError(String(issue.path[0]), issue.message);
78
- return;
79
- }
80
- saveMutation.mutate();
81
- }, children: isCreate ? "Add to Catalogue" : "Save Changes" })] })) }));
125
+ const onSubmit = () => {
126
+ clearErrors();
127
+ const parsed = createCatalogueItemSchema.safeParse({
128
+ ...form,
129
+ price: Math.round(Number(form.price) * 100) / 100,
130
+ quantity: Number(form.quantity),
131
+ brandSlug: form.brandSlug || undefined,
132
+ });
133
+ if (!parsed.success) {
134
+ applyZodIssues(parsed.error.issues, setFieldError);
135
+ return;
136
+ }
137
+ saveMutation.mutate();
138
+ };
139
+ return (_jsxs(Stack, { gap: "md", children: [item?.listingStatus && item.listingStatus !== "not_listed" && (_jsxs(Alert, { variant: "info", children: ["This item is ", item.listingStatus.replace(/_/g, " "), item.listingStatus === "rejected" && item.rejectionReason
140
+ ? `: ${item.rejectionReason}`
141
+ : "."] })), _jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: form, onChange: (partial) => setForm((prev) => Object.assign({}, prev, partial)), onSubmit: onSubmit, schema: createCatalogueItemSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: saveMutation.isPending, submitLabel: isCreate ? "Add to Catalogue" : "Save Changes" })] })] }));
82
142
  }
@@ -2,13 +2,13 @@ import { z } from "zod";
2
2
  export declare const createCatalogueItemSchema: z.ZodObject<{
3
3
  title: z.ZodString;
4
4
  description: z.ZodOptional<z.ZodString>;
5
+ condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken", "graded"]>>;
6
+ quantity: z.ZodDefault<z.ZodNumber>;
5
7
  images: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
6
8
  mainImage: z.ZodOptional<z.ZodString>;
7
- condition: z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken", "graded"]>>;
8
9
  categorySlugs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
10
  brandSlug: z.ZodOptional<z.ZodString>;
10
11
  price: z.ZodOptional<z.ZodNumber>;
11
- quantity: z.ZodDefault<z.ZodNumber>;
12
12
  visibility: z.ZodDefault<z.ZodEnum<["public", "private"]>>;
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  title: string;
@@ -36,13 +36,13 @@ export declare const createCatalogueItemSchema: z.ZodObject<{
36
36
  export declare const updateCatalogueItemSchema: z.ZodObject<{
37
37
  title: z.ZodOptional<z.ZodString>;
38
38
  description: z.ZodOptional<z.ZodOptional<z.ZodString>>;
39
+ condition: z.ZodOptional<z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken", "graded"]>>>;
40
+ quantity: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
39
41
  images: z.ZodOptional<z.ZodDefault<z.ZodArray<z.ZodString, "many">>>;
40
42
  mainImage: z.ZodOptional<z.ZodOptional<z.ZodString>>;
41
- condition: z.ZodOptional<z.ZodOptional<z.ZodEnum<["new", "like_new", "good", "fair", "poor", "used", "refurbished", "broken", "graded"]>>>;
42
43
  categorySlugs: z.ZodOptional<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;
43
44
  brandSlug: z.ZodOptional<z.ZodOptional<z.ZodString>>;
44
45
  price: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
45
- quantity: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
46
46
  visibility: z.ZodOptional<z.ZodDefault<z.ZodEnum<["public", "private"]>>>;
47
47
  }, "strip", z.ZodTypeAny, {
48
48
  title?: string | undefined;