@mohasinac/appkit 4.53.2 → 4.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/auth/capabilities.js +7 -5
  3. package/dist/_internal/server/features/checkout/actions.d.ts +22 -6
  4. package/dist/_internal/server/features/checkout/actions.js +96 -51
  5. package/dist/_internal/server/features/checkout/index.d.ts +1 -1
  6. package/dist/_internal/server/features/checkout/index.js +1 -1
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
  8. package/dist/_internal/server/features/orders/adapters.js +1 -3
  9. package/dist/_internal/server/features/products/listing-tab-counts.js +81 -25
  10. package/dist/_internal/server/features/search/index-build/sources.js +43 -2
  11. package/dist/_internal/server/features/site-settings/adapters.js +1 -1
  12. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  13. package/dist/_internal/server/features/stores/adapters.js +0 -1
  14. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +2 -5
  15. package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
  16. package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
  17. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +0 -2
  18. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +0 -1
  19. package/dist/_internal/server/jobs/core/onScamReportCreate.js +0 -1
  20. package/dist/_internal/server/jobs/core/onScamReportRejected.js +0 -1
  21. package/dist/_internal/server/jobs/core/onScamReportVerified.js +0 -1
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +0 -1
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +0 -1
  24. package/dist/_internal/server/notifications/channel-health.d.ts +7 -3
  25. package/dist/_internal/server/notifications/channel-health.js +2 -2
  26. package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
  27. package/dist/_internal/server/notifications/send-guard.js +2 -4
  28. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  29. package/dist/_internal/shared/actions/action-registry.js +128 -28
  30. package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
  31. package/dist/_internal/shared/checkout/order-math.js +1 -1
  32. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
  33. package/dist/_internal/shared/features/checkout/config.d.ts +0 -2
  34. package/dist/_internal/shared/features/checkout/config.js +0 -2
  35. package/dist/_internal/shared/features/messaging/config.d.ts +7 -5
  36. package/dist/_internal/shared/features/messaging/config.js +1 -4
  37. package/dist/_internal/shared/features/site-settings/fees.d.ts +0 -2
  38. package/dist/_internal/shared/features/site-settings/fees.js +0 -2
  39. package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
  40. package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
  41. package/dist/_internal/shared/fees/calculator.d.ts +0 -8
  42. package/dist/_internal/shared/fees/calculator.js +0 -7
  43. package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
  44. package/dist/_internal/shared/listing-types/tiers.js +150 -0
  45. package/dist/cli/index.js +0 -2
  46. package/dist/client.d.ts +25 -4
  47. package/dist/client.js +36 -5
  48. package/dist/constants/api-endpoints.d.ts +21 -15
  49. package/dist/constants/api-endpoints.js +7 -8
  50. package/dist/constants/field-names.d.ts +1 -0
  51. package/dist/constants/field-names.js +1 -0
  52. package/dist/constants/notification-types.d.ts +1 -1
  53. package/dist/constants/notification-types.js +1 -1
  54. package/dist/core/integration-keys.d.ts +5 -5
  55. package/dist/core/integration-keys.js +21 -10
  56. package/dist/features/about/components/FeesView.js +0 -6
  57. package/dist/features/about/components/GuidesHubView.js +15 -2
  58. package/dist/features/account/components/NotificationPreferencesPanel.js +3 -4
  59. package/dist/features/account/components/UserOrdersView.js +26 -7
  60. package/dist/features/account/types/index.d.ts +0 -2
  61. package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
  62. package/dist/features/addresses/utils/postal-autofill.js +54 -0
  63. package/dist/features/admin/actions/notification-actions.d.ts +2 -8
  64. package/dist/features/admin/actions/notification-actions.js +9 -104
  65. package/dist/features/admin/components/AdminGuideHubView.js +1 -8
  66. package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
  67. package/dist/features/admin/components/AdminOrdersView.js +0 -2
  68. package/dist/features/admin/components/AdminSectionsView.js +3 -2
  69. package/dist/features/admin/components/AdminSiteSettingsView.js +22 -68
  70. package/dist/features/admin/components/AdminStoreEditorView.js +72 -34
  71. package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
  72. package/dist/features/admin/components/OwnerQuickActions.js +142 -0
  73. package/dist/features/admin/components/sections/adminSectionsTypes.js +13 -4
  74. package/dist/features/admin/components/sections/icon-options.d.ts +21 -0
  75. package/dist/features/admin/components/sections/icon-options.js +23 -0
  76. package/dist/features/admin/components/sections/sectionTemplates.js +4 -1
  77. package/dist/features/admin/constants/site-settings-tabs.d.ts +7 -12
  78. package/dist/features/admin/constants/site-settings-tabs.js +6 -12
  79. package/dist/features/admin/repository/site-settings.repository.js +1 -1
  80. package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
  81. package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
  82. package/dist/features/admin/schemas/admin-user-form.d.ts +0 -61
  83. package/dist/features/admin/schemas/admin-user-form.js +3 -44
  84. package/dist/features/admin/schemas/firestore.d.ts +28 -54
  85. package/dist/features/admin/schemas/firestore.js +40 -18
  86. package/dist/features/auctions/components/AuctionDetailPageView.js +4 -3
  87. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
  88. package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
  89. package/dist/features/auth/checkout-value-otp.d.ts +1 -1
  90. package/dist/features/auth/checkout-value-otp.js +1 -1
  91. package/dist/features/auth/permissions/constants.d.ts +29 -2
  92. package/dist/features/auth/permissions/constants.js +67 -1
  93. package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
  94. package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
  95. package/dist/features/cart/components/CartDrawer.js +3 -2
  96. package/dist/features/cart/components/CartGroupLineRow.js +2 -1
  97. package/dist/features/cart/components/CartPriceBreakdown.d.ts +0 -2
  98. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  99. package/dist/features/cart/components/StoreAddonsPicker.d.ts +2 -5
  100. package/dist/features/cart/components/StoreAddonsPicker.js +4 -5
  101. package/dist/features/cart/hooks/useAddToCart.js +10 -5
  102. package/dist/features/cart/repository/cart.repository.js +1 -2
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -2
  104. package/dist/features/cart/utils/pending-ops.d.ts +11 -1
  105. package/dist/features/cart/utils/pending-ops.js +14 -2
  106. package/dist/features/categories/components/BrandDetailPageView.js +2 -1
  107. package/dist/features/categories/components/BundleDetailView.js +2 -1
  108. package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
  109. package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
  110. package/dist/features/categories/components/CategoryGrid.js +9 -2
  111. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
  112. package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
  113. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  114. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -1
  115. package/dist/features/events/components/EventCard.d.ts +11 -1
  116. package/dist/features/events/components/EventCard.js +9 -2
  117. package/dist/features/grouped/components/GroupedListingDetailView.js +3 -1
  118. package/dist/features/homepage/components/CTABannerSection.d.ts +3 -1
  119. package/dist/features/homepage/components/CTABannerSection.js +6 -3
  120. package/dist/features/homepage/components/HeroCarousel.js +8 -6
  121. package/dist/features/homepage/components/MarketplaceHomepageView.js +33 -7
  122. package/dist/features/homepage/components/SectionCarousel.js +13 -5
  123. package/dist/features/homepage/components/SecurityHighlightsSection.d.ts +8 -3
  124. package/dist/features/homepage/components/SecurityHighlightsSection.js +2 -2
  125. package/dist/features/homepage/components/StatsCounterSection.d.ts +9 -4
  126. package/dist/features/homepage/components/StatsCounterSection.js +2 -2
  127. package/dist/features/homepage/components/TestimonialsCarousel.js +1 -1
  128. package/dist/features/homepage/components/TrustFeaturesSection.d.ts +18 -4
  129. package/dist/features/homepage/components/TrustFeaturesSection.js +5 -5
  130. package/dist/features/homepage/components/WelcomeSection.d.ts +13 -2
  131. package/dist/features/homepage/components/WelcomeSection.js +3 -3
  132. package/dist/features/homepage/components/WhatsAppCommunitySection.d.ts +28 -0
  133. package/dist/features/homepage/components/WhatsAppCommunitySection.js +42 -6
  134. package/dist/features/homepage/components/WhatsAppGlyph.d.ts +20 -0
  135. package/dist/features/homepage/components/WhatsAppGlyph.js +20 -0
  136. package/dist/features/homepage/constants/hero-carousel-styles.d.ts +8 -6
  137. package/dist/features/homepage/constants/hero-carousel-styles.js +8 -6
  138. package/dist/features/homepage/constants/section-copy.d.ts +12 -10
  139. package/dist/features/homepage/constants/section-copy.js +28 -6
  140. package/dist/features/homepage/constants/stat-icons.d.ts +12 -0
  141. package/dist/features/homepage/constants/stat-icons.js +17 -0
  142. package/dist/features/homepage/constants/trust-icon-aliases.d.ts +8 -0
  143. package/dist/features/homepage/constants/trust-icon-aliases.js +75 -0
  144. package/dist/features/homepage/lib/section-defaults.d.ts +16 -1
  145. package/dist/features/homepage/lib/section-defaults.js +24 -4
  146. package/dist/features/homepage/lib/section-renderer.js +48 -8
  147. package/dist/features/homepage/schemas/firestore.d.ts +18 -1
  148. package/dist/features/layout/AppLayoutShell.js +2 -1
  149. package/dist/features/layout/BottomActions.d.ts +1 -0
  150. package/dist/features/layout/BottomActions.js +330 -0
  151. package/dist/features/layout/BottomChrome.d.ts +45 -0
  152. package/dist/features/layout/BottomChrome.js +5 -17
  153. package/dist/features/layout/SideActionDock.js +36 -13
  154. package/dist/features/layout/index.d.ts +2 -7
  155. package/dist/features/layout/index.js +7 -7
  156. package/dist/features/layout/useIsDesktop.d.ts +36 -0
  157. package/dist/features/layout/useIsDesktop.js +48 -0
  158. package/dist/features/live/components/LiveItemDetailPageView.js +2 -1
  159. package/dist/features/orders/actions/refund-actions.js +0 -1
  160. package/dist/features/orders/components/OrderAddonBadges.d.ts +1 -3
  161. package/dist/features/orders/components/OrderAddonBadges.js +3 -4
  162. package/dist/features/orders/components/OrderPaymentSummary.js +15 -2
  163. package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
  164. package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
  165. package/dist/features/orders/components/index.d.ts +2 -1
  166. package/dist/features/orders/components/index.js +1 -1
  167. package/dist/features/orders/repository/orders.repository.d.ts +0 -8
  168. package/dist/features/orders/repository/orders.repository.js +0 -2
  169. package/dist/features/orders/schemas/firestore.d.ts +17 -9
  170. package/dist/features/orders/types/index.d.ts +2 -5
  171. package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
  172. package/dist/features/orders/utils/order-announcement-message.js +16 -0
  173. package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
  174. package/dist/features/orders/utils/payment-proof-message.js +17 -0
  175. package/dist/features/parts/components/PartDetailPageView.js +96 -1
  176. package/dist/features/parts/schemas/firestore.d.ts +247 -0
  177. package/dist/features/parts/schemas/firestore.js +6 -6
  178. package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
  179. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
  180. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +4 -3
  181. package/dist/features/products/api/[id]/route.js +8 -1
  182. package/dist/features/products/api/route.js +16 -4
  183. package/dist/features/products/components/AvailabilityScopeHint.js +1 -1
  184. package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
  185. package/dist/features/products/components/InteractiveProductCard.js +16 -4
  186. package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
  187. package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
  188. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
  189. package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
  190. package/dist/features/products/components/PrizeDrawDetailPageView.js +2 -1
  191. package/dist/features/products/components/ProductDetailPageView.js +27 -2
  192. package/dist/features/products/components/ProductGrid.d.ts +14 -1
  193. package/dist/features/products/components/ProductGrid.js +22 -5
  194. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  195. package/dist/features/reviews/api/[id]/route.js +83 -23
  196. package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
  197. package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
  198. package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
  199. package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
  200. package/dist/features/reviews/components/index.d.ts +4 -0
  201. package/dist/features/reviews/components/index.js +2 -0
  202. package/dist/features/scams/schemas/index.d.ts +10 -10
  203. package/dist/features/search/action-index/settings-entries.js +0 -9
  204. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  205. package/dist/features/seller/actions/seller-actions.js +49 -3
  206. package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
  207. package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
  208. package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
  209. package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
  210. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +1 -6
  211. package/dist/features/store-extensions/repository/store-extensions.repositories.js +1 -10
  212. package/dist/features/store-extensions/schemas/firestore.d.ts +1 -17
  213. package/dist/features/store-extensions/schemas/firestore.js +0 -10
  214. package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
  215. package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
  216. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +0 -1
  217. package/dist/features/stores/components/StoreGuideHubView.js +2 -8
  218. package/dist/features/stores/components/StoreSettingsGuideView.js +2 -6
  219. package/dist/features/stores/components/index.d.ts +0 -2
  220. package/dist/features/stores/components/index.js +0 -1
  221. package/dist/features/stores/repository/store.repository.d.ts +1 -26
  222. package/dist/features/stores/repository/store.repository.js +5 -60
  223. package/dist/features/stores/schemas/firestore.d.ts +6 -25
  224. package/dist/features/stores/schemas/firestore.js +6 -3
  225. package/dist/features/stores/schemas/index.d.ts +1 -87
  226. package/dist/features/stores/schemas/index.js +10 -15
  227. package/dist/features/tester/seed-data/authored/admin__site-system.js +0 -48
  228. package/dist/features/tester/seed-data/authored/buying__buying-checkout.js +0 -40
  229. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +0 -16
  230. package/dist/features/tester/seed-data/authored/selling__seller-guide.js +1 -1
  231. package/dist/features/tester/seed-data/authored/selling__seller-marketing-extras.js +0 -79
  232. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +1 -1
  233. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +415 -69
  234. package/dist/index.d.ts +20 -32
  235. package/dist/index.js +34 -43
  236. package/dist/next/routing/route-map.d.ts +2 -8
  237. package/dist/next/routing/route-map.js +21 -4
  238. package/dist/providers/db-firebase/base.repository.d.ts +1 -1
  239. package/dist/providers/db-firebase/base.repository.js +1 -1
  240. package/dist/providers/payment-razorpay/index.d.ts +125 -0
  241. package/dist/providers/payment-razorpay/index.js +232 -0
  242. package/dist/providers/payment-razorpay/signature.d.ts +53 -0
  243. package/dist/providers/payment-razorpay/signature.js +71 -0
  244. package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
  245. package/dist/providers/shipping-shiprocket/index.js +356 -0
  246. package/dist/providers.d.ts +3 -0
  247. package/dist/providers.js +3 -0
  248. package/dist/react/hooks/useCanSeePrices.d.ts +0 -27
  249. package/dist/react/hooks/useCanSeePrices.js +25 -0
  250. package/dist/repositories/index.d.ts +1 -1
  251. package/dist/repositories/index.js +1 -1
  252. package/dist/schemas/registry.d.ts +10 -115
  253. package/dist/schemas/registry.js +1 -2
  254. package/dist/security/pii-encrypt.js +1 -1
  255. package/dist/seed/_helpers/category-forest.js +27 -0
  256. package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -88
  257. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  258. package/dist/seed/_helpers/category-parts.js +13399 -3019
  259. package/dist/seed/carousel-slides-seed-data.js +47 -10
  260. package/dist/seed/categories-seed-data.js +48 -6
  261. package/dist/seed/faq-seed-data.js +2 -2
  262. package/dist/seed/homepage-sections-seed-data.js +286 -54
  263. package/dist/seed/index.d.ts +1 -1
  264. package/dist/seed/index.js +1 -1
  265. package/dist/seed/manifest.d.ts +1 -1
  266. package/dist/seed/manifest.js +1 -2
  267. package/dist/seed/model-composition-seed-data.js +6 -8
  268. package/dist/seed/part-glossary/chunk0.d.ts +9 -0
  269. package/dist/seed/part-glossary/chunk0.js +35448 -0
  270. package/dist/seed/part-glossary/chunk1.d.ts +9 -0
  271. package/dist/seed/part-glossary/chunk1.js +40494 -0
  272. package/dist/seed/part-glossary/chunk2.d.ts +9 -0
  273. package/dist/seed/part-glossary/chunk2.js +51242 -0
  274. package/dist/seed/part-glossary/chunk3.d.ts +9 -0
  275. package/dist/seed/part-glossary/chunk3.js +53337 -0
  276. package/dist/seed/part-glossary/chunk4.d.ts +9 -0
  277. package/dist/seed/part-glossary/chunk4.js +51454 -0
  278. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  279. package/dist/seed/part-glossary-seed-data.js +23 -47483
  280. package/dist/seed/products-catalogue-seed-data.d.ts +5 -5
  281. package/dist/seed/products-catalogue-seed-data.js +63936 -13803
  282. package/dist/seed/site-settings-seed-data.js +34 -35
  283. package/dist/seed/store-extensions-seed-data.d.ts +1 -2
  284. package/dist/seed/store-extensions-seed-data.js +1 -23
  285. package/dist/seed/stores-seed-data.js +0 -1
  286. package/dist/server-entry.d.ts +1 -1
  287. package/dist/server-entry.js +1 -1
  288. package/dist/server.d.ts +8 -10
  289. package/dist/server.js +16 -22
  290. package/dist/styles.css +392 -43
  291. package/dist/tailwind-utilities.css +1 -1
  292. package/dist/tokens/themes/default-dark.js +1 -1
  293. package/dist/tokens/themes/default-light.js +2 -1
  294. package/dist/tokens/tokens.css +6 -2
  295. package/dist/ui/components/Button.d.ts +11 -0
  296. package/dist/ui/components/Button.js +11 -0
  297. package/dist/ui/components/Button.style.css +29 -0
  298. package/dist/ui/components/CarouselNav.d.ts +57 -0
  299. package/dist/ui/components/CarouselNav.js +25 -0
  300. package/dist/ui/components/CarouselNav.style.css +265 -0
  301. package/dist/ui/components/GatedPrice.js +30 -3
  302. package/dist/ui/components/GatedPrice.style.css +43 -40
  303. package/dist/ui/components/HorizontalScroller.d.ts +12 -1
  304. package/dist/ui/components/HorizontalScroller.js +43 -3
  305. package/dist/ui/components/ImageMosaic.d.ts +8 -1
  306. package/dist/ui/components/ImageMosaic.js +3 -2
  307. package/dist/ui/components/ImageMosaic.style.css +22 -0
  308. package/dist/ui/components/Layout.style.css +6 -0
  309. package/dist/ui/components/Semantic.d.ts +3 -2
  310. package/dist/ui/components/Semantic.js +1 -0
  311. package/dist/ui/components/Typography.d.ts +2 -0
  312. package/dist/ui/components/Typography.js +2 -0
  313. package/dist/ui/components/Typography.style.css +19 -0
  314. package/dist/ui/components/index.style.css +1 -0
  315. package/dist/ui/index.d.ts +3 -1
  316. package/dist/ui/index.js +4 -0
  317. package/package.json +3 -2
  318. package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
  319. package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
  320. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
  321. package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
  322. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
  323. package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
  324. package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
  325. package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
  326. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
  327. package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
  328. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
  329. package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
  330. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
  331. package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
  332. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
  333. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
  334. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
  335. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
  336. package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
  337. package/dist/features/whatsapp-bot/components/index.js +0 -2
  338. package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
  339. package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
  340. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
  341. package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
  342. package/dist/features/whatsapp-bot/index.d.ts +0 -3
  343. package/dist/features/whatsapp-bot/index.js +0 -3
  344. package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
  345. package/dist/features/whatsapp-bot/manifest.js +0 -13
  346. package/dist/features/whatsapp-bot/messages/en.json +0 -7
  347. package/dist/features/whatsapp-bot/server.d.ts +0 -7
  348. package/dist/features/whatsapp-bot/server.js +0 -7
  349. package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
  350. package/dist/features/whatsapp-bot/types/index.js +0 -1
@@ -31,10 +31,6 @@ export interface NotificationDocument extends BaseDocument {
31
31
  readAt?: Date;
32
32
  relatedId?: string;
33
33
  relatedType?: NotificationRelatedType;
34
- /** Outcome of the async whatsappNotify job for this notification's WhatsApp send, if one was enqueued. */
35
- whatsappStatus?: "queued" | "sent" | "failed" | "skipped";
36
- /** jobs/{id} doc that ran (or is running) the WhatsApp send for this notification. */
37
- whatsappJobId?: string;
38
34
  }
39
35
  export declare const NOTIFICATIONS_COLLECTION: "notifications";
40
36
  export declare const NOTIFICATIONS_INDEXED_FIELDS: readonly ["userId", "isRead", "createdAt", "type"];
@@ -99,8 +95,20 @@ export interface SiteSettingsCredentials {
99
95
  /** Username/password pair configured in the PhonePe dashboard's Webhook tab (SHA/Basic auth mode) — verified via the SDK's `validateCallback()`. */
100
96
  phonepeWebhookUsername?: string;
101
97
  phonepeWebhookPassword?: string;
98
+ /**
99
+ * Razorpay publishable key (`rzp_test_…` / `rzp_live_…`).
100
+ *
101
+ * 🛑 The ONLY credential in this interface that is deliberately sent to the
102
+ * browser — Razorpay's `checkout.js` needs it to open the modal. It is
103
+ * published by `/api/payment/create-order`, not by the site-settings
104
+ * projection, so it stays in `PRIVATE_SITE_SETTINGS_FIELDS` like every
105
+ * other credential (see CLAUDE.md § Public Data Projections).
106
+ */
107
+ razorpayKeyId?: string;
108
+ razorpayKeySecret?: string;
109
+ /** Webhook signing secret from Razorpay's dashboard → Webhooks. Without it `verifyWebhook()` fails closed. */
110
+ razorpayWebhookSecret?: string;
102
111
  resendApiKey?: string;
103
- whatsappApiKey?: string;
104
112
  metaAppId?: string;
105
113
  metaAppSecret?: string;
106
114
  metaPageAccessToken?: string;
@@ -115,28 +123,6 @@ export interface SiteSettingsCredentials {
115
123
  /** Google Places API for Google Business Reviews (HS4) */
116
124
  googleMapsApiKey?: string;
117
125
  googlePlaceId?: string;
118
- /** WhatsApp Business Cloud API — platform level (for admin purchase announcements) */
119
- whatsappPhoneNumberId?: string;
120
- whatsappCloudApiToken?: string;
121
- /** Comma-separated digits-only phone numbers that receive order announcements */
122
- whatsappAdminNotifyNumbers?: string;
123
- /**
124
- * Approved Meta message-template names, one flat string field per
125
- * order-lifecycle notification type — every `SiteSettingsCredentials`
126
- * field is stored as an individually-encrypted string (see
127
- * `getDecryptedCredentials`/`getCredentialsMasked`, which iterate + mask
128
- * every field as a string), so this can't be a `Record<string,string>`
129
- * map. Business-initiated WhatsApp sends outside the 24h customer-service
130
- * window require a pre-approved template.
131
- */
132
- whatsappTemplateOrderPlaced?: string;
133
- whatsappTemplateOrderConfirmed?: string;
134
- whatsappTemplateOrderShipped?: string;
135
- whatsappTemplateOrderDelivered?: string;
136
- whatsappTemplateOrderCancelled?: string;
137
- whatsappTemplateRefundInitiated?: string;
138
- /** BCP-47 language code the templates above were approved in, e.g. "en" or "en_US". */
139
- whatsappTemplateLanguage?: string;
140
126
  }
141
127
  export interface SiteSettingsCredentialsMasked {
142
128
  phonepeClientId?: string;
@@ -145,8 +131,10 @@ export interface SiteSettingsCredentialsMasked {
145
131
  phonepeEnvironment?: string;
146
132
  phonepeWebhookUsername?: string;
147
133
  phonepeWebhookPassword?: string;
134
+ razorpayKeyId?: string;
135
+ razorpayKeySecret?: string;
136
+ razorpayWebhookSecret?: string;
148
137
  resendApiKey?: string;
149
- whatsappApiKey?: string;
150
138
  metaAppId?: string;
151
139
  metaAppSecret?: string;
152
140
  metaPageAccessToken?: string;
@@ -158,16 +146,6 @@ export interface SiteSettingsCredentialsMasked {
158
146
  deviantartClientSecret?: string;
159
147
  googleMapsApiKey?: string;
160
148
  googlePlaceId?: string;
161
- whatsappPhoneNumberId?: string;
162
- whatsappCloudApiToken?: string;
163
- whatsappAdminNotifyNumbers?: string;
164
- whatsappTemplateOrderPlaced?: string;
165
- whatsappTemplateOrderConfirmed?: string;
166
- whatsappTemplateOrderShipped?: string;
167
- whatsappTemplateOrderDelivered?: string;
168
- whatsappTemplateOrderCancelled?: string;
169
- whatsappTemplateRefundInitiated?: string;
170
- whatsappTemplateLanguage?: string;
171
149
  }
172
150
  export interface NotificationChannelConfig {
173
151
  inApp: {
@@ -183,15 +161,6 @@ export interface NotificationChannelConfig {
183
161
  /** Subset of NotificationTypes to email — absent means all types. */
184
162
  types?: NotificationType[];
185
163
  };
186
- whatsapp: {
187
- enabled: boolean;
188
- /** Minimum priority that triggers a WhatsApp message (default: "high"). */
189
- minPriority?: NotificationPriority;
190
- /** Subset of NotificationTypes to WhatsApp — absent means all types. */
191
- types?: NotificationType[];
192
- /** When true, WhatsApp OTP is offered as an alternative to email OTP at checkout. */
193
- otpEnabled?: boolean;
194
- };
195
164
  sms: {
196
165
  enabled: boolean;
197
166
  /** Minimum priority that triggers an SMS (default: "high"). */
@@ -368,6 +337,18 @@ export interface SiteSettingsDocument extends BaseDocument {
368
337
  supportHours?: string;
369
338
  };
370
339
  payment: {
340
+ /**
341
+ * Razorpay (client-side card/UPI/netbanking modal).
342
+ *
343
+ * 🛑 Checked BEFORE `phonepeEnabled` in `providers.config.ts` — exactly
344
+ * one online gateway can be registered per process, so the order is the
345
+ * tie-break when both are on, not a precedence anyone should rely on.
346
+ * Turning this on with no credentials makes the provider constructor
347
+ * throw at boot; `resolveKeys()` treats a seeded `*_PLACEHOLDER` as
348
+ * absent, so a placeholder reads as "unconfigured" rather than failing
349
+ * mid-checkout.
350
+ */
351
+ razorpayEnabled: boolean;
371
352
  phonepeEnabled: boolean;
372
353
  upiManualEnabled: boolean;
373
354
  codEnabled: boolean;
@@ -433,10 +414,6 @@ export interface SiteSettingsDocument extends BaseDocument {
433
414
  /** COD handling fee charged to the buyer: max(codHandlingFeeMin, subtotal × codHandlingFeePercent / 100). */
434
415
  codHandlingFeeMin: number;
435
416
  codHandlingFeePercent: number;
436
- /** Admin master toggle — whether the WhatsApp order-updates addon is offered at checkout at all. */
437
- whatsappNotifyFeeEnabled: boolean;
438
- /** Flat rupee fee charged to the buyer when they opt into the WhatsApp order-updates addon. */
439
- whatsappNotifyFee: number;
440
417
  /** Admin master toggle — whether the gift-wrap addon is offered at checkout at all. */
441
418
  giftWrapFeeEnabled: boolean;
442
419
  /** Flat rupee fee charged to the buyer when they opt into gift wrap. */
@@ -522,8 +499,6 @@ export interface SiteSettingsDocument extends BaseDocument {
522
499
  messaging?: {
523
500
  /** User-facing email. Staff mail (daily digest, payout summary) ignores this. */
524
501
  emailEnabled: boolean;
525
- /** User-facing WhatsApp. Same exemption for staff. */
526
- whatsappEnabled: boolean;
527
502
  /**
528
503
  * Reserved sends allowed per channel per day. Applies to staff and
529
504
  * transactional mail too — the ceiling exists to stay inside the
@@ -531,7 +506,6 @@ export interface SiteSettingsDocument extends BaseDocument {
531
506
  */
532
507
  dailyCeiling: {
533
508
  email: number;
534
- whatsapp: number;
535
509
  sms: number;
536
510
  };
537
511
  };
@@ -15,7 +15,7 @@ export const ADMIN_CHECKOUT_BYPASS_FLAG_KEY = "adminCheckoutBypass";
15
15
  * · `constants/notification-types.ts` (9) — fed the admin filter chips and
16
16
  * the per-channel allow-list, so the admin list could not filter 18 of
17
17
  * the 27 real types and an admin literally could not allow-list
18
- * `offer_received` or `payment_review` for email or WhatsApp
18
+ * `offer_received` or `payment_review` for email
19
19
  * · `features/account/types/index.ts` (5: order|offer|promo|system|message)
20
20
  * — a different vocabulary entirely, on a `UserNotification` type with
21
21
  * zero real consumers
@@ -195,7 +195,6 @@ export const notificationQueryHelpers = {
195
195
  export const DEFAULT_NOTIFICATION_CHANNELS = {
196
196
  inApp: { enabled: true, readOnly: true },
197
197
  email: { enabled: false, minPriority: "normal" },
198
- whatsapp: { enabled: false, minPriority: "high", otpEnabled: false },
199
198
  sms: { enabled: false, minPriority: "high" },
200
199
  };
201
200
  const PRIORITY_ORDER = {
@@ -231,6 +230,7 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
231
230
  motto: "Your Marketplace, Your Rules",
232
231
  theme: DEFAULT_SITE_SETTINGS_THEME,
233
232
  payment: {
233
+ razorpayEnabled: false,
234
234
  phonepeEnabled: false,
235
235
  upiManualEnabled: true,
236
236
  codEnabled: true,
@@ -243,8 +243,7 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
243
243
  // half-configured site.
244
244
  messaging: {
245
245
  emailEnabled: false,
246
- whatsappEnabled: false,
247
- dailyCeiling: { email: 80, whatsapp: 200, sms: 50 },
246
+ dailyCeiling: { email: 80, sms: 50 },
248
247
  },
249
248
  emi: {
250
249
  enabled: false,
@@ -267,11 +266,9 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
267
266
  // The buyer add-ons ship ENABLED. They were `false`, which made
268
267
  // StoreAddonsPicker return null on both the cart and checkout, so a fresh
269
268
  // install had no add-ons at all and no indication why. `undefined` still
270
- // means disabled everywhere (computeWhatsAppNotifyFee et al. read
269
+ // means disabled everywhere (computeGiftWrapFee et al. read
271
270
  // `if (!rates.XEnabled) return 0`) — only the DEFAULT changed, so an
272
271
  // existing siteSettings document keeps whatever it already stored.
273
- whatsappNotifyFeeEnabled: true,
274
- whatsappNotifyFee: 10,
275
272
  giftWrapFeeEnabled: true,
276
273
  giftWrapFee: 49,
277
274
  shipmentProtectionFeeEnabled: true,
@@ -306,28 +303,53 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
306
303
  autoExtendWindowMinutes: 5,
307
304
  settlementGracePeriodHours: 24,
308
305
  },
309
- // Every type on by default. `isListingTypeEnabled` treats a MISSING key as
310
- // enabled, so this block is documentation of the full set rather than the
311
- // mechanism — a type absent here still renders. `art` and `stickers` were
312
- // missing from the old `featureFlags.listingTypes` literal for exactly that
313
- // reason: nothing broke, so nobody noticed (Root Cause #58).
306
+ /*
307
+ * 🛑 THIS BLOCK IS NOW LOAD-BEARING, and it was not before.
308
+ *
309
+ * `isListingTypeEnabled` treats a MISSING key as ENABLED, so while every
310
+ * value here was `true` the block was pure documentation — a type left out
311
+ * rendered anyway, which is how `art`, `stickers` and `spares` sat outside it
312
+ * for months with nothing breaking (Root Cause #58). Now that five types ship
313
+ * `false`, an omission is no longer harmless in one direction only: a new
314
+ * union member added to `ListingType` and forgotten here goes LIVE on every
315
+ * site, which is the opposite of the intended default.
316
+ *
317
+ * So: every member of `ListingType` gets an explicit line, including the ones
318
+ * that are `true`. `spares` was missing entirely and is spelled out now.
319
+ *
320
+ * 2026-10-20 — open for business: standard, spares, pre-order, art, stickers.
321
+ * The other five are built, routed and reachable by URL; what is switched off
322
+ * is the site OFFERING them. An admin flips any of them back in
323
+ * Admin → Site Settings → Listings without a deploy, which is the whole point
324
+ * of this living in settings rather than in a constant.
325
+ *
326
+ * ⚠️ A default only reaches a site that has never written its settings
327
+ * document. An existing deployment already has a singleton with the old
328
+ * all-true map, and changing this file does NOT rewrite it — flip those in the
329
+ * admin UI, or re-seed.
330
+ */
314
331
  listings: {
315
332
  listingTypes: {
316
333
  standard: true,
317
- auction: true,
334
+ spares: true,
318
335
  "pre-order": true,
319
- "prize-draw": true,
320
- classified: true,
321
- "digital-code": true,
322
- live: true,
323
336
  art: true,
324
337
  stickers: true,
338
+ auction: false,
339
+ "prize-draw": false,
340
+ classified: false,
341
+ "digital-code": false,
342
+ live: false,
325
343
  },
344
+ // `bundle: false` is the admin-side switch for bundles, which are a
345
+ // CATEGORY type rather than a listing type — it is what hides the
346
+ // homepage's featured-bundles rail and the bundle tabs on category and
347
+ // brand pages. Same reasoning as the five listing types above.
326
348
  categoryTypes: {
327
349
  category: true,
328
350
  sublisting: true,
329
351
  brand: true,
330
- bundle: true,
352
+ bundle: false,
331
353
  },
332
354
  },
333
355
  shipping: {
@@ -42,6 +42,7 @@ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListing
42
42
  import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data.js";
43
43
  import { SublistingCarouselSection } from "../../products/components/SublistingCarouselSection.js";
44
44
  import { isFinalSale } from "../../products/constants/final-sale.js";
45
+ import { OwnerQuickActions } from "../../admin/components/OwnerQuickActions.js";
45
46
  function toDescriptionHtml(raw) {
46
47
  if (!raw)
47
48
  return "";
@@ -49,8 +50,8 @@ function toDescriptionHtml(raw) {
49
50
  return normalizeRichTextHtml(s);
50
51
  }
51
52
  function renderAuctionInfoPanel(props) {
52
- const { productId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, finalSale, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
53
- return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), _jsx(AuctionLiveStatusBadge, { auctionEndDate: endDate, initialIsEnded: isEnded })] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsx(LiveBidPrice, { productId: productId, currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "2xl", badgeSize: "sm" }), endDate && !isEnded && (_jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: ["Ends in ", _jsx(Span, { weight: "medium", color: "muted", children: _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" }) })] })), endDate && _jsxs(Text, { className: "mt-0.5", color: "faint", size: "xs", children: [isEnded ? "Ended" : "Ends", " ", endDate.toLocaleString()] })] }), buyNowAvailable && buyNowPrice !== null && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: _jsx(GatedPrice, { label: "Sign in to see the Buy Now price", children: formatCurrency(buyNowPrice, currency) }) }), _jsx(Span, { size: "xs", color: "faint", children: "\u2014 in \u201CPlace a bid\u201D" })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, finalSale: finalSale, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", finalSale: "Final Sale", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
53
+ const { productId, storeId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, finalSale, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
54
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), _jsx(AuctionLiveStatusBadge, { auctionEndDate: endDate, initialIsEnded: isEnded })] }), _jsx(OwnerQuickActions, { resource: "PRODUCT", placement: "detail", recordLabel: title, storeId: storeId, editHref: String(ROUTES.STORE.PRODUCTS_EDIT(productId)) }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsx(LiveBidPrice, { productId: productId, currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "2xl", badgeSize: "sm" }), endDate && !isEnded && (_jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: ["Ends in ", _jsx(Span, { weight: "medium", color: "muted", children: _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" }) })] })), endDate && _jsxs(Text, { className: "mt-0.5", color: "faint", size: "xs", children: [isEnded ? "Ended" : "Ends", " ", endDate.toLocaleString()] })] }), buyNowAvailable && buyNowPrice !== null && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: _jsx(GatedPrice, { label: "Sign in to see the Buy Now price", children: formatCurrency(buyNowPrice, currency) }) }), _jsx(Span, { size: "xs", color: "faint", children: "\u2014 in \u201CPlace a bid\u201D" })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, finalSale: finalSale, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", finalSale: "Final Sale", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-surface hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
54
55
  }
55
56
  /**
56
57
  * Store reviews for the auction's seller.
@@ -215,7 +216,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
215
216
  price: typeof p.price === "number" ? p.price : undefined,
216
217
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
217
218
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
218
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => renderAuctionInfoPanel({ productId: String(product.id), title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, finalSale: isFinalSale(p), category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "xl", badgeSize: "xs" }), !isEnded && (_jsx(LiveMinIncrement, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride }))] }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
219
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => renderAuctionInfoPanel({ productId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, buyNowAvailable, featured, freeShipping, condition, finalSale: isFinalSale(p), category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "xl", badgeSize: "xs" }), !isEnded && (_jsx(LiveMinIncrement, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride }))] }), !isEnded && endDate && (_jsxs(Text, { align: "center", size: "xs", color: "muted", children: ["Ends in ", _jsx(CountdownDisplay, { targetDate: endDate, format: "auto", expiredLabel: "Ended" })] })), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, tiers: bidIncrementTiers, minBidIncrementOverride: minBidIncrementOverride, currency: currency, isEnded: isEnded, auctionEndDate: endDate, buyNowPrice: buyNowPrice, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
219
220
  /* Read-only bid panel — shown when no bid action is wired (preview/demo) */
220
221
  _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsx(Text, { size: "xs", color: "muted", children: _jsxs(GatedPrice, { label: "Sign in to see the starting bid", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(initialMinBidIncrement, currency)] })] }) }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number",
221
222
  // The minimum is stated in the gated line above rather than
@@ -74,5 +74,15 @@ export interface MarketplaceAuctionCardProps {
74
74
  onNavigate?: (href: string) => void;
75
75
  wishlistActions?: WishlistToggleActions;
76
76
  labels?: MarketplaceAuctionCardLabels;
77
+ /**
78
+ * Owner-only pencil / clone / bin. Pass `<OwnerQuickActions>`; it renders
79
+ * nothing for a visitor who does not own the record.
80
+ *
81
+ * 🛑 Rendered OUTSIDE this card's own anchor, as a sibling. A `<button>`
82
+ * inside an `<a>` is invalid HTML that browsers resolve by closing the
83
+ * anchor early, which breaks the CARD's navigation rather than just the
84
+ * button's — `stopPropagation` stops a click but cannot fix a parse.
85
+ */
86
+ renderOwnerActions?: () => React.ReactNode;
77
87
  }
78
- export declare function MarketplaceAuctionCard({ product, buyoutPrice, className, variant, selectable, isSelected, onSelect, inWishlist: initialInWishlist, href, hrefBuilder, onNavigate, wishlistActions, labels, }: MarketplaceAuctionCardProps): import("react").JSX.Element;
88
+ export declare function MarketplaceAuctionCard({ product, buyoutPrice, className, variant, selectable, isSelected, onSelect, inWishlist: initialInWishlist, href, hrefBuilder, onNavigate, wishlistActions, labels, renderOwnerActions, }: MarketplaceAuctionCardProps): import("react").JSX.Element;
@@ -94,7 +94,7 @@ function formatCountdownLabel(remaining, endedLabel) {
94
94
  return `${hours}h ${minutes}m ${seconds}s`;
95
95
  return `${minutes}m ${seconds}s`;
96
96
  }
97
- export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", variant = "grid", selectable = false, isSelected = false, onSelect, inWishlist: initialInWishlist = false, href, hrefBuilder, onNavigate, wishlistActions, labels, }) {
97
+ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", variant = "grid", selectable = false, isSelected = false, onSelect, inWishlist: initialInWishlist = false, href, hrefBuilder, onNavigate, wishlistActions, labels, renderOwnerActions, }) {
98
98
  const router = useRouter();
99
99
  const mergedLabels = { ...DEFAULT_LABELS, ...labels };
100
100
  const baseVariant = variant === "list" ? "list" : "grid";
@@ -180,5 +180,12 @@ export function MarketplaceAuctionCard({ product, buyoutPrice, className = "", v
180
180
  remaining, resolvedBuyoutPrice, inWishlist, wishlistLoading, wishlistActions,
181
181
  countdownClass, mergedLabels, handleWishlist, handleNavigate,
182
182
  };
183
- return (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
183
+ const card = (_jsxs(BaseListingCard, { isSelected: isSelected, isDisabled: isEnded, variant: baseVariant, className: className, onMouseDown: !isSelected ? longPress.onMouseDown : undefined, onMouseUp: !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: !isSelected ? longPress.onTouchCancel : undefined, children: [renderAuctionCardHero({ product, auctionHref, currentSrc, hovered, images, imageIndex, hasVideo, isEnded, isEndingSoon, isSelected, selectable, mergedLabels, onSelect, setHovered, handleSelect }), _jsx(BaseListingCard.Info, { variant: baseVariant, children: baseVariant === "list" ? renderAuctionCardInfoList(cardInfoProps) : renderAuctionCardInfoGrid(cardInfoProps) })] }));
184
+ if (!renderOwnerActions)
185
+ return card;
186
+ /*
187
+ * Sibling of the card, never a child — see the prop's docstring. `z-20`
188
+ * clears this card's own overlays, which all sit at `z-10`.
189
+ */
190
+ return (_jsxs(Div, { className: "relative h-full", children: [card, _jsx(Div, { className: "absolute right-2 bottom-2 z-20", children: renderOwnerActions() })] }));
184
191
  }
@@ -44,6 +44,6 @@ export interface CheckoutValueOtpDoc {
44
44
  * making the whole feature look broken.
45
45
  */
46
46
  export declare function enforceCheckoutValueOtpRateLimit(db: Db, uid: string): Promise<void>;
47
- /** Build the checkout-value OTP WhatsApp/SMS message body. */
47
+ /** Build the checkout-value OTP message body. */
48
48
  export declare function buildCheckoutValueOtpMessage(code: string, siteName: string): string;
49
49
  export {};
@@ -75,7 +75,7 @@ export async function enforceCheckoutValueOtpRateLimit(db, uid) {
75
75
  tx.set(metaRef, { lastSentAt: new Date() }, { merge: true });
76
76
  });
77
77
  }
78
- /** Build the checkout-value OTP WhatsApp/SMS message body. */
78
+ /** Build the checkout-value OTP message body. */
79
79
  export function buildCheckoutValueOtpMessage(code, siteName) {
80
80
  return (`*${siteName} — Order Verification*\n\n` +
81
81
  `Your verification code for this high-value order is: *${code}*\n\n` +
@@ -67,8 +67,18 @@ export declare const ROUTE_PERMISSION_MAP: Record<string, Permission>;
67
67
  *
68
68
  * Defaults on store creation: ["suggest_brands", "create_coupons"]
69
69
  */
70
- export type StoreCapability = "host_auctions" | "host_preorders" | "create_categories" | "suggest_brands" | "create_coupons" | "bulk_listing_import" | "extended_return_window" | "verified_seller" | "featured_placement" | "promotional_banner" | "priority_support" | "multiple_stores" | "custom_store_slug" | "api_access" | "lower_commission_rate" | "early_access_features" | "advanced_analytics" | "whatsapp_catalog_sync";
71
- /** Default capabilities granted to every new store on creation. */
70
+ export type StoreCapability = "advanced_listing_types" | "host_auctions" | "host_preorders" | "create_categories" | "suggest_brands" | "create_coupons" | "bulk_listing_import" | "extended_return_window" | "verified_seller" | "featured_placement" | "promotional_banner" | "priority_support" | "multiple_stores" | "custom_store_slug" | "api_access" | "lower_commission_rate" | "early_access_features" | "advanced_analytics";
71
+ export declare const ALL_STORE_CAPABILITIES: StoreCapability[];
72
+ /** Narrow an untrusted string to a real capability. */
73
+ export declare function isStoreCapability(value: unknown): value is StoreCapability;
74
+ /**
75
+ * Default capabilities granted to every new store on creation.
76
+ *
77
+ * 🛑 THE one definition. `AdminStoreEditorView` hardcoded its own copy, which
78
+ * is also the `??` fallback on its load path — so the two could disagree about
79
+ * what a store with no `capabilities` field is entitled to, and the admin
80
+ * editor's copy is the one that gets SAVED BACK. Import this; never restate it.
81
+ */
72
82
  export declare const DEFAULT_STORE_CAPABILITIES: StoreCapability[];
73
83
  /**
74
84
  * Check whether a permission array includes a required permission.
@@ -87,6 +97,23 @@ export declare function hasAllPermissions(permissions: Permission[], required: P
87
97
  * Check whether a store's capabilities include a specific capability.
88
98
  */
89
99
  export declare function hasCapability(capabilities: StoreCapability[], required: StoreCapability): boolean;
100
+ /**
101
+ * Resolve a store's stored `capabilities` array to its effective set.
102
+ *
103
+ * 🛑 The PURE half of `getStoreCapabilities`, extracted so a caller that
104
+ * already holds the store document does not pay a second Firestore read to ask
105
+ * what it is entitled to (Rule #6). Several routes were reading
106
+ * `store.capabilities ?? []` inline for exactly that reason, which made them a
107
+ * second, divergent reader of the field — they skipped the default merge, so
108
+ * they and `getStoreCapabilities` could disagree about the same store.
109
+ *
110
+ * Note the empty-array case falls back to the defaults, matching
111
+ * `getStoreCapabilities`' long-standing `raw.length > 0` test. That is
112
+ * arguably the wrong call — an admin who deliberately unticks everything gets
113
+ * the defaults handed back — but it is the established behaviour and changing
114
+ * it would silently re-grant capabilities on stores that had them removed.
115
+ */
116
+ export declare function resolveStoreCapabilities(raw: readonly string[] | null | undefined): StoreCapability[];
90
117
  /**
91
118
  * Groups permission prefixes into human-labeled domains for display —
92
119
  * used by the employee-permission editor and the read-only permissions
@@ -319,7 +319,52 @@ export const ROUTE_PERMISSION_MAP = {
319
319
  "/admin/maintenance/payment-rollbacks": "admin:maintenance:view-payment-rollbacks",
320
320
  "/admin/maintenance/analysis": "admin:maintenance:run-analysis",
321
321
  };
322
- /** Default capabilities granted to every new store on creation. */
322
+ /**
323
+ * Every capability, as a runtime value.
324
+ *
325
+ * 🛑 `Record<StoreCapability, true>` rather than a plain array, for the same
326
+ * reason `ALL_LISTING_TYPES_MAP` is: adding a member to the union without
327
+ * adding it here is then a COMPILE error, not a silent omission. This is what
328
+ * lets `capabilities` be validated as a `z.enum` instead of
329
+ * `z.array(z.string())` — which accepted any string, so a typo'd capability
330
+ * saved successfully and the gate it was meant to open never opened, silently.
331
+ *
332
+ * It also backs the admin editor's grouping, so a new capability cannot be
333
+ * granted in code and remain unreachable from the admin UI.
334
+ */
335
+ const STORE_CAPABILITY_MAP = {
336
+ advanced_listing_types: true,
337
+ host_auctions: true,
338
+ host_preorders: true,
339
+ create_categories: true,
340
+ suggest_brands: true,
341
+ create_coupons: true,
342
+ bulk_listing_import: true,
343
+ extended_return_window: true,
344
+ verified_seller: true,
345
+ featured_placement: true,
346
+ promotional_banner: true,
347
+ priority_support: true,
348
+ multiple_stores: true,
349
+ custom_store_slug: true,
350
+ api_access: true,
351
+ lower_commission_rate: true,
352
+ early_access_features: true,
353
+ advanced_analytics: true,
354
+ };
355
+ export const ALL_STORE_CAPABILITIES = Object.keys(STORE_CAPABILITY_MAP);
356
+ /** Narrow an untrusted string to a real capability. */
357
+ export function isStoreCapability(value) {
358
+ return typeof value === "string" && value in STORE_CAPABILITY_MAP;
359
+ }
360
+ /**
361
+ * Default capabilities granted to every new store on creation.
362
+ *
363
+ * 🛑 THE one definition. `AdminStoreEditorView` hardcoded its own copy, which
364
+ * is also the `??` fallback on its load path — so the two could disagree about
365
+ * what a store with no `capabilities` field is entitled to, and the admin
366
+ * editor's copy is the one that gets SAVED BACK. Import this; never restate it.
367
+ */
323
368
  export const DEFAULT_STORE_CAPABILITIES = [
324
369
  "suggest_brands",
325
370
  "create_coupons",
@@ -352,6 +397,27 @@ export function hasAllPermissions(permissions, required) {
352
397
  export function hasCapability(capabilities, required) {
353
398
  return capabilities.includes(required);
354
399
  }
400
+ /**
401
+ * Resolve a store's stored `capabilities` array to its effective set.
402
+ *
403
+ * 🛑 The PURE half of `getStoreCapabilities`, extracted so a caller that
404
+ * already holds the store document does not pay a second Firestore read to ask
405
+ * what it is entitled to (Rule #6). Several routes were reading
406
+ * `store.capabilities ?? []` inline for exactly that reason, which made them a
407
+ * second, divergent reader of the field — they skipped the default merge, so
408
+ * they and `getStoreCapabilities` could disagree about the same store.
409
+ *
410
+ * Note the empty-array case falls back to the defaults, matching
411
+ * `getStoreCapabilities`' long-standing `raw.length > 0` test. That is
412
+ * arguably the wrong call — an admin who deliberately unticks everything gets
413
+ * the defaults handed back — but it is the established behaviour and changing
414
+ * it would silently re-grant capabilities on stores that had them removed.
415
+ */
416
+ export function resolveStoreCapabilities(raw) {
417
+ if (Array.isArray(raw) && raw.length > 0)
418
+ return raw;
419
+ return [...DEFAULT_STORE_CAPABILITIES];
420
+ }
355
421
  // ============================================================================
356
422
  // PERMISSION DOMAINS — display grouping (S-ADMIN-7, 2026-08-19)
357
423
  // ============================================================================
@@ -17,5 +17,15 @@ export interface BlogFeaturedCardProps {
17
17
  showAuthor?: boolean;
18
18
  /** Show the cover image. Defaults to `true`. */
19
19
  showThumbnail?: boolean;
20
+ /**
21
+ * Owner-only pencil / clone / bin, for an admin or whoever owns this record.
22
+ * Pass `<OwnerQuickActions>`; it renders nothing for anyone else.
23
+ *
24
+ * 🛑 Rendered OUTSIDE this card's own `<Link>`/`<TextLink>`, as a sibling.
25
+ * A `<button>` inside an `<a>` is invalid HTML that browsers resolve by
26
+ * closing the anchor early, which breaks the CARD's navigation rather than
27
+ * just the button's — `stopPropagation` stops a click but cannot fix a parse.
28
+ */
29
+ renderOwnerActions?: () => React.ReactNode;
20
30
  }
21
- export declare function BlogFeaturedCard({ post, href, labels, className, selectable, isSelected, onSelect, showReadTime, showAuthor, showThumbnail, }: BlogFeaturedCardProps): React.JSX.Element;
31
+ export declare function BlogFeaturedCard({ post, href, labels, className, selectable, isSelected, onSelect, showReadTime, showAuthor, showThumbnail, renderOwnerActions, }: BlogFeaturedCardProps): React.JSX.Element;
@@ -20,7 +20,7 @@ const CATEGORY_BADGE = {
20
20
  community: "bg-warning-surface text-warning dark:bg-warning-surface dark:text-warning",
21
21
  };
22
22
  const CLS_FEATURED_BADGE = "bg-warning-surface text-warning dark:bg-warning-surface dark:text-warning px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] rounded-full";
23
- export function BlogFeaturedCard({ post, href, labels = {}, className = "", selectable = false, isSelected = false, onSelect, showReadTime = true, showAuthor = true, showThumbnail = true, }) {
23
+ export function BlogFeaturedCard({ post, href, labels = {}, className = "", selectable = false, isSelected = false, onSelect, showReadTime = true, showAuthor = true, showThumbnail = true, renderOwnerActions, }) {
24
24
  const longPress = useLongPress(() => onSelect?.(post.id, !isSelected));
25
25
  const safeTitle = post.title?.trim() || "Untitled post";
26
26
  const coverImageUrl = showThumbnail ? getMediaUrl(post.coverImage) : undefined;
@@ -31,5 +31,12 @@ export function BlogFeaturedCard({ post, href, labels = {}, className = "", sele
31
31
  day: "numeric",
32
32
  })
33
33
  : "";
34
- return (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [showThumbnail && (_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) })), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [showAuthor && post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), showReadTime && post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
34
+ const card = (_jsxs(Article, { rounded: "xl", shadow: "hover-md", className: `group relative h-full overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] ${isSelected ? "ring-2 ring-primary outline outline-2 outline-primary" : ""} ${className}`, onMouseDown: onSelect && !isSelected ? longPress.onMouseDown : undefined, onMouseUp: onSelect && !isSelected ? longPress.onMouseUp : undefined, onMouseLeave: onSelect && !isSelected ? longPress.onMouseLeave : undefined, onTouchStart: onSelect && !isSelected ? longPress.onTouchStart : undefined, onTouchEnd: onSelect && !isSelected ? longPress.onTouchEnd : undefined, onTouchCancel: onSelect && !isSelected ? longPress.onTouchCancel : undefined, children: [onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(post.id, !isSelected); }, label: isSelected ? "Deselect post" : "Select post", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsxs(TextLink, { href: href, layout: "flex-col", className: "h-full", children: [showThumbnail && (_jsx(Div, { className: `relative aspect-video ${__O.hidden} flex-shrink-0`, children: coverImageUrl ? (_jsx(MediaImage, { src: coverImageUrl, alt: safeTitle, size: "card", className: "transition-transform duration-300 group-hover:scale-105" })) : (_jsx(Div, { role: "img", "aria-label": safeTitle, surface: "muted", className: "h-full w-full" })) })), _jsxs(Stack, { className: `flex-1 ${__P.p4}`, children: [_jsxs(Row, { gap: "xs", className: "mb-2", wrap: true, children: [_jsx(Span, { size: "xs", weight: "medium", className: `inline-block capitalize ${CATEGORY_BADGE[post.category] ?? "bg-[var(--appkit-color-bg)] text-[var(--appkit-color-text-muted)]"}`, rounded: "full", padding: "pill-xs", children: post.category }), post.isFeatured && (_jsx(Span, { size: "xs", weight: "medium", className: CLS_FEATURED_BADGE, children: labels.featuredBadge ?? "Featured" }))] }), _jsx(Heading, { level: 3, className: `leading-snug mb-1`, color: "primary", truncate: 2, size: "base", weight: "semibold", children: safeTitle }), post.excerpt && (_jsx(Text, { className: `mb-3`, color: "muted", truncate: 2, size: "sm", children: post.excerpt })), _jsxs(Row, { color: "muted", textSize: "xs", className: "mt-auto", gap: "3", wrap: true, children: [showAuthor && post.authorName && (_jsx(Span, { children: safeDisplayName(post.authorName, "Author") })), showReadTime && post.readTimeMinutes != null && (_jsxs(Span, { children: [post.readTimeMinutes, " ", labels.readTime ?? "min read"] })), date && _jsx(Span, { children: date })] })] })] })] }));
35
+ if (!renderOwnerActions)
36
+ return card;
37
+ /*
38
+ * Sibling of the card, never a child — see the prop's docstring. `z-20`
39
+ * beats this card's own overlays, which all sit at `z-10`.
40
+ */
41
+ return (_jsxs(Div, { className: "relative h-full", children: [card, _jsx(Div, { className: "absolute right-2 top-2 z-20", children: renderOwnerActions() })] }));
35
42
  }
@@ -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
  }