@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
@@ -172,9 +172,17 @@ export const ACTIONS = {
172
172
  iconKey: "message",
173
173
  id: "product.make-offer",
174
174
  label: "Make an offer",
175
- description: "Propose a price to the seller for a classified listing.",
175
+ /*
176
+ * Scope corrected 2026-08-31: this was `["classified"]`, but the button it
177
+ * labels (`MakeOfferButton`) mounts on every type whose capability says
178
+ * `canMakeOffer` — which is standard, art and stickers as well. On
179
+ * classified specifically it is the ONLY purchase path, and a seller who
180
+ * has not opted into haggling receives a request to buy at the asking
181
+ * price rather than a lower offer.
182
+ */
183
+ description: "Propose a price to the seller, or request to buy at the asking price.",
176
184
  kind: "secondary",
177
- listingTypeScope: ["classified"],
185
+ listingTypeScope: ["standard", "classified", "art", "stickers"],
178
186
  },
179
187
  },
180
188
  AUCTION: {
@@ -271,16 +279,15 @@ export const ACTIONS = {
271
279
  listingTypeScope: ["prize-draw"],
272
280
  },
273
281
  },
274
- CLASSIFIED: {
275
- "contact-seller": {
276
- iconKey: "message",
277
- id: "classified.contact-seller",
278
- label: "Contact seller",
279
- description: "Open a conversations thread with the seller — classified flow (SB-UNI-M).",
280
- kind: "primary",
281
- listingTypeScope: ["classified"],
282
- },
283
- },
282
+ /*
283
+ * `CLASSIFIED` held one entry, "contact-seller", whose description read
284
+ * "Open a conversations thread with the seller". It had ZERO consumers — the
285
+ * button on the classified PDP was hand-written — and the feature it named was
286
+ * deleted 2026-08-31. `PRODUCT["make-offer"]` below is the action that is
287
+ * actually wired (`MakeOfferButton`), and on a classified it is now the only
288
+ * purchase path, so a second bucket duplicating it would be exactly the drift
289
+ * Rule #7 exists to prevent.
290
+ */
284
291
  DIGITAL_CODE: {
285
292
  "claim-code": {
286
293
  iconKey: "confirm",
@@ -291,16 +298,12 @@ export const ACTIONS = {
291
298
  listingTypeScope: ["digital-code"],
292
299
  },
293
300
  },
294
- LIVE: {
295
- "inquire": {
296
- iconKey: "message",
297
- id: "live.inquire",
298
- label: "Inquire",
299
- description: "Open a conversation with the seller about a live-item listing.",
300
- kind: "primary",
301
- listingTypeScope: ["live"],
302
- },
303
- },
301
+ /*
302
+ * `LIVE.inquire` ("Open a conversation with the seller about a live-item
303
+ * listing") went the same way and for the same two reasons: no consumer, and
304
+ * the conversations feature is gone. It was never needed either — a live item
305
+ * has `canAddToCart: true`, so unlike classified it has a real checkout.
306
+ */
304
307
  BUNDLE: {
305
308
  "buy-now": {
306
309
  iconKey: "cart",
@@ -879,6 +882,26 @@ export const ACTIONS = {
879
882
  confirmKind: "danger",
880
883
  },
881
884
  },
885
+ /*
886
+ * The alerts list rendered a bare `<Button variant="ghost">Delete</Button>`
887
+ * with a raw onClick — no ActionDef, so no confirmation, so one misplaced
888
+ * click silently removed an alert with nothing to undo it (Rule #7).
889
+ */
890
+ "delete-analytics-alert": {
891
+ iconKey: "delete",
892
+ id: "store.delete-analytics-alert",
893
+ label: "Delete",
894
+ ariaLabel: "Delete this analytics alert",
895
+ description: "Permanently removes this alert rule. Past notifications are unaffected.",
896
+ kind: "danger",
897
+ permissions: ["seller", "admin"],
898
+ confirmation: {
899
+ title: "Delete this alert?",
900
+ body: "You will stop being notified when this rule matches. Alerts already sent are unaffected. This cannot be undone.",
901
+ confirmLabel: "Delete alert",
902
+ confirmKind: "danger",
903
+ },
904
+ },
882
905
  "cancel-form": {
883
906
  iconKey: "cancel",
884
907
  id: "store.cancel-form",
@@ -1197,6 +1220,30 @@ export const ACTIONS = {
1197
1220
  confirmKind: "danger",
1198
1221
  },
1199
1222
  },
1223
+ /*
1224
+ * A LOT, not the whole shipment.
1225
+ *
1226
+ * The lot row's delete button used `shipment.delete`, so the dialog asked
1227
+ * "Delete this shipment?" and warned about removing every lot — while the
1228
+ * handler deleted one lot. A confirmation that names the wrong object is
1229
+ * worse than none: it is either ignored, or it stops someone doing
1230
+ * something harmless.
1231
+ */
1232
+ "delete-lot": {
1233
+ iconKey: "delete",
1234
+ id: "shipment.delete-lot",
1235
+ label: "Delete",
1236
+ ariaLabel: "Delete this lot and its items",
1237
+ description: "Permanently deletes this lot and every item in it.",
1238
+ kind: "danger",
1239
+ permissions: ["admin"],
1240
+ confirmation: {
1241
+ title: "Delete this lot?",
1242
+ body: "This permanently removes the lot and every item in it. The rest of the shipment is untouched. Items still linked to a product must be unlinked first.",
1243
+ confirmLabel: "Delete lot",
1244
+ confirmKind: "danger",
1245
+ },
1246
+ },
1200
1247
  },
1201
1248
  CATALOGUE: {
1202
1249
  "list-item": {
@@ -1,9 +1,14 @@
1
1
  /**
2
- * Classified listing rule — chat-only, no checkout.
2
+ * Classified listing rule — no cart, no checkout; the offer IS the purchase path.
3
3
  *
4
- * Classified listings (OLX / Facebook Marketplace pattern) surface a
5
- * "Contact Seller" CTA that opens a conversations thread. No cart, no
6
- * payment, no order. SB-UNI-M will wire the full flow.
4
+ * Classified listings (OLX / Facebook Marketplace pattern) never reach cart or
5
+ * order creation. The buyer transacts through Make-an-Offer: the seller accepts,
6
+ * which produces a locked cart line in the offer lane, and checkout proceeds
7
+ * from there like any other accepted offer.
8
+ *
9
+ * A seller who has not set `allowOffers` still receives requests to buy at their
10
+ * asking price — see `resolveOfferBounds` in the offers config. Without that,
11
+ * unchecking one flag would make the listing untransactable.
7
12
  */
8
13
  import type { ListingCheckoutRule } from "./types";
9
14
  export declare const classifiedRule: ListingCheckoutRule;
@@ -1,9 +1,14 @@
1
1
  import { DEFAULT_LISTING_RULE } from "./_defaults";
2
2
  export const classifiedRule = {
3
3
  ...DEFAULT_LISTING_RULE,
4
- orderType: "standard", // placeholder — classified never reaches order creation
4
+ orderType: "standard", // placeholder — a classified reaches order creation only via an accepted offer
5
5
  purchaseable: false,
6
6
  cartEligible: false,
7
- cartIneligibleHint: 'Use "Contact Seller" to arrange a meetup.',
7
+ /*
8
+ * Names the control the page actually has. It read 'Use "Contact Seller" to
9
+ * arrange a meetup' until 2026-08-31, and that button opened a conversations
10
+ * thread which no longer exists.
11
+ */
12
+ cartIneligibleHint: "Make an offer to buy this — classified listings are arranged with the seller.",
8
13
  requiresShippingAddress: false,
9
14
  };
@@ -0,0 +1,14 @@
1
+ /** The fields the matcher reads. Everything is optional but `label`. */
2
+ export interface NavSearchable {
3
+ label: string;
4
+ description?: string;
5
+ keywords?: string[];
6
+ }
7
+ /**
8
+ * How well `item` answers `query`. `0` when it does not.
9
+ *
10
+ * The query is matched whole rather than tokenised: "prize draws" should find
11
+ * Prize Draws, and splitting on spaces would also match anything containing
12
+ * "draws" on its own, which for a two-word query is nearly everything.
13
+ */
14
+ export declare function matchesNavQuery(item: NavSearchable, query: string): number;
@@ -0,0 +1,72 @@
1
+ /*
2
+ * WHY: The sidebar search was `item.label.toLowerCase().includes(q)` — so it
3
+ * could only ever find a screen by the name it already shows you. Typing
4
+ * "refund" found nothing, because the screen is called "Payouts"; typing
5
+ * "postcode" found nothing, because the field is called PIN code; typing
6
+ * "maintenance" found nothing, because the toggle lives inside "Site
7
+ * Settings". A search that only matches what is already on screen is a
8
+ * filter, not a search.
9
+ * WHAT: One matcher over label + description + keywords, shared by the sidebar
10
+ * search and (in W7) the action index.
11
+ *
12
+ * ## Ranking, not just filtering
13
+ *
14
+ * A label hit and a keyword hit are not equally good answers. "orders" should
15
+ * put **Orders** first and "Orders & Finance" (a guide that mentions orders)
16
+ * second — so the matcher returns a SCORE and the caller sorts by it. Without
17
+ * that, alphabetical order decides, and the exact match can land ninth.
18
+ *
19
+ * EXPORTS: matchesNavQuery, type NavSearchable
20
+ *
21
+ * @tag domain:layout
22
+ * @tag layer:shared
23
+ * @tag pattern:none
24
+ * @tag access:isomorphic
25
+ * @tag consumers:useSidebarSearch,action index
26
+ * @tag sideEffects:none
27
+ */
28
+ /** Higher is a better answer. `0` means no match at all. */
29
+ const SCORE = {
30
+ labelExact: 100,
31
+ labelPrefix: 80,
32
+ labelSubstring: 60,
33
+ keywordExact: 50,
34
+ keywordSubstring: 30,
35
+ description: 10,
36
+ };
37
+ /**
38
+ * How well `item` answers `query`. `0` when it does not.
39
+ *
40
+ * The query is matched whole rather than tokenised: "prize draws" should find
41
+ * Prize Draws, and splitting on spaces would also match anything containing
42
+ * "draws" on its own, which for a two-word query is nearly everything.
43
+ */
44
+ export function matchesNavQuery(item, query) {
45
+ const q = query.trim().toLowerCase();
46
+ if (!q)
47
+ return 0;
48
+ const label = item.label.toLowerCase();
49
+ if (label === q)
50
+ return SCORE.labelExact;
51
+ if (label.startsWith(q))
52
+ return SCORE.labelPrefix;
53
+ if (label.includes(q))
54
+ return SCORE.labelSubstring;
55
+ for (const keyword of item.keywords ?? []) {
56
+ const k = keyword.toLowerCase();
57
+ if (k === q)
58
+ return SCORE.keywordExact;
59
+ if (k.includes(q) || q.includes(k))
60
+ return SCORE.keywordSubstring;
61
+ }
62
+ /*
63
+ * The description is matched LAST and scores lowest on purpose: it is a
64
+ * sentence, so it contains common words, and letting it outrank a keyword
65
+ * would mean "shipping" surfaced every screen whose description happens to
66
+ * mention shipping ahead of the one actually called Shipping.
67
+ */
68
+ if (item.description && item.description.toLowerCase().includes(q)) {
69
+ return SCORE.description;
70
+ }
71
+ return 0;
72
+ }
@@ -25,12 +25,48 @@ export interface SidebarNavItem {
25
25
  badge?: number;
26
26
  /**
27
27
  * Stable nav-* slug used as the key in siteSettings.navConfig.
28
- * If absent, item is always visible (no admin toggle, no permission check).
28
+ *
29
+ * 🛑 **Derived from the href, never hand-written** — `navItemId(portal, href)`.
30
+ * A hand-written id drifts from the route it names, and because it is also
31
+ * the `navConfig` key, a drifted id silently un-toggles the item: the admin
32
+ * hides "Payouts", the toggle writes a key nothing reads, and the entry
33
+ * stays.
34
+ *
35
+ * If absent, the item is always visible — no admin toggle and **no
36
+ * permission check**, which is why every admin item having no id meant
37
+ * `requiredPermission` had never once been enforced.
29
38
  */
30
39
  id?: string;
31
40
  /** RBAC permission key — item only shows to users with this permission. */
32
41
  requiredPermission?: string;
42
+ /**
43
+ * One sentence on what this screen is FOR, in the user's words rather than
44
+ * the entity's. "Refund a buyer, or check why a payout is late" finds its
45
+ * screen; "Payouts" only finds itself.
46
+ *
47
+ * Consumed by the sidebar search and, in W7, by the action index.
48
+ */
49
+ description?: string;
50
+ /**
51
+ * Words someone might search for that are not in the label — synonyms
52
+ * ("postcode" for PIN), the old name of a renamed screen, the noun the rest
53
+ * of the industry uses.
54
+ */
55
+ keywords?: string[];
56
+ /** Which dashboard this belongs to. Set by the helper alongside `id`. */
57
+ portal?: NavPortal;
33
58
  }
59
+ /** The three dashboards a sidebar item can belong to. */
60
+ export type NavPortal = "admin" | "store" | "user";
61
+ /**
62
+ * The `navConfig` key for a nav item: `nav-{portal}-{slug-of-href}`.
63
+ *
64
+ * Derived rather than authored so an id cannot disagree with the route it
65
+ * points at. Locale prefixes and dynamic segments are stripped, so
66
+ * `/en/admin/orders` and `/admin/orders` produce the same key — otherwise a
67
+ * localised build would write a second set of toggles nobody can find.
68
+ */
69
+ export declare function navItemId(portal: NavPortal, href: string): string;
34
70
  /** Sidebar group with title + items + default-open state. */
35
71
  export interface SidebarNavGroup {
36
72
  title: string;
@@ -10,4 +10,22 @@
10
10
  * 2. render-prop slots on each section (defined on the section component itself).
11
11
  * 3. Section replacement via AppShell named slots.
12
12
  */
13
- export {};
13
+ /**
14
+ * The `navConfig` key for a nav item: `nav-{portal}-{slug-of-href}`.
15
+ *
16
+ * Derived rather than authored so an id cannot disagree with the route it
17
+ * points at. Locale prefixes and dynamic segments are stripped, so
18
+ * `/en/admin/orders` and `/admin/orders` produce the same key — otherwise a
19
+ * localised build would write a second set of toggles nobody can find.
20
+ */
21
+ export function navItemId(portal, href) {
22
+ const path = href
23
+ .replace(/^https?:\/\/[^/]+/, "")
24
+ .replace(/^\/[a-z]{2}(-[A-Z]{2})?(?=\/)/, "")
25
+ .replace(/[?#].*$/, "")
26
+ .replace(/^\/+|\/+$/g, "")
27
+ .replace(/[^a-zA-Z0-9/-]+/g, "-")
28
+ .replace(/\//g, "-")
29
+ .toLowerCase();
30
+ return `nav-${portal}-${path || "root"}`;
31
+ }
@@ -25,7 +25,7 @@
25
25
  * When those pages are built, change the entry here and all 40 call sites
26
26
  * follow. That is the whole point of the indirection.
27
27
  */
28
- import type { NotificationRelatedType } from "../../../../features/admin/schemas/firestore";
28
+ import type { NotificationRelatedType, NotificationType } from "../../../../features/admin/schemas/firestore";
29
29
  /**
30
30
  * Which portal the recipient reads this in.
31
31
  *
@@ -45,3 +45,11 @@ export type NotificationAudience = "buyer" | "seller" | "admin";
45
45
  * looking at.
46
46
  */
47
47
  export declare function resolveNotificationActionUrl(relatedType: NotificationRelatedType | undefined, relatedId: string | undefined, audience: NotificationAudience): string | undefined;
48
+ /**
49
+ * Which portal each notification type belongs to.
50
+ *
51
+ * `Record<NotificationType, …>`, so a new type cannot compile without an
52
+ * answer — the same discipline `typeToPrefsKey` needed after it silently
53
+ * omitted three types and ignored those users' opt-outs.
54
+ */
55
+ export declare const TYPE_AUDIENCE: Record<NotificationType, NotificationAudience>;
@@ -109,3 +109,50 @@ export function resolveNotificationActionUrl(relatedType, relatedId, audience) {
109
109
  }
110
110
  }
111
111
  }
112
+ /*
113
+ * Moved here from `features/admin/actions/notification-actions.ts` 2026-08-31.
114
+ * It belongs beside the resolver it feeds, and being module-private in a
115
+ * `"use server"` file meant the SEED could not use it — so seeded notifications
116
+ * carried no `actionUrl` and rendered unclickable, the exact defect the central
117
+ * resolver was built to end.
118
+ */
119
+ /**
120
+ * Which portal each notification type belongs to.
121
+ *
122
+ * `Record<NotificationType, …>`, so a new type cannot compile without an
123
+ * answer — the same discipline `typeToPrefsKey` needed after it silently
124
+ * omitted three types and ignored those users' opt-outs.
125
+ */
126
+ export const TYPE_AUDIENCE = {
127
+ // Buyer-facing: the order lifecycle they are waiting on.
128
+ order_placed: "buyer",
129
+ order_confirmed: "buyer",
130
+ order_shipped: "buyer",
131
+ order_delivered: "buyer",
132
+ order_cancelled: "buyer",
133
+ refund_initiated: "buyer",
134
+ payment_review: "buyer",
135
+ emi_installment_due_soon: "buyer",
136
+ emi_installment_overdue: "buyer",
137
+ prize_won: "buyer",
138
+ prize_reveal_expired: "buyer",
139
+ product_available: "buyer",
140
+ bid_placed: "buyer",
141
+ bid_outbid: "buyer",
142
+ bid_won: "buyer",
143
+ bid_lost: "buyer",
144
+ offer_responded: "buyer",
145
+ offer_counter_accepted: "buyer",
146
+ offer_expired: "buyer",
147
+ review_replied: "buyer",
148
+ catalogue_images_stale: "buyer",
149
+ // Seller-facing.
150
+ auction_ended: "seller", // "your auction closed with no winner"
151
+ offer_received: "seller",
152
+ review_approved: "seller",
153
+ // Account-level, which lives in the user portal for everyone.
154
+ welcome: "buyer",
155
+ account_action: "buyer",
156
+ system: "buyer",
157
+ promotion: "buyer",
158
+ };
@@ -1,3 +1,4 @@
1
+ import type { ListingType } from "../../../../features/products/types/index";
1
2
  /**
2
3
  * The floor, as a percentage of the listed price, below which an offer is
3
4
  * rejected — used whenever a listing does not set its own `minOfferPercent`.
@@ -28,3 +29,67 @@ export declare function minOfferAmount(listedPrice: number, percent?: number | n
28
29
  * below the floor the seller has said they don't want to hear it.
29
30
  */
30
31
  export declare function isOfferAmountValid(amount: number, listedPrice: number, percent?: number | null): boolean;
32
+ /**
33
+ * Is Make-an-Offer the only way to buy this listing type?
34
+ *
35
+ * True exactly when the type can be offered on but has no cart line — today
36
+ * that is `classified` alone (`canAddToCart: false`, `canMakeOffer: true`).
37
+ *
38
+ * Derived from the capability registry rather than testing for `"classified"`,
39
+ * so a future type that lands in the same shape inherits the rule instead of
40
+ * silently becoming unbuyable. That is not hypothetical: this function exists
41
+ * because classified's actual purchase path was a `conversations` chat thread,
42
+ * and when that feature was deleted the listing type would have been left with
43
+ * no way to transact at all.
44
+ */
45
+ export declare function offerIsOnlyPurchasePath(listingType: ListingType): boolean;
46
+ /**
47
+ * May a buyer send an offer on this listing at all?
48
+ *
49
+ * The two ordinary gates are the type's `canMakeOffer` capability and the
50
+ * seller's own `allowOffers` opt-in. The third clause is the point:
51
+ *
52
+ * 🛑 **`allowOffers` is an opt-in to NEGOTIATION, not to being contactable.**
53
+ * On a listing with no cart, unchecking it would mean the buyer has no way to
54
+ * transact whatsoever — the page would render a price and no path to it. So
55
+ * where the offer IS the purchase path, an offer is always permitted; what
56
+ * `allowOffers` then controls is whether the buyer may propose a price *below*
57
+ * the asking one. See `resolveOfferBounds`.
58
+ */
59
+ export declare function offersEnabledFor(listingType: ListingType, allowOffers?: boolean): boolean;
60
+ export interface OfferBounds {
61
+ /** Lowest legal amount, inclusive. */
62
+ min: number;
63
+ /** Highest legal amount, inclusive. */
64
+ max: number;
65
+ /**
66
+ * The offer is pinned to the asking price — the buyer is requesting to buy,
67
+ * not negotiating. True when the offer is the only purchase path and the
68
+ * seller has not opted into haggling.
69
+ *
70
+ * Callers use this for COPY, never for a second bounds calculation: `min`
71
+ * and `max` are already equal when it is true.
72
+ */
73
+ isBuyRequest: boolean;
74
+ }
75
+ /**
76
+ * The amounts this listing will accept, as one answer for the form, the schema
77
+ * and the server.
78
+ *
79
+ * Two cases beyond the ordinary one, both driven by there being no cart:
80
+ *
81
+ * - **No haggling opted in** → `min === max === listedPrice`. A "request to buy".
82
+ * - **Haggling opted in** → the usual floor, but the ceiling is the asking price
83
+ * **inclusive**. On a cartable listing an offer at full price is rejected with
84
+ * "use Add to Cart instead", which on a classified is advice the buyer cannot
85
+ * take — there is no cart button to use. That rejection was already a dead end
86
+ * before this change.
87
+ */
88
+ export declare function resolveOfferBounds(opts: {
89
+ listingType: ListingType;
90
+ listedPrice: number;
91
+ minOfferPercent?: number | null;
92
+ allowOffers?: boolean;
93
+ }): OfferBounds;
94
+ /** Whether `amount` falls within `bounds`. The one comparison, shared. */
95
+ export declare function isWithinOfferBounds(amount: number, bounds: OfferBounds): boolean;
@@ -11,15 +11,18 @@
11
11
  * Same discipline as `resolveMinBid` in the auctions config next door.
12
12
  *
13
13
  * EXPORTS:
14
- * DEFAULT_MIN_OFFER_PERCENT, minOfferAmount, isOfferAmountValid
14
+ * DEFAULT_MIN_OFFER_PERCENT, minOfferAmount, isOfferAmountValid,
15
+ * offerIsOnlyPurchasePath, offersEnabledFor, resolveOfferBounds,
16
+ * type OfferBounds
15
17
  *
16
18
  * @tag domain:offers,seller
17
19
  * @tag layer:shared
18
20
  * @tag pattern:none
19
21
  * @tag access:isomorphic
20
- * @tag consumers:offer-actions,MakeOfferButton,ProductDetailPageView,ProductForm,SellerProductShell
22
+ * @tag consumers:offer-actions,MakeOfferButton,ProductDetailPageView,ClassifiedDetailPageView,ProductForm,SellerProductShell
21
23
  * @tag sideEffects:none
22
24
  */
25
+ import { canAddToCart, canMakeOffer, } from "../../listing-types/capabilities";
23
26
  /**
24
27
  * The floor, as a percentage of the listed price, below which an offer is
25
28
  * rejected — used whenever a listing does not set its own `minOfferPercent`.
@@ -57,3 +60,71 @@ export function minOfferAmount(listedPrice, percent) {
57
60
  export function isOfferAmountValid(amount, listedPrice, percent) {
58
61
  return amount >= minOfferAmount(listedPrice, percent) && amount < listedPrice;
59
62
  }
63
+ /* -------------------------------------------------------------------------
64
+ * Listings whose ONLY purchase path is an offer
65
+ * ---------------------------------------------------------------------- */
66
+ /**
67
+ * Is Make-an-Offer the only way to buy this listing type?
68
+ *
69
+ * True exactly when the type can be offered on but has no cart line — today
70
+ * that is `classified` alone (`canAddToCart: false`, `canMakeOffer: true`).
71
+ *
72
+ * Derived from the capability registry rather than testing for `"classified"`,
73
+ * so a future type that lands in the same shape inherits the rule instead of
74
+ * silently becoming unbuyable. That is not hypothetical: this function exists
75
+ * because classified's actual purchase path was a `conversations` chat thread,
76
+ * and when that feature was deleted the listing type would have been left with
77
+ * no way to transact at all.
78
+ */
79
+ export function offerIsOnlyPurchasePath(listingType) {
80
+ return canMakeOffer(listingType) && !canAddToCart(listingType);
81
+ }
82
+ /**
83
+ * May a buyer send an offer on this listing at all?
84
+ *
85
+ * The two ordinary gates are the type's `canMakeOffer` capability and the
86
+ * seller's own `allowOffers` opt-in. The third clause is the point:
87
+ *
88
+ * 🛑 **`allowOffers` is an opt-in to NEGOTIATION, not to being contactable.**
89
+ * On a listing with no cart, unchecking it would mean the buyer has no way to
90
+ * transact whatsoever — the page would render a price and no path to it. So
91
+ * where the offer IS the purchase path, an offer is always permitted; what
92
+ * `allowOffers` then controls is whether the buyer may propose a price *below*
93
+ * the asking one. See `resolveOfferBounds`.
94
+ */
95
+ export function offersEnabledFor(listingType, allowOffers) {
96
+ if (!canMakeOffer(listingType))
97
+ return false;
98
+ return allowOffers === true || offerIsOnlyPurchasePath(listingType);
99
+ }
100
+ /**
101
+ * The amounts this listing will accept, as one answer for the form, the schema
102
+ * and the server.
103
+ *
104
+ * Two cases beyond the ordinary one, both driven by there being no cart:
105
+ *
106
+ * - **No haggling opted in** → `min === max === listedPrice`. A "request to buy".
107
+ * - **Haggling opted in** → the usual floor, but the ceiling is the asking price
108
+ * **inclusive**. On a cartable listing an offer at full price is rejected with
109
+ * "use Add to Cart instead", which on a classified is advice the buyer cannot
110
+ * take — there is no cart button to use. That rejection was already a dead end
111
+ * before this change.
112
+ */
113
+ export function resolveOfferBounds(opts) {
114
+ const { listingType, listedPrice, minOfferPercent, allowOffers } = opts;
115
+ const onlyPath = offerIsOnlyPurchasePath(listingType);
116
+ if (onlyPath && allowOffers !== true) {
117
+ return { min: listedPrice, max: listedPrice, isBuyRequest: true };
118
+ }
119
+ return {
120
+ min: minOfferAmount(listedPrice, minOfferPercent),
121
+ // `- 0.01` and not `- 1`: money is decimal rupees, and this is the exact
122
+ // ceiling `makeOfferFormSchema` has always enforced.
123
+ max: onlyPath ? listedPrice : Math.round((listedPrice - 0.01) * 100) / 100,
124
+ isBuyRequest: false,
125
+ };
126
+ }
127
+ /** Whether `amount` falls within `bounds`. The one comparison, shared. */
128
+ export function isWithinOfferBounds(amount, bounds) {
129
+ return amount >= bounds.min && amount <= bounds.max;
130
+ }
@@ -13,7 +13,7 @@ export declare const createOrderSchema: z.ZodObject<{
13
13
  quantity: number;
14
14
  productId: string;
15
15
  }>, "many">;
16
- shippingAddress: z.ZodObject<{
16
+ shippingAddress: z.ZodEffects<z.ZodObject<{
17
17
  fullName: z.ZodString;
18
18
  phone: z.ZodString;
19
19
  addressLine1: z.ZodString;
@@ -40,6 +40,24 @@ export declare const createOrderSchema: z.ZodObject<{
40
40
  pincode: string;
41
41
  country?: string | undefined;
42
42
  addressLine2?: string | undefined;
43
+ }>, {
44
+ country: string;
45
+ fullName: string;
46
+ phone: string;
47
+ addressLine1: string;
48
+ city: string;
49
+ state: string;
50
+ pincode: string;
51
+ addressLine2?: string | undefined;
52
+ }, {
53
+ fullName: string;
54
+ phone: string;
55
+ addressLine1: string;
56
+ city: string;
57
+ state: string;
58
+ pincode: string;
59
+ country?: string | undefined;
60
+ addressLine2?: string | undefined;
43
61
  }>;
44
62
  paymentMethod: z.ZodEnum<["razorpay", "cod", "upi"]>;
45
63
  couponCode: z.ZodOptional<z.ZodString>;
@@ -1,5 +1,17 @@
1
1
  import { z } from "zod";
2
+ import { DEFAULT_COUNTRY, isValidPostalCode, postalLabelFor, } from "../../../../constants/geo/countries";
2
3
  import { ORDER_CANCEL_REASON_MAX_LENGTH, ORDER_NOTE_MAX_LENGTH, ORDER_TRACKING_NUMBER_MAX_LENGTH, } from "./config";
4
+ /*
5
+ * The order's own SNAPSHOT of where it shipped, denormalised onto the order
6
+ * document at creation. Its field names (`pincode`) are stored on every
7
+ * existing order, so renaming them to match `AddressDocument` is a data
8
+ * migration rather than a refactor — and a snapshot deliberately does NOT
9
+ * follow the address it was copied from.
10
+ *
11
+ * The postal RULE is shared, which is the half that was wrong: `/^\d{6}$/`
12
+ * is India-only, on an order whose country is whatever the buyer chose.
13
+ */
14
+ // audit-address-shape-ok: an order's frozen shipping snapshot, not the address entity
3
15
  const addressSchema = z.object({
4
16
  fullName: z.string().min(1),
5
17
  phone: z.string().min(10).max(15),
@@ -7,8 +19,16 @@ const addressSchema = z.object({
7
19
  addressLine2: z.string().optional(),
8
20
  city: z.string().min(1),
9
21
  state: z.string().min(1),
10
- pincode: z.string().regex(/^\d{6}$/, "Pincode must be 6 digits"),
11
- country: z.string().default("India"),
22
+ pincode: z.string().trim().min(1, "A postal code is required."),
23
+ country: z.string().default(DEFAULT_COUNTRY),
24
+ }).superRefine((v, ctx) => {
25
+ if (!isValidPostalCode(v.country, v.pincode)) {
26
+ ctx.addIssue({
27
+ code: z.ZodIssueCode.custom,
28
+ path: ["pincode"],
29
+ message: `That is not a valid ${postalLabelFor(v.country)}.`,
30
+ });
31
+ }
12
32
  });
13
33
  export const createOrderSchema = z.object({
14
34
  items: z.array(z.object({
@@ -25,9 +25,18 @@ export interface PublicSiteSettings {
25
25
  payment: {
26
26
  otpCheckoutThreshold?: number;
27
27
  };
28
- /** Deliberately NOT the full `featureFlags` — `adminCheckoutBypass` must never be advertised publicly. */
29
- featureFlags: {
30
- listingTypes?: SiteSettingsDocument["featureFlags"]["listingTypes"];
28
+ /**
29
+ * Which listing types the site offers — the client needs this to avoid
30
+ * rendering a browse tab for a type nothing will return.
31
+ *
32
+ * Deliberately NOT the whole `listings` block: `categoryTypes` has no client
33
+ * reader. This was `featureFlags.listingTypes` until that group was deleted
34
+ * on 2026-08-29; the projection stayed narrow for the same reason it always
35
+ * was — a public payload carries what a client demonstrably reads, nothing
36
+ * more (Root Cause #70).
37
+ */
38
+ listings: {
39
+ listingTypes?: NonNullable<SiteSettingsDocument["listings"]>["listingTypes"];
31
40
  };
32
41
  notificationChannels?: NotificationChannelConfig;
33
42
  announcementBar?: SiteSettingsDocument["announcementBar"];