@mohasinac/appkit 4.13.1 → 4.14.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 (377) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +14 -5
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +3 -0
  3. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +6 -0
  4. package/dist/_internal/client/features/lottery/LotteryDetailView.d.ts +8 -0
  5. package/dist/_internal/client/features/lottery/LotteryDetailView.js +4 -1
  6. package/dist/_internal/client/features/lottery/LotteryListView.d.ts +7 -0
  7. package/dist/_internal/client/features/lottery/LotteryListView.js +1 -1
  8. package/dist/_internal/client/features/lottery/PrizeDrawLotteryDetailView.js +4 -1
  9. package/dist/_internal/client/features/lottery/slot-collage-items.d.ts +15 -0
  10. package/dist/_internal/client/features/lottery/slot-collage-items.js +13 -0
  11. package/dist/_internal/server/features/auctions/actions.js +6 -0
  12. package/dist/_internal/server/features/bundles/data.d.ts +16 -0
  13. package/dist/_internal/server/features/bundles/data.js +26 -0
  14. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  15. package/dist/_internal/server/features/bundles/index.js +1 -1
  16. package/dist/_internal/server/features/checkout/actions.js +6 -0
  17. package/dist/_internal/server/features/checkout/locked-lines.d.ts +38 -0
  18. package/dist/_internal/server/features/checkout/locked-lines.js +171 -0
  19. package/dist/_internal/server/features/homepage/index.d.ts +0 -1
  20. package/dist/_internal/server/features/homepage/index.js +7 -1
  21. package/dist/_internal/server/features/lottery/adapters.js +4 -0
  22. package/dist/_internal/server/features/products/data.js +15 -37
  23. package/dist/_internal/server/features/products/index.d.ts +1 -1
  24. package/dist/_internal/server/features/products/index.js +1 -1
  25. package/dist/_internal/server/features/products/list-public.d.ts +46 -20
  26. package/dist/_internal/server/features/products/list-public.js +266 -90
  27. package/dist/_internal/server/features/products/listing-tab-counts.d.ts +44 -0
  28. package/dist/_internal/server/features/products/listing-tab-counts.js +97 -0
  29. package/dist/_internal/server/features/site-settings/adapters.d.ts +51 -0
  30. package/dist/_internal/server/features/site-settings/adapters.js +75 -0
  31. package/dist/_internal/server/features/site-settings/index.d.ts +1 -0
  32. package/dist/_internal/server/features/site-settings/index.js +1 -0
  33. package/dist/_internal/server/features/stores/adapters.d.ts +56 -0
  34. package/dist/_internal/server/features/stores/adapters.js +91 -0
  35. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  36. package/dist/_internal/server/features/stores/index.js +1 -0
  37. package/dist/_internal/server/jobs/core/auctionSettlement.js +56 -2
  38. package/dist/_internal/server/jobs/core/offerExpiry.js +45 -9
  39. package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.d.ts +5 -0
  40. package/dist/_internal/server/jobs/core/paymentReviewAutoApprove.js +33 -1
  41. package/dist/_internal/server/jobs/handlers/messages.d.ts +7 -0
  42. package/dist/_internal/server/jobs/handlers/messages.js +10 -0
  43. package/dist/_internal/shared/actions/action-registry.js +15 -1
  44. package/dist/_internal/shared/checkout/lanes.d.ts +25 -0
  45. package/dist/_internal/shared/checkout/lanes.js +26 -0
  46. package/dist/_internal/shared/features/auctions/config.d.ts +27 -0
  47. package/dist/_internal/shared/features/auctions/config.js +27 -0
  48. package/dist/_internal/shared/features/blog/schema.d.ts +2 -2
  49. package/dist/_internal/shared/features/cart/schema.d.ts +12 -12
  50. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
  51. package/dist/_internal/shared/features/emi/schedule.d.ts +24 -3
  52. package/dist/_internal/shared/features/emi/schedule.js +20 -5
  53. package/dist/_internal/shared/features/events/schema.d.ts +2 -2
  54. package/dist/_internal/shared/features/homepage/section-gate.d.ts +14 -0
  55. package/dist/_internal/shared/features/homepage/section-gate.js +51 -0
  56. package/dist/_internal/shared/features/offers/config.d.ts +30 -0
  57. package/dist/_internal/shared/features/offers/config.js +59 -0
  58. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  59. package/dist/_internal/shared/features/products/schema.d.ts +32 -32
  60. package/dist/_internal/shared/features/products/to-product-item.js +6 -0
  61. package/dist/_internal/shared/features/reviews/schema.d.ts +2 -2
  62. package/dist/_internal/shared/features/site-settings/fees.d.ts +60 -0
  63. package/dist/_internal/shared/features/site-settings/fees.js +39 -0
  64. package/dist/_internal/shared/features/site-settings/types.d.ts +40 -0
  65. package/dist/_internal/shared/features/site-settings/types.js +1 -0
  66. package/dist/_internal/shared/history/history.d.ts +66 -0
  67. package/dist/_internal/shared/history/history.js +158 -0
  68. package/dist/_internal/shared/history/index.d.ts +4 -0
  69. package/dist/_internal/shared/history/index.js +2 -0
  70. package/dist/_internal/shared/history/types.d.ts +61 -0
  71. package/dist/_internal/shared/history/types.js +6 -0
  72. package/dist/_internal/shared/listing-types/_registry.d.ts +54 -1
  73. package/dist/_internal/shared/listing-types/_registry.js +67 -0
  74. package/dist/_internal/shared/listing-types/art/config.d.ts +3 -0
  75. package/dist/_internal/shared/listing-types/art/config.js +5 -0
  76. package/dist/_internal/shared/listing-types/auction/config.d.ts +13 -0
  77. package/dist/_internal/shared/listing-types/auction/config.js +21 -0
  78. package/dist/_internal/shared/listing-types/availability.d.ts +79 -0
  79. package/dist/_internal/shared/listing-types/availability.js +104 -0
  80. package/dist/_internal/shared/listing-types/capabilities.d.ts +15 -0
  81. package/dist/_internal/shared/listing-types/capabilities.js +12 -0
  82. package/dist/_internal/shared/listing-types/classified/config.d.ts +3 -0
  83. package/dist/_internal/shared/listing-types/classified/config.js +5 -0
  84. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +11 -0
  85. package/dist/_internal/shared/listing-types/digital-code/config.js +22 -2
  86. package/dist/_internal/shared/listing-types/live/config.d.ts +3 -0
  87. package/dist/_internal/shared/listing-types/live/config.js +5 -0
  88. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +3 -0
  89. package/dist/_internal/shared/listing-types/pre-order/config.js +19 -0
  90. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +11 -0
  91. package/dist/_internal/shared/listing-types/prize-draw/config.js +15 -0
  92. package/dist/_internal/shared/listing-types/standard/config.d.ts +3 -0
  93. package/dist/_internal/shared/listing-types/standard/config.js +5 -0
  94. package/dist/_internal/shared/listing-types/stickers/config.d.ts +3 -0
  95. package/dist/_internal/shared/listing-types/stickers/config.js +5 -0
  96. package/dist/client.d.ts +7 -5
  97. package/dist/client.js +2 -2
  98. package/dist/constants/api-endpoints.d.ts +6 -0
  99. package/dist/constants/api-endpoints.js +2 -0
  100. package/dist/constants/field-names.d.ts +49 -2
  101. package/dist/constants/field-names.js +53 -2
  102. package/dist/constants/table-keys.d.ts +17 -0
  103. package/dist/constants/table-keys.js +17 -0
  104. package/dist/errors/error-codes.d.ts +13 -1
  105. package/dist/errors/error-codes.js +13 -1
  106. package/dist/errors/error-display-map.js +12 -3
  107. package/dist/errors/messages.d.ts +10 -2
  108. package/dist/errors/messages.js +10 -2
  109. package/dist/features/about/components/AboutView.d.ts +3 -0
  110. package/dist/features/about/components/AboutView.js +3 -2
  111. package/dist/features/about/components/PolicyPageView.d.ts +2 -1
  112. package/dist/features/about/components/PolicyPageView.js +61 -23
  113. package/dist/features/about/components/PublicProfileView.js +5 -0
  114. package/dist/features/about/schemas/firestore.d.ts +3 -0
  115. package/dist/features/account/components/UserOrdersView.js +7 -0
  116. package/dist/features/account/components/index.d.ts +0 -2
  117. package/dist/features/account/components/index.js +0 -1
  118. package/dist/features/account/schemas/index.d.ts +2 -2
  119. package/dist/features/account/types/index.d.ts +21 -2
  120. package/dist/features/addresses/schemas/index.d.ts +4 -4
  121. package/dist/features/admin/components/AdminArtView.js +8 -3
  122. package/dist/features/admin/components/AdminClassifiedView.js +8 -3
  123. package/dist/features/admin/components/AdminDigitalCodesView.js +8 -3
  124. package/dist/features/admin/components/AdminFeatureFlagsView.js +1 -0
  125. package/dist/features/admin/components/AdminLiveView.js +8 -3
  126. package/dist/features/admin/components/AdminOffersView.d.ts +4 -0
  127. package/dist/features/admin/components/AdminOffersView.js +186 -0
  128. package/dist/features/admin/components/AdminOrdersView.js +10 -1
  129. package/dist/features/admin/components/AdminPrizeDrawsView.js +5 -0
  130. package/dist/features/admin/components/AdminProductsView.js +16 -29
  131. package/dist/features/admin/components/AdminSiteSettingsView.js +31 -13
  132. package/dist/features/admin/components/AdminSiteView.js +6 -2
  133. package/dist/features/admin/components/AdminStickersView.js +8 -3
  134. package/dist/features/admin/components/index.d.ts +2 -0
  135. package/dist/features/admin/components/index.js +1 -0
  136. package/dist/features/admin/constants/filter-tabs.d.ts +86 -0
  137. package/dist/features/admin/constants/filter-tabs.js +55 -0
  138. package/dist/features/admin/schemas/firestore.d.ts +122 -3
  139. package/dist/features/admin/schemas/firestore.js +1 -0
  140. package/dist/features/auctions/actions/bid-actions.d.ts +20 -5
  141. package/dist/features/auctions/actions/bid-actions.js +93 -20
  142. package/dist/features/auctions/components/AuctionDetailPageView.js +11 -6
  143. package/dist/features/auctions/components/AuctionsListView.js +5 -6
  144. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +7 -0
  145. package/dist/features/auctions/components/MarketplaceAuctionCard.js +19 -2
  146. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -2
  147. package/dist/features/auctions/components/PlaceBidFormClient.js +8 -4
  148. package/dist/features/auctions/repository/bid.repository.d.ts +13 -0
  149. package/dist/features/auctions/repository/bid.repository.js +23 -0
  150. package/dist/features/auctions/schemas/firestore.d.ts +17 -0
  151. package/dist/features/auctions/schemas/index.d.ts +12 -12
  152. package/dist/features/auth/permissions/constants.d.ts +1 -1
  153. package/dist/features/auth/permissions/constants.js +10 -1
  154. package/dist/features/blog/actions/blog-actions.d.ts +7 -7
  155. package/dist/features/blog/schemas/index.d.ts +6 -6
  156. package/dist/features/cart/repository/cart.repository.js +1 -0
  157. package/dist/features/cart/schemas/firestore.d.ts +15 -0
  158. package/dist/features/catalogue/schemas/validation.d.ts +4 -4
  159. package/dist/features/categories/components/BrandDetailPageView.js +23 -40
  160. package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -7
  161. package/dist/features/categories/components/BrandDetailTabs.js +8 -12
  162. package/dist/features/categories/components/BundleCollage.js +1 -1
  163. package/dist/features/categories/components/CategoryDetailPageView.js +53 -57
  164. package/dist/features/categories/components/CategoryDetailTabs.d.ts +10 -8
  165. package/dist/features/categories/components/CategoryDetailTabs.js +11 -16
  166. package/dist/features/categories/components/CategoryProductsListing.js +11 -1
  167. package/dist/features/categories/components/ConcernCard.js +7 -1
  168. package/dist/features/categories/schemas/firestore.d.ts +13 -0
  169. package/dist/features/categories/schemas/index.d.ts +26 -26
  170. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +16 -1
  171. package/dist/features/classified/components/ClassifiedDetailPageView.js +23 -3
  172. package/dist/features/events/components/AdminEventsView.js +15 -11
  173. package/dist/features/events/components/EventFilters.d.ts +9 -1
  174. package/dist/features/events/components/EventFilters.js +45 -21
  175. package/dist/features/events/components/EventsIndexListing.d.ts +6 -1
  176. package/dist/features/events/components/EventsIndexListing.js +2 -2
  177. package/dist/features/events/components/EventsListPageView.js +13 -4
  178. package/dist/features/events/repository/events.repository.d.ts +17 -0
  179. package/dist/features/events/repository/events.repository.js +27 -0
  180. package/dist/features/events/schemas/firestore.d.ts +12 -0
  181. package/dist/features/events/schemas/firestore.js +12 -0
  182. package/dist/features/events/schemas/index.d.ts +50 -50
  183. package/dist/features/events/types/index.d.ts +11 -0
  184. package/dist/features/events/types/index.js +57 -1
  185. package/dist/features/faq/actions/faq-actions.d.ts +12 -12
  186. package/dist/features/faq/schemas/index.d.ts +8 -8
  187. package/dist/features/filters/FilterFacetSection.d.ts +7 -1
  188. package/dist/features/filters/FilterFacetSection.js +11 -3
  189. package/dist/features/grouped/schemas/index.d.ts +4 -4
  190. package/dist/features/homepage/components/AnnouncementBar.d.ts +7 -1
  191. package/dist/features/homepage/components/AnnouncementBar.js +3 -3
  192. package/dist/features/homepage/components/MarketplaceHomepageView.js +20 -6
  193. package/dist/features/homepage/hooks/homepage-query.d.ts +16 -0
  194. package/dist/features/homepage/hooks/homepage-query.js +45 -0
  195. package/dist/features/homepage/hooks/useFeaturedAuctions.d.ts +10 -0
  196. package/dist/features/homepage/hooks/useFeaturedAuctions.js +28 -8
  197. package/dist/features/homepage/hooks/useFeaturedPreOrders.js +17 -8
  198. package/dist/features/homepage/hooks/useFeaturedProducts.js +15 -8
  199. package/dist/features/homepage/schemas/index.d.ts +2 -2
  200. package/dist/features/layout/AppLayoutShell.d.ts +11 -1
  201. package/dist/features/layout/AppLayoutShell.js +15 -14
  202. package/dist/features/layout/BackToTop.d.ts +9 -0
  203. package/dist/features/layout/BackToTop.js +10 -1
  204. package/dist/features/layout/BottomActions.js +28 -25
  205. package/dist/features/layout/BottomChrome.d.ts +34 -0
  206. package/dist/features/layout/BottomChrome.js +55 -0
  207. package/dist/features/layout/BottomNavLayout.d.ts +6 -0
  208. package/dist/features/layout/BottomNavLayout.js +7 -1
  209. package/dist/features/layout/BottomNavbar.js +15 -2
  210. package/dist/features/layout/FooterLayout.js +11 -1
  211. package/dist/features/layout/NavbarLayout.js +1 -1
  212. package/dist/features/layout/TitleBarLayout.d.ts +7 -0
  213. package/dist/features/layout/TitleBarLayout.js +28 -5
  214. package/dist/features/layout/index.d.ts +2 -0
  215. package/dist/features/layout/index.js +1 -0
  216. package/dist/features/lottery/types/index.d.ts +3 -0
  217. package/dist/features/media/MediaPickerModal.js +5 -1
  218. package/dist/features/messages/schemas/index.d.ts +2 -2
  219. package/dist/features/orders/components/OrderScopeTabs.d.ts +20 -0
  220. package/dist/features/orders/components/OrderScopeTabs.js +31 -0
  221. package/dist/features/orders/constants/order-scope.d.ts +48 -0
  222. package/dist/features/orders/constants/order-scope.js +111 -0
  223. package/dist/features/orders/repository/orders.repository.d.ts +54 -7
  224. package/dist/features/orders/repository/orders.repository.js +116 -22
  225. package/dist/features/orders/schemas/firestore.d.ts +117 -0
  226. package/dist/features/orders/schemas/firestore.js +30 -0
  227. package/dist/features/orders/schemas/index.d.ts +18 -18
  228. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +14 -9
  229. package/dist/features/pre-orders/components/PreOrdersListView.js +6 -5
  230. package/dist/features/pre-orders/schemas/index.d.ts +1 -1
  231. package/dist/features/products/actions/product-actions.d.ts +14 -0
  232. package/dist/features/products/actions/product-actions.js +49 -11
  233. package/dist/features/products/api/route.d.ts +12 -0
  234. package/dist/features/products/api/route.js +42 -100
  235. package/dist/features/products/components/ArtStickersListView.js +2 -2
  236. package/dist/features/products/components/AuctionsIndexListing.js +13 -11
  237. package/dist/features/products/components/AvailabilityTabs.d.ts +11 -0
  238. package/dist/features/products/components/AvailabilityTabs.js +47 -0
  239. package/dist/features/products/components/MakeOfferButton.js +48 -49
  240. package/dist/features/products/components/PrizeDrawCollage.d.ts +20 -4
  241. package/dist/features/products/components/PrizeDrawCollage.js +9 -1
  242. package/dist/features/products/components/PrizeDrawsIndexListing.js +14 -12
  243. package/dist/features/products/components/PrizeDrawsListingView.js +2 -2
  244. package/dist/features/products/components/PrizeDrawsSection.d.ts +7 -1
  245. package/dist/features/products/components/PrizeDrawsSection.js +23 -18
  246. package/dist/features/products/components/ProductDetailPageView.d.ts +4 -1
  247. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  248. package/dist/features/products/components/ProductForm.js +5 -2
  249. package/dist/features/products/components/ProductGrid.js +6 -1
  250. package/dist/features/products/components/ProductsIndexListing.js +22 -32
  251. package/dist/features/products/components/ProductsIndexPageView.js +6 -9
  252. package/dist/features/products/components/index.d.ts +2 -2
  253. package/dist/features/products/components/index.js +1 -1
  254. package/dist/features/products/config/listing-type-listing-config.d.ts +20 -0
  255. package/dist/features/products/config/listing-type-listing-config.js +22 -10
  256. package/dist/features/products/constants/action-defs.d.ts +2 -0
  257. package/dist/features/products/constants/action-defs.js +6 -0
  258. package/dist/features/products/hooks/useAvailabilityScope.d.ts +13 -0
  259. package/dist/features/products/hooks/useAvailabilityScope.js +19 -0
  260. package/dist/features/products/hooks/useProducts.d.ts +5 -0
  261. package/dist/features/products/hooks/useProducts.js +13 -2
  262. package/dist/features/products/repository/products.repository.d.ts +31 -2
  263. package/dist/features/products/repository/products.repository.js +53 -21
  264. package/dist/features/products/schemas/firestore.d.ts +17 -3
  265. package/dist/features/products/schemas/firestore.js +7 -2
  266. package/dist/features/products/schemas/index.d.ts +66 -66
  267. package/dist/features/products/schemas/product-features.validators.d.ts +6 -6
  268. package/dist/features/products/types/index.d.ts +16 -1
  269. package/dist/features/promotions/schemas/index.d.ts +2 -2
  270. package/dist/features/reviews/schemas/index.d.ts +16 -16
  271. package/dist/features/search/schemas/index.d.ts +4 -4
  272. package/dist/features/seller/actions/offer-actions.js +25 -2
  273. package/dist/features/seller/components/SellerArtView.js +5 -0
  274. package/dist/features/seller/components/SellerAuctionsView.js +13 -15
  275. package/dist/features/seller/components/SellerClassifiedView.js +5 -0
  276. package/dist/features/seller/components/SellerDigitalCodesView.js +5 -0
  277. package/dist/features/seller/components/SellerLiveView.js +5 -0
  278. package/dist/features/seller/components/SellerOrdersView.js +31 -9
  279. package/dist/features/seller/components/SellerPayoutSettingsView.js +19 -18
  280. package/dist/features/seller/components/SellerPreOrdersView.js +5 -0
  281. package/dist/features/seller/components/SellerPrizeDrawsView.js +14 -16
  282. package/dist/features/seller/components/SellerProductShell.js +7 -1
  283. package/dist/features/seller/components/SellerProductsView.js +11 -8
  284. package/dist/features/seller/components/SellerStickersView.js +5 -0
  285. package/dist/features/seller/components/index.d.ts +0 -2
  286. package/dist/features/seller/components/index.js +0 -1
  287. package/dist/features/seller/repository/offer.repository.d.ts +73 -16
  288. package/dist/features/seller/repository/offer.repository.js +38 -3
  289. package/dist/features/seller/schemas/index.d.ts +2 -2
  290. package/dist/features/seller/schemas/offer-forms.d.ts +25 -0
  291. package/dist/features/seller/schemas/offer-forms.js +21 -0
  292. package/dist/features/seller/server.d.ts +0 -1
  293. package/dist/features/seller/server.js +0 -1
  294. package/dist/features/shell/SectionForm.d.ts +99 -0
  295. package/dist/features/shell/SectionForm.js +128 -0
  296. package/dist/features/shell/entity-form.d.ts +109 -0
  297. package/dist/features/shell/entity-form.js +96 -0
  298. package/dist/features/shell/index.d.ts +4 -0
  299. package/dist/features/shell/index.js +2 -0
  300. package/dist/features/stores/api/[storeSlug]/route.js +2 -25
  301. package/dist/features/stores/components/StoreAuctionsListing.js +15 -1
  302. package/dist/features/stores/components/StoreClassifiedsListing.js +12 -2
  303. package/dist/features/stores/components/StoreDetailLayoutView.js +24 -71
  304. package/dist/features/stores/components/StoreDigitalCodesListing.js +12 -2
  305. package/dist/features/stores/components/StoreLiveItemsListing.js +12 -2
  306. package/dist/features/stores/components/StorePreOrdersListing.js +12 -1
  307. package/dist/features/stores/components/StoreProductsListing.js +11 -1
  308. package/dist/features/stores/repository/store.repository.js +6 -0
  309. package/dist/features/stores/schemas/index.d.ts +6 -6
  310. package/dist/features/support/schemas/index.d.ts +4 -4
  311. package/dist/features/tester/actions/checklist-item-actions.d.ts +4 -4
  312. package/dist/features/tester/seed-data/offers-tester-seed-data.js +119 -3
  313. package/dist/features/tester/seed-data/products-tester-seed-data.js +131 -1
  314. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +105 -1
  315. package/dist/features/wishlist/hooks/useWishlistWithGuest.d.ts +1 -1
  316. package/dist/index.d.ts +13 -10
  317. package/dist/index.js +14 -9
  318. package/dist/next/routing/route-map.d.ts +7 -1
  319. package/dist/next/routing/route-map.js +5 -0
  320. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  321. package/dist/providers/db-firebase/base.repository.js +50 -1
  322. package/dist/providers/db-firebase/index.d.ts +1 -1
  323. package/dist/providers/db-firebase/index.js +1 -1
  324. package/dist/providers/db-firebase/sieve.d.ts +25 -0
  325. package/dist/providers/db-firebase/sieve.js +52 -18
  326. package/dist/react/hooks/useUrlTable.js +10 -1
  327. package/dist/schemas/registry.d.ts +68 -68
  328. package/dist/seed/_helpers/category-forest.d.ts +2 -0
  329. package/dist/seed/_helpers/category-forest.js +309 -0
  330. package/dist/seed/_helpers/category-tree.d.ts +27 -0
  331. package/dist/seed/_helpers/category-tree.js +103 -0
  332. package/dist/seed/catalogue-seed-data.js +12 -12
  333. package/dist/seed/categories-seed-data.js +177 -162
  334. package/dist/seed/events-seed-data.js +61 -10
  335. package/dist/seed/homepage-sections-seed-data.js +5 -1
  336. package/dist/seed/offers-seed-data.js +32 -27
  337. package/dist/seed/products-art-seed-data.js +50 -12
  338. package/dist/seed/products-auctions-seed-data.js +97 -18
  339. package/dist/seed/products-classifieds-seed-data.js +80 -16
  340. package/dist/seed/products-digital-codes-seed-data.js +66 -18
  341. package/dist/seed/products-live-items-seed-data.js +64 -13
  342. package/dist/seed/products-preorders-seed-data.d.ts +0 -1
  343. package/dist/seed/products-preorders-seed-data.js +52 -13
  344. package/dist/seed/products-prize-draws-seed-data.js +63 -14
  345. package/dist/seed/products-standard-seed-data.js +99 -26
  346. package/dist/seed/products-stickers-seed-data.js +47 -12
  347. package/dist/seed/reviews-seed-data.js +6 -1
  348. package/dist/seed/site-settings-seed-data.d.ts +1 -38
  349. package/dist/seed/site-settings-seed-data.js +38 -76
  350. package/dist/seed/stores-seed-data.js +71 -1
  351. package/dist/seed/users-seed-data.js +15 -5
  352. package/dist/server-entry.d.ts +1 -2
  353. package/dist/server-entry.js +4 -3
  354. package/dist/server.d.ts +4 -3
  355. package/dist/server.js +8 -4
  356. package/dist/styles.css +78 -12
  357. package/dist/tailwind-utilities.css +1 -1
  358. package/dist/ui/components/Button.style.css +19 -4
  359. package/dist/ui/components/CollapsibleSection.d.ts +17 -1
  360. package/dist/ui/components/CollapsibleSection.js +6 -2
  361. package/dist/ui/components/FilterChipGroup.d.ts +25 -1
  362. package/dist/ui/components/FilterChipGroup.js +21 -4
  363. package/dist/ui/components/HandMode.style.css +47 -0
  364. package/dist/ui/components/Layout.d.ts +9 -2
  365. package/dist/ui/components/Layout.js +1 -1
  366. package/dist/ui/components/ListingLayout.js +8 -8
  367. package/dist/ui/components/ListingLayout.style.css +8 -7
  368. package/dist/ui/components/StickyToolbar.d.ts +3 -1
  369. package/dist/ui/components/StickyToolbar.js +62 -20
  370. package/dist/ui/components/bottom-chrome-slot.d.ts +21 -0
  371. package/dist/ui/components/bottom-chrome-slot.js +29 -0
  372. package/dist/ui/components/index.style.css +3 -0
  373. package/dist/ui/forms/FormShell.d.ts +43 -13
  374. package/dist/ui/forms/FormShell.js +22 -11
  375. package/dist/ui/forms/index.d.ts +1 -1
  376. package/dist/ui/index.d.ts +1 -1
  377. package/package.json +1 -1
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cap for any history array. Matches `HISTORY_MAX`'s reasoning: an unbounded
3
+ * array on a hot document inflates every read of that document (Rule #6), and
4
+ * an order that changes status more than this has a different problem.
5
+ */
6
+ export const STATUS_HISTORY_MAX = 50;
@@ -22,8 +22,10 @@
22
22
  * that doesn't cover the full union.
23
23
  */
24
24
  import { LISTING_TYPE_CAPABILITIES } from "./capabilities";
25
+ import { type AvailabilityRow, type UnavailableClause } from "./availability";
25
26
  import type { ListingType } from "../../../features/products/types/index";
26
27
  import type { SortOption } from "../../../features/products/constants/sieve";
28
+ import { type AvailabilityFilter } from "../../../constants/field-names";
27
29
  /**
28
30
  * Which "Show X" quick-filter a listing type's browse surfaces default to
29
31
  * hiding. Drives both the toolbar toggle label and the SSR default filter,
@@ -78,8 +80,29 @@ export interface ListingTypePlugin {
78
80
  * /products when exactly this type is selected.
79
81
  */
80
82
  browseRoute: string | null;
81
- /** Which "Show X" default this type's browse surfaces apply. */
83
+ /**
84
+ * Which "Show X" default this type's browse surfaces apply. Since 2026-08-24
85
+ * this drives the LABEL of the availability tab bar's middle tab
86
+ * (`availabilityTabsFor`) rather than a toggle pill's wording.
87
+ */
82
88
  hideDefault: ListingHideDefault;
89
+ /**
90
+ * Is this row still buyable / biddable / enterable? Type-specific checks
91
+ * ONLY — `baseAvailable` (isSold / availableQuantity / stockQuantity) is
92
+ * applied to every type by `isListingRowAvailable` before this runs, so a
93
+ * type with no extra rule returns `true`.
94
+ *
95
+ * Must stay pure and client-safe: the same predicate runs inside the SSR
96
+ * query, the homepage fetch and the related-items carousel.
97
+ */
98
+ isAvailable: (row: AvailabilityRow, now: Date) => boolean;
99
+ /**
100
+ * The query half of the same question — see `UnavailableClause`. Used to
101
+ * front-load the "Sold & Ended" scope, which is sparse and therefore
102
+ * expressible as an OR of equalities, unlike "available", which is dense and
103
+ * negation-shaped and stays an in-memory predicate.
104
+ */
105
+ unavailableClauses: readonly UnavailableClause[];
83
106
  /** Sort set offered on admin/seller dashboards for this type. */
84
107
  sortOptions: readonly SortOption[];
85
108
  /** Narrower sort set offered on public browse surfaces for this type. */
@@ -124,4 +147,34 @@ export declare function commonSortOptionsFor(types: readonly ListingType[], vari
124
147
  * so this returns every distinct default across the set.
125
148
  */
126
149
  export declare function hideDefaultsFor(types: readonly ListingType[]): ListingHideDefault[];
150
+ export interface AvailabilityTab {
151
+ id: AvailabilityFilter;
152
+ label: string;
153
+ }
154
+ /**
155
+ * The three-tab availability bar for a set of listing types.
156
+ *
157
+ * The middle label is derived, not a constant: `/auctions` reads "Ended",
158
+ * `/art` reads "Sold", and `/products` — which spans both kinds — reads
159
+ * "Sold & Ended". Ordering is fixed rather than set-iteration order so the
160
+ * label can never flip to "Ended & Sold" between renders.
161
+ */
162
+ export declare function availabilityTabsFor(types: readonly ListingType[]): AvailabilityTab[];
163
+ /**
164
+ * THE definition of "this listing is still available", shared by the listing
165
+ * query, the homepage fetch and the related-items carousel. Before this there
166
+ * were four partial versions of it and they disagreed — see the header of
167
+ * `./availability`.
168
+ *
169
+ * An unrecognised `listingType` falls back to `standard` rather than throwing:
170
+ * these rows come from Firestore and a bad value must degrade to the base
171
+ * checks, not blank the page.
172
+ */
173
+ export declare function isListingRowAvailable(row: AvailabilityRow, now: Date): boolean;
174
+ /**
175
+ * Every distinct `UnavailableClause` across a set of listing types, deduped by
176
+ * field+op+value. `/products` (all nine types) collapses to five clauses, so
177
+ * the "Sold & Ended" scope costs five bounded queries, not nine.
178
+ */
179
+ export declare function unavailableClausesFor(types: readonly ListingType[]): UnavailableClause[];
127
180
  export { LISTING_TYPE_CAPABILITIES };
@@ -33,6 +33,8 @@ import * as live from "./live/config";
33
33
  import * as art from "./art/config";
34
34
  import * as stickers from "./stickers/config";
35
35
  import { LISTING_TYPE_CAPABILITIES } from "./capabilities";
36
+ import { baseAvailable, } from "./availability";
37
+ import { AVAILABILITY_VALUES, } from "../../../constants/field-names";
36
38
  // SB-UNI-D — bundle entry removed; bundles are a categoryType, not a listingType.
37
39
  // SB-UNI-F 2026-05-13 — classified / digital-code / live added.
38
40
  // EMI/art-stickers session — art / stickers added.
@@ -112,6 +114,71 @@ export function hideDefaultsFor(types) {
112
114
  seen.add(LISTING_TYPE_REGISTRY[type].hideDefault);
113
115
  return [...seen];
114
116
  }
117
+ // ---------------------------------------------------------------------------
118
+ // Availability scope
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * The user-facing noun for each hide-default. "ended" and "closed" are one
122
+ * query mechanic and one word to a buyer — an auction that ended and a prize
123
+ * draw that closed are both simply over.
124
+ *
125
+ * `Record<ListingHideDefault, …>` on purpose: adding a fourth hide-default is
126
+ * then a compile error rather than a tab that silently renders `undefined`.
127
+ */
128
+ const HIDE_DEFAULT_NOUN = {
129
+ sold: "Sold",
130
+ ended: "Ended",
131
+ closed: "Ended",
132
+ };
133
+ /**
134
+ * The three-tab availability bar for a set of listing types.
135
+ *
136
+ * The middle label is derived, not a constant: `/auctions` reads "Ended",
137
+ * `/art` reads "Sold", and `/products` — which spans both kinds — reads
138
+ * "Sold & Ended". Ordering is fixed rather than set-iteration order so the
139
+ * label can never flip to "Ended & Sold" between renders.
140
+ */
141
+ export function availabilityTabsFor(types) {
142
+ const nouns = new Set((types.length > 0 ? types : Object.keys(LISTING_TYPE_REGISTRY)).map((t) => HIDE_DEFAULT_NOUN[LISTING_TYPE_REGISTRY[t].hideDefault]));
143
+ const unavailableLabel = ["Sold", "Ended"].filter((n) => nouns.has(n)).join(" & ") || "Sold";
144
+ return [
145
+ { id: AVAILABILITY_VALUES.AVAILABLE, label: "Available" },
146
+ { id: AVAILABILITY_VALUES.UNAVAILABLE, label: unavailableLabel },
147
+ { id: AVAILABILITY_VALUES.ALL, label: "All" },
148
+ ];
149
+ }
150
+ /**
151
+ * THE definition of "this listing is still available", shared by the listing
152
+ * query, the homepage fetch and the related-items carousel. Before this there
153
+ * were four partial versions of it and they disagreed — see the header of
154
+ * `./availability`.
155
+ *
156
+ * An unrecognised `listingType` falls back to `standard` rather than throwing:
157
+ * these rows come from Firestore and a bad value must degrade to the base
158
+ * checks, not blank the page.
159
+ */
160
+ export function isListingRowAvailable(row, now) {
161
+ if (!baseAvailable(row))
162
+ return false;
163
+ const raw = row.listingType;
164
+ const type = typeof raw === "string" && raw in LISTING_TYPE_REGISTRY ? raw : "standard";
165
+ return LISTING_TYPE_REGISTRY[type].isAvailable(row, now);
166
+ }
167
+ /**
168
+ * Every distinct `UnavailableClause` across a set of listing types, deduped by
169
+ * field+op+value. `/products` (all nine types) collapses to five clauses, so
170
+ * the "Sold & Ended" scope costs five bounded queries, not nine.
171
+ */
172
+ export function unavailableClausesFor(types) {
173
+ const effective = types.length > 0 ? types : Object.keys(LISTING_TYPE_REGISTRY);
174
+ const seen = new Map();
175
+ for (const type of effective) {
176
+ for (const clause of LISTING_TYPE_REGISTRY[type].unavailableClauses) {
177
+ seen.set(`${clause.field}|${clause.op}|${String(clause.value)}`, clause);
178
+ }
179
+ }
180
+ return [...seen.values()];
181
+ }
115
182
  // Re-export the capability map so consumers can pull both surfaces from
116
183
  // the same module path.
117
184
  export { LISTING_TYPE_CAPABILITIES };
@@ -1,3 +1,4 @@
1
+ import { alwaysAvailable } from "../availability";
1
2
  export declare const LISTING_TYPE: "art";
2
3
  export declare const capability: import("../capabilities").ListingTypeCapability;
3
4
  export declare const config: {
@@ -17,6 +18,8 @@ export declare const config: {
17
18
  chipLabel: string;
18
19
  browseRoute: string;
19
20
  hideDefault: "sold";
21
+ isAvailable: typeof alwaysAvailable;
22
+ unavailableClauses: readonly import("../availability").UnavailableClause[];
20
23
  sortOptions: readonly [{
21
24
  readonly value: string;
22
25
  readonly label: "Newest First";
@@ -1,4 +1,5 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
+ import { alwaysAvailable, STOCK_ONLY_UNAVAILABLE_CLAUSES, } from "../availability";
2
3
  import { ROUTES } from "../../../../next/routing/route-map";
3
4
  import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
4
5
  export const LISTING_TYPE = "art";
@@ -20,6 +21,10 @@ export const config = {
20
21
  // dedicated browse surface even though detailRoute points at /products.
21
22
  browseRoute: String(ROUTES.PUBLIC.ART),
22
23
  hideDefault: "sold",
24
+ // Runs out rather than ending — no time dimension, so the shared
25
+ // stock-only rules are the whole story for this type.
26
+ isAvailable: alwaysAvailable,
27
+ unavailableClauses: STOCK_ONLY_UNAVAILABLE_CLAUSES,
23
28
  sortOptions: STANDARD_SORT_OPTIONS,
24
29
  publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
25
30
  extraFacetKeys: [],
@@ -1,3 +1,4 @@
1
+ import { type AvailabilityRow } from "../availability";
1
2
  export declare const LISTING_TYPE: "auction";
2
3
  export declare const capability: import("../capabilities").ListingTypeCapability;
3
4
  export declare const config: {
@@ -17,6 +18,18 @@ export declare const config: {
17
18
  chipLabel: string;
18
19
  browseRoute: string;
19
20
  hideDefault: "ended";
21
+ isAvailable: (row: AvailabilityRow, now: Date) => boolean;
22
+ unavailableClauses: ({
23
+ field: "auctionEndDate";
24
+ op: "lt";
25
+ value: "NOW";
26
+ sortField: "auctionEndDate";
27
+ } | {
28
+ field: "isSold";
29
+ op: "eq";
30
+ value: boolean;
31
+ sortField?: undefined;
32
+ })[];
20
33
  sortOptions: readonly [{
21
34
  readonly value: string;
22
35
  readonly label: "Ending Soon";
@@ -1,6 +1,8 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
+ import { toDate } from "../availability";
2
3
  import { ROUTES } from "../../../../next/routing/route-map";
3
4
  import { TABLE_KEYS } from "../../../../constants/table-keys";
5
+ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
4
6
  import { AUCTION_SORT_OPTIONS, AUCTION_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
5
7
  export const LISTING_TYPE = "auction";
6
8
  export const capability = LISTING_TYPE_CAPABILITIES.auction;
@@ -20,6 +22,25 @@ export const config = {
20
22
  // An auction is never "sold out" — it ends. Hiding by stock would hide
21
23
  // every live auction, since auctions don't carry meaningful stock.
22
24
  hideDefault: "ended",
25
+ // There is no `auctionStatus` field — "ended" is purely the end date having
26
+ // passed. An auction with no end date at all is treated as live rather than
27
+ // hidden; a missing date is a data defect, not a signal that it is over.
28
+ isAvailable: (row, now) => {
29
+ const end = toDate(row[PRODUCT_FIELDS.AUCTION_END_DATE]);
30
+ return end === null || end > now;
31
+ },
32
+ unavailableClauses: [
33
+ // The ONLY inequality in the registry — safe because it names the sort
34
+ // field the query must order by, which is also the natural sort for an
35
+ // "ended auctions" archive ("Recently Ended").
36
+ {
37
+ field: PRODUCT_FIELDS.AUCTION_END_DATE,
38
+ op: "lt",
39
+ value: "NOW",
40
+ sortField: PRODUCT_FIELDS.AUCTION_END_DATE,
41
+ },
42
+ { field: PRODUCT_FIELDS.IS_SOLD, op: "eq", value: true },
43
+ ],
23
44
  sortOptions: AUCTION_SORT_OPTIONS,
24
45
  publicSortOptions: AUCTION_PUBLIC_SORT_OPTIONS,
25
46
  extraFacetKeys: [
@@ -0,0 +1,79 @@
1
+ import type { FirestoreValue } from "../../../schemas/types";
2
+ /**
3
+ * A product row as it reaches a predicate.
4
+ *
5
+ * Deliberately the project’s sanctioned loose Firestore-row alias rather than
6
+ * `ProductDocument`: the same row arrives here in three shapes — a Firestore
7
+ * document (Date / Timestamp fields), an already-serialised payload from the
8
+ * colocated `listingProcessor` Function (ISO strings), and JSON from
9
+ * `/api/products`. Narrowing to any one of them would just push a cast to
10
+ * every call site.
11
+ */
12
+ export type AvailabilityRow = Record<string, FirestoreValue>;
13
+ /**
14
+ * Everything a timestamp field can be by the time it reaches `toDate`. The
15
+ * `{ toDate() }` arm is a Firestore `Timestamp`, which cannot be imported
16
+ * here — this module is client-safe and firebase-admin is not.
17
+ */
18
+ export type DateLike = FirestoreValue | {
19
+ toDate: () => Date;
20
+ };
21
+ /**
22
+ * Firestore Timestamp | Date | ISO string | epoch millis → Date, or null when
23
+ * the value is absent or unparseable. Firestore hands back a `Timestamp` with
24
+ * a `toDate()` method server-side but a plain ISO string once serialised, and
25
+ * a predicate that handles only one of those silently returns the wrong answer
26
+ * on the other runtime.
27
+ */
28
+ export declare function toDate(value: DateLike): Date | null;
29
+ /** Read a numeric field, or `null` when absent/non-numeric. */
30
+ export declare function num(value: DateLike): number | null;
31
+ /**
32
+ * The checks every listing type shares, regardless of `listingType`.
33
+ *
34
+ * Note what is NOT here: `status`. Publication state (draft / in_review /
35
+ * archived) is a different axis from availability — an archived listing is
36
+ * hidden from the public entirely, while a sold one is still browsable in the
37
+ * "Sold & Ended" scope. Conflating them is what made `isValidRelatedItem`
38
+ * test for a `status: "sold"` value that has never existed.
39
+ *
40
+ * A field that is absent is treated as "not a reason to hide" — most rows
41
+ * carry `isSold: false` and a real quantity, but older documents predate both
42
+ * and must not vanish from the default scope.
43
+ */
44
+ export declare function baseAvailable(row: AvailabilityRow): boolean;
45
+ /**
46
+ * Whether a row may be shown on a public surface at all. Separate from
47
+ * availability on purpose — see `baseAvailable`.
48
+ */
49
+ export declare function isPubliclyVisible(row: AvailabilityRow): boolean;
50
+ /**
51
+ * The Firestore-expressible half of "this row is UNAVAILABLE".
52
+ *
53
+ * Every entry must be an EQUALITY, or an inequality that names its own
54
+ * `sortField` — because Firestore implicitly appends an inequality's field to
55
+ * the `orderBy`, so pairing one with an unrelated sort demands a composite
56
+ * index in an order nobody declares and fails with FAILED_PRECONDITION
57
+ * (Recurrent Root Cause #59). Equalities carry no such constraint, which is
58
+ * what makes the "Sold & Ended" fan-out safe on a mixed type selection.
59
+ */
60
+ export interface UnavailableClause {
61
+ field: string;
62
+ op: "eq" | "lt";
63
+ /** `"NOW"` is substituted with the query's timestamp at build time. */
64
+ value: string | number | boolean | "NOW";
65
+ /** Required when `op !== "eq"` — the query MUST order by this field. */
66
+ sortField?: string;
67
+ }
68
+ /**
69
+ * For types whose only end-state is running out: standard, classified, live,
70
+ * art, stickers. Shared rather than copy-pasted into five plugin configs,
71
+ * which is exactly the duplication the registry exists to prevent.
72
+ */
73
+ export declare const STOCK_ONLY_UNAVAILABLE_CLAUSES: readonly UnavailableClause[];
74
+ /**
75
+ * `isAvailable` for a type with no rule beyond `baseAvailable`. Named rather
76
+ * than an inline `() => true` so the five configs that use it read as
77
+ * deliberately sharing one decision, not as five independent stubs.
78
+ */
79
+ export declare function alwaysAvailable(): boolean;
@@ -0,0 +1,104 @@
1
+ /*
2
+ * WHY: There was no single definition of "this listing is still available".
3
+ * The closest thing was a private `isValidRelatedItem()` in
4
+ * `_internal/server/features/products/data.ts`, reachable only from the
5
+ * related-items carousel and carrying three bugs: it read
6
+ * `row.codesAvailable` at the top level when the schema nests it under
7
+ * `digitalCode.codesAvailable`; it filtered on `status` values
8
+ * ("sold", "out_of_stock", "discontinued") that are not members of the
9
+ * real `ProductStatus` union; and it had no branch for classified / live
10
+ * / art / stickers. Meanwhile the listing pages expressed the same idea a
11
+ * fourth way (an `inStock` boolean plus a `dateFrom` cutoff) and the
12
+ * homepage expressed it not at all — which is why the "Live Auctions"
13
+ * strip rendered the most-expired lots first.
14
+ *
15
+ * WHAT: The type-agnostic half of the availability predicate. The per-type
16
+ * half lives on each listing-type plugin as `isAvailable`; the two are
17
+ * combined by `isListingRowAvailable()` in `./_registry`.
18
+ *
19
+ * EXPORTS: AvailabilityRow, UnavailableClause, toDate, num, baseAvailable,
20
+ * isPubliclyVisible, alwaysAvailable, STOCK_ONLY_UNAVAILABLE_CLAUSES
21
+ *
22
+ * @tag domain:products
23
+ * @tag layer:shared
24
+ * @tag pattern:none
25
+ * @tag access:isomorphic
26
+ * @tag consumers:_registry,list-public,data,PrizeDrawsSection
27
+ * @tag sideEffects:none
28
+ */
29
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
30
+ /**
31
+ * Firestore Timestamp | Date | ISO string | epoch millis → Date, or null when
32
+ * the value is absent or unparseable. Firestore hands back a `Timestamp` with
33
+ * a `toDate()` method server-side but a plain ISO string once serialised, and
34
+ * a predicate that handles only one of those silently returns the wrong answer
35
+ * on the other runtime.
36
+ */
37
+ export function toDate(value) {
38
+ if (!value)
39
+ return null;
40
+ if (value instanceof Date)
41
+ return Number.isNaN(value.getTime()) ? null : value;
42
+ if (typeof value === "object" && typeof value.toDate === "function") {
43
+ const converted = value.toDate();
44
+ return converted instanceof Date && !Number.isNaN(converted.getTime()) ? converted : null;
45
+ }
46
+ if (typeof value === "string" || typeof value === "number") {
47
+ const parsed = new Date(value);
48
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
49
+ }
50
+ return null;
51
+ }
52
+ /** Read a numeric field, or `null` when absent/non-numeric. */
53
+ export function num(value) {
54
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
55
+ }
56
+ /**
57
+ * The checks every listing type shares, regardless of `listingType`.
58
+ *
59
+ * Note what is NOT here: `status`. Publication state (draft / in_review /
60
+ * archived) is a different axis from availability — an archived listing is
61
+ * hidden from the public entirely, while a sold one is still browsable in the
62
+ * "Sold & Ended" scope. Conflating them is what made `isValidRelatedItem`
63
+ * test for a `status: "sold"` value that has never existed.
64
+ *
65
+ * A field that is absent is treated as "not a reason to hide" — most rows
66
+ * carry `isSold: false` and a real quantity, but older documents predate both
67
+ * and must not vanish from the default scope.
68
+ */
69
+ export function baseAvailable(row) {
70
+ if (row[PRODUCT_FIELDS.IS_SOLD] === true)
71
+ return false;
72
+ const available = num(row[PRODUCT_FIELDS.AVAILABLE_QUANTITY]);
73
+ if (available !== null && available <= 0)
74
+ return false;
75
+ const stock = num(row[PRODUCT_FIELDS.STOCK_QUANTITY]);
76
+ if (stock !== null && stock <= 0)
77
+ return false;
78
+ return true;
79
+ }
80
+ /**
81
+ * Whether a row may be shown on a public surface at all. Separate from
82
+ * availability on purpose — see `baseAvailable`.
83
+ */
84
+ export function isPubliclyVisible(row) {
85
+ const status = row[PRODUCT_FIELDS.STATUS];
86
+ return status === undefined || status === PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED;
87
+ }
88
+ /**
89
+ * For types whose only end-state is running out: standard, classified, live,
90
+ * art, stickers. Shared rather than copy-pasted into five plugin configs,
91
+ * which is exactly the duplication the registry exists to prevent.
92
+ */
93
+ export const STOCK_ONLY_UNAVAILABLE_CLAUSES = [
94
+ { field: PRODUCT_FIELDS.AVAILABLE_QUANTITY, op: "eq", value: 0 },
95
+ { field: PRODUCT_FIELDS.IS_SOLD, op: "eq", value: true },
96
+ ];
97
+ /**
98
+ * `isAvailable` for a type with no rule beyond `baseAvailable`. Named rather
99
+ * than an inline `() => true` so the five configs that use it read as
100
+ * deliberately sharing one decision, not as five independent stubs.
101
+ */
102
+ export function alwaysAvailable() {
103
+ return true;
104
+ }
@@ -24,6 +24,20 @@ export interface ListingTypeCapability {
24
24
  requiresJurisdictionCheck: boolean;
25
25
  /** Listing fulfills immediately on purchase (no shipping label flow). */
26
26
  hasInstantFulfillment: boolean;
27
+ /**
28
+ * Buyers can negotiate the price via the Make-an-Offer flow.
29
+ *
30
+ * A per-listing `allowOffers` flag is still required on top of this — the
31
+ * capability says the TYPE can support offers at all, the flag says this
32
+ * particular seller opted in. `makeOffer` checks both.
33
+ *
34
+ * False wherever a negotiated price would collide with the type's own pricing
35
+ * mechanism: an auction already has bidding, a prize-draw sells fixed-price
36
+ * entries, a digital code is a fungible pool item, a pre-order is a deposit
37
+ * against a future price, and a live item is jurisdiction-gated with a
38
+ * verified vendor on the hook for it.
39
+ */
40
+ canMakeOffer: boolean;
27
41
  }
28
42
  export declare const LISTING_TYPE_CAPABILITIES: Record<ListingType, ListingTypeCapability>;
29
43
  export declare function capabilityFor(type: ListingType): ListingTypeCapability;
@@ -33,6 +47,7 @@ export declare function supportsShipping(type: ListingType): boolean;
33
47
  export declare function requiresVendorVerified(type: ListingType): boolean;
34
48
  export declare function requiresJurisdictionCheck(type: ListingType): boolean;
35
49
  export declare function hasInstantFulfillment(type: ListingType): boolean;
50
+ export declare function canMakeOffer(type: ListingType): boolean;
36
51
  /**
37
52
  * Exhaustive-switch helper. Use in `switch (type) { case ...: default: assertNever(type); }`
38
53
  * so adding a new `ListingType` member is a TS error at every consumer
@@ -18,6 +18,7 @@ export const LISTING_TYPE_CAPABILITIES = {
18
18
  requiresVendorVerified: false,
19
19
  requiresJurisdictionCheck: false,
20
20
  hasInstantFulfillment: false,
21
+ canMakeOffer: true, // the ordinary negotiable listing
21
22
  },
22
23
  auction: {
23
24
  canAddToCart: false,
@@ -26,6 +27,7 @@ export const LISTING_TYPE_CAPABILITIES = {
26
27
  requiresVendorVerified: false,
27
28
  requiresJurisdictionCheck: false,
28
29
  hasInstantFulfillment: false,
30
+ canMakeOffer: false, // bidding IS the price mechanism
29
31
  },
30
32
  "pre-order": {
31
33
  canAddToCart: true,
@@ -34,6 +36,7 @@ export const LISTING_TYPE_CAPABILITIES = {
34
36
  requiresVendorVerified: false,
35
37
  requiresJurisdictionCheck: false,
36
38
  hasInstantFulfillment: false,
39
+ canMakeOffer: false, // deposit against a future price, not a price to haggle
37
40
  },
38
41
  "prize-draw": {
39
42
  canAddToCart: true,
@@ -42,6 +45,7 @@ export const LISTING_TYPE_CAPABILITIES = {
42
45
  requiresVendorVerified: false,
43
46
  requiresJurisdictionCheck: false,
44
47
  hasInstantFulfillment: false,
48
+ canMakeOffer: false, // fixed-price entries; a discounted entry breaks the draw's economics
45
49
  },
46
50
  // SB-UNI-F 2026-05-13 — Phase 2 union extension.
47
51
  classified: {
@@ -51,6 +55,7 @@ export const LISTING_TYPE_CAPABILITIES = {
51
55
  requiresVendorVerified: false,
52
56
  requiresJurisdictionCheck: false,
53
57
  hasInstantFulfillment: false,
58
+ canMakeOffer: true, // haggling is the norm here — see the `negotiable` flag
54
59
  },
55
60
  "digital-code": {
56
61
  canAddToCart: true,
@@ -59,6 +64,7 @@ export const LISTING_TYPE_CAPABILITIES = {
59
64
  requiresVendorVerified: false,
60
65
  requiresJurisdictionCheck: false,
61
66
  hasInstantFulfillment: true, // atomic code claim at order paid
67
+ canMakeOffer: false, // fungible pool item, priced per code
62
68
  },
63
69
  live: {
64
70
  canAddToCart: true, // but jurisdictionAllowed must include buyer's state
@@ -67,6 +73,7 @@ export const LISTING_TYPE_CAPABILITIES = {
67
73
  requiresVendorVerified: true,
68
74
  requiresJurisdictionCheck: true,
69
75
  hasInstantFulfillment: false,
76
+ canMakeOffer: false, // jurisdiction-gated with a verified vendor accountable for the price
70
77
  },
71
78
  // Art/stickers session — printed-only physical goods, standard-like checkout.
72
79
  art: {
@@ -76,6 +83,7 @@ export const LISTING_TYPE_CAPABILITIES = {
76
83
  requiresVendorVerified: false,
77
84
  requiresJurisdictionCheck: false,
78
85
  hasInstantFulfillment: false,
86
+ canMakeOffer: true, // printed goods, standard-like
79
87
  },
80
88
  stickers: {
81
89
  canAddToCart: true,
@@ -84,6 +92,7 @@ export const LISTING_TYPE_CAPABILITIES = {
84
92
  requiresVendorVerified: false,
85
93
  requiresJurisdictionCheck: false,
86
94
  hasInstantFulfillment: false,
95
+ canMakeOffer: true, // printed goods, standard-like
87
96
  },
88
97
  };
89
98
  export function capabilityFor(type) {
@@ -107,6 +116,9 @@ export function requiresJurisdictionCheck(type) {
107
116
  export function hasInstantFulfillment(type) {
108
117
  return LISTING_TYPE_CAPABILITIES[type].hasInstantFulfillment;
109
118
  }
119
+ export function canMakeOffer(type) {
120
+ return LISTING_TYPE_CAPABILITIES[type].canMakeOffer;
121
+ }
110
122
  /**
111
123
  * Exhaustive-switch helper. Use in `switch (type) { case ...: default: assertNever(type); }`
112
124
  * so adding a new `ListingType` member is a TS error at every consumer
@@ -1,3 +1,4 @@
1
+ import { alwaysAvailable } from "../availability";
1
2
  export declare const LISTING_TYPE: "classified";
2
3
  export declare const capability: import("../capabilities").ListingTypeCapability;
3
4
  export declare const config: {
@@ -17,6 +18,8 @@ export declare const config: {
17
18
  chipLabel: string;
18
19
  browseRoute: string;
19
20
  hideDefault: "sold";
21
+ isAvailable: typeof alwaysAvailable;
22
+ unavailableClauses: readonly import("../availability").UnavailableClause[];
20
23
  sortOptions: readonly [{
21
24
  readonly value: string;
22
25
  readonly label: "Newest First";
@@ -1,4 +1,5 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
+ import { alwaysAvailable, STOCK_ONLY_UNAVAILABLE_CLAUSES, } from "../availability";
2
3
  import { ROUTES } from "../../../../next/routing/route-map";
3
4
  import { TABLE_KEYS } from "../../../../constants/table-keys";
4
5
  import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
@@ -18,6 +19,10 @@ export const config = {
18
19
  chipLabel: "Classifieds",
19
20
  browseRoute: String(ROUTES.PUBLIC.CLASSIFIED),
20
21
  hideDefault: "sold",
22
+ // Runs out rather than ending — no time dimension, so the shared
23
+ // stock-only rules are the whole story for this type.
24
+ isAvailable: alwaysAvailable,
25
+ unavailableClauses: STOCK_ONLY_UNAVAILABLE_CLAUSES,
21
26
  sortOptions: STANDARD_SORT_OPTIONS,
22
27
  publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
23
28
  extraFacetKeys: [
@@ -1,3 +1,4 @@
1
+ import { type AvailabilityRow } from "../availability";
1
2
  export declare const LISTING_TYPE: "digital-code";
2
3
  export declare const capability: import("../capabilities").ListingTypeCapability;
3
4
  export declare const config: {
@@ -17,6 +18,16 @@ export declare const config: {
17
18
  chipLabel: string;
18
19
  browseRoute: string;
19
20
  hideDefault: "sold";
21
+ isAvailable: (row: AvailabilityRow) => boolean;
22
+ unavailableClauses: ({
23
+ field: "digitalCode.codesAvailable";
24
+ op: "eq";
25
+ value: number;
26
+ } | {
27
+ field: "availableQuantity";
28
+ op: "eq";
29
+ value: number;
30
+ })[];
20
31
  sortOptions: readonly [{
21
32
  readonly value: string;
22
33
  readonly label: "Newest First";
@@ -1,9 +1,23 @@
1
1
  import { LISTING_TYPE_CAPABILITIES } from "../capabilities";
2
+ import { num } from "../availability";
2
3
  import { ROUTES } from "../../../../next/routing/route-map";
3
4
  import { TABLE_KEYS } from "../../../../constants/table-keys";
5
+ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
4
6
  import { STANDARD_SORT_OPTIONS, STANDARD_PUBLIC_SORT_OPTIONS, } from "../../../../features/products/constants/sieve";
5
7
  export const LISTING_TYPE = "digital-code";
6
8
  export const capability = LISTING_TYPE_CAPABILITIES["digital-code"];
9
+ /**
10
+ * Read the pool counter from either row shape. A Firestore document nests it
11
+ * under `digitalCode`; a row that has been through a Sieve projection or the
12
+ * listingProcessor Function can carry the flattened dotted key instead.
13
+ */
14
+ function readCodesAvailable(row) {
15
+ const nested = row.digitalCode;
16
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
17
+ return nested.codesAvailable;
18
+ }
19
+ return row[PRODUCT_FIELDS.DIGITAL_CODES_AVAILABLE];
20
+ }
7
21
  export const config = {
8
22
  listingType: LISTING_TYPE,
9
23
  slugPrefix: "digitalcode-",
@@ -17,9 +31,15 @@ export const config = {
17
31
  pluralLabel: "Digital Codes",
18
32
  chipLabel: "Digital Codes",
19
33
  browseRoute: String(ROUTES.PUBLIC.DIGITAL_CODES),
20
- // "Sold out" for a code pool means codesAvailable hit 0, which the
21
- // stockQuantity>0 predicate mirrors on this collection.
34
+ // "Sold out" for a code pool means codesAvailable hit 0. That was ASSUMED to
35
+ // be mirrored by stockQuantity, and it isn't — seed and production rows exist
36
+ // with a drained pool and non-zero stock. The pool count is authoritative.
22
37
  hideDefault: "sold",
38
+ isAvailable: (row) => num(readCodesAvailable(row)) !== 0,
39
+ unavailableClauses: [
40
+ { field: PRODUCT_FIELDS.DIGITAL_CODES_AVAILABLE, op: "eq", value: 0 },
41
+ { field: PRODUCT_FIELDS.AVAILABLE_QUANTITY, op: "eq", value: 0 },
42
+ ],
23
43
  sortOptions: STANDARD_SORT_OPTIONS,
24
44
  publicSortOptions: STANDARD_PUBLIC_SORT_OPTIONS,
25
45
  extraFacetKeys: [TABLE_KEYS.DELIVERY_METHOD],
@@ -1,3 +1,4 @@
1
+ import { alwaysAvailable } from "../availability";
1
2
  export declare const LISTING_TYPE: "live";
2
3
  export declare const capability: import("../capabilities").ListingTypeCapability;
3
4
  export declare const config: {
@@ -17,6 +18,8 @@ export declare const config: {
17
18
  chipLabel: string;
18
19
  browseRoute: string;
19
20
  hideDefault: "sold";
21
+ isAvailable: typeof alwaysAvailable;
22
+ unavailableClauses: readonly import("../availability").UnavailableClause[];
20
23
  sortOptions: readonly [{
21
24
  readonly value: string;
22
25
  readonly label: "Newest First";