@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,2 +1,34 @@
1
1
  import React from "react";
2
- export declare function ContactPageView(): React.JSX.Element;
2
+ /**
3
+ * The legal entity and support channels, passed in by the consumer page.
4
+ *
5
+ * 🛑 PROPS, not an import. This is a `"use client"` component inside appkit,
6
+ * so it cannot reach the consumer's `appkit.config.js` — and it must not, or
7
+ * appkit would carry a second definition of facts whose entire point is to
8
+ * have one owner (Root Cause #81). Every field is a plain string or `null`,
9
+ * so the whole object crosses the server/client boundary as serialisable data.
10
+ *
11
+ * Everything optional is `null` when undecided and renders a visible
12
+ * `[to be confirmed]` marker — never an invented support number.
13
+ */
14
+ export interface ContactBusinessInfo {
15
+ legalName: string;
16
+ proprietor?: string | null;
17
+ structure?: string | null;
18
+ gstin?: string | null;
19
+ /** Pre-formatted lines — the consumer owns the address formatting. */
20
+ addressLines?: readonly string[];
21
+ supportEmail?: string | null;
22
+ supportPhone?: string | null;
23
+ /** Ready-to-use wa.me URL, or null. Built by the consumer so the digit-stripping rule lives in one place. */
24
+ whatsAppLink?: string | null;
25
+ whatsAppDisplay?: string | null;
26
+ supportHours?: string | null;
27
+ /** Community group invite — marketing, not support. Optional. */
28
+ communityUrl?: string | null;
29
+ grievanceHref?: string | null;
30
+ }
31
+ export interface ContactPageViewProps {
32
+ business: ContactBusinessInfo;
33
+ }
34
+ export declare function ContactPageView({ business }: ContactPageViewProps): React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Container, Div, Heading, Main, Section, Stack, Text } from "../../../ui/index.js";
3
+ import { Anchor, Container, Div, Heading, Main, PendingValue, Section, Stack, Text } from "../../../ui/index.js";
4
4
  import { AdSlot } from "../../homepage/components/AdSlot.js";
5
5
  import { ContactForm } from "./ContactForm.js";
6
6
  import { ContactInfoSidebar } from "./ContactInfoSidebar.js";
@@ -9,7 +9,53 @@ import { CONTACT_ENDPOINTS } from "../../../constants/api-endpoints.js";
9
9
  async function submitContact(data) {
10
10
  await apiClient.post(CONTACT_ENDPOINTS.SUBMIT, data);
11
11
  }
12
- export function ContactPageView() {
12
+ export function ContactPageView({ business }) {
13
+ /*
14
+ * Built from `business` rather than hardcoded. Every one of these was a
15
+ * literal in this file until 2026-10-19 — `support@letitrip.in`,
16
+ * `+91 89196 65811`, `Mon-Sat · 10:00 AM - 7:00 PM IST` — which meant the
17
+ * contact page could disagree with `siteSettings.contact`, with the FAQ
18
+ * variables and with the footer, and did.
19
+ *
20
+ * A channel with no value is OMITTED rather than rendered pending: a contact
21
+ * sidebar listing "Email: [to be confirmed]" as a clickable row is worse
22
+ * than a sidebar that does not offer email. The pending markers belong on
23
+ * the legal-identity block below, where absence is the thing worth seeing.
24
+ */
25
+ const infoItems = [
26
+ business.supportEmail
27
+ ? {
28
+ icon: "📧",
29
+ label: "Email",
30
+ value: business.supportEmail,
31
+ href: `mailto:${business.supportEmail}`,
32
+ }
33
+ : null,
34
+ business.supportPhone
35
+ ? {
36
+ icon: "📞",
37
+ label: "Phone",
38
+ value: business.supportPhone,
39
+ href: `tel:${business.supportPhone.replace(/[^\d+]/g, "")}`,
40
+ }
41
+ : null,
42
+ business.whatsAppLink
43
+ ? {
44
+ icon: "💬",
45
+ label: "WhatsApp",
46
+ value: business.whatsAppDisplay ?? "Message us",
47
+ href: business.whatsAppLink,
48
+ }
49
+ : null,
50
+ business.communityUrl
51
+ ? {
52
+ icon: "👥",
53
+ label: "WhatsApp Community",
54
+ value: "Join the group",
55
+ href: business.communityUrl,
56
+ }
57
+ : null,
58
+ ].filter((i) => i !== null);
13
59
  return (_jsx(Main, { children: _jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "section", children: [_jsxs(Stack, { gap: "3", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: "Contact Us" }), _jsx(Text, { color: "muted", children: "Questions, support requests, or partnership inquiries." })] }), _jsx(AdSlot, { id: "detail-below-gallery", className: "mb-2" }), _jsxs(Div, { layout: "grid", gap: "6", className: "grid-cols-12", children: [_jsx(Div, { className: "col-span-12 lg:col-span-8", children: _jsx(ContactForm, { onSubmit: submitContact, labels: {
14
60
  title: "Send us a message",
15
61
  nameLabel: "Your Name",
@@ -17,30 +63,14 @@ export function ContactPageView() {
17
63
  subjectLabel: "Subject",
18
64
  messageLabel: "Message",
19
65
  submitButton: "Send Message",
20
- } }) }), _jsx(Div, { className: "col-span-12 lg:col-span-4", children: _jsx(ContactInfoSidebar, { infoItems: [
21
- {
22
- icon: "📧",
23
- label: "Email",
24
- value: "support@letitrip.in",
25
- href: "mailto:support@letitrip.in",
26
- },
27
- {
28
- icon: "💬",
29
- label: "WhatsApp",
30
- value: "+91 89196 65811",
31
- href: "https://wa.me/918919665811",
32
- },
33
- {
34
- icon: "👥",
35
- label: "WhatsApp Community",
36
- value: "Join the group",
37
- href: "https://chat.whatsapp.com/JOlbdSwhVWYDKujolcBZDr",
38
- },
39
- ], labels: {
40
- title: "Support",
41
- businessHoursLabel: "Business Hours",
42
- businessHoursValue: "Mon-Sat · 10:00 AM - 7:00 PM IST",
43
- responseTimeLabel: "Response Time",
44
- responseTimeValue: "Usually within 24 hours",
45
- } }) })] }), _jsx(AdSlot, { id: "detail-below-price" })] }) }) }) }));
66
+ } }) }), _jsx(Div, { className: "col-span-12 lg:col-span-4", children: _jsxs(Stack, { gap: "lg", children: [_jsx(ContactInfoSidebar, { infoItems: infoItems, labels: {
67
+ title: "Support",
68
+ businessHoursLabel: "Business Hours",
69
+ // 🛑 No invented fallback. "Mon-Sat · 10:00 AM - 7:00 PM
70
+ // IST" was hardcoded here and matched nothing anyone had
71
+ // committed to.
72
+ businessHoursValue: business.supportHours ?? "[to be confirmed]",
73
+ responseTimeLabel: "Response Time",
74
+ responseTimeValue: "Usually within 24 hours",
75
+ } }), _jsxs(Div, { surface: "muted", padding: "card", rounded: "lg", border: "default", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", className: "mb-3 uppercase tracking-wide", color: "muted", children: "Business details" }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: business.legalName }), business.structure ? (_jsx(Text, { size: "sm", color: "muted", children: business.structure })) : null, business.proprietor ? (_jsxs(Text, { size: "sm", color: "muted", children: ["Proprietor: ", business.proprietor] })) : null, (business.addressLines ?? []).map((line) => (_jsx(Text, { size: "sm", color: "muted", children: line }, line))), _jsxs(Text, { size: "sm", color: "muted", className: "mt-2", children: ["GSTIN:", " ", _jsx(PendingValue, { value: business.gstin, label: "GSTIN" })] }), business.grievanceHref ? (_jsx(Text, { size: "sm", className: "mt-2", children: _jsx(Anchor, { href: business.grievanceHref, children: "Grievance redressal" }) })) : null] })] })] }) })] }), _jsx(AdSlot, { id: "detail-below-price" })] }) }) }) }));
46
76
  }
@@ -1,18 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import Link from "next/link.js";
3
3
  import { getDigitalCodeForDetail } from "../../../_internal/server/features/digital-code/data.js";
4
- const CLS_BREADCRUMB_LINK = "hover:text-primary-600 transition-colors";
4
+ const CLS_BREADCRUMB_LINK = "hover:text-primary-600 dark:hover:text-primary-400 transition-colors";
5
5
  const CLS_DIGITALCODE_BADGE = "inline-block rounded-full bg-violet-100 dark:bg-violet-900/30 px-[var(--appkit-space-2-5)] py-[var(--appkit-space-0-5)] text-violet-700 dark:text-violet-300";
6
6
  import { ROUTES } from "../../../next/routing/route-map.js";
7
7
  import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
8
8
  import { formatCurrency } from "../../../utils/number.formatter.js";
9
9
  import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
10
10
  import { safeDisplayName } from "../../../security/index.js";
11
- import { Div, Container, GatedPrice, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, Dl, Dt, Dd, } from "../../../ui/index.js";
11
+ import { Div, Container, GatedPrice, Heading, Main, Nav, RichText, Row, Section, Span, Stack, Text, SpecTable, } from "../../../ui/index.js";
12
12
  import { PreOrderDetailView } from "../../products/components/PreOrderDetailView.js";
13
13
  import { ProductGalleryClient } from "../../products/components/ProductGalleryClient.js";
14
14
  import { ProductTabsShell } from "../../products/components/ProductTabsShell.js";
15
- import { CustomSectionTabContent } from "../../products/components/CustomSectionTabContent.js";
15
+ import { toCustomSectionTabs } from "../../products/components/CustomSectionTabContent.js";
16
16
  import { ShareButton } from "../../products/components/ShareButton.js";
17
17
  import { HistoryTracker } from "../../history/components/HistoryTracker.js";
18
18
  import { RelatedItemsSection } from "../../products/components/RelatedItemsSection.js";
@@ -21,6 +21,7 @@ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListing
21
21
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data.js";
22
22
  import { ReviewsListingPanel } from "../../reviews/components/ReviewsListingPanel.js";
23
23
  import { ListingBottomActions } from "../../products/components/ListingBottomActions.js";
24
+ import { productImageUrls } from "../../products/schemas/firestore.js";
24
25
  function toDescriptionHtml(raw) {
25
26
  if (!raw)
26
27
  return "";
@@ -41,7 +42,15 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
41
42
  const currency = p.currency || getDefaultCurrency();
42
43
  const title = String(p.title ?? p.name ?? "Digital Code");
43
44
  const price = typeof p.price === "number" ? p.price : null;
44
- const images = Array.isArray(p.images) ? p.images : typeof p.mainImage === "string" ? [p.mainImage] : [];
45
+ // `images` is `ProductImage[]` since A6 — `productImageUrls` is the one
46
+ // normaliser (it also accepts the legacy `string[]`). Casting it to
47
+ // `string[]` handed the gallery objects and rendered a placeholder.
48
+ const galleryUrls = productImageUrls(p.images);
49
+ const images = galleryUrls.length > 0
50
+ ? galleryUrls
51
+ : typeof p.mainImage === "string"
52
+ ? [p.mainImage]
53
+ : [];
45
54
  const meta = product.digitalCode;
46
55
  const codesLeft = meta?.codesAvailable ?? 0;
47
56
  const category = Array.isArray(p.categorySlugs) && p.categorySlugs.length > 0 ? String(p.categorySlugs[0]) : (typeof p.category === "string" ? p.category : null);
@@ -52,7 +61,6 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
52
61
  const storeSlug = (typeof p.storeSlug === "string" ? p.storeSlug : null) || (typeof p.storeId === "string" ? p.storeId : null);
53
62
  const storeHref = storeSlug ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug)) : null;
54
63
  const specs = Array.isArray(p.specifications) ? p.specifications : [];
55
- const customSections = Array.isArray(p.customSections) ? p.customSections : [];
56
64
  const descriptionHtml = toDescriptionHtml(p.description);
57
65
  const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups, initialReviews] = await Promise.all([
58
66
  computeRelatedItems(product),
@@ -65,11 +73,7 @@ export async function DigitalCodeDetailPageView({ slug, initialProduct, renderPr
65
73
  price: price ?? undefined,
66
74
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
67
75
  storeName: storeName ?? undefined,
68
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), 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, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", 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 })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { 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: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.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, 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) => ({
69
- id: s.id,
70
- label: s.title,
71
- content: _jsx(CustomSectionTabContent, { section: s }),
72
- })) })), renderBuyBar: () => (_jsx(Div, { id: "digital-code-buy-bar", children: renderPrimaryActions?.({
76
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.DIGITAL_CODES), className: CLS_BREADCRUMB_LINK, children: "Digital Codes" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => _jsx(ProductGalleryClient, { images: images, productName: title }), renderInfo: () => (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_DIGITALCODE_BADGE, children: "Digital Code" }), codesLeft > 0 ? (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: [codesLeft, " available"] })) : (_jsx(Span, { size: "xs", weight: "semibold", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "danger-surface", color: "error", children: "Sold out" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), price !== null && (_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { children: formatCurrency(price, currency) }) })), meta?.redemptionInstructions && (_jsx(Text, { size: "xs", color: "muted", children: meta.redemptionInstructions })), 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, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", 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 })] }), storeHref && (_jsx(Link, { href: storeHref, className: "shrink-0 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { 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: specs.length > 0 ? (_jsx(SpecTable, { rows: specs })) : 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) })), renderBuyBar: () => (_jsx(Div, { id: "digital-code-buy-bar", children: renderPrimaryActions?.({
73
77
  productId: String(product.id),
74
78
  productSlug: String(p.slug ?? product.id),
75
79
  productTitle: title,
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Container, Div, Heading, Section, Span, Stack, Text, } from "../../../ui/index.js";
4
4
  import { MediaImage } from "../../media/MediaImage.js";
5
5
  import { GroupMemberPicker } from "../../products/components/GroupMemberPicker.js";
6
+ import { readGroupRules } from "../../../_internal/shared/features/products/group-rules.js";
6
7
  const __O = { hidden: "overflow-hidden" };
7
8
  /** Human label per theme — mirrors GROUP_THEME_TITLE on the carousel. */
8
9
  const THEME_LABEL = {
@@ -13,5 +14,12 @@ const THEME_LABEL = {
13
14
  generic: "Grouped listing",
14
15
  };
15
16
  export function GroupedListingDetailView({ group, members }) {
16
- return (_jsx(Section, { padding: "section", children: _jsx(Container, { children: _jsxs(Stack, { gap: "comfortable", children: [group.coverImage && (_jsx(Div, { className: `relative h-48 w-full md:h-64 ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: group.coverImage, alt: group.title, size: "banner" }) })), _jsxs(Stack, { gap: "xs", children: [_jsx(Span, { size: "xs", color: "muted", transform: "uppercase", weight: "semibold", children: THEME_LABEL[group.groupTheme] }), _jsx(Heading, { level: 1, children: group.title }), group.description && (_jsx(Text, { color: "muted", children: group.description })), _jsxs(Text, { size: "sm", color: "muted", children: [members.length, " item", members.length === 1 ? "" : "s", " in this group"] })] }), members.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "lg", children: "Pick what you want" }), _jsx(Text, { size: "sm", color: "muted", children: "Choose a quantity for each item \u2014 they'll go into your cart as a single line you can edit later." }), _jsx(GroupMemberPicker, { groupId: group.id, groupSource: "grouped-listing", members: members })] })) : (_jsx(Text, { color: "muted", children: "Nothing in this group is available right now." }))] }) }) }));
17
+ return (_jsx(Section, { padding: "section", children: _jsx(Container, { children: _jsxs(Stack, { gap: "comfortable", children: [group.coverImage && (_jsx(Div, { className: `relative h-48 w-full md:h-64 ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: group.coverImage, alt: group.title, size: "banner" }) })), _jsxs(Stack, { gap: "xs", children: [_jsx(Span, { size: "xs", color: "muted", transform: "uppercase", weight: "semibold", children: THEME_LABEL[group.groupTheme] }), _jsx(Heading, { level: 1, children: group.title }), group.description && (_jsx(Text, { color: "muted", children: group.description })), _jsxs(Text, { size: "sm", color: "muted", children: [members.length, " item", members.length === 1 ? "" : "s", " in this group"] })] }), members.length > 0 ? (_jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "lg", children: "Pick what you want" }), _jsx(Text, { size: "sm", color: "muted", children: "Choose a quantity for each item \u2014 they'll go into your cart as a single line you can edit later." }), _jsx(GroupMemberPicker, { groupId: group.id, groupSource: "grouped-listing", members: members,
18
+ /*
19
+ * Off the group document this view already loaded. The picker
20
+ * and `addGroupLineToCart` run the SAME `checkGroupRules`, so
21
+ * passing them here only changes WHEN the buyer learns a
22
+ * selection is invalid — not whether it is refused.
23
+ */
24
+ rules: readGroupRules(group) })] })) : (_jsx(Text, { color: "muted", children: "Nothing in this group is available right now." }))] }) }) }));
17
25
  }
@@ -39,6 +39,11 @@ export interface GroupedListingDocument extends BaseDocument {
39
39
  /** Computed from activeMemberCount < minActiveMembers. */
40
40
  visibilityStatus: GroupVisibility;
41
41
  isActive: boolean;
42
+ groupMinMembers?: number;
43
+ groupMaxMembers?: number;
44
+ groupMaxUnitsPerMember?: number;
45
+ groupRequiredMemberIds?: string[];
46
+ groupDiscountPercent?: number;
42
47
  isFeatured: boolean;
43
48
  storeId?: string;
44
49
  brandSlug?: string;
@@ -2,17 +2,17 @@ import { z } from "zod";
2
2
  import type { HomepageSectionDocument } from "../schemas";
3
3
  import type { FirebaseSieveResult } from "../../../providers/db-firebase";
4
4
  export declare const createSectionSchema: z.ZodObject<{
5
- type: z.ZodEnum<["welcome", "carousel", "stats", "trust-indicators", "categories", "brands", "products", "pre-orders", "auctions", "banner", "features", "reviews", "whatsapp-community", "faq", "blog-articles", "newsletter", "stores", "events", "social-feed", "custom-cards", "google-reviews", "featured-bundles", "prize-draws", "event-raffles", "collection-cards"]>;
5
+ type: z.ZodEnum<[import("../schemas").SectionType, ...import("../schemas").SectionType[]]>;
6
6
  enabled: z.ZodDefault<z.ZodBoolean>;
7
7
  order: z.ZodDefault<z.ZodNumber>;
8
8
  config: z.ZodOptional<z.ZodObject<{}, "passthrough", z.ZodTypeAny, z.objectOutputType<{}, z.ZodTypeAny, "passthrough">, z.objectInputType<{}, z.ZodTypeAny, "passthrough">>>;
9
9
  }, "strip", z.ZodTypeAny, {
10
- type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
10
+ type: import("../schemas").SectionType;
11
11
  enabled: boolean;
12
12
  order: number;
13
13
  config?: z.objectOutputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
14
14
  }, {
15
- type: "banner" | "stores" | "products" | "pre-orders" | "prize-draws" | "auctions" | "reviews" | "events" | "categories" | "brands" | "carousel" | "newsletter" | "features" | "stats" | "welcome" | "trust-indicators" | "whatsapp-community" | "faq" | "blog-articles" | "social-feed" | "custom-cards" | "google-reviews" | "featured-bundles" | "event-raffles" | "collection-cards";
15
+ type: import("../schemas").SectionType;
16
16
  enabled?: boolean | undefined;
17
17
  order?: number | undefined;
18
18
  config?: z.objectInputType<{}, z.ZodTypeAny, "passthrough"> | undefined;
@@ -1,13 +1,35 @@
1
1
  import type { CollectionCardsSectionConfig } from "../schemas/firestore";
2
+ import type { CategoryItem } from "../../categories/types";
3
+ import type { StoreListItem } from "../../stores/types";
2
4
  export interface CollectionCardsSectionProps {
3
5
  config: CollectionCardsSectionConfig;
6
+ /**
7
+ * Pre-fetched items for the entry types that are actually wired — see the
8
+ * module docstring below for which ones and why those three specifically.
9
+ */
10
+ sectionData?: {
11
+ categories?: CategoryItem[];
12
+ brands?: CategoryItem[];
13
+ stores?: StoreListItem[];
14
+ };
4
15
  }
5
16
  /**
6
- * Renders a single homepage strip that mixes multiple resource types (products,
7
- * stores, blog, etc.) — driven entirely from config.
17
+ * Renders a single homepage strip that mixes multiple resource types
18
+ * (products, stores, blog, etc.), driven entirely from config.
8
19
  *
9
- * Each entry's data fetch wires into the existing per-resource repositories.
10
- * Until the data wiring lands the section renders the configured collection
11
- * tabs + a placeholder, exercising the schema/admin plumbing end-to-end.
20
+ * 🛑 `categories` / `brands` / `stores` entries render REAL data as of
21
+ * 2026-10-19 — reusing `sectionData` that `MarketplaceHomepageView` already
22
+ * fetches for the dedicated `categories`/`brands`/`stores` sections, so this
23
+ * costs zero extra round-trips (Rule #6). Those three were chosen first
24
+ * because they are the PERMANENT axes: populated independently of the
25
+ * product catalogue's size, so a `collection-cards` section built from them
26
+ * can never go empty the way a `"products"`/`"auctions"` entry could.
27
+ *
28
+ * Every other entry type (`products`, `auctions`, `pre-orders`, `events`,
29
+ * `blog-posts`, `reviews`) still renders the honest placeholder — a real
30
+ * "not wired yet" state is correct here, not a bug to paper over with
31
+ * fabricated data, and the earlier revision's "data wiring lands with the
32
+ * bundle/raffle work" message survives per-entry rather than as a blanket
33
+ * claim about the whole section.
12
34
  */
13
- export declare function CollectionCardsSection({ config, }: CollectionCardsSectionProps): Promise<import("react").JSX.Element | null>;
35
+ export declare function CollectionCardsSection({ config, sectionData, }: CollectionCardsSectionProps): Promise<import("react").JSX.Element | null>;
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link.js";
3
- import { Container, Heading, Row, Section, Span, Stack, Text, } from "../../../ui/index.js";
3
+ import { Container, Div, Grid, Heading, Row, Section, Span, Stack, Text, } from "../../../ui/index.js";
4
+ import { MediaImage } from "../../media/MediaImage.js";
5
+ import { ROUTES } from "../../../next/routing/route-map.js";
4
6
  const DEFAULT_ITEMS_PER_ROW = 4;
5
7
  const DEFAULT_MAX_ITEMS = 12;
6
8
  function entryLabel(entry) {
@@ -19,14 +21,25 @@ function entryLabel(entry) {
19
21
  }
20
22
  }
21
23
  /**
22
- * Renders a single homepage strip that mixes multiple resource types (products,
23
- * stores, blog, etc.) — driven entirely from config.
24
+ * Renders a single homepage strip that mixes multiple resource types
25
+ * (products, stores, blog, etc.), driven entirely from config.
24
26
  *
25
- * Each entry's data fetch wires into the existing per-resource repositories.
26
- * Until the data wiring lands the section renders the configured collection
27
- * tabs + a placeholder, exercising the schema/admin plumbing end-to-end.
27
+ * 🛑 `categories` / `brands` / `stores` entries render REAL data as of
28
+ * 2026-10-19 — reusing `sectionData` that `MarketplaceHomepageView` already
29
+ * fetches for the dedicated `categories`/`brands`/`stores` sections, so this
30
+ * costs zero extra round-trips (Rule #6). Those three were chosen first
31
+ * because they are the PERMANENT axes: populated independently of the
32
+ * product catalogue's size, so a `collection-cards` section built from them
33
+ * can never go empty the way a `"products"`/`"auctions"` entry could.
34
+ *
35
+ * Every other entry type (`products`, `auctions`, `pre-orders`, `events`,
36
+ * `blog-posts`, `reviews`) still renders the honest placeholder — a real
37
+ * "not wired yet" state is correct here, not a bug to paper over with
38
+ * fabricated data, and the earlier revision's "data wiring lands with the
39
+ * bundle/raffle work" message survives per-entry rather than as a blanket
40
+ * claim about the whole section.
28
41
  */
29
- export async function CollectionCardsSection({ config, }) {
42
+ export async function CollectionCardsSection({ config, sectionData, }) {
30
43
  const title = config.title ?? "Featured Collections";
31
44
  const subtitle = config.subtitle;
32
45
  const collections = Array.isArray(config.collections) ? config.collections : [];
@@ -37,5 +50,27 @@ export async function CollectionCardsSection({ config, }) {
37
50
  if (collections.length === 0) {
38
51
  return null;
39
52
  }
40
- return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), showTabs ? (_jsx(Row, { gap: "sm", wrap: true, children: collections.slice(0, 3).map((entry, idx) => (_jsx(Span, { border: "default", padding: "x-sm", className: "py-[0.25rem]", rounded: "full", color: "muted", size: "xs", weight: "medium", children: entryLabel(entry) }, `${entry.type}-${idx}`))) })) : null, _jsxs(Stack, { align: "start", gap: "sm", className: "border-dashed text-left", border: "strong", paddingY: "y-3xl", paddingX: "x-lg", rounded: "2xl", children: [_jsxs(Text, { size: "sm", color: "muted", children: ["Mixed collection rendering (", collections.length, "/3 collection", collections.length === 1 ? "" : "s", ", up to ", maxItems, " items,", " ", itemsPerRow, " per row) \u2014 data wiring lands with the bundle/raffle work."] }), cta ? (_jsxs(Link, { href: cta.href, className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: [cta.label, " \u2192"] })) : null] })] }) }) }));
53
+ const wiredEntries = collections.filter((e) => e.type === "categories" || e.type === "brands" || e.type === "stores");
54
+ const unwiredEntries = collections.filter((e) => e.type !== "categories" && e.type !== "brands" && e.type !== "stores");
55
+ return (_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Heading, { level: 2, size: "2xl", weight: "semibold", color: "primary", children: title }), subtitle ? (_jsx(Text, { size: "sm", color: "muted", children: subtitle })) : null] }), showTabs ? (_jsx(Row, { gap: "sm", wrap: true, children: collections.slice(0, 3).map((entry, idx) => (_jsx(Span, { border: "default", padding: "x-sm", className: "py-[0.25rem]", rounded: "full", color: "muted", size: "xs", weight: "medium", children: entryLabel(entry) }, `${entry.type}-${idx}`))) })) : null, wiredEntries.map((entry, idx) => {
56
+ const rows = entry.type === "categories"
57
+ ? (sectionData?.categories ?? [])
58
+ .slice(0, entry.limit ?? maxItems)
59
+ .map((c) => ({ slug: c.slug, name: c.name, image: c.display?.coverImage }))
60
+ : entry.type === "brands"
61
+ ? (sectionData?.brands ?? [])
62
+ .slice(0, entry.limit ?? maxItems)
63
+ .map((b) => ({ slug: b.slug, name: b.name, image: b.display?.coverImage }))
64
+ : (sectionData?.stores ?? [])
65
+ .slice(0, entry.limit ?? maxItems)
66
+ .map((s) => ({ slug: s.storeSlug, name: s.storeName, image: s.storeLogoURL }));
67
+ if (rows.length === 0)
68
+ return null;
69
+ const hrefFor = (slug) => entry.type === "categories"
70
+ ? String(ROUTES.PUBLIC.CATEGORY_DETAIL(slug))
71
+ : entry.type === "brands"
72
+ ? String(ROUTES.PUBLIC.BRAND_DETAIL(slug))
73
+ : String(ROUTES.PUBLIC.STORE_DETAIL(slug));
74
+ return (_jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "semibold", color: "muted", children: entryLabel(entry) }), _jsx(Grid, { cols: "categoryCards", gap: "md", children: rows.map((row) => (_jsx(Link, { href: hrefFor(row.slug), className: "group rounded-xl border border-[var(--appkit-color-border)] transition-all hover:border-primary-300 hover:shadow-sm dark:hover:border-primary-600", children: _jsxs(Stack, { align: "center", gap: "xs", padding: "sm", children: [_jsx(Div, { className: "h-16 w-16", rounded: "lg", overflow: "hidden", children: _jsx(MediaImage, { src: row.image, alt: row.name, size: "thumbnail" }) }), _jsx(Text, { size: "xs", weight: "medium", className: "line-clamp-2", children: row.name })] }) }, row.slug))) })] }, `${entry.type}-${idx}`));
75
+ }), unwiredEntries.length > 0 && (_jsx(Stack, { align: "start", gap: "sm", className: "border-dashed text-left", border: "strong", paddingY: "y-3xl", paddingX: "x-lg", rounded: "2xl", children: _jsxs(Text, { size: "sm", color: "muted", children: [unwiredEntries.map(entryLabel).join(", "), " \u2014 up to ", maxItems, " ", "items, ", itemsPerRow, " per row when wired \u2014 data wiring lands with the bundle/raffle work."] }) })), cta ? (_jsxs(Link, { href: cta.href, className: "text-[length:var(--appkit-text-sm)] font-medium text-primary hover:underline", children: [cta.label, " \u2192"] })) : null] }) }) }));
41
76
  }
@@ -1,12 +1,23 @@
1
1
  import React from "react";
2
+ import type { IconKey } from "../../../ui";
3
+ /**
4
+ * Promoted to a registered `SectionType` ("how-it-works") 2026-10-19 — see
5
+ * `HowItWorksSectionConfig` in `schemas/firestore.ts`, which this prop shape
6
+ * mirrors exactly (Firestore config must be JSON-serialisable; a function
7
+ * value cannot round-trip through a document write).
8
+ *
9
+ * `renderIcon?: (props) => ReactNode` was the blocker: a function prop has no
10
+ * representation in a Firestore document. Replaced with `iconKey?: IconKey`,
11
+ * rendered through the shared `<Icon>` registry primitive (Root Cause #80) —
12
+ * the same indirection `TrustBadge.iconKey` already uses for this exact
13
+ * problem. `accentFrom` is dropped: declared on the old type, never read
14
+ * anywhere in this component.
15
+ */
2
16
  export interface HowItWorksStep {
3
17
  number: number;
4
18
  title: string;
5
19
  desc: string;
6
- renderIcon?: (props: {
7
- className?: string;
8
- }) => React.ReactNode;
9
- accentFrom?: string;
20
+ iconKey?: IconKey;
10
21
  iconColor?: string;
11
22
  iconBg?: string;
12
23
  badgeBg?: string;
@@ -16,8 +27,14 @@ export interface HowItWorksSectionProps {
16
27
  subtitle?: string;
17
28
  pillLabel?: string;
18
29
  ctaLabel?: string;
19
- onCtaClick?: () => void;
30
+ /**
31
+ * A link, not a handler — `onCtaClick?: () => void` could not survive a
32
+ * Firestore round-trip either. Renders as an `<Anchor>`, matching how every
33
+ * other promoted section's CTA resolves (`EventRafflesSectionConfig`'s
34
+ * `cta`, `FeaturedBundlesSectionConfig`'s `viewMoreLabel` pattern, etc.).
35
+ */
36
+ ctaLink?: string;
20
37
  steps: HowItWorksStep[];
21
38
  className?: string;
22
39
  }
23
- export declare function HowItWorksSection({ title, subtitle, pillLabel, ctaLabel, onCtaClick, steps, className, }: HowItWorksSectionProps): React.JSX.Element;
40
+ export declare function HowItWorksSection({ title, subtitle, pillLabel, ctaLabel, ctaLink, steps, className, }: HowItWorksSectionProps): React.JSX.Element;
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { THEMED_TEXT_PRIMARY, THEMED_TEXT_SECONDARY } from "../../../_internal/shared/styles/themed.js";
5
- import { Button, Div, Grid, Heading, Row, Section, Span, Text } from "../../../ui/index.js";
5
+ import { Anchor, Button, Div, Grid, Heading, Icon, Row, Section, Span, Text } from "../../../ui/index.js";
6
6
  // --- Constants ---------------------------------------------------------------
7
7
  const CLS_VISIBLE = "opacity-100 translate-y-0";
8
8
  const STEP_DELAYS = ["delay-0", "delay-150", "delay-300", "delay-[450ms]"];
@@ -16,10 +16,10 @@ function StepCard({ step, visible, delayClass, }) {
16
16
  "transition-all duration-300",
17
17
  delayClass,
18
18
  visible ? CLS_VISIBLE : "opacity-0 translate-y-8",
19
- ].join(" "), "data-section": "howitworkssection-div-340", children: [_jsx(Span, { gradient: "brand", size: "7xl", family: "display", className: "absolute top-4 right-5 opacity-10 select-none pointer-events-none leading-none", "aria-hidden": "true", children: step.number }), _jsx(Row, { textWeight: "bold", textSize: "sm", className: `relative z-10 w-10 h-10 ${badgeBg} text-white mb-5`, align: "center", justify: "center", rounded: "full", shadow: "md", children: step.number }), step.renderIcon && (_jsx(Row, { className: `relative z-10 w-14 h-14 ${iconBg} mb-4 border border-white/80 dark:border-[var(--appkit-color-border)]`, align: "center", justify: "center", rounded: "2xl", children: _jsx(Span, { className: `${iconColor}`, "aria-hidden": "true", children: step.renderIcon({ className: "w-6 h-6" }) }) })), _jsx(Heading, { level: 3, className: `relative z-10 ${THEMED_TEXT_PRIMARY} mb-2`, size: "base", weight: "semibold", children: step.title }), _jsx(Text, { className: `relative z-10 ${THEMED_TEXT_SECONDARY} leading-relaxed`, size: "sm", children: step.desc })] }));
19
+ ].join(" "), "data-section": "howitworkssection-div-340", children: [_jsx(Span, { gradient: "brand", size: "7xl", family: "display", className: "absolute top-4 right-5 opacity-10 select-none pointer-events-none leading-none", "aria-hidden": "true", children: step.number }), _jsx(Row, { textWeight: "bold", textSize: "sm", className: `relative z-10 w-10 h-10 ${badgeBg} text-white mb-5`, align: "center", justify: "center", rounded: "full", shadow: "md", children: step.number }), step.iconKey && (_jsx(Row, { className: `relative z-10 w-14 h-14 ${iconBg} mb-4 border border-white/80 dark:border-[var(--appkit-color-border)]`, align: "center", justify: "center", rounded: "2xl", children: _jsx(Span, { className: `${iconColor}`, "aria-hidden": "true", children: _jsx(Icon, { name: step.iconKey, size: "lg" }) }) })), _jsx(Heading, { level: 3, className: `relative z-10 ${THEMED_TEXT_PRIMARY} mb-2`, size: "base", weight: "semibold", children: step.title }), _jsx(Text, { className: `relative z-10 ${THEMED_TEXT_SECONDARY} leading-relaxed`, size: "sm", children: step.desc })] }));
20
20
  }
21
21
  // --- Section -----------------------------------------------------------------
22
- export function HowItWorksSection({ title, subtitle, pillLabel, ctaLabel, onCtaClick, steps, className = "", }) {
22
+ export function HowItWorksSection({ title, subtitle, pillLabel, ctaLabel, ctaLink, steps, className = "", }) {
23
23
  const themed = { textPrimary: THEMED_TEXT_PRIMARY, textSecondary: THEMED_TEXT_SECONDARY };
24
24
  const sectionRef = useRef(null);
25
25
  const [visible, setVisible] = useState(false);
@@ -36,5 +36,5 @@ export function HowItWorksSection({ title, subtitle, pillLabel, ctaLabel, onCtaC
36
36
  observer.observe(el);
37
37
  return () => observer.disconnect();
38
38
  }, []);
39
- return (_jsx(Section, { ref: sectionRef, className: `${className}`, surface: "muted", padding: "xl", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [_jsxs(Div, { className: `text-left mb-12 transition-all duration-700 ${visible ? CLS_VISIBLE : "opacity-0 translate-y-4"}`, children: [pillLabel && (_jsx(Div, { layout: "inline-flex", gap: "2", align: "center", textWeight: "medium", textSize: "xs", className: /* audit-variant-ok: colour+alpha border (primary-500/30); the `border` variant takes a token name, not an rgba step */ "mb-4 border border-primary-500/30 bg-primary-500/10 py-[0.375rem] tracking-[0.2em] uppercase text-primary-700 dark:text-primary-400 backdrop-blur-sm", padding: "x-md", rounded: "full", children: pillLabel })), _jsx(Heading, { level: 2, className: `${themed.textPrimary} mb-3`, size: "3xl", mdSize: "4xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `${themed.textSecondary} max-w-xl`, size: "base", children: subtitle }))] }), _jsx(Grid, { gap: "lg", className: "grid-cols-1 md:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-3 mb-10", children: steps.map((step, i) => (_jsx(StepCard, { step: step, visible: visible, delayClass: STEP_DELAYS[Math.min(i, STEP_DELAYS.length - 1)] }, step.number))) }), ctaLabel && onCtaClick && (_jsx(Div, { className: `text-left transition-all duration-700 delay-500 ${visible ? CLS_VISIBLE : "opacity-0 translate-y-4"}`, children: _jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel }) }))] }) }));
39
+ return (_jsx(Section, { ref: sectionRef, className: `${className}`, surface: "muted", padding: "xl", children: _jsxs(Div, { className: "max-w-6xl mx-auto", children: [_jsxs(Div, { className: `text-left mb-12 transition-all duration-700 ${visible ? CLS_VISIBLE : "opacity-0 translate-y-4"}`, children: [pillLabel && (_jsx(Div, { layout: "inline-flex", gap: "2", align: "center", textWeight: "medium", textSize: "xs", className: /* audit-variant-ok: colour+alpha border (primary-500/30); the `border` variant takes a token name, not an rgba step */ "mb-4 border border-primary-500/30 bg-primary-500/10 py-[0.375rem] tracking-[0.2em] uppercase text-primary-700 dark:text-primary-400 backdrop-blur-sm", padding: "x-md", rounded: "full", children: pillLabel })), _jsx(Heading, { level: 2, className: `${themed.textPrimary} mb-3`, size: "3xl", mdSize: "4xl", weight: "bold", children: title }), subtitle && (_jsx(Text, { className: `${themed.textSecondary} max-w-xl`, size: "base", children: subtitle }))] }), _jsx(Grid, { gap: "lg", className: "grid-cols-1 md:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-3 mb-10", children: steps.map((step, i) => (_jsx(StepCard, { step: step, visible: visible, delayClass: STEP_DELAYS[Math.min(i, STEP_DELAYS.length - 1)] }, step.number))) }), ctaLabel && ctaLink && (_jsx(Div, { className: `text-left transition-all duration-700 delay-500 ${visible ? CLS_VISIBLE : "opacity-0 translate-y-4"}`, children: _jsx(Button, { variant: "primary", size: "lg", asChild: true, children: _jsx(Anchor, { href: ctaLink, tone: "none", children: ctaLabel }) }) }))] }) }));
40
40
  }
@@ -1,6 +1,35 @@
1
- import type { PromoBanner } from "../types";
1
+ /** Hard cap — this layout only has slots for 4 (one hero tile + three). */
2
+ export declare const PROMO_GRID_MAX_BANNERS: 4;
3
+ /**
4
+ * Promoted to a registered `SectionType` ("promo-grid") 2026-10-19 — see
5
+ * `PromoGridSectionConfig` in `schemas/firestore.ts`.
6
+ *
7
+ * Two fixes made in the same pass:
8
+ * 1. `resolveMediaUrl(banner.image)!` was a non-null assertion on a function
9
+ * that genuinely returns `undefined` for an empty slug — an admin saving a
10
+ * banner with no image picked would crash `next/image`. The image slot now
11
+ * simply renders nothing: the `<Link>` already carries its own dark
12
+ * background and the gradient + caption paint on top regardless, so a
13
+ * placeholder tile would have been a second element doing what the card
14
+ * behind it already does.
15
+ * 2. `--dark-section-alt` / `--dark-section-card` / `--color-red` /
16
+ * `--font-bangers` were undefined anywhere in the codebase — this
17
+ * component would have rendered on a transparent background with no
18
+ * borders. The section now uses the `media-dark` SURFACE TOKEN (the real
19
+ * theme-invariant dark preset), plus `text-error` / `family="display"`.
20
+ * It is dark in every theme by design — see `PromoGridSectionConfig`'s
21
+ * docstring for why no admin background field was added (Root Cause #67).
22
+ */
23
+ export interface PromoBannerConfig {
24
+ id: string;
25
+ title: string;
26
+ ctaLabel: string;
27
+ ctaUrl: string;
28
+ /** A media slug (`/media/<id>`), not a raw URL — resolved via `resolveMediaUrl`. */
29
+ image: string;
30
+ }
2
31
  export interface PromoGridProps {
3
- banners: PromoBanner[];
32
+ banners: PromoBannerConfig[];
4
33
  /** Eyebrow label above the section heading. Default: "LIMITED TIME". */
5
34
  eyebrow?: string;
6
35
  /** Section heading. Default: "HOT DEALS & PROMOS". */
@@ -6,10 +6,19 @@ import { resolveMediaUrl } from "../../../utils/media-url.js";
6
6
  const __P = {
7
7
  p4: "p-[var(--appkit-space-4)]",
8
8
  };
9
+ /** Hard cap — this layout only has slots for 4 (one hero tile + three). */
10
+ export const PROMO_GRID_MAX_BANNERS = 4;
9
11
  export function PromoGrid({ banners, eyebrow = "LIMITED TIME", heading = "HOT DEALS & PROMOS", }) {
10
12
  if (banners.length === 0)
11
13
  return null;
12
- return (_jsx(Section, { paddingY: "y-4xl", className: "bg-[var(--dark-section-alt)] [border-top:1px_solid_rgba(255,255,255,0.06)] [border-bottom:1px_solid_rgba(255,255,255,0.06)]", children: _jsxs(Div, { className: "mx-auto max-w-7xl", padding: "x-md", children: [_jsx(Row, { wrap: true, align: "end", justify: "between", gap: "sm", className: "mb-7 gap-y-2", children: _jsxs(Div, { children: [_jsx(Text, { className: "mb-1 font-black uppercase tracking-widest text-[var(--color-red)] tracking-[0.18em]", size: "xs", children: eyebrow }), _jsx(Heading, { level: 2, className: "[font-family:var(--font-bangers,Bangers,cursive)] text-[clamp(1.6rem,4vw,2.4rem)] tracking-[0.08em] text-white leading-none", children: heading })] }) }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: banners.slice(0, 4).map((banner, i) => (_jsxs(Link, { href: banner.ctaUrl, className: `group relative block overflow-hidden bg-[var(--dark-section-card)] ${i === 0 ? "sm:row-span-2 min-h-[clamp(220px,32vh,480px)]" : "min-h-[clamp(130px,16vh,240px)]"}`, children: [_jsx(Image, { src: resolveMediaUrl(banner.image), alt: banner.title, fill: true, className: "object-cover transition-transform duration-500 group-hover:scale-105", sizes: i === 0
13
- ? "(max-width: 640px) 100vw, 40vw"
14
- : "(max-width: 640px) 50vw, 30vw" }), _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 ring-2 ring-[var(--color-yellow)] ring-inset" }), _jsxs(Div, { className: `absolute bottom-0 left-0 right-0 ${__P.p4}`, children: [_jsx(Text, { className: `leading-tight [font-family:var(--font-bangers,Bangers,cursive)] tracking-[0.06em] text-white ${i === 0 ? "text-[1.35rem]" : "text-[1rem]"}`, children: banner.title }), _jsxs(Span, { layout: "inline-flex", gap: "xs", className: "mt-1 font-black uppercase text-[var(--color-yellow)]", size: "xs", children: [banner.ctaLabel, " \u2192"] })] })] }, banner.id))) })] }) }));
14
+ return (_jsx(Section, { paddingY: "y-4xl",
15
+ // `media-dark` IS the theme-invariant dark surface token (bg-zinc-900)
16
+ // — exactly what this always-dark section needs, so no raw className
17
+ // colour and no suppression marker.
18
+ surface: "media-dark", className: "border-t border-b border-white/[0.06]", children: _jsxs(Div, { className: "mx-auto max-w-7xl", padding: "x-md", children: [_jsx(Row, { wrap: true, align: "end", justify: "between", gap: "sm", className: "mb-7 gap-y-2", children: _jsxs(Div, { children: [_jsx(Text, { className: "mb-1 font-black uppercase tracking-widest text-error tracking-[0.18em]", size: "xs", children: eyebrow }), _jsx(Heading, { level: 2, family: "display", className: "tracking-[0.08em] leading-none text-white", size: "xl", mdSize: "2xl", weight: "bold", children: heading })] }) }), _jsx(Div, { layout: "grid", gap: "3", className: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3", children: banners.slice(0, PROMO_GRID_MAX_BANNERS).map((banner, i) => {
19
+ const resolvedSrc = resolveMediaUrl(banner.image);
20
+ return (_jsxs(Link, { href: banner.ctaUrl, className: `group relative block overflow-hidden bg-zinc-900 ${i === 0 ? "sm:row-span-2 min-h-[clamp(220px,32vh,480px)]" : "min-h-[clamp(130px,16vh,240px)]"}`, children: [resolvedSrc ? (_jsx(Image, { src: resolvedSrc, alt: banner.title, fill: true, className: "object-cover transition-transform duration-500 group-hover:scale-105", sizes: i === 0
21
+ ? "(max-width: 640px) 100vw, 40vw"
22
+ : "(max-width: 640px) 50vw, 30vw" })) : null, _jsx(Div, { className: `absolute inset-0 ${i === 0 ? "[background:linear-gradient(to_top,rgba(0,0,0,0.9)_0%,rgba(0,0,0,0.3)_50%,transparent_100%)]" : "[background:linear-gradient(to_top,rgba(0,0,0,0.85)_0%,rgba(0,0,0,0.15)_100%)]"}` }), _jsx(Div, { className: "absolute inset-0 pointer-events-none opacity-0 transition-opacity duration-300 group-hover:opacity-100 ring-2 ring-[var(--color-yellow)] ring-inset" }), _jsxs(Div, { className: `absolute bottom-0 left-0 right-0 ${__P.p4}`, children: [_jsx(Text, { family: "display", className: `leading-tight tracking-[0.06em] text-white ${i === 0 ? "text-[1.35rem]" : "text-[1rem]"}`, children: banner.title }), _jsxs(Span, { layout: "inline-flex", gap: "xs", className: "mt-1 font-black uppercase text-[var(--color-yellow)]", size: "xs", children: [banner.ctaLabel, " \u2192"] })] })] }, banner.id));
23
+ }) })] }) }));
15
24
  }