@mohasinac/appkit 4.53.0 → 4.53.1

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 (500) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/scaffolds/AppShell.js +1 -1
  3. package/dist/_internal/server/features/auctions/og.d.ts +3 -1
  4. package/dist/_internal/server/features/auctions/og.js +3 -1
  5. package/dist/_internal/server/features/bundles/actions.js +1 -1
  6. package/dist/_internal/server/features/business-documents/download.d.ts +41 -0
  7. package/dist/_internal/server/features/business-documents/download.js +91 -0
  8. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  9. package/dist/_internal/server/features/categories/adapters.js +10 -0
  10. package/dist/_internal/server/features/checkout/actions.d.ts +14 -0
  11. package/dist/_internal/server/features/checkout/actions.js +106 -12
  12. package/dist/_internal/server/features/checkout/bundle-expansion.js +17 -0
  13. package/dist/_internal/server/features/checkout/data.d.ts +22 -0
  14. package/dist/_internal/server/features/checkout/group-rule-revalidation.d.ts +34 -0
  15. package/dist/_internal/server/features/checkout/group-rule-revalidation.js +124 -0
  16. package/dist/_internal/server/features/checkout/supplier-identity.d.ts +55 -0
  17. package/dist/_internal/server/features/checkout/supplier-identity.js +81 -0
  18. package/dist/_internal/server/features/classified/metadata.js +1 -1
  19. package/dist/_internal/server/features/classified/og.d.ts +3 -1
  20. package/dist/_internal/server/features/classified/og.js +3 -1
  21. package/dist/_internal/server/features/digital-code/metadata.js +1 -1
  22. package/dist/_internal/server/features/digital-code/og.d.ts +3 -1
  23. package/dist/_internal/server/features/digital-code/og.js +3 -1
  24. package/dist/_internal/server/features/digital-code/pool.d.ts +19 -4
  25. package/dist/_internal/server/features/digital-code/pool.js +19 -4
  26. package/dist/_internal/server/features/live/metadata.js +1 -1
  27. package/dist/_internal/server/features/live/og.d.ts +3 -1
  28. package/dist/_internal/server/features/live/og.js +3 -1
  29. package/dist/_internal/server/features/messages/actions.js +1 -1
  30. package/dist/_internal/server/features/parts/og.js +1 -1
  31. package/dist/_internal/server/features/pre-orders/og.d.ts +3 -1
  32. package/dist/_internal/server/features/pre-orders/og.js +3 -1
  33. package/dist/_internal/server/features/price-index/search.d.ts +18 -0
  34. package/dist/_internal/server/features/price-index/search.js +47 -7
  35. package/dist/_internal/server/features/prize-draws/og.d.ts +3 -1
  36. package/dist/_internal/server/features/prize-draws/og.js +3 -1
  37. package/dist/_internal/server/features/products/group-members.d.ts +26 -0
  38. package/dist/_internal/server/features/products/group-members.js +7 -1
  39. package/dist/_internal/server/features/products/og.d.ts +3 -1
  40. package/dist/_internal/server/features/products/og.js +3 -1
  41. package/dist/_internal/server/features/search/scoped-sources.d.ts +26 -0
  42. package/dist/_internal/server/features/search/scoped-sources.js +252 -0
  43. package/dist/_internal/server/features/search/search-everything.d.ts +57 -0
  44. package/dist/_internal/server/features/search/search-everything.js +70 -20
  45. package/dist/_internal/server/features/seo/sitemap.js +6 -0
  46. package/dist/_internal/server/features/shared/listing-adapters.js +4 -2
  47. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +11 -0
  48. package/dist/_internal/server/features/sticker-trace/adapters.js +1 -0
  49. package/dist/_internal/server/features/sticker-trace/promote-to-listings.d.ts +26 -0
  50. package/dist/_internal/server/features/sticker-trace/promote-to-listings.js +264 -0
  51. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +3 -1
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +32 -0
  53. package/dist/_internal/server/features/sticker-trace/storage.d.ts +20 -1
  54. package/dist/_internal/server/features/sticker-trace/storage.js +20 -1
  55. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  56. package/dist/_internal/server/features/stores/adapters.js +16 -0
  57. package/dist/_internal/server/features/stores/index.d.ts +1 -0
  58. package/dist/_internal/server/features/stores/index.js +1 -0
  59. package/dist/_internal/server/features/stores/tabs.d.ts +25 -0
  60. package/dist/_internal/server/features/stores/tabs.js +122 -0
  61. package/dist/_internal/shared/checkout/rules/_defaults.js +0 -2
  62. package/dist/_internal/shared/checkout/rules/_limits.d.ts +14 -1
  63. package/dist/_internal/shared/checkout/rules/_limits.js +14 -1
  64. package/dist/_internal/shared/checkout/rules/_registry.js +2 -0
  65. package/dist/_internal/shared/checkout/rules/auction.rule.js +9 -1
  66. package/dist/_internal/shared/checkout/rules/bundle.rule.js +13 -0
  67. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  68. package/dist/_internal/shared/checkout/rules/index.js +9 -0
  69. package/dist/_internal/shared/checkout/rules/offer.rule.js +0 -1
  70. package/dist/_internal/shared/checkout/rules/payment-gate.d.ts +26 -0
  71. package/dist/_internal/shared/checkout/rules/payment-gate.js +133 -0
  72. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +29 -8
  73. package/dist/_internal/shared/checkout/rules/spares.rule.d.ts +16 -0
  74. package/dist/_internal/shared/checkout/rules/spares.rule.js +5 -0
  75. package/dist/_internal/shared/checkout/rules/types.d.ts +26 -2
  76. package/dist/_internal/shared/config/schema.d.ts +122 -0
  77. package/dist/_internal/shared/features/cart/schema.js +3 -2
  78. package/dist/_internal/shared/features/products/group-rules.d.ts +75 -0
  79. package/dist/_internal/shared/features/products/group-rules.js +142 -0
  80. package/dist/_internal/shared/listing-types/_registry.d.ts +24 -0
  81. package/dist/_internal/shared/listing-types/_registry.js +39 -1
  82. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  83. package/dist/_internal/shared/listing-types/art/config.js +2 -0
  84. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  85. package/dist/_internal/shared/listing-types/auction/config.js +2 -0
  86. package/dist/_internal/shared/listing-types/availability.js +48 -0
  87. package/dist/_internal/shared/listing-types/capabilities.js +19 -0
  88. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  89. package/dist/_internal/shared/listing-types/classified/config.js +2 -0
  90. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  91. package/dist/_internal/shared/listing-types/digital-code/config.js +2 -0
  92. package/dist/_internal/shared/listing-types/feature-flags.js +1 -0
  93. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  94. package/dist/_internal/shared/listing-types/live/config.js +2 -0
  95. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  96. package/dist/_internal/shared/listing-types/pre-order/config.js +2 -0
  97. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  98. package/dist/_internal/shared/listing-types/prize-draw/config.js +2 -0
  99. package/dist/_internal/shared/listing-types/spares/config.d.ts +74 -0
  100. package/dist/_internal/shared/listing-types/spares/config.js +60 -0
  101. package/dist/_internal/shared/listing-types/spares/ctas.d.ts +1 -0
  102. package/dist/_internal/shared/listing-types/spares/ctas.js +4 -0
  103. package/dist/_internal/shared/listing-types/spares/og.js +1 -0
  104. package/dist/_internal/shared/listing-types/spares/schema.d.ts +2 -0
  105. package/dist/_internal/shared/listing-types/spares/schema.js +3 -0
  106. package/dist/_internal/shared/listing-types/spares/seed-factory.d.ts +1 -0
  107. package/dist/_internal/shared/listing-types/spares/seed-factory.js +1 -0
  108. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  109. package/dist/_internal/shared/listing-types/standard/config.js +2 -0
  110. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  111. package/dist/_internal/shared/listing-types/stickers/config.js +2 -0
  112. package/dist/_internal/shared/search/match.d.ts +11 -0
  113. package/dist/_internal/shared/search/match.js +2 -1
  114. package/dist/_internal/shared/search/to-groups.d.ts +52 -0
  115. package/dist/_internal/shared/search/to-groups.js +141 -0
  116. package/dist/client.d.ts +8 -2
  117. package/dist/client.js +4 -1
  118. package/dist/constants/api-endpoints.d.ts +39 -15
  119. package/dist/constants/api-endpoints.js +28 -5
  120. package/dist/constants/cache-invalidation.js +3 -1
  121. package/dist/constants/field-names.d.ts +6 -0
  122. package/dist/constants/field-names.js +6 -0
  123. package/dist/features/about/components/PolicyPageView.d.ts +22 -2
  124. package/dist/features/about/components/PolicyPageView.js +14 -2
  125. package/dist/features/about/components/PublicProfileView.js +2 -1
  126. package/dist/features/about/components/ShippingPolicyView.js +42 -2
  127. package/dist/features/account/components/NotificationBell.js +1 -1
  128. package/dist/features/account/hooks/useProfileStats.js +5 -1
  129. package/dist/features/addresses/repository/addresses.repository.d.ts +16 -0
  130. package/dist/features/addresses/repository/addresses.repository.js +21 -0
  131. package/dist/features/admin/actions/admin-actions.js +16 -1
  132. package/dist/features/admin/components/AdminCategoryEditorView.js +19 -2
  133. package/dist/features/admin/components/AdminNavigationView.js +1 -1
  134. package/dist/features/admin/components/AdminPageHeader.js +1 -1
  135. package/dist/features/admin/components/AdminProductEditorView.js +17 -2
  136. package/dist/features/admin/components/AdminSidebar.js +1 -1
  137. package/dist/features/admin/components/AdminSiteSettingsView.js +39 -8
  138. package/dist/features/admin/components/AdminSparesView.d.ts +12 -0
  139. package/dist/features/admin/components/AdminSparesView.js +30 -0
  140. package/dist/features/admin/components/AdminUsersView.js +9 -7
  141. package/dist/features/admin/components/index.d.ts +2 -0
  142. package/dist/features/admin/components/index.js +1 -0
  143. package/dist/features/admin/components/sections/adminSectionsTypes.js +7 -0
  144. package/dist/features/admin/constants/filter-tabs.d.ts +3 -0
  145. package/dist/features/admin/constants/filter-tabs.js +1 -0
  146. package/dist/features/admin/schemas/firestore.d.ts +26 -2
  147. package/dist/features/auctions/components/AuctionDetailPageView.js +4 -11
  148. package/dist/features/auth/repository/user.repository.d.ts +13 -0
  149. package/dist/features/auth/repository/user.repository.js +28 -0
  150. package/dist/features/auth/schemas/firestore.d.ts +2 -0
  151. package/dist/features/auth/schemas/firestore.js +2 -0
  152. package/dist/features/business-documents/repository/business-documents.repository.d.ts +58 -0
  153. package/dist/features/business-documents/repository/business-documents.repository.js +54 -0
  154. package/dist/features/business-documents/schemas/firestore.d.ts +105 -0
  155. package/dist/features/business-documents/schemas/firestore.js +75 -0
  156. package/dist/features/cart/actions/cart-actions.js +83 -6
  157. package/dist/features/cart/repository/cart.repository.js +32 -4
  158. package/dist/features/cart/schemas/firestore.d.ts +20 -0
  159. package/dist/features/cart/schemas/index.d.ts +2 -2
  160. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +1 -1
  161. package/dist/features/categories/api/[id]/price-guidance/route.d.ts +42 -0
  162. package/dist/features/categories/api/[id]/price-guidance/route.js +113 -0
  163. package/dist/features/categories/components/BrandDetailPageView.js +19 -3
  164. package/dist/features/categories/components/BrandDetailTabs.js +10 -0
  165. package/dist/features/categories/components/CategoriesIndexListing.js +23 -8
  166. package/dist/features/categories/components/CategoryDetailPageView.js +141 -14
  167. package/dist/features/categories/components/CategoryDetailTabs.js +12 -0
  168. package/dist/features/categories/components/CategoryGrid.js +10 -1
  169. package/dist/features/categories/components/PriceGuidanceHint.d.ts +29 -0
  170. package/dist/features/categories/components/PriceGuidanceHint.js +61 -0
  171. package/dist/features/categories/components/SubcategoryCardRow.d.ts +27 -0
  172. package/dist/features/categories/components/SubcategoryCardRow.js +98 -0
  173. package/dist/features/categories/constants/category-tabs.d.ts +20 -25
  174. package/dist/features/categories/constants/category-tabs.js +22 -39
  175. package/dist/features/categories/repository/categories.repository.d.ts +12 -0
  176. package/dist/features/categories/repository/categories.repository.js +20 -0
  177. package/dist/features/categories/schemas/firestore.d.ts +22 -1
  178. package/dist/features/categories/schemas/firestore.js +10 -0
  179. package/dist/features/categories/schemas/index.d.ts +6 -0
  180. package/dist/features/categories/schemas/index.js +2 -0
  181. package/dist/features/categories/types/index.d.ts +8 -0
  182. package/dist/features/categories/utils/taxonomy-visual.d.ts +16 -3
  183. package/dist/features/categories/utils/taxonomy-visual.js +26 -10
  184. package/dist/features/classified/components/ClassifiedDetailPageView.js +4 -9
  185. package/dist/features/contact/components/ContactPageView.d.ts +33 -1
  186. package/dist/features/contact/components/ContactPageView.js +58 -28
  187. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +4 -9
  188. package/dist/features/grouped/components/GroupedListingDetailView.js +9 -1
  189. package/dist/features/grouped/schemas/firestore.d.ts +5 -0
  190. package/dist/features/homepage/actions/homepage-section-actions.d.ts +3 -3
  191. package/dist/features/homepage/components/CollectionCardsSection.d.ts +28 -6
  192. package/dist/features/homepage/components/CollectionCardsSection.js +43 -8
  193. package/dist/features/homepage/components/HowItWorksSection.d.ts +23 -6
  194. package/dist/features/homepage/components/HowItWorksSection.js +4 -4
  195. package/dist/features/homepage/components/PromoGrid.d.ts +31 -2
  196. package/dist/features/homepage/components/PromoGrid.js +12 -3
  197. package/dist/features/homepage/components/TestimonialsCarousel.d.ts +34 -2
  198. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  199. package/dist/features/homepage/components/index.d.ts +0 -20
  200. package/dist/features/homepage/components/index.js +0 -11
  201. package/dist/features/homepage/index.d.ts +0 -2
  202. package/dist/features/homepage/index.js +0 -2
  203. package/dist/features/homepage/lib/section-renderer.d.ts +9 -0
  204. package/dist/features/homepage/lib/section-renderer.js +202 -58
  205. package/dist/features/homepage/repository/homepage-sections.repository.d.ts +35 -3
  206. package/dist/features/homepage/repository/homepage-sections.repository.js +58 -8
  207. package/dist/features/homepage/schemas/firestore.d.ts +106 -3
  208. package/dist/features/homepage/schemas/firestore.js +32 -2
  209. package/dist/features/homepage/schemas/index.d.ts +36 -7
  210. package/dist/features/homepage/schemas/index.js +39 -27
  211. package/dist/features/homepage/server.d.ts +0 -3
  212. package/dist/features/homepage/server.js +0 -3
  213. package/dist/features/homepage/types/index.d.ts +0 -47
  214. package/dist/features/homepage/types/index.js +49 -0
  215. package/dist/features/layout/AppLayoutShell.js +1 -1
  216. package/dist/features/layout/BottomNavItem.js +1 -1
  217. package/dist/features/layout/BottomSheet.js +1 -1
  218. package/dist/features/layout/TitleBarLayout.js +1 -1
  219. package/dist/features/live/components/LiveItemDetailPageView.js +4 -9
  220. package/dist/features/media/hooks/useMedia.js +17 -0
  221. package/dist/features/media/repository/media-assets.repository.d.ts +46 -0
  222. package/dist/features/media/repository/media-assets.repository.js +31 -0
  223. package/dist/features/media/trust-signals.d.ts +85 -0
  224. package/dist/features/media/trust-signals.js +126 -0
  225. package/dist/features/parts/components/PartDetailPageView.d.ts +14 -1
  226. package/dist/features/parts/components/PartDetailPageView.js +2 -2
  227. package/dist/features/parts/components/PartsIndexPageView.d.ts +11 -2
  228. package/dist/features/parts/components/PartsIndexPageView.js +2 -2
  229. package/dist/features/parts/components/PartsSearchBox.d.ts +9 -0
  230. package/dist/features/parts/components/PartsSearchBox.js +57 -0
  231. package/dist/features/parts/schemas/firestore.d.ts +149 -3
  232. package/dist/features/parts/schemas/firestore.js +184 -0
  233. package/dist/features/payments/repository/payout.repository.d.ts +15 -0
  234. package/dist/features/payments/repository/payout.repository.js +21 -0
  235. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -11
  236. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +1 -1
  237. package/dist/features/price-index/components/PriceIndexToolView.d.ts +3 -1
  238. package/dist/features/price-index/components/PriceIndexToolView.js +92 -13
  239. package/dist/features/products/components/CompareOverlay.d.ts +18 -1
  240. package/dist/features/products/components/CompareOverlay.js +21 -2
  241. package/dist/features/products/components/CustomSectionTabContent.d.ts +31 -0
  242. package/dist/features/products/components/CustomSectionTabContent.js +29 -3
  243. package/dist/features/products/components/GroupMemberPicker.d.ts +19 -1
  244. package/dist/features/products/components/GroupMemberPicker.js +46 -8
  245. package/dist/features/products/components/GroupSettingsPanel.d.ts +11 -1
  246. package/dist/features/products/components/GroupSettingsPanel.js +91 -3
  247. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  248. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +15 -1
  249. package/dist/features/products/components/PrizeDrawEntryActions.js +70 -8
  250. package/dist/features/products/components/ProductDetailPageView.js +74 -15
  251. package/dist/features/products/components/ProductForm.d.ts +13 -1
  252. package/dist/features/products/components/ProductForm.js +16 -2
  253. package/dist/features/products/components/ProductsIndexListing.d.ts +16 -1
  254. package/dist/features/products/components/ProductsIndexListing.js +3 -3
  255. package/dist/features/products/components/ShowGroupSection.js +15 -2
  256. package/dist/features/products/components/SparesListView.d.ts +7 -0
  257. package/dist/features/products/components/SparesListView.js +39 -0
  258. package/dist/features/products/constants/action-defs.d.ts +1 -0
  259. package/dist/features/products/constants/action-defs.js +1 -0
  260. package/dist/features/products/repository/products.repository.d.ts +98 -0
  261. package/dist/features/products/repository/products.repository.js +235 -17
  262. package/dist/features/products/schemas/firestore.d.ts +97 -4
  263. package/dist/features/products/schemas/firestore.js +104 -0
  264. package/dist/features/products/schemas/index.d.ts +10 -19
  265. package/dist/features/products/schemas/index.js +23 -6
  266. package/dist/features/products/types/index.d.ts +45 -1
  267. package/dist/features/products/utils/group-member-image.d.ts +39 -0
  268. package/dist/features/products/utils/group-member-image.js +3 -0
  269. package/dist/features/products/utils/listing-badge-variant.js +4 -0
  270. package/dist/features/reviews/components/ReviewsList.js +1 -1
  271. package/dist/features/scams/components/ScamProfileView.js +1 -1
  272. package/dist/features/search/components/EntityTypeahead.d.ts +44 -0
  273. package/dist/features/search/components/EntityTypeahead.js +92 -0
  274. package/dist/features/search/components/Search.d.ts +12 -1
  275. package/dist/features/search/components/Search.js +47 -3
  276. package/dist/features/search/components/SearchFacetRail.d.ts +11 -0
  277. package/dist/features/search/components/SearchFacetRail.js +92 -0
  278. package/dist/features/search/components/SearchResultsClient.d.ts +33 -0
  279. package/dist/features/search/components/SearchResultsClient.js +103 -0
  280. package/dist/features/search/components/SearchResultsPageView.d.ts +15 -1
  281. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  282. package/dist/features/search/components/index.d.ts +0 -6
  283. package/dist/features/search/components/index.js +0 -3
  284. package/dist/features/search/hooks/useNavSuggestions.d.ts +13 -0
  285. package/dist/features/search/hooks/useScopedSearch.d.ts +34 -0
  286. package/dist/features/search/hooks/useScopedSearch.js +114 -0
  287. package/dist/features/search/hooks/useSiteSearch.d.ts +26 -0
  288. package/dist/features/search/hooks/useSiteSearch.js +149 -0
  289. package/dist/features/search/index.d.ts +0 -4
  290. package/dist/features/search/index.js +0 -4
  291. package/dist/features/search/manifest.d.ts +12 -0
  292. package/dist/features/search/manifest.js +17 -2
  293. package/dist/features/search/server.d.ts +0 -3
  294. package/dist/features/search/server.js +0 -3
  295. package/dist/features/seller/components/SellerListingTypeView.d.ts +1 -1
  296. package/dist/features/seller/components/SellerListingTypeView.js +20 -0
  297. package/dist/features/seller/components/SellerPayoutSettingsView.js +35 -14
  298. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  299. package/dist/features/seller/components/SellerProductShell.js +2 -1
  300. package/dist/features/seller/components/SellerSidebar.js +1 -1
  301. package/dist/features/seller/components/SellerSparesView.d.ts +17 -0
  302. package/dist/features/seller/components/SellerSparesView.js +6 -0
  303. package/dist/features/seller/components/index.d.ts +2 -0
  304. package/dist/features/seller/components/index.js +1 -0
  305. package/dist/features/shell/CommandPalette.js +1 -1
  306. package/dist/features/sticker-trace/schemas/firestore.d.ts +19 -0
  307. package/dist/features/stores/components/StoreDetailLayoutView.d.ts +1 -1
  308. package/dist/features/stores/components/StoreDetailLayoutView.js +7 -82
  309. package/dist/features/stores/components/StoreHeader.js +1 -2
  310. package/dist/features/stores/components/StoreSparesPageView.d.ts +27 -0
  311. package/dist/features/stores/components/StoreSparesPageView.js +48 -0
  312. package/dist/features/stores/schemas/firestore.d.ts +19 -0
  313. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.d.ts +2 -0
  314. package/dist/features/tester/seed-data/authored/buying__spares-and-groups.js +96 -0
  315. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  316. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +280 -45
  317. package/dist/features/tester/seed-data/authored/content-discovery__category-tabs-franchise-bundles.js +54 -32
  318. package/dist/features/tester/seed-data/authored/content-discovery__parts-database.js +3 -2
  319. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.d.ts +2 -0
  320. package/dist/features/tester/seed-data/authored/content-discovery__price-index-and-compare.js +73 -0
  321. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.d.ts +2 -0
  322. package/dist/features/tester/seed-data/authored/content-discovery__site-wide-index.js +340 -0
  323. package/dist/features/tester/seed-data/authored/index.js +16 -0
  324. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.d.ts +1 -0
  325. package/dist/features/tester/seed-data/authored/search-and-nav__header-search.js +57 -0
  326. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.d.ts +2 -0
  327. package/dist/features/tester/seed-data/authored/selling__spares-and-group-editing.js +77 -0
  328. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.d.ts +2 -0
  329. package/dist/features/tester/seed-data/authored/tools__sticker-promote-to-listings.js +58 -0
  330. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +5 -4
  331. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +372 -18
  332. package/dist/index.d.ts +17 -57
  333. package/dist/index.js +35 -96
  334. package/dist/next/routing/route-map.d.ts +42 -1
  335. package/dist/next/routing/route-map.js +45 -0
  336. package/dist/providers/db-firebase/base.repository.d.ts +22 -0
  337. package/dist/providers/db-firebase/base.repository.js +36 -0
  338. package/dist/schemas/registry.d.ts +15 -6
  339. package/dist/security/blind-index-tokens.d.ts +54 -0
  340. package/dist/security/blind-index-tokens.js +152 -0
  341. package/dist/security/index.d.ts +1 -1
  342. package/dist/security/index.js +1 -1
  343. package/dist/security/settings-encryption.d.ts +35 -0
  344. package/dist/security/settings-encryption.js +91 -0
  345. package/dist/seed/_helpers/brand-rows.js +38 -14
  346. package/dist/seed/_helpers/category-forest.js +73 -7
  347. package/dist/seed/_helpers/category-images-generated.d.ts +25 -6
  348. package/dist/seed/_helpers/category-images-generated.js +81 -6
  349. package/dist/seed/_helpers/category-models.d.ts +1 -1
  350. package/dist/seed/_helpers/category-models.js +7 -1
  351. package/dist/seed/_helpers/category-parts-glossary-map.js +8 -0
  352. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  353. package/dist/seed/_helpers/category-parts.js +50 -2
  354. package/dist/seed/_helpers/character-features.d.ts +1 -1
  355. package/dist/seed/_helpers/character-features.js +2 -1
  356. package/dist/seed/_helpers/franchise-rows.js +10 -2
  357. package/dist/seed/_helpers/media.d.ts +47 -0
  358. package/dist/seed/_helpers/media.js +34 -0
  359. package/dist/seed/authenticity-knowledge-seed-data.d.ts +49 -5
  360. package/dist/seed/authenticity-knowledge-seed-data.js +10001 -549
  361. package/dist/seed/categories-seed-data.js +124 -20
  362. package/dist/seed/grouped-listings-seed-data.js +16 -0
  363. package/dist/seed/homepage-sections-seed-data.js +94 -5
  364. package/dist/seed/index.d.ts +2 -3
  365. package/dist/seed/index.js +2 -2
  366. package/dist/seed/manifest.js +3 -0
  367. package/dist/seed/media-assets-seed-data.d.ts +1 -1
  368. package/dist/seed/media-assets-seed-data.js +746 -196
  369. package/dist/seed/model-composition-seed-data.js +122 -6
  370. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  371. package/dist/seed/part-glossary-seed-data.js +37219 -463
  372. package/dist/seed/products-art-seed-data.js +7 -18
  373. package/dist/seed/products-auctions-seed-data.js +56 -47
  374. package/dist/seed/products-beyblade-x-seed-data.d.ts +2 -0
  375. package/dist/seed/products-beyblade-x-seed-data.js +441 -0
  376. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  377. package/dist/seed/products-catalogue-seed-data.js +1500 -1240
  378. package/dist/seed/products-classifieds-seed-data.js +9 -27
  379. package/dist/seed/products-digital-codes-seed-data.js +9 -18
  380. package/dist/seed/products-live-items-seed-data.js +5 -13
  381. package/dist/seed/products-preorders-seed-data.js +67 -33
  382. package/dist/seed/products-prize-draws-seed-data.js +8 -17
  383. package/dist/seed/products-spares-seed-data.d.ts +2 -0
  384. package/dist/seed/products-spares-seed-data.js +368 -0
  385. package/dist/seed/products-standard-seed-data.js +39 -126
  386. package/dist/seed/products-stickers-seed-data.js +59 -15
  387. package/dist/seed/site-settings-seed-data.js +8 -0
  388. package/dist/seo/json-ld.d.ts +25 -0
  389. package/dist/seo/json-ld.js +28 -0
  390. package/dist/server-entry.d.ts +3 -1
  391. package/dist/server-entry.js +2 -2
  392. package/dist/server.d.ts +9 -9
  393. package/dist/server.js +27 -25
  394. package/dist/styles.css +202 -1
  395. package/dist/tailwind-input.css +31 -17
  396. package/dist/tailwind-utilities.css +1 -1
  397. package/dist/ui/components/Accordion.js +1 -1
  398. package/dist/ui/components/Breadcrumb.js +1 -1
  399. package/dist/ui/components/DetailPageGallery.js +0 -2
  400. package/dist/ui/components/DetailPageHero.js +0 -1
  401. package/dist/ui/components/DetailPageTabs.js +1 -1
  402. package/dist/ui/components/Divider.js +3 -3
  403. package/dist/ui/components/GatedPrice.js +5 -1
  404. package/dist/ui/components/HorizontalScroller.js +1 -1
  405. package/dist/ui/components/ListingLayout.js +3 -3
  406. package/dist/ui/components/MediaTabs.style.css +81 -0
  407. package/dist/ui/components/Pagination.js +1 -1
  408. package/dist/ui/components/PendingValue.d.ts +45 -0
  409. package/dist/ui/components/PendingValue.js +7 -0
  410. package/dist/ui/components/Radio.style.css +119 -0
  411. package/dist/ui/components/RatingDisplay.js +2 -2
  412. package/dist/ui/components/SettingsSection.js +0 -2
  413. package/dist/ui/components/SiteLogo.js +3 -0
  414. package/dist/ui/components/SiteMark.js +3 -0
  415. package/dist/ui/components/SortDropdown.js +1 -1
  416. package/dist/ui/components/SpecTable.d.ts +58 -0
  417. package/dist/ui/components/SpecTable.js +8 -0
  418. package/dist/ui/components/StarRating.js +1 -3
  419. package/dist/ui/components/SummaryCard.js +1 -1
  420. package/dist/ui/components/index.style.css +1 -0
  421. package/dist/ui/icons/icon-registry.d.ts +1 -0
  422. package/dist/ui/icons/icon-registry.js +9 -1
  423. package/dist/ui/index.d.ts +4 -0
  424. package/dist/ui/index.js +2 -0
  425. package/dist/utils/search-txt-query.d.ts +12 -0
  426. package/dist/utils/search-txt-query.js +12 -0
  427. package/package.json +1 -1
  428. package/scripts/dedupe-peer-deps.mjs +18 -0
  429. package/scripts/seed-cli.mjs +135 -26
  430. package/scripts/verify-css-build.mjs +118 -37
  431. package/dist/_internal/server/features/search/actions.d.ts +0 -7
  432. package/dist/_internal/server/features/search/actions.js +0 -21
  433. package/dist/_internal/server/features/search/data.d.ts +0 -15
  434. package/dist/_internal/server/features/search/data.js +0 -18
  435. package/dist/_internal/server/features/search/index.d.ts +0 -3
  436. package/dist/_internal/server/features/search/index.js +0 -2
  437. package/dist/features/homepage/api/[id]/route.d.ts +0 -23
  438. package/dist/features/homepage/api/[id]/route.js +0 -103
  439. package/dist/features/homepage/api/route.d.ts +0 -14
  440. package/dist/features/homepage/api/route.js +0 -140
  441. package/dist/features/homepage/api/sections/[id]/route.d.ts +0 -26
  442. package/dist/features/homepage/api/sections/[id]/route.js +0 -95
  443. package/dist/features/homepage/api/sections/route.d.ts +0 -13
  444. package/dist/features/homepage/api/sections/route.js +0 -82
  445. package/dist/features/homepage/components/AdvertisementBanner.d.ts +0 -14
  446. package/dist/features/homepage/components/AdvertisementBanner.js +0 -35
  447. package/dist/features/homepage/components/BeforeAfterCard.d.ts +0 -9
  448. package/dist/features/homepage/components/BeforeAfterCard.js +0 -73
  449. package/dist/features/homepage/components/FeaturedResultsSection.d.ts +0 -17
  450. package/dist/features/homepage/components/FeaturedResultsSection.js +0 -17
  451. package/dist/features/homepage/components/HeroBanner.d.ts +0 -7
  452. package/dist/features/homepage/components/HeroBanner.js +0 -43
  453. package/dist/features/homepage/components/HeroSection.d.ts +0 -7
  454. package/dist/features/homepage/components/HeroSection.js +0 -6
  455. package/dist/features/homepage/components/HomepageSkeleton.d.ts +0 -2
  456. package/dist/features/homepage/components/HomepageSkeleton.js +0 -21
  457. package/dist/features/homepage/components/HomepageView.d.ts +0 -25
  458. package/dist/features/homepage/components/HomepageView.js +0 -5
  459. package/dist/features/homepage/components/NewsletterBanner.d.ts +0 -23
  460. package/dist/features/homepage/components/NewsletterBanner.js +0 -37
  461. package/dist/features/homepage/components/SiteFeaturesSection.d.ts +0 -14
  462. package/dist/features/homepage/components/SiteFeaturesSection.js +0 -11
  463. package/dist/features/homepage/components/TrustBadges.d.ts +0 -6
  464. package/dist/features/homepage/components/TrustBadges.js +0 -46
  465. package/dist/features/homepage/components/TrustIndicatorsSection.d.ts +0 -12
  466. package/dist/features/homepage/components/TrustIndicatorsSection.js +0 -11
  467. package/dist/features/homepage/hooks/useHomepage.d.ts +0 -11
  468. package/dist/features/homepage/hooks/useHomepage.js +0 -17
  469. package/dist/features/homepage/hooks/useHomepageSections.d.ts +0 -2
  470. package/dist/features/homepage/hooks/useHomepageSections.js +0 -10
  471. package/dist/features/homepage/repository/homepage.repository.d.ts +0 -11
  472. package/dist/features/homepage/repository/homepage.repository.js +0 -25
  473. package/dist/features/search/actions/index.d.ts +0 -1
  474. package/dist/features/search/actions/index.js +0 -1
  475. package/dist/features/search/actions/search-actions.d.ts +0 -18
  476. package/dist/features/search/actions/search-actions.js +0 -43
  477. package/dist/features/search/api/route.d.ts +0 -22
  478. package/dist/features/search/api/route.js +0 -128
  479. package/dist/features/search/columns/index.d.ts +0 -17
  480. package/dist/features/search/columns/index.js +0 -49
  481. package/dist/features/search/components/SearchFiltersRow.d.ts +0 -22
  482. package/dist/features/search/components/SearchFiltersRow.js +0 -21
  483. package/dist/features/search/components/SearchResultsSection.d.ts +0 -53
  484. package/dist/features/search/components/SearchResultsSection.js +0 -35
  485. package/dist/features/search/components/SearchView.d.ts +0 -27
  486. package/dist/features/search/components/SearchView.js +0 -8
  487. package/dist/features/search/hooks/useSearch.d.ts +0 -21
  488. package/dist/features/search/hooks/useSearch.js +0 -33
  489. package/dist/features/search/repository/search.repository.d.ts +0 -14
  490. package/dist/features/search/repository/search.repository.js +0 -59
  491. package/dist/features/search/schemas/index.d.ts +0 -70
  492. package/dist/features/search/schemas/index.js +0 -36
  493. package/dist/features/search/types/index.d.ts +0 -48
  494. package/dist/features/stores/components/StoreScopedSearch.d.ts +0 -17
  495. package/dist/features/stores/components/StoreScopedSearch.js +0 -27
  496. package/dist/seed/defaults/homepage-sections.d.ts +0 -2
  497. package/dist/seed/defaults/homepage-sections.js +0 -60
  498. package/dist/seed/factories/homepage-section.factory.d.ts +0 -10
  499. package/dist/seed/factories/homepage-section.factory.js +0 -13
  500. /package/dist/{features/search/types/index.js → _internal/shared/listing-types/spares/og.d.ts} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { normalizeError } from "../../../errors/normalize.js";
2
+ import { buildTokensIndex } from "../../../security/blind-index-tokens.js";
2
3
  import { DatabaseError } from "../../../errors/index.js";
3
4
  import { BaseRepository, getFirestoreCount, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
4
5
  import { USER_PII_FIELDS, USER_PII_INDEX_MAP, decryptPayoutDetails, maskPii, revealPii, decryptShippingConfig, encryptPayoutDetails, encryptShippingConfig, piiBlindIndex, } from "../../../security/index.js";
@@ -89,6 +90,24 @@ export class UserRepository extends BaseRepository {
89
90
  .filter(([, v]) => typeof v === "string")
90
91
  .map(([k]) => k));
91
92
  }
93
+ /**
94
+ * Admin search by first/last name or email local-part, without decryption.
95
+ * `displayName` and `email` only — `phoneNumber` is excluded on purpose: a
96
+ * phone number has no word boundaries, so tokenising it yields one token
97
+ * that is the whole number, an exact lookup `phoneIndex` already covers.
98
+ *
99
+ * Running through `applyWriteHooks` is what makes a `users` row that
100
+ * SURVIVES a tester cascade (it is PRESERVE-tier and never wiped) keep a
101
+ * correct index going forward, and what makes a NEWLY signed-up real user
102
+ * searchable without anyone having to remember to re-run the backfill.
103
+ */
104
+ buildTokensIndexFor(data) {
105
+ const displayName = typeof data.displayName === "string" ? data.displayName : undefined;
106
+ const email = typeof data.email === "string" ? data.email : undefined;
107
+ if (!displayName && !email)
108
+ return null;
109
+ return { nameTokensIndex: buildTokensIndex(displayName, email) };
110
+ }
92
111
  /**
93
112
  * The base hook, plus the nested objects it cannot see.
94
113
  *
@@ -381,6 +400,15 @@ UserRepository.SIEVE_FIELDS = {
381
400
  // filter matched every employee. The composite indexes it needs are already
382
401
  // declared and deployed — (permissionGroup, role, createdAt|displayName).
383
402
  permissionGroup: { canFilter: true, canSort: false },
403
+ /*
404
+ * Whole-word admin name search. `canSort: false` — an array field has no
405
+ * sort order Firestore can express. Without this entry, `@=` (Firestore
406
+ * `array-contains`) on `nameTokensIndex` is dropped SILENTLY by Sieve
407
+ * (`throwExceptions: false`), and the clause after it goes with it —
408
+ * which here would be nothing, but is the exact mechanism Root Cause #62
409
+ * documents for every other field this has happened to.
410
+ */
411
+ nameTokensIndex: { canFilter: true, canSort: false },
384
412
  createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
385
413
  updatedAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
386
414
  };
@@ -271,6 +271,8 @@ export declare const USER_FIELDS: {
271
271
  readonly DISPLAY_NAME: "displayName";
272
272
  readonly EMAIL_INDEX: "emailIndex";
273
273
  readonly PHONE_INDEX: "phoneIndex";
274
+ /** Whole-word HMAC digests of `displayName` + `email`. See `blind-index-tokens.ts`. */
275
+ readonly NAME_TOKENS_INDEX: "nameTokensIndex";
274
276
  readonly PHOTO_URL: "photoURL";
275
277
  readonly AVATAR_METADATA: "avatarMetadata";
276
278
  readonly ROLE: "role";
@@ -102,6 +102,8 @@ export const USER_FIELDS = {
102
102
  DISPLAY_NAME: "displayName",
103
103
  EMAIL_INDEX: "emailIndex",
104
104
  PHONE_INDEX: "phoneIndex",
105
+ /** Whole-word HMAC digests of `displayName` + `email`. See `blind-index-tokens.ts`. */
106
+ NAME_TOKENS_INDEX: "nameTokensIndex",
105
107
  PHOTO_URL: "photoURL",
106
108
  AVATAR_METADATA: "avatarMetadata",
107
109
  ROLE: "role",
@@ -0,0 +1,58 @@
1
+ /**
2
+ * BusinessDocumentsRepository — thin wrapper over the `businessDocuments`
3
+ * collection. See `../schemas/firestore.ts` for the doc shape and for why the
4
+ * bytes are not in Firestore.
5
+ */
6
+ import { BaseRepository, parseSieveDateValue, type SieveModel, type FirebaseSieveResult } from "../../../providers/db-firebase";
7
+ import { type BusinessDocumentDocument, type BusinessDocumentKind } from "../schemas/firestore";
8
+ export declare class BusinessDocumentsRepository extends BaseRepository<BusinessDocumentDocument> {
9
+ constructor();
10
+ static readonly SIEVE_FIELDS: {
11
+ id: {
12
+ canFilter: boolean;
13
+ canSort: boolean;
14
+ };
15
+ kind: {
16
+ canFilter: boolean;
17
+ canSort: boolean;
18
+ };
19
+ fileName: {
20
+ canFilter: boolean;
21
+ canSort: boolean;
22
+ };
23
+ uploadedBy: {
24
+ canFilter: boolean;
25
+ canSort: boolean;
26
+ };
27
+ uploadedAt: {
28
+ canFilter: boolean;
29
+ canSort: boolean;
30
+ parseValue: typeof parseSieveDateValue;
31
+ };
32
+ createdAt: {
33
+ canFilter: boolean;
34
+ canSort: boolean;
35
+ parseValue: typeof parseSieveDateValue;
36
+ };
37
+ };
38
+ list(model: SieveModel): Promise<FirebaseSieveResult<BusinessDocumentDocument>>;
39
+ /**
40
+ * The current document of a given kind — newest `uploadedAt` wins.
41
+ *
42
+ * Re-uploading a re-issued certificate is normal and both copies are kept:
43
+ * the superseded one is the evidence of what was true at the time, which is
44
+ * the entire point of keeping a registration document. So this resolves the
45
+ * current one rather than assuming a single row per kind.
46
+ *
47
+ * 🛑 Ordered by `createdAt`, NOT `uploadedAt`, even though the latter reads
48
+ * more naturally here. `BaseRepository.create()` stamps `createdAt` on every
49
+ * document it writes, so it is guaranteed present; `uploadedAt` is set by the
50
+ * caller and a single write path that forgot it would make that row vanish
51
+ * from this query — a Firestore `orderBy` on a field a document lacks is a
52
+ * silent `WHERE field IS NOT NULL` (Root Cause #100). The two are written in
53
+ * the same operation and agree, so there is nothing to gain by taking the
54
+ * field that can be missed.
55
+ */
56
+ findCurrentByKind(kind: BusinessDocumentKind): Promise<BusinessDocumentDocument | null>;
57
+ }
58
+ export declare const businessDocumentsRepository: BusinessDocumentsRepository;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * BusinessDocumentsRepository — thin wrapper over the `businessDocuments`
3
+ * collection. See `../schemas/firestore.ts` for the doc shape and for why the
4
+ * bytes are not in Firestore.
5
+ */
6
+ import { BaseRepository, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
7
+ import { BUSINESS_DOCUMENTS_COLLECTION, } from "../schemas/firestore.js";
8
+ export class BusinessDocumentsRepository extends BaseRepository {
9
+ constructor() {
10
+ super(BUSINESS_DOCUMENTS_COLLECTION);
11
+ }
12
+ async list(model) {
13
+ return this.sieveQuery(model, BusinessDocumentsRepository.SIEVE_FIELDS, { defaultPageSize: 25, maxPageSize: 100 });
14
+ }
15
+ /**
16
+ * The current document of a given kind — newest `uploadedAt` wins.
17
+ *
18
+ * Re-uploading a re-issued certificate is normal and both copies are kept:
19
+ * the superseded one is the evidence of what was true at the time, which is
20
+ * the entire point of keeping a registration document. So this resolves the
21
+ * current one rather than assuming a single row per kind.
22
+ *
23
+ * 🛑 Ordered by `createdAt`, NOT `uploadedAt`, even though the latter reads
24
+ * more naturally here. `BaseRepository.create()` stamps `createdAt` on every
25
+ * document it writes, so it is guaranteed present; `uploadedAt` is set by the
26
+ * caller and a single write path that forgot it would make that row vanish
27
+ * from this query — a Firestore `orderBy` on a field a document lacks is a
28
+ * silent `WHERE field IS NOT NULL` (Root Cause #100). The two are written in
29
+ * the same operation and agree, so there is nothing to gain by taking the
30
+ * field that can be missed.
31
+ */
32
+ async findCurrentByKind(kind) {
33
+ const result = await this.list({
34
+ filters: `kind==${kind}`,
35
+ sorts: "-createdAt",
36
+ page: 1,
37
+ pageSize: 1,
38
+ });
39
+ return result.items[0] ?? null;
40
+ }
41
+ }
42
+ BusinessDocumentsRepository.SIEVE_FIELDS = {
43
+ id: { canFilter: true, canSort: false },
44
+ kind: { canFilter: true, canSort: true },
45
+ fileName: { canFilter: true, canSort: true },
46
+ uploadedBy: { canFilter: true, canSort: false },
47
+ // Timestamp fields that are filterable MUST carry parseValue, or an ISO
48
+ // string reaches Firestore as a string, the type does not match the stored
49
+ // Timestamp, and the query matches ZERO documents with no error anywhere
50
+ // (Root Cause #47).
51
+ uploadedAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
52
+ createdAt: { canFilter: true, canSort: true, parseValue: parseSieveDateValue },
53
+ };
54
+ export const businessDocumentsRepository = new BusinessDocumentsRepository();
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Business Documents Firestore Document Types & Constants
3
+ *
4
+ * Collection: businessDocuments
5
+ * Document ID: Firestore auto-ID (no slug prefix — same convention as
6
+ * `adminAuditLog` / `notifications` / `sessions` / `jobs`; see CLAUDE.md's
7
+ * "True Firestore auto-IDs" list).
8
+ *
9
+ * The statutory registration documents of the business itself — GST
10
+ * certificate, Udyam registration, bank passbook. These are NOT product media
11
+ * and NOT user uploads; there is exactly one current document per `kind`.
12
+ *
13
+ * ## 🛑 Metadata only. The bytes are never in Firestore.
14
+ *
15
+ * A Firestore document is capped at 1 MiB and the Udyam certificate alone is
16
+ * 680 KB, which base64-encodes to ~907 KB — so an "encrypt the PDF into a
17
+ * string field" design is one slightly larger certificate away from an
18
+ * unrecoverable write failure, at upload time, after the user has handed over
19
+ * the document. The ciphertext lives in Storage; this collection records where.
20
+ *
21
+ * ## 🛑 Why `storagePath` must never be served through /api/media
22
+ *
23
+ * `GET /api/media/[...slug]` applies NO authentication — deliberately, because
24
+ * its job is serving public product photography through a watermarker — and
25
+ * `storage.rules` is `allow read: if true` for every object in the bucket. So
26
+ * the only thing standing between these documents and the public internet is
27
+ * that nothing routes them there, and that they carry no `mediaAssets` row (the
28
+ * row IS the public `/media/<shortId>` slug, so no row means no public URL).
29
+ *
30
+ * Read them through `GET /api/admin/business-documents/[id]` and nothing else.
31
+ */
32
+ import type { BaseDocument } from "../../../_internal/shared/types/base-document";
33
+ export declare const BUSINESS_DOCUMENTS_COLLECTION = "businessDocuments";
34
+ export declare const BusinessDocumentKindValues: {
35
+ readonly GST_CERTIFICATE: "gst_certificate";
36
+ readonly UDYAM_CERTIFICATE: "udyam_certificate";
37
+ readonly BANK_PASSBOOK: "bank_passbook";
38
+ };
39
+ export type BusinessDocumentKind = (typeof BusinessDocumentKindValues)[keyof typeof BusinessDocumentKindValues];
40
+ export declare const ALL_BUSINESS_DOCUMENT_KINDS: readonly BusinessDocumentKind[];
41
+ /**
42
+ * A `Record<BusinessDocumentKind, string>` rather than a lookup with a
43
+ * fallback, so adding a fourth kind is a COMPILE error here instead of
44
+ * rendering as a raw snake_case id in the admin list.
45
+ */
46
+ export declare const BUSINESS_DOCUMENT_KIND_LABELS: Record<BusinessDocumentKind, string>;
47
+ export interface BusinessDocumentDocument extends BaseDocument {
48
+ kind: BusinessDocumentKind;
49
+ /**
50
+ * Path inside the Storage bucket, e.g.
51
+ * `private/business-documents/3f2a…-gst-certificate.pdf`.
52
+ *
53
+ * 🛑 Never returned by any API, to anyone. The download route resolves it
54
+ * server-side and streams the decrypted bytes; handing the path to a client
55
+ * would let it be fetched straight from Storage, where the rules permit any
56
+ * read.
57
+ */
58
+ storagePath: string;
59
+ /** Original filename as uploaded — shown in the admin list and used for Content-Disposition. */
60
+ fileName: string;
61
+ /** Verified against the file's magic bytes at upload; never trusted from the client. */
62
+ contentType: string;
63
+ /** Size of the PLAINTEXT, before the envelope. What an admin expects to see downloaded. */
64
+ sizeBytes: number;
65
+ /**
66
+ * Whether the stored object carries the `enc:v1:` binary envelope.
67
+ *
68
+ * Always `true` for anything this codebase writes — `encryptBuffer` throws
69
+ * rather than degrading when `SETTINGS_ENCRYPTION_KEY` is unset, so there is
70
+ * no path that produces a plaintext object. It is recorded anyway because
71
+ * the download route must not GUESS: a future hand-uploaded object would
72
+ * otherwise be fed to `decryptBuffer` and throw an envelope error that
73
+ * describes the crypto rather than the upload.
74
+ */
75
+ encrypted: boolean;
76
+ /** Auth UID of the admin who uploaded it. */
77
+ uploadedBy: string;
78
+ uploadedAt: Date;
79
+ }
80
+ export declare const BUSINESS_DOCUMENT_FIELDS: {
81
+ readonly KIND: "kind";
82
+ readonly STORAGE_PATH: "storagePath";
83
+ readonly FILE_NAME: "fileName";
84
+ readonly CONTENT_TYPE: "contentType";
85
+ readonly SIZE_BYTES: "sizeBytes";
86
+ readonly ENCRYPTED: "encrypted";
87
+ readonly UPLOADED_BY: "uploadedBy";
88
+ readonly UPLOADED_AT: "uploadedAt";
89
+ readonly CREATED_AT: "createdAt";
90
+ readonly KIND_VALUES: {
91
+ readonly GST_CERTIFICATE: "gst_certificate";
92
+ readonly UDYAM_CERTIFICATE: "udyam_certificate";
93
+ readonly BANK_PASSBOOK: "bank_passbook";
94
+ };
95
+ };
96
+ /** Storage prefix. Mirrors `private/digital-content/` — outside any public read path. */
97
+ export declare const BUSINESS_DOCUMENTS_STORAGE_PREFIX = "private/business-documents";
98
+ /**
99
+ * Accepted upload types. PDFs and scans only.
100
+ *
101
+ * Checked against the file's MAGIC BYTES, not the declared Content-Type: a
102
+ * declared type is a claim by the uploader, and an HTML document served from
103
+ * this origin would be stored XSS against a signed-in admin.
104
+ */
105
+ export declare const BUSINESS_DOCUMENT_ALLOWED_TYPES: readonly ["application/pdf", "image/jpeg", "image/png", "image/webp"];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Business Documents Firestore Document Types & Constants
3
+ *
4
+ * Collection: businessDocuments
5
+ * Document ID: Firestore auto-ID (no slug prefix — same convention as
6
+ * `adminAuditLog` / `notifications` / `sessions` / `jobs`; see CLAUDE.md's
7
+ * "True Firestore auto-IDs" list).
8
+ *
9
+ * The statutory registration documents of the business itself — GST
10
+ * certificate, Udyam registration, bank passbook. These are NOT product media
11
+ * and NOT user uploads; there is exactly one current document per `kind`.
12
+ *
13
+ * ## 🛑 Metadata only. The bytes are never in Firestore.
14
+ *
15
+ * A Firestore document is capped at 1 MiB and the Udyam certificate alone is
16
+ * 680 KB, which base64-encodes to ~907 KB — so an "encrypt the PDF into a
17
+ * string field" design is one slightly larger certificate away from an
18
+ * unrecoverable write failure, at upload time, after the user has handed over
19
+ * the document. The ciphertext lives in Storage; this collection records where.
20
+ *
21
+ * ## 🛑 Why `storagePath` must never be served through /api/media
22
+ *
23
+ * `GET /api/media/[...slug]` applies NO authentication — deliberately, because
24
+ * its job is serving public product photography through a watermarker — and
25
+ * `storage.rules` is `allow read: if true` for every object in the bucket. So
26
+ * the only thing standing between these documents and the public internet is
27
+ * that nothing routes them there, and that they carry no `mediaAssets` row (the
28
+ * row IS the public `/media/<shortId>` slug, so no row means no public URL).
29
+ *
30
+ * Read them through `GET /api/admin/business-documents/[id]` and nothing else.
31
+ */
32
+ export const BUSINESS_DOCUMENTS_COLLECTION = "businessDocuments";
33
+ export const BusinessDocumentKindValues = {
34
+ GST_CERTIFICATE: "gst_certificate",
35
+ UDYAM_CERTIFICATE: "udyam_certificate",
36
+ BANK_PASSBOOK: "bank_passbook",
37
+ };
38
+ export const ALL_BUSINESS_DOCUMENT_KINDS = Object.values(BusinessDocumentKindValues);
39
+ /**
40
+ * A `Record<BusinessDocumentKind, string>` rather than a lookup with a
41
+ * fallback, so adding a fourth kind is a COMPILE error here instead of
42
+ * rendering as a raw snake_case id in the admin list.
43
+ */
44
+ export const BUSINESS_DOCUMENT_KIND_LABELS = {
45
+ [BusinessDocumentKindValues.GST_CERTIFICATE]: "GST Certificate",
46
+ [BusinessDocumentKindValues.UDYAM_CERTIFICATE]: "Udyam Registration Certificate",
47
+ [BusinessDocumentKindValues.BANK_PASSBOOK]: "Bank Passbook",
48
+ };
49
+ export const BUSINESS_DOCUMENT_FIELDS = {
50
+ KIND: "kind",
51
+ STORAGE_PATH: "storagePath",
52
+ FILE_NAME: "fileName",
53
+ CONTENT_TYPE: "contentType",
54
+ SIZE_BYTES: "sizeBytes",
55
+ ENCRYPTED: "encrypted",
56
+ UPLOADED_BY: "uploadedBy",
57
+ UPLOADED_AT: "uploadedAt",
58
+ CREATED_AT: "createdAt",
59
+ KIND_VALUES: BusinessDocumentKindValues,
60
+ };
61
+ /** Storage prefix. Mirrors `private/digital-content/` — outside any public read path. */
62
+ export const BUSINESS_DOCUMENTS_STORAGE_PREFIX = "private/business-documents";
63
+ /**
64
+ * Accepted upload types. PDFs and scans only.
65
+ *
66
+ * Checked against the file's MAGIC BYTES, not the declared Content-Type: a
67
+ * declared type is a claim by the uploader, and an HTML document served from
68
+ * this origin would be stored XSS against a signed-in admin.
69
+ */
70
+ export const BUSINESS_DOCUMENT_ALLOWED_TYPES = [
71
+ "application/pdf",
72
+ "image/jpeg",
73
+ "image/png",
74
+ "image/webp",
75
+ ];
@@ -15,6 +15,7 @@ import { CART_LANE, activeLane, canAddNewItems, } from "../../../_internal/share
15
15
  import { CART_MAX_ITEMS } from "../../../constants/limits.js";
16
16
  import { getExpandedDecrements, validateCartItemStock, } from "../../../_internal/server/features/checkout/bundle-expansion.js";
17
17
  import { safeRead } from "../../../errors/safe-read.js";
18
+ import { checkGroupRules, readGroupRules, } from "../../../_internal/shared/features/products/group-rules.js";
18
19
  /**
19
20
  * Product documents don't always carry a denormalized `storeName` — the
20
21
  * create routes only set it when the caller explicitly passes one, so
@@ -133,7 +134,13 @@ export async function addGroupLineToCart(userId, input) {
133
134
  if (!rule.cartEligible) {
134
135
  throw new ValidationError(`"${product.title}" can't be added to the cart. ${rule.cartIneligibleHint ?? ""}`.trim());
135
136
  }
136
- if ((product.availableQuantity ?? 0) < m.quantity) {
137
+ /*
138
+ * 🛑 Made to order: no stock CHECK for this member. Nothing is held in
139
+ * reserve — it is printed or built when the order arrives — so the only
140
+ * honest answer to "how many are left" is "as many as you want". The
141
+ * decrement still happens at checkout; see `validateCartItemStock`.
142
+ */
143
+ if (product.madeToOrder !== true && (product.availableQuantity ?? 0) < m.quantity) {
137
144
  throw new ValidationError(`Only ${product.availableQuantity ?? 0} left of "${product.title}".`);
138
145
  }
139
146
  resolved.push({
@@ -141,7 +148,7 @@ export async function addGroupLineToCart(userId, input) {
141
148
  quantity: m.quantity,
142
149
  unitPrice: product.price,
143
150
  title: product.title,
144
- image: product.images?.[0] ?? product.mainImage,
151
+ image: product.images?.[0]?.url ?? product.mainImage,
145
152
  gstRate: product.gstRate,
146
153
  hsnCode: product.hsnCode,
147
154
  listingType,
@@ -164,7 +171,7 @@ export async function addGroupLineToCart(userId, input) {
164
171
  return addItemToCart(userId, {
165
172
  productId: first.id,
166
173
  productTitle: first.title,
167
- productImage: first.images?.[0] ?? first.mainImage,
174
+ productImage: first.images?.[0]?.url ?? first.mainImage,
168
175
  price: first.price,
169
176
  currency: first.currency,
170
177
  quantity: resolved[0].quantity,
@@ -192,6 +199,25 @@ export async function addGroupLineToCart(userId, input) {
192
199
  throw new ValidationError(`Only ${shortfall.availableQty} left of "${short?.title ?? shortfall.productId}" once your cart is counted.`);
193
200
  }
194
201
  const groupTitle = await resolveGroupTitle(groupId, groupSource, first ?? null);
202
+ /*
203
+ * 🛑 SERVER-SIDE ENFORCEMENT OF THE GROUP'S OWN RULES.
204
+ *
205
+ * `GroupMemberPicker` mirrors this so a buyer is not told "no" only after
206
+ * submitting — but the picker is a convenience and this is the boundary. The
207
+ * two share `checkGroupRules`, so they cannot disagree about what a valid
208
+ * selection is; only about WHEN the buyer finds out.
209
+ *
210
+ * It throws on the FIRST violation while the picker shows all of them
211
+ * together, which is each surface doing the right thing for its own job: a
212
+ * form lists everything to fix, an API answers one reason.
213
+ */
214
+ const rules = await resolveGroupRules(groupId, groupSource, first ?? null);
215
+ const violations = checkGroupRules(rules, resolved.map((r) => ({ productId: r.productId, quantity: r.quantity, title: r.title })));
216
+ if (violations.length > 0) {
217
+ throw Object.assign(new ValidationError(violations[0].message), {
218
+ code: violations[0].code,
219
+ });
220
+ }
195
221
  return cartRepository.addGroupLine(userId, {
196
222
  productId: groupId,
197
223
  productTitle: groupTitle,
@@ -208,9 +234,51 @@ export async function addGroupLineToCart(userId, input) {
208
234
  groupId,
209
235
  groupSlug: groupSource === "product-group" ? groupId : undefined,
210
236
  groupTitle,
237
+ /*
238
+ * Snapshotted at add time — see `CartItemDocument.groupDiscountPercent`.
239
+ * Omitted entirely when there is no discount, so a line with no group
240
+ * pricing carries no field rather than a zero that reads as "0% off".
241
+ */
242
+ ...(rules.groupDiscountPercent !== undefined && rules.groupDiscountPercent > 0
243
+ ? { groupDiscountPercent: rules.groupDiscountPercent }
244
+ : {}),
211
245
  groupMembers: resolved,
212
246
  });
213
247
  }
248
+ /**
249
+ * The group's selection rules, from whichever of the two homes owns them.
250
+ *
251
+ * 🛑 ONE resolver, because there are TWO homes: a product group keeps them on
252
+ * its PARENT PRODUCT (identified by the parent's slug, which IS the groupId),
253
+ * while a `groupedListing` keeps them on its own document. Reading them at each
254
+ * call site is how the picker and the server come to disagree.
255
+ *
256
+ * Fails OPEN — an unreadable group yields no rules rather than refusing the
257
+ * add. A transient Firestore error must not look like "your selection is
258
+ * invalid", which is a statement about the buyer's choice and would send them
259
+ * to change something that was already correct.
260
+ */
261
+ async function resolveGroupRules(groupId, groupSource, sample) {
262
+ if (groupSource === "grouped-listing") {
263
+ const { groupedListingsRepository } = await import("../../grouped/repository/grouped-listings.repository.js");
264
+ const group = await safeRead(() => groupedListingsRepository.findById(groupId), {
265
+ route: "/cart", key: "groupedListings.rules", fallback: null,
266
+ });
267
+ return readGroupRules(group);
268
+ }
269
+ /*
270
+ * A product group's rules live on the PARENT, and `sample` is an arbitrary
271
+ * member — so the parent has to be fetched unless the sample happens to BE
272
+ * it. `groupId` is the parent's slug and products are pure slugs
273
+ * (`id === slug`), so `findById` resolves it directly.
274
+ */
275
+ if (sample?.isGroupParent && sample.id === groupId)
276
+ return readGroupRules(sample);
277
+ const parent = await safeRead(() => productRepository.findById(groupId), {
278
+ route: "/cart", key: "products.groupParentRules", fallback: null,
279
+ });
280
+ return readGroupRules(parent);
281
+ }
214
282
  /** Is this product listed on that `groupedListings` document? */
215
283
  async function isGroupedListingMember(groupId, productId) {
216
284
  const { groupedListingsRepository } = await import("../../grouped/repository/grouped-listings.repository.js");
@@ -263,14 +331,23 @@ export async function mergeGuestCart(userId, items) {
263
331
  const product = await productRepository.findById(item.productId);
264
332
  if (!product || product.status !== "published")
265
333
  continue;
266
- if (product.availableQuantity < 1)
334
+ /*
335
+ * A made-to-order row survives the guest-cart merge at its full requested
336
+ * quantity. Dropping it (or clamping it to 0) would silently delete the
337
+ * line a signed-out visitor had chosen, which reads as the cart losing
338
+ * items on login — and there is no stock level for the clamp to clamp to.
339
+ */
340
+ const unlimited = product.madeToOrder === true;
341
+ if (!unlimited && product.availableQuantity < 1)
267
342
  continue;
268
- const safeQty = Math.min(item.quantity, product.availableQuantity);
343
+ const safeQty = unlimited
344
+ ? item.quantity
345
+ : Math.min(item.quantity, product.availableQuantity);
269
346
  const storeName = await resolveStoreName(product.storeId, product.storeName);
270
347
  await cartRepository.addItem(userId, {
271
348
  productId: product.id,
272
349
  productTitle: product.title,
273
- productImage: product.images?.[0] ?? "",
350
+ productImage: product.images?.[0]?.url ?? "",
274
351
  price: product.price,
275
352
  currency: product.currency ?? getDefaultCurrency(),
276
353
  quantity: safeQty,
@@ -162,10 +162,33 @@ export class CartRepository extends BaseRepository {
162
162
  * subtotals all compute `price × quantity`, and a grouped line pins
163
163
  * `quantity` to 1, so all four stay correct with no changes of their own.
164
164
  */
165
- deriveLinePrice(members) {
165
+ deriveLinePrice(members, discountPercent) {
166
166
  const raw = members.reduce((sum, m) => sum + m.unitPrice * m.quantity, 0);
167
+ /*
168
+ * 🛑 THE GROUP DISCOUNT IS APPLIED HERE AND NOWHERE ELSE.
169
+ *
170
+ * This function is the only writer of `item.price` on a multi-member line,
171
+ * and `item.price` is what `getSubtotal`, the cart page's per-seller
172
+ * subtotal, both lane subtotals, `unitPriceFor` and
173
+ * `expandCartLineToOrderRows`' per-member GST proration (via
174
+ * `allocateAcrossMembers`) all read. So discounting here reaches every one
175
+ * of them with no change of their own — and discounting anywhere else means
176
+ * the cart total and the invoice are computed from two different numbers,
177
+ * which is exactly what Root Cause #75 cost.
178
+ *
179
+ * In particular `allocateAcrossMembers` prorates the LOCKED line price
180
+ * across members for tax, so a discount applied after it would under- or
181
+ * over-report GST against the amount actually charged.
182
+ *
183
+ * Clamped 0–100 rather than trusted: the percent is a snapshot copied off a
184
+ * seller-editable rule, and a negative value would silently INFLATE the
185
+ * line while a value over 100 would make it negative.
186
+ */
187
+ const pct = Math.min(100, Math.max(0, discountPercent ?? 0));
188
+ // audit-money-units-ok: pct/100 is a PERCENTAGE divisor (0-100 -> 0-1 fraction), not a paise conversion — raw/discounted are decimal rupees throughout.
189
+ const discounted = pct > 0 ? raw * (1 - pct / 100) : raw;
167
190
  // audit-money-units-ok: decimal rupees throughout; rounding to 2dp, not paise conversion.
168
- return Math.round(raw * 100) / 100;
191
+ return Math.round(discounted * 100) / 100;
169
192
  }
170
193
  /**
171
194
  * Create a multi-member line. Deliberately never merges into an existing
@@ -182,7 +205,7 @@ export class CartRepository extends BaseRepository {
182
205
  productId: input.productId,
183
206
  productTitle: input.productTitle,
184
207
  productImage: input.productImage,
185
- price: this.deriveLinePrice(input.groupMembers),
208
+ price: this.deriveLinePrice(input.groupMembers, input.groupDiscountPercent),
186
209
  currency: input.currency,
187
210
  quantity: input.quantity,
188
211
  storeId: input.storeId,
@@ -264,7 +287,12 @@ export class CartRepository extends BaseRepository {
264
287
  items[itemIndex] = {
265
288
  ...target,
266
289
  groupMembers: nextMembers,
267
- price: this.deriveLinePrice(nextMembers),
290
+ /*
291
+ * The line's OWN stored percent, so changing a member quantity
292
+ * preserves the discount the buyer was quoted. Re-resolving the rule
293
+ * here would silently reprice a line mid-edit.
294
+ */
295
+ price: this.deriveLinePrice(nextMembers, target.groupDiscountPercent),
268
296
  updatedAt: new Date(),
269
297
  };
270
298
  const updatedCart = { ...cart, items, updatedAt: new Date() };
@@ -148,6 +148,24 @@ export interface CartItemDocument {
148
148
  groupSlug?: string;
149
149
  /** Display snapshot of the group/bundle name. */
150
150
  groupTitle?: string;
151
+ /**
152
+ * The group discount, as a percentage, AS QUOTED WHEN THIS LINE WAS ADDED.
153
+ *
154
+ * 🛑 Stored on the line rather than resolved at read time, and that is not
155
+ * the mirror-drift mistake it resembles (Root Cause #42). It has to be here
156
+ * for a mechanical reason — `updateGroupMembers` recomputes `price` from
157
+ * `groupMembers` and holds only the CART, so with nothing on the line a
158
+ * buyer changing a quantity would silently lose the discount — and for a
159
+ * commercial one: this is the basis of the price the buyer was shown.
160
+ *
161
+ * 🛑 Because it is a snapshot it is RE-VALIDATED at checkout against the
162
+ * group's current rule, and a disagreement is REPORTED, never silently
163
+ * resolved either way. Honouring a stale percent would charge less than the
164
+ * seller now offers; re-deriving silently would change a total the buyer has
165
+ * already read. Same discipline as `revalidateAppliedCoupons`, which drops a
166
+ * lapsed coupon and tells the caller.
167
+ */
168
+ groupDiscountPercent?: number;
151
169
  /**
152
170
  * The members of a multi-member line, AUTHORITATIVE over `bundleProductIds`.
153
171
  * See `CartLineMember` for the quantity invariant.
@@ -300,6 +318,8 @@ export type AddToCartInput = {
300
318
  groupId?: string;
301
319
  groupSlug?: string;
302
320
  groupTitle?: string;
321
+ /** See `CartItemDocument.groupDiscountPercent` — a quoted-at-add snapshot. */
322
+ groupDiscountPercent?: number;
303
323
  groupMembers?: CartLineMember[];
304
324
  };
305
325
  /**
@@ -174,8 +174,8 @@ export declare const cartSummarySchema: z.ZodObject<{
174
174
  sessionId?: string | undefined;
175
175
  addedAt?: string | undefined;
176
176
  }[];
177
- itemCount: number;
178
177
  subtotal: number;
178
+ itemCount: number;
179
179
  }, {
180
180
  items: {
181
181
  id: string;
@@ -193,7 +193,7 @@ export declare const cartSummarySchema: z.ZodObject<{
193
193
  sessionId?: string | undefined;
194
194
  addedAt?: string | undefined;
195
195
  }[];
196
- itemCount: number;
197
196
  subtotal: number;
197
+ itemCount: number;
198
198
  currency?: string | undefined;
199
199
  }>;
@@ -29,5 +29,5 @@ export function PublicCatalogueItemDetailView({ ownerSlug, itemId }) {
29
29
  }
30
30
  const { owner, item, linkedProductHref } = data;
31
31
  const images = item.images.length > 0 ? item.images : item.mainImage ? [item.mainImage] : [];
32
- return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { label: "Sign in to see the estimated value", children: formatCurrency(item.price) }) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
32
+ return (_jsx(Main, { children: _jsxs(Container, { size: "lg", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", gap: "xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: "/", className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: `${ROUTES.PUBLIC.PROFILE(ownerSlug)}/catalogue`, className: "hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: owner.displayName ?? "Catalogue" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: item.title })] }), _jsxs(Grid, { cols: 2, gap: "lg", children: [images.length > 0 ? (_jsx(Div, { className: `relative aspect-square w-full ${__O.hidden}`, rounded: "xl", surface: "muted", children: _jsx(MediaImage, { src: images[0], alt: item.title, size: "hero", objectFit: "contain" }) })) : (_jsx(Div, { className: "relative aspect-square w-full", rounded: "xl", surface: "muted" })), images.length > 1 && (_jsx(Row, { gap: "sm", wrap: true, className: "col-span-2", children: images.slice(1).map((src, i) => (_jsx(Div, { className: `relative h-20 w-20 ${__O.hidden}`, rounded: "lg", surface: "muted", children: _jsx(MediaImage, { src: src, alt: `${item.title} photo ${i + 2}`, size: "thumbnail", objectFit: "cover" }) }, i))) })), _jsxs(Stack, { gap: "md", children: [_jsx(Heading, { level: 1, size: "2xl", weight: "bold", color: "primary", children: item.title }), typeof item.price === "number" && item.price > 0 && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "2xl", weight: "bold", color: "primary", children: _jsx(GatedPrice, { label: "Sign in to see the estimated value", children: formatCurrency(item.price) }) }), _jsx(Text, { size: "xs", color: "muted", children: "Estimated value \u2014 not for direct sale from this page." })] })), _jsxs(Row, { gap: "sm", wrap: true, children: [item.condition && (_jsx(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: item.condition })), typeof item.quantity === "number" && (_jsxs(Span, { size: "xs", weight: "medium", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Qty: ", item.quantity] }))] }), item.description && (_jsx(Text, { color: "muted", className: "whitespace-pre-line", children: item.description })), owner.displayName && (_jsxs(Div, { border: "subtle", rounded: "xl", surface: "muted", padding: "md", children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Owned by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: owner.displayName })] })), linkedProductHref && (_jsx(Link, { href: linkedProductHref, className: "inline-flex w-fit items-center rounded-lg bg-primary px-[var(--appkit-space-4)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] font-semibold text-white transition-colors hover:opacity-90", children: "View live listing \u2192" }))] })] })] }) }));
33
33
  }