@mohasinac/appkit 4.53.0 → 4.53.2

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 (520) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/scaffolds/AppShell.js +1 -1
  3. package/dist/_internal/client/theme/ThemeProvider.js +15 -1
  4. package/dist/_internal/server/features/auctions/og.d.ts +3 -1
  5. package/dist/_internal/server/features/auctions/og.js +3 -1
  6. package/dist/_internal/server/features/bundles/actions.js +1 -1
  7. package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
  8. package/dist/_internal/server/features/business-documents/download.js +91 -0
  9. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  10. package/dist/_internal/server/features/categories/adapters.js +10 -0
  11. package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
  12. package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
  13. package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
  14. package/dist/_internal/server/features/checkout/actions.js +106 -12
  15. package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
  16. package/dist/_internal/server/features/checkout/data.d.ts +22 -0
  17. package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
  18. package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
  19. package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
  20. package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
  21. package/dist/_internal/server/features/classified/metadata.js +1 -1
  22. package/dist/_internal/server/features/classified/og.d.ts +3 -1
  23. package/dist/_internal/server/features/classified/og.js +3 -1
  24. package/dist/_internal/server/features/digital-code/metadata.js +1 -1
  25. package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
  26. package/dist/_internal/server/features/digital-code/og.js +3 -1
  27. package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
  28. package/dist/_internal/server/features/digital-code/pool.js +19 -4
  29. package/dist/_internal/server/features/live/metadata.js +1 -1
  30. package/dist/_internal/server/features/live/og.d.ts +3 -1
  31. package/dist/_internal/server/features/live/og.js +3 -1
  32. package/dist/_internal/server/features/messages/actions.js +1 -1
  33. package/dist/_internal/server/features/parts/og.js +1 -1
  34. package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
  35. package/dist/_internal/server/features/pre-orders/og.js +3 -1
  36. package/dist/_internal/server/features/price-index/market.d.ts +57 -0
  37. package/dist/_internal/server/features/price-index/market.js +120 -0
  38. package/dist/_internal/server/features/price-index/search.d.ts +18 -0
  39. package/dist/_internal/server/features/price-index/search.js +47 -7
  40. package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
  41. package/dist/_internal/server/features/prize-draws/og.js +3 -1
  42. package/dist/_internal/server/features/products/group-members.d.ts +26 -0
  43. package/dist/_internal/server/features/products/group-members.js +7 -1
  44. package/dist/_internal/server/features/products/list-public.d.ts +12 -0
  45. package/dist/_internal/server/features/products/list-public.js +10 -1
  46. package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
  47. package/dist/_internal/server/features/products/listing-tab-counts.js +128 -10
  48. package/dist/_internal/server/features/products/og.d.ts +3 -1
  49. package/dist/_internal/server/features/products/og.js +3 -1
  50. package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
  51. package/dist/_internal/server/features/search/scoped-sources.js +252 -0
  52. package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
  53. package/dist/_internal/server/features/search/search-everything.js +70 -20
  54. package/dist/_internal/server/features/seo/sitemap.js +6 -0
  55. package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
  56. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
  57. package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
  58. package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
  59. package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
  60. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
  61. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
  62. package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
  63. package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
  64. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  65. package/dist/_internal/server/features/stores/adapters.js +16 -0
  66. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  67. package/dist/_internal/server/features/stores/index.js +1 -0
  68. package/dist/_internal/server/features/stores/tabs.d.ts +30 -0
  69. package/dist/_internal/server/features/stores/tabs.js +124 -0
  70. package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
  71. package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
  72. package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
  73. package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
  74. package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
  75. package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
  76. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  77. package/dist/_internal/shared/checkout/rules/index.js +9 -0
  78. package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
  79. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
  80. package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
  81. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
  82. package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
  83. package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
  84. package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
  85. package/dist/_internal/shared/config/schema.d.ts +122 -0
  86. package/dist/_internal/shared/features/cart/schema.js +3 -2
  87. package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
  88. package/dist/_internal/shared/features/products/group-rules.js +142 -0
  89. package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
  90. package/dist/_internal/shared/listing-types/_registry.js +39 -1
  91. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  92. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  93. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  94. package/dist/_internal/shared/listing-types/auction/config.js +2 -0
  95. package/dist/_internal/shared/listing-types/availability.js +48 -0
  96. package/dist/_internal/shared/listing-types/capabilities.js +19 -0
  97. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  98. package/dist/_internal/shared/listing-types/classified/config.js +2 -0
  99. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  100. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
  101. package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
  102. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  103. package/dist/_internal/shared/listing-types/live/config.js +2 -0
  104. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  105. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  106. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  107. package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
  108. package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
  109. package/dist/_internal/shared/listing-types/spares/config.js +60 -0
  110. package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
  111. package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
  112. package/dist/_internal/shared/listing-types/spares/og.js +1 -0
  113. package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
  114. package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
  115. package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
  116. package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
  117. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  118. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  119. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  120. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  121. package/dist/_internal/shared/search/match.d.ts +11 -0
  122. package/dist/_internal/shared/search/match.js +30 -3
  123. package/dist/_internal/shared/search/to-groups.d.ts +52 -0
  124. package/dist/_internal/shared/search/to-groups.js +141 -0
  125. package/dist/client.d.ts +8 -2
  126. package/dist/client.js +4 -1
  127. package/dist/constants/api-endpoints.d.ts +46 -15
  128. package/dist/constants/api-endpoints.js +35 -5
  129. package/dist/constants/cache-invalidation.js +3 -1
  130. package/dist/constants/field-names.d.ts +6 -0
  131. package/dist/constants/field-names.js +6 -0
  132. package/dist/features/about/components/PolicyPageView.d.ts +22 -2
  133. package/dist/features/about/components/PolicyPageView.js +14 -2
  134. package/dist/features/about/components/PublicProfileView.js +2 -1
  135. package/dist/features/about/components/ShippingPolicyView.js +42 -2
  136. package/dist/features/account/components/NotificationBell.js +1 -1
  137. package/dist/features/account/hooks/useProfileStats.js +5 -1
  138. package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
  139. package/dist/features/addresses/repository/addresses.repository.js +21 -0
  140. package/dist/features/admin/actions/admin-actions.js +16 -1
  141. package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
  142. package/dist/features/admin/components/AdminNavigationView.js +1 -1
  143. package/dist/features/admin/components/AdminPageHeader.js +1 -1
  144. package/dist/features/admin/components/AdminProductEditorView.js +17 -2
  145. package/dist/features/admin/components/AdminSidebar.js +1 -1
  146. package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
  147. package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
  148. package/dist/features/admin/components/AdminSparesView.js +30 -0
  149. package/dist/features/admin/components/AdminUsersView.js +9 -7
  150. package/dist/features/admin/components/index.d.ts +2 -0
  151. package/dist/features/admin/components/index.js +1 -0
  152. package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
  153. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  154. package/dist/features/admin/constants/filter-tabs.js +1 -0
  155. package/dist/features/admin/schemas/firestore.d.ts +26 -2
  156. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -18
  157. package/dist/features/auth/repository/user.repository.d.ts +13 -0
  158. package/dist/features/auth/repository/user.repository.js +28 -0
  159. package/dist/features/auth/schemas/firestore.d.ts +2 -0
  160. package/dist/features/auth/schemas/firestore.js +2 -0
  161. package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
  162. package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
  163. package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
  164. package/dist/features/business-documents/schemas/firestore.js +75 -0
  165. package/dist/features/cart/actions/cart-actions.js +83 -6
  166. package/dist/features/cart/repository/cart.repository.js +32 -4
  167. package/dist/features/cart/schemas/firestore.d.ts +20 -0
  168. package/dist/features/cart/schemas/index.d.ts +2 -2
  169. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
  170. package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
  171. package/dist/features/categories/api/[id]/price-guidance/route.js +90 -0
  172. package/dist/features/categories/components/BrandDetailPageView.js +32 -8
  173. package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
  174. package/dist/features/categories/components/BrandDetailTabs.js +28 -3
  175. package/dist/features/categories/components/BundleCollage.js +3 -2
  176. package/dist/features/categories/components/BundleItemsPicker.js +3 -4
  177. package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
  178. package/dist/features/categories/components/CategoryDetailPageView.js +157 -20
  179. package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
  180. package/dist/features/categories/components/CategoryDetailTabs.js +41 -7
  181. package/dist/features/categories/components/CategoryGrid.js +10 -1
  182. package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
  183. package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
  184. package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
  185. package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
  186. package/dist/features/categories/constants/category-tabs.d.ts +20 -25
  187. package/dist/features/categories/constants/category-tabs.js +22 -39
  188. package/dist/features/categories/repository/categories.repository.d.ts +12 -0
  189. package/dist/features/categories/repository/categories.repository.js +20 -0
  190. package/dist/features/categories/schemas/firestore.d.ts +22 -1
  191. package/dist/features/categories/schemas/firestore.js +10 -0
  192. package/dist/features/categories/schemas/index.d.ts +6 -0
  193. package/dist/features/categories/schemas/index.js +2 -0
  194. package/dist/features/categories/types/index.d.ts +8 -0
  195. package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
  196. package/dist/features/categories/utils/taxonomy-visual.js +26 -10
  197. package/dist/features/classified/components/ClassifiedDetailPageView.js +14 -10
  198. package/dist/features/contact/components/ContactPageView.d.ts +33 -1
  199. package/dist/features/contact/components/ContactPageView.js +58 -28
  200. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +14 -10
  201. package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
  202. package/dist/features/grouped/schemas/firestore.d.ts +5 -0
  203. package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
  204. package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
  205. package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
  206. package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
  207. package/dist/features/homepage/components/HowItWorksSection.js +4 -4
  208. package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
  209. package/dist/features/homepage/components/PromoGrid.js +12 -3
  210. package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
  211. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  212. package/dist/features/homepage/components/index.d.ts +0 -20
  213. package/dist/features/homepage/components/index.js +0 -11
  214. package/dist/features/homepage/index.d.ts +0 -2
  215. package/dist/features/homepage/index.js +0 -2
  216. package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
  217. package/dist/features/homepage/lib/section-renderer.js +202 -58
  218. package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
  219. package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
  220. package/dist/features/homepage/schemas/firestore.d.ts +106 -3
  221. package/dist/features/homepage/schemas/firestore.js +32 -2
  222. package/dist/features/homepage/schemas/index.d.ts +36 -7
  223. package/dist/features/homepage/schemas/index.js +39 -27
  224. package/dist/features/homepage/server.d.ts +0 -3
  225. package/dist/features/homepage/server.js +0 -3
  226. package/dist/features/homepage/types/index.d.ts +0 -47
  227. package/dist/features/homepage/types/index.js +49 -0
  228. package/dist/features/layout/AppLayoutShell.js +1 -1
  229. package/dist/features/layout/BottomNavItem.js +1 -1
  230. package/dist/features/layout/BottomSheet.js +1 -1
  231. package/dist/features/layout/TitleBarLayout.js +1 -1
  232. package/dist/features/live/components/LiveItemDetailPageView.js +14 -10
  233. package/dist/features/media/hooks/useMedia.js +17 -0
  234. package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
  235. package/dist/features/media/repository/media-assets.repository.js +31 -0
  236. package/dist/features/media/trust-signals.d.ts +85 -0
  237. package/dist/features/media/trust-signals.js +126 -0
  238. package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
  239. package/dist/features/parts/components/PartDetailPageView.js +2 -2
  240. package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
  241. package/dist/features/parts/components/PartsIndexPageView.js +2 -2
  242. package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
  243. package/dist/features/parts/components/PartsSearchBox.js +57 -0
  244. package/dist/features/parts/schemas/firestore.d.ts +149 -3
  245. package/dist/features/parts/schemas/firestore.js +184 -0
  246. package/dist/features/payments/repository/payout.repository.d.ts +15 -0
  247. package/dist/features/payments/repository/payout.repository.js +21 -0
  248. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -18
  249. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
  250. package/dist/features/price-index/components/PriceIndexToolView.d.ts +24 -1
  251. package/dist/features/price-index/components/PriceIndexToolView.js +299 -17
  252. package/dist/features/price-index/lookup-target.d.ts +48 -0
  253. package/dist/features/price-index/lookup-target.js +107 -0
  254. package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
  255. package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
  256. package/dist/features/products/components/CompareOverlay.d.ts +20 -2
  257. package/dist/features/products/components/CompareOverlay.js +23 -3
  258. package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
  259. package/dist/features/products/components/CustomSectionTabContent.js +29 -3
  260. package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
  261. package/dist/features/products/components/GroupMemberPicker.js +46 -8
  262. package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
  263. package/dist/features/products/components/GroupSettingsPanel.js +92 -3
  264. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  265. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
  266. package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
  267. package/dist/features/products/components/ProductDetailPageView.js +81 -17
  268. package/dist/features/products/components/ProductForm.d.ts +13 -1
  269. package/dist/features/products/components/ProductForm.js +16 -2
  270. package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
  271. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  272. package/dist/features/products/components/ShowGroupSection.js +15 -2
  273. package/dist/features/products/components/SparesListView.d.ts +7 -0
  274. package/dist/features/products/components/SparesListView.js +39 -0
  275. package/dist/features/products/components/SublistingCarouselSection.js +2 -1
  276. package/dist/features/products/constants/action-defs.d.ts +1 -0
  277. package/dist/features/products/constants/action-defs.js +1 -0
  278. package/dist/features/products/repository/products.repository.d.ts +99 -0
  279. package/dist/features/products/repository/products.repository.js +240 -18
  280. package/dist/features/products/schemas/firestore.d.ts +97 -4
  281. package/dist/features/products/schemas/firestore.js +104 -0
  282. package/dist/features/products/schemas/index.d.ts +10 -19
  283. package/dist/features/products/schemas/index.js +23 -6
  284. package/dist/features/products/types/index.d.ts +45 -1
  285. package/dist/features/products/utils/group-member-image.d.ts +46 -0
  286. package/dist/features/products/utils/group-member-image.js +4 -0
  287. package/dist/features/products/utils/listing-badge-variant.js +4 -0
  288. package/dist/features/reviews/components/ReviewsList.js +1 -1
  289. package/dist/features/scams/components/ScamProfileView.js +1 -1
  290. package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
  291. package/dist/features/search/components/EntityTypeahead.js +92 -0
  292. package/dist/features/search/components/Search.d.ts +12 -1
  293. package/dist/features/search/components/Search.js +47 -3
  294. package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
  295. package/dist/features/search/components/SearchFacetRail.js +92 -0
  296. package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
  297. package/dist/features/search/components/SearchResultsClient.js +103 -0
  298. package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
  299. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  300. package/dist/features/search/components/index.d.ts +0 -6
  301. package/dist/features/search/components/index.js +0 -3
  302. package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
  303. package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
  304. package/dist/features/search/hooks/useScopedSearch.js +114 -0
  305. package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
  306. package/dist/features/search/hooks/useSiteSearch.js +149 -0
  307. package/dist/features/search/index.d.ts +0 -4
  308. package/dist/features/search/index.js +0 -4
  309. package/dist/features/search/manifest.d.ts +12 -0
  310. package/dist/features/search/manifest.js +17 -2
  311. package/dist/features/search/server.d.ts +0 -3
  312. package/dist/features/search/server.js +0 -3
  313. package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
  314. package/dist/features/seller/components/SellerListingTypeView.js +20 -0
  315. package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
  316. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  317. package/dist/features/seller/components/SellerProductShell.js +2 -1
  318. package/dist/features/seller/components/SellerProductsView.js +3 -2
  319. package/dist/features/seller/components/SellerSidebar.js +1 -1
  320. package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
  321. package/dist/features/seller/components/SellerSparesView.js +6 -0
  322. package/dist/features/seller/components/index.d.ts +2 -0
  323. package/dist/features/seller/components/index.js +1 -0
  324. package/dist/features/shell/CommandPalette.js +1 -1
  325. package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
  326. package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
  327. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  328. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
  329. package/dist/features/stores/components/StoreHeader.js +1 -2
  330. package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
  331. package/dist/features/stores/components/StoreSparesPageView.js +48 -0
  332. package/dist/features/stores/schemas/firestore.d.ts +19 -0
  333. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
  334. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
  335. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  336. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +392 -45
  337. package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
  338. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
  339. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
  340. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
  341. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
  342. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
  343. package/dist/features/tester/seed-data/authored/index.js +16 -0
  344. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
  345. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
  346. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
  347. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
  348. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
  349. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
  350. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
  351. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +384 -18
  352. package/dist/index.d.ts +17 -57
  353. package/dist/index.js +35 -96
  354. package/dist/next/routing/route-map.d.ts +42 -1
  355. package/dist/next/routing/route-map.js +45 -0
  356. package/dist/providers/db-firebase/base.repository.d.ts +22 -0
  357. package/dist/providers/db-firebase/base.repository.js +36 -0
  358. package/dist/schemas/registry.d.ts +15 -6
  359. package/dist/security/blind-index-tokens.d.ts +54 -0
  360. package/dist/security/blind-index-tokens.js +152 -0
  361. package/dist/security/index.d.ts +1 -1
  362. package/dist/security/index.js +1 -1
  363. package/dist/security/settings-encryption.d.ts +35 -0
  364. package/dist/security/settings-encryption.js +91 -0
  365. package/dist/seed/_helpers/brand-rows.js +38 -14
  366. package/dist/seed/_helpers/category-forest.js +73 -7
  367. package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
  368. package/dist/seed/_helpers/category-images-generated.js +81 -6
  369. package/dist/seed/_helpers/category-models.d.ts +1 -1
  370. package/dist/seed/_helpers/category-models.js +7 -1
  371. package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
  372. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  373. package/dist/seed/_helpers/category-parts.js +50 -2
  374. package/dist/seed/_helpers/character-features.d.ts +1 -1
  375. package/dist/seed/_helpers/character-features.js +2 -1
  376. package/dist/seed/_helpers/franchise-rows.js +10 -2
  377. package/dist/seed/_helpers/media.d.ts +47 -0
  378. package/dist/seed/_helpers/media.js +34 -0
  379. package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
  380. package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
  381. package/dist/seed/categories-seed-data.js +124 -20
  382. package/dist/seed/grouped-listings-seed-data.js +16 -0
  383. package/dist/seed/homepage-sections-seed-data.js +94 -5
  384. package/dist/seed/index.d.ts +2 -3
  385. package/dist/seed/index.js +2 -2
  386. package/dist/seed/manifest.js +3 -0
  387. package/dist/seed/media-assets-seed-data.d.ts +1 -1
  388. package/dist/seed/media-assets-seed-data.js +746 -196
  389. package/dist/seed/model-composition-seed-data.js +122 -6
  390. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  391. package/dist/seed/part-glossary-seed-data.js +37219 -463
  392. package/dist/seed/products-art-seed-data.js +7 -18
  393. package/dist/seed/products-auctions-seed-data.js +56 -47
  394. package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
  395. package/dist/seed/products-beyblade-x-seed-data.js +476 -0
  396. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  397. package/dist/seed/products-catalogue-seed-data.js +1500 -1240
  398. package/dist/seed/products-classifieds-seed-data.js +9 -27
  399. package/dist/seed/products-digital-codes-seed-data.js +9 -18
  400. package/dist/seed/products-live-items-seed-data.js +5 -13
  401. package/dist/seed/products-preorders-seed-data.js +67 -33
  402. package/dist/seed/products-prize-draws-seed-data.js +8 -17
  403. package/dist/seed/products-spares-seed-data.d.ts +2 -0
  404. package/dist/seed/products-spares-seed-data.js +368 -0
  405. package/dist/seed/products-standard-seed-data.js +39 -126
  406. package/dist/seed/products-stickers-seed-data.js +59 -15
  407. package/dist/seed/site-settings-seed-data.js +8 -0
  408. package/dist/seo/json-ld.d.ts +25 -0
  409. package/dist/seo/json-ld.js +28 -0
  410. package/dist/server-entry.d.ts +3 -1
  411. package/dist/server-entry.js +2 -2
  412. package/dist/server.d.ts +11 -9
  413. package/dist/server.js +35 -25
  414. package/dist/styles.css +202 -1
  415. package/dist/tailwind-input.css +31 -17
  416. package/dist/tailwind-utilities.css +1 -1
  417. package/dist/ui/components/Accordion.js +1 -1
  418. package/dist/ui/components/Breadcrumb.js +1 -1
  419. package/dist/ui/components/DetailPageGallery.js +0 -2
  420. package/dist/ui/components/DetailPageHero.js +0 -1
  421. package/dist/ui/components/DetailPageTabs.js +1 -1
  422. package/dist/ui/components/Divider.js +3 -3
  423. package/dist/ui/components/GatedPrice.js +5 -1
  424. package/dist/ui/components/HorizontalScroller.js +1 -1
  425. package/dist/ui/components/ListingLayout.js +3 -3
  426. package/dist/ui/components/MediaTabs.style.css +81 -0
  427. package/dist/ui/components/Pagination.js +1 -1
  428. package/dist/ui/components/PendingValue.d.ts +45 -0
  429. package/dist/ui/components/PendingValue.js +7 -0
  430. package/dist/ui/components/Radio.style.css +119 -0
  431. package/dist/ui/components/RatingDisplay.js +2 -2
  432. package/dist/ui/components/SettingsSection.js +0 -2
  433. package/dist/ui/components/SiteLogo.js +3 -0
  434. package/dist/ui/components/SiteMark.js +3 -0
  435. package/dist/ui/components/SortDropdown.js +1 -1
  436. package/dist/ui/components/SpecTable.d.ts +58 -0
  437. package/dist/ui/components/SpecTable.js +8 -0
  438. package/dist/ui/components/StarRating.js +1 -3
  439. package/dist/ui/components/SummaryCard.js +1 -1
  440. package/dist/ui/components/index.style.css +1 -0
  441. package/dist/ui/icons/icon-registry.d.ts +1 -0
  442. package/dist/ui/icons/icon-registry.js +9 -1
  443. package/dist/ui/index.d.ts +4 -0
  444. package/dist/ui/index.js +2 -0
  445. package/dist/utils/search-txt-query.d.ts +12 -0
  446. package/dist/utils/search-txt-query.js +12 -0
  447. package/package.json +1 -1
  448. package/scripts/dedupe-peer-deps.mjs +18 -0
  449. package/scripts/seed-cli.mjs +250 -35
  450. package/scripts/verify-css-build.mjs +118 -37
  451. package/dist/_internal/server/features/search/actions.d.ts +0 -7
  452. package/dist/_internal/server/features/search/actions.js +0 -21
  453. package/dist/_internal/server/features/search/data.d.ts +0 -15
  454. package/dist/_internal/server/features/search/data.js +0 -18
  455. package/dist/_internal/server/features/search/index.d.ts +0 -3
  456. package/dist/_internal/server/features/search/index.js +0 -2
  457. package/dist/features/homepage/api/[id]/route.d.ts +0 -23
  458. package/dist/features/homepage/api/[id]/route.js +0 -103
  459. package/dist/features/homepage/api/route.d.ts +0 -14
  460. package/dist/features/homepage/api/route.js +0 -140
  461. package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
  462. package/dist/features/homepage/api/sections/[id]/route.js +0 -95
  463. package/dist/features/homepage/api/sections/route.d.ts +0 -13
  464. package/dist/features/homepage/api/sections/route.js +0 -82
  465. package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
  466. package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
  467. package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
  468. package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
  469. package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
  470. package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
  471. package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
  472. package/dist/features/homepage/components/HeroBanner.js +0 -43
  473. package/dist/features/homepage/components/HeroSection.d.ts +0 -7
  474. package/dist/features/homepage/components/HeroSection.js +0 -6
  475. package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
  476. package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
  477. package/dist/features/homepage/components/HomepageView.d.ts +0 -25
  478. package/dist/features/homepage/components/HomepageView.js +0 -5
  479. package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
  480. package/dist/features/homepage/components/NewsletterBanner.js +0 -37
  481. package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
  482. package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
  483. package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
  484. package/dist/features/homepage/components/TrustBadges.js +0 -46
  485. package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
  486. package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
  487. package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
  488. package/dist/features/homepage/hooks/useHomepage.js +0 -17
  489. package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
  490. package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
  491. package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
  492. package/dist/features/homepage/repository/homepage.repository.js +0 -25
  493. package/dist/features/search/actions/index.d.ts +0 -1
  494. package/dist/features/search/actions/index.js +0 -1
  495. package/dist/features/search/actions/search-actions.d.ts +0 -18
  496. package/dist/features/search/actions/search-actions.js +0 -43
  497. package/dist/features/search/api/route.d.ts +0 -22
  498. package/dist/features/search/api/route.js +0 -128
  499. package/dist/features/search/columns/index.d.ts +0 -17
  500. package/dist/features/search/columns/index.js +0 -49
  501. package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
  502. package/dist/features/search/components/SearchFiltersRow.js +0 -21
  503. package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
  504. package/dist/features/search/components/SearchResultsSection.js +0 -35
  505. package/dist/features/search/components/SearchView.d.ts +0 -27
  506. package/dist/features/search/components/SearchView.js +0 -8
  507. package/dist/features/search/hooks/useSearch.d.ts +0 -21
  508. package/dist/features/search/hooks/useSearch.js +0 -33
  509. package/dist/features/search/repository/search.repository.d.ts +0 -14
  510. package/dist/features/search/repository/search.repository.js +0 -59
  511. package/dist/features/search/schemas/index.d.ts +0 -70
  512. package/dist/features/search/schemas/index.js +0 -36
  513. package/dist/features/search/types/index.d.ts +0 -48
  514. package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
  515. package/dist/features/stores/components/StoreScopedSearch.js +0 -27
  516. package/dist/seed/defaults/homepage-sections.d.ts +0 -2
  517. package/dist/seed/defaults/homepage-sections.js +0 -60
  518. package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
  519. package/dist/seed/factories/homepage-section.factory.js +0 -13
  520. /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
@@ -1,8 +1,11 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import { useRouter } from "next/navigation.js";
5
- import { Anchor, Button, GatedPrice, Stack, Text, LoginRequiredModal } from "../../../ui/index.js";
5
+ import { Anchor, Button, GatedPrice, QuantityStepper, Row, Stack, Text, LoginRequiredModal, } from "../../../ui/index.js";
6
+ import { useAddToCart } from "../../cart/hooks/useAddToCart.js";
7
+ import { useToast } from "../../../ui/components/Toast.js";
8
+ import { normalizeError } from "../../../errors/normalize.js";
6
9
  import { ROUTES } from "../../../next/routing/route-map.js";
7
10
  import { formatCurrency } from "../../../utils/number.formatter.js";
8
11
  import { NonRefundableConsentModal } from "./NonRefundableConsentModal.js";
@@ -13,23 +16,82 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
13
16
  * Client buy panel for a prize-draw detail page (SB4-G).
14
17
  *
15
18
  * - Enter Draw → NonRefundableConsentModal (listingType="prize-draw")
16
- * - On confirm → add 1 entry to guest cart and route to /user/cart
19
+ * - On confirm → add the chosen number of entries to the cart, then /user/cart
17
20
  * - "View RNG source" link → prizeGithubFileUrl
21
+ *
22
+ * 🛑 THIS FLOW DID NOT WORK AT ALL UNTIL 2026-10-19, and the docstring above
23
+ * described the behaviour it was supposed to have. `handleConfirm` pushed to
24
+ * `/checkout?directItem=<slug>&type=prize-draw` — and NOTHING anywhere reads
25
+ * `directItem` (CheckoutRouteClient reads only `coupon` and `lane`), and
26
+ * nothing added to the cart. A buyer pressing "Enter draw" landed on a
27
+ * checkout of whatever was already in their basket, with no entry in it.
28
+ * Buying an entry was impossible.
29
+ *
30
+ * 🛑 MULTI-ENTRY NEEDED NO SERVER WORK. `prizeDrawRule.splitMultipleOrders`
31
+ * already chunks a quantity into batches of PRIZE_DRAW_MAX_REVEALS_PER_ORDER
32
+ * (7 entries → [3,3,1]), `enforceMaxPerUserForCart` already caps per buyer, and
33
+ * the rule's preflight already checks `prizeMaxEntries`. The whole server half
34
+ * was written and unreachable because the client never sent a quantity.
18
35
  */
19
36
  export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
20
37
  const router = useRouter();
21
38
  const { requireAuth, isAuthResolving, modalOpen, modalMessage, closeModal } = useAuthGate();
22
39
  const [consentOpen, setConsentOpen] = useState(false);
40
+ const [quantity, setQuantity] = useState(1);
41
+ const { showToast } = useToast();
42
+ const { mutate: addToCart, isLoading: adding } = useAddToCart();
23
43
  const closed = revealStatus === "closed" || remainingEntries === 0;
44
+ /*
45
+ * Capped by what is LEFT, not by maxPerUser.
46
+ *
47
+ * `remainingEntries` is a fact about the draw and is already on screen.
48
+ * `maxPerUser` is an async per-buyer check (`enforceMaxPerUserForCart` counts
49
+ * their existing orders), so mirroring it here would mean a second, slower
50
+ * source of truth that disagrees with the server whenever an order is in
51
+ * flight — and the server's refusal already names the cap.
52
+ */
53
+ const maxQuantity = Math.max(1, Math.min(99, remainingEntries || 1));
24
54
  const handleEnter = useCallback(() => {
25
55
  if (closed)
26
56
  return;
27
57
  requireAuth(ACTION_ID.ENTER_PRIZE_DRAW, () => setConsentOpen(true));
28
58
  }, [closed, requireAuth]);
29
- const handleConfirm = useCallback(() => {
59
+ const handleConfirm = useCallback(async () => {
30
60
  setConsentOpen(false);
31
- const slug = productSlug ?? productId;
32
- router.push(`${String(ROUTES.USER.CHECKOUT)}?directItem=${encodeURIComponent(slug)}&type=prize-draw`);
33
- }, [productSlug, productId, router]);
34
- return (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsxs(GatedPrice, { label: "Sign in to see the entry price", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }) }), _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed || isAuthResolving, onClick: handleEnter, children: closed ? "Draw closed" : isAuthResolving ? "Checking…" : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: handleConfirm }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
61
+ /*
62
+ * ADD, then route. The previous version only routed — and to a parameter
63
+ * nobody reads. `useAddToCart` falls back to the guest cart on a 401, so
64
+ * this works signed-out too and the entry survives the login merge.
65
+ */
66
+ try {
67
+ await addToCart({
68
+ productId,
69
+ quantity,
70
+ productTitle: title,
71
+ price: pricePerEntry,
72
+ });
73
+ }
74
+ catch (err) {
75
+ const n = normalizeError(err);
76
+ /*
77
+ * 🛑 Surfaced, and the ROUTE IS SKIPPED. The add can legitimately fail —
78
+ * the draw sold out between render and confirm, or this buyer is over
79
+ * `maxPerUser`, which is an async per-buyer check the stepper cannot
80
+ * mirror. Routing to the cart anyway would show an empty basket and make
81
+ * a correct refusal look like the same silent nothing this flow did
82
+ * before it was repaired.
83
+ */
84
+ showToast(n.message, "error");
85
+ return;
86
+ }
87
+ router.push(String(ROUTES.USER.CART));
88
+ }, [addToCart, productId, quantity, title, pricePerEntry, router, showToast]);
89
+ const totalLabel = useMemo(() => formatCurrency(pricePerEntry * quantity, currency), [pricePerEntry, quantity, currency]);
90
+ return (_jsxs(Stack, { gap: "md", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsxs(GatedPrice, { label: "Sign in to see the entry price", children: [formatCurrency(pricePerEntry, currency), _jsx(Text, { as: "span", className: "ml-1 text-[var(--appkit-color-text-muted)]", size: "xs", weight: "normal", children: "per entry" })] }) }), !closed ? (_jsxs(Row, { align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsx(Text, { size: "sm", color: "muted", children: "Entries" }), _jsx(QuantityStepper, { value: quantity, onChange: setQuantity, min: 1, max: maxQuantity })] })) : null, !closed && quantity > 1 ? (_jsx(Text, { size: "sm", color: "muted", children: _jsxs(GatedPrice, { label: "Sign in to see the total", children: [quantity, " entries \u00B7 ", totalLabel] }) })) : null, _jsx(Button, { action: closed ? undefined : ACTIONS.PRIZE_DRAW["buy-now"], variant: "primary", size: "md", className: "w-full", disabled: closed || isAuthResolving || adding, onClick: handleEnter, children: closed
91
+ ? "Draw closed"
92
+ : isAuthResolving
93
+ ? "Checking…"
94
+ : adding
95
+ ? "Adding…"
96
+ : undefined }), prizeGithubFileUrl ? (_jsx(Anchor, { href: prizeGithubFileUrl, tone: "none", underline: "hover", size: "xs", weight: "medium", className: "text-left text-primary-600 underline-offset-4 dark:text-primary-400", children: "View RNG source code on GitHub \u2192" })) : null, _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", align: "start", children: "Winners chosen by Node.js crypto.randomInt \u2014 fully auditable. Entries are locked once paid; refunds only if the prize pool is exhausted." }), _jsx(NonRefundableConsentModal, { open: consentOpen, listingType: "prize-draw", itemTitle: title, priceLabel: formatCurrency(pricePerEntry, currency), onCancel: () => setConsentOpen(false), onConfirm: () => void handleConfirm() }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
35
97
  }
@@ -24,11 +24,14 @@ const CLS_BUNDLE_ROW = "flex items-center justify-between gap-[var(--appkit-spac
24
24
  const CLS_BUNDLE_LABEL = "text-[length:var(--appkit-text-xs)] text-success dark:text-success font-medium uppercase tracking-wide mb-0.5";
25
25
  const CLS_BUNDLE_CTA = "flex-shrink-0 rounded-lg bg-success-solid hover:brightness-110 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-success-on-solid transition-colors";
26
26
  import { ROUTES } from "../../../next/routing/route-map.js";
27
+ import { productImageUrls } from "../schemas/firestore.js";
28
+ import { PART_SLOT_LABEL } from "../../parts/schemas/firestore.js";
29
+ import { getModelsUsingPart } from "../../../_internal/server/features/parts/data.js";
27
30
  import { ctaAnchorProps } from "../../layout/cta-anchor.js";
28
31
  import { ContextualTrail } from "../../categories/components/ContextualTrail.js";
29
32
  import { productCrumbs, withTrailHead } from "../../categories/utils/breadcrumb-chain.js";
30
33
  import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
31
- import { Button, Container, Div, Heading, Main, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, Dl, Dt, Dd, Icon, } from "../../../ui/index.js";
34
+ import { Button, Container, Div, Heading, Main, PriceDisplay, RichText, Row, Section, Span, Stack, Text, Ul, Ol, Li, SpecTable, Icon, } from "../../../ui/index.js";
32
35
  import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
33
36
  import { safeDisplayName } from "../../../security/index.js";
34
37
  import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel.js";
@@ -39,7 +42,7 @@ import { ProductFeatureBadges } from "./ProductFeatureBadges.js";
39
42
  import { isFinalSale } from "../constants/final-sale.js";
40
43
  import { FeatureBadgeList } from "./FeatureBadge.js";
41
44
  import { ShareButton } from "./ShareButton.js";
42
- import { CustomSectionTabContent } from "./CustomSectionTabContent.js";
45
+ import { toCustomSectionTabs } from "./CustomSectionTabContent.js";
43
46
  import { SublistingCarouselSection } from "./SublistingCarouselSection.js";
44
47
  import { ShowGroupSection } from "./ShowGroupSection.js";
45
48
  import { HistoryTracker } from "../../history/components/HistoryTracker.js";
@@ -89,12 +92,56 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
89
92
  }
90
93
  const p = product;
91
94
  const currency = p.currency || getDefaultCurrency();
95
+ /*
96
+ * The REVERSE link: a spare names a glossary entry, so the glossary entry's
97
+ * page should name it back. Fetched only when both conditions hold — most
98
+ * listings carry no `partGlossaryId` at all, and this must not become an
99
+ * extra read on every product page in the catalogue.
100
+ *
101
+ * `getModelsUsingPart` does its own `safeRead`-equivalent internally
102
+ * (catches and logs, returns `[]`), and this wraps it in `safeRead` again
103
+ * anyway — belt-and-braces costs nothing against a function that is already
104
+ * `cache()`-wrapped and this page is the only caller that would ever see a
105
+ * throw from a future change to it.
106
+ */
107
+ const partGlossaryId = typeof p.partGlossaryId === "string" ? p.partGlossaryId : undefined;
108
+ const fitsModels = partGlossaryId
109
+ ? await safeRead(() => getModelsUsingPart(partGlossaryId), {
110
+ route: "/products/[slug]",
111
+ key: "products.fitsModels",
112
+ fallback: [],
113
+ })
114
+ : [];
115
+ /*
116
+ * `(slot, tail)` from the id's OWN composition, `${slot}--${tail}` —
117
+ * mirrors `ModelCompositionPanel`'s private `tailOf`, duplicated rather than
118
+ * imported because that helper is a component-internal detail, not a public
119
+ * export, and pulling in the whole panel module for one split would be the
120
+ * heavier dependency.
121
+ */
122
+ const partHref = (() => {
123
+ if (!partGlossaryId)
124
+ return undefined;
125
+ const i = partGlossaryId.indexOf("--");
126
+ if (i < 0)
127
+ return undefined;
128
+ const slot = partGlossaryId.slice(0, i);
129
+ const tail = partGlossaryId.slice(i + 2);
130
+ return String(ROUTES.PUBLIC.PART_DETAIL(slot, tail));
131
+ })();
132
+ const partSlotLabel = partGlossaryId
133
+ ? PART_SLOT_LABEL[partGlossaryId.slice(0, partGlossaryId.indexOf("--"))]
134
+ : undefined;
92
135
  // -- Derived values ---------------------------------------------------------
93
136
  const title = String(p.title ?? p.name ?? "");
94
137
  const price = typeof p.price === "number" ? p.price : null;
95
138
  const originalPrice = typeof p.originalPrice === "number" ? p.originalPrice : null;
96
- const images = Array.isArray(p.images)
97
- ? p.images
139
+ // `images` is `ProductImage[]` since A6 — `productImageUrls` is the one
140
+ // normaliser (it also accepts the legacy `string[]`). Casting it to
141
+ // `string[]` handed the gallery objects and rendered a placeholder.
142
+ const galleryUrls = productImageUrls(p.images);
143
+ const images = galleryUrls.length > 0
144
+ ? galleryUrls
98
145
  : typeof p.mainImage === "string"
99
146
  ? [p.mainImage]
100
147
  : [];
@@ -103,8 +150,24 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
103
150
  const availableQuantity = typeof p.availableQuantity === "number"
104
151
  ? p.availableQuantity
105
152
  : null;
106
- const effectiveStock = availableQuantity ?? stockQuantity;
107
- const inStock = effectiveStock !== null ? effectiveStock > 0 : p.status === "published";
153
+ /*
154
+ * Made to order — printed or built when ordered, so there is no stock level
155
+ * to report and no "only N left" to be true.
156
+ *
157
+ * 🛑 `effectiveStock` is deliberately forced to `null` rather than left
158
+ * alone: every scarcity line below is guarded on `effectiveStock !== null`,
159
+ * and a made-to-order row's stored quantity goes NEGATIVE by design (it is
160
+ * the production queue). Leaving it would print "only -4 left — order soon!".
161
+ * One assignment suppresses all four of those branches, which is why this is
162
+ * done here and not at each of them.
163
+ */
164
+ const madeToOrder = p.madeToOrder === true;
165
+ const effectiveStock = madeToOrder ? null : availableQuantity ?? stockQuantity;
166
+ const inStock = madeToOrder
167
+ ? p.status === "published"
168
+ : effectiveStock !== null
169
+ ? effectiveStock > 0
170
+ : p.status === "published";
108
171
  const avgRating = typeof p.avgRating === "number" ? p.avgRating : null;
109
172
  const reviewCount = typeof p.reviewCount === "number" ? p.reviewCount : null;
110
173
  /*
@@ -222,9 +285,6 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
222
285
  }))
223
286
  : [];
224
287
  const allSpecs = [...specs, ...customFields];
225
- const customSections = Array.isArray(p.customSections)
226
- ? p.customSections
227
- : [];
228
288
  const allowOffers = p.allowOffers === true;
229
289
  // Was `p.type === "simple"`. `ProductDocument` has no `type` field at all —
230
290
  // the check passed only because the `?? "simple"` fallback made it vacuously
@@ -298,9 +358,17 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
298
358
  ? "Refurbished"
299
359
  : condition === "used"
300
360
  ? "Used"
301
- : condition })), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title || "Untitled Product" })] }), avgRating !== null ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { size: "sm", color: "muted", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), showViews && (_jsxs(Span, { size: "sm", color: "muted", children: ["\u00B7 ", views7d.toLocaleString(), " viewed this week"] })), _jsxs(Span, { size: "xs", weight: "medium", surface: inStock ? "success-surface" : "danger-surface", className: `ml-auto ${inStock ? "text-success" : "text-error"}`, padding: "pill-sm", rounded: "full", children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
361
+ : condition })), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title || "Untitled Product" })] }), avgRating !== null ? (_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(StarRating, { value: avgRating }), _jsxs(Span, { size: "sm", color: "muted", children: [avgRating.toFixed(1), reviewCount ? ` (${reviewCount} reviews)` : ""] }), showViews && (_jsxs(Span, { size: "sm", color: "muted", children: ["\u00B7 ", views7d.toLocaleString(), " viewed this week"] })), _jsxs(Span, { size: "xs", weight: "medium", surface: inStock ? "success-surface" : "danger-surface", className: `ml-auto ${inStock ? "text-success" : "text-error"}`, padding: "pill-sm", rounded: "full", children: [madeToOrder
362
+ ? "✓ Made to order"
363
+ : inStock
364
+ ? "✓ In Stock"
365
+ : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
302
366
  ? ` — only ${effectiveStock} left`
303
- : ""] })] })) : (_jsxs(Span, { layout: "inline-flex", gap: "xs", className: `w-fit .5 ${inStock ? CLS_STOCK_IN : CLS_STOCK_OUT}`, rounded: "full", padding: "pill-md", size: "xs", weight: "medium", children: [inStock ? "✓ In Stock" : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
367
+ : ""] })] })) : (_jsxs(Span, { layout: "inline-flex", gap: "xs", className: `w-fit .5 ${inStock ? CLS_STOCK_IN : CLS_STOCK_OUT}`, rounded: "full", padding: "pill-md", size: "xs", weight: "medium", children: [madeToOrder
368
+ ? "✓ Made to order"
369
+ : inStock
370
+ ? "✓ In Stock"
371
+ : "✗ Out of Stock", inStock && effectiveStock !== null && effectiveStock <= 10
304
372
  ? ` — only ${effectiveStock} left`
305
373
  : ""] })), (categorySlugs.length > 0 || subcategory || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [categorySlugs.map((slug, i) => (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryNames[i] ?? slug }, slug))), subcategory && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", transform: "capitalize", children: subcategory })), brand && brandSlug && (_jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand })), brand && !brandSlug && (_jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand }))] })), bundleMemberships.length > 0 && (_jsx(Row, { gap: "sm", wrap: true, children: bundleMemberships.map((b) => (_jsxs(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_PILL, children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), "In bundle: ", b.title] }, b.id))) })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, codAvailable: codEnabled === true, codHref: String(ROUTES.PUBLIC.HOW_CHECKOUT_WORKS), emiAvailable: emiEnabled === true, emiHref: String(ROUTES.PUBLIC.HOW_EMI_WORKS), condition: condition ?? undefined, returnable: !finalSale, finalSale: finalSale, labels: {
306
374
  featured: "Featured",
@@ -320,7 +388,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
320
388
  categoryProductCount: (n, cat) => `${n} in ${cat}`,
321
389
  } }), productFeatures && features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures })), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this product" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i))) })] })), descriptionHtml && (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" })), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Sold by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller }), typeof p.storeRating === "number" && p.storeRating > 0 && (_jsx(Row, { gap: "xs", align: "center", className: "mt-0.5", children: _jsxs(Text, { size: "xs", color: "muted", children: [_jsx(Span, { className: "text-warning", "aria-hidden": "true", children: "\u2605" }), " ", p.storeRating.toFixed(1), typeof p.storeReviewCount === "number" &&
322
390
  p.storeReviewCount > 0 &&
323
- ` · ${p.storeReviewCount} reviews`] }) }))] }), _jsxs(Row, { gap: "xs", align: "center", className: "shrink-0", children: [storeId && _jsx(MessageSellerButton, { storeId: storeId, productId: product.id }), storeHref && (_jsx(Link, { href: storeHref, className: "rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] })] }) }))] })), renderActions: () => (_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [price !== null && (_jsxs(Div, { children: [_jsx(PriceDisplay, { amount: price, originalAmount: originalPrice ?? undefined, currency: currency, variant: "detail" }), _jsx(MarketPriceAnchor, { band: priceBand?.byListingType?.[normalizeListingType(p)] ?? null, windowDays: priceBand?.windowDays ?? null, currency: currency, price: price }), inStock && effectiveStock !== null && effectiveStock <= 3 && (_jsxs(Text, { className: "mt-1 text-warning", size: "xs", children: ["Only ", effectiveStock, " left \u2014 order soon!"] })), inStock && effectiveStock !== null && effectiveStock > 3 && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [effectiveStock, " in stock"] }))] })), _jsxs(Stack, { gap: "sm", ...ctaAnchorProps(), children: [renderPrimaryActions ? (renderPrimaryActions({
391
+ ` · ${p.storeReviewCount} reviews`] }) }))] }), _jsxs(Row, { gap: "xs", align: "center", className: "shrink-0", children: [storeId && _jsx(MessageSellerButton, { storeId: storeId, productId: product.id }), storeHref && (_jsx(Link, { href: storeHref, className: "rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" }))] })] }) }))] })), renderActions: () => (_jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [price !== null && (_jsxs(Div, { children: [_jsx(PriceDisplay, { amount: price, originalAmount: originalPrice ?? undefined, currency: currency, variant: "detail" }), _jsx(MarketPriceAnchor, { band: priceBand?.byListingType?.[normalizeListingType(p)] ?? null, windowDays: priceBand?.windowDays ?? null, currency: currency, price: price }), inStock && effectiveStock !== null && effectiveStock <= 3 && (_jsxs(Text, { className: "mt-1 text-warning", size: "xs", children: ["Only ", effectiveStock, " left \u2014 order soon!"] })), inStock && effectiveStock !== null && effectiveStock > 3 && effectiveStock <= 10 && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [effectiveStock, " in stock"] })), madeToOrder && (_jsx(Text, { className: "mt-1", color: "muted", size: "xs", children: "Made to order \u2014 printed when you order, so it never runs out." })), partHref && (_jsxs(Text, { className: "mt-1", color: "muted", size: "xs", children: [partSlotLabel ?? "Part", ".", " ", _jsx(Link, { href: partHref, className: "text-primary-600 hover:underline", children: "See what else fits \u2192" }), fitsModels.length > 0 && (_jsxs(_Fragment, { children: [" ", "Fits:", " ", fitsModels.slice(0, 6).map((m, i) => (_jsxs(Text, { as: "span", children: [i > 0 ? ", " : "", m.categoryId ? (_jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(m.categoryId)), className: "text-primary-600 hover:underline", children: m.name })) : (m.name)] }, m.id)))] }))] }))] })), _jsxs(Stack, { gap: "sm", ...ctaAnchorProps(), children: [renderPrimaryActions ? (renderPrimaryActions({
324
392
  productId: product.id,
325
393
  productSlug: product.slug ?? slug,
326
394
  productTitle: product.title,
@@ -353,11 +421,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
353
421
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
354
422
  : undefined, renderGroupSection: groupId
355
423
  ? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
356
- : undefined, renderTabs: () => (_jsx(ProductTabsShell, { partsContent: compositionContent, descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: allSpecs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
357
- id: s.id,
358
- label: s.title,
359
- content: _jsx(CustomSectionTabContent, { section: s }),
360
- })) })), renderBundleSection: bundleMemberships.length > 0
424
+ : undefined, renderTabs: () => (_jsx(ProductTabsShell, { partsContent: compositionContent, descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: allSpecs.length > 0 ? (_jsx(SpecTable, { rows: allSpecs })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx(Ul, { spacing: "comfortable", children: ingredients.map((item, i) => (_jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 bg-primary-400", rounded: "full" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx(Ol, { spacing: "loose", children: howToUse.map((step, i) => (_jsxs(Li, { layout: "flex-start", gap: "3", textSize: "sm", color: "muted", children: [_jsx(Span, { layout: "flex-center", size: "xs", weight: "bold", className: "flex-shrink-0 h-6 w-6 bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300", rounded: "full", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsListingPanel, { source: { kind: "product", productId: product.id }, stateMode: "local", context: "listing", initialData: initialReviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: toCustomSectionTabs(p.customSections) })), renderBundleSection: bundleMemberships.length > 0
361
425
  ? () => (_jsxs(Div, { className: CLS_BUNDLE_BOX, children: [_jsxs(Row, { className: "mb-3", align: "center", gap: "sm", children: [_jsx(Span, { size: "base", className: CLS_BUNDLE_ICON, "aria-hidden": "true", children: "\uD83D\uDCE6" }), _jsxs(Text, { className: CLS_BUNDLE_TITLE, children: ["Part of ", bundleMemberships.length === 1 ? "a bundle" : `${bundleMemberships.length} bundles`] })] }), _jsx(Stack, { gap: "3", children: bundleMemberships.map((b) => (_jsxs(Div, { className: CLS_BUNDLE_ROW, children: [_jsxs(Div, { className: "min-w-0", children: [_jsx(Text, { className: CLS_BUNDLE_LABEL, children: "Included in bundle" }), _jsx(Text, { className: "truncate", color: "primary", size: "sm", weight: "semibold", children: b.title })] }), _jsx(Link, { href: String(ROUTES.PUBLIC.BUNDLE_DETAIL(b.id)), className: CLS_BUNDLE_CTA, children: "View Bundle \u2192" })] }, b.id))) })] }))
362
426
  : undefined, renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryNames[0] || categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] })) }), renderPrimaryActions
363
427
  ? renderPrimaryActions({
@@ -71,6 +71,18 @@ export interface ProductFormProps {
71
71
  }) => React.ReactNode;
72
72
  /** Render app-specific category selector. */
73
73
  renderCategorySelector?: (args: CategorySelectorRenderArgs) => React.ReactNode;
74
+ /**
75
+ * Render the seller-facing price hint (`<PriceGuidanceHint categoryId>`)
76
+ * beside the price field. Omitted when absent — this form has no
77
+ * data-fetching of its own (every app-specific concern is a render-prop),
78
+ * and the hint is seller/admin-only, never mounted for a buyer/public
79
+ * context. The admin path passes nothing here on purpose: pricing an
80
+ * entire category's catalog from one editor is a different workflow than
81
+ * a seller pricing their own single listing.
82
+ */
83
+ renderPriceGuidance?: (args: {
84
+ categorySlug: string | undefined;
85
+ }) => React.ReactNode;
74
86
  /**
75
87
  * Render app-specific brand selector (select existing or create new).
76
88
  * Receives `multi=true` when brandMode is "mixed".
@@ -102,5 +114,5 @@ export interface ProductFormProps {
102
114
  disabled: boolean;
103
115
  }) => React.ReactNode;
104
116
  }
105
- export declare function ProductForm({ product, onChange, isReadonly, canEditTax, renderDescriptionEditor, renderCategorySelector, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix, renderGroupSettings, renderGroupJoinField, }: ProductFormProps): React.JSX.Element;
117
+ export declare function ProductForm({ product, onChange, isReadonly, canEditTax, renderDescriptionEditor, renderCategorySelector, renderPriceGuidance, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix, renderGroupSettings, renderGroupJoinField, }: ProductFormProps): React.JSX.Element;
106
118
  export {};
@@ -28,6 +28,14 @@ const PRIZE_REVEAL_MODE_OPTIONS = [
28
28
  // (classified/digital-code/live/prize-draw/art/stickers) to "product".
29
29
  const LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE = {
30
30
  standard: "product",
31
+ /*
32
+ * 🛑 "product", not a new `ProductFeatureProductType` member. That union is
33
+ * the FEATURE-badge vocabulary ("free shipping", "authenticity checked") and
34
+ * a spare earns the same badges a standard physical item does. Minting a
35
+ * value here would mean every existing feature row stopped matching spares —
36
+ * i.e. a brand-new type with no features at all, which reads as a bug.
37
+ */
38
+ spares: "product",
31
39
  auction: "auction",
32
40
  "pre-order": "preorder",
33
41
  "prize-draw": "prize-draw",
@@ -52,7 +60,7 @@ function readFileAsDataUrl(file) {
52
60
  }
53
61
  export function ProductForm({ product, onChange, isReadonly = false,
54
62
  /* Defaults to false — the seller path must not reach it. See the prop's docs. */
55
- canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix = "", renderGroupSettings, renderGroupJoinField, }) {
63
+ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPriceGuidance, renderBrandSelector, renderStoreAddressSelector, onMediaAbort, currencyPrefix = "", renderGroupSettings, renderGroupJoinField, }) {
56
64
  const t = useTranslations("adminProducts");
57
65
  const { upload } = useMediaUpload();
58
66
  const galleryIndexRef = useRef(0);
@@ -138,7 +146,13 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderBrand
138
146
  onValueChange: (value) => update({ brand: value }),
139
147
  onValuesChange: (values) => update({ brands: values }),
140
148
  disabled: isReadonly,
141
- })) : product.brandMode === "mixed" ? (_jsx(TagInput, { label: "Brands", value: product.brands || [], onChange: (brands) => update({ brands }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), _jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: product.mainImage, onUpload: (file) => upload(file, "products", true, {
149
+ })) : product.brandMode === "mixed" ? (_jsx(TagInput, { label: "Brands", value: product.brands || [], onChange: (brands) => update({ brands }), disabled: isReadonly, placeholder: "e.g. Nike, Adidas, Puma" })) : (_jsx(FormField, { name: "brand", label: t("formBrand"), type: "text", value: product.brand || "", onChange: (value) => update({ brand: value }), disabled: isReadonly, placeholder: "e.g. Apple" })) }))] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Div, {}), " ", _jsx(FormField, { name: "status", label: t("formStatus"), type: "select", value: product.status || "draft", onChange: (value) => update({ status: value }), disabled: isReadonly, options: PRODUCT_STATUS_OPTIONS })] }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "price", label: t("formPrice"), type: "number", value: String(product.price ?? ""), onChange: (value) => update({ price: Number(value) }), disabled: isReadonly, placeholder: "0" }), product.madeToOrder === true ? null : (_jsx(FormField, { name: "stockQuantity", label: t("formStock"), type: "number", value: String(product.stockQuantity ?? ""), onChange: (value) => update({ stockQuantity: Number(value) }), disabled: isReadonly, placeholder: "0" }))] }), renderPriceGuidance?.({
150
+ categorySlug: (product.categorySlugs?.[0] ?? product.category) || undefined,
151
+ }), _jsx(Checkbox, { label: "Made to order",
152
+ /* `suffix`, not a `helperText` prop — Checkbox has no such prop, and the
153
+ * explanation is load-bearing: a seller needs to know this turns stock
154
+ * tracking OFF before they tick it, not after. */
155
+ suffix: _jsx(Text, { size: "xs", color: "muted", children: "Printed or built when someone orders it. Stock is not tracked and the listing never shows as sold out." }), checked: !!product.madeToOrder, onChange: (e) => update({ madeToOrder: e.target.checked }), disabled: isReadonly }), !isReadonly && (_jsx(ImageUpload, { currentImage: product.mainImage, onUpload: (file) => upload(file, "products", true, {
142
156
  type: "product-image",
143
157
  index: 1,
144
158
  name: product.title || "product",
@@ -15,5 +15,20 @@ export interface ProductsIndexListingProps {
15
15
  label: string;
16
16
  }[];
17
17
  searchPlaceholder?: string;
18
+ /**
19
+ * Scope every query to one store, for a store-detail tab.
20
+ *
21
+ * 🛑 Added so a store tab can REUSE this component. There are six
22
+ * hand-written `Store<Type>Listing` components — ~140 lines each of the same
23
+ * toolbar, availability tabs, sticky pagination and card grid — and a seventh
24
+ * copy for spares would push a Rule-of-Three breach from six to seven while
25
+ * being the cheapest possible moment not to. Consolidating the existing six
26
+ * is its own session; not adding to them costs one prop.
27
+ *
28
+ * It OVERRIDES the URL's `storeId` rather than merging: on a store tab the
29
+ * store is the page, so a `?storeId=` from a stale link must not be able to
30
+ * show another seller's stock under this seller's heading.
31
+ */
32
+ storeId?: string;
18
33
  }
19
- export declare function ProductsIndexListing({ initialData, listingTypes, typeTabs, searchPlaceholder, }: ProductsIndexListingProps): React.JSX.Element;
34
+ export declare function ProductsIndexListing({ initialData, listingTypes, typeTabs, searchPlaceholder, storeId: scopedStoreId, }: ProductsIndexListingProps): React.JSX.Element;
@@ -67,7 +67,7 @@ function dedicatedPageFor(types) {
67
67
  label: `Full ${plugin.pluralLabel} filters →`,
68
68
  };
69
69
  }
70
- export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", }) {
70
+ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODUCT_LISTING_TYPES, typeTabs = PRODUCT_TYPE_FILTER_TABS, searchPlaceholder = "Search products...", storeId: scopedStoreId, }) {
71
71
  const router = useRouter();
72
72
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
73
73
  const { showToast } = useToast();
@@ -155,7 +155,7 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
155
155
  maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
156
156
  condition: table.get(TABLE_KEYS.CONDITION) || undefined,
157
157
  brand: table.get(TABLE_KEYS.BRAND) || undefined,
158
- storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
158
+ storeId: scopedStoreId || table.get(TABLE_KEYS.STORE_ID) || undefined,
159
159
  freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
160
160
  // These three are in FILTER_KEYS (so they count toward the filter badge)
161
161
  // but were never put on the wire — the drawer sections were inert.
@@ -345,5 +345,5 @@ export function ProductsIndexListing({ initialData, listingTypes = GENERIC_PRODU
345
345
  ] }), 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(Grid, { cols: "cards", gap: "md", children: Array.from({ length: 10 }).map((_, i) => (_jsxs(Div, { className: `${__O.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-4 w-1/3", surface: "subtle", rounded: "default" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => pluginFor(p.listingType ?? "standard").detailRoute(p.slug || p.id), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
346
346
  setCompareIds([]);
347
347
  selection.clearSelection();
348
- }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
348
+ }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== id)), onAdd: (id) => setCompareIds((ids) => (ids.includes(id) ? ids : [...ids, id])) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", listingTypeOptions: listingTypeOptions, listingTypeFooter: dedicatedPage ? (_jsx(Div, { padding: "x-md", paddingY: "b-sm", children: _jsx(TextLink, { href: dedicatedPage.href, size: "sm", weight: "medium", children: dedicatedPage.label }) })) : null, categoryOptions: categoryOptions, brandOptions: brandOptions, featureOptions: featureOptions }) }), _jsx(LoginRequiredModal, { isOpen: modalOpen, onClose: closeModal, message: modalMessage })] }));
349
349
  }
@@ -5,6 +5,8 @@ import Link from "next/link.js";
5
5
  import { Div, PricesOnly, Row, Span, Text, Modal, SideDrawer, Button } from "../../../ui/index.js";
6
6
  import { MediaImage } from "../../media/MediaImage.js";
7
7
  import { GroupMemberPicker } from "./GroupMemberPicker.js";
8
+ import { resolveGroupMemberImage } from "../utils/group-member-image.js";
9
+ import { readGroupRules } from "../../../_internal/shared/features/products/group-rules.js";
8
10
  import { formatCurrency } from "../../../utils/number.formatter.js";
9
11
  import { normalizeListingType } from "../utils/listing-type.js";
10
12
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
@@ -18,7 +20,7 @@ function memberHref(m) {
18
20
  return pluginFor(normalizeListingType(m)).detailRoute(slug);
19
21
  }
20
22
  function MemberThumb({ member, isCurrent }) {
21
- const image = member.images?.[0] ?? "";
23
+ const image = resolveGroupMemberImage(member);
22
24
  const href = memberHref(member);
23
25
  const price = formatCurrency(member.price, member.currency ?? "INR");
24
26
  return (_jsxs(Link, { href: href, "aria-label": member.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsxs(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
@@ -48,7 +50,18 @@ export function ShowGroupSection({ groupId, currentSlug, isParent, groupTitle })
48
50
  // The overlay is now a PICKER, not a read-only table: pick how many of each
49
51
  // member you want and add the whole selection as one cart line. The picker
50
52
  // stands itself down (read-only, no CTA) when the group spans sellers.
51
- const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members, onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
53
+ const tableContent = (_jsx(GroupMemberPicker, { groupId: groupId, groupSource: "product-group", members: members,
54
+ /*
55
+ * A product group's rules live on the PARENT, and the parent is one of
56
+ * the members this component already fetched — found by `isGroupParent`
57
+ * rather than by position, because the endpoint makes no ordering
58
+ * promise and the parent is not reliably first.
59
+ *
60
+ * 🛑 This is why the five rule fields are in `PRODUCT_PUBLIC_FIELDS`: they
61
+ * reach `toPublicGroupMember` through that allow-list, and without it the
62
+ * picker would silently see no rules and permit what the server refuses.
63
+ */
64
+ rules: readGroupRules(members.find((m) => m.isGroupParent)), onAdded: () => setShowAll(false), onAuthRequired: () => setShowAll(false) }));
52
65
  return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: `${__O.hidden}`, surface: "muted", rounded: "xl", border: "default", children: [_jsxs(Row, { role: "button", tabIndex: 0, onClick: () => setOpen((v) => !v), onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") {
53
66
  e.preventDefault();
54
67
  setOpen((v) => !v);
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ type SearchParams = Record<string, string | string[]>;
3
+ export interface SparesListViewProps {
4
+ searchParams?: SearchParams;
5
+ }
6
+ export declare function SparesListView({ searchParams }: SparesListViewProps): Promise<React.JSX.Element>;
7
+ export {};
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Container, Heading, Main, Section, Text, TextLink } from "../../../ui/index.js";
3
+ import { AdSlot } from "../../homepage/components/AdSlot.js";
4
+ import { ProductsIndexListing } from "./ProductsIndexListing.js";
5
+ import { PRODUCT_FIELDS } from "../../../constants/field-names.js";
6
+ import { sortBy } from "../../../constants/sort.js";
7
+ import { ROUTES } from "../../../next/routing/route-map.js";
8
+ import { listPublicProducts, parsePublicProductParams, defaultAvailabilityForListingTypes, } from "../../../_internal/server/features/products/list-public.js";
9
+ const DEFAULT_PAGE_SIZE = 24;
10
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
11
+ /** The one type this page spans. Named so the two call sites cannot disagree. */
12
+ const SPARES_LISTING_TYPES = ["spares"];
13
+ export async function SparesListView({ searchParams = {} }) {
14
+ /*
15
+ * Filter semantics live in `listPublicProducts`, shared verbatim with
16
+ * `/api/products`, so the SSR first paint and the client's refetch cannot
17
+ * disagree about what the default view means. That matters more here than it
18
+ * looks: every public listing hook sets `staleTime: Infinity` once given SSR
19
+ * `initialData`, so a disagreement is FROZEN for that query key and no
20
+ * refetch ever repairs it (Root Cause #30).
21
+ */
22
+ const result = await listPublicProducts(parsePublicProductParams(searchParams, {
23
+ listingTypes: SPARES_LISTING_TYPES,
24
+ pageSize: DEFAULT_PAGE_SIZE,
25
+ sorts: DEFAULT_SORT,
26
+ // Derived from the plugin's own `hideDefault`, so this page and
27
+ // `ProductsIndexListing` cannot mirror two different literals.
28
+ ...defaultAvailabilityForListingTypes(SPARES_LISTING_TYPES),
29
+ }));
30
+ return (_jsxs(Main, { children: [_jsx(Section, { border: "bottom-subtle", paddingY: "b-md", padding: "t-xl", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "bold", color: "primary", children: "Spares" }), _jsx(Text, { className: "mt-1", color: "muted", size: "sm", children: "Single components off disassembled tops \u2014 attack rings, weight disks, drivers, bits. Sellers list what they have spare; most are one of a kind." }), _jsxs(Text, { className: "mt-2", size: "sm", children: ["Not sure which part you need?", " ", _jsx(TextLink, { href: String(ROUTES.PUBLIC.PARTS), children: "Browse the parts glossary" }), " ", "\u2014 every component, what it does, and which tops use it."] })] }) }), _jsxs(Container, { size: "xl", padding: "x-md", children: [_jsx(AdSlot, { id: "listing-sidebar-top", className: "mb-4 mt-4" }), _jsx(ProductsIndexListing
31
+ // `undefined`, not `null` — React Query treats `null` as defined
32
+ // data, so a failed query would freeze an empty grid forever under
33
+ // `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
34
+ , {
35
+ // `undefined`, not `null` — React Query treats `null` as defined
36
+ // data, so a failed query would freeze an empty grid forever under
37
+ // `staleTime: Infinity`. Same reasoning as ProductsIndexPageView.
38
+ initialData: result ?? undefined, listingTypes: SPARES_LISTING_TYPES, searchPlaceholder: "Search spares by part, colour or model..." }), _jsx(AdSlot, { id: "listing-sidebar-bottom", className: "mt-8" })] })] }));
39
+ }
@@ -9,6 +9,7 @@ import { formatCurrency } from "../../../utils/number.formatter.js";
9
9
  import { normalizeListingType } from "../utils/listing-type.js";
10
10
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
11
11
  import { SUBLISTING_CATEGORY_ENDPOINTS } from "../../../constants/api-endpoints.js";
12
+ import { productImageUrls } from "../schemas/firestore.js";
12
13
  const __O = {
13
14
  hidden: "overflow-hidden",
14
15
  xAuto: "overflow-x-auto",
@@ -18,7 +19,7 @@ function getHref(listing) {
18
19
  return pluginFor(normalizeListingType(listing)).detailRoute(slug);
19
20
  }
20
21
  function ListingThumb({ listing, isCurrent, }) {
21
- const image = listing.images?.[0] ?? listing.mainImage ?? "";
22
+ const image = productImageUrls(listing.images)[0] || listing.mainImage || "";
22
23
  const href = getHref(listing);
23
24
  const price = formatCurrency(listing.price, listing.currency ?? "INR");
24
25
  return (_jsxs(Link, { href: href, "aria-label": listing.title, className: "flex flex-col items-center gap-[var(--appkit-space-1-5)] flex-shrink-0 w-16 group", children: [_jsx(Div, { rounded: "full", className: `relative w-14 h-14 overflow-hidden border-2 transition-all ${isCurrent
@@ -411,6 +411,7 @@ export declare const SELLER_BULK_ACTIONS: {
411
411
  readonly live: readonly ["row-delete"];
412
412
  readonly art: readonly ["row-delete"];
413
413
  readonly stickers: readonly ["row-delete"];
414
+ readonly spares: readonly ["row-delete"];
414
415
  readonly auctions: readonly ["row-delete"];
415
416
  readonly preOrders: readonly ["row-delete"];
416
417
  readonly prizeDraws: readonly ["row-delete"];
@@ -566,6 +566,7 @@ export const SELLER_BULK_ACTIONS = {
566
566
  live: [ROW_ACTION_ID.DELETE],
567
567
  art: [ROW_ACTION_ID.DELETE],
568
568
  stickers: [ROW_ACTION_ID.DELETE],
569
+ spares: [ROW_ACTION_ID.DELETE],
569
570
  auctions: [ROW_ACTION_ID.DELETE],
570
571
  preOrders: [ROW_ACTION_ID.DELETE],
571
572
  prizeDraws: [ROW_ACTION_ID.DELETE],