@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
@@ -14,12 +14,10 @@ import { storeRepository } from "../../stores/repository/store.repository";
14
14
  import { cartRepository } from "../../cart/repository/cart.repository";
15
15
  import { maskOfferForSeller } from "../../../security";
16
16
  import { ERROR_MESSAGES, AuthorizationError, ValidationError, NotFoundError, OFFER_ERROR_CODES, } from "../../../errors";
17
- import { siteSettingsRepository } from "../../admin/repository/site-settings.repository";
18
- import { safeRead } from "../../../errors/safe-read";
19
17
  import { canMakeOffer } from "../../../_internal/shared/listing-types/capabilities";
20
18
  import { normalizeListingType } from "../../products/utils/listing-type";
21
19
  import { OfferStatusValues } from "../schemas";
22
- import { DEFAULT_MIN_OFFER_PERCENT, minOfferAmount, } from "../../../_internal/shared/features/offers/config";
20
+ import { DEFAULT_MIN_OFFER_PERCENT, offerIsOnlyPurchasePath, offersEnabledFor, resolveOfferBounds, } from "../../../_internal/shared/features/offers/config";
23
21
  /**
24
22
  * How long an accepted offer stays claimable at its locked price. Shared by the
25
23
  * seller-accept path, the buyer-accepts-counter path, and the expiry sweep so
@@ -32,38 +30,59 @@ const ERR_NOT_AUTHORISED = "Not authorized";
32
30
  export async function makeOffer(userId, userEmail, input) {
33
31
  const { productId, offerAmount, buyerNote } = input;
34
32
  /**
35
- * Three independent gates, checked in widening-to-narrowing order. Only
36
- * `makeOffer` is guarded — switching the site flag off must stop NEW offers
37
- * without stranding in-flight ones, so accept/decline/counter/withdraw and
38
- * `checkoutOffer` deliberately stay open.
33
+ * Two independent gates, checked in widening-to-narrowing order: the listing
34
+ * type's `canMakeOffer` capability, then the seller's own `allowOffers`
35
+ * opt-in.
36
+ *
37
+ * There was a third — the site-wide `featureFlags.offers` kill switch — which
38
+ * cost a Firestore singleton read on every offer attempt. It went with the
39
+ * rest of that group on 2026-08-29. The two that remain are the ones somebody
40
+ * actually sets: a per-type capability and a per-listing choice by the seller
41
+ * who owns the item.
42
+ *
43
+ * Both now go through `offersEnabledFor` / `resolveOfferBounds`, which the
44
+ * buyer's form reads too — the whole point of moving the rule into the shared
45
+ * config. A second copy here is how the form came to advertise a 50% floor
46
+ * this function then rejected (see the config's own header).
39
47
  */
40
- // Fails OPEN — a settings read that dies must not block offers site-wide. But
41
- // `null` here is indistinguishable from "the flag is on", so it is recorded.
42
- const settings = await safeRead(() => siteSettingsRepository.getSingleton(), {
43
- route: "offers.makeOffer", key: "makeOffer.siteSettings", fallback: null,
44
- });
45
- if (settings?.featureFlags?.offers === false) {
46
- throw new ValidationError(ERROR_MESSAGES.OFFER.DISABLED, {
47
- code: OFFER_ERROR_CODES.DISABLED,
48
- });
49
- }
50
48
  const product = await productRepository.findById(productId);
51
49
  if (!product)
52
50
  throw new NotFoundError(ERROR_MESSAGES.PRODUCT.NOT_FOUND);
51
+ const listingType = normalizeListingType(product);
53
52
  // Some listing types cannot be negotiated at all — an auction already has
54
53
  // bidding, a prize-draw sells fixed-price entries. This was never checked, so
55
54
  // an offer could be (and in seed data was) attached to an auction, producing
56
55
  // a cart line that claims `listingType: "standard"` in the offer lane.
57
- if (!canMakeOffer(normalizeListingType(product))) {
56
+ if (!canMakeOffer(listingType)) {
58
57
  throw new ValidationError("Offers aren't available for this kind of listing.");
59
58
  }
60
- if (!product.allowOffers)
59
+ if (!offersEnabledFor(listingType, product.allowOffers))
61
60
  throw new ValidationError("This product does not accept offers.");
62
- const minAllowed = minOfferAmount(product.price, product.minOfferPercent);
63
- if (offerAmount < minAllowed)
64
- throw new ValidationError(`Minimum offer is ₹${minAllowed} (${product.minOfferPercent ?? DEFAULT_MIN_OFFER_PERCENT}% of listing price).`);
65
- if (offerAmount >= product.price)
66
- throw new ValidationError("Your offer must be below the listed price. Use Add to Cart instead.");
61
+ const bounds = resolveOfferBounds({
62
+ listingType,
63
+ listedPrice: product.price,
64
+ minOfferPercent: product.minOfferPercent,
65
+ allowOffers: product.allowOffers,
66
+ });
67
+ if (bounds.isBuyRequest) {
68
+ // No cart on this type and the seller has not opted into haggling, so the
69
+ // asking price is the only legal amount. Naming that plainly beats a
70
+ // "minimum offer is ₹X" message where X happens to equal the price.
71
+ if (offerAmount !== bounds.max)
72
+ throw new ValidationError(`This seller is not taking lower offers — send a request to buy at ₹${bounds.max}.`);
73
+ }
74
+ else {
75
+ if (offerAmount < bounds.min)
76
+ throw new ValidationError(`Minimum offer is ₹${bounds.min} (${product.minOfferPercent ?? DEFAULT_MIN_OFFER_PERCENT}% of listing price).`);
77
+ if (offerAmount > bounds.max)
78
+ throw new ValidationError(
79
+ // Deliberately NOT "use Add to Cart instead" unconditionally: on a
80
+ // listing whose only purchase path is an offer there is no cart button,
81
+ // so that advice named a control the page does not have.
82
+ offerIsOnlyPurchasePath(listingType)
83
+ ? `Your offer cannot exceed the asking price of ₹${product.price}.`
84
+ : "Your offer must be below the listed price. Use Add to Cart instead.");
85
+ }
67
86
  const offerCount = await offerRepository.countByBuyerAndProduct(userId, productId, product.updatedAt);
68
87
  if (offerCount >= 3)
69
88
  throw new ValidationError(ERROR_MESSAGES.OFFER.LIMIT_REACHED);
@@ -9,7 +9,7 @@
9
9
  * (src/actions/seller.actions.ts) alongside the other seller server actions.
10
10
  */
11
11
  import { serverLogger } from "../../../monitoring";
12
- import { isAdminUser, isSellerUser } from "../../auth/role-predicates";
12
+ import { isAdminUser, isStrictSellerUser, isTesterUser } from "../../auth/role-predicates";
13
13
  import { ApiError, AuthorizationError, NotFoundError, ValidationError, } from "../../../errors";
14
14
  import { userRepository } from "../../auth/repository/user.repository";
15
15
  import { storeRepository } from "../../stores/repository/store.repository";
@@ -53,16 +53,40 @@ function maskAccountNumber(accountNumber) {
53
53
  // --- Become Seller ------------------------------------------------------------
54
54
  export async function becomeSeller(userId) {
55
55
  const profile = await userRepository.findById(userId);
56
- if (profile && (isSellerUser(profile) || isAdminUser(profile))) {
56
+ const isTester = isTesterUser(profile);
57
+ /*
58
+ * 🛑 `isStrictSellerUser`, not `isSellerUser`.
59
+ *
60
+ * `isSellerUser` now answers true for the `tester` role — testers own real
61
+ * stores and must clear every seller gate. But that makes it the WRONG test
62
+ * here: a brand-new tester has role `tester` from the moment they are
63
+ * flagged, so an early return keyed on it would send them straight back with
64
+ * `storeStatus: "pending"` and they would never reach the auto-approve below.
65
+ * The tester sandbox depends on that approval.
66
+ *
67
+ * A tester who HAS already onboarded is caught by the `storeStatus` clause,
68
+ * so this stays idempotent for them.
69
+ */
70
+ const alreadyOnboarded = profile &&
71
+ (isStrictSellerUser(profile) ||
72
+ isAdminUser(profile) ||
73
+ (isTester && Boolean(profile.storeStatus)));
74
+ if (alreadyOnboarded) {
57
75
  return {
58
76
  alreadySeller: true,
59
77
  storeStatus: profile.storeStatus ??
60
78
  "pending",
61
79
  };
62
80
  }
63
- const storeStatus = profile?.isTester ? "approved" : "pending";
81
+ const storeStatus = isTester ? "approved" : "pending";
64
82
  await userRepository.update(userId, {
65
- role: "seller",
83
+ // 🛑 A tester must NOT be demoted to `seller` here. `tester` is a peer of
84
+ // `seller` in the hierarchy and already clears every seller gate
85
+ // (`isSellerUser` returns true for it), so overwriting the role would
86
+ // silently strip their tester identity — and with it the Tester Hub and
87
+ // the test-data visibility their whole programme depends on — the first
88
+ // time they opened a store.
89
+ ...(isTester ? {} : { role: "seller" }),
66
90
  storeStatus,
67
91
  });
68
92
  serverLogger.info("becomeSeller: application submitted", { userId, storeStatus });
@@ -83,7 +107,7 @@ export async function createStore(userId, userName, input) {
83
107
  storeSlug = `${baseSlug.slice(0, 80 - suffix.length)}${suffix}`;
84
108
  }
85
109
  const profile = await userRepository.findById(userId);
86
- const isTester = profile?.isTester === true;
110
+ const isTester = isTesterUser(profile);
87
111
  const store = await storeRepository.create({
88
112
  storeSlug,
89
113
  ownerId: userId,
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ export interface CategoryChipPickerProps {
3
+ selected: string[];
4
+ onSelectedChange: (next: string[]) => void;
5
+ /** Copy under the control. Defaults to the coupon wording. */
6
+ emptyHint?: string;
7
+ allowCreate?: boolean;
8
+ }
9
+ export declare function CategoryChipPicker({ selected, onSelectedChange, emptyHint, allowCreate, }: CategoryChipPickerProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { IconButton, Row, Span, Stack, Text } from "../../../ui";
4
+ import { CategoryInlineSelect } from "./CategoryInlineSelect";
5
+ export function CategoryChipPicker({ selected, onSelectedChange, emptyHint = "Leave empty to apply to every category.", allowCreate = false, }) {
6
+ const add = (id) => {
7
+ if (!id || selected.includes(id))
8
+ return;
9
+ onSelectedChange([...selected, id]);
10
+ };
11
+ return (_jsxs(Stack, { gap: "xs", children: [_jsx(CategoryInlineSelect, { value: "", onChange: add, allowCreate: allowCreate, placeholder: "Add a category\u2026" }), selected.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", padding: "t-2xs", children: selected.map((cid) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", border: "strong", padding: "pill-sm", rounded: "full", surface: "muted", color: "primary", size: "xs", children: [cid, _jsx(IconButton, { "aria-label": `Remove ${cid}`, variant: "ghost", size: "sm", onClick: () => onSelectedChange(selected.filter((c) => c !== cid)), icon: "\u00D7" })] }, cid))) })), _jsx(Text, { size: "xs", color: "muted", children: emptyHint })] }));
12
+ }
@@ -146,5 +146,9 @@ export function SellerAddressesView({ apiBase = SELLER_ENDPOINTS.STORE_ADDRESSES
146
146
  return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(StickyToolbar, { offset: "header", tone: "default", border: true, padding: "md", children: _jsxs(Row, { justify: "between", children: [_jsxs(Stack, { gap: "none", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: "Pickup Addresses" }), _jsx(Text, { size: "xs", color: "muted", className: "mt-0.5", children: "Manage your store's pickup and return locations" })] }), _jsxs(Row, { gap: "sm", align: "center", children: [_jsx(TextLink, { href: String(ROUTES.STORE.ADDRESSES_NEW), children: "Open full page \u2192" }), _jsxs(Button, { gap: "sm", size: "sm", onClick: openAdd, children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx(Span, { children: "Add Address" })] })] })] }) }), _jsxs(Div, { paddingX: "x-md-lg", className: "max-w-2xl", padding: "y-lg", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), isLoading ? (_jsx(Row, { justify: "center", padding: "y-4xl", children: _jsx(Div, { className: "h-6 w-6 animate-spin border-2 border-[var(--appkit-color-primary)] border-t-transparent", rounded: "full" }) })) : addresses.length === 0 ? (_jsxs(Stack, { border: "default", className: "border-2 border-dashed", padding: "y-4xl", align: "center", gap: "3", rounded: "xl", children: [_jsx(MapPin, { className: "h-8 w-8 text-[var(--appkit-color-text-muted)]" }), _jsx(Text, { size: "sm", color: "muted", children: "No pickup addresses yet" }), _jsx(Button, { size: "sm", variant: "outline", onClick: openAdd, children: "Add your first address" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Row, { justify: "end", className: "mb-2", children: [_jsx(Button, { size: "sm", variant: listView === "table" ? "primary" : "ghost", onClick: () => setListView("table"), children: "Table" }), _jsx(Button, { size: "sm", variant: listView === "cards" ? "primary" : "ghost", onClick: () => setListView("cards"), children: "Cards" })] }), listView === "cards" ? (_jsx(Grid, { gap: "sm", children: addresses.map((addr) => (_jsx(Div, { className: deletingId === addr.id ? "opacity-50 pointer-events-none" : "", children: _jsx(AddressCard, { address: addr, onEdit: () => openEdit(addr), onDelete: () => handleDelete(addr) }) }, addr.id))) })) : (_jsx(Div, { className: `${__O.xAuto}`, rounded: "lg", border: "default", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { surface: "muted", children: _jsxs(Tr, { children: [_jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Label" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Name" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "City" }), _jsx(Th, { className: "text-left", padding: "sm", weight: "semibold", children: "Phone" }), _jsx(Th, { className: "text-right", padding: "sm", weight: "semibold", children: "Actions" })] }) }), _jsx(Tbody, { children: addresses.map((addr) => {
147
147
  const isBanned = addr.banStatus === "banned";
148
148
  return (_jsxs(Tr, { className: `border-t border-[var(--appkit-color-border)] ${deletingId === addr.id ? "opacity-50" : ""}`, children: [_jsx(Td, { padding: "sm", children: _jsxs(Row, { gap: "xs", align: "center", children: [_jsx(Span, { children: addr.label }), isBanned && _jsx(Badge, { variant: "danger", size: "sm", children: "Banned" })] }) }), _jsx(Td, { padding: "sm", children: addr.fullName }), _jsxs(Td, { padding: "sm", children: [addr.city, ", ", addr.state] }), _jsx(Td, { className: "tabular-nums", padding: "sm", children: addr.phone }), _jsx(Td, { className: "text-right", padding: "sm", children: isBanned ? (_jsx(Span, { size: "xs", color: "muted", title: "Contact support to resolve address ban", children: "Contact support" })) : (_jsxs(Row, { justify: "end", gap: "xs", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => openEdit(addr), children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => handleDelete(addr), children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label })] })) })] }, addr.id));
149
- }) })] }) }))] }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer: _jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, className: "flex-1", children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, className: "flex-1", children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs(Stack, { gap: "md", padding: "y-2xs", children: [saveError && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError })), _jsx(FieldInput, { name: "label", label: "Label *", hint: "e.g. Warehouse, Shop, Home", type: "text", value: draft.label, onChange: setField("label"), placeholder: "Warehouse", maxLength: 60 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "fullName", label: "Full Name *", type: "text", value: draft.fullName, onChange: setField("fullName"), placeholder: "Ravi Kumar", maxLength: 100 }), _jsx(FieldInput, { name: "phone", label: "Phone *", type: "tel", value: draft.phone, onChange: setField("phone"), placeholder: "+91 98765 43210", maxLength: 20 })] }), _jsx(FieldInput, { name: "addressLine1", label: "Address Line 1 *", type: "text", value: draft.addressLine1, onChange: setField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200 }), _jsx(FieldInput, { name: "addressLine2", label: "Address Line 2", type: "text", value: draft.addressLine2, onChange: setField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200 }), _jsx(FieldInput, { name: "landmark", label: "Landmark", type: "text", value: draft.landmark, onChange: setField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "city", label: "City *", type: "text", value: draft.city, onChange: setField("city"), placeholder: "Mumbai", maxLength: 100 }), _jsx(FieldInput, { name: "state", label: "State *", type: "text", value: draft.state, onChange: setField("state"), placeholder: "Maharashtra", maxLength: 100 })] }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "postalCode", label: "Postal Code *", type: "text", value: draft.postalCode, onChange: setField("postalCode"), placeholder: "400001", maxLength: 10 }), _jsx(FieldInput, { name: "country", label: "Country *", type: "text", value: draft.country, onChange: setField("country"), placeholder: "India", maxLength: 60 })] }), _jsx(FieldCheckbox, { name: "isDefault", label: "Set as default pickup address", checked: draft.isDefault, onChange: (c) => set("isDefault", c) })] }) }), deleteTargetAddr && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Address", message: `Delete address "${deleteTargetAddr.label}"? This cannot be undone.`, onConfirm: () => { deleteById(deleteTargetAddr.id); setDeleteTargetAddr(null); }, onClose: () => setDeleteTargetAddr(null), isDeleting: deletingId === deleteTargetAddr.id }))] }));
149
+ }) })] }) }))] }))] }), _jsx(SideDrawer, { isOpen: drawerOpen, onClose: closeDrawer, title: editingId ? "Edit Address" : "Add Address", footer:
150
+ /* Bare buttons — the SideDrawer footer slot supplies the ActionRow.
151
+ The `flex-1` these carried would have beaten its sizing under
152
+ `important: true`. */
153
+ _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: closeDrawer, children: "Cancel" }), _jsx(Button, { onClick: handleSave, disabled: saving, children: saving ? "Saving…" : editingId ? "Save Changes" : "Add Address" })] }), children: _jsxs(Stack, { gap: "md", padding: "y-2xs", children: [saveError && (_jsx(Div, { textSize: "sm", className: "border border-error/20", color: "error", surface: "danger-surface", padding: "inlineSm", rounded: "lg", children: saveError })), _jsx(FieldInput, { name: "label", label: "Label *", hint: "e.g. Warehouse, Shop, Home", type: "text", value: draft.label, onChange: setField("label"), placeholder: "Warehouse", maxLength: 60 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "fullName", label: "Full Name *", type: "text", value: draft.fullName, onChange: setField("fullName"), placeholder: "Ravi Kumar", maxLength: 100 }), _jsx(FieldInput, { name: "phone", label: "Phone *", type: "tel", value: draft.phone, onChange: setField("phone"), placeholder: "+91 98765 43210", maxLength: 20 })] }), _jsx(FieldInput, { name: "addressLine1", label: "Address Line 1 *", type: "text", value: draft.addressLine1, onChange: setField("addressLine1"), placeholder: "Shop 12, Main Market", maxLength: 200 }), _jsx(FieldInput, { name: "addressLine2", label: "Address Line 2", type: "text", value: draft.addressLine2, onChange: setField("addressLine2"), placeholder: "Building / Floor (optional)", maxLength: 200 }), _jsx(FieldInput, { name: "landmark", label: "Landmark", type: "text", value: draft.landmark, onChange: setField("landmark"), placeholder: "Near metro station (optional)", maxLength: 100 }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "city", label: "City *", type: "text", value: draft.city, onChange: setField("city"), placeholder: "Mumbai", maxLength: 100 }), _jsx(FieldInput, { name: "state", label: "State *", type: "text", value: draft.state, onChange: setField("state"), placeholder: "Maharashtra", maxLength: 100 })] }), _jsxs(Grid, { cols: 2, gap: "sm", children: [_jsx(FieldInput, { name: "postalCode", label: "Postal Code *", type: "text", value: draft.postalCode, onChange: setField("postalCode"), placeholder: "400001", maxLength: 10 }), _jsx(FieldInput, { name: "country", label: "Country *", type: "text", value: draft.country, onChange: setField("country"), placeholder: "India", maxLength: 60 })] }), _jsx(FieldCheckbox, { name: "isDefault", label: "Set as default pickup address", checked: draft.isDefault, onChange: (c) => set("isDefault", c) })] }) }), deleteTargetAddr && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Address", message: `Delete address "${deleteTargetAddr.label}"? This cannot be undone.`, onConfirm: () => { deleteById(deleteTargetAddr.id); setDeleteTargetAddr(null); }, onClose: () => setDeleteTargetAddr(null), isDeleting: deletingId === deleteTargetAddr.id }))] }));
150
154
  }
@@ -1,27 +1,44 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useApiMutation } from "@mohasinac/appkit/client";
4
- import { useState, useCallback } from "react";
4
+ import React, { useState, useCallback } from "react";
5
5
  import { useQuery, useQueryClient } from "@tanstack/react-query";
6
- import { Alert, Badge, Button, Div, Form, FormActions, Heading, Input, Row, Section, Select, Stack, Text, Toggle, useToast } from "../../../ui";
6
+ import { Alert, Badge, Button, Div, Heading, Row, Section, Stack, Text, Toggle, useToast } from "../../../ui";
7
7
  import { apiClient } from "../../../http";
8
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
- import { analyticsAlertCreateSchema } from "../../store-extensions/schemas/analytics-forms";
9
+ import { analyticsAlertCreateSchema, ANALYTICS_ALERT_OPERATORS, } from "../../store-extensions/schemas/analytics-forms";
10
10
  import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
11
- import { ValidationError } from "../../../errors/validation-error";
11
+ import { FormShellContext, useFormShellState } from "../../../ui/forms/FormShell";
12
+ import { applyZodIssues } from "../../../ui/forms/apply-zod-issues";
13
+ import { buildSectionsFromSchema, visibleValues } from "../../shell/build-sections";
14
+ import { SectionForm, useSectionFormNav } from "../../shell/SectionForm";
15
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
12
16
  const __P = {
13
17
  p5: "p-[var(--appkit-space-5)]",
14
18
  };
15
19
  // ---------------------------------------------------------------------------
16
20
  // Config
17
21
  // ---------------------------------------------------------------------------
18
- const OPERATOR_OPTIONS = [
19
- { value: ">", label: "Greater than (>)" },
20
- { value: "<", label: "Less than (<)" },
21
- { value: ">=", label: "≥" },
22
- { value: "<=", label: "≤" },
23
- { value: "==", label: "Equals (==)" },
24
- ];
22
+ /**
23
+ * Operators, DERIVED from the schema's own list.
24
+ *
25
+ * The hand-written array had five of the six — `!=` was missing, so "not equal
26
+ * to" could never be chosen although `ALERT_OPERATOR_MAP` declares it and the
27
+ * route accepts it. That map is a `Record<AlertOperator, true>` precisely so
28
+ * the union cannot drift; the drift moved into the UI copy instead.
29
+ */
30
+ const OPERATOR_LABELS = {
31
+ ">": "Greater than (>)",
32
+ "<": "Less than (<)",
33
+ ">=": "At least (≥)",
34
+ "<=": "At most (≤)",
35
+ "==": "Equals (==)",
36
+ "!=": "Not equal to (≠)",
37
+ };
38
+ const OPERATOR_OPTIONS = ANALYTICS_ALERT_OPERATORS.map((value) => ({
39
+ value,
40
+ label: OPERATOR_LABELS[value] ?? value,
41
+ }));
25
42
  const METRIC_OPTIONS = [
26
43
  { value: "daily_revenue", label: "Daily Revenue (₹)" },
27
44
  { value: "daily_orders", label: "Daily Orders" },
@@ -57,7 +74,7 @@ function AlertCard({ alert, onToggle, onDelete, }) {
57
74
  const metricLabel = METRIC_OPTIONS.find((m) => m.value === alert.metric)?.label ?? alert.metric;
58
75
  const opLabel = OPERATOR_OPTIONS.find((o) => o.value === alert.operator)?.label ?? alert.operator;
59
76
  const windowLabel = WINDOW_OPTIONS.find((w) => w.value === String(alert.windowHours))?.label ?? `${alert.windowHours}h`;
60
- return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(alert.id), className: "text-error hover:text-error", children: "Delete" })] })] }));
77
+ return (_jsxs(Row, { surface: "card", padding: "sm", align: "start", justify: "between", gap: "md", children: [_jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { className: "mb-1", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: alert.label }), _jsx(Badge, { variant: alert.isActive ? "success" : "default", children: alert.isActive ? "Active" : "Paused" })] }), _jsxs(Text, { className: "mb-1", color: "muted", size: "xs", children: [metricLabel, " ", opLabel, " ", alert.threshold, " \u00B7 ", windowLabel] }), _jsx(Row, { gap: "xs", wrap: true, children: alert.notifyChannels.map((ch) => (_jsx(Badge, { variant: OPERATOR_BADGE[alert.operator] ?? "default", children: ch }, ch))) }), alert.lastTriggeredAt && (_jsxs(Text, { className: "mt-1", color: "faint", size: "xs", children: ["Last triggered: ", new Date(alert.lastTriggeredAt).toLocaleString("en-IN")] }))] }), _jsxs(Row, { className: "shrink-0", align: "center", gap: "sm", children: [_jsx(Toggle, { checked: alert.isActive, onChange: (v) => onToggle(alert.id, v), size: "sm" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-analytics-alert"], onClick: () => onDelete(alert.id), className: "text-error hover:text-error" })] })] }));
61
78
  }
62
79
  const EMPTY_DRAFT = {
63
80
  label: "",
@@ -66,7 +83,17 @@ const EMPTY_DRAFT = {
66
83
  threshold: "",
67
84
  windowHours: "24",
68
85
  notifyChannels: ["in-app"],
86
+ isActive: true,
69
87
  };
88
+ /**
89
+ * The delivery-channel chips.
90
+ *
91
+ * Module level rather than an inline renderer — an inline one's handler sits
92
+ * six braces deep inside `useMemo`, which is DEEP_NESTING's whole subject.
93
+ */
94
+ function ChannelChips({ selected, onToggle, }) {
95
+ return (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { color: "muted", size: "xs", weight: "medium", children: "Notify via" }), _jsx(Row, { gap: "sm", wrap: true, children: CHANNEL_OPTIONS.map((ch) => (_jsx(Button, { variant: selected.includes(ch.value) ? "primary" : "outline", type: "button", onClick: () => onToggle(ch.value), rounded: "full", paddingX: "sm", paddingY: "xs", textSize: "xs", children: ch.label }, ch.value))) })] }));
96
+ }
70
97
  export function SellerAnalyticsAlertsView({ labels = {}, }) {
71
98
  const queryClient = useQueryClient();
72
99
  const { showToast } = useToast();
@@ -82,31 +109,7 @@ export function SellerAnalyticsAlertsView({ labels = {}, }) {
82
109
  const alerts = data ?? [];
83
110
  const createMutation = useApiMutation({
84
111
  errorMessage: "Failed to create alert",
85
- mutationFn: async () => {
86
- /*
87
- * Parsed against the SAME schema the route uses, before the request.
88
- *
89
- * This form used to hand-build its payload and included `scope: "seller"`
90
- * — a field the route pins from the session and the schema does not
91
- * declare. Once that schema became `.strict()`, every "Create Alert"
92
- * became a 400, and nothing local could have caught it: the client had
93
- * no schema to check against. Parsing here is the structural fix, not
94
- * the deletion of one key.
95
- */
96
- const parsed = analyticsAlertCreateSchema.safeParse({
97
- label: draft.label,
98
- metric: draft.metric,
99
- operator: draft.operator,
100
- threshold: draft.threshold,
101
- windowHours: draft.windowHours,
102
- notifyChannels: draft.notifyChannels,
103
- isActive: true,
104
- });
105
- if (!parsed.success) {
106
- throw new ValidationError(parsed.error.issues[0]?.message ?? "Invalid alert", parsed.error.issues);
107
- }
108
- return apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, parsed.data);
109
- },
112
+ mutationFn: async (values) => apiClient.post(SELLER_ENDPOINTS.ANALYTICS_ALERTS, values),
110
113
  onSuccess: () => {
111
114
  showToast("Alert created", "success");
112
115
  void queryClient.invalidateQueries({ queryKey: ["seller", "analytics-alerts"] });
@@ -137,10 +140,47 @@ export function SellerAnalyticsAlertsView({ labels = {}, }) {
137
140
  : [...d.notifyChannels, ch],
138
141
  }));
139
142
  }, []);
140
- return (_jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { justify: "between", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { size: "sm", color: "muted", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: `${__P.p5} space-y-[1rem]`, rounded: "xl", border: "default", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "primary", children: "Create Alert" }), _jsxs(Form, { schema: analyticsAlertCreateSchema, onSubmit: (e) => {
141
- e.preventDefault();
142
- createMutation.mutate();
143
- }, children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Alert label", value: draft.label, onChange: (e) => setDraft((d) => ({ ...d, label: e.target.value })), placeholder: "e.g. Low daily orders", required: true }), _jsx(Select, { label: "Metric", value: draft.metric, onValueChange: (v) => setDraft((d) => ({ ...d, metric: v })), options: METRIC_OPTIONS }), _jsx(Select, { label: "Operator", value: draft.operator, onValueChange: (v) => setDraft((d) => ({ ...d, operator: v })), options: OPERATOR_OPTIONS }), _jsx(Input, { label: "Threshold", type: "number", value: draft.threshold, onChange: (e) => setDraft((d) => ({ ...d, threshold: e.target.value })), placeholder: "e.g. 10", required: true }), _jsx(Select, { label: "Check window", value: draft.windowHours, onValueChange: (v) => setDraft((d) => ({ ...d, windowHours: v })), options: WINDOW_OPTIONS })] }), _jsxs(Div, { className: "mt-3", children: [_jsx(Text, { className: "mb-2", color: "muted", size: "xs", weight: "medium", children: "Notify via" }), _jsx(Row, { gap: "sm", wrap: true, children: CHANNEL_OPTIONS.map((ch) => (_jsx(Button, { variant: "outline", type: "button", onClick: () => toggleChannel(ch.value), rounded: "full", paddingX: "sm", paddingY: "xs", textSize: "xs", className: draft.notifyChannels.includes(ch.value)
144
- ? "bg-[var(--appkit-color-primary)] text-white border-transparent"
145
- : "bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text-muted)] border-[var(--appkit-color-border)]", children: ch.label }, ch.value))) })] }), _jsx(FormErrorSummary, {}), _jsx(FormActions, { align: "right", className: "mt-4", children: _jsx(Button, { type: "submit", isLoading: createMutation.isPending, children: createMutation.isPending ? "Creating…" : "Create Alert" }) })] })] })), isLoading && (_jsx(Stack, { gap: "sm", children: [1, 2, 3].map((i) => (_jsx(Div, { className: "h-16 animate-pulse", surface: "subtle", rounded: "lg" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx(Stack, { gap: "3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
143
+ const sections = React.useMemo(() => buildSectionsFromSchema(analyticsAlertCreateSchema, {
144
+ options: {
145
+ metric: METRIC_OPTIONS,
146
+ operator: OPERATOR_OPTIONS,
147
+ windowHours: WINDOW_OPTIONS,
148
+ },
149
+ renderers: {
150
+ notifyChannels: ({ values }) => (_jsx(ChannelChips, { selected: values.notifyChannels, onToggle: toggleChannel })),
151
+ },
152
+ }), [toggleChannel]);
153
+ const nav = useSectionFormNav(sections, draft, { scope: "store:analytics-alert" });
154
+ const { shellCtx, setFieldError, clearErrors } = useFormShellState(analyticsAlertCreateSchema, {
155
+ sections: nav.sectionMeta,
156
+ onGoToSection: nav.goToSection,
157
+ fieldToSectionIndex: nav.fieldToSectionIndex,
158
+ });
159
+ const submitAlert = () => {
160
+ clearErrors();
161
+ /*
162
+ * Parsed against the SAME schema the route uses, before the request.
163
+ *
164
+ * This form used to hand-build its payload and included `scope: "seller"`
165
+ * — a field the route pins from the session and the schema does not
166
+ * declare. Once that schema became `.strict()`, every "Create Alert"
167
+ * became a 400, and nothing local could have caught it: the client had no
168
+ * schema to check against. The parse is the structural fix; what changed
169
+ * here is only that its issues now reach the fields instead of being
170
+ * flattened into one thrown message.
171
+ */
172
+ const parsed = analyticsAlertCreateSchema.safeParse(visibleValues(analyticsAlertCreateSchema, draft));
173
+ if (!parsed.success) {
174
+ applyZodIssues(parsed.error.issues, setFieldError);
175
+ return;
176
+ }
177
+ createMutation.mutate(parsed.data);
178
+ };
179
+ return (_jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { justify: "between", children: [_jsx(Heading, { level: 2, size: "base", weight: "semibold", color: "primary", children: labels.title ?? "Analytics Alerts" }), _jsx(Button, { size: "sm", onClick: () => setShowForm((v) => !v), children: showForm ? "Cancel" : "+ New Alert" })] }), _jsx(Text, { size: "sm", color: "muted", children: "Get notified when a key metric crosses a threshold. Alerts check the metric over the selected window and fire through your chosen channels." }), showForm && (_jsxs(Section, { className: `${__P.p5} space-y-[1rem]`, rounded: "xl", border: "default", children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "primary", children: "Create Alert" }), _jsxs(FormShellContext.Provider, { value: shellCtx, children: [_jsx(FormErrorSummary, {}), _jsx(SectionForm, { sections: sections, values: draft, onChange: (partial) => setDraft((prev) => ({ ...prev, ...partial })), onSubmit: submitAlert, schema: analyticsAlertCreateSchema, openIds: nav.openIds, onOpenChange: nav.setOpenIds, isLoading: createMutation.isPending, submitLabel: "Create alert", onCancel: () => setShowForm(false),
180
+ /*
181
+ * This panel is one card inside a longer analytics page, not the
182
+ * page's own form — a viewport-pinned Save would claim the
183
+ * bottom tier for a control the reader may have scrolled past.
184
+ */
185
+ bottomBar: false })] })] })), isLoading && (_jsx(Stack, { gap: "sm", children: [1, 2, 3].map((i) => (_jsx(Div, { className: "h-16 animate-pulse", surface: "subtle", rounded: "lg" }, i))) })), !isLoading && alerts.length === 0 && !showForm && (_jsx(Alert, { variant: "info", children: "No alerts configured. Create an alert to get notified when a key metric crosses a threshold." })), !isLoading && alerts.length > 0 && (_jsx(Stack, { gap: "3", children: alerts.map((alert) => (_jsx(AlertCard, { alert: alert, onToggle: (id, isActive) => toggleMutation.mutate({ id, isActive }), onDelete: (id) => deleteMutation.mutate(id) }, alert.id))) }))] }));
146
186
  }
@@ -1,8 +1,15 @@
1
+ /**
2
+ * SellerArtView — seller browse/manage of art print listings.
3
+ *
4
+ * A named wrapper, kept so the page, the barrel and every existing import path
5
+ * stay unchanged. All behaviour is `SellerListingTypeView`; everything this
6
+ * type does differently is one entry in `SELLER_LISTING_TYPE_SPECS`.
7
+ *
8
+ * This file was 194 lines of a component that four siblings also contained
9
+ * verbatim — see that file's header for why the duplication mattered rather
10
+ * than merely being untidy.
11
+ */
1
12
  import React from "react";
2
- export interface SellerArtViewProps {
3
- onCreateClick?: () => void;
4
- onEditClick?: (id: string) => void;
5
- onDelete?: (id: string) => Promise<void>;
6
- onBulkDelete?: (ids: string[]) => Promise<void>;
7
- }
8
- export declare function SellerArtView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerArtViewProps): React.JSX.Element;
13
+ import { type SellerListingTypeViewProps } from "./SellerListingTypeView";
14
+ export type SellerArtViewProps = SellerListingTypeViewProps;
15
+ export declare function SellerArtView(props: SellerArtViewProps): React.JSX.Element;
@@ -1,121 +1,6 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * SellerArtView — seller browse/manage of art print listings
5
- * (EMI/art-stickers session). Mirrors SellerClassifiedView's pattern.
6
- */
7
- import { Badge, sortBy } from "@mohasinac/appkit/client";
8
- import { useState, useCallback } from "react";
9
- import { useEntityDelete } from "../../../react/hooks/useEntityDelete";
10
- import { ConfirmDeleteModal, RowActionMenu, Text } from "../../../ui";
11
- import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
12
- import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
13
- import { ROUTES } from "../../../constants/index";
14
- import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
15
- import { DataListingView } from "../../admin/components/DataListingView";
16
- import { SELLER_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
17
- import { useAvailabilityScope } from "../../products/hooks/useAvailabilityScope";
18
- const LISTING_TYPES = ["art"];
19
- const COLUMNS = [
20
- { key: "title", header: "Listing", render: (row) => _jsx(Text, { size: "sm", weight: "medium", children: row.title }) },
21
- { key: "price", header: "Price", render: (row) => _jsx(Text, { className: "tabular-nums", size: "sm", children: row.price }) },
22
- {
23
- key: "size",
24
- header: "Size",
25
- render: (row) => _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: row.size || "—" }),
26
- },
27
- {
28
- key: "status",
29
- header: "Status",
30
- render: (row) => (_jsx(Badge, { variant: row.status === "active" ? "active" : "inactive", size: "xs", className: "capitalize", children: row.status })),
31
- },
32
- {
33
- key: "createdAt",
34
- header: "Created",
35
- render: (row) => _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: row.createdAt }),
36
- },
37
- ];
38
- export function SellerArtView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }) {
39
- const [deleteTargetId, setDeleteTargetId] = useState(null);
40
- const { deletingId, handleDelete: performDelete } = useEntityDelete({
41
- endpoint: SELLER_ENDPOINTS.PRODUCT_BY_ID,
42
- deleteFn: onDelete,
43
- successMessage: "Listing deleted.",
44
- fetchOptions: { credentials: "include" },
45
- });
46
- const handleDelete = useCallback(async (id) => {
47
- await performDelete(id);
48
- setDeleteTargetId(null);
49
- }, [performDelete]);
50
- const handleEdit = useCallback((id) => {
51
- if (onEditClick)
52
- onEditClick(id);
53
- else
54
- window.location.href = String(ROUTES.STORE.ART_EDIT(id));
55
- }, [onEditClick]);
56
- const handleCreate = useCallback(() => {
57
- if (onCreateClick)
58
- onCreateClick();
59
- else
60
- window.location.href = String(ROUTES.STORE.ART_NEW);
61
- }, [onCreateClick]);
62
- const scope = useAvailabilityScope(LISTING_TYPES);
63
- const config = {
64
- portal: "seller",
65
- title: "Art",
66
- searchPlaceholder: "Search art prints...",
67
- emptyLabel: "No art listings yet — list your first print",
68
- filterKeys: [],
69
- defaultSort: sortBy("createdAt", "DESC"),
70
- queryKey: ["seller", "art"],
71
- endpoint: SELLER_ENDPOINTS.PRODUCTS,
72
- sortOptions: [
73
- { value: sortBy("createdAt", "DESC"), label: "Newest" },
74
- { value: sortBy("createdAt", "ASC"), label: "Oldest" },
75
- { value: "title", label: "Name A–Z" },
76
- { value: sortBy("price", "ASC"), label: "Price: Low–High" },
77
- { value: sortBy("price", "DESC"), label: "Price: High–Low" },
78
- ],
79
- columns: COLUMNS,
80
- mapRows: (response) => toRecordArray(response.products).map((item, index) => {
81
- const printMeta = (item.printMeta ?? {});
82
- return {
83
- id: toStringValue(item.id, `art-${index}`),
84
- title: toStringValue(item.productTitle ?? item.title, "Untitled"),
85
- price: toCurrency(item.price),
86
- size: toStringValue(printMeta.size, ""),
87
- status: toStringValue(item.status, "draft"),
88
- createdAt: toRelativeDate(item.createdAt),
89
- };
90
- }),
91
- getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
92
- buildFilters: () => "listingType==art",
93
- buildExtraParams: () => scope.extraParams,
94
- renderAboveContent: scope.renderAboveContent,
95
- primaryAction: { label: "New Art Print", onClick: () => handleCreate() },
96
- // Mirrors the "Edit" row action below so table rows and cards both navigate on click.
97
- onRowClick: (row) => handleEdit(row.id),
98
- // Rule #7: bulk-action array sourced from the SELLER_BULK_ACTIONS preset.
99
- buildBulkActions: onBulkDelete
100
- ? (selection) => SELLER_BULK_ACTIONS.art.map((id) => ({
101
- id,
102
- label: ROW_ACTION_META[id].label,
103
- destructive: ROW_ACTION_META[id].destructive,
104
- onClick: async () => {
105
- await onBulkDelete(selection.selectedIds);
106
- selection.clearSelection();
107
- },
108
- }))
109
- : undefined,
110
- renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
111
- { label: ACTIONS.STORE["edit-listing"].label, onClick: () => handleEdit(row.id) },
112
- {
113
- label: ACTIONS.STORE["delete-listing"].label,
114
- destructive: true,
115
- onClick: () => setDeleteTargetId(row.id),
116
- disabled: deletingId === row.id,
117
- },
118
- ] })),
119
- };
120
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Art Listing", message: "Are you sure you want to delete this art listing? This cannot be undone.", onConfirm: () => handleDelete(deleteTargetId), onClose: () => setDeleteTargetId(null), isDeleting: deletingId === deleteTargetId }))] }));
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { SellerListingTypeView, } from "./SellerListingTypeView";
4
+ export function SellerArtView(props) {
5
+ return _jsx(SellerListingTypeView, { type: "art", ...props });
121
6
  }
@@ -1,8 +1,15 @@
1
+ /**
2
+ * SellerClassifiedView — seller browse/manage of classified listings.
3
+ *
4
+ * A named wrapper, kept so the page, the barrel and every existing import path
5
+ * stay unchanged. All behaviour is `SellerListingTypeView`; everything this
6
+ * type does differently is one entry in `SELLER_LISTING_TYPE_SPECS`.
7
+ *
8
+ * This file was 208 lines of a component that four siblings also contained
9
+ * verbatim — see that file's header for why the duplication mattered rather
10
+ * than merely being untidy.
11
+ */
1
12
  import React from "react";
2
- export interface SellerClassifiedViewProps {
3
- onCreateClick?: () => void;
4
- onEditClick?: (id: string) => void;
5
- onDelete?: (id: string) => Promise<void>;
6
- onBulkDelete?: (ids: string[]) => Promise<void>;
7
- }
8
- export declare function SellerClassifiedView({ onCreateClick, onEditClick, onDelete, onBulkDelete, }: SellerClassifiedViewProps): React.JSX.Element;
13
+ import { type SellerListingTypeViewProps } from "./SellerListingTypeView";
14
+ export type SellerClassifiedViewProps = SellerListingTypeViewProps;
15
+ export declare function SellerClassifiedView(props: SellerClassifiedViewProps): React.JSX.Element;