@mohasinac/appkit 4.53.1 → 4.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (384) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/theme/ThemeProvider.js +15 -1
  3. package/dist/_internal/server/features/auth/capabilities.js +7 -5
  4. package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
  5. package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
  6. package/dist/_internal/server/features/checkout/actions.d.ts +22 -6
  7. package/dist/_internal/server/features/checkout/actions.js +96 -51
  8. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  9. package/dist/_internal/server/features/checkout/index.js +1 -1
  10. package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
  11. package/dist/_internal/server/features/orders/adapters.js +1 -3
  12. package/dist/_internal/server/features/price-index/market.d.ts +57 -0
  13. package/dist/_internal/server/features/price-index/market.js +120 -0
  14. package/dist/_internal/server/features/products/list-public.d.ts +12 -0
  15. package/dist/_internal/server/features/products/list-public.js +10 -1
  16. package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
  17. package/dist/_internal/server/features/products/listing-tab-counts.js +184 -10
  18. package/dist/_internal/server/features/search/index-build/sources.js +43 -2
  19. package/dist/_internal/server/features/site-settings/adapters.js +1 -1
  20. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  21. package/dist/_internal/server/features/stores/adapters.js +0 -1
  22. package/dist/_internal/server/features/stores/tabs.d.ts +7 -2
  23. package/dist/_internal/server/features/stores/tabs.js +5 -3
  24. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +2 -5
  25. package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
  26. package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
  27. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +0 -2
  28. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +0 -1
  29. package/dist/_internal/server/jobs/core/onScamReportCreate.js +0 -1
  30. package/dist/_internal/server/jobs/core/onScamReportRejected.js +0 -1
  31. package/dist/_internal/server/jobs/core/onScamReportVerified.js +0 -1
  32. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +0 -1
  33. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +0 -1
  34. package/dist/_internal/server/notifications/channel-health.d.ts +7 -3
  35. package/dist/_internal/server/notifications/channel-health.js +2 -2
  36. package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
  37. package/dist/_internal/server/notifications/send-guard.js +2 -4
  38. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  39. package/dist/_internal/shared/actions/action-registry.js +128 -28
  40. package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
  41. package/dist/_internal/shared/checkout/order-math.js +1 -1
  42. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
  43. package/dist/_internal/shared/features/checkout/config.d.ts +0 -2
  44. package/dist/_internal/shared/features/checkout/config.js +0 -2
  45. package/dist/_internal/shared/features/messaging/config.d.ts +7 -5
  46. package/dist/_internal/shared/features/messaging/config.js +1 -4
  47. package/dist/_internal/shared/features/site-settings/fees.d.ts +0 -2
  48. package/dist/_internal/shared/features/site-settings/fees.js +0 -2
  49. package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
  50. package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
  51. package/dist/_internal/shared/fees/calculator.d.ts +0 -8
  52. package/dist/_internal/shared/fees/calculator.js +0 -7
  53. package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
  54. package/dist/_internal/shared/listing-types/tiers.js +150 -0
  55. package/dist/_internal/shared/search/match.js +28 -2
  56. package/dist/cli/index.js +0 -2
  57. package/dist/client.d.ts +25 -4
  58. package/dist/client.js +36 -5
  59. package/dist/constants/api-endpoints.d.ts +28 -15
  60. package/dist/constants/api-endpoints.js +14 -8
  61. package/dist/constants/field-names.d.ts +1 -0
  62. package/dist/constants/field-names.js +1 -0
  63. package/dist/constants/notification-types.d.ts +1 -1
  64. package/dist/constants/notification-types.js +1 -1
  65. package/dist/core/integration-keys.d.ts +5 -5
  66. package/dist/core/integration-keys.js +21 -10
  67. package/dist/features/about/components/FeesView.js +0 -6
  68. package/dist/features/about/components/GuidesHubView.js +15 -2
  69. package/dist/features/account/components/NotificationPreferencesPanel.js +3 -4
  70. package/dist/features/account/components/UserOrdersView.js +26 -7
  71. package/dist/features/account/types/index.d.ts +0 -2
  72. package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
  73. package/dist/features/addresses/utils/postal-autofill.js +54 -0
  74. package/dist/features/admin/actions/notification-actions.d.ts +2 -8
  75. package/dist/features/admin/actions/notification-actions.js +9 -104
  76. package/dist/features/admin/components/AdminGuideHubView.js +1 -8
  77. package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
  78. package/dist/features/admin/components/AdminOrdersView.js +0 -2
  79. package/dist/features/admin/components/AdminSectionsView.js +3 -2
  80. package/dist/features/admin/components/AdminSiteSettingsView.js +22 -68
  81. package/dist/features/admin/components/AdminStoreEditorView.js +72 -34
  82. package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
  83. package/dist/features/admin/components/OwnerQuickActions.js +142 -0
  84. package/dist/features/admin/components/sections/adminSectionsTypes.js +13 -4
  85. package/dist/features/admin/components/sections/icon-options.d.ts +21 -0
  86. package/dist/features/admin/components/sections/icon-options.js +23 -0
  87. package/dist/features/admin/components/sections/sectionTemplates.js +4 -1
  88. package/dist/features/admin/constants/site-settings-tabs.d.ts +7 -12
  89. package/dist/features/admin/constants/site-settings-tabs.js +6 -12
  90. package/dist/features/admin/repository/site-settings.repository.js +1 -1
  91. package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
  92. package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
  93. package/dist/features/admin/schemas/admin-user-form.d.ts +0 -61
  94. package/dist/features/admin/schemas/admin-user-form.js +3 -44
  95. package/dist/features/admin/schemas/firestore.d.ts +28 -54
  96. package/dist/features/admin/schemas/firestore.js +40 -18
  97. package/dist/features/auctions/components/AuctionDetailPageView.js +12 -10
  98. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
  99. package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
  100. package/dist/features/auth/checkout-value-otp.d.ts +1 -1
  101. package/dist/features/auth/checkout-value-otp.js +1 -1
  102. package/dist/features/auth/permissions/constants.d.ts +29 -2
  103. package/dist/features/auth/permissions/constants.js +67 -1
  104. package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
  105. package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
  106. package/dist/features/cart/components/CartDrawer.js +3 -2
  107. package/dist/features/cart/components/CartGroupLineRow.js +2 -1
  108. package/dist/features/cart/components/CartPriceBreakdown.d.ts +0 -2
  109. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  110. package/dist/features/cart/components/StoreAddonsPicker.d.ts +2 -5
  111. package/dist/features/cart/components/StoreAddonsPicker.js +4 -5
  112. package/dist/features/cart/hooks/useAddToCart.js +10 -5
  113. package/dist/features/cart/repository/cart.repository.js +1 -2
  114. package/dist/features/cart/schemas/firestore.d.ts +1 -2
  115. package/dist/features/cart/utils/pending-ops.d.ts +11 -1
  116. package/dist/features/cart/utils/pending-ops.js +14 -2
  117. package/dist/features/categories/api/[id]/price-guidance/route.js +1 -24
  118. package/dist/features/categories/components/BrandDetailPageView.js +15 -6
  119. package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
  120. package/dist/features/categories/components/BrandDetailTabs.js +18 -3
  121. package/dist/features/categories/components/BundleCollage.js +3 -2
  122. package/dist/features/categories/components/BundleDetailView.js +2 -1
  123. package/dist/features/categories/components/BundleItemsPicker.js +3 -4
  124. package/dist/features/categories/components/CategoryDetailPageView.js +19 -8
  125. package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
  126. package/dist/features/categories/components/CategoryDetailTabs.js +29 -7
  127. package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
  128. package/dist/features/categories/components/CategoryGrid.js +9 -2
  129. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
  130. package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
  131. package/dist/features/classified/components/ClassifiedDetailPageView.js +12 -2
  132. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +12 -2
  133. package/dist/features/events/components/EventCard.d.ts +11 -1
  134. package/dist/features/events/components/EventCard.js +9 -2
  135. package/dist/features/grouped/components/GroupedListingDetailView.js +3 -1
  136. package/dist/features/homepage/components/CTABannerSection.d.ts +3 -1
  137. package/dist/features/homepage/components/CTABannerSection.js +6 -3
  138. package/dist/features/homepage/components/HeroCarousel.js +8 -6
  139. package/dist/features/homepage/components/MarketplaceHomepageView.js +33 -7
  140. package/dist/features/homepage/components/SectionCarousel.js +13 -5
  141. package/dist/features/homepage/components/SecurityHighlightsSection.d.ts +8 -3
  142. package/dist/features/homepage/components/SecurityHighlightsSection.js +2 -2
  143. package/dist/features/homepage/components/StatsCounterSection.d.ts +9 -4
  144. package/dist/features/homepage/components/StatsCounterSection.js +2 -2
  145. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  146. package/dist/features/homepage/components/TrustFeaturesSection.d.ts +18 -4
  147. package/dist/features/homepage/components/TrustFeaturesSection.js +5 -5
  148. package/dist/features/homepage/components/WelcomeSection.d.ts +13 -2
  149. package/dist/features/homepage/components/WelcomeSection.js +3 -3
  150. package/dist/features/homepage/components/WhatsAppCommunitySection.d.ts +28 -0
  151. package/dist/features/homepage/components/WhatsAppCommunitySection.js +42 -6
  152. package/dist/features/homepage/components/WhatsAppGlyph.d.ts +20 -0
  153. package/dist/features/homepage/components/WhatsAppGlyph.js +20 -0
  154. package/dist/features/homepage/constants/hero-carousel-styles.d.ts +8 -6
  155. package/dist/features/homepage/constants/hero-carousel-styles.js +8 -6
  156. package/dist/features/homepage/constants/section-copy.d.ts +12 -10
  157. package/dist/features/homepage/constants/section-copy.js +28 -6
  158. package/dist/features/homepage/constants/stat-icons.d.ts +12 -0
  159. package/dist/features/homepage/constants/stat-icons.js +17 -0
  160. package/dist/features/homepage/constants/trust-icon-aliases.d.ts +8 -0
  161. package/dist/features/homepage/constants/trust-icon-aliases.js +75 -0
  162. package/dist/features/homepage/lib/section-defaults.d.ts +16 -1
  163. package/dist/features/homepage/lib/section-defaults.js +24 -4
  164. package/dist/features/homepage/lib/section-renderer.js +48 -8
  165. package/dist/features/homepage/schemas/firestore.d.ts +18 -1
  166. package/dist/features/layout/AppLayoutShell.js +2 -1
  167. package/dist/features/layout/BottomActions.d.ts +1 -0
  168. package/dist/features/layout/BottomActions.js +330 -0
  169. package/dist/features/layout/BottomChrome.d.ts +45 -0
  170. package/dist/features/layout/BottomChrome.js +5 -17
  171. package/dist/features/layout/SideActionDock.js +36 -13
  172. package/dist/features/layout/index.d.ts +2 -7
  173. package/dist/features/layout/index.js +7 -7
  174. package/dist/features/layout/useIsDesktop.d.ts +36 -0
  175. package/dist/features/layout/useIsDesktop.js +48 -0
  176. package/dist/features/live/components/LiveItemDetailPageView.js +12 -2
  177. package/dist/features/orders/actions/refund-actions.js +0 -1
  178. package/dist/features/orders/components/OrderAddonBadges.d.ts +1 -3
  179. package/dist/features/orders/components/OrderAddonBadges.js +3 -4
  180. package/dist/features/orders/components/OrderPaymentSummary.js +15 -2
  181. package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
  182. package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
  183. package/dist/features/orders/components/index.d.ts +2 -1
  184. package/dist/features/orders/components/index.js +1 -1
  185. package/dist/features/orders/repository/orders.repository.d.ts +0 -8
  186. package/dist/features/orders/repository/orders.repository.js +0 -2
  187. package/dist/features/orders/schemas/firestore.d.ts +17 -9
  188. package/dist/features/orders/types/index.d.ts +2 -5
  189. package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
  190. package/dist/features/orders/utils/order-announcement-message.js +16 -0
  191. package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
  192. package/dist/features/orders/utils/payment-proof-message.js +17 -0
  193. package/dist/features/parts/components/PartDetailPageView.js +96 -1
  194. package/dist/features/parts/schemas/firestore.d.ts +247 -0
  195. package/dist/features/parts/schemas/firestore.js +6 -6
  196. package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
  197. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
  198. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +12 -10
  199. package/dist/features/price-index/components/PriceIndexToolView.d.ts +21 -0
  200. package/dist/features/price-index/components/PriceIndexToolView.js +230 -27
  201. package/dist/features/price-index/lookup-target.d.ts +48 -0
  202. package/dist/features/price-index/lookup-target.js +107 -0
  203. package/dist/features/products/api/[id]/route.js +8 -1
  204. package/dist/features/products/api/route.js +16 -4
  205. package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
  206. package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
  207. package/dist/features/products/components/CompareOverlay.d.ts +2 -1
  208. package/dist/features/products/components/CompareOverlay.js +2 -1
  209. package/dist/features/products/components/GroupSettingsPanel.js +2 -1
  210. package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
  211. package/dist/features/products/components/InteractiveProductCard.js +16 -4
  212. package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
  213. package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
  214. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
  215. package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
  216. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
  217. package/dist/features/products/components/ProductDetailPageView.js +34 -4
  218. package/dist/features/products/components/ProductGrid.d.ts +14 -1
  219. package/dist/features/products/components/ProductGrid.js +22 -5
  220. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  221. package/dist/features/products/components/SublistingCarouselSection.js +2 -1
  222. package/dist/features/products/repository/products.repository.d.ts +1 -0
  223. package/dist/features/products/repository/products.repository.js +5 -1
  224. package/dist/features/products/utils/group-member-image.d.ts +9 -2
  225. package/dist/features/products/utils/group-member-image.js +2 -1
  226. package/dist/features/reviews/api/[id]/route.js +83 -23
  227. package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
  228. package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
  229. package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
  230. package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
  231. package/dist/features/reviews/components/index.d.ts +4 -0
  232. package/dist/features/reviews/components/index.js +2 -0
  233. package/dist/features/scams/schemas/index.d.ts +10 -10
  234. package/dist/features/search/action-index/settings-entries.js +0 -9
  235. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  236. package/dist/features/seller/actions/seller-actions.js +49 -3
  237. package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
  238. package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
  239. package/dist/features/seller/components/SellerProductsView.js +3 -2
  240. package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
  241. package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
  242. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +1 -6
  243. package/dist/features/store-extensions/repository/store-extensions.repositories.js +1 -10
  244. package/dist/features/store-extensions/schemas/firestore.d.ts +1 -17
  245. package/dist/features/store-extensions/schemas/firestore.js +0 -10
  246. package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
  247. package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
  248. package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
  249. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +0 -1
  250. package/dist/features/stores/components/StoreGuideHubView.js +2 -8
  251. package/dist/features/stores/components/StoreSettingsGuideView.js +2 -6
  252. package/dist/features/stores/components/index.d.ts +0 -2
  253. package/dist/features/stores/components/index.js +0 -1
  254. package/dist/features/stores/repository/store.repository.d.ts +1 -26
  255. package/dist/features/stores/repository/store.repository.js +5 -60
  256. package/dist/features/stores/schemas/firestore.d.ts +6 -25
  257. package/dist/features/stores/schemas/firestore.js +6 -3
  258. package/dist/features/stores/schemas/index.d.ts +1 -87
  259. package/dist/features/stores/schemas/index.js +10 -15
  260. package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -48
  261. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +0 -40
  262. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +114 -2
  263. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +0 -16
  264. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +1 -1
  265. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +0 -79
  266. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +1 -1
  267. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +427 -69
  268. package/dist/index.d.ts +20 -32
  269. package/dist/index.js +34 -43
  270. package/dist/next/routing/route-map.d.ts +2 -8
  271. package/dist/next/routing/route-map.js +21 -4
  272. package/dist/providers/db-firebase/base.repository.d.ts +1 -1
  273. package/dist/providers/db-firebase/base.repository.js +1 -1
  274. package/dist/providers/payment-razorpay/index.d.ts +125 -0
  275. package/dist/providers/payment-razorpay/index.js +232 -0
  276. package/dist/providers/payment-razorpay/signature.d.ts +53 -0
  277. package/dist/providers/payment-razorpay/signature.js +71 -0
  278. package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
  279. package/dist/providers/shipping-shiprocket/index.js +356 -0
  280. package/dist/providers.d.ts +3 -0
  281. package/dist/providers.js +3 -0
  282. package/dist/react/hooks/useCanSeePrices.d.ts +0 -27
  283. package/dist/react/hooks/useCanSeePrices.js +25 -0
  284. package/dist/repositories/index.d.ts +1 -1
  285. package/dist/repositories/index.js +1 -1
  286. package/dist/schemas/registry.d.ts +10 -115
  287. package/dist/schemas/registry.js +1 -2
  288. package/dist/security/pii-encrypt.js +1 -1
  289. package/dist/seed/_helpers/category-forest.js +27 -0
  290. package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
  291. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  292. package/dist/seed/_helpers/category-parts.js +13412 -3020
  293. package/dist/seed/carousel-slides-seed-data.js +47 -10
  294. package/dist/seed/categories-seed-data.js +48 -6
  295. package/dist/seed/faq-seed-data.js +2 -2
  296. package/dist/seed/homepage-sections-seed-data.js +286 -54
  297. package/dist/seed/index.d.ts +1 -1
  298. package/dist/seed/index.js +1 -1
  299. package/dist/seed/manifest.d.ts +1 -1
  300. package/dist/seed/manifest.js +1 -2
  301. package/dist/seed/model-composition-seed-data.js +6 -8
  302. package/dist/seed/part-glossary/chunk0.d.ts +9 -0
  303. package/dist/seed/part-glossary/chunk0.js +35448 -0
  304. package/dist/seed/part-glossary/chunk1.d.ts +9 -0
  305. package/dist/seed/part-glossary/chunk1.js +40494 -0
  306. package/dist/seed/part-glossary/chunk2.d.ts +9 -0
  307. package/dist/seed/part-glossary/chunk2.js +51242 -0
  308. package/dist/seed/part-glossary/chunk3.d.ts +9 -0
  309. package/dist/seed/part-glossary/chunk3.js +53337 -0
  310. package/dist/seed/part-glossary/chunk4.d.ts +9 -0
  311. package/dist/seed/part-glossary/chunk4.js +51454 -0
  312. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  313. package/dist/seed/part-glossary-seed-data.js +23 -47483
  314. package/dist/seed/products-beyblade-x-seed-data.js +36 -1
  315. package/dist/seed/site-settings-seed-data.js +34 -35
  316. package/dist/seed/store-extensions-seed-data.d.ts +1 -2
  317. package/dist/seed/store-extensions-seed-data.js +1 -23
  318. package/dist/seed/stores-seed-data.js +0 -1
  319. package/dist/server-entry.d.ts +1 -1
  320. package/dist/server-entry.js +1 -1
  321. package/dist/server.d.ts +10 -10
  322. package/dist/server.js +24 -22
  323. package/dist/styles.css +392 -43
  324. package/dist/tailwind-utilities.css +1 -1
  325. package/dist/tokens/themes/default-dark.js +1 -1
  326. package/dist/tokens/themes/default-light.js +2 -1
  327. package/dist/tokens/tokens.css +6 -2
  328. package/dist/ui/components/Button.d.ts +11 -0
  329. package/dist/ui/components/Button.js +11 -0
  330. package/dist/ui/components/Button.style.css +29 -0
  331. package/dist/ui/components/CarouselNav.d.ts +57 -0
  332. package/dist/ui/components/CarouselNav.js +25 -0
  333. package/dist/ui/components/CarouselNav.style.css +265 -0
  334. package/dist/ui/components/GatedPrice.js +30 -3
  335. package/dist/ui/components/GatedPrice.style.css +43 -40
  336. package/dist/ui/components/HorizontalScroller.d.ts +12 -1
  337. package/dist/ui/components/HorizontalScroller.js +43 -3
  338. package/dist/ui/components/ImageMosaic.d.ts +8 -1
  339. package/dist/ui/components/ImageMosaic.js +3 -2
  340. package/dist/ui/components/ImageMosaic.style.css +22 -0
  341. package/dist/ui/components/Layout.style.css +6 -0
  342. package/dist/ui/components/Semantic.d.ts +3 -2
  343. package/dist/ui/components/Semantic.js +1 -0
  344. package/dist/ui/components/Typography.d.ts +2 -0
  345. package/dist/ui/components/Typography.js +2 -0
  346. package/dist/ui/components/Typography.style.css +19 -0
  347. package/dist/ui/components/index.style.css +1 -0
  348. package/dist/ui/index.d.ts +3 -1
  349. package/dist/ui/index.js +4 -0
  350. package/package.json +3 -2
  351. package/scripts/seed-cli.mjs +133 -27
  352. package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
  353. package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
  354. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
  355. package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
  356. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
  357. package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
  358. package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
  359. package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
  360. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
  361. package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
  362. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
  363. package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
  364. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
  365. package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
  366. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
  367. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
  368. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
  369. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
  370. package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
  371. package/dist/features/whatsapp-bot/components/index.js +0 -2
  372. package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
  373. package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
  374. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
  375. package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
  376. package/dist/features/whatsapp-bot/index.d.ts +0 -3
  377. package/dist/features/whatsapp-bot/index.js +0 -3
  378. package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
  379. package/dist/features/whatsapp-bot/manifest.js +0 -13
  380. package/dist/features/whatsapp-bot/messages/en.json +0 -7
  381. package/dist/features/whatsapp-bot/server.d.ts +0 -7
  382. package/dist/features/whatsapp-bot/server.js +0 -7
  383. package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
  384. package/dist/features/whatsapp-bot/types/index.js +0 -1
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { Aside, Button, Div, Heading, Row, Span, Stack, Text, TextLink } from "../../../ui/index.js";
4
4
  import { MediaImage } from "../../media/MediaImage.js";
5
5
  import { QuantityStepper } from "../../../ui/components/QuantityStepper.js";
6
+ import { GatedPrice } from "../../../ui/components/GatedPrice.js";
6
7
  import { formatCurrency } from "../../../utils/number.formatter.js";
7
8
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
8
9
  import { useHandMode } from "../../../_internal/client/hand-mode/index.js";
@@ -19,11 +20,11 @@ export function CartItemRow({ item, onQtyChange, onRemove, href, isOutOfStock =
19
20
  return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: `min-w-0 transition-opacity ${isOutOfStock ? "opacity-60" : ""}`, children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: item.meta.image && (_jsx(MediaImage, { src: item.meta.image, alt: item.meta.title, size: "thumbnail" })) }), _jsxs(Stack, { justify: "between", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", className: "min-w-0", align: "start", children: [href ? (_jsx(TextLink, { href: href, target: "_blank", rel: "noopener noreferrer", weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: item.meta.title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: item.meta.title })), isOutOfStock && (_jsx(Span, { padding: "pill-2xs", color: "error", surface: "danger-surface", weight: "semibold", className: "flex-shrink-0 text-[10px] tracking-wide", rounded: "default", transform: "uppercase", children: "Out of Stock" }))] }), item.meta.attributes &&
20
21
  Object.keys(item.meta.attributes).length > 0 && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "xs", truncate: 1, children: Object.entries(item.meta.attributes)
21
22
  .map(([k, v]) => `${k}: ${v}`)
22
- .join(", ") })), _jsxs(Row, { justify: "between", className: "min-w-0", gap: "sm", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }), onQtyChange && !isOutOfStock && (_jsx(QuantityStepper, { value: item.quantity, onChange: (next) => onQtyChange(item.id, next), min: 1, ariaLabel: `Quantity for ${item.meta.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
23
+ .join(", ") })), _jsxs(Row, { justify: "between", className: "min-w-0", gap: "sm", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)]", weight: "semibold", children: _jsx(GatedPrice, { children: formatCurrency(item.meta.price * item.quantity, item.meta.currency) }) }), onQtyChange && !isOutOfStock && (_jsx(QuantityStepper, { value: item.quantity, onChange: (next) => onQtyChange(item.id, next), min: 1, ariaLabel: `Quantity for ${item.meta.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel })), isOutOfStock && (_jsxs(Span, { size: "xs", color: "muted", children: ["Qty: ", item.quantity] }))] })] }), onRemove && (_jsx(Button, { onClick: () => onRemove(item.id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: CLS_REMOVE_BTN, children: "\u2715" }))] }));
23
24
  }
24
25
  export function CartDrawer({ isOpen, onClose, items, subtotal = 0, currency, isLoading, onQtyChange, onRemove, onCheckout, labels = {}, }) {
25
26
  const { hand } = useHandMode();
26
27
  if (!isOpen)
27
28
  return null;
28
- return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: formatCurrency(subtotal, currency) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
29
+ return (_jsxs(_Fragment, { children: [_jsx(Div, { surface: "overlay-xs", role: "presentation", className: "fixed inset-0 z-40", onClick: onClose }), _jsx(Aside, { surface: "sidePanel", shadow: "xl", className: `fixed inset-y-0 ${hand === "left" ? "left-0" : "right-0"} z-50 w-full max-w-sm`, children: _jsxs(Stack, { className: "h-full", children: [_jsxs(Row, { justify: "between", border: "bottom", className: __P.p4, reverse: hand === "left", children: [_jsx(Heading, { level: 2, size: "lg", weight: "semibold", children: labels.title ?? "Cart" }), _jsx(Button, { onClick: onClose, variant: "ghost", size: "sm", "aria-label": "Close cart", textColor: "faint", className: "hover:text-[var(--appkit-color-text)] dark:hover:text-[var(--appkit-color-text)]", children: "\u2715" })] }), _jsx(Stack, { className: `flex-1 ${__O.yAuto} ${__P.p4}`, gap: "3", children: isLoading ? (_jsx(Row, { justify: "center", padding: "y-3xl", children: _jsx(Div, { className: "h-8 w-8 animate-spin border-2 border-neutral-300 border-t-neutral-800 dark:border-t-zinc-200", rounded: "full" }) })) : items.length === 0 ? (_jsx(Text, { paddingY: "3xl", className: "text-[var(--appkit-color-text-muted)]", size: "sm", align: "center", children: labels.empty ?? "Your cart is empty" })) : (items.map((item) => (_jsx(CartItemRow, { item: item, onQtyChange: onQtyChange, onRemove: onRemove }, item.id)))) }), items.length > 0 && (_jsxs(Stack, { border: "top", className: __P.p4, gap: "md", children: [_jsxs(Row, { textSize: "sm", justify: "between", children: [_jsx(Span, { className: "text-[var(--appkit-color-text-muted)]", children: labels.subtotal ?? "Subtotal" }), _jsx(Span, { weight: "semibold", children: _jsx(GatedPrice, { children: formatCurrency(subtotal, currency) }) })] }), onCheckout && (_jsx(Button, { rounded: "xl", onClick: onCheckout, variant: "primary", textSize: "sm", className: "w-full bg-neutral-900 py-[0.75rem] font-[600] text-white transition hover:bg-neutral-800", children: labels.checkout ?? ACTIONS.CART["checkout"].label }))] }))] }) })] }));
29
30
  }
@@ -20,6 +20,7 @@ import { useState } from "react";
20
20
  import { Button, Div, Row, Span, Stack, Text, TextLink } from "../../../ui/index.js";
21
21
  import { QuantityStepper } from "../../../ui/components/QuantityStepper.js";
22
22
  import { MediaImage } from "../../media/MediaImage.js";
23
+ import { GatedPrice, PricesOnly } from "../../../ui/components/GatedPrice.js";
23
24
  import { formatCurrency } from "../../../utils/number.formatter.js";
24
25
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
25
26
  const __O = { hidden: "overflow-hidden" };
@@ -29,5 +30,5 @@ export function CartGroupLineRow({ id, title, image, currency = "INR", lineKind,
29
30
  const onePrice = members.reduce((sum, m) => sum + m.unitPrice * m.quantity, 0);
30
31
  const lineTotal = onePrice * quantity;
31
32
  const unitCount = members.reduce((sum, m) => sum + m.quantity, 0);
32
- return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: "min-w-0", children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: image && _jsx(MediaImage, { src: image, alt: title, size: "thumbnail" }) }), _jsxs(Stack, { justify: "between", gap: "xs", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", align: "start", className: "min-w-0", children: [href ? (_jsx(TextLink, { href: href, weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: title })), _jsx(Span, { padding: "pill-2xs", surface: "subtle", color: "muted", rounded: "default", className: "flex-shrink-0 text-[10px] tracking-wide", transform: "uppercase", children: lineKind === "bundle" ? "Bundle" : "Group" })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsOpen((v) => !v), "aria-expanded": isOpen, className: "self-start text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-primary)] hover:underline", children: [isOpen ? "▼" : "▶", " ", members.length, " item", members.length === 1 ? "" : "s", unitCount !== members.length ? ` · ${unitCount} units` : ""] }), isOpen && (_jsx(Stack, { gap: "xs", className: "w-full", children: members.map((m) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "min-w-0", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0 flex-1", children: [_jsx(Div, { className: `h-10 w-10 flex-shrink-0 ${__O.hidden}`, rounded: "default", surface: "muted", children: m.image && _jsx(MediaImage, { src: m.image, alt: m.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", truncate: 1, color: "primary", children: m.title }), _jsxs(Text, { size: "xs", color: "muted", children: [formatCurrency(m.unitPrice, currency), lineKind === "bundle" ? ` × ${m.quantity}` : ""] })] })] }), lineKind === "group" && onMemberQtyChange && !locked && (_jsxs(Row, { gap: "xs", align: "center", className: "flex-shrink-0", children: [_jsx(QuantityStepper, { value: m.quantity, onChange: (next) => onMemberQtyChange(id, m.productId, next), min: 0, max: maxByProductId?.[m.productId], ariaLabel: `Quantity for ${m.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-group-member"].ariaLabel, onClick: () => onMemberQtyChange(id, m.productId, 0), className: "text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" })] })), lineKind === "bundle" && (_jsxs(Span, { size: "xs", color: "muted", className: "flex-shrink-0", children: ["\u00D7 ", m.quantity] }))] }, m.productId))) })), _jsxs(Row, { justify: "between", gap: "sm", className: "min-w-0", children: [_jsx(Text, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: formatCurrency(lineTotal, currency) }), lineKind === "bundle" && onQtyChange && !locked && (_jsx(QuantityStepper, { value: quantity, onChange: (next) => onQtyChange(id, next), min: 1, ariaLabel: `Number of copies of ${title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }))] })] }), onRemove && !locked && (_jsx(Button, { onClick: () => onRemove(id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" }))] }));
33
+ return (_jsxs(Div, { layout: "flex", gap: isRow ? "3" : "4", surface: isRow ? "none" : "card", padding: isRow ? "none" : "sm", className: "min-w-0", children: [_jsx(Div, { surface: "muted", className: `relative ${isRow ? "h-16 w-16" : "h-20 w-20"} flex-shrink-0 ${__O.hidden}`, rounded: "lg", children: image && _jsx(MediaImage, { src: image, alt: title, size: "thumbnail" }) }), _jsxs(Stack, { justify: "between", gap: "xs", className: "flex-1 min-w-0", children: [_jsxs(Row, { gap: "xs", align: "start", className: "min-w-0", children: [href ? (_jsx(TextLink, { href: href, weight: "medium", className: "text-[var(--appkit-color-text)] hover:underline underline-offset-2 line-clamp-2", children: title })) : (_jsx(Text, { truncate: 2, weight: "medium", color: "primary", children: title })), _jsx(Span, { padding: "pill-2xs", surface: "subtle", color: "muted", rounded: "default", className: "flex-shrink-0 text-[10px] tracking-wide", transform: "uppercase", children: lineKind === "bundle" ? "Bundle" : "Group" })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setIsOpen((v) => !v), "aria-expanded": isOpen, className: "self-start text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-primary)] hover:underline", children: [isOpen ? "▼" : "▶", " ", members.length, " item", members.length === 1 ? "" : "s", unitCount !== members.length ? ` · ${unitCount} units` : ""] }), isOpen && (_jsx(Stack, { gap: "xs", className: "w-full", children: members.map((m) => (_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "min-w-0", children: [_jsxs(Row, { gap: "sm", align: "center", className: "min-w-0 flex-1", children: [_jsx(Div, { className: `h-10 w-10 flex-shrink-0 ${__O.hidden}`, rounded: "default", surface: "muted", children: m.image && _jsx(MediaImage, { src: m.image, alt: m.title, size: "thumbnail" }) }), _jsxs(Stack, { gap: "none", className: "min-w-0", children: [_jsx(Text, { size: "sm", truncate: 1, color: "primary", children: m.title }), _jsxs(Text, { size: "xs", color: "muted", children: [_jsx(PricesOnly, { children: formatCurrency(m.unitPrice, currency) }), lineKind === "bundle" ? ` × ${m.quantity}` : ""] })] })] }), lineKind === "group" && onMemberQtyChange && !locked && (_jsxs(Row, { gap: "xs", align: "center", className: "flex-shrink-0", children: [_jsx(QuantityStepper, { value: m.quantity, onChange: (next) => onMemberQtyChange(id, m.productId, next), min: 0, max: maxByProductId?.[m.productId], ariaLabel: `Quantity for ${m.title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-group-member"].ariaLabel, onClick: () => onMemberQtyChange(id, m.productId, 0), className: "text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" })] })), lineKind === "bundle" && (_jsxs(Span, { size: "xs", color: "muted", className: "flex-shrink-0", children: ["\u00D7 ", m.quantity] }))] }, m.productId))) })), _jsxs(Row, { justify: "between", gap: "sm", className: "min-w-0", children: [_jsx(Text, { weight: "semibold", className: "text-[var(--appkit-color-text)]", children: _jsx(GatedPrice, { children: formatCurrency(lineTotal, currency) }) }), lineKind === "bundle" && onQtyChange && !locked && (_jsx(QuantityStepper, { value: quantity, onChange: (next) => onQtyChange(id, next), min: 1, ariaLabel: `Number of copies of ${title}`, decrementLabel: ACTIONS.CART["decrease-quantity"].ariaLabel, incrementLabel: ACTIONS.CART["increase-quantity"].ariaLabel }))] })] }), onRemove && !locked && (_jsx(Button, { onClick: () => onRemove(id), variant: "ghost", size: "sm", "aria-label": ACTIONS.CART["remove-item"].ariaLabel, className: "self-start text-[var(--appkit-color-text-faint)] transition hover:text-error", children: "\u2715" }))] }));
33
34
  }
@@ -21,7 +21,6 @@ export interface CartPriceBreakdownStore {
21
21
  subtotal: number;
22
22
  shippingFee: number;
23
23
  codHandlingFee: number;
24
- whatsappNotifyFee: number;
25
24
  giftWrapFee: number;
26
25
  shipmentProtectionFee: number;
27
26
  saleProtectionFee: number;
@@ -38,7 +37,6 @@ export interface CartPriceBreakdownData {
38
37
  subtotal: number;
39
38
  shippingFee: number;
40
39
  codHandlingFee: number;
41
- whatsappNotifyFee: number;
42
40
  giftWrapFee: number;
43
41
  shipmentProtectionFee: number;
44
42
  saleProtectionFee: number;
@@ -21,5 +21,5 @@ export function CartPriceBreakdown({ preview, itemCount, currency = "INR", unava
21
21
  const subtotalLabel = itemCount !== undefined
22
22
  ? `Subtotal (${itemCount} item${itemCount !== 1 ? "s" : ""}${storeCount > 1 ? `, ${storeCount} stores` : ""})`
23
23
  : "Subtotal";
24
- return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: subtotalLabel, amount: money(preview.subtotal) }), preview.shippingFee > 0 && (_jsx(Line, { label: withStoreCount("Shipping", storeCount), amount: money(preview.shippingFee) })), preview.codHandlingFee > 0 && (_jsx(Line, { label: "COD handling fee", amount: money(preview.codHandlingFee) })), preview.whatsappNotifyFee > 0 && (_jsx(Line, { label: withStoreCount("WhatsApp updates", countCharged((s) => s.whatsappNotifyFee)), amount: money(preview.whatsappNotifyFee) })), preview.giftWrapFee > 0 && (_jsx(Line, { label: withStoreCount("Gift wrap", countCharged((s) => s.giftWrapFee)), amount: money(preview.giftWrapFee) })), preview.shipmentProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Shipment protection", countCharged((s) => s.shipmentProtectionFee)), amount: money(preview.shipmentProtectionFee) })), preview.saleProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Sale protection", countCharged((s) => s.saleProtectionFee)), amount: money(preview.saleProtectionFee) })), preview.platformFee > 0 && _jsx(Line, { label: "Platform fee", amount: money(preview.platformFee) }), (preview.gstAmount > 0 || preview.gstOnFee > 0) && (_jsx(Line, { label: "GST", amount: money(preview.gstAmount + preview.gstOnFee) })), preview.couponDiscount > 0 && (_jsx(Line, { label: "Coupon discount", amount: `−${money(preview.couponDiscount)}`, tone: "success" })), _jsx(Div, { border: "top-subtle", paddingY: "t-sm", children: _jsx(Line, { label: "Total", amount: money(preview.total), tone: "primary", strong: true }) }), errorNote ? (_jsx(Text, { size: "xs", color: "error", children: errorNote })) : (isLoading && (_jsx(Text, { size: "xs", color: "muted", children: "Updating\u2026" })))] }));
24
+ return (_jsxs(Stack, { gap: "xs", className: className, children: [_jsx(Line, { label: subtotalLabel, amount: money(preview.subtotal) }), preview.shippingFee > 0 && (_jsx(Line, { label: withStoreCount("Shipping", storeCount), amount: money(preview.shippingFee) })), preview.codHandlingFee > 0 && (_jsx(Line, { label: "COD handling fee", amount: money(preview.codHandlingFee) })), preview.giftWrapFee > 0 && (_jsx(Line, { label: withStoreCount("Gift wrap", countCharged((s) => s.giftWrapFee)), amount: money(preview.giftWrapFee) })), preview.shipmentProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Shipment protection", countCharged((s) => s.shipmentProtectionFee)), amount: money(preview.shipmentProtectionFee) })), preview.saleProtectionFee > 0 && (_jsx(Line, { label: withStoreCount("Sale protection", countCharged((s) => s.saleProtectionFee)), amount: money(preview.saleProtectionFee) })), preview.platformFee > 0 && _jsx(Line, { label: "Platform fee", amount: money(preview.platformFee) }), (preview.gstAmount > 0 || preview.gstOnFee > 0) && (_jsx(Line, { label: "GST", amount: money(preview.gstAmount + preview.gstOnFee) })), preview.couponDiscount > 0 && (_jsx(Line, { label: "Coupon discount", amount: `−${money(preview.couponDiscount)}`, tone: "success" })), _jsx(Div, { border: "top-subtle", paddingY: "t-sm", children: _jsx(Line, { label: "Total", amount: money(preview.total), tone: "primary", strong: true }) }), errorNote ? (_jsx(Text, { size: "xs", color: "error", children: errorNote })) : (isLoading && (_jsx(Text, { size: "xs", color: "muted", children: "Updating\u2026" })))] }));
25
25
  }
@@ -3,13 +3,12 @@ import React from "react";
3
3
  * Paid add-on checkboxes for ONE store.
4
4
  *
5
5
  * Add-on fees are billed per order group (= per store), so they are chosen per
6
- * store: ticking "WhatsApp updates" for one seller must not bill it for every
6
+ * store: ticking "gift wrap" for one seller must not bill it for every
7
7
  * seller in the cart. Rendered inside that store's card in the cart, and inside
8
8
  * that store's row of the checkout summary — Buy Now skips the cart entirely,
9
9
  * so both surfaces have to be able to edit them.
10
10
  */
11
11
  export interface StoreAddonsValue {
12
- whatsappNotifyAddon?: boolean;
13
12
  giftWrapAddon?: boolean;
14
13
  giftWrapMessage?: string;
15
14
  shipmentProtectionAddon?: boolean;
@@ -17,8 +16,6 @@ export interface StoreAddonsValue {
17
16
  }
18
17
  /** The subset of `siteSettings.commissions` that gates and prices the add-ons. */
19
18
  export interface StoreAddonsRates {
20
- whatsappNotifyFeeEnabled?: boolean;
21
- whatsappNotifyFee?: number;
22
19
  giftWrapFeeEnabled?: boolean;
23
20
  giftWrapFee?: number;
24
21
  shipmentProtectionFeeEnabled?: boolean;
@@ -59,7 +56,7 @@ export interface StoreProtectionContext {
59
56
  * showing an explanatory line instead.
60
57
  *
61
58
  * 🛑 Deliberately asks only about TIER 1. A seller who supports neither
62
- * protection still has WhatsApp updates and gift wrap, and a store whose only
59
+ * protection still has gift wrap, and a store whose only
63
60
  * enabled add-ons are protections it declined must still render the picker —
64
61
  * because that is where the risk notice lives, and a hidden notice is the
65
62
  * tri-state failure in § `StoreProtectionContext`.
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Gift, MessageCircle, ShieldAlert, ShieldCheck, ShieldOff } from "lucide-react";
3
+ import { Gift, ShieldAlert, ShieldCheck, ShieldOff } from "lucide-react";
4
4
  import { Div, Row, Stack, Text } from "../../../ui/index.js";
5
5
  import { IconBox } from "../../../ui/components/IconBox.js";
6
6
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox.js";
@@ -15,15 +15,14 @@ import { formatCurrency } from "../../../utils/number.formatter.js";
15
15
  * showing an explanatory line instead.
16
16
  *
17
17
  * 🛑 Deliberately asks only about TIER 1. A seller who supports neither
18
- * protection still has WhatsApp updates and gift wrap, and a store whose only
18
+ * protection still has gift wrap, and a store whose only
19
19
  * enabled add-ons are protections it declined must still render the picker —
20
20
  * because that is where the risk notice lives, and a hidden notice is the
21
21
  * tri-state failure in § `StoreProtectionContext`.
22
22
  */
23
23
  export function hasAnyStoreAddon(rates) {
24
24
  return (!!rates &&
25
- !!(rates.whatsappNotifyFeeEnabled ||
26
- rates.giftWrapFeeEnabled ||
25
+ !!(rates.giftWrapFeeEnabled ||
27
26
  rates.shipmentProtectionFeeEnabled ||
28
27
  rates.saleProtectionFeeEnabled));
29
28
  }
@@ -51,7 +50,7 @@ export function StoreAddonsPicker({ storeId, storeSubtotal, value, onChange, rat
51
50
  const shippingDeclined = !!rates.shipmentProtectionFeeEnabled && !protection?.supportsShippingProtection;
52
51
  const saleDeclined = !!rates.saleProtectionFeeEnabled && !protection?.supportsSaleProtection;
53
52
  const saleBlockedByFinalSale = !!rates.saleProtectionFeeEnabled && !!protection?.supportsSaleProtection && !!protection?.hasFinalSaleItem;
54
- return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [disabled && disabledReason && (_jsx(Text, { size: "xs", color: "muted", children: disabledReason })), rates.whatsappNotifyFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(MessageCircle, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `whatsappNotifyAddon-${storeId}`, label: `WhatsApp order updates (+${money(rates.whatsappNotifyFee ?? 10)})`, checked: !!value.whatsappNotifyAddon, onChange: (checked) => set({ whatsappNotifyAddon: checked }), disabled: disabled }) })] })), rates.giftWrapFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(Gift, { size: 14 }) }), _jsxs(Stack, { gap: "xs", className: "flex-1 min-w-0", children: [_jsx(FieldCheckbox, { name: `giftWrapAddon-${storeId}`, label: `Gift wrap (+${money(rates.giftWrapFee ?? 49)})`, checked: !!value.giftWrapAddon, onChange: (checked) => set({ giftWrapAddon: checked }), disabled: disabled }), showGiftMessage && value.giftWrapAddon && (_jsx(FieldTextarea, { name: `giftWrapMessage-${storeId}`, label: "Gift message (optional)", value: value.giftWrapMessage ?? "", onChange: (next) => set({ giftWrapMessage: next.slice(0, 500) }), rows: 2, maxLength: 500, showCharCount: true, placeholder: "Add a note for the recipient\u2026", disabled: disabled }))] })] })), shippingOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldCheck, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `shipmentProtectionAddon-${storeId}`, label: `Shipment protection — loss or damage in transit (+${money(protectionFee)})`, checked: !!value.shipmentProtectionAddon, onChange: (checked) => set({ shipmentProtectionAddon: checked }), disabled: disabled }) })] })), saleOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldAlert, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `saleProtectionAddon-${storeId}`, label: `Sale protection — item not as described (+${money(saleFee)})`, checked: !!value.saleProtectionAddon, onChange: (checked) => set({ saleProtectionAddon: checked }), disabled: disabled }) })] })), (shippingDeclined || saleDeclined) && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "muted", children: _jsx(ShieldOff, { size: 14 }) }), _jsx(Text, { size: "xs", color: "muted", className: "flex-1 min-w-0", children: shippingDeclined && saleDeclined
53
+ return (_jsxs(Stack, { gap: "xs", className: `min-w-0 ${className}`, children: [disabled && disabledReason && (_jsx(Text, { size: "xs", color: "muted", children: disabledReason })), rates.giftWrapFeeEnabled && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(Gift, { size: 14 }) }), _jsxs(Stack, { gap: "xs", className: "flex-1 min-w-0", children: [_jsx(FieldCheckbox, { name: `giftWrapAddon-${storeId}`, label: `Gift wrap (+${money(rates.giftWrapFee ?? 49)})`, checked: !!value.giftWrapAddon, onChange: (checked) => set({ giftWrapAddon: checked }), disabled: disabled }), showGiftMessage && value.giftWrapAddon && (_jsx(FieldTextarea, { name: `giftWrapMessage-${storeId}`, label: "Gift message (optional)", value: value.giftWrapMessage ?? "", onChange: (next) => set({ giftWrapMessage: next.slice(0, 500) }), rows: 2, maxLength: 500, showCharCount: true, placeholder: "Add a note for the recipient\u2026", disabled: disabled }))] })] })), shippingOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldCheck, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `shipmentProtectionAddon-${storeId}`, label: `Shipment protection — loss or damage in transit (+${money(protectionFee)})`, checked: !!value.shipmentProtectionAddon, onChange: (checked) => set({ shipmentProtectionAddon: checked }), disabled: disabled }) })] })), saleOffered && storeSubtotal > 0 && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "brand", children: _jsx(ShieldAlert, { size: 14 }) }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(FieldCheckbox, { name: `saleProtectionAddon-${storeId}`, label: `Sale protection — item not as described (+${money(saleFee)})`, checked: !!value.saleProtectionAddon, onChange: (checked) => set({ saleProtectionAddon: checked }), disabled: disabled }) })] })), (shippingDeclined || saleDeclined) && (_jsxs(Row, { gap: "sm", align: "start", className: "min-w-0", children: [_jsx(IconBox, { size: "sm", tone: "muted", children: _jsx(ShieldOff, { size: 14 }) }), _jsx(Text, { size: "xs", color: "muted", className: "flex-1 min-w-0", children: shippingDeclined && saleDeclined
55
54
  ? "This seller does not offer shipment or sale protection."
56
55
  : shippingDeclined
57
56
  ? "This seller does not offer shipment protection."
@@ -8,9 +8,10 @@ import { CART_ENDPOINTS } from "../../../constants/api-endpoints.js";
8
8
  import { dispatchCartUpdated, formatCartAddedMessage } from "../utils/pending-ops.js";
9
9
  import { useToast } from "../../../ui/components/Toast.js";
10
10
  import { formatCurrency } from "../../../utils/number.formatter.js";
11
+ import { useCanSeePrices } from "../../../react/hooks/useCanSeePrices.js";
11
12
  import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
12
- function showAddedToast(showToast, detail) {
13
- showToast(formatCartAddedMessage(detail, (amount) => formatCurrency(amount, getDefaultCurrency())), "success");
13
+ function showAddedToast(showToast, detail, canSeePrices) {
14
+ showToast(formatCartAddedMessage(detail, (amount) => formatCurrency(amount, getDefaultCurrency()), canSeePrices), "success");
14
15
  }
15
16
  /**
16
17
  * useAddToCart
@@ -21,6 +22,10 @@ export function useAddToCart(options) {
21
22
  const queryClient = useQueryClient();
22
23
  const endpoint = options?.endpoint ?? CART_ENDPOINTS.GET;
23
24
  const { showToast } = useToast();
25
+ // The guest fallback below fires precisely when the viewer is NOT signed in,
26
+ // so a toast that quoted the cart total was the one place the price gate was
27
+ // guaranteed to be speaking to a guest.
28
+ const { canSeePrices } = useCanSeePrices();
24
29
  const optionsRef = useRef(options);
25
30
  useEffect(() => {
26
31
  optionsRef.current = options;
@@ -31,7 +36,7 @@ export function useAddToCart(options) {
31
36
  queryClient.invalidateQueries({ queryKey: ["cart"] });
32
37
  const itemCount = data?.itemCount ?? 0;
33
38
  const totalValue = data?.subtotal ?? 0;
34
- showAddedToast(showToast, { productTitle: variables.productTitle, itemCount, totalValue });
39
+ showAddedToast(showToast, { productTitle: variables.productTitle, itemCount, totalValue }, canSeePrices);
35
40
  dispatchCartUpdated({ itemCount, totalValue, productTitle: variables.productTitle });
36
41
  optionsRef.current?.onSuccess?.();
37
42
  },
@@ -55,14 +60,14 @@ export function useAddToCart(options) {
55
60
  const guestItems = getGuestCartItems();
56
61
  const itemCount = guestItems.reduce((sum, it) => sum + it.quantity, 0);
57
62
  const totalValue = guestItems.reduce((sum, it) => sum + (it.price ?? 0) * it.quantity, 0);
58
- showAddedToast(showToast, { productTitle: data.productTitle, itemCount, totalValue });
63
+ showAddedToast(showToast, { productTitle: data.productTitle, itemCount, totalValue }, canSeePrices);
59
64
  dispatchCartUpdated({ itemCount, totalValue, productTitle: data.productTitle });
60
65
  optionsRef.current?.onSuccess?.();
61
66
  return undefined;
62
67
  }
63
68
  throw err;
64
69
  }
65
- }, [serverMutation, showToast]);
70
+ }, [serverMutation, showToast, canSeePrices]);
66
71
  return {
67
72
  mutate,
68
73
  isLoading: serverMutation.isPending,
@@ -577,8 +577,7 @@ export class CartRepository extends BaseRepository {
577
577
  */
578
578
  async setStoreAddons(userId, storeId, addons) {
579
579
  const isEmpty = !addons ||
580
- (!addons.whatsappNotifyAddon &&
581
- !addons.giftWrapAddon &&
580
+ (!addons.giftWrapAddon &&
582
581
  !addons.shipmentProtectionAddon &&
583
582
  !addons.saleProtectionAddon);
584
583
  await this.db
@@ -202,12 +202,11 @@ export interface CartAppliedCoupon {
202
202
  *
203
203
  * Add-on fees have always been billed per order group (= per store) by
204
204
  * `createOrderForGroup` / `previewCheckoutPricing`, but the selection used to be
205
- * a single cart-wide boolean — so one tick on "WhatsApp updates" quietly billed
205
+ * a single cart-wide boolean — so one tick on "gift wrap" quietly billed
206
206
  * ₹10 × every store in the cart, with no way to opt in for just one seller.
207
207
  * Keying the selection by storeId makes the choice match the billing.
208
208
  */
209
209
  export interface CartStoreAddons {
210
- whatsappNotifyAddon?: boolean;
211
210
  giftWrapAddon?: boolean;
212
211
  giftWrapMessage?: string;
213
212
  /** Loss or damage IN TRANSIT. */
@@ -30,7 +30,17 @@ export declare function dispatchCartUpdated(detail: CartUpdatedEventDetail): voi
30
30
  * instead of a bare "Added to cart", so callers only need to format
31
31
  * currency, not compose the sentence twice.
32
32
  */
33
- export declare function formatCartAddedMessage(detail: CartUpdatedEventDetail, formatCurrency: (amount: number) => string): string;
33
+ export declare function formatCartAddedMessage(detail: CartUpdatedEventDetail, formatCurrency: (amount: number) => string,
34
+ /**
35
+ * 🛑 A toast is a STRING, so it cannot be wrapped in `<GatedPrice>` — this is
36
+ * the third form of the guest-price gate, read as a value at the call site
37
+ * (CLAUDE.md § "Guest price gating"). Default `true` so no existing caller
38
+ * silently loses the total; the signed-out callers pass `false`.
39
+ *
40
+ * The COUNT is deliberately still shown. It is not money, and a guest who
41
+ * just added something is owed confirmation that it landed.
42
+ */
43
+ includeTotal?: boolean): string;
34
44
  export type CartOpKind = "add" | "remove";
35
45
  export type WishlistOpKind = "add" | "remove";
36
46
  export interface CartOp {
@@ -35,9 +35,21 @@ export function dispatchCartUpdated(detail) {
35
35
  * instead of a bare "Added to cart", so callers only need to format
36
36
  * currency, not compose the sentence twice.
37
37
  */
38
- export function formatCartAddedMessage(detail, formatCurrency) {
38
+ export function formatCartAddedMessage(detail, formatCurrency,
39
+ /**
40
+ * 🛑 A toast is a STRING, so it cannot be wrapped in `<GatedPrice>` — this is
41
+ * the third form of the guest-price gate, read as a value at the call site
42
+ * (CLAUDE.md § "Guest price gating"). Default `true` so no existing caller
43
+ * silently loses the total; the signed-out callers pass `false`.
44
+ *
45
+ * The COUNT is deliberately still shown. It is not money, and a guest who
46
+ * just added something is owed confirmation that it landed.
47
+ */
48
+ includeTotal = true) {
39
49
  const itemLabel = detail.productTitle ? `"${detail.productTitle}"` : "Item";
40
- return `${itemLabel} added to cart — ${detail.itemCount} item${detail.itemCount !== 1 ? "s" : ""}, ${formatCurrency(detail.totalValue)} total`;
50
+ const count = `${detail.itemCount} item${detail.itemCount !== 1 ? "s" : ""}`;
51
+ const total = includeTotal ? `, ${formatCurrency(detail.totalValue)} total` : "";
52
+ return `${itemLabel} added to cart — ${count}${total}`;
41
53
  }
42
54
  function dispatchCartOpsChange() {
43
55
  if (typeof window === "undefined")
@@ -43,30 +43,7 @@ import { getProviders } from "../../../../../contracts/index.js";
43
43
  import { categoriesRepository } from "../../../repository/categories.repository.js";
44
44
  import { isAdminUser } from "../../../../auth/role-predicates.js";
45
45
  import { priceGuidanceOnSellerBasis, describePriceGuidanceBasis, } from "../../../../../_internal/shared/features/categories/price-guidance.js";
46
- const MAX_ANCESTORS = 8;
47
- /**
48
- * Nearest-first ancestor walk for `priceGuidance`, same shape as
49
- * `description-template/route.ts`'s `resolveInherited` — the leaf's own band
50
- * wins, and only a leaf with none falls back to the nearest ancestor that has
51
- * one. ONE bounded `findByIds`, not a sequential walk: a seller form hitting
52
- * this on every category change should not pay up to eight round trips for it.
53
- */
54
- async function resolveInheritedGuidance(leaf) {
55
- if (leaf.productDefaults?.priceGuidance)
56
- return leaf.productDefaults.priceGuidance;
57
- const parentIds = (leaf.parentIds ?? []).slice(0, MAX_ANCESTORS);
58
- if (parentIds.length === 0)
59
- return undefined;
60
- const nearestFirst = [...parentIds].reverse();
61
- const ancestors = await categoriesRepository.findByIds(nearestFirst);
62
- const byId = new Map(ancestors.map((a) => [a.id, a]));
63
- for (const pid of nearestFirst) {
64
- const guidance = byId.get(pid)?.productDefaults?.priceGuidance;
65
- if (guidance)
66
- return guidance;
67
- }
68
- return undefined;
69
- }
46
+ import { resolveInheritedGuidance, } from "../../../../../_internal/server/features/categories/price-guidance-resolve.js";
70
47
  export const GET = createRouteHandler({
71
48
  roles: ["seller", "admin", "moderator"],
72
49
  handler: async ({ user, params }) => {
@@ -5,6 +5,7 @@ import Link from "next/link.js";
5
5
  import { categoriesRepository, productRepository, } from "../../../repositories/index.js";
6
6
  import { ROUTES } from "../../../next/routing/route-map.js";
7
7
  import { Anchor, Container, Div, Dl, Dt, Dd, Heading, Main, Nav, Section, Span, Stack, Text } from "../../../ui/index.js";
8
+ import { OwnerQuickActions } from "../../admin/components/OwnerQuickActions.js";
8
9
  import { DynamicBgDiv } from "../../../ui/components/DynamicBgDiv.js";
9
10
  import { MediaImage } from "../../media/MediaImage.js";
10
11
  import { RichTextRenderer } from "../../../ui/rich-text/RichTextRenderer.js";
@@ -60,7 +61,7 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
60
61
  // therefore never hide, however empty the brand was.
61
62
  brandName
62
63
  ? listingTabCounts(CATEGORY_PAGE_TABS, { brandName })
63
- : Promise.resolve({}),
64
+ : Promise.resolve({ available: {}, total: {} }),
64
65
  // SB-UNI-D — bundles are categoryType:"bundle" rows on the categories
65
66
  // collection, tagged to a brand via the real brandSlug field (added
66
67
  // 2026-08-21 — previously a fragile seo.keywords string-match heuristic).
@@ -108,15 +109,23 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
108
109
  // Keyed by tabSlug. `bundles` is overridden with the brand-scoped figure —
109
110
  // `listingTabCounts` cannot narrow bundles by brand (a bundle's brand lives on
110
111
  // `brandSlug`, which this page already filters on locally).
112
+ // Badge = available scope; hiding + the header pill = total. See the same
113
+ // split in `CategoryDetailPageView` and `ListingTabCountResult`.
111
114
  const counts = {
112
- ...tabCounts,
113
- products: tabCounts.products ?? brand?.metrics?.productCount ?? 0,
114
- auctions: tabCounts.auctions ?? brand?.metrics?.auctionCount ?? 0,
115
+ ...tabCounts.available,
116
+ products: tabCounts.available.products ?? brand?.metrics?.productCount ?? 0,
117
+ auctions: tabCounts.available.auctions ?? brand?.metrics?.auctionCount ?? 0,
118
+ bundles: brandBundles.length,
119
+ };
120
+ const totals = {
121
+ ...tabCounts.total,
122
+ products: tabCounts.total.products ?? brand?.metrics?.productCount ?? 0,
123
+ auctions: tabCounts.total.auctions ?? brand?.metrics?.auctionCount ?? 0,
115
124
  bundles: brandBundles.length,
116
125
  };
117
126
  // Header total — `?? 0` is right here (a pill just doesn't render), unlike the
118
127
  // tab bar where an unknown count must keep its tab visible.
119
- const totalItems = Object.values(counts).reduce((sum, n) => sum + (n ?? 0), 0);
128
+ const totalItems = Object.values(totals).reduce((sum, n) => sum + (n ?? 0), 0);
120
129
  /*
121
130
  * ── D4: structured data on the brand page ───────────────────────────────
122
131
  *
@@ -168,5 +177,5 @@ export async function BrandDetailPageView({ slug, initialBrand }) {
168
177
  const ldFaq = brand?.faqs && brand.faqs.length > 0
169
178
  ? faqJsonLd(brand.faqs.map((f) => ({ question: f.question, answer: f.answer })))
170
179
  : null;
171
- return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [_jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
180
+ return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && (_jsx(DynamicBgDiv, { color: brandColor, className: "absolute inset-0 opacity-10" })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsxs(Nav, { layout: "flex", gap: "2xs", textSize: "sm", className: "mb-4", "aria-label": "Breadcrumb", children: [_jsx(Link, { href: String(ROUTES.HOME), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Home" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.BRANDS), className: hasCover ? "text-white/70 hover:text-white transition-colors" : "text-[var(--appkit-color-text-muted)] hover:text-primary-600 dark:hover:text-primary-400 transition-colors", children: "Brands" }), _jsx(Span, { className: hasCover ? "text-white/40" : "text-zinc-400", children: "/" }), _jsx(Span, { weight: "medium", className: hasCover ? "text-white" : "text-[var(--appkit-color-text)]", children: brand?.name ?? slug })] }), _jsxs(Row, { className: "mb-3", align: "center", gap: "md", children: [brand?.display?.icon && (_jsx(Span, { className: "leading-none", size: "5xl", children: brand.display.icon })), _jsxs(_Fragment, { children: [brand?.id && (_jsx(OwnerQuickActions, { resource: "BRAND", placement: "detail", className: "mb-2", recordLabel: brand.name ?? slug, editHref: String(ROUTES.ADMIN.BRANDS_EDIT(brand.id)) })), _jsx(Heading, { color: "inverse", level: 1, className: `${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: brand?.name ?? slug }), brand?.description && typeof brand.description === "string" && !brand.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mt-1 ${hasCover ? "/80" : ""}`, size: "base", children: brand.description }))] })] }), _jsxs(Row, { gap: "sm", wrap: true, className: "mt-3", children: [(counts.products ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: `${hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400"}`, rounded: "full", padding: "pill-sm-tall", children: [counts.products.toLocaleString(), " ", counts.products === 1 ? "product" : "products"] })), (counts.auctions ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts.auctions.toLocaleString(), " ", counts.auctions === 1 ? "auction" : "auctions"] })), (counts["pre-orders"] ?? 0) > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [counts["pre-orders"].toLocaleString(), " ", counts["pre-orders"] === 1 ? "pre-order" : "pre-orders"] })), totalItems === 0 && (_jsx(Span, { size: "sm", className: hasCover ? "text-white/60" : "text-zinc-400", children: "No items listed yet" }))] })] })] }), (brand?.brandWebsite || brand?.brandCountry || brand?.brandFounded) && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "sm", children: [_jsx(Heading, { level: 2, size: "sm", weight: "semibold", color: "muted", transform: "uppercase", children: "About this brand" }), _jsxs(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden max-w-lg", children: [brand?.brandWebsite && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Website" }), _jsx(Dd, { className: "flex-1", children: _jsx(Anchor, { href: brand.brandWebsite, tone: "brand", underline: "hover", children: brand.brandWebsite }) })] })), brand?.brandCountry && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Country" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandCountry })] })), brand?.brandFounded && (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-32 flex-shrink-0", color: "primary", weight: "medium", children: "Founded" }), _jsx(Dd, { className: "flex-1", color: "muted", children: brand.brandFounded })] }))] })] }) }) })), brandContentBody && brandContentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: brandContentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: brand?.highlights, faqs: brand?.faqs }), _jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: brandName ? (_jsx(BrandDetailTabs, { brandName: brandName, initialProductsData: productsResult ?? undefined, initialBundles: brandBundles, counts: counts, totals: totals, enabledListingTypes: enabledListingTypes(settings), enabledCategoryTypes: enabledCategoryTypes(settings) })) : (_jsx(Text, { paddingY: "3xl", color: "muted", size: "sm", align: "start", children: "Brand not found." })) }) }), groupedListings.length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(GroupedListingsCarousel, { groups: groupedListings }) }) })), relatedBrands.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-t", padding: "y-lg", children: _jsxs(Container, { size: "xl", children: [_jsx(Heading, { level: 2, className: "mb-4", size: "xl", weight: "semibold", children: "Related Brands" }), _jsx(CategoryGrid, { categories: relatedBrands, getHref: (b) => String(ROUTES.PUBLIC.BRAND_DETAIL(b.slug)) })] }) }))] }));
172
181
  }
@@ -10,10 +10,16 @@ export interface BrandDetailTabsProps {
10
10
  * inventory (Root Cause #59). Keyed by tab id; the previous hand-named shape
11
11
  * covered only 5 of 9 tabs (Root Cause #61).
12
12
  */
13
+ /** Badge counts — what the default (available) scope will render. */
13
14
  counts?: Record<string, number | undefined>;
15
+ /**
16
+ * Every published row per tab, all availability scopes. Drives hide-at-zero
17
+ * and the "N ended — show all" hint; see `ListingTabCountResult`.
18
+ */
19
+ totals?: Record<string, number | undefined>;
14
20
  /** Enabled listing types (e.g. ["standard","auction"]). When omitted, all tabs shown. */
15
21
  enabledListingTypes?: string[];
16
22
  /** Enabled category types (e.g. ["category","brand","bundle"]). When omitted, all tabs shown. */
17
23
  enabledCategoryTypes?: string[];
18
24
  }
19
- export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): React.JSX.Element;
25
+ export declare function BrandDetailTabs({ brandName, initialProductsData, initialBundles, counts, totals, enabledListingTypes, enabledCategoryTypes, }: BrandDetailTabsProps): React.JSX.Element;
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
4
  import { EmptyState, Tabs, TabsList, TabsTrigger } from "../../../ui/index.js";
5
+ import { AvailabilityScopeHint } from "../../products/components/AvailabilityScopeHint.js";
5
6
  import { CategoryProductsListing } from "./CategoryProductsListing.js";
6
7
  import { AuctionsIndexListing } from "../../products/components/AuctionsIndexListing.js";
7
8
  import { PreOrdersIndexListing } from "../../pre-orders/components/PreOrdersIndexListing.js";
@@ -28,6 +29,18 @@ const TAB_TYPE_MAP = {
28
29
  ])),
29
30
  bundles: { kind: "category", type: "bundle" },
30
31
  };
32
+ /**
33
+ * The listing types a tab spans, for the availability-scope hint. Derived
34
+ * from the same maps the tabs render from, never hand-written (Root Cause
35
+ * #61).
36
+ */
37
+ function typesForTab(id) {
38
+ const generic = GENERIC_TAB_LISTING_TYPES[id];
39
+ if (generic)
40
+ return generic;
41
+ const mapping = TAB_TYPE_MAP[id];
42
+ return mapping?.kind === "listing" ? [mapping.type] : [];
43
+ }
31
44
  /** Listing types rendered by the shared products listing rather than a dedicated one. */
32
45
  const GENERIC_TAB_LISTING_TYPES = {
33
46
  [pluginFor("standard").tabSlug]: ["standard"],
@@ -46,8 +59,9 @@ const GENERIC_TAB_LISTING_TYPES = {
46
59
  */
47
60
  [pluginFor("spares").tabSlug]: ["spares"],
48
61
  };
49
- export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, enabledListingTypes, enabledCategoryTypes, }) {
62
+ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles = [], counts, totals, enabledListingTypes, enabledCategoryTypes, }) {
50
63
  const countFor = (id) => counts?.[id];
64
+ const totalFor = (id) => totals?.[id] ?? counts?.[id];
51
65
  const visibleTabs = CATEGORY_PAGE_TABS.filter((t) => {
52
66
  const mapping = TAB_TYPE_MAP[t.id];
53
67
  // A tab with no mapping has no render branch below and would show a blank
@@ -67,7 +81,8 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
67
81
  // Hide a tab only when its count is known and explicitly zero. `undefined`
68
82
  // now means the count query FAILED (every tab is counted), so the tab stays
69
83
  // visible rather than hiding real listings behind a swallowed error.
70
- const count = countFor(t.id);
84
+ // TOTAL, not the badge — see the same note in CategoryDetailTabs.
85
+ const count = totalFor(t.id);
71
86
  return count === undefined || count > 0;
72
87
  });
73
88
  const firstTabId = (visibleTabs[0]?.id ?? "products");
@@ -75,5 +90,5 @@ export function BrandDetailTabs({ brandName, initialProductsData, initialBundles
75
90
  if (visibleTabs.length === 0) {
76
91
  return (_jsx(EmptyState, { title: "Nothing listed here yet", description: "This brand has no active listings right now. Check back soon." }));
77
92
  }
78
- return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
93
+ return (_jsxs(_Fragment, { children: [_jsx(Tabs, { value: activeTab, onChange: (v) => setActiveTab(v), className: "mb-6", children: _jsx(TabsList, { children: visibleTabs.map((t) => (_jsx(TabsTrigger, { value: t.id, badge: countFor(t.id), children: t.label }, t.id))) }) }), _jsx(AvailabilityScopeHint, { total: totalFor(activeTab), available: countFor(activeTab), types: typesForTab(activeTab) }), GENERIC_TAB_LISTING_TYPES[activeTab] && (_jsx(CategoryProductsListing, { categorySlug: "", brandName: brandName, listingTypes: GENERIC_TAB_LISTING_TYPES[activeTab], initialData: activeTab === pluginFor("standard").tabSlug ? initialProductsData : undefined })), activeTab === pluginFor("auction").tabSlug && (_jsx(AuctionsIndexListing, { brandName: brandName })), activeTab === pluginFor("pre-order").tabSlug && (_jsx(PreOrdersIndexListing, { brandName: brandName })), activeTab === pluginFor("prize-draw").tabSlug && (_jsx(PrizeDrawsIndexListing, { brandName: brandName })), activeTab === "bundles" && (_jsx(CategoryBundlesListing, { initialBundles: initialBundles, brandName: brandName }))] }));
79
94
  }
@@ -16,10 +16,11 @@ import { MediaImage } from "../../media/MediaImage.js";
16
16
  import { ImageLightbox } from "../../../ui/components/ImageLightbox.js";
17
17
  import { ROUTES } from "../../../next/routing/route-map.js";
18
18
  import { formatCurrency } from "../../../utils/number.formatter.js";
19
+ import { productImageUrls } from "../../products/schemas/firestore.js";
19
20
  const PLACEHOLDER_EMOJI = "📦";
20
21
  function toGalleryImages(members, canSeePrices) {
21
22
  return members.map((p, i) => ({
22
- src: p.mainImage ?? p.images?.[0] ?? "",
23
+ src: p.mainImage || productImageUrls(p.images)[0] || "",
23
24
  alt: p.title,
24
25
  badge: `#${i + 1}`,
25
26
  caption: p.title,
@@ -46,7 +47,7 @@ export function BundleCollage({ members, onItemClick }) {
46
47
  return null;
47
48
  const galleryImages = toGalleryImages(members, canSeePrices);
48
49
  return (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: "categoryCards", gap: "3", children: members.map((p, idx) => {
49
- const cover = p.mainImage ?? p.images?.[0];
50
+ const cover = p.mainImage || productImageUrls(p.images)[0];
50
51
  const href = String(ROUTES.PUBLIC.PRODUCT_DETAIL?.(p.slug ?? p.id) ?? "#");
51
52
  return (_jsxs(Div, { layout: "flex-col", className: "group relative overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] transition-transform hover:scale-[1.01]", rounded: "lg", children: [_jsx(Button, { variant: "ghost", type: "button", onClick: makeBundleItemClickHandler(p, idx, onItemClick, setLightboxIndex), paddingX: "none", paddingY: "none", rounded: "none", className: "relative aspect-square w-full overflow-hidden focus-visible:ring-2 focus-visible:ring-[var(--appkit-color-primary)]", "aria-label": `View ${p.title} in lightbox`, children: _jsxs(Div, { className: "relative aspect-square w-full", children: [cover ? (_jsx(MediaImage, { src: cover, alt: p.title, size: "card", className: "transition-transform group-hover:scale-105" })) : (_jsx(Row, { textSize: "3xl", surface: "muted", className: "absolute inset-0", align: "center", justify: "center", children: PLACEHOLDER_EMOJI })), _jsxs(Div, { textWeight: "semibold", textSize: "xs", surface: "overlay-xl", padding: "chip-2xs", className: "absolute left-2 top-2 text-white", rounded: "default", children: ["#", idx + 1] })] }) }), _jsxs(Div, { padding: "xs", children: [_jsx(Link, { href: href, className: "line-clamp-2 text-[length:var(--appkit-text-sm)] font-medium hover:underline", children: p.title }), _jsx(Text, { className: "mt-0.5 text-[var(--appkit-color-text-muted)]", size: "xs", children: _jsx(GatedPrice, { children: formatCurrency(p.price ?? 0, p.currency ?? "INR") }) })] })] }, p.id));
52
53
  }) }), _jsx(ImageLightbox, { images: galleryImages, activeIndex: lightboxIndex, onClose: () => setLightboxIndex(null), onNavigate: (i) => setLightboxIndex(i), showThumbnails: true })] }));
@@ -9,6 +9,7 @@ import { MediaImage } from "../../media/MediaImage.js";
9
9
  import { ROUTES } from "../../../next/routing/route-map.js";
10
10
  import { computeBundleDiscount } from "../../../_internal/shared/features/categories/bundle-pricing.js";
11
11
  import { CategoryGrid } from "./CategoryGrid.js";
12
+ import { OwnerQuickActions } from "../../../features/admin/components/OwnerQuickActions.js";
12
13
  const __O = {
13
14
  hidden: "overflow-hidden",
14
15
  };
@@ -25,7 +26,7 @@ export function BundleDetailView({ bundle, members = [], onBuyNow, onAddToCart,
25
26
  ? formatCurrency(bundle.bundlePrice, "INR")
26
27
  : BUNDLE_COPY.detail.priceFallback;
27
28
  const discount = computeBundleDiscount(bundle.bundlePrice, bundle.bundleOriginalTotal);
28
- return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
29
+ return (_jsxs(Main, { children: [_jsx(Section, { padding: "y-2xl", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Stack, { gap: "lg", direction: "md-row", align: "start", children: [_jsx(Div, { className: "w-full md:w-2/5", children: members.length > 0 ? (_jsx(BundleCollage, { members: members })) : (_jsx(Div, { className: `aspect-video w-full ${__O.hidden}`, rounded: "2xl", surface: "subtle", children: _jsx(Div, { className: "h-full w-full", children: _jsx(MediaImage, { src: cover, alt: bundle.name, size: "hero", fallback: PLACEHOLDER_EMOJI }) }) })) }), _jsxs(Stack, { gap: "md", className: "flex-1", children: [_jsx(OwnerQuickActions, { resource: "BUNDLE", placement: "detail", recordLabel: String(bundle.name), editHref: String(ROUTES.ADMIN.BUNDLES_EDIT(String(bundle.id))) }), _jsx(Heading, { level: 1, size: "3xl", weight: "semibold", color: "primary", children: bundle.name }), _jsxs(Row, { gap: "sm", align: "center", wrap: true, children: [_jsx(Text, { size: "lg", weight: "bold", children: _jsx(GatedPrice, { label: "Sign in to see the bundle price", children: priceLabel }) }), discount && (
29
30
  // Both the struck-through total and the "N% off" badge are
30
31
  // derived from the bundle price, so they disclose it even
31
32
  // though the badge carries no currency symbol.
@@ -18,6 +18,7 @@ import { Badge, Button, Checkbox, Div, Input, Row, Stack, Text } from "../../../
18
18
  import { BUNDLE_MAX_ITEMS, BUNDLE_MIN_ITEMS, } from "../../../_internal/shared/features/categories/bundle-config.js";
19
19
  import { BUNDLE_COPY } from "../../../_internal/shared/features/categories/bundle-copy.js";
20
20
  import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints.js";
21
+ import { productImageUrls } from "../../products/schemas/firestore.js";
21
22
  const __P = {
22
23
  p3: "p-[var(--appkit-space-3)]",
23
24
  };
@@ -137,11 +138,9 @@ export async function defaultBundleItemsFetch(query) {
137
138
  return {
138
139
  id,
139
140
  title,
140
- image: typeof rec.mainImage === "string"
141
+ image: typeof rec.mainImage === "string" && rec.mainImage
141
142
  ? rec.mainImage
142
- : Array.isArray(rec.images) && typeof rec.images[0] === "string"
143
- ? rec.images[0]
144
- : undefined,
143
+ : productImageUrls(rec.images)[0],
145
144
  price: typeof rec.price === "number" ? rec.price : undefined,
146
145
  storeId: typeof rec.storeId === "string" ? rec.storeId : undefined,
147
146
  };