@mohasinac/appkit 4.13.0 → 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 +2 -2
@@ -1,74 +1,71 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useTransition } from "react";
4
- import { Button, Div, Input, LoginRequiredModal, Modal, Span, Stack, Text } from "../../../ui";
4
+ import { Button, Div, LoginRequiredModal, Modal, Span, Stack, Text, } from "../../../ui";
5
+ import { Form } from "../../../ui/components/Form";
6
+ import { FieldInput } from "../../../ui/forms/FieldInput";
7
+ import { FormErrorSummary } from "../../../ui/forms/FormErrorSummary";
8
+ import { applyZodIssues } from "../../../ui/forms/FormShell";
9
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
+ import { makeOfferFormSchema } from "../../seller/schemas/offer-forms";
11
+ import { DEFAULT_MIN_OFFER_PERCENT, minOfferAmount, } from "../../../_internal/shared/features/offers/config";
5
12
  import { isAuthError } from "../../../utils/auth-error";
6
- import { formatCurrency, roundRupees } from "../../../utils/number.formatter";
13
+ import { formatCurrency } from "../../../utils/number.formatter";
7
14
  import { normalizeError } from "../../../errors/normalize";
8
15
  const CLS_SUCCESS_BOX = "rounded-xl border border-success dark:border-success bg-success-surface p-[var(--appkit-space-4)] text-left space-y-1";
9
16
  const CLS_PENDING_BOX = "rounded-xl border border-warning dark:border-warning bg-warning-surface dark:bg-warning-surface p-[var(--appkit-space-4)] text-left space-y-1";
10
17
  const CLS_PENDING_TITLE = "text-[length:var(--appkit-text-sm)] font-medium text-warning dark:text-warning";
11
18
  const CLS_PENDING_BODY = "text-[length:var(--appkit-text-xs)] text-warning dark:text-warning";
19
+ /**
20
+ * "You already have an offer on this listing" is a legitimate, expected outcome
21
+ * rather than a failure, so it gets its own panel instead of an error message —
22
+ * but the server signals it through an ordinary rejection, so it has to be
23
+ * recognised from the message. Matched on both the human string and the error
24
+ * code because `makeOffer` throws `ERROR_MESSAGES.OFFER.ACTIVE_OFFER_EXISTS`
25
+ * while `OFFER_ERROR_CODES.ALREADY_ACTIVE` is what a coded rejection carries.
26
+ */
12
27
  function isActiveOfferError(msg) {
13
28
  return msg.includes("active offer") || msg.includes("ACTIVE_OFFER");
14
29
  }
15
- export function MakeOfferButton({ productId, listedPrice, currency, minOfferPercent = 70, onMakeOffer, className = "", }) {
30
+ export function MakeOfferButton({ productId, listedPrice, currency, minOfferPercent = DEFAULT_MIN_OFFER_PERCENT, onMakeOffer, className = "", }) {
16
31
  const [state, setState] = useState("idle");
17
- const [errorMsg, setErrorMsg] = useState("");
18
32
  const [showLoginModal, setShowLoginModal] = useState(false);
19
33
  const [isPending, startTransition] = useTransition();
20
- const minOffer = roundRupees(listedPrice * (minOfferPercent / 100));
21
- const defaultOffer = roundRupees(listedPrice * 0.9);
22
- const [offerAmount, setOfferAmount] = useState(Math.max(defaultOffer, minOffer));
34
+ const fmt = (n) => (currency ? formatCurrency(n, currency) : `₹${n.toLocaleString()}`);
35
+ // The SAME floor the server enforces — shared so the input can never seed or
36
+ // accept an amount `makeOffer` would then reject.
37
+ const minOffer = minOfferAmount(listedPrice, minOfferPercent);
38
+ const schema = makeOfferFormSchema({ listedPrice, minOffer, formatAmount: fmt });
39
+ const [offerAmount, setOfferAmount] = useState(String(Math.max(minOfferAmount(listedPrice, 90), minOffer)));
23
40
  const [buyerNote, setBuyerNote] = useState("");
24
- const fmt = (n) => currency ? formatCurrency(n, currency) : `₹${n.toLocaleString()}`;
25
- function handleOpenConfirm() {
26
- setState("confirm");
27
- }
28
- function handleCancel() {
29
- setState("idle");
30
- }
31
- function handleAmountChange(raw) {
32
- const parsed = parseInt(raw.replace(/[^0-9]/g, ""), 10);
33
- if (!isNaN(parsed))
34
- setOfferAmount(parsed);
35
- }
36
- function handleOfferError(err) {
37
- if (isAuthError(err)) {
38
- setState("idle");
39
- setShowLoginModal(true);
40
- }
41
- else {
42
- const msg = err instanceof Error ? err.message : String(err);
43
- if (isActiveOfferError(msg)) {
44
- setState("pending");
45
- }
46
- else {
47
- setErrorMsg(msg || "Could not send offer. Please try again.");
48
- setState("error");
49
- }
50
- }
51
- }
52
- function handleSubmit() {
53
- if (offerAmount < minOffer) {
54
- setErrorMsg(`Minimum offer is ${fmt(minOffer)}`);
55
- setState("error");
56
- return;
57
- }
58
- if (offerAmount >= listedPrice) {
59
- setErrorMsg(`Offer must be below the listed price of ${fmt(listedPrice)}`);
60
- setState("error");
41
+ function submit(setFieldError, clearErrors) {
42
+ clearErrors();
43
+ const parsed = schema.safeParse({ offerAmount, buyerNote: buyerNote || undefined });
44
+ if (!parsed.success) {
45
+ applyZodIssues(parsed.error.issues, setFieldError);
61
46
  return;
62
47
  }
63
- setState("loading");
64
48
  startTransition(async () => {
65
49
  try {
66
- await onMakeOffer(productId, offerAmount, buyerNote || undefined);
50
+ await onMakeOffer(productId, parsed.data.offerAmount, parsed.data.buyerNote);
67
51
  setState("success");
68
52
  }
69
53
  catch (err) {
70
54
  void normalizeError(err);
71
- handleOfferError(err);
55
+ if (isAuthError(err)) {
56
+ setState("idle");
57
+ setShowLoginModal(true);
58
+ return;
59
+ }
60
+ const msg = err instanceof Error ? err.message : String(err);
61
+ if (isActiveOfferError(msg)) {
62
+ setState("pending");
63
+ return;
64
+ }
65
+ // Server-side rejections land on the amount field — it is the only
66
+ // field the server can actually be objecting to, and Rule #9.6 wants
67
+ // them inline rather than in a banner.
68
+ setFieldError("offerAmount", msg || "Could not send offer. Please try again.");
72
69
  }
73
70
  });
74
71
  }
@@ -78,6 +75,8 @@ export function MakeOfferButton({ productId, listedPrice, currency, minOfferPerc
78
75
  if (state === "pending") {
79
76
  return (_jsxs(Div, { className: `${CLS_PENDING_BOX} ${className}`, children: [_jsx(Span, { size: "lg", children: "\u23F3" }), _jsx(Text, { className: CLS_PENDING_TITLE, children: "Offer Pending" }), _jsx(Text, { className: CLS_PENDING_BODY, children: "You already have an offer on this item. Check My Offers for updates." })] }));
80
77
  }
81
- const modalOpen = state === "confirm" || state === "loading" || state === "error";
82
- return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", border: "strong", className: `w-full ${className}`, onClick: handleOpenConfirm, children: "Make Offer" }), _jsx(Modal, { isOpen: modalOpen, onClose: handleCancel, size: "md", title: "Make an offer", actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: handleCancel, disabled: isPending, children: "Cancel" }), _jsx(Button, { variant: "primary", size: "sm", onClick: handleSubmit, disabled: isPending || offerAmount < minOffer, children: isPending ? "Sending…" : `Send offer of ${fmt(offerAmount)}` })] }), children: _jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Your offer amount" }), _jsx(Input, { type: "number", value: String(offerAmount), onChange: (e) => handleAmountChange(e.target.value), min: minOffer, max: listedPrice - 1, step: 1, "aria-label": "Offer amount" }), _jsxs(Text, { size: "xs", color: "faint", children: ["Must be between ", fmt(minOffer), " and ", fmt(listedPrice - 1)] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", color: "muted", children: "Note to seller (optional)" }), _jsx(Input, { type: "text", value: buyerNote, onChange: (e) => setBuyerNote(e.target.value), placeholder: "E.g. Bundle deal, long-time fan\u2026", maxLength: 300, "aria-label": "Note to seller" })] }), _jsx(Text, { size: "xs", color: "muted", children: "The seller will accept, decline, or suggest a counter price." }), state === "error" && (_jsx(Text, { className: "text-error", size: "xs", children: errorMsg }))] }) })] }));
78
+ return (_jsxs(_Fragment, { children: [_jsx(LoginRequiredModal, { isOpen: showLoginModal, onClose: () => setShowLoginModal(false), message: "You need to be signed in to make an offer. Please log in or create an account to continue." }), _jsx(Button, { variant: "ghost", size: "md", border: "strong", className: `w-full ${className}`, onClick: () => setState("open"), children: "Make Offer" }), _jsx(Modal, { isOpen: state === "open", onClose: () => setState("idle"), size: "md", title: "Make an offer", children: _jsx(Form, { onSubmit: (e) => e.preventDefault(), children: ({ setFieldError, clearErrors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Text, { size: "xs", color: "muted", children: ["Listed at ", fmt(listedPrice), " \u00B7 Minimum offer: ", fmt(minOffer)] }), _jsx(FieldInput, { name: "offerAmount", type: "number", label: "Your offer amount", required: true, value: offerAmount, onChange: (value) => {
79
+ setOfferAmount(value);
80
+ clearErrors();
81
+ }, min: minOffer, max: listedPrice - 1, step: 1, "aria-label": "Offer amount" }), _jsxs(Text, { size: "xs", color: "faint", children: ["Must be between ", fmt(minOffer), " and ", fmt(listedPrice - 1)] }), _jsx(FieldInput, { name: "buyerNote", type: "text", label: "Note to seller (optional)", value: buyerNote, onChange: setBuyerNote, placeholder: "E.g. Bundle deal, long-time fan\u2026", maxLength: 300, "aria-label": "Note to seller" }), _jsx(Text, { size: "xs", color: "muted", children: "The seller will accept, decline, or suggest a counter price." }), _jsx(FormErrorSummary, {}), _jsxs(Stack, { gap: "xs", children: [_jsx(Button, { action: ACTIONS.PRODUCT["make-offer"], variant: "primary", size: "sm", type: "button", isLoading: isPending, disabled: isPending, onClick: () => submit(setFieldError, clearErrors), children: isPending ? "Sending…" : `Send offer of ${fmt(Number(offerAmount) || 0)}` }), _jsx(Button, { variant: "ghost", size: "sm", type: "button", onClick: () => setState("idle"), disabled: isPending, children: "Cancel" })] })] })) }) })] }));
83
82
  }
@@ -1,10 +1,26 @@
1
1
  import React from "react";
2
2
  import type { PrizeDrawItem } from "../schemas/firestore";
3
- export interface PrizeDrawCollageProps {
4
- items: PrizeDrawItem[];
3
+ /**
4
+ * The subset of a prize this collage actually renders.
5
+ *
6
+ * Declared structurally, and deliberately narrower than `PrizeDrawItem`, so a
7
+ * lottery slot can be mapped onto it without inventing the fields it does not
8
+ * have (`condition` is required on `PrizeDrawItem`; a lottery slot has no such
9
+ * concept, and faking one would be a shape lie). `PrizeDrawItem` already
10
+ * satisfies this interface, so every existing caller is unaffected.
11
+ */
12
+ export interface CollagePrizeItem {
13
+ itemNumber: number;
14
+ title: string;
15
+ images?: string[];
16
+ estimatedValue?: number;
17
+ isWon?: boolean;
18
+ }
19
+ export interface PrizeDrawCollageProps<T extends CollagePrizeItem = PrizeDrawItem> {
20
+ items: T[];
5
21
  highlightItemNumber?: number;
6
22
  /** Custom click handler — overrides the built-in lightbox. */
7
- onItemClick?: (item: PrizeDrawItem) => void;
23
+ onItemClick?: (item: T) => void;
8
24
  /** Defaults to "Won". Use for localisation. */
9
25
  wonLabel?: string;
10
26
  /**
@@ -15,5 +31,5 @@ export interface PrizeDrawCollageProps {
15
31
  */
16
32
  hideWonState?: boolean;
17
33
  }
18
- export declare function PrizeDrawCollage({ items, highlightItemNumber, onItemClick, wonLabel, hideWonState, }: PrizeDrawCollageProps): React.JSX.Element;
34
+ export declare function PrizeDrawCollage<T extends CollagePrizeItem = PrizeDrawItem>({ items, highlightItemNumber, onItemClick, wonLabel, hideWonState, }: PrizeDrawCollageProps<T>): React.JSX.Element;
19
35
  export default PrizeDrawCollage;
@@ -55,7 +55,15 @@ export function PrizeDrawCollage({ items, highlightItemNumber, onItemClick, wonL
55
55
  const cover = it.images?.[0];
56
56
  const isHighlight = highlightItemNumber === it.itemNumber;
57
57
  return (_jsxs(Button, { variant: "ghost", type: "button", onClick: makePrizeItemClickHandler(it, idx, onItemClick, setLightboxIndex), rounded: "lg", paddingX: "none", paddingY: "none", className: [
58
- "group relative overflow-hidden border bg-[var(--appkit-color-surface)] text-left transition-transform",
58
+ // `flex-col items-stretch` reaches the real children only because
59
+ // `.appkit-button__content` declares `flex-direction: inherit` /
60
+ // `align-items: inherit`. Without them the square tile and the caption
61
+ // become side-by-side row items and the tile shrinks to ~half width.
62
+ // `items-stretch` is load-bearing: the span would otherwise inherit
63
+ // `center` from `.appkit-button` and the caption would ignore `text-left`.
64
+ // Never add a bare `block` / `flex` / `hidden` utility here — see
65
+ // Root Cause #68 and scripts/audit-primitive-child-wrappers.mjs.
66
+ "group relative flex-col items-stretch overflow-hidden border bg-[var(--appkit-color-surface)] text-left transition-transform",
59
67
  "border-[var(--appkit-color-border)]",
60
68
  isHighlight
61
69
  ? "ring-2 ring-offset-2 ring-[var(--appkit-color-primary)] scale-[1.02]"
@@ -11,7 +11,9 @@ import { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
11
11
  import { ProductFilters } from "./ProductFilters";
12
12
  import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
13
13
  import { PRIZE_DRAW_SORT_OPTIONS } from "../constants/sieve";
14
- import { PRODUCT_FIELDS } from "../../../constants/field-names";
14
+ import { AVAILABILITY_VALUES } from "../../../constants/field-names";
15
+ import { AvailabilityTabs } from "./AvailabilityTabs";
16
+ const PRIZE_DRAW_LISTING_TYPES = ["prize-draw"];
15
17
  const __P = {
16
18
  p3: "p-[var(--appkit-space-3)]",
17
19
  };
@@ -28,7 +30,8 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
28
30
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
29
31
  const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
30
32
  const [filterOpen, setFilterOpen] = useState(false);
31
- const showClosed = table.get(TABLE_KEYS.SHOW_CLOSED) === "true";
33
+ const availability = table.get(TABLE_KEYS.AVAILABILITY) ||
34
+ AVAILABILITY_VALUES.AVAILABLE;
32
35
  const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
33
36
  const { categories } = useCategoryTree();
34
37
  const categoryOptions = categoriesToFacetOptions(categories);
@@ -43,11 +46,11 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
43
46
  setFilterOpen(false);
44
47
  }, [onFilterApply]);
45
48
  const resetAll = useCallback(() => {
46
- onResetAll({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_CLOSED]: "" });
49
+ onResetAll({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.AVAILABILITY]: "" });
47
50
  setSearchInput("");
48
51
  }, [onResetAll]);
49
52
  const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
50
- showClosed ||
53
+ availability !== AVAILABILITY_VALUES.AVAILABLE ||
51
54
  table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
52
55
  filterActiveCount > 0;
53
56
  const revealFilter = (table.get(TABLE_KEYS.PRIZE_REVEAL_STATUS) || undefined);
@@ -64,13 +67,14 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
64
67
  page: table.getNumber(TABLE_KEYS.PAGE, 1),
65
68
  perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
66
69
  listingType: "prize-draw",
70
+ availability,
67
71
  };
68
72
  const { products: draws, totalPages, page, isLoading } = useProducts(params, { initialData });
69
- // When no explicit reveal-status filter is set, hide closed draws client-side
70
- // as a UX default (showClosed toggle). Server handles explicit status filters.
71
- const filteredDraws = !revealFilter && !showClosed
72
- ? draws.filter((d) => d.prizeRevealStatus !== PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.CLOSED)
73
- : draws;
73
+ // Closed draws used to be filtered out of the page HERE, client-side, after
74
+ // pagination — which silently shrank the grid below the page size and left
75
+ // `totalPages` counting rows the user could never see. The availability
76
+ // scope does it in the query instead, where the count is computed.
77
+ const filteredDraws = draws;
74
78
  const commitSearch = useCallback(() => {
75
79
  table.set(TABLE_KEYS.QUERY, searchInput.trim());
76
80
  }, [searchInput, table]);
@@ -86,9 +90,7 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
86
90
  return;
87
91
  setView(v);
88
92
  table.set(TABLE_KEYS.VIEW, v);
89
- }, onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
90
- { label: "Show closed", active: showClosed, onChange: (next) => table.set(TABLE_KEYS.SHOW_CLOSED, next ? "true" : "") },
91
- ] }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No prize draws found." })) : (_jsx(Div, { className: gridClass, children: filteredDraws.map((product) => (_jsx(MarketplacePrizeDrawCard, { product: product, variant: view }, product.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsxs(Stack, { gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "prizeRevealStatusFilter", size: "xs", weight: "semibold", color: "muted", className: "block mb-1.5", children: "Reveal status" }), _jsx(Select, { id: "prizeRevealStatusFilter", options: [
93
+ }, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: PRIZE_DRAW_LISTING_TYPES }) }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), _jsx(Div, { padding: "y-lg", children: isLoading ? (_jsx(Div, { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs(Div, { className: "overflow-hidden animate-pulse", border: "subtle", rounded: "xl", children: [_jsx(Div, { className: "aspect-square", surface: "subtle" }), _jsxs(Stack, { className: `${__P.p3}`, gap: "sm", children: [_jsx(Div, { className: "h-3 w-3/4", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-3 w-1/2", surface: "subtle", rounded: "default" }), _jsx(Div, { className: "h-8", surface: "subtle", rounded: "default" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "No prize draws found." })) : (_jsx(Div, { className: gridClass, children: filteredDraws.map((product) => (_jsx(MarketplacePrizeDrawCard, { product: product, variant: view }, product.id))) })) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsxs(Stack, { gap: "md", children: [_jsxs(_Fragment, { children: [_jsx(Label, { htmlFor: "prizeRevealStatusFilter", size: "xs", weight: "semibold", color: "muted", className: "block mb-1.5", children: "Reveal status" }), _jsx(Select, { id: "prizeRevealStatusFilter", options: [
92
94
  { value: "", label: "Any" },
93
95
  { value: "pending", label: "Reveal pending" },
94
96
  { value: "open", label: "Reveal open" },
@@ -3,7 +3,7 @@ import { Container, Main, Heading, Section, Text } from "../../../ui";
3
3
  import { AdSlot } from "../../homepage/components/AdSlot";
4
4
  import { PRODUCT_FIELDS } from "../../../constants/field-names";
5
5
  import { sortBy } from "../../../constants/sort";
6
- import { listPublicProducts, parsePublicProductParams, defaultTogglesForListingTypes, } from "../../../_internal/server/features/products/list-public";
6
+ import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public";
7
7
  import { PrizeDrawsIndexListing } from "./PrizeDrawsIndexListing";
8
8
  const LISTING_TYPES = ["prize-draw"];
9
9
  const DEFAULT_PAGE_SIZE = 24;
@@ -29,7 +29,7 @@ export async function PrizeDrawsListingView({ searchParams = {}, }) {
29
29
  listingTypes: LISTING_TYPES,
30
30
  pageSize: DEFAULT_PAGE_SIZE,
31
31
  sorts: DEFAULT_SORT,
32
- ...defaultTogglesForListingTypes(LISTING_TYPES),
32
+ ...defaultAvailabilityForListingTypes(LISTING_TYPES),
33
33
  }));
34
34
  return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, className: "mb-2", color: "primary", size: "3xl", weight: "semibold", children: "Prize Draws" }), _jsx(Text, { className: "mb-6 text-[var(--appkit-color-text-muted)]", size: "sm", children: "Fair-RNG draws for sealed Pok\u00E9mon, Hot Wheels Super Treasure Hunts, Gundam kits and more. Every winner picked by crypto.randomInt \u2014 proof on GitHub." }), _jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-6" }), _jsx(PrizeDrawsIndexListing, { initialData: result ?? undefined }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] }) }) }));
35
35
  }
@@ -4,6 +4,12 @@ export interface PrizeDrawsSectionProps {
4
4
  }
5
5
  /**
6
6
  * Renders a prize-draws strip on the homepage.
7
- * W1-38 (2026-05-23): fetches active prize-draw listings from productRepository.
7
+ *
8
+ * Until 2026-08-24 this read `config.title` / `config.subtitle` and ignored
9
+ * every other field it was given — `revealStatus`, `maxItems`, `storeId`,
10
+ * `showCountdown` and `showEntriesRemaining` were all inert, with the count
11
+ * hardcoded to 8. `revealStatus` mattering most: the seeded value is `"all"`,
12
+ * which is why a closed, already-revealed draw sat on the homepage advertising
13
+ * entries nobody could buy.
8
14
  */
9
15
  export declare function PrizeDrawsSection({ config, }: PrizeDrawsSectionProps): Promise<import("react").JSX.Element | null>;
@@ -1,32 +1,37 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { normalizeError } from "../../../errors/normalize";
3
- import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit/client";
4
2
  import { sortBy } from "@mohasinac/appkit/client";
5
3
  import { Container, Div, Heading, Section, Stack, Text, } from "../../../ui";
6
4
  import { ROUTES } from "../../../next";
7
- import { productRepository } from "../repository/products.repository";
5
+ import { listPublicProducts } from "../../../_internal/server/features/products/list-public";
6
+ import { AVAILABILITY_VALUES, PRODUCT_FIELDS } from "../../../constants/field-names";
8
7
  import { InteractiveProductCard } from "./InteractiveProductCard";
9
8
  /**
10
9
  * Renders a prize-draws strip on the homepage.
11
- * W1-38 (2026-05-23): fetches active prize-draw listings from productRepository.
10
+ *
11
+ * Until 2026-08-24 this read `config.title` / `config.subtitle` and ignored
12
+ * every other field it was given — `revealStatus`, `maxItems`, `storeId`,
13
+ * `showCountdown` and `showEntriesRemaining` were all inert, with the count
14
+ * hardcoded to 8. `revealStatus` mattering most: the seeded value is `"all"`,
15
+ * which is why a closed, already-revealed draw sat on the homepage advertising
16
+ * entries nobody could buy.
12
17
  */
13
18
  export async function PrizeDrawsSection({ config, }) {
14
19
  const title = config.title ?? "Prize Draws";
15
20
  const subtitle = config.subtitle ?? "Enter for a chance to win rare collectibles";
16
- const limit = 8;
17
- let draws = [];
18
- try {
19
- const result = await productRepository.list({
20
- filters: sieveAnd(sieveFilter("listingType", SIEVE_OP.EQ, "prize-draw"), sieveFilter("status", SIEVE_OP.EQ, "published")),
21
- sorts: sortBy("createdAt", "DESC"),
22
- pageSize: limit,
23
- });
24
- draws = (result.items ?? []);
25
- }
26
- catch (_err) {
27
- void normalizeError(_err);
28
- draws = [];
29
- }
21
+ const revealStatus = config.revealStatus ?? "open";
22
+ const showAll = revealStatus === "all";
23
+ const result = await listPublicProducts({
24
+ listingTypes: [PRODUCT_FIELDS.LISTING_TYPE_VALUES.PRIZE_DRAW],
25
+ storeId: config.storeId,
26
+ // "all" means the admin deliberately wants closed draws too — an archive
27
+ // strip — so neither the status filter nor the availability scope applies.
28
+ prizeRevealStatus: showAll ? undefined : revealStatus,
29
+ availability: showAll ? AVAILABILITY_VALUES.ALL : AVAILABILITY_VALUES.AVAILABLE,
30
+ sorts: sortBy(PRODUCT_FIELDS.CREATED_AT, "DESC"),
31
+ page: 1,
32
+ pageSize: config.maxItems ?? 8,
33
+ });
34
+ const draws = (result?.items ?? []);
30
35
  if (draws.length === 0)
31
36
  return null;
32
37
  return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), _jsx(Div, { gap: "3", className: "fluid-grid-card", children: draws.map((draw) => (_jsx(InteractiveProductCard, { product: draw, href: String(ROUTES.PUBLIC.PRODUCT_DETAIL(draw.slug ?? draw.id ?? "")) }, draw.id))) })] }) }) }));
@@ -10,7 +10,10 @@ export interface ProductDetailPageViewProps {
10
10
  initialProduct?: import("../schemas/firestore").ProductDocument | null;
11
11
  /**
12
12
  * Render prop for offer UI. Receives the resolved product fields.
13
- * Only called when product.allowOffers is true and product.type is "simple".
13
+ *
14
+ * Called only when all three offer gates pass: the site-wide
15
+ * `featureFlags.offers`, the listing type's `canMakeOffer` capability, and the
16
+ * seller's own `product.allowOffers` opt-in.
14
17
  */
15
18
  renderOfferAction?: (opts: {
16
19
  productId: string;
@@ -40,6 +40,10 @@ import { RelatedItemsSection } from "./RelatedItemsSection";
40
40
  import { computeRelatedItems, getReviewPageForProduct } from "../../../_internal/server/features/products/data";
41
41
  import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
42
42
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
43
+ import { getSiteSettingsGlobal } from "../../admin/utils/getSiteSettingsGlobal";
44
+ import { canMakeOffer } from "../../../_internal/shared/listing-types/capabilities";
45
+ import { normalizeListingType } from "../utils/listing-type";
46
+ import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config";
43
47
  // ---------------------------------------------------------------------------
44
48
  // Helpers
45
49
  // ---------------------------------------------------------------------------
@@ -145,8 +149,14 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
145
149
  ? p.customSections
146
150
  : [];
147
151
  const allowOffers = p.allowOffers === true;
148
- const productType = typeof p.type === "string" ? p.type : "simple";
149
- const minOfferPercent = typeof p.minOfferPercent === "number" ? p.minOfferPercent : 70;
152
+ // Was `p.type === "simple"`. `ProductDocument` has no `type` field at all —
153
+ // the check passed only because the `?? "simple"` fallback made it vacuously
154
+ // true, so it was a gate that could never fail and could never be right.
155
+ // `listingType` is the real discriminator.
156
+ const offerableType = canMakeOffer(normalizeListingType(p));
157
+ const minOfferPercent = typeof p.minOfferPercent === "number"
158
+ ? p.minOfferPercent
159
+ : DEFAULT_MIN_OFFER_PERCENT;
150
160
  const shippingInfo = typeof p.shippingInfo === "string" ? p.shippingInfo : null;
151
161
  const returnPolicy = typeof p.returnPolicy === "string" ? p.returnPolicy : null;
152
162
  const shippingPaidBy = p.shippingPaidBy;
@@ -166,11 +176,15 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
166
176
  const storeId = typeof p.storeId === "string" ? p.storeId : null;
167
177
  // -- Fetch reviews + the shared 4-signal related-items computation + grouped listings in parallel --
168
178
  // Only page 1 of reviews is fetched here; <ReviewsListingPanel> pages the rest client-side.
169
- const [initialReviews, related, groups] = await Promise.all([
179
+ const [initialReviews, related, groups, siteSettings] = await Promise.all([
170
180
  getReviewPageForProduct(product.id),
171
181
  computeRelatedItems(product),
172
182
  getGroupsWithItemsForProduct(product.id),
183
+ getSiteSettingsGlobal().catch(() => null),
173
184
  ]);
185
+ // Site-wide kill switch. Absent/unreadable settings mean ON — a failed
186
+ // settings read must not silently remove a working feature.
187
+ const offersEnabled = siteSettings?.featureFlags?.offers !== false;
174
188
  const { relatedItems, relatedByBrand, relatedByTags, relatedByStore } = related;
175
189
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: product.id, productType: "product", snapshot: {
176
190
  title: product.title,
@@ -218,7 +232,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
218
232
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
219
233
  inStock,
220
234
  variant: "desktop",
221
- })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && productType === "simple" && price !== null && renderOfferAction?.({
235
+ })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Buy Now" : "Out of Stock" }), _jsx(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: inStock ? "Add to Cart" : "Out of Stock" }), _jsx(Button, { variant: "ghost", size: "md", className: "w-full", disabled: true, title: ACTION_NOT_WIRED, children: "\u2661 Add to Wishlist" })] })), allowOffers && offerableType && offersEnabled && price !== null && renderOfferAction?.({
222
236
  productId: product.id,
223
237
  price,
224
238
  currency,
@@ -10,6 +10,7 @@ import { ProductFeaturesSelector } from "./ProductFeaturesSelector";
10
10
  import { ImageUpload, MediaUploadField, MediaUploadList, } from "../../media";
11
11
  import { useMediaUpload } from "../../media";
12
12
  import { resolveDate } from "../../../utils/date.formatter";
13
+ import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config";
13
14
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
14
15
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags";
15
16
  import { isAuctionListing, isPreOrderListing, isPrizeDrawListing, normalizeListingType, } from "../utils/listing-type";
@@ -141,8 +142,10 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
141
142
  ? "Required for live listings — buyers must see the actual animal/plant moving before purchase."
142
143
  : t("formVideoHelper"), onAbort: onMediaAbort })), _jsx(TagInput, { label: t("formTags"), value: product.tags || [], onChange: (tags) => update({ tags }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowOffers"), checked: !!product.allowOffers, onChange: (e) => update({
143
144
  allowOffers: e.target.checked,
144
- minOfferPercent: e.target.checked ? product.minOfferPercent ?? 50 : undefined,
145
- }), disabled: isReadonly })] }), product.allowOffers && (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "info", title: t("formAllowOffersHelp"), children: t("formAllowOffersHelp") }), _jsx(FormField, { name: "minOfferPercent", label: t("formMinOfferPercent"), type: "number", value: String(product.minOfferPercent ?? ""), onChange: (value) => update({ minOfferPercent: Number(value) }), disabled: isReadonly, placeholder: "50" })] })), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionTaxGst") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "gstRate", label: t("formGstRate"), type: "select",
145
+ minOfferPercent: e.target.checked
146
+ ? product.minOfferPercent ?? DEFAULT_MIN_OFFER_PERCENT
147
+ : undefined,
148
+ }), disabled: isReadonly })] }), product.allowOffers && (_jsxs(_Fragment, { children: [_jsx(Alert, { variant: "info", title: t("formAllowOffersHelp"), children: t("formAllowOffersHelp") }), _jsx(FormField, { name: "minOfferPercent", label: t("formMinOfferPercent"), type: "number", value: String(product.minOfferPercent ?? ""), onChange: (value) => update({ minOfferPercent: Number(value) }), disabled: isReadonly, placeholder: String(DEFAULT_MIN_OFFER_PERCENT) })] })), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionTaxGst") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "gstRate", label: t("formGstRate"), type: "select",
146
149
  // Unset is semantically "exempt" (see ProductDocument.gstRate JSDoc),
147
150
  // same as 0% — default the control to 0 so the select stays a fixed
148
151
  // 5-option enum instead of adding a 6th "unset" placeholder entry.
@@ -25,6 +25,11 @@ const CLS_BADGE_NEW = "rounded-full bg-error-solid px-[var(--appkit-space-2)] py
25
25
  const CLS_BADGE_SALE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-success-on-solid shadow-sm";
26
26
  const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
27
27
  const CLS_BADGE_LIMITED = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-info-on-solid shadow-sm";
28
+ // Overlay badge sitting on the product photo, so the pairing is `-solid` +
29
+ // `-on-solid` (Root Cause #67) — never `text-white` on a status colour, which
30
+ // inverts with the theme. `warning` because success/info/error are each already
31
+ // taken by a sibling pill in this same stack.
32
+ const CLS_BADGE_OFFERABLE = "rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-warning-on-solid shadow-sm";
28
33
  const CLS_HEART_ACTIVE = "bg-[var(--appkit-color-surface-elevated)]/90 text-error hover:bg-[var(--appkit-color-surface-elevated)]";
29
34
  const CLS_HEART_IDLE = "bg-[var(--appkit-color-surface-elevated)]/90 text-[var(--appkit-color-text-muted)] hover:text-error hover:bg-[var(--appkit-color-surface-elevated)]";
30
35
  const CLS_STAR = "text-[11px] text-warning";
@@ -68,7 +73,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
68
73
  .filter(Boolean)
69
74
  .join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { weight: "bold", className: CLS_BADGE_NEW, children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { weight: "bold", className: `text-[10px] ${typeBadge.cls}`, rounded: "full", padding: "pill-xs", shadow: "sm", children: typeBadge.label })), product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: CLS_BADGE_SALE, title: product.partOfBundleTitles && product.partOfBundleTitles.length > 0
70
75
  ? `In bundle: ${product.partOfBundleTitles[0]}`
71
- : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_BADGE_TRENDING, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_BADGE_LIMITED, title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
76
+ : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_BADGE_TRENDING, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.allowOffers && (_jsx(Span, { className: CLS_BADGE_OFFERABLE, title: "The seller accepts offers on this listing", children: "Taking Offers" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_BADGE_LIMITED, title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: (e) => {
72
77
  e.stopPropagation();
73
78
  e.preventDefault();
74
79
  onAddToWishlist(product.id);
@@ -29,7 +29,9 @@ import { PRODUCT_FIELDS } from "../../../constants/field-names";
29
29
  import { useBottomActions } from "../../layout";
30
30
  import { GENERIC_PRODUCT_LISTING_TYPES, PRODUCT_TYPE_FILTER_TABS } from "../constants/listing-tabs";
31
31
  import { parseSelectedListingTypes } from "../utils/listing-type";
32
- import { commonSortOptionsFor, hideDefaultsFor } from "../../../_internal/shared/listing-types/_registry";
32
+ import { commonSortOptionsFor } from "../../../_internal/shared/listing-types/_registry";
33
+ import { AVAILABILITY_VALUES } from "../../../constants/field-names";
34
+ import { AvailabilityTabs } from "./AvailabilityTabs";
33
35
  import { TextLink } from "../../../ui";
34
36
  const __P = {
35
37
  p3: "p-[var(--appkit-space-3)]",
@@ -47,7 +49,6 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
47
49
  const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
48
50
  const [filterOpen, setFilterOpen] = useState(false);
49
51
  const [compareIds, setCompareIds] = useState([]);
50
- const showSold = table.get(TABLE_KEYS.SHOW_SOLD) === "true";
51
52
  const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
52
53
  // ── Type filter (multi-select) ───────────────────────────────────────────
53
54
  // The URL value is a pipe-joined set; empty means "every type this page
@@ -68,12 +69,11 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
68
69
  const effectiveSort = sortOptions.some((o) => o.value === sortValue)
69
70
  ? sortValue
70
71
  : DEFAULT_SORT;
71
- // Which "Show X" toggles are meaningful for the current selection. A mixed
72
- // set can need both (products sell out, auctions end).
73
- const hideDefaults = hideDefaultsFor(effectiveTypes);
74
- const showsSoldToggle = hideDefaults.includes("sold");
75
- const showsEndedToggle = hideDefaults.includes("ended") || hideDefaults.includes("closed");
76
- const showEnded = table.get(TABLE_KEYS.SHOW_ENDED) === "true";
72
+ // The availability scope. Absent means "available" — the same default
73
+ // `defaultAvailabilityForListingTypes` resolves server-side, which is what
74
+ // keeps the SSR paint and the first refetch in agreement (Root Cause #30).
75
+ const availability = table.get(TABLE_KEYS.AVAILABILITY) ||
76
+ AVAILABILITY_VALUES.AVAILABLE;
77
77
  // "Full <type> filters →" — a single selected type with a dedicated browse
78
78
  // page gets a link to it, since that page carries facets this generic one
79
79
  // can't (bid range, delivery-date window, reveal status).
@@ -96,15 +96,13 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
96
96
  onResetAll({
97
97
  [TABLE_KEYS.QUERY]: "",
98
98
  [TABLE_KEYS.SORT]: "",
99
- [TABLE_KEYS.SHOW_SOLD]: "",
100
- [TABLE_KEYS.SHOW_ENDED]: "",
99
+ [TABLE_KEYS.AVAILABILITY]: "",
101
100
  [TABLE_KEYS.LISTING_TYPE]: "",
102
101
  });
103
102
  setSearchInput("");
104
103
  }, [onResetAll]);
105
104
  const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
106
- table.get(TABLE_KEYS.SHOW_SOLD) === "true" ||
107
- table.get(TABLE_KEYS.SHOW_ENDED) === "true" ||
105
+ availability !== AVAILABILITY_VALUES.AVAILABLE ||
108
106
  !!typeParam ||
109
107
  table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
110
108
  filterActiveCount > 0;
@@ -136,16 +134,12 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
136
134
  // Pipe-joined OR-group — sievejs parses it as a same-field OR and the
137
135
  // Firebase adapter upgrades it to a `.where(…, "in", …)` query.
138
136
  listingType: effectiveTypes.join("|"),
139
- // Hide sold-out items by default, but only when a sold-out-able type is in
140
- // play — an auction has no meaningful stock, so applying it to an
141
- // auctions-only view would hide every live auction.
142
- inStock: showsSoldToggle && !showSold ? true : undefined,
143
- // Same shape for time-boxed types. `dateFrom` only takes effect when
144
- // exactly one type is selected (there is no single end-date field shared
145
- // across types), which `dateFieldFor` already enforces server-side.
146
- dateFrom: showsEndedToggle && !showEnded && selectedTypes.length === 1
147
- ? new Date().toISOString()
148
- : undefined,
137
+ // One scope for every type. The per-type meaning of "unavailable" — sold
138
+ // out, ended, closed, depleted — is resolved server-side from the listing
139
+ // -type registry, which is why this no longer needs the old
140
+ // "only when exactly one type is selected" caveat that let ended auctions
141
+ // leak into the default /products view.
142
+ availability,
149
143
  };
150
144
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
151
145
  const selection = useBulkSelection({ items: products, keyExtractor: (p) => p.id });
@@ -259,21 +253,17 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
259
253
  },
260
254
  ] } } : {});
261
255
  return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: searchPlaceholder, onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: effectiveSort, sortOptions: sortOptions, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, toggles: [
262
- // Each toggle appears only when it means something for the selected
263
- // types — "Show ended" against a Products-only view would be noise,
264
- // and "Show sold" against an Auctions-only view is meaningless.
265
- ...(showsSoldToggle
266
- ? [{ label: "Show sold", active: showSold, onChange: (next) => table.set(TABLE_KEYS.SHOW_SOLD, next ? "true" : "") }]
267
- : []),
268
- ...(showsEndedToggle
269
- ? [{ label: "Show ended", active: showEnded, onChange: (next) => table.set(TABLE_KEYS.SHOW_ENDED, next ? "true" : "") }]
270
- : []),
256
+ // "Show sold" / "Show ended" used to live here. They WIDENED the
257
+ // list rather than scoping it, so live and dead rows arrived mixed
258
+ // together and the archive was unbrowsable — replaced by the
259
+ // <AvailabilityTabs> scope bar below.
260
+ //
271
261
  // Inline quick-filter: the highest-frequency drawer-only facet promoted
272
262
  // to the sticky toolbar so it doesn't require open-drawer → check → Apply
273
263
  // → close for every toggle. The full facet set (category/price/brand/…)
274
264
  // remains in <FilterDrawer> below.
275
265
  { label: "Free shipping", active: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (next) => table.set(TABLE_KEYS.FREE_SHIPPING, next ? "true" : "") },
276
- ] }), _jsx(Div, { padding: "y-sm", children: _jsxs(Row, { justify: "between", align: "center", wrap: true, gap: "sm", children: [_jsx(FilterChipGroup, { multiple: true, label: "Type", tabs: typeTabs, value: selectedTypes.join("|"), onChange: (next) => {
266
+ ] }), _jsx(Div, { padding: "y-sm", children: _jsx(AvailabilityTabs, { types: effectiveTypes }) }), _jsx(Div, { padding: "y-sm", children: _jsxs(Row, { justify: "between", align: "center", wrap: true, gap: "sm", children: [_jsx(FilterChipGroup, { multiple: true, label: "Type", tabs: typeTabs, value: selectedTypes.join("|"), onChange: (next) => {
277
267
  // Clear `sort` alongside the type change: a sort valid for the
278
268
  // old selection (say -auctionEndDate) can be invalid for the new
279
269
  // one, and useUrlTable's single router.replace means we must set