@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
@@ -273,6 +273,15 @@ export declare const CATEGORY_ENDPOINTS: {
273
273
  * category change in the form costs zero function invocations on a hit.
274
274
  */
275
275
  readonly DESCRIPTION_TEMPLATE: (id: string, variant?: string) => string;
276
+ /**
277
+ * The inherited `priceGuidance` band, restated on the calling seller's own
278
+ * tax basis — seller/admin/moderator only. Deliberately a SEPARATE
279
+ * endpoint from `DESCRIPTION_TEMPLATE`: that one is public and edge-cached
280
+ * so anyone can fetch it, and `priceGuidance` is withheld from that
281
+ * response for the same reason a listing's own price is hidden from guests
282
+ * (CLAUDE.md § "Guest price gating").
283
+ */
284
+ readonly PRICE_GUIDANCE: (id: string) => string;
276
285
  };
277
286
  export declare const TOOL_ENDPOINTS: {
278
287
  /**
@@ -286,6 +295,19 @@ export declare const TOOL_ENDPOINTS: {
286
295
  * its invocation allowance.
287
296
  */
288
297
  readonly PRICE_INDEX_SEARCH: (q: string) => string;
298
+ /**
299
+ * Tier 1 adoption — the client resolved these ids LOCALLY, from the
300
+ * public search artifact, at zero Firestore reads. This route's only job
301
+ * is the multi-get `searchPriceIndex` otherwise pays for every lookup.
302
+ */
303
+ readonly PRICE_INDEX_BANDS: (ids: readonly string[]) => string;
304
+ /**
305
+ * The competitor-crawl market band for the same leaves — SIGNED-IN ONLY, and
306
+ * therefore a separate request from `PRICE_INDEX_BANDS` rather than more
307
+ * fields on it. That route is public and edge-cached; this one must not be,
308
+ * or one signed-in lookup populates an entry every guest reads.
309
+ */
310
+ readonly PRICE_INDEX_MARKET: (ids: readonly string[]) => string;
289
311
  };
290
312
  /**
291
313
  * The sticker-sheet kiss-cut tracer. Admin-only, every route behind
@@ -377,8 +399,6 @@ export declare const FAQ_ENDPOINTS: {
377
399
  readonly VOTE: "/api/faqs/vote";
378
400
  };
379
401
  export declare const HOMEPAGE_ENDPOINTS: {
380
- readonly GET: "/api/homepage";
381
- readonly SECTIONS: "/api/homepage-sections";
382
402
  readonly CAROUSEL: "/api/carousel";
383
403
  readonly NEWSLETTER_SUBSCRIBE: "/api/newsletter/subscribe";
384
404
  };
@@ -434,10 +454,9 @@ export declare const REVIEW_ENDPOINTS: {
434
454
  readonly FEATURED: "/api/reviews?featured=true";
435
455
  };
436
456
  export declare const SEARCH_ENDPOINTS: {
437
- readonly QUERY: (q: string, extra?: string) => string;
438
457
  readonly SUGGESTIONS: "/api/search/suggestions";
439
- /** @param params — pre-built querystring (no leading `?`) from a caller-owned URLSearchParams */
440
- readonly RAW: (params: string) => string;
458
+ /** Tier 2 — the viewer's own orders/offers/drafts/addresses. `private, no-store`. */
459
+ readonly SCOPED: (q: string) => string;
441
460
  };
442
461
  export declare const SELLER_ENDPOINTS: {
443
462
  readonly BECOME: "/api/seller/become";
@@ -816,6 +835,15 @@ export declare const API_ENDPOINTS: {
816
835
  * category change in the form costs zero function invocations on a hit.
817
836
  */
818
837
  readonly DESCRIPTION_TEMPLATE: (id: string, variant?: string) => string;
838
+ /**
839
+ * The inherited `priceGuidance` band, restated on the calling seller's own
840
+ * tax basis — seller/admin/moderator only. Deliberately a SEPARATE
841
+ * endpoint from `DESCRIPTION_TEMPLATE`: that one is public and edge-cached
842
+ * so anyone can fetch it, and `priceGuidance` is withheld from that
843
+ * response for the same reason a listing's own price is hidden from guests
844
+ * (CLAUDE.md § "Guest price gating").
845
+ */
846
+ readonly PRICE_GUIDANCE: (id: string) => string;
819
847
  };
820
848
  readonly BEFORE_AFTER: {
821
849
  readonly LIST: "/api/before-after";
@@ -880,8 +908,6 @@ export declare const API_ENDPOINTS: {
880
908
  readonly VOTE: "/api/faqs/vote";
881
909
  };
882
910
  readonly HOMEPAGE: {
883
- readonly GET: "/api/homepage";
884
- readonly SECTIONS: "/api/homepage-sections";
885
911
  readonly CAROUSEL: "/api/carousel";
886
912
  readonly NEWSLETTER_SUBSCRIBE: "/api/newsletter/subscribe";
887
913
  };
@@ -934,10 +960,9 @@ export declare const API_ENDPOINTS: {
934
960
  readonly FEATURED: "/api/reviews?featured=true";
935
961
  };
936
962
  readonly SEARCH: {
937
- readonly QUERY: (q: string, extra?: string) => string;
938
963
  readonly SUGGESTIONS: "/api/search/suggestions";
939
- /** @param params — pre-built querystring (no leading `?`) from a caller-owned URLSearchParams */
940
- readonly RAW: (params: string) => string;
964
+ /** Tier 2 — the viewer's own orders/offers/drafts/addresses. `private, no-store`. */
965
+ readonly SCOPED: (q: string) => string;
941
966
  };
942
967
  readonly SELLER: {
943
968
  readonly BECOME: "/api/seller/become";
@@ -1337,6 +1362,15 @@ export declare const API_ROUTES: {
1337
1362
  * category change in the form costs zero function invocations on a hit.
1338
1363
  */
1339
1364
  readonly DESCRIPTION_TEMPLATE: (id: string, variant?: string) => string;
1365
+ /**
1366
+ * The inherited `priceGuidance` band, restated on the calling seller's own
1367
+ * tax basis — seller/admin/moderator only. Deliberately a SEPARATE
1368
+ * endpoint from `DESCRIPTION_TEMPLATE`: that one is public and edge-cached
1369
+ * so anyone can fetch it, and `priceGuidance` is withheld from that
1370
+ * response for the same reason a listing's own price is hidden from guests
1371
+ * (CLAUDE.md § "Guest price gating").
1372
+ */
1373
+ readonly PRICE_GUIDANCE: (id: string) => string;
1340
1374
  };
1341
1375
  readonly BEFORE_AFTER: {
1342
1376
  readonly LIST: "/api/before-after";
@@ -1401,8 +1435,6 @@ export declare const API_ROUTES: {
1401
1435
  readonly VOTE: "/api/faqs/vote";
1402
1436
  };
1403
1437
  readonly HOMEPAGE: {
1404
- readonly GET: "/api/homepage";
1405
- readonly SECTIONS: "/api/homepage-sections";
1406
1438
  readonly CAROUSEL: "/api/carousel";
1407
1439
  readonly NEWSLETTER_SUBSCRIBE: "/api/newsletter/subscribe";
1408
1440
  };
@@ -1455,10 +1487,9 @@ export declare const API_ROUTES: {
1455
1487
  readonly FEATURED: "/api/reviews?featured=true";
1456
1488
  };
1457
1489
  readonly SEARCH: {
1458
- readonly QUERY: (q: string, extra?: string) => string;
1459
1490
  readonly SUGGESTIONS: "/api/search/suggestions";
1460
- /** @param params — pre-built querystring (no leading `?`) from a caller-owned URLSearchParams */
1461
- readonly RAW: (params: string) => string;
1491
+ /** Tier 2 — the viewer's own orders/offers/drafts/addresses. `private, no-store`. */
1492
+ readonly SCOPED: (q: string) => string;
1462
1493
  };
1463
1494
  readonly SELLER: {
1464
1495
  readonly BECOME: "/api/seller/become";
@@ -317,6 +317,15 @@ export const CATEGORY_ENDPOINTS = {
317
317
  * category change in the form costs zero function invocations on a hit.
318
318
  */
319
319
  DESCRIPTION_TEMPLATE: (id, variant) => `/api/categories/${id}/description-template${variant ? `?variant=${variant}` : ""}`,
320
+ /**
321
+ * The inherited `priceGuidance` band, restated on the calling seller's own
322
+ * tax basis — seller/admin/moderator only. Deliberately a SEPARATE
323
+ * endpoint from `DESCRIPTION_TEMPLATE`: that one is public and edge-cached
324
+ * so anyone can fetch it, and `priceGuidance` is withheld from that
325
+ * response for the same reason a listing's own price is hidden from guests
326
+ * (CLAUDE.md § "Guest price gating").
327
+ */
328
+ PRICE_GUIDANCE: (id) => `/api/categories/${id}/price-guidance`,
320
329
  };
321
330
  // ---------------------------------------------------------------------------
322
331
  // Tools — public surfaces that answer a question rather than sell something
@@ -333,6 +342,19 @@ export const TOOL_ENDPOINTS = {
333
342
  * its invocation allowance.
334
343
  */
335
344
  PRICE_INDEX_SEARCH: (q) => `/api/tools/price-index?q=${encodeURIComponent(q)}`,
345
+ /**
346
+ * Tier 1 adoption — the client resolved these ids LOCALLY, from the
347
+ * public search artifact, at zero Firestore reads. This route's only job
348
+ * is the multi-get `searchPriceIndex` otherwise pays for every lookup.
349
+ */
350
+ PRICE_INDEX_BANDS: (ids) => `/api/tools/price-index/bands?ids=${ids.map(encodeURIComponent).join(",")}`,
351
+ /**
352
+ * The competitor-crawl market band for the same leaves — SIGNED-IN ONLY, and
353
+ * therefore a separate request from `PRICE_INDEX_BANDS` rather than more
354
+ * fields on it. That route is public and edge-cached; this one must not be,
355
+ * or one signed-in lookup populates an entry every guest reads.
356
+ */
357
+ PRICE_INDEX_MARKET: (ids) => `/api/tools/price-index/market?ids=${ids.map(encodeURIComponent).join(",")}`,
336
358
  };
337
359
  /**
338
360
  * The sticker-sheet kiss-cut tracer. Admin-only, every route behind
@@ -473,8 +495,17 @@ export const FAQ_ENDPOINTS = {
473
495
  // Homepage
474
496
  // ---------------------------------------------------------------------------
475
497
  export const HOMEPAGE_ENDPOINTS = {
476
- GET: "/api/homepage",
477
- SECTIONS: "/api/homepage-sections",
498
+ /*
499
+ * 🛑 `GET` and `SECTIONS` were both removed 2026-10-19 — `/api/homepage` was
500
+ * NEVER mounted (only a dead hook referenced it), and `/api/homepage-sections`
501
+ * was a mounted generic-starter-kit stub writing/reading the real
502
+ * `homepageSections` collection through an incompatible schema. See the
503
+ * deletion note in `features/homepage/types/index.ts` for the full writeup.
504
+ * The real, single section API is `/api/admin/sections` (admin CRUD);
505
+ * public rendering reads `homepageSectionsRepository` directly, server-side,
506
+ * in `MarketplaceHomepageView` — no public GET endpoint for sections exists
507
+ * or is needed.
508
+ */
478
509
  CAROUSEL: "/api/carousel",
479
510
  NEWSLETTER_SUBSCRIBE: "/api/newsletter/subscribe",
480
511
  };
@@ -566,10 +597,9 @@ export const REVIEW_ENDPOINTS = {
566
597
  // Search
567
598
  // ---------------------------------------------------------------------------
568
599
  export const SEARCH_ENDPOINTS = {
569
- QUERY: (q, extra) => `/api/search?q=${encodeURIComponent(q)}${extra ? `&${extra}` : ""}`,
570
600
  SUGGESTIONS: "/api/search/suggestions",
571
- /** @param params — pre-built querystring (no leading `?`) from a caller-owned URLSearchParams */
572
- RAW: (params) => `/api/search?${params}`,
601
+ /** Tier 2 — the viewer's own orders/offers/drafts/addresses. `private, no-store`. */
602
+ SCOPED: (q) => `/api/search/scoped?q=${encodeURIComponent(q)}`,
573
603
  };
574
604
  // ---------------------------------------------------------------------------
575
605
  // Seller
@@ -20,7 +20,9 @@ export const COLLECTION_CACHE_PATHS = {
20
20
  categories: [CATEGORY_ENDPOINTS.LIST],
21
21
  products: [PRODUCT_ENDPOINTS.LIST],
22
22
  carouselSlides: [HOMEPAGE_ENDPOINTS.CAROUSEL],
23
- homepageSections: [HOMEPAGE_ENDPOINTS.SECTIONS],
23
+ // No public GET endpoint exists for homepageSections (removed 2026-10-19 —
24
+ // see HOMEPAGE_ENDPOINTS' own comment). Real-time freshness comes from
25
+ // `revalidatePath("/")`, which /api/admin/sections already calls on write.
24
26
  siteSettings: [SITE_SETTINGS_ENDPOINTS.GET],
25
27
  faqs: [FAQ_ENDPOINTS.LIST],
26
28
  reviews: [REVIEW_ENDPOINTS.LIST],
@@ -49,6 +49,9 @@ export declare const PRODUCT_FIELDS: {
49
49
  readonly STOCK_QUANTITY: "stockQuantity";
50
50
  readonly AVAILABLE_QUANTITY: "availableQuantity";
51
51
  readonly IS_SOLD: "isSold";
52
+ readonly MADE_TO_ORDER: "madeToOrder";
53
+ readonly PART_GLOSSARY_ID: "partGlossaryId";
54
+ readonly COLOURWAY: "colourway";
52
55
  readonly MAIN_IMAGE: "mainImage";
53
56
  readonly IMAGES: "images";
54
57
  readonly VIDEO: "video";
@@ -181,6 +184,7 @@ export declare const PRODUCT_FIELDS: {
181
184
  readonly LIVE: "live";
182
185
  readonly ART: "art";
183
186
  readonly STICKERS: "stickers";
187
+ readonly SPARES: "spares";
184
188
  };
185
189
  };
186
190
  export declare const PRODUCT_STATUS_TRANSITIONS: Record<string, readonly string[]>;
@@ -853,6 +857,8 @@ export declare const HOMEPAGE_SECTION_FIELDS: {
853
857
  readonly ORDER: "order";
854
858
  readonly ENABLED: "enabled";
855
859
  readonly CONFIG: "config";
860
+ /** Which page the row belongs to. Absent on a row means "homepage". */
861
+ readonly SURFACE: "surface";
856
862
  readonly CREATED_AT: "createdAt";
857
863
  readonly UPDATED_AT: "updatedAt";
858
864
  readonly TYPE_VALUES: {
@@ -58,6 +58,9 @@ export const PRODUCT_FIELDS = {
58
58
  STOCK_QUANTITY: "stockQuantity",
59
59
  AVAILABLE_QUANTITY: "availableQuantity",
60
60
  IS_SOLD: "isSold",
61
+ MADE_TO_ORDER: "madeToOrder",
62
+ PART_GLOSSARY_ID: "partGlossaryId",
63
+ COLOURWAY: "colourway",
61
64
  MAIN_IMAGE: "mainImage",
62
65
  IMAGES: "images",
63
66
  VIDEO: "video",
@@ -201,6 +204,7 @@ export const PRODUCT_FIELDS = {
201
204
  LIVE: "live",
202
205
  ART: "art",
203
206
  STICKERS: "stickers",
207
+ SPARES: "spares",
204
208
  },
205
209
  };
206
210
  export const PRODUCT_STATUS_TRANSITIONS = {
@@ -1035,6 +1039,8 @@ export const HOMEPAGE_SECTION_FIELDS = {
1035
1039
  ORDER: "order",
1036
1040
  ENABLED: "enabled",
1037
1041
  CONFIG: "config",
1042
+ /** Which page the row belongs to. Absent on a row means "homepage". */
1043
+ SURFACE: "surface",
1038
1044
  CREATED_AT: "createdAt",
1039
1045
  UPDATED_AT: "updatedAt",
1040
1046
  TYPE_VALUES: {
@@ -1,6 +1,26 @@
1
- export type PolicyKey = "privacy" | "terms" | "cookies" | "refund" | "ethics" | "conduct";
1
+ export type PolicyKey = "privacy" | "terms" | "cookies" | "refund" | "ethics" | "conduct" | "cancellation" | "grievance";
2
2
  export interface PolicyPageViewProps {
3
3
  /** Which policy to render */
4
4
  policy: PolicyKey;
5
+ /**
6
+ * Rendered after the policy sections and before the related links.
7
+ *
8
+ * The slot exists for /grievance, whose officer block (name, designation,
9
+ * email, phone) comes from `appkit.config.js` → `business.grievance` rather
10
+ * than from i18n — those values are deployment-specific and some are still
11
+ * undecided, so they render through `<PendingValue>` as a visible
12
+ * `[to be confirmed]` marker.
13
+ *
14
+ * 🛑 Deliberately a `children` ReactNode and not a render prop: this is an
15
+ * async Server Component, and a function prop would have to cross the
16
+ * server/client boundary at the call site to be useful. The caller builds
17
+ * the node and passes it down, which needs nothing serialisable.
18
+ *
19
+ * Note it renders under the i18n sections but is SKIPPED when an admin HTML
20
+ * override is present — an override replaces the page's body, and silently
21
+ * appending a block the admin cannot see or edit would make the override a
22
+ * half-truth.
23
+ */
24
+ children?: React.ReactNode;
5
25
  }
6
- export declare function PolicyPageView({ policy, }: PolicyPageViewProps): Promise<import("react").JSX.Element>;
26
+ export declare function PolicyPageView({ policy, children, }: PolicyPageViewProps): Promise<import("react").JSX.Element>;
@@ -58,9 +58,21 @@ const POLICY_META = {
58
58
  firestoreField: "codeOfConduct",
59
59
  relatedLabelKey: "relatedConduct",
60
60
  },
61
+ cancellation: {
62
+ path: String(ROUTES.PUBLIC.CANCELLATION_POLICY),
63
+ namespace: "cancellationPolicy",
64
+ firestoreField: "cancellation",
65
+ relatedLabelKey: "relatedCancellation",
66
+ },
67
+ grievance: {
68
+ path: String(ROUTES.PUBLIC.GRIEVANCE),
69
+ namespace: "grievance",
70
+ firestoreField: "grievance",
71
+ relatedLabelKey: "relatedGrievance",
72
+ },
61
73
  };
62
74
  const POLICY_KEYS = Object.keys(POLICY_META);
63
- export async function PolicyPageView({ policy, }) {
75
+ export async function PolicyPageView({ policy, children, }) {
64
76
  const page = { container: PAGE_CONTAINER };
65
77
  const meta = POLICY_META[policy];
66
78
  const { getTranslations } = await import("next-intl/server");
@@ -93,5 +105,5 @@ export async function PolicyPageView({ policy, }) {
93
105
  }));
94
106
  return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm}`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80", size: "sm", children: t("lastUpdated") })] }) }), _jsxs(Div, { className: `${page.container.sm}`, padding: "content-banner", children: [adminHtml ? (
95
107
  /* Admin-set HTML takes priority */
96
- _jsx(Div, { className: "prose max-w-none", dangerouslySetInnerHTML: { __html: adminHtml } })) : (_jsxs(_Fragment, { children: [t.has("intro") && (_jsx(Text, { variant: "secondary", className: "mb-10 leading-relaxed", size: "base", children: t("intro") })), _jsx(Stack, { gap: "xl", children: sections.map((section, i) => (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-3", children: section.heading }), _jsx(Text, { variant: "secondary", className: "leading-relaxed whitespace-pre-line", children: section.body })] }, i))) })] })), relatedLinks.length > 0 && (_jsxs(Div, { className: `mt-14 border-t`, border: "default", padding: "t-xl", children: [_jsx(Heading, { level: 3, className: "mb-3 uppercase tracking-wide", color: "muted", size: "sm", weight: "semibold", children: t("relatedTitle") }), _jsx(Row, { wrap: true, gap: "md", children: relatedLinks.map((l) => (_jsx(TextLink, { href: l.href, variant: "muted", size: "sm", children: l.label }, l.href))) })] }))] })] }));
108
+ _jsx(Div, { className: "prose max-w-none", dangerouslySetInnerHTML: { __html: adminHtml } })) : (_jsxs(_Fragment, { children: [t.has("intro") && (_jsx(Text, { variant: "secondary", className: "mb-10 leading-relaxed", size: "base", children: t("intro") })), _jsx(Stack, { gap: "xl", children: sections.map((section, i) => (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-3", children: section.heading }), _jsx(Text, { variant: "secondary", className: "leading-relaxed whitespace-pre-line", children: section.body })] }, i))) }), children] })), relatedLinks.length > 0 && (_jsxs(Div, { className: `mt-14 border-t`, border: "default", padding: "t-xl", children: [_jsx(Heading, { level: 3, className: "mb-3 uppercase tracking-wide", color: "muted", size: "sm", weight: "semibold", children: t("relatedTitle") }), _jsx(Row, { wrap: true, gap: "md", children: relatedLinks.map((l) => (_jsx(TextLink, { href: l.href, variant: "muted", size: "sm", children: l.label }, l.href))) })] }))] })] }));
97
109
  }
@@ -1,5 +1,6 @@
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
+ import { productImageUrls } from "../../products/schemas/firestore.js";
3
4
  import { getPublicUserProfile, getProfileStoreProducts, getReviewsAuthoredBy, getReviewsReceivedBy } from "../../auth/actions/profile-actions.js";
4
5
  import { storeRepository } from "../../stores/repository/store.repository.js";
5
6
  import { safeRead } from "../../../errors/safe-read.js";
@@ -37,7 +38,7 @@ function toProductItem(p) {
37
38
  originalPrice: p.originalPrice,
38
39
  currency: p.currency,
39
40
  mainImage: p.mainImage,
40
- images: p.images,
41
+ images: productImageUrls(p.images),
41
42
  status: p.status,
42
43
  condition: p.condition,
43
44
  listingType: p.listingType,
@@ -1,4 +1,7 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { normalizeError } from "../../../errors/normalize.js";
3
+ import { serverLogger } from "../../../monitoring/server-logger.js";
4
+ import { siteSettingsRepository } from "../../../repositories/index.js";
2
5
  import { ROUTES } from "../../../constants/index.js";
3
6
  import { PAGE_CONTAINER } from "../../../_internal/shared/styles/page.js";
4
7
  import { Div, Heading, Text, Section, Stack, FlowDiagram } from "../../../ui/index.js";
@@ -8,6 +11,35 @@ export async function ShippingPolicyView({} = {}) {
8
11
  const page = { container: PAGE_CONTAINER };
9
12
  const { getTranslations } = await import("next-intl/server");
10
13
  const t = await getTranslations("shippingPolicy");
14
+ /*
15
+ * 🛑 `siteSettings.legalPages.shipping` was WRITTEN by the admin form and
16
+ * read by nothing.
17
+ *
18
+ * Every other legal page renders through `PolicyPageView`, which checks that
19
+ * group for an admin HTML override. This page does not — it has a bespoke
20
+ * layout with a five-step delivery diagram that a flat `sections[]` cannot
21
+ * express — so the override had no reader and an admin who rewrote the
22
+ * shipping policy saw their text saved successfully and never appear.
23
+ *
24
+ * Reading it here rather than converting the page is the deliberate choice:
25
+ * converting would mean flattening 38 i18n keys into `sections[]` and losing
26
+ * the diagram, which is a regression dressed up as consolidation.
27
+ */
28
+ let adminHtml = "";
29
+ try {
30
+ const settings = await siteSettingsRepository.getSingleton();
31
+ adminHtml = settings.legalPages?.shipping ?? "";
32
+ }
33
+ catch (_err) {
34
+ void normalizeError(_err);
35
+ // The i18n copy below is a complete policy in its own right, so a failed
36
+ // read degrades to it — but an admin who HAS overridden this page would
37
+ // silently see the old text, so the operator needs to know.
38
+ serverLogger.warn("about.shippingPolicyView: siteSettings.legalPages read failed", {
39
+ field: "shipping",
40
+ error: normalizeError(_err).message,
41
+ });
42
+ }
11
43
  const SECTIONS = [
12
44
  { title: t("orderProcessTitle"), text: t("orderProcessText") },
13
45
  { title: t("standardTitle"), text: t("standardText") },
@@ -55,5 +87,13 @@ export async function ShippingPolicyView({} = {}) {
55
87
  desc: t("diagramStatus5Desc"),
56
88
  },
57
89
  ];
58
- return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm}`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80", children: t("lastUpdated") })] }) }), _jsxs(Div, { className: `${page.container.sm}`, padding: "content-banner", children: [_jsx(Text, { size: "lg", variant: "secondary", className: "mb-8", children: t("subtitle") }), _jsx(FlowDiagram, { title: `🚚 ${t("diagramTitle")}`, titleClass: "text-sky-700 dark:text-sky-300", connectorClass: "bg-sky-200 dark:bg-sky-800", steps: DIAGRAM_STEPS, className: "mb-10", note: `📌 ${t("diagramNote")}` }), _jsxs(Stack, { gap: "xl", children: [SECTIONS.map(({ title, text }) => (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-3", children: title }), _jsx(Text, { variant: "secondary", className: "leading-relaxed", children: text })] }, title))), _jsxs(Section, { rounded: "xl", padding: "lg", border: "default", surface: "subtle", children: [_jsx(Heading, { level: 2, className: "mb-2", children: t("contactTitle") }), _jsx(Text, { variant: "secondary", children: t("contactText") })] })] }), _jsxs(Div, { layout: "flex", gap: "6", textSize: "sm", className: `mt-12 border-t`, border: "default", padding: "t-xl", children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.TRACK_ORDER), className: CLS_FOOTER_LINK, children: t("trackOrder") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.HELP), className: CLS_FOOTER_LINK, children: t("helpCenter") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.CONTACT), className: CLS_FOOTER_LINK, children: t("contactUs") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.REFUND_POLICY), className: CLS_FOOTER_LINK, children: "Refund Policy" })] })] })] }));
90
+ return (_jsxs(Div, { className: "-mx-4 md:-mx-6 lg:-mx-8 -mt-6 sm:-mt-8 lg:-mt-10", children: [_jsx(Section, { color: "inverse", tone: "accent-banner", padding: "banner", children: _jsxs(Div, { className: `${page.container.sm}`, children: [_jsx(Heading, { color: "inverse", level: 1, variant: "none", className: "mb-3", children: t("title") }), _jsx(Text, { color: "inverse", variant: "none", className: "/80", children: t("lastUpdated") })] }) }), _jsxs(Div, { className: `${page.container.sm}`, padding: "content-banner", children: [adminHtml ? (
91
+ /*
92
+ Admin-set HTML replaces the WHOLE body — subtitle, sections,
93
+ diagram and contact block — matching PolicyPageView's behaviour
94
+ exactly. Rendering the override above the bundled copy would show
95
+ two shipping policies on one page, and the reader has no way to
96
+ know which one binds.
97
+ */
98
+ _jsx(Div, { className: "prose max-w-none", dangerouslySetInnerHTML: { __html: adminHtml } })) : (_jsxs(_Fragment, { children: [_jsx(Text, { size: "lg", variant: "secondary", className: "mb-8", children: t("subtitle") }), _jsx(FlowDiagram, { title: `🚚 ${t("diagramTitle")}`, titleClass: "text-sky-700 dark:text-sky-300", connectorClass: "bg-sky-200 dark:bg-sky-800", steps: DIAGRAM_STEPS, className: "mb-10", note: `📌 ${t("diagramNote")}` }), _jsxs(Stack, { gap: "xl", children: [SECTIONS.map(({ title, text }) => (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "mb-3", children: title }), _jsx(Text, { variant: "secondary", className: "leading-relaxed", children: text })] }, title))), _jsxs(Section, { rounded: "xl", padding: "lg", border: "default", surface: "subtle", children: [_jsx(Heading, { level: 2, className: "mb-2", children: t("contactTitle") }), _jsx(Text, { variant: "secondary", children: t("contactText") })] })] })] })), _jsxs(Div, { layout: "flex", gap: "6", textSize: "sm", className: `mt-12 border-t`, border: "default", padding: "t-xl", children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.TRACK_ORDER), className: CLS_FOOTER_LINK, children: t("trackOrder") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.HELP), className: CLS_FOOTER_LINK, children: t("helpCenter") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.CONTACT), className: CLS_FOOTER_LINK, children: t("contactUs") }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.REFUND_POLICY), className: CLS_FOOTER_LINK, children: "Refund Policy" })] })] })] }));
59
99
  }
@@ -109,5 +109,5 @@ function renderNotificationListContent(props) {
109
109
  return _jsx(Row, { align: "center", justify: "center", padding: "y-2xl", children: _jsx(Spinner, { size: "md" }) });
110
110
  if (notifications.length === 0)
111
111
  return (_jsxs(Stack, { justify: "center", className: "text-left", paddingY: "y-2xl", paddingX: "x-md", align: "start", children: [_jsx("svg", { className: "w-12 h-12 text-[var(--appkit-color-text-faint)] mb-3", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }) }), _jsx(Text, { weight: "medium", color: "primary", children: labels.empty }), _jsx(Text, { size: "sm", className: "mt-1", color: "muted", children: labels.emptyDesc })] }));
112
- return (_jsx(Ul, { children: notifications.map((notification) => (_jsxs(Li, { layout: "flex-start", gap: "3", padding: "inline", className: `group border-b border-[var(--appkit-color-border)] last:border-0 transition-colors hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-surface-elevated)] ${!notification.isRead ? "bg-primary/5 dark:bg-primary/10" : ""}`, children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: notificationIcons[notification.type] ?? "🔔" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { align: "start", justify: "between", gap: "xs", children: [_jsxs(Text, { size: "sm", className: "leading-tight", color: "primary", weight: "medium", children: [notification.title, !notification.isRead && _jsx(Span, { className: "ml-1.5 inline-block w-2 h-2 bg-primary flex-shrink-0 align-middle", rounded: "full" })] }), _jsx(Span, { size: "xs", className: "flex-shrink-0", color: "muted", children: formatRelativeTime(notification.createdAt) })] }), _jsx(Text, { size: "sm", className: `mt-0.5`, truncate: 2, color: "muted", children: notification.message }), _jsxs(Row, { gap: "3", className: "mt-1.5", children: [notification.actionUrl && renderActionLink({ href: notification.actionUrl, onClick: () => { void handleMarkReadAndClose(notification); }, className: "text-[length:var(--appkit-text-xs)] text-primary hover:underline font-medium", children: notification.actionLabel ?? labels.viewAction }), !notification.isRead && _jsx(Button, { variant: "ghost", onClick: () => void handleMarkRead(notification.id), className: "text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-zinc-700 hover:text-[var(--appkit-color-text-muted)] hover:underline p-[var(--appkit-space-0)] h-auto", children: labels.markRead })] })] })] }, notification.id))) }));
112
+ return (_jsx(Ul, { children: notifications.map((notification) => (_jsxs(Li, { layout: "flex-start", gap: "3", padding: "inline", className: `group border-b border-[var(--appkit-color-border)] last:border-0 transition-colors hover:bg-[var(--appkit-color-bg)] dark:hover:bg-[var(--appkit-color-surface-elevated)] ${!notification.isRead ? "bg-primary/5 dark:bg-primary/10" : ""}`, children: [_jsx(Span, { size: "xl", className: "flex-shrink-0 mt-0.5", children: notificationIcons[notification.type] ?? "🔔" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Row, { align: "start", justify: "between", gap: "xs", children: [_jsxs(Text, { size: "sm", className: "leading-tight", color: "primary", weight: "medium", children: [notification.title, !notification.isRead && _jsx(Span, { className: "ml-1.5 inline-block w-2 h-2 bg-primary flex-shrink-0 align-middle", rounded: "full" })] }), _jsx(Span, { size: "xs", className: "flex-shrink-0", color: "muted", children: formatRelativeTime(notification.createdAt) })] }), _jsx(Text, { size: "sm", className: `mt-0.5`, truncate: 2, color: "muted", children: notification.message }), _jsxs(Row, { gap: "3", className: "mt-1.5", children: [notification.actionUrl && renderActionLink({ href: notification.actionUrl, onClick: () => { void handleMarkReadAndClose(notification); }, className: "text-[length:var(--appkit-text-xs)] text-primary hover:underline font-medium", children: notification.actionLabel ?? labels.viewAction }), !notification.isRead && _jsx(Button, { variant: "ghost", onClick: () => void handleMarkRead(notification.id), className: "text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] hover:underline p-[var(--appkit-space-0)] h-auto", children: labels.markRead })] })] })] }, notification.id))) }));
113
113
  }
@@ -4,7 +4,11 @@ import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints.js";
4
4
  export function useProfileStats(enabled) {
5
5
  const { data: ordersData, isLoading: ordersLoading } = useQuery({
6
6
  queryKey: ["user-orders-count"],
7
- queryFn: () => apiClient.get(ACCOUNT_ENDPOINTS.ORDERS),
7
+ queryFn: () =>
8
+ // `items`, not `orders` — GET /api/user/orders emits the shared list
9
+ // envelope. Only `total` is read below, so the wrong key never threw
10
+ // here; it was a false declaration waiting for someone to read it.
11
+ apiClient.get(ACCOUNT_ENDPOINTS.ORDERS),
8
12
  enabled,
9
13
  });
10
14
  const { data: addressesData, isLoading: addressesLoading } = useQuery({
@@ -1,3 +1,4 @@
1
+ import type { JsonValue } from "../../../schemas/types";
1
2
  import { BaseRepository } from "../../../providers/db-firebase";
2
3
  import { type AddressBanStatus, type AddressCreateInput, type AddressDocument, type AddressPlain, type AddressOwnerType, type AddressUpdateInput } from "../schemas";
3
4
  export declare class AddressesRepository extends BaseRepository<AddressDocument> {
@@ -14,6 +15,21 @@ export declare class AddressesRepository extends BaseRepository<AddressDocument>
14
15
  * call.
15
16
  */
16
17
  protected piiFields: readonly ["fullName", "phone", "addressLine1", "addressLine2"];
18
+ /**
19
+ * Admin search by name, without decryption. ONLY `fullName` — never
20
+ * `addressLine1`: "find everyone on Brigade Road" is not a capability
21
+ * admin needs and is the single most re-identifying field on the
22
+ * document. See `security/blind-index-tokens.ts` for why this is
23
+ * whole-word rather than a prefix-expanded index.
24
+ *
25
+ * Runs through `applyWriteHooks`, so it reaches every write path
26
+ * including a seed load — which is what makes it survive the tester
27
+ * harness's cascade on `addresses` (it references `ownerId`, a
28
+ * PRESERVE-tier user, so the row itself is never actually wiped — but
29
+ * the field regenerating on every write is what makes that true by
30
+ * construction rather than by luck).
31
+ */
32
+ protected buildTokensIndexFor(data: Record<string, JsonValue>): Record<string, string[]> | null;
17
33
  /** Plaintext PII. **Opt-in and named** — shows up in review and in a grep. */
18
34
  revealAddress(doc: AddressDocument): AddressPlain;
19
35
  /** As `revealAddress`, for a list read. */
@@ -14,6 +14,7 @@ import { DatabaseError } from "../../../errors/index.js";
14
14
  import { serverLogger } from "../../../monitoring/index.js";
15
15
  import { BaseRepository, getFirestoreCount, prepareForFirestore, } from "../../../providers/db-firebase/index.js";
16
16
  import { maskPii, revealPii, ADDRESS_PII_FIELDS, } from "../../../security/index.js";
17
+ import { buildTokensIndex } from "../../../security/blind-index-tokens.js";
17
18
  import { ADDRESS_FIELDS, ADDRESSES_COLLECTION, } from "../schemas/index.js";
18
19
  export class AddressesRepository extends BaseRepository {
19
20
  constructor() {
@@ -31,6 +32,26 @@ export class AddressesRepository extends BaseRepository {
31
32
  */
32
33
  this.piiFields = ADDRESS_PII_FIELDS;
33
34
  }
35
+ /**
36
+ * Admin search by name, without decryption. ONLY `fullName` — never
37
+ * `addressLine1`: "find everyone on Brigade Road" is not a capability
38
+ * admin needs and is the single most re-identifying field on the
39
+ * document. See `security/blind-index-tokens.ts` for why this is
40
+ * whole-word rather than a prefix-expanded index.
41
+ *
42
+ * Runs through `applyWriteHooks`, so it reaches every write path
43
+ * including a seed load — which is what makes it survive the tester
44
+ * harness's cascade on `addresses` (it references `ownerId`, a
45
+ * PRESERVE-tier user, so the row itself is never actually wiped — but
46
+ * the field regenerating on every write is what makes that true by
47
+ * construction rather than by luck).
48
+ */
49
+ buildTokensIndexFor(data) {
50
+ const fullName = typeof data.fullName === "string" ? data.fullName : undefined;
51
+ if (!fullName)
52
+ return null;
53
+ return { nameTokensIndex: buildTokensIndex(fullName) };
54
+ }
34
55
  /*
35
56
  * 🛑 `mapDoc` is NOT overridden, and that absence IS the change.
36
57
  *
@@ -238,7 +238,22 @@ export async function adminUpdateProduct(adminId, id, input) {
238
238
  finalized.mainImage = await finalizeStagedMediaUrl(finalized.mainImage);
239
239
  }
240
240
  if (Array.isArray(finalized.images) && finalized.images.length > 0) {
241
- finalized.images = await finalizeStagedMediaArray(finalized.images);
241
+ /*
242
+ * 🛑 Finalise the URL and KEEP THE REST OF THE OBJECT (A6).
243
+ *
244
+ * `finalizeStagedMediaArray` takes and returns `string[]` — it moves
245
+ * staged uploads to their permanent path. Handing it
246
+ * `images.map(i => i.url)` and assigning the result back would have
247
+ * silently dropped `alt`, `width`, `height` and `thumbnailUrl` on every
248
+ * admin save: the exact lossy-write failure `toProductImages` carries its
249
+ * own warning about, on the one code path where a human has just typed
250
+ * the alt text.
251
+ */
252
+ const finalisedUrls = await finalizeStagedMediaArray(finalized.images.map((img) => img.url));
253
+ finalized.images = finalized.images.map((img, i) => ({
254
+ ...img,
255
+ url: finalisedUrls[i] ?? img.url,
256
+ }));
242
257
  }
243
258
  if (finalized.video?.url) {
244
259
  finalized.video = {
@@ -115,6 +115,14 @@ const categoryFormSchema = z.object({
115
115
  .optional()
116
116
  .or(z.literal("")),
117
117
  highlightsText: z.string().optional().or(z.literal("")),
118
+ /*
119
+ * The Hasbro / international-market name for a generation wave, rendered on
120
+ * the category page as "Also known as". Editable here because wave naming is
121
+ * editorial and gets corrected — the seed deliberately leaves it unset
122
+ * wherever a divergence is not evidenced, and this is how one gets filled in
123
+ * later without a reseed.
124
+ */
125
+ hasbroName: z.string().max(120, "Keep the name under 120 characters").optional().or(z.literal("")),
118
126
  taxCodeId: z.string().optional().or(z.literal("")),
119
127
  defaultCondition: z.string().optional().or(z.literal("")),
120
128
  defaultAuthenticity: z.string().optional().or(z.literal("")),
@@ -145,6 +153,7 @@ const EMPTY_VALUES = {
145
153
  showInMenu: true,
146
154
  contentBody: "",
147
155
  highlightsText: "",
156
+ hasbroName: "",
148
157
  taxCodeId: "",
149
158
  defaultCondition: "",
150
159
  defaultAuthenticity: "",
@@ -245,6 +254,13 @@ function buildContentPayload(v, faqs, specs, templates, carriedPriceGuidance) {
245
254
  contentBody: String(v.contentBody ?? ""),
246
255
  highlights: linesToList(String(v.highlightsText ?? "")),
247
256
  faqs: faqs.filter((f) => f.question.trim() !== "" && f.answer.trim() !== ""),
257
+ /*
258
+ * Omitted when blank rather than sent as `""`. The route schema is
259
+ * `.min(1)`, so an empty string is a 400 — and "I cleared the field" has
260
+ * to mean "do not write it", not "write an empty string every reader then
261
+ * has to treat as absent".
262
+ */
263
+ ...(String(v.hasbroName ?? "").trim() ? { hasbroName: String(v.hasbroName).trim() } : {}),
248
264
  descriptionTemplates: templates
249
265
  .filter((t) => t.label.trim() !== "" && t.body.trim() !== "")
250
266
  .map((t) => ({ ...t, id: t.id || toCategorySlug(t.label) || "template" })),
@@ -316,6 +332,7 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
316
332
  showInMenu: cat.display?.showInMenu ?? true,
317
333
  contentBody: cat.contentBody ?? "",
318
334
  highlightsText: listToLines(cat.highlights),
335
+ hasbroName: cat.hasbroName ?? "",
319
336
  taxCodeId: cat.productDefaults?.taxCodeId ?? "",
320
337
  defaultCondition: cat.productDefaults?.defaultCondition ?? "",
321
338
  defaultAuthenticity: cat.productDefaults?.defaultAuthenticity ?? "",
@@ -430,8 +447,8 @@ export function AdminCategoryEditorView({ categoryId, onSaved, onDeleted, embedd
430
447
  {
431
448
  id: "content",
432
449
  label: "Content & Templates",
433
- fields: ["contentBody", "highlightsText"],
434
- render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Category page body" }), _jsx(Textarea, { value: String(v.contentBody ?? ""), onChange: (e) => onChange({ contentBody: e.target.value }), rows: 8, placeholder: "400\u2013900 characters of keyword-rich HTML with links to sibling and parent categories\u2026" }), errors.contentBody ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.contentBody })) : (_jsx(Text, { size: "xs", color: "muted", children: "Rendered on the category page. Distinct from the short Description above, which also feeds the SEO meta and every card. 36 of worldhobbyshop's 46 categories carry one of these and it is their actual ranking mechanism; no Shopify competitor in the crawl has any." }))] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Highlights \u2014 one per line" }), _jsx(Textarea, { value: String(v.highlightsText ?? ""), onChange: (e) => onChange({ highlightsText: e.target.value }), rows: 4, placeholder: "Genuine Takara Tomy stock\nChecked and photographed before listing" }), _jsx(Text, { size: "xs", color: "muted", children: "Short \"why shop this\" bullets, shown above the FAQs. Blank lines are dropped. Up to 12." })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Category FAQs" }), _jsx(Text, { size: "xs", color: "muted", children: "Rendered as an accordion on the category page and emitted as FAQPage structured data. Keep platform-wide answers in the global FAQ collection instead \u2014 two copies of one answer means the one that gets fixed is never the one being read." }), _jsx(Repeater, { rows: faqs, onChange: setFaqs, blank: () => ({ question: "", answer: "" }), addLabel: "Add an FAQ", max: 20, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Question", value: row.question, onChange: (e) => set({ question: e.target.value }), placeholder: "Are these compatible with Burst launchers?" }), _jsx(Textarea, { value: row.answer, onChange: (e) => set({ answer: e.target.value }), rows: 3, placeholder: "Answer\u2026" })] })) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Description templates" }), _jsxs(Text, { size: "xs", color: "muted", children: ["What the seller form offers when a listing is filed here. One per condition band \u2014 worldhobbyshop reuses exactly two across 1,195 listings. Slots like", " ", _jsx(Span, { family: "mono", children: "{{title}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{brand}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{condition}}" }), " and", " ", _jsx(Span, { family: "mono", children: "{{series}}" }), " are filled from the listing; an unresolved one becomes empty and is reported to the seller, never left as a literal brace pair."] }), _jsx(Repeater, { rows: templates, onChange: setTemplates, blank: () => ({ id: "", label: "", variant: "default", body: "" }), addLabel: "Add a template", max: 8, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Label", value: row.label, onChange: (e) => set({ label: e.target.value }), placeholder: "Pre-owned \u2014 condition as pictured" }), _jsx(Select, { label: "Condition band", value: row.variant, onChange: (e) => set({ variant: e.target.value }), options: TEMPLATE_VARIANT_OPTIONS })] }), _jsx(Textarea, { value: row.body, onChange: (e) => set({ body: e.target.value }), rows: 6, placeholder: "Important things to know about {{title}} \u2014 HTML permitted\u2026" }), _jsx(Text, { size: "xs", color: "muted", children: usedPlaceholders(row.body).length > 0
450
+ fields: ["contentBody", "highlightsText", "hasbroName"],
451
+ render: ({ values: v, onChange, errors }) => (_jsxs(Stack, { gap: "md", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Category page body" }), _jsx(Textarea, { value: String(v.contentBody ?? ""), onChange: (e) => onChange({ contentBody: e.target.value }), rows: 8, placeholder: "400\u2013900 characters of keyword-rich HTML with links to sibling and parent categories\u2026" }), errors.contentBody ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: errors.contentBody })) : (_jsx(Text, { size: "xs", color: "muted", children: "Rendered on the category page. Distinct from the short Description above, which also feeds the SEO meta and every card. 36 of worldhobbyshop's 46 categories carry one of these and it is their actual ranking mechanism; no Shopify competitor in the crawl has any." }))] }), _jsx(FieldInput, { name: "hasbroName", label: "Hasbro / international name", value: String(v.hasbroName ?? ""), onChange: (val) => onChange({ hasbroName: val }), placeholder: "e.g. Beyblade Burst Turbo", error: errors.hasbroName, hint: "Only when the Hasbro season name genuinely differs from the Takara-Tomy one \u2014 Cho-Z ships as Turbo, God as Evolution. Rendered on the category page as 'Also known as'. Leave blank if there is no divergence, or if you cannot confirm one: a guessed pairing reads as fact." }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Highlights \u2014 one per line" }), _jsx(Textarea, { value: String(v.highlightsText ?? ""), onChange: (e) => onChange({ highlightsText: e.target.value }), rows: 4, placeholder: "Genuine Takara Tomy stock\nChecked and photographed before listing" }), _jsx(Text, { size: "xs", color: "muted", children: "Short \"why shop this\" bullets, shown above the FAQs. Blank lines are dropped. Up to 12." })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Category FAQs" }), _jsx(Text, { size: "xs", color: "muted", children: "Rendered as an accordion on the category page and emitted as FAQPage structured data. Keep platform-wide answers in the global FAQ collection instead \u2014 two copies of one answer means the one that gets fixed is never the one being read." }), _jsx(Repeater, { rows: faqs, onChange: setFaqs, blank: () => ({ question: "", answer: "" }), addLabel: "Add an FAQ", max: 20, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsx(Input, { label: "Question", value: row.question, onChange: (e) => set({ question: e.target.value }), placeholder: "Are these compatible with Burst launchers?" }), _jsx(Textarea, { value: row.answer, onChange: (e) => set({ answer: e.target.value }), rows: 3, placeholder: "Answer\u2026" })] })) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: "Description templates" }), _jsxs(Text, { size: "xs", color: "muted", children: ["What the seller form offers when a listing is filed here. One per condition band \u2014 worldhobbyshop reuses exactly two across 1,195 listings. Slots like", " ", _jsx(Span, { family: "mono", children: "{{title}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{brand}}" }), ",", " ", _jsx(Span, { family: "mono", children: "{{condition}}" }), " and", " ", _jsx(Span, { family: "mono", children: "{{series}}" }), " are filled from the listing; an unresolved one becomes empty and is reported to the seller, never left as a literal brace pair."] }), _jsx(Repeater, { rows: templates, onChange: setTemplates, blank: () => ({ id: "", label: "", variant: "default", body: "" }), addLabel: "Add a template", max: 8, renderRow: (row, set) => (_jsxs(_Fragment, { children: [_jsxs(Div, { layout: "grid", gap: "4", className: "sm:grid-cols-2", children: [_jsx(Input, { label: "Label", value: row.label, onChange: (e) => set({ label: e.target.value }), placeholder: "Pre-owned \u2014 condition as pictured" }), _jsx(Select, { label: "Condition band", value: row.variant, onChange: (e) => set({ variant: e.target.value }), options: TEMPLATE_VARIANT_OPTIONS })] }), _jsx(Textarea, { value: row.body, onChange: (e) => set({ body: e.target.value }), rows: 6, placeholder: "Important things to know about {{title}} \u2014 HTML permitted\u2026" }), _jsx(Text, { size: "xs", color: "muted", children: usedPlaceholders(row.body).length > 0
435
452
  ? `Slots used: ${usedPlaceholders(row.body).map((p) => `{{${p}}}`).join(", ")}`
436
453
  : "No slots used — this body will read identically on every listing." })] })) })] })] })),
437
454
  },
@@ -12,7 +12,7 @@ const __O = {
12
12
  hidden: "overflow-hidden",
13
13
  };
14
14
  function NavItemRow({ item, idx, total, reorderPending, onMoveUp, onMoveDown, onVisibilityChange, onEdit, onDelete, }) {
15
- return (_jsxs(Row, { surface: "default", padding: "inline", align: "center", gap: "3", children: [_jsxs(Stack, { className: "shrink-0 gap-[0.125rem]", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: () => onMoveUp(idx), disabled: idx === 0 || reorderPending, className: "text-zinc-400 hover:text-zinc-700 hover:text-[var(--appkit-color-text-muted)] disabled:opacity-30 leading-none text-[length:var(--appkit-text-xs)] p-[var(--appkit-space-0)] min-h-0 h-auto rounded-none", "aria-label": "Move up", children: "\u25B2" }), _jsx(Button, { variant: "ghost", type: "button", onClick: () => onMoveDown(idx), disabled: idx >= total - 1 || reorderPending, className: "text-zinc-400 hover:text-zinc-700 hover:text-[var(--appkit-color-text-muted)] disabled:opacity-30 leading-none text-[length:var(--appkit-text-xs)] p-[var(--appkit-space-0)] min-h-0 h-auto rounded-none", "aria-label": "Move down", children: "\u25BC" })] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Text, { className: "truncate", color: "primary", size: "sm", weight: "medium", children: [item.parentId ? _jsx(Span, { className: "mr-1", color: "faint", children: "\u21B3" }) : null, item.label] }), _jsx(Text, { className: "truncate", color: "muted", size: "xs", children: item.href })] }), _jsx(Toggle, { checked: item.isVisible ?? true, onChange: (val) => onVisibilityChange(item.id, val), label: "" }), _jsx(RowActionMenu, { actions: [
15
+ return (_jsxs(Row, { surface: "default", padding: "inline", align: "center", gap: "3", children: [_jsxs(Stack, { className: "shrink-0 gap-[0.125rem]", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: () => onMoveUp(idx), disabled: idx === 0 || reorderPending, className: "text-zinc-400 hover:text-[var(--appkit-color-text)] disabled:opacity-30 leading-none text-[length:var(--appkit-text-xs)] p-[var(--appkit-space-0)] min-h-0 h-auto rounded-none", "aria-label": "Move up", children: "\u25B2" }), _jsx(Button, { variant: "ghost", type: "button", onClick: () => onMoveDown(idx), disabled: idx >= total - 1 || reorderPending, className: "text-zinc-400 hover:text-[var(--appkit-color-text)] disabled:opacity-30 leading-none text-[length:var(--appkit-text-xs)] p-[var(--appkit-space-0)] min-h-0 h-auto rounded-none", "aria-label": "Move down", children: "\u25BC" })] }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsxs(Text, { className: "truncate", color: "primary", size: "sm", weight: "medium", children: [item.parentId ? _jsx(Span, { className: "mr-1", color: "faint", children: "\u21B3" }) : null, item.label] }), _jsx(Text, { className: "truncate", color: "muted", size: "xs", children: item.href })] }), _jsx(Toggle, { checked: item.isVisible ?? true, onChange: (val) => onVisibilityChange(item.id, val), label: "" }), _jsx(RowActionMenu, { actions: [
16
16
  { label: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label, onClick: () => onEdit(item) },
17
17
  { label: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label, destructive: true, onClick: () => onDelete(item) },
18
18
  ] })] }));