@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
@@ -34,9 +34,9 @@ import { filterNavItems } from "./filterNavItems";
34
34
  import { useSiteSettings } from "../../../../core/hooks/useSiteSettings";
35
35
  import { useTheme } from "../../theme";
36
36
  import { useHandMode } from "../../hand-mode";
37
- import { BackgroundRenderer, Div, Nav, Span } from "../../../../ui";
37
+ import { BackgroundRenderer, Div, Li, Nav, Span } from "../../../../ui";
38
+ import { BottomNavLayout } from "../../../../features/layout/BottomNavLayout";
38
39
  import { useToast } from "../../../../ui/components/Toast";
39
- import { useVisualViewportInset } from "../../../../react/hooks/useVisualViewportInset";
40
40
  import { useSession } from "../../../../react/contexts/SessionContext";
41
41
  import { ROUTES } from "../../../../next/routing/route-map";
42
42
  /**
@@ -140,14 +140,23 @@ const MAX_BOTTOM_NAV_ITEMS = 5;
140
140
  * behind a hamburger toggle). Pattern lifted from the unused
141
141
  * `DashboardScaffold` scaffold, which has since been retired in favour of
142
142
  * wiring it directly here where the sidebars already live.
143
+ *
144
+ * Renders through the shared `<BottomNavLayout>` rather than hand-rolling its
145
+ * own `fixed bottom-0` chrome. That is not cosmetic: `BottomNavLayout` is what
146
+ * publishes `--bottom-nav-height`, which every bottom-anchored surface above it
147
+ * (the CTA tier, the pagination bar, BackToTop, the footer's clearance) offsets
148
+ * against. The hand-rolled version published nothing, so on dashboard routes
149
+ * that whole stack was positioned against the PUBLIC nav's height instead —
150
+ * which was itself a bug, since both bars were rendering on the same pixels.
151
+ * `AppLayoutShell` is now told to suppress the public one here via
152
+ * `showBottomNav={!isDashboard}`; exactly one nav must be mounted at a time.
143
153
  */
144
154
  function DashboardBottomNav({ items, activeHref }) {
145
- const isKeyboardOpen = useVisualViewportInset();
146
155
  if (items.length === 0)
147
156
  return null;
148
- return (_jsx(Nav, { "aria-label": "Dashboard bottom navigation", "aria-hidden": isKeyboardOpen, className: `fixed bottom-0 left-0 right-0 z-[var(--appkit-z-bottom-nav)] flex items-stretch justify-around border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] lg:hidden transition-transform duration-200 ease-out ${isKeyboardOpen ? "translate-y-full pointer-events-none" : "translate-y-0"}`, children: items.slice(0, MAX_BOTTOM_NAV_ITEMS).map((item) => {
157
+ return (_jsx(BottomNavLayout, { id: "dashboard-bottom-navbar", ariaLabel: "Dashboard bottom navigation", children: items.slice(0, MAX_BOTTOM_NAV_ITEMS).map((item) => {
149
158
  const isActive = activeHref === item.href || activeHref.startsWith(`${item.href}/`);
150
- return (_jsxs(Link, { href: item.href, className: `flex flex-1 flex-col items-center justify-center gap-[var(--appkit-space-0-5)] py-[var(--appkit-space-2)] ${isActive ? "text-[var(--appkit-color-primary)]" : "text-[var(--appkit-color-text-muted)]"}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && _jsx(Span, { size: "base", children: item.icon }), _jsx(Span, { size: "xs", className: "max-w-full truncate", children: item.label })] }, item.href));
159
+ return (_jsx(Li, { className: "flex-1", children: _jsxs(Link, { href: item.href, className: `flex h-full flex-col items-center justify-center gap-[var(--appkit-space-0-5)] py-[var(--appkit-space-2)] ${isActive ? "text-[var(--appkit-color-primary)]" : "text-[var(--appkit-color-text-muted)]"}`, "aria-current": isActive ? "page" : undefined, children: [item.icon && _jsx(Span, { size: "base", children: item.icon }), _jsx(Span, { size: "xs", className: "max-w-full truncate", children: item.label })] }) }, item.href));
151
160
  }) }));
152
161
  }
153
162
  // Asymmetric padding clears the persistent sidebar rail — right-heavy by
@@ -3,6 +3,8 @@ interface LotterySlotRow {
3
3
  slotNumber: number;
4
4
  name: string;
5
5
  price: number;
6
+ /** Prize photo — rendered publicly in the prize collage on the detail page. */
7
+ image?: string;
6
8
  }
7
9
  interface LotteryEventFormData {
8
10
  title: string;
@@ -12,6 +14,7 @@ interface LotteryEventFormData {
12
14
  slots: Array<{
13
15
  slotNumber: number;
14
16
  name: string;
17
+ image?: string;
15
18
  price: number;
16
19
  weight: number;
17
20
  isBooked: boolean;
@@ -37,6 +37,7 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
37
37
  slots: slots.map((s) => ({
38
38
  slotNumber: s.slotNumber,
39
39
  name: s.name,
40
+ image: s.image?.trim() || undefined,
40
41
  price: s.price,
41
42
  weight: 0,
42
43
  isBooked: false,
@@ -68,6 +69,11 @@ export function LotteryAdminEditView({ eventId, initialData, onSubmit, }) {
68
69
  header: "Prize Name",
69
70
  render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: `Prize for slot ${s.slotNumber}`, value: s.name, onChange: (e) => updateSlot(s._idx, "name", e.target.value), "aria-label": `Slot ${s.slotNumber} prize name` })),
70
71
  },
72
+ {
73
+ key: "image",
74
+ header: "Image",
75
+ render: (s) => (_jsx(Input, { type: "text", className: "w-full", placeholder: "/media/\u2026 or https://\u2026", value: s.image ?? "", onChange: (e) => updateSlot(s._idx, "image", e.target.value), "aria-label": `Slot ${s.slotNumber} prize image URL` })),
76
+ },
71
77
  ...(pricingMode === "variable"
72
78
  ? [
73
79
  {
@@ -6,7 +6,15 @@ interface ClientLotteryEvent {
6
6
  description?: string;
7
7
  status: string;
8
8
  endsAt?: string | Date | null;
9
+ /**
10
+ * Two spellings exist on EventDocument and only `coverImage.url` is ever
11
+ * populated — by seed data or the admin editor. Reading `coverImageUrl`
12
+ * alone is why this page showed the 🎰 placeholder for every lottery.
13
+ */
9
14
  coverImageUrl?: string;
15
+ coverImage?: {
16
+ url?: string;
17
+ } | null;
10
18
  lotteryConfig?: ClientLotteryConfig;
11
19
  }
12
20
  interface LotteryDetailViewProps {
@@ -6,6 +6,8 @@ import { MediaImage } from "../../../../features/media/MediaImage";
6
6
  import { ROUTES } from "../../../../next/routing/route-map";
7
7
  import { LotterySlotGrid } from "./LotterySlotGrid";
8
8
  import { LotteryPullForm } from "./LotteryPullForm";
9
+ import { PrizeDrawCollage } from "../../../../features/products/components/PrizeDrawCollage";
10
+ import { slotsToCollageItems } from "./slot-collage-items";
9
11
  /**
10
12
  * Full lottery detail page view.
11
13
  * Shows cover + description + slot grid + pull form (if active + logged in).
@@ -13,7 +15,8 @@ import { LotteryPullForm } from "./LotteryPullForm";
13
15
  export function LotteryDetailView({ event, user, currentEntry }) {
14
16
  const [pulledEntry, setPulledEntry] = useState(currentEntry?.assignedPrizeSlotNumber != null ? currentEntry : null);
15
17
  const config = event.lotteryConfig;
18
+ const prizeItems = slotsToCollageItems(config?.slots ?? []);
16
19
  const isActive = event.status === "active";
17
20
  const isEnded = event.status === "ended" || event.status === "cancelled";
18
- return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [event.coverImageUrl ? (_jsx(Div, { rounded: "2xl", className: "relative overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx(MediaImage, { src: event.coverImageUrl, alt: event.title, size: "hero" }) })) : (_jsx(Div, { rounded: "2xl", surface: "subtle", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: isActive ? "success" : isEnded ? "danger" : "warning", children: event.status }), config && (_jsxs(Badge, { variant: "info", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " slots claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: event.title }), event.description ? (_jsx(Text, { color: "muted", size: "sm", children: event.description.replace(/<[^>]+>/g, "").slice(0, 200) })) : null, event.endsAt ? (_jsxs(Text, { size: "sm", color: "muted", children: ["Ends: ", new Date(event.endsAt).toLocaleDateString("en-IN", { dateStyle: "long" })] })) : null] }), config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Participate?" }), _jsx(Text, { color: "muted", children: "Log in to submit your lottery entry." }), _jsx(Anchor, { href: String(ROUTES.AUTH.LOGIN), tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Lottery #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "event", eventId: event.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) })), isEnded && !pulledEntry && (_jsx(Stack, { gap: "sm", surface: "muted", padding: "md", rounded: "xl", children: _jsx(Text, { color: "muted", weight: "semibold", children: "This lottery has ended." }) }))] }) }) }));
21
+ return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { rounded: "2xl", className: "relative overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: event.title, size: "hero" }) })) : (_jsx(Div, { rounded: "2xl", surface: "subtle", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: isActive ? "success" : isEnded ? "danger" : "warning", children: event.status }), config && (_jsxs(Badge, { variant: "info", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " slots claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: event.title }), event.description ? (_jsx(Text, { color: "muted", size: "sm", children: event.description.replace(/<[^>]+>/g, "").slice(0, 200) })) : null, event.endsAt ? (_jsxs(Text, { size: "sm", color: "muted", children: ["Ends: ", new Date(event.endsAt).toLocaleDateString("en-IN", { dateStyle: "long" })] })) : null] }), prizeItems.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Prizes" }), _jsx(PrizeDrawCollage, { items: prizeItems, wonLabel: "Claimed" })] })) : null, config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Participate?" }), _jsx(Text, { color: "muted", children: "Log in to submit your lottery entry." }), _jsx(Anchor, { href: String(ROUTES.AUTH.LOGIN), tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Lottery #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "event", eventId: event.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) })), isEnded && !pulledEntry && (_jsx(Stack, { gap: "sm", surface: "muted", padding: "md", rounded: "xl", children: _jsx(Text, { color: "muted", weight: "semibold", children: "This lottery has ended." }) }))] }) }) }));
19
22
  }
@@ -7,7 +7,14 @@ interface ClientLotteryEvent {
7
7
  status: string;
8
8
  startsAt?: string | Date;
9
9
  endsAt?: string | Date;
10
+ /**
11
+ * Only `coverImage.url` is ever populated on EventDocument — reading
12
+ * `coverImageUrl` alone showed the 🎰 placeholder on every card.
13
+ */
10
14
  coverImageUrl?: string;
15
+ coverImage?: {
16
+ url?: string;
17
+ } | null;
11
18
  lotteryConfig?: ClientLotteryConfig;
12
19
  }
13
20
  interface LotteryListViewProps {
@@ -17,7 +17,7 @@ export function LotteryListView({ items, adminMode = false }) {
17
17
  const claimed = event.lotteryConfig?.slots.filter((s) => s.isBooked).length ?? 0;
18
18
  const total = event.lotteryConfig?.totalSlots ?? 0;
19
19
  const slotsLeft = total - claimed;
20
- return (_jsx(Anchor, { href: `/lottery/${event.id}`, tone: "brand", rounded: "2xl", className: "block focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": `${event.title} lottery`, children: _jsxs(Card, { variant: "default", padding: "none", animate: "hoverLift", className: "overflow-hidden", children: [event.coverImageUrl ? (_jsx(Div, { className: "relative aspect-video overflow-hidden", children: _jsx(MediaImage, { src: event.coverImageUrl, alt: event.title, size: "card" }) })) : (_jsx(Div, { className: "aspect-video", surface: "muted", align: "center", justify: "center", children: _jsx(Text, { size: "3xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: event.status === "active"
20
+ return (_jsx(Anchor, { href: `/lottery/${event.id}`, tone: "brand", rounded: "2xl", className: "block focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", "aria-label": `${event.title} lottery`, children: _jsxs(Card, { variant: "default", padding: "none", animate: "hoverLift", className: "overflow-hidden", children: [event.coverImageUrl || event.coverImage?.url ? (_jsx(Div, { className: "relative aspect-video overflow-hidden", children: _jsx(MediaImage, { src: event.coverImageUrl || event.coverImage?.url || "", alt: event.title, size: "card" }) })) : (_jsx(Div, { className: "aspect-video", surface: "muted", align: "center", justify: "center", children: _jsx(Text, { size: "3xl", children: "\uD83C\uDFB0" }) })), _jsxs(Stack, { gap: "xs", padding: "md", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: event.status === "active"
21
21
  ? "success"
22
22
  : event.status === "ended"
23
23
  ? "danger"
@@ -6,6 +6,8 @@ import { MediaImage } from "../../../../features/media/MediaImage";
6
6
  import { ROUTES } from "../../../../next/routing/route-map";
7
7
  import { LotterySlotGrid } from "./LotterySlotGrid";
8
8
  import { LotteryPullForm } from "./LotteryPullForm";
9
+ import { PrizeDrawCollage } from "../../../../features/products/components/PrizeDrawCollage";
10
+ import { slotsToCollageItems } from "./slot-collage-items";
9
11
  /**
10
12
  * Prize-draw product detail view for lottery mode.
11
13
  * Same flow as LotteryDetailView but sourced from a product.
@@ -15,6 +17,7 @@ export function PrizeDrawLotteryDetailView({ product, user, currentEntry, }) {
15
17
  ? currentEntry
16
18
  : null);
17
19
  const config = product.lotteryConfig;
20
+ const prizeItems = slotsToCollageItems(config?.slots ?? []);
18
21
  const isActive = product.status === "published";
19
- return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [product.mainImage ? (_jsx(Div, { rounded: "2xl", className: "relative overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "hero" }) })) : (_jsx(Div, { rounded: "2xl", surface: "muted", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDF81" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: "info", children: "Lottery Prize Draw" }), config && (_jsxs(Badge, { variant: isActive ? "success" : "secondary", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: product.title }), product.description ? (_jsx(Text, { color: "muted", children: product.description })) : null] }), config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Enter?" }), _jsx(Text, { color: "muted", children: "Log in to submit your entry." }), _jsx(Anchor, { href: String(ROUTES.AUTH.LOGIN), tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Entry #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "product", productId: product.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) }))] }) }) }));
22
+ return (_jsx(Container, { children: _jsx(Section, { children: _jsxs(Stack, { gap: "xl", children: [product.mainImage ? (_jsx(Div, { rounded: "2xl", className: "relative overflow-hidden", style: { aspectRatio: "16/9", maxHeight: "16rem" }, children: _jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "hero" }) })) : (_jsx(Div, { rounded: "2xl", surface: "muted", padding: "xl", align: "center", justify: "center", children: _jsx(Text, { size: "5xl", children: "\uD83C\uDF81" }) })), _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { gap: "xs", align: "center", wrap: true, children: [_jsx(Badge, { variant: "info", children: "Lottery Prize Draw" }), config && (_jsxs(Badge, { variant: isActive ? "success" : "secondary", children: [config.slots.filter((s) => s.isBooked).length, "/", config.totalSlots, " claimed"] }))] }), _jsx(Heading, { level: 1, size: "3xl", weight: "bold", children: product.title }), product.description ? (_jsx(Text, { color: "muted", children: product.description })) : null] }), prizeItems.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Prizes" }), _jsx(PrizeDrawCollage, { items: prizeItems, wonLabel: "Claimed" })] })) : null, config && config.slots.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Slots" }), _jsx(LotterySlotGrid, { slots: config.slots, totalSlots: config.totalSlots })] })) : null, isActive && config && (_jsx(Div, { padding: "md", rounded: "2xl", surface: "card", className: "border border-[var(--appkit-color-border)]", children: !user ? (_jsxs(Stack, { gap: "md", className: "text-center", children: [_jsx(Heading, { level: 2, size: "xl", weight: "semibold", children: "Want to Enter?" }), _jsx(Text, { color: "muted", children: "Log in to submit your entry." }), _jsx(Anchor, { href: String(ROUTES.AUTH.LOGIN), tone: "brand", children: "Log In to Enter" })] })) : pulledEntry ? (_jsxs(Stack, { gap: "sm", surface: "success-surface", padding: "md", rounded: "xl", className: "border border-success/20", children: [_jsxs(Heading, { level: 2, size: "lg", weight: "bold", className: "text-success", children: ["You're Entry #", pulledEntry.userLotteryNumber, "!"] }), _jsxs(Text, { className: "text-success", size: "sm", children: ["Assigned to Slot #", pulledEntry.assignedPrizeSlotNumber, pulledEntry.slotName ? ` — ${pulledEntry.slotName}` : "", "."] })] })) : (_jsx(LotteryPullForm, { sourceType: "product", productId: product.id, totalSlots: config.totalSlots, maxPullsPerUser: config.maxPullsPerUser, onSuccess: (res) => setPulledEntry(res) })) }))] }) }) }));
20
23
  }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Map lottery slots onto the shape `PrizeDrawCollage` renders.
3
+ *
4
+ * Shared by all three lottery surfaces (event lottery detail, prize-draw-mode
5
+ * lottery detail, and any future one) so the "which slots get a tile" rule
6
+ * cannot drift between them.
7
+ *
8
+ * Slots with no image are dropped rather than rendered as empty tiles: a
9
+ * lottery may have 200 slots and only a handful of photographed prizes, and a
10
+ * collage of placeholders communicates nothing. The numbered `LotterySlotGrid`
11
+ * below it is still the complete, authoritative slot map.
12
+ */
13
+ import type { ClientLotterySlot } from "../../../../features/lottery/types";
14
+ import type { CollagePrizeItem } from "../../../../features/products/components/PrizeDrawCollage";
15
+ export declare function slotsToCollageItems(slots: readonly ClientLotterySlot[]): CollagePrizeItem[];
@@ -0,0 +1,13 @@
1
+ export function slotsToCollageItems(slots) {
2
+ return slots
3
+ .filter((slot) => Boolean(slot.image))
4
+ .map((slot) => ({
5
+ itemNumber: slot.slotNumber,
6
+ title: slot.name,
7
+ images: slot.image ? [slot.image] : [],
8
+ // `isBooked` is the lottery's equivalent of a prize being taken. The
9
+ // collage's own label prop renames the stamp to "Claimed" at the call
10
+ // site — price and weight never reach the client at all.
11
+ isWon: slot.isBooked,
12
+ }));
13
+ }
@@ -32,9 +32,15 @@ export async function placeBidAction(input) {
32
32
  });
33
33
  // Now set this new bid as the winning bid
34
34
  await bidRepository.setWinningBid(bid.id, auctionId);
35
+ // `bidsHaveStarted` and `leadingBidderId` are set by the primary bid path
36
+ // (`features/auctions/actions/bid-actions.ts`) but were missing here, so a
37
+ // bid placed through this legacy entrypoint left both mirrors stale —
38
+ // CLAUDE.md Root Cause #42.
35
39
  const updates = {
36
40
  currentBid: amount,
37
41
  bidCount: (product.bidCount ?? 0) + 1,
42
+ leadingBidderId: user.uid,
43
+ bidsHaveStarted: true,
38
44
  };
39
45
  if (shouldAutoExtend(product)) {
40
46
  updates.auctionEndDate = computeExtendedEndDate(product);
@@ -40,6 +40,22 @@ export declare const listBundleMembers: (bundle: Pick<CategoryDocument, "bundleP
40
40
  * displayed discount.
41
41
  */
42
42
  export declare function resolveBundleOriginalTotal(productIds: string[]): Promise<number | undefined>;
43
+ /**
44
+ * Union of the member products' `categorySlugs` — the `bundleCategorySlugs`
45
+ * mirror that lets a category page scope its Bundles tab.
46
+ *
47
+ * A bundle row has no category field of its own, which is why the category page
48
+ * counted every active bundle site-wide before this existed. Maintained at the
49
+ * same call sites as `resolveBundleOriginalTotal` so the two mirrors cannot
50
+ * drift apart.
51
+ *
52
+ * Unlike the price total, a partially-resolvable member set still yields a
53
+ * useful answer: over-inclusion (a bundle appearing under one category too many)
54
+ * is a far smaller error than a bundle vanishing from the category it belongs
55
+ * to, and readers already treat an ABSENT mirror as "unscoped" rather than
56
+ * "belongs nowhere" (Root Cause #42).
57
+ */
58
+ export declare function resolveBundleCategorySlugs(productIds: string[]): Promise<string[]>;
43
59
  /**
44
60
  * List the most recent active bundles for homepage placement. Bounded to
45
61
  * `limit` (default 8) and filtered to `isActive: true` server-side.
@@ -68,6 +68,32 @@ export async function resolveBundleOriginalTotal(productIds) {
68
68
  const total = prices.reduce((sum, p) => sum + p, 0);
69
69
  return Math.round(total * 100) / 100;
70
70
  }
71
+ /**
72
+ * Union of the member products' `categorySlugs` — the `bundleCategorySlugs`
73
+ * mirror that lets a category page scope its Bundles tab.
74
+ *
75
+ * A bundle row has no category field of its own, which is why the category page
76
+ * counted every active bundle site-wide before this existed. Maintained at the
77
+ * same call sites as `resolveBundleOriginalTotal` so the two mirrors cannot
78
+ * drift apart.
79
+ *
80
+ * Unlike the price total, a partially-resolvable member set still yields a
81
+ * useful answer: over-inclusion (a bundle appearing under one category too many)
82
+ * is a far smaller error than a bundle vanishing from the category it belongs
83
+ * to, and readers already treat an ABSENT mirror as "unscoped" rather than
84
+ * "belongs nowhere" (Root Cause #42).
85
+ */
86
+ export async function resolveBundleCategorySlugs(productIds) {
87
+ if (productIds.length === 0)
88
+ return [];
89
+ const results = await Promise.all(productIds.map((id) => productRepository.findById(id).catch(() => null)));
90
+ const slugs = new Set();
91
+ for (const p of results) {
92
+ for (const slug of p?.categorySlugs ?? [])
93
+ slugs.add(slug);
94
+ }
95
+ return [...slugs];
96
+ }
71
97
  /**
72
98
  * List the most recent active bundles for homepage placement. Bounded to
73
99
  * `limit` (default 8) and filtered to `isActive: true` server-side.
@@ -1,4 +1,4 @@
1
- export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, type BundleDataOptions, } from "./data";
1
+ export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, type BundleDataOptions, } from "./data";
2
2
  export { buildBundleMetadata, type BundleMetadataOptions } from "./metadata";
3
3
  export { renderBundleOg, renderBundleOgImage, type BundleOgData, } from "./og";
4
4
  export { addBundleToCartAction } from "./actions";
@@ -1,4 +1,4 @@
1
- export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, } from "./data";
1
+ export { getBundleForDetail, listBundleMembers, listFeaturedBundles, getRelatedBundles, resolveBundleMemberIds, resolveBundleOriginalTotal, resolveBundleCategorySlugs, } from "./data";
2
2
  export { buildBundleMetadata } from "./metadata";
3
3
  // S-SBUNI-4 2026-05-13 — bundle OG renderer.
4
4
  export { renderBundleOg, renderBundleOgImage, } from "./og";
@@ -503,6 +503,9 @@ async function createOrderForGroup(group, orderType, ctx) {
503
503
  items: orderItems,
504
504
  orderType,
505
505
  offerId: firstItem.offerId ?? undefined,
506
+ // Distinguishes a Buy Now purchase from a settled auction win. Both are
507
+ // orderType "auction"; only this one gets the 24h admin-review window.
508
+ isBuyout: firstItem.isBuyout ? true : undefined,
506
509
  status: adminBypass ? OrderStatusValues.PROCESSING : OrderStatusValues.PENDING,
507
510
  paymentStatus: adminBypass ? PaymentStatusValues.PAID : PaymentStatusValues.PENDING,
508
511
  paymentMethod: adminBypass ? PaymentMethodValues.ADMIN_BYPASS : paymentMethod,
@@ -1216,6 +1219,9 @@ async function createRazorpayGroupOrder(group, orderType, ctx) {
1216
1219
  items: orderItems,
1217
1220
  orderType,
1218
1221
  offerId: firstItem.offerId ?? undefined,
1222
+ // Distinguishes a Buy Now purchase from a settled auction win. Both are
1223
+ // orderType "auction"; only this one gets the 24h admin-review window.
1224
+ isBuyout: firstItem.isBuyout ? true : undefined,
1219
1225
  status: OrderStatusValues.CONFIRMED,
1220
1226
  paymentStatus: PaymentStatusValues.PAID,
1221
1227
  paymentMethod: PaymentMethodValues.ONLINE,
@@ -11,6 +11,38 @@ import type { CartItemDocument } from "../../../../features/cart/schemas/firesto
11
11
  export declare function assertCheckoutLane(allCartItems: readonly CartItemDocument[], selectedItems: readonly CartItemDocument[]): void;
12
12
  /** Error code the client maps to a lane-aware message. */
13
13
  export declare const CHECKOUT_LANE_BLOCKED = "CHECKOUT_LANE_BLOCKED";
14
+ /** Why a buyout claim could not be converted into a sale. */
15
+ type ClaimFailure = "NOT_FOUND" | "ALREADY_SOLD" | "AUCTION_ENDED" | "LAPSED" | "OUTBID" | "WRONG_BUYER";
16
+ /**
17
+ * Convert a pending Buy Now claim into an actual sale, atomically.
18
+ *
19
+ * This is where a buyout is decided, and it is the ONLY place the auction is
20
+ * closed. `buyNowAuction` deliberately leaves the listing untouched: the auction
21
+ * keeps running, keeps taking bids, and keeps accepting other buyers' buyouts.
22
+ * That means several people can be holding a claim on the same auction at once,
23
+ * which is intentional — whoever reaches this transaction first gets the item,
24
+ * and everyone else is refused HERE, before their order is written and before
25
+ * any payment is captured.
26
+ *
27
+ * Four ways to lose, all of them checked inside the transaction against freshly
28
+ * read documents rather than against the caller's stale cart line:
29
+ * - the auction is already sold (another buyout committed first);
30
+ * - its clock ran out (settlement will award it to a real bidder — the user's
31
+ * explicit rule: an unfinished buyout at auction end fails);
32
+ * - the claim's own bid is no longer `active` (the deadline sweep cancelled it);
33
+ * - bidding passed the buyout price, so buying out is no longer the better deal.
34
+ *
35
+ * Returns a discriminated result rather than throwing out of the transaction
36
+ * body, so each of these precise, buyer-readable refusals survives as itself
37
+ * instead of being reshaped by the transaction wrapper. The caller throws.
38
+ */
39
+ export declare function claimAuctionForCheckout(item: CartItemDocument, buyerUid: string): Promise<{
40
+ ok: true;
41
+ bidAmount: number;
42
+ } | {
43
+ ok: false;
44
+ reason: ClaimFailure;
45
+ }>;
14
46
  /**
15
47
  * Re-validate every locked line immediately before the order is written.
16
48
  *
@@ -18,6 +50,11 @@ export declare const CHECKOUT_LANE_BLOCKED = "CHECKOUT_LANE_BLOCKED";
18
50
  * to the cart, but nothing re-checked at order time — so a buyer could add an
19
51
  * accepted offer, wait out the 48h window, and still be charged the locked
20
52
  * price. Throws on the first invalid line; the caller's transaction aborts.
53
+ *
54
+ * Auction lines were skipped entirely until 2026-08-24 (`if (!item.offerId)
55
+ * continue`), so a won auction's own `checkoutDeadline` had never been checked
56
+ * at order time at all — and a buyout claim, which is decided here, had nowhere
57
+ * to be decided.
21
58
  */
22
59
  export declare function assertLockedLinesStillValid(items: readonly CartItemDocument[], buyerUid: string): Promise<void>;
23
60
  /**
@@ -28,3 +65,4 @@ export declare function assertLockedLinesStillValid(items: readonly CartItemDocu
28
65
  * bookkeeping problem, refusing a completed order is a customer-facing one.
29
66
  */
30
67
  export declare function finalizeLockedLines(items: readonly CartItemDocument[], orderId: string): Promise<void>;
68
+ export {};
@@ -24,9 +24,15 @@
24
24
  import { offerRepository } from "../../../../features/seller/repository/offer.repository";
25
25
  import { bidRepository } from "../../../../repositories";
26
26
  import { OfferStatusValues } from "../../../../features/seller/schemas";
27
+ import { BID_COLLECTION } from "../../../../features/auctions/schemas/firestore";
28
+ import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firestore";
27
29
  import { ValidationError } from "../../../../errors";
30
+ import { BID_ERROR_CODES } from "../../../../errors/error-codes";
31
+ import { ERROR_MESSAGES } from "../../../../errors";
32
+ import { getAdminDb } from "../../../../providers/db-firebase";
28
33
  import { serverLogger } from "../../../../monitoring/server-logger";
29
34
  import { normalizeError } from "../../../../errors/normalize";
35
+ import { resolveDate } from "../../../../utils/date.formatter";
30
36
  import { activeLane, laneOf, laneBlockReason } from "../../../shared/checkout/lanes";
31
37
  /**
32
38
  * Refuse a checkout that isn't the cart's active lane.
@@ -49,6 +55,108 @@ export function assertCheckoutLane(allCartItems, selectedItems) {
49
55
  }
50
56
  /** Error code the client maps to a lane-aware message. */
51
57
  export const CHECKOUT_LANE_BLOCKED = "CHECKOUT_LANE_BLOCKED";
58
+ const CLAIM_FAILURE_ERRORS = {
59
+ NOT_FOUND: {
60
+ message: ERROR_MESSAGES.BID.AUCTION_NOT_FOUND,
61
+ code: BID_ERROR_CODES.PRODUCT_NOT_FOUND,
62
+ },
63
+ ALREADY_SOLD: {
64
+ message: ERROR_MESSAGES.BID.AUCTION_ALREADY_SOLD,
65
+ code: BID_ERROR_CODES.AUCTION_ALREADY_SOLD,
66
+ },
67
+ AUCTION_ENDED: {
68
+ message: ERROR_MESSAGES.BID.AUCTION_ENDED_BEFORE_CHECKOUT,
69
+ code: BID_ERROR_CODES.AUCTION_ENDED_BEFORE_CHECKOUT,
70
+ },
71
+ LAPSED: {
72
+ message: ERROR_MESSAGES.BID.BUYOUT_LAPSED,
73
+ code: BID_ERROR_CODES.BUYOUT_LAPSED,
74
+ },
75
+ OUTBID: {
76
+ message: ERROR_MESSAGES.BID.BUYOUT_OUTBID,
77
+ code: BID_ERROR_CODES.BUYOUT_OUTBID,
78
+ },
79
+ WRONG_BUYER: {
80
+ message: ERROR_MESSAGES.BID.BUYOUT_LAPSED,
81
+ code: BID_ERROR_CODES.BUYOUT_LAPSED,
82
+ },
83
+ };
84
+ /**
85
+ * Convert a pending Buy Now claim into an actual sale, atomically.
86
+ *
87
+ * This is where a buyout is decided, and it is the ONLY place the auction is
88
+ * closed. `buyNowAuction` deliberately leaves the listing untouched: the auction
89
+ * keeps running, keeps taking bids, and keeps accepting other buyers' buyouts.
90
+ * That means several people can be holding a claim on the same auction at once,
91
+ * which is intentional — whoever reaches this transaction first gets the item,
92
+ * and everyone else is refused HERE, before their order is written and before
93
+ * any payment is captured.
94
+ *
95
+ * Four ways to lose, all of them checked inside the transaction against freshly
96
+ * read documents rather than against the caller's stale cart line:
97
+ * - the auction is already sold (another buyout committed first);
98
+ * - its clock ran out (settlement will award it to a real bidder — the user's
99
+ * explicit rule: an unfinished buyout at auction end fails);
100
+ * - the claim's own bid is no longer `active` (the deadline sweep cancelled it);
101
+ * - bidding passed the buyout price, so buying out is no longer the better deal.
102
+ *
103
+ * Returns a discriminated result rather than throwing out of the transaction
104
+ * body, so each of these precise, buyer-readable refusals survives as itself
105
+ * instead of being reshaped by the transaction wrapper. The caller throws.
106
+ */
107
+ export async function claimAuctionForCheckout(item, buyerUid) {
108
+ const productId = item.auctionId ?? item.productId;
109
+ const bidId = item.bidId;
110
+ if (!bidId)
111
+ return { ok: false, reason: "NOT_FOUND" };
112
+ const db = getAdminDb();
113
+ return db.runTransaction(async (tx) => {
114
+ const productRef = db.collection(PRODUCT_COLLECTION).doc(productId);
115
+ const bidRef = db.collection(BID_COLLECTION).doc(bidId);
116
+ const [productSnap, bidSnap] = await tx.getAll(productRef, bidRef);
117
+ if (!productSnap.exists || !bidSnap.exists) {
118
+ return { ok: false, reason: "NOT_FOUND" };
119
+ }
120
+ const product = productSnap.data();
121
+ const bid = bidSnap.data();
122
+ if (bid.userId !== buyerUid)
123
+ return { ok: false, reason: "WRONG_BUYER" };
124
+ if (bid.status !== "active")
125
+ return { ok: false, reason: "LAPSED" };
126
+ if (product.isSold === true)
127
+ return { ok: false, reason: "ALREADY_SOLD" };
128
+ const endDate = product.auctionEndDate ? resolveDate(product.auctionEndDate) : null;
129
+ if (endDate && endDate.getTime() <= Date.now()) {
130
+ return { ok: false, reason: "AUCTION_ENDED" };
131
+ }
132
+ const bidAmount = bid.bidAmount;
133
+ const standingBid = typeof product.currentBid === "number" ? product.currentBid : 0;
134
+ // `currentBid` never reflects a pending buyout (see BidDocument.isBuyout),
135
+ // so this compares against real bidding only — exactly the check the buyer
136
+ // passed when they started, re-run against the price as it is right now.
137
+ if (standingBid >= bidAmount) {
138
+ return { ok: false, reason: "OUTBID" };
139
+ }
140
+ tx.update(productRef, {
141
+ isSold: true,
142
+ availableQuantity: 0,
143
+ // Ends the auction NOW. Everything above has already established that
144
+ // nobody outbid the buyout price, so no bidder loses anything they would
145
+ // otherwise have won.
146
+ auctionEndDate: new Date(),
147
+ currentBid: bidAmount,
148
+ leadingBidderId: bid.userId,
149
+ bidsHaveStarted: true,
150
+ updatedAt: new Date(),
151
+ });
152
+ tx.update(bidRef, {
153
+ status: "won",
154
+ isWinning: true,
155
+ updatedAt: new Date(),
156
+ });
157
+ return { ok: true, bidAmount };
158
+ });
159
+ }
52
160
  /**
53
161
  * Re-validate every locked line immediately before the order is written.
54
162
  *
@@ -56,10 +164,37 @@ export const CHECKOUT_LANE_BLOCKED = "CHECKOUT_LANE_BLOCKED";
56
164
  * to the cart, but nothing re-checked at order time — so a buyer could add an
57
165
  * accepted offer, wait out the 48h window, and still be charged the locked
58
166
  * price. Throws on the first invalid line; the caller's transaction aborts.
167
+ *
168
+ * Auction lines were skipped entirely until 2026-08-24 (`if (!item.offerId)
169
+ * continue`), so a won auction's own `checkoutDeadline` had never been checked
170
+ * at order time at all — and a buyout claim, which is decided here, had nowhere
171
+ * to be decided.
59
172
  */
60
173
  export async function assertLockedLinesStillValid(items, buyerUid) {
61
174
  const now = new Date();
62
175
  for (const item of items) {
176
+ // --- Auction lines: deadline for every one, plus the claim for a buyout ---
177
+ if (item.bidId || item.isAuctionWin) {
178
+ if (item.checkoutDeadline && now > resolveDate(item.checkoutDeadline)) {
179
+ throw new ValidationError(item.isBuyout
180
+ ? ERROR_MESSAGES.BID.BUYOUT_LAPSED
181
+ : "The payment window for this auction win has expired.", { code: item.isBuyout ? BID_ERROR_CODES.BUYOUT_LAPSED : undefined });
182
+ }
183
+ if (item.isBuyout) {
184
+ const claim = await claimAuctionForCheckout(item, buyerUid);
185
+ if (!claim.ok) {
186
+ const { message, code } = CLAIM_FAILURE_ERRORS[claim.reason];
187
+ serverLogger.info("claimAuctionForCheckout refused", {
188
+ productId: item.auctionId ?? item.productId,
189
+ bidId: item.bidId,
190
+ buyerUid,
191
+ reason: claim.reason,
192
+ });
193
+ throw new ValidationError(message, { code });
194
+ }
195
+ }
196
+ continue;
197
+ }
63
198
  if (!item.offerId)
64
199
  continue;
65
200
  const offer = await offerRepository.findById(item.offerId);
@@ -112,5 +247,41 @@ export async function finalizeLockedLines(items, orderId) {
112
247
  });
113
248
  }
114
249
  }
250
+ if (item.isBuyout && item.bidId) {
251
+ await closeOutBuyoutLosers(item.auctionId ?? item.productId, item.bidId);
252
+ }
253
+ }
254
+ }
255
+ /**
256
+ * A buyout ended the auction, so everyone still bidding on it lost.
257
+ *
258
+ * Deliberately AFTER the claim transaction rather than inside it: the number of
259
+ * active bids on a hot auction is unbounded, and a transaction that touches all
260
+ * of them would either blow the 500-write limit or contend with every incoming
261
+ * bid. The claim only has to be atomic about who gets the ITEM; tidying up the
262
+ * losers is bookkeeping, so it is bounded, best-effort and logged.
263
+ *
264
+ * Their bids are `lost`, not `outbid` — the auction is over, which is the same
265
+ * distinction `auctionSettlement` draws.
266
+ */
267
+ async function closeOutBuyoutLosers(productId, winningBidId) {
268
+ try {
269
+ const active = await bidRepository.getActiveByProduct(productId);
270
+ const losers = active.filter((e) => e.id !== winningBidId);
271
+ if (losers.length === 0)
272
+ return;
273
+ await bidRepository.markManyLost(losers.map((e) => e.ref));
274
+ serverLogger.info("finalizeLockedLines: buyout closed out losing bids", {
275
+ productId,
276
+ winningBidId,
277
+ losers: losers.length,
278
+ });
279
+ }
280
+ catch (err) {
281
+ void normalizeError(err);
282
+ serverLogger.error("finalizeLockedLines: buyout loser close-out failed", {
283
+ productId,
284
+ winningBidId,
285
+ });
115
286
  }
116
287
  }
@@ -1,2 +1 @@
1
- export { getHomepageInitial, getHomepageSections, getHeroCarouselSlides, } from "./data";
2
1
  export { HOMEPAGE_FEATURED_REVIEWS_LIMIT, HOMEPAGE_RECENT_BLOG_POSTS_LIMIT, HOMEPAGE_MAX_SECTIONS, } from "../../../shared/features/homepage/config";
@@ -1,2 +1,8 @@
1
- export { getHomepageInitial, getHomepageSections, getHeroCarouselSlides, } from "./data";
1
+ // `getHomepageInitial` / `getHomepageSections` / `getHeroCarouselSlides` lived
2
+ // in ./data and were deleted 2026-08-24. All three had ZERO call sites — the
3
+ // live homepage is `MarketplaceHomepageView`, which reads its repositories
4
+ // directly — and the only part worth keeping, the feature-flag gate, moved to
5
+ // `_internal/shared/features/homepage/section-gate.ts` and is now actually
6
+ // applied. The rest was a Rule #6 liability: `findActiveAuctions()` /
7
+ // `findActivePreOrders()` were unbounded `.get()` collection scans.
2
8
  export { HOMEPAGE_FEATURED_REVIEWS_LIMIT, HOMEPAGE_RECENT_BLOG_POSTS_LIMIT, HOMEPAGE_MAX_SECTIONS, } from "../../../shared/features/homepage/config";
@@ -1,8 +1,12 @@
1
1
  /** Strip price and weight from a single slot for client consumption. */
2
2
  export function toClientLotterySlot(slot) {
3
+ // Allow-list, not a spread: every field named here is a deliberate public
4
+ // exposure with a known reader (§ Public Data Projections). `image` is read
5
+ // by the prize collage on the lottery detail page.
3
6
  return {
4
7
  slotNumber: slot.slotNumber,
5
8
  name: slot.name,
9
+ image: slot.image,
6
10
  isBooked: slot.isBooked,
7
11
  bookedByUserLotteryNumber: slot.bookedByUserLotteryNumber,
8
12
  bookedByDisplayName: slot.bookedByDisplayName,