@mohasinac/appkit 4.59.0 → 4.60.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 (531) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
  4. package/dist/_internal/server/features/brands/actions.js +78 -4
  5. package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
  6. package/dist/_internal/server/features/checkout/actions.js +668 -183
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
  8. package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
  9. package/dist/_internal/server/features/gateway-events/index.js +10 -0
  10. package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
  11. package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
  12. package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
  13. package/dist/_internal/server/features/gateway-events/redact.js +151 -0
  14. package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
  15. package/dist/_internal/server/features/grievance/actions.js +173 -0
  16. package/dist/_internal/server/features/grievance/index.d.ts +8 -0
  17. package/dist/_internal/server/features/grievance/index.js +8 -0
  18. package/dist/_internal/server/features/grouped/data.js +28 -3
  19. package/dist/_internal/server/features/lottery/actions.js +99 -4
  20. package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
  21. package/dist/_internal/server/features/order-completion/actions.js +336 -0
  22. package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
  23. package/dist/_internal/server/features/order-completion/index.js +7 -0
  24. package/dist/_internal/server/features/orders/actions.d.ts +13 -0
  25. package/dist/_internal/server/features/orders/actions.js +56 -2
  26. package/dist/_internal/server/features/orders/adapters.js +26 -0
  27. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
  28. package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
  29. package/dist/_internal/server/features/orders/index.d.ts +2 -1
  30. package/dist/_internal/server/features/orders/index.js +1 -1
  31. package/dist/_internal/server/features/orders/service.d.ts +60 -0
  32. package/dist/_internal/server/features/orders/service.js +74 -0
  33. package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
  34. package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
  35. package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
  36. package/dist/_internal/server/features/payment-cases/index.js +9 -0
  37. package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
  38. package/dist/_internal/server/features/payouts/actions.js +84 -10
  39. package/dist/_internal/server/features/points/adjust.d.ts +61 -0
  40. package/dist/_internal/server/features/points/adjust.js +121 -0
  41. package/dist/_internal/server/features/points/award.d.ts +65 -0
  42. package/dist/_internal/server/features/points/award.js +145 -0
  43. package/dist/_internal/server/features/price-index/market.d.ts +33 -0
  44. package/dist/_internal/server/features/price-index/market.js +54 -1
  45. package/dist/_internal/server/features/products/data.js +40 -22
  46. package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
  47. package/dist/_internal/server/features/protection-claims/actions.js +228 -0
  48. package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
  49. package/dist/_internal/server/features/protection-claims/index.js +8 -0
  50. package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
  51. package/dist/_internal/server/features/refunds/actions.js +259 -17
  52. package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
  53. package/dist/_internal/server/features/refunds/capacity.js +37 -0
  54. package/dist/_internal/server/features/reviews/actions.js +3 -2
  55. package/dist/_internal/server/features/search/index-build/sources.js +20 -1
  56. package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
  57. package/dist/_internal/server/features/shipping/reconcile.js +34 -3
  58. package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
  59. package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
  60. package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
  61. package/dist/_internal/server/features/stores/tabs.js +24 -4
  62. package/dist/_internal/server/functions/https.d.ts +31 -1
  63. package/dist/_internal/server/functions/https.js +52 -1
  64. package/dist/_internal/server/functions/scheduled.d.ts +72 -1
  65. package/dist/_internal/server/functions/scheduled.js +124 -1
  66. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
  67. package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
  68. package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
  69. package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
  70. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
  71. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
  72. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  73. package/dist/_internal/server/jobs/core/index.js +2 -0
  74. package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
  75. package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
  76. package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
  77. package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
  78. package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
  79. package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
  80. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
  81. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
  82. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
  83. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
  84. package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
  85. package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
  86. package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
  87. package/dist/_internal/server/jobs/handlers/index.js +5 -0
  88. package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
  89. package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
  90. package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
  91. package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
  92. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
  93. package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
  94. package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
  95. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  96. package/dist/_internal/shared/actions/action-registry.js +71 -0
  97. package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
  98. package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
  99. package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
  100. package/dist/_internal/shared/checkout/lanes.js +46 -0
  101. package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
  102. package/dist/_internal/shared/checkout/order-math.js +251 -3
  103. package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
  104. package/dist/_internal/shared/checkout/refundable-base.js +122 -0
  105. package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
  106. package/dist/_internal/shared/features/auctions/config.js +30 -1
  107. package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
  108. package/dist/_internal/shared/features/brands/schema.js +18 -0
  109. package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
  110. package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
  111. package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
  112. package/dist/_internal/shared/features/print/label-profiles.js +350 -0
  113. package/dist/_internal/shared/features/products/to-product-item.js +33 -5
  114. package/dist/_internal/shared/fees/calculator.d.ts +31 -2
  115. package/dist/_internal/shared/fees/calculator.js +103 -5
  116. package/dist/_internal/shared/fees/india-states.d.ts +195 -0
  117. package/dist/_internal/shared/fees/india-states.js +190 -0
  118. package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
  119. package/dist/_internal/shared/fees/payout-hold.js +74 -0
  120. package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
  121. package/dist/_internal/shared/obligations/deadlines.js +155 -0
  122. package/dist/_internal/shared/shipping/quote.d.ts +96 -0
  123. package/dist/_internal/shared/shipping/quote.js +67 -0
  124. package/dist/_internal/shared/time/business-days.d.ts +91 -0
  125. package/dist/_internal/shared/time/business-days.js +185 -0
  126. package/dist/cli/index.js +0 -2
  127. package/dist/client.d.ts +9 -4
  128. package/dist/client.js +26 -3
  129. package/dist/configs/tailwind.d.ts +0 -12
  130. package/dist/configs/tailwind.js +28 -15
  131. package/dist/constants/api-endpoints.d.ts +51 -9
  132. package/dist/constants/api-endpoints.js +23 -7
  133. package/dist/constants/index.d.ts +1 -1
  134. package/dist/constants/index.js +1 -1
  135. package/dist/contracts/index.d.ts +1 -1
  136. package/dist/contracts/payment.d.ts +131 -0
  137. package/dist/features/about/components/AboutView.js +1 -1
  138. package/dist/features/about/components/DeveloperView.js +5 -2
  139. package/dist/features/about/components/FAQPageView.js +2 -1
  140. package/dist/features/about/components/FeesView.js +2 -1
  141. package/dist/features/about/components/HelpPageView.js +2 -1
  142. package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
  143. package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
  144. package/dist/features/about/components/HowEmiWorksView.js +2 -1
  145. package/dist/features/about/components/HowOffersWorkView.js +2 -1
  146. package/dist/features/about/components/HowOrdersWorkView.js +2 -1
  147. package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
  148. package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
  149. package/dist/features/about/components/HowReviewsWorkView.js +2 -1
  150. package/dist/features/about/components/PolicyPageView.js +2 -1
  151. package/dist/features/about/components/ProseHero.d.ts +73 -0
  152. package/dist/features/about/components/ProseHero.js +18 -0
  153. package/dist/features/about/components/SecurityPrivacyView.js +2 -1
  154. package/dist/features/about/components/ShippingPolicyView.js +2 -1
  155. package/dist/features/about/components/TrackOrderView.js +2 -1
  156. package/dist/features/about/components/UnauthorizedView.js +1 -1
  157. package/dist/features/about/index.d.ts +2 -0
  158. package/dist/features/about/index.js +5 -0
  159. package/dist/features/account/actions/address-actions.d.ts +26 -2
  160. package/dist/features/account/actions/address-actions.js +25 -1
  161. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  162. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  163. package/dist/features/account/components/UserOrdersView.js +17 -0
  164. package/dist/features/account/components/UserSidebar.js +1 -1
  165. package/dist/features/account/hooks/useNotifications.js +29 -0
  166. package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
  167. package/dist/features/admin/components/AdminBeysView.js +2 -2
  168. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  169. package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
  170. package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
  171. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  172. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  173. package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
  174. package/dist/features/admin/components/AdminContactEditorView.js +1 -1
  175. package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
  176. package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
  177. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  178. package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
  179. package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
  180. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  181. package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
  182. package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
  183. package/dist/features/admin/components/AdminPartsView.js +2 -2
  184. package/dist/features/admin/components/AdminProductsView.js +75 -4
  185. package/dist/features/admin/components/AdminSidebar.js +2 -2
  186. package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
  187. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  188. package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
  189. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  190. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  191. package/dist/features/admin/components/AdminUsersView.js +72 -1
  192. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  193. package/dist/features/admin/components/AdminViewCards.js +4 -4
  194. package/dist/features/admin/components/DataListingView.d.ts +28 -0
  195. package/dist/features/admin/components/DataListingView.js +175 -14
  196. package/dist/features/admin/components/DataTable.d.ts +12 -1
  197. package/dist/features/admin/components/DataTable.js +3 -3
  198. package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
  199. package/dist/features/admin/components/OwnerQuickActions.js +60 -2
  200. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
  201. package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
  202. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
  203. package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
  204. package/dist/features/admin/components/index.d.ts +4 -0
  205. package/dist/features/admin/components/index.js +6 -0
  206. package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
  207. package/dist/features/admin/constants/filter-tabs.js +106 -0
  208. package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
  209. package/dist/features/admin/repository/site-settings.repository.js +82 -1
  210. package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
  211. package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
  212. package/dist/features/admin/schemas/firestore.js +5 -1
  213. package/dist/features/admin/schemas/site-settings-form.js +30 -1
  214. package/dist/features/auctions/actions/bid-actions.js +13 -1
  215. package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
  216. package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
  217. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
  218. package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
  219. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
  220. package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
  221. package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
  222. package/dist/features/audit-log/schemas/firestore.js +14 -0
  223. package/dist/features/auth/permissions/constants.d.ts +17 -1
  224. package/dist/features/auth/permissions/constants.js +18 -0
  225. package/dist/features/blog/components/BlogPostView.js +1 -1
  226. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  227. package/dist/features/categories/api/[id]/description-template/route.js +107 -1
  228. package/dist/features/categories/components/BrandDetailPageView.js +2 -2
  229. package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
  230. package/dist/features/categories/components/BundleDetailView.js +1 -1
  231. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  232. package/dist/features/categories/components/CategoryForm.js +12 -1
  233. package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
  234. package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
  235. package/dist/features/categories/repository/market-price-index.repository.js +105 -0
  236. package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
  237. package/dist/features/categories/repository/price-observation.repository.js +132 -0
  238. package/dist/features/events/components/EventSaleBanner.js +1 -1
  239. package/dist/features/events/components/EventStatusBadge.js +1 -1
  240. package/dist/features/faq/components/FAQAccordion.js +2 -1
  241. package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
  242. package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
  243. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  244. package/dist/features/filters/FilterFacetSection.js +1 -1
  245. package/dist/features/filters/SwitchFilter.js +1 -1
  246. package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
  247. package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
  248. package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
  249. package/dist/features/gateway-events/schemas/firestore.js +125 -0
  250. package/dist/features/gateway-events/schemas/index.d.ts +81 -0
  251. package/dist/features/gateway-events/schemas/index.js +79 -0
  252. package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
  253. package/dist/features/homepage/components/AnnouncementBar.js +37 -1
  254. package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
  255. package/dist/features/homepage/components/BrandsSection.js +1 -1
  256. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  257. package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
  258. package/dist/features/homepage/components/HeroCarousel.js +2 -2
  259. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  260. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
  261. package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
  262. package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
  263. package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
  264. package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
  265. package/dist/features/inventory/components/ReceiveStationView.js +413 -0
  266. package/dist/features/inventory/components/operator-message.d.ts +29 -0
  267. package/dist/features/inventory/components/operator-message.js +41 -0
  268. package/dist/features/inventory/index.d.ts +13 -0
  269. package/dist/features/inventory/index.js +28 -0
  270. package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
  271. package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
  272. package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
  273. package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
  274. package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
  275. package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
  276. package/dist/features/inventory/schemas/firestore.d.ts +197 -0
  277. package/dist/features/inventory/schemas/firestore.js +159 -0
  278. package/dist/features/layout/AppLayoutShell.d.ts +12 -1
  279. package/dist/features/layout/AppLayoutShell.js +25 -12
  280. package/dist/features/layout/AutoBreadcrumbs.js +162 -3
  281. package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
  282. package/dist/features/layout/BottomNavbar.d.ts +1 -1
  283. package/dist/features/layout/BottomNavbar.js +2 -2
  284. package/dist/features/media/MediaAvatar.js +1 -1
  285. package/dist/features/media/MediaImage.d.ts +24 -4
  286. package/dist/features/media/MediaImage.js +44 -4
  287. package/dist/features/media/MediaLightbox.js +1 -1
  288. package/dist/features/media/modals/ImageCropModal.js +1 -1
  289. package/dist/features/media/modals/ImageEditor.js +1 -1
  290. package/dist/features/media/upload/MediaUploadField.js +91 -3
  291. package/dist/features/obligations/constants/policies.d.ts +92 -0
  292. package/dist/features/obligations/constants/policies.js +378 -0
  293. package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
  294. package/dist/features/obligations/repository/obligations.repository.js +158 -0
  295. package/dist/features/obligations/schemas/firestore.d.ts +160 -0
  296. package/dist/features/obligations/schemas/firestore.js +87 -0
  297. package/dist/features/orders/actions/order-actions.d.ts +43 -4
  298. package/dist/features/orders/actions/order-actions.js +60 -3
  299. package/dist/features/orders/actions/refund-actions.js +70 -10
  300. package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
  301. package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
  302. package/dist/features/orders/components/OrdersList.js +2 -2
  303. package/dist/features/orders/components/RefundHistoryTable.js +3 -1
  304. package/dist/features/orders/components/index.d.ts +2 -0
  305. package/dist/features/orders/components/index.js +6 -0
  306. package/dist/features/orders/constants/transitions.d.ts +59 -0
  307. package/dist/features/orders/constants/transitions.js +104 -0
  308. package/dist/features/orders/repository/orders.repository.d.ts +109 -6
  309. package/dist/features/orders/repository/orders.repository.js +464 -21
  310. package/dist/features/orders/schemas/firestore.d.ts +270 -0
  311. package/dist/features/orders/schemas/firestore.js +24 -0
  312. package/dist/features/orders/schemas/index.d.ts +61 -0
  313. package/dist/features/orders/schemas/index.js +22 -0
  314. package/dist/features/orders/types/index.d.ts +79 -0
  315. package/dist/features/parts/components/PartsIndexPageView.js +8 -1
  316. package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
  317. package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
  318. package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
  319. package/dist/features/payment-cases/schemas/firestore.js +105 -0
  320. package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
  321. package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
  322. package/dist/features/payments/repository/payout.repository.d.ts +0 -1
  323. package/dist/features/payments/repository/payout.repository.js +0 -1
  324. package/dist/features/points/repository/points.repository.d.ts +122 -0
  325. package/dist/features/points/repository/points.repository.js +304 -1
  326. package/dist/features/points/schemas/adjust-form.d.ts +58 -0
  327. package/dist/features/points/schemas/adjust-form.js +63 -0
  328. package/dist/features/points/schemas/firestore.d.ts +108 -1
  329. package/dist/features/points/schemas/firestore.js +84 -0
  330. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  331. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  332. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  333. package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
  334. package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
  335. package/dist/features/price-index/market-stats.d.ts +82 -0
  336. package/dist/features/price-index/market-stats.js +230 -0
  337. package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
  338. package/dist/features/print/components/BoxLabelPrintView.js +66 -0
  339. package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
  340. package/dist/features/print/components/LabelProfilePicker.js +38 -0
  341. package/dist/features/print/components/labels.d.ts +117 -0
  342. package/dist/features/print/components/labels.js +258 -0
  343. package/dist/features/print/components/labels.style.css +46 -0
  344. package/dist/features/print/index.d.ts +6 -0
  345. package/dist/features/print/index.js +16 -0
  346. package/dist/features/products/columns/productTableColumns.js +5 -4
  347. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  348. package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
  349. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  350. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  351. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  352. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  353. package/dist/features/products/components/ProductForm.js +34 -23
  354. package/dist/features/products/components/ProductGrid.js +86 -62
  355. package/dist/features/products/constants/card-badges.d.ts +97 -0
  356. package/dist/features/products/constants/card-badges.js +100 -0
  357. package/dist/features/products/repository/products.repository.js +34 -1
  358. package/dist/features/products/schemas/firestore.d.ts +46 -3
  359. package/dist/features/products/schemas/firestore.js +21 -0
  360. package/dist/features/promotions/components/PromotionsView.js +1 -1
  361. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
  362. package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
  363. package/dist/features/promotions/schemas/firestore.js +30 -3
  364. package/dist/features/promotions/schemas/index.d.ts +6 -6
  365. package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
  366. package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
  367. package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
  368. package/dist/features/protection-claims/schemas/firestore.js +130 -0
  369. package/dist/features/reviews/actions/review-actions.js +3 -2
  370. package/dist/features/reviews/components/ReviewModal.js +1 -1
  371. package/dist/features/reviews/components/ReviewsList.js +1 -1
  372. package/dist/features/reviews/schemas/firestore.d.ts +20 -1
  373. package/dist/features/scams/constants/scam-types.d.ts +2 -0
  374. package/dist/features/scams/constants/scam-types.js +18 -0
  375. package/dist/features/scams/schemas/report-form.d.ts +3 -3
  376. package/dist/features/scams/schemas/report-form.js +26 -1
  377. package/dist/features/search/components/Search.js +9 -9
  378. package/dist/features/search/components/SearchResultsClient.js +18 -3
  379. package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
  380. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  381. package/dist/features/seller/actions/offer-actions.js +16 -1
  382. package/dist/features/seller/actions/seller-actions.js +14 -3
  383. package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
  384. package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
  385. package/dist/features/seller/components/PrintCenterView.js +41 -30
  386. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  387. package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
  388. package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
  389. package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
  390. package/dist/features/seller/components/SellerProductShell.js +123 -13
  391. package/dist/features/seller/components/SellerReviewsView.js +4 -2
  392. package/dist/features/seller/components/SellerSidebar.js +1 -1
  393. package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
  394. package/dist/features/seller/schemas/coupon-form.js +27 -0
  395. package/dist/features/seller/schemas/product-form.d.ts +3 -0
  396. package/dist/features/seller/schemas/product-form.js +5 -0
  397. package/dist/features/shell/FormShell.js +2 -2
  398. package/dist/features/shell/QuickFormDrawer.js +14 -0
  399. package/dist/features/shell/SectionForm.d.ts +19 -0
  400. package/dist/features/shell/SectionForm.js +133 -9
  401. package/dist/features/shell/SectionForm.style.css +54 -0
  402. package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
  403. package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
  404. package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
  405. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  406. package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
  407. package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
  408. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  409. package/dist/features/stores/components/StoreHeader.js +1 -1
  410. package/dist/features/support/schemas/firestore.d.ts +73 -2
  411. package/dist/features/support/schemas/firestore.js +59 -0
  412. package/dist/index.d.ts +16 -24
  413. package/dist/index.js +38 -47
  414. package/dist/next/ErrorBoundary.js +1 -1
  415. package/dist/next/routing/route-map.d.ts +24 -0
  416. package/dist/next/routing/route-map.js +40 -0
  417. package/dist/providers/payment-manual/index.d.ts +15 -1
  418. package/dist/providers/payment-manual/index.js +37 -0
  419. package/dist/providers/payment-phonepe/index.d.ts +14 -1
  420. package/dist/providers/payment-phonepe/index.js +36 -0
  421. package/dist/providers/payment-razorpay/index.d.ts +44 -1
  422. package/dist/providers/payment-razorpay/index.js +99 -0
  423. package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
  424. package/dist/providers/shipping-shiprocket/index.js +18 -0
  425. package/dist/schemas/registry.d.ts +44 -11
  426. package/dist/server-entry.d.ts +2 -0
  427. package/dist/server-entry.js +27 -0
  428. package/dist/server.d.ts +42 -0
  429. package/dist/server.js +93 -0
  430. package/dist/styles.css +418 -149
  431. package/dist/tailwind-utilities.css +1 -1
  432. package/dist/tokens/themes/default-dark.js +15 -12
  433. package/dist/tokens/themes/default-light.js +14 -11
  434. package/dist/tokens/tokens.css +106 -23
  435. package/dist/ui/DataTable.style.css +4 -4
  436. package/dist/ui/columns/cell-renderers.d.ts +20 -0
  437. package/dist/ui/columns/cell-renderers.js +26 -1
  438. package/dist/ui/columns/index.d.ts +1 -1
  439. package/dist/ui/columns/index.js +4 -1
  440. package/dist/ui/components/Badge.style.css +1 -1
  441. package/dist/ui/components/Breadcrumb.js +19 -1
  442. package/dist/ui/components/BulkActionBar.style.css +4 -4
  443. package/dist/ui/components/Button.style.css +1 -1
  444. package/dist/ui/components/Checkbox.style.css +1 -1
  445. package/dist/ui/components/FallbackShell.js +1 -1
  446. package/dist/ui/components/Form.d.ts +14 -1
  447. package/dist/ui/components/Form.js +2 -2
  448. package/dist/ui/components/FormField.js +18 -4
  449. package/dist/ui/components/FormField.style.css +43 -3
  450. package/dist/ui/components/GatedPrice.style.css +1 -1
  451. package/dist/ui/components/IconButton.style.css +1 -1
  452. package/dist/ui/components/Input.js +22 -1
  453. package/dist/ui/components/Input.style.css +38 -22
  454. package/dist/ui/components/ListingFilterDrawer.js +1 -1
  455. package/dist/ui/components/ListingToolbar.js +1 -1
  456. package/dist/ui/components/PaginatedSelect.js +0 -0
  457. package/dist/ui/components/Radio.style.css +1 -1
  458. package/dist/ui/components/Repeater.d.ts +53 -0
  459. package/dist/ui/components/Repeater.js +7 -0
  460. package/dist/ui/components/RichTextEditor.d.ts +20 -1
  461. package/dist/ui/components/RichTextEditor.js +23 -2
  462. package/dist/ui/components/Select.style.css +7 -21
  463. package/dist/ui/components/Semantic.js +27 -3
  464. package/dist/ui/components/TagInput.style.css +4 -9
  465. package/dist/ui/components/Textarea.style.css +4 -18
  466. package/dist/ui/components/Toggle.style.css +1 -1
  467. package/dist/ui/components/Typography.d.ts +3 -3
  468. package/dist/ui/components/Typography.js +15 -15
  469. package/dist/ui/components/ViewToggle.style.css +4 -4
  470. package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
  471. package/dist/ui/forms/AddAnotherToggle.js +56 -0
  472. package/dist/ui/forms/ColorPickerField.js +15 -2
  473. package/dist/ui/forms/FieldCheckbox.js +15 -2
  474. package/dist/ui/forms/FieldInput.d.ts +22 -1
  475. package/dist/ui/forms/FieldInput.js +19 -5
  476. package/dist/ui/forms/FieldLabel.d.ts +35 -0
  477. package/dist/ui/forms/FieldLabel.js +70 -0
  478. package/dist/ui/forms/FieldSelect.d.ts +4 -1
  479. package/dist/ui/forms/FieldSelect.js +27 -5
  480. package/dist/ui/forms/FieldTextarea.d.ts +4 -1
  481. package/dist/ui/forms/FieldTextarea.js +22 -5
  482. package/dist/ui/forms/FormShell.d.ts +17 -1
  483. package/dist/ui/forms/FormShell.js +36 -2
  484. package/dist/ui/forms/FormShell.style.css +41 -33
  485. package/dist/ui/forms/index.d.ts +5 -1
  486. package/dist/ui/forms/index.js +2 -0
  487. package/dist/ui/icons/icon-registry.d.ts +1 -0
  488. package/dist/ui/icons/icon-registry.js +17 -1
  489. package/dist/utils/id-generators.d.ts +80 -0
  490. package/dist/utils/id-generators.js +93 -0
  491. package/dist/utils/search-txt-builders.d.ts +12 -0
  492. package/dist/utils/search-txt-builders.js +17 -0
  493. package/package.json +1 -1
  494. package/scripts/seed-cli.mjs +106 -5
  495. package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
  496. package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
  497. package/dist/features/loyalty/api/balance/route.d.ts +0 -12
  498. package/dist/features/loyalty/api/balance/route.js +0 -41
  499. package/dist/features/loyalty/api/earn/route.d.ts +0 -12
  500. package/dist/features/loyalty/api/earn/route.js +0 -38
  501. package/dist/features/loyalty/api/history/route.d.ts +0 -12
  502. package/dist/features/loyalty/api/history/route.js +0 -58
  503. package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
  504. package/dist/features/loyalty/api/redeem/route.js +0 -38
  505. package/dist/features/loyalty/columns/index.d.ts +0 -17
  506. package/dist/features/loyalty/columns/index.js +0 -40
  507. package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
  508. package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
  509. package/dist/features/loyalty/components/index.d.ts +0 -1
  510. package/dist/features/loyalty/components/index.js +0 -1
  511. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
  512. package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
  513. package/dist/features/loyalty/index.d.ts +0 -6
  514. package/dist/features/loyalty/index.js +0 -6
  515. package/dist/features/loyalty/manifest.d.ts +0 -2
  516. package/dist/features/loyalty/manifest.js +0 -16
  517. package/dist/features/loyalty/messages/en.json +0 -19
  518. package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
  519. package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
  520. package/dist/features/loyalty/schemas/index.d.ts +0 -85
  521. package/dist/features/loyalty/schemas/index.js +0 -40
  522. package/dist/features/loyalty/server.d.ts +0 -6
  523. package/dist/features/loyalty/server.js +0 -6
  524. package/dist/features/loyalty/types/index.d.ts +0 -31
  525. package/dist/features/loyalty/types/index.js +0 -1
  526. package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
  527. package/dist/features/loyalty/types/loyalty-config.js +0 -1
  528. package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
  529. package/dist/features/loyalty/types/loyalty-math.js +0 -27
  530. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
  531. package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
@@ -9,9 +9,15 @@ import { PRODUCT_MAX_IMAGES, PRODUCT_MAX_VIDEOS, PRODUCT_IMAGE_INDEX_MAX, } from
9
9
  import { CustomSectionsEditor } from "./CustomSectionsEditor.js";
10
10
  import { SublistingCategorySelect } from "./SublistingCategorySelect.js";
11
11
  import { ProductFeaturesSelector } from "./ProductFeaturesSelector.js";
12
+ // The one `ProductImage[] | string[]` → `string[]` normaliser. Value import,
13
+ // not a type: the gallery needs it at runtime (see `galleryImages` below).
14
+ import { productImageUrls } from "../schemas/firestore.js";
12
15
  import { ImageUpload, MediaUploadField, MediaUploadList, } from "../../media/index.js";
13
16
  import { useMediaUpload } from "../../media/index.js";
14
17
  import { resolveDate } from "../../../utils/date.formatter.js";
18
+ // Read-only here: the inputs that WROTE this field are gone — see the Physical
19
+ // Inventory block for why three free-text boxes could not be made consistent.
20
+ import { formatPhysicalLocation } from "../../../utils/physical-location.js";
15
21
  import { DEFAULT_MIN_OFFER_PERCENT } from "../../../_internal/shared/features/offers/config.js";
16
22
  import { normalizeRichTextHtml } from "../../../utils/string.formatter.js";
17
23
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags.js";
@@ -68,13 +74,36 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
68
74
  const update = (partial) => {
69
75
  onChange({ ...product, ...partial });
70
76
  };
77
+ /*
78
+ * The legacy free-text location, if this listing carries one. Shown so the
79
+ * value is not invisible while it still exists; there is no input for it any
80
+ * more. `formatPhysicalLocation` returns `undefined` when all three parts are
81
+ * blank, which is what distinguishes "never set" from "set to nothing".
82
+ */
83
+ const existingLocation = formatPhysicalLocation(product.physicalLocation);
71
84
  /*
72
85
  * One control for both media kinds — see the matching comment in
73
- * SellerProductShell's StepMedia. The document keeps `images: string[]` plus
74
- * a single `video`; this seeds from both and splits again on write.
86
+ * SellerProductShell's StepMedia. This seeds from `images` + `video` and
87
+ * splits them again on write.
88
+ *
89
+ * 🛑 `images` IS NOT `string[]` AT RUNTIME, whatever the type says. This
90
+ * comment used to assert "the document keeps `images: string[]`" and the
91
+ * line below mapped each entry as a URL — but `ProductDocument.images` has
92
+ * been `ProductImage[]` (objects) since A6, and the editors seed this form
93
+ * by CASTING the raw API document to `Partial<ProductItem>`
94
+ * (`AdminProductEditorView:231`). The cast is what makes the compiler agree
95
+ * with a shape the data does not have.
96
+ *
97
+ * The result was `{ url: { url: "/media/…" }, type: "image" }`, so the
98
+ * gallery tile handed `MediaImage` an object, `resolveMediaUrl` returned
99
+ * undefined for a non-string, and the tile rendered the 📦 placeholder with
100
+ * no network request at all.
101
+ *
102
+ * `productImageUrls` is the one normaliser and accepts both shapes, so this
103
+ * is correct whether the seeder passed documents or legacy strings.
75
104
  */
76
105
  const galleryImages = [
77
- ...(product.images ?? []).map((url) => ({ url, type: "image" })),
106
+ ...productImageUrls(product.images).map((url) => ({ url, type: "image" })),
78
107
  ...(product.video?.url
79
108
  ? [{
80
109
  url: product.video.url,
@@ -123,7 +152,7 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
123
152
  content: product.description || "",
124
153
  onChange: (value) => update({ description: value }),
125
154
  editable: !isReadonly,
126
- }) })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: t("formDescription") }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(product.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, placeholder: "Enter product description" }), _jsx(Text, { size: "xs", color: "muted", children: "Rich text is supported for product descriptions." })] })), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsx(_Fragment, { children: renderCategorySelector({
155
+ }) })) : (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", children: t("formDescription") }), _jsx(RichTextEditor, { ariaLabel: t("formDescription"), value: normalizeRichTextHtml(product.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, placeholder: "Enter product description" }), _jsx(Text, { size: "xs", color: "muted", children: "Rich text is supported for product descriptions." })] })), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsx(_Fragment, { children: renderCategorySelector({
127
156
  label: t("formCategory"),
128
157
  value: (product.categorySlugs?.[0] ?? product.category) || "",
129
158
  onChange: (value) => update({ categorySlugs: value ? [value] : [] }),
@@ -284,23 +313,5 @@ canEditTax = false, renderDescriptionEditor, renderCategorySelector, renderPrice
284
313
  disabled: isReadonly,
285
314
  }), !isAuctionListing(product) && renderGroupSettings?.(product), _jsx(ProductFeaturesSelector, { value: product.features ?? [], onChange: (features) => update({ features }), productType: LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE[normalizeListingType(product)], storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Custom Sections" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsxs(Div, { children: [_jsx(Toggle, { checked: !isFinalSale(product), onChange: (checked) => update({ finalSale: !checked }), disabled: isReadonly, label: "Accept change-of-mind returns" }), _jsx(Text, { className: "mt-1", size: "xs", color: "muted", children: isFinalSale(product)
286
315
  ? "Final sale (default). Not-received, damaged, wrong-item, not-as-described and counterfeit claims are still accepted."
287
- : "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Assign a storage location for warehouse or shelf organisation. Used by the Print & Label Center." }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: "physicalLocation.zone", label: "Zone", type: "text", placeholder: "e.g. A", value: product.physicalLocation?.zone ?? "", onChange: (v) => update({
288
- physicalLocation: {
289
- zone: v,
290
- shelf: product.physicalLocation?.shelf ?? "",
291
- bin: product.physicalLocation?.bin ?? "",
292
- },
293
- }), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.shelf", label: "Shelf", type: "text", placeholder: "e.g. 3", value: product.physicalLocation?.shelf ?? "", onChange: (v) => update({
294
- physicalLocation: {
295
- zone: product.physicalLocation?.zone ?? "",
296
- shelf: v,
297
- bin: product.physicalLocation?.bin ?? "",
298
- },
299
- }), disabled: isReadonly }), _jsx(FormField, { name: "physicalLocation.bin", label: "Bin", type: "text", placeholder: "e.g. Blue", value: product.physicalLocation?.bin ?? "", onChange: (v) => update({
300
- physicalLocation: {
301
- zone: product.physicalLocation?.zone ?? "",
302
- shelf: product.physicalLocation?.shelf ?? "",
303
- bin: v,
304
- },
305
- }), disabled: isReadonly })] })] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
316
+ : "Buyers can return this listing within the platform return window for any reason." })] }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), existingLocation ? (_jsxs(Text, { className: "mb-3", color: "muted", size: "xs", children: ["Legacy location on this listing: ", _jsx("strong", { children: existingLocation }), ". It is still printed on labels, but it is free text with no box behind it \u2014 place this item in a storage box to replace it."] })) : (_jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Where this item is stored is recorded by placing it in a storage box, not typed here \u2014 a placement has a quantity, and one product can sit in two boxes. Use the receive station or the box's own page." }))] }), product.storeName && (_jsx(FormField, { name: "storeName", label: t("formSeller"), type: "text", value: product.storeName, onChange: () => { }, disabled: true }))] }));
306
317
  }
@@ -1,9 +1,7 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
4
  import Link from "next/link.js";
5
- import { useRouter } from "next/navigation.js";
6
- import { ROUTES } from "../../../next/routing/route-map.js";
7
5
  import { BaseListingCard, Button, Div, GatedPrice, Grid, PriceDisplay, PricesOnly, Row, Span, Stack, Text } from "../../../ui/index.js";
8
6
  import { MediaImage } from "../../media/MediaImage.js";
9
7
  import { formatCurrency } from "../../../utils/number.formatter.js";
@@ -11,13 +9,14 @@ import { getDefaultCurrency } from "../../../core/baseline-resolver.js";
11
9
  import { useLongPress } from "../../../react/hooks/useLongPress.js";
12
10
  import { CardStoreLine } from "./CardStoreLine.js";
13
11
  import { isFinalSale } from "../constants/final-sale.js";
12
+ import { CARD_BADGE, resolveCardBadges } from "../constants/card-badges.js";
14
13
  import { FeatureBadgeList } from "./FeatureBadge.js";
15
14
  import { useProductFeatures } from "./ProductFeaturesContext.js";
16
15
  import { PRODUCT_FEATURE_CARD_MAX_VISIBLE } from "../constants/product-features.constants.js";
17
16
  import { normalizeListingType } from "../utils/listing-type.js";
18
17
  import { pluginFor } from "../../../_internal/shared/listing-types/_registry.js";
19
18
  import { WishlistHeartButton } from "../../wishlist/components/WishlistHeartButton.js";
20
- import { ProductOwnerQuickActions } from "./ProductOwnerQuickActions.js";
19
+ import { RecordOwnerQuickActions } from "../../admin/components/RecordOwnerQuickActions.js";
21
20
  const __P = {
22
21
  p3: "p-[var(--appkit-space-3)]",
23
22
  };
@@ -25,22 +24,6 @@ const __O = {
25
24
  hidden: "overflow-hidden",
26
25
  };
27
26
  const CLS_BADGE_NEW = "rounded-full bg-error-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] text-error-on-solid shadow-sm";
28
- const CLS_BADGE_SALE = "rounded-full bg-success-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-success-on-solid shadow-sm";
29
- const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
30
- const CLS_BADGE_LIMITED = "rounded-full bg-info-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-info-on-solid shadow-sm";
31
- // Overlay badge sitting on the product photo, so the pairing is `-solid` +
32
- // `-on-solid` (Root Cause #67) — never `text-white` on a status colour, which
33
- // inverts with the theme. `warning` because success/info/error are each already
34
- // taken by a sibling pill in this same stack.
35
- const CLS_BADGE_OFFERABLE = "rounded-full bg-warning-solid px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-warning-on-solid shadow-sm";
36
- /*
37
- * Final sale. A fixed dark scrim with white ink, following CLS_BADGE_TRENDING's
38
- * precedent rather than a `-surface` token: this sits on top of the product
39
- * PHOTO, where a theme-relative tint is a near-white fill that vanishes against
40
- * a light image (Root Cause #67). Not `warning-solid`, which "Taking Offers"
41
- * already owns — two amber pills stacked would read as one control.
42
- */
43
- const CLS_BADGE_FINAL_SALE = "rounded-full bg-zinc-900/90 px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] font-bold text-white shadow-sm";
44
27
  const CLS_STAR = "text-[11px] text-warning";
45
28
  const CLS_DISCOUNT_TEXT = "mt-1 text-[11px] font-semibold text-error";
46
29
  const CLS_DISCOUNT_TEXT_BARE = "text-[10px] text-error";
@@ -51,11 +34,65 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
51
34
  ? Math.round(((product.originalPrice - product.price) / product.originalPrice) *
52
35
  100)
53
36
  : null;
54
- const router = useRouter();
55
37
  const listingPlugin = pluginFor(normalizeListingType(product));
56
38
  const typeBadge = listingPlugin.badge
57
39
  ? { label: listingPlugin.badge.label, cls: listingPlugin.badge.className }
58
40
  : null;
41
+ /*
42
+ * The badge corner, resolved once. Two candidates, ordered and capped by
43
+ * `card-badges.ts` — see the stack below for what the other five badges were
44
+ * and where they went.
45
+ */
46
+ const cardBadges = resolveCardBadges([
47
+ discount
48
+ ? {
49
+ id: CARD_BADGE.DISCOUNT,
50
+ label: `-${discount}%`,
51
+ className: CLS_BADGE_NEW,
52
+ title: `${discount}% off the original price`,
53
+ }
54
+ : null,
55
+ typeBadge
56
+ ? {
57
+ id: CARD_BADGE.LISTING_TYPE,
58
+ label: typeBadge.label,
59
+ // The plugin's own class, plus the shared pill geometry the inline
60
+ // version used to spell out at the call site.
61
+ className: `rounded-full px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[10px] shadow-sm ${typeBadge.cls}`,
62
+ }
63
+ : null,
64
+ ]);
65
+ /*
66
+ * The five demoted badges, as plain words on the metadata line.
67
+ *
68
+ * 🛑 DEMOTED, NOT DELETED. Each states something true that a buyer needs —
69
+ * "Final Sale" is a refund-policy warning and dropping it silently would be
70
+ * a worse defect than crowding the image. But a 10px pill over a photograph
71
+ * is the wrong place to say it: a shopper scanning a grid cannot act on any
72
+ * of them, and five of them hid the two they could act on.
73
+ *
74
+ * `isFinalSale(product)` rather than `product.finalSale` — absent MEANS
75
+ * final sale, so reading the raw field would leave every listing created
76
+ * before it existed unlabelled while still being final sale at the refund
77
+ * gate.
78
+ */
79
+ const cardNotes = [
80
+ product.partOfBundleIds && product.partOfBundleIds.length > 0
81
+ ? product.partOfBundleTitles?.[0]
82
+ ? `In bundle: ${product.partOfBundleTitles[0]}`
83
+ : "In a bundle"
84
+ : null,
85
+ product.groupId
86
+ ? product.isGroupParent
87
+ ? "Set parent"
88
+ : product.groupTitle
89
+ ? `Part of set: ${product.groupTitle}`
90
+ : "Part of a set"
91
+ : null,
92
+ product.allowOffers ? "Taking offers" : null,
93
+ isFinalSale(product) ? "Final sale" : null,
94
+ product.sublistingCategoryId ? "Has variants" : null,
95
+ ].filter((n) => n != null);
59
96
  const longPress = useLongPress(() => onSelect?.(product.id));
60
97
  const featuresList = useProductFeatures();
61
98
  const handleCardClick = selectionMode
@@ -78,36 +115,13 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
78
115
  className,
79
116
  ]
80
117
  .filter(Boolean)
81
- .join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode && !isSelected && (_jsxs(_Fragment, { children: [discount && (_jsxs(Span, { weight: "bold", className: CLS_BADGE_NEW, children: ["-", discount, "%"] })), typeBadge && (_jsx(Span, { weight: "bold", className: `text-[10px] ${typeBadge.cls}`, rounded: "full", padding: "pill-xs", shadow: "sm", children: typeBadge.label })), product.partOfBundleIds && product.partOfBundleIds.length > 0 && (_jsx(Span, { className: CLS_BADGE_SALE, title: product.partOfBundleTitles && product.partOfBundleTitles.length > 0
82
- ? `In bundle: ${product.partOfBundleTitles[0]}`
83
- : "In a bundle", children: "Bundled" })), product.groupId && (_jsx(Span, { className: CLS_BADGE_TRENDING, title: product.groupTitle ? `Part of set: ${product.groupTitle}` : "Part of a set", children: product.isGroupParent ? "Set Parent" : "In Set" })), product.allowOffers && (_jsx(Span, { className: CLS_BADGE_OFFERABLE, title: "The seller accepts offers on this listing", children: "Taking Offers" })), isFinalSale(product) && (_jsx(Span, { className: CLS_BADGE_FINAL_SALE, title: "Final sale \u2014 no change-of-mind returns. Not-received, damaged, wrong-item, not-as-described and counterfeit claims are still accepted.", children: "Final Sale" })), product.sublistingCategoryId && (_jsx(Span, { className: CLS_BADGE_LIMITED, title: "Has variants or sub-listings", children: "Has Variants" }))] })) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(WishlistHeartButton, { size: "card", variant: "overlay", inWishlist: !!isWishlisted, addLabel: "Add to wishlist", removeLabel: "Remove from wishlist", className: "absolute right-2 top-2", onToggle: (e) => {
118
+ .join(" "), children: [_jsxs(Div, { className: `relative ${__O.hidden} aspect-square`, surface: "subtle", children: [product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "card", className: "transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { surface: "muted", className: "h-full w-full", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-30", size: "4xl", children: "\uD83D\uDECD\uFE0F" }) })), onSelect && (_jsx(BaseListingCard.Checkbox, { selected: isSelected, onSelect: (e) => { e.preventDefault(); onSelect(product.id); }, label: isSelected ? "Deselect" : "Select", position: "top-2 left-2", className: selectionMode || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsx(Stack, { className: `absolute top-2 ${onSelect ? "left-10" : "left-2"}`, gap: "xs", children: !selectionMode &&
119
+ !isSelected &&
120
+ cardBadges.map((b) => (_jsx(Span, { weight: "bold", className: b.className, title: b.title, children: b.label }, b.id))) }), _jsx(BaseListingCard.IconOverlay, { groupIcon: product.groupIcon, sublistingIcon: product.sublistingIcon, position: "bottom-2 right-2" }), onAddToWishlist && (_jsx(WishlistHeartButton, { size: "card", variant: "overlay", inWishlist: !!isWishlisted, addLabel: "Add to wishlist", removeLabel: "Remove from wishlist", className: "absolute right-2 top-2", onToggle: (e) => {
84
121
  e.stopPropagation();
85
122
  e.preventDefault();
86
123
  onAddToWishlist(product.id);
87
- } }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), (() => {
88
- const categories = Array.isArray(product.categoryNames)
89
- ? product.categoryNames.filter(Boolean)
90
- : product.categoryName
91
- ? [product.categoryName]
92
- : [];
93
- if (!categories.length && !product.brand)
94
- return null;
95
- const MAX_CHIPS = 3;
96
- const visible = categories.slice(0, MAX_CHIPS);
97
- const overflow = Math.max(0, categories.length - MAX_CHIPS);
98
- return (_jsxs(Row, { className: "mt-1", gap: "xs", wrap: true, children: [visible.map((name) => (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", className: "gap-[0.125rem] text-[10px]", rounded: "full", surface: "subtle", color: "muted", children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 7a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2H5a2 2 0 01-2-2V7z" }) }), name] }, name))), overflow > 0 && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", weight: "medium", className: "text-[10px]", rounded: "full", surface: "subtle", color: "muted", title: categories.slice(MAX_CHIPS).join(", "), children: ["+", overflow] })), product.brand && (_jsxs(Span, { padding: "pill-2xs", layout: "inline-flex", gap: "2xs", className: `text-[10px] ${product.brandSlug ? "cursor-pointer hover:underline" : ""}`, rounded: "full", surface: "subtle", color: "muted", ...(product.brandSlug
99
- ? {
100
- onClick: (e) => {
101
- // The whole card is already wrapped in a <Link> — a nested
102
- // <a> here would be invalid HTML, so this navigates via
103
- // stopPropagation + router.push instead of a real Link.
104
- e.preventDefault();
105
- e.stopPropagation();
106
- router.push(String(ROUTES.PUBLIC.BRAND_DETAIL(product.brandSlug)));
107
- },
108
- }
109
- : {}), children: [_jsx("svg", { className: "h-2.5 w-2.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" }) }), product.brand] }))] }));
110
- })(), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.rating !== undefined && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsx(PriceDisplay, { amount: product.price, originalAmount: product.originalPrice, currency: getDefaultCurrency(), variant: "compact" }), (() => {
124
+ } }))] }), _jsxs(Stack, { className: `flex-1 ${__P.p3} pt-[0.625rem]`, children: [_jsx(Text, { className: `leading-snug`, truncate: 2, size: "sm", weight: "semibold", color: "primary", children: product.title }), product.brand && (_jsx(Text, { className: "mt-1", size: "xs", color: "muted", truncate: 1, children: product.brand })), cardNotes.length > 0 && (_jsx(Text, { className: "mt-1", size: "xs", color: "muted", truncate: 1, title: cardNotes.join(" · "), children: cardNotes.join(" · ") })), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId }), product.rating !== undefined && (product.reviewCount ?? 0) > 0 && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsx(PriceDisplay, { amount: product.price, originalAmount: product.originalPrice, currency: getDefaultCurrency(), variant: "compact" }), (() => {
111
125
  const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
112
126
  if (stock === undefined || !listingPlugin.showsStockQuantity)
113
127
  return null;
@@ -116,15 +130,15 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
116
130
  }
117
131
  const low = stock <= 5;
118
132
  return (_jsx(Text, { className: `mt-1 text-[11px] ${low ? "text-warning" : "text-[var(--appkit-color-text-faint)]"}`, weight: "medium", children: low ? `Only ${stock} left` : `${stock} in stock` }));
119
- })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { layout: "grid", gap: "1", className: `mt-2 ${onBuyNow && onAddToCart ? "grid-cols-2" : "grid-cols-1"}`, children: [onBuyNow && (_jsxs(Button, { gap: "sm", type: "button", variant: "primary", size: "sm", onClick: (e) => {
133
+ })(), featuresList && product.features && product.features.length > 0 && (_jsx(FeatureBadgeList, { productFeatureIds: product.features, features: featuresList, maxVisible: PRODUCT_FEATURE_CARD_MAX_VISIBLE, className: "mt-2" })), (onAddToCart || onBuyNow) && (_jsxs(Div, { layout: "grid", gap: "1", className: `mt-2 ${onBuyNow && onAddToCart ? "grid-cols-2" : "grid-cols-1"}`, children: [onBuyNow && (_jsx(Button, { type: "button", variant: "primary", size: "sm", onClick: (e) => {
120
134
  e.stopPropagation();
121
135
  e.preventDefault();
122
136
  onBuyNow(product);
123
- }, className: "justify-center text-[length:var(--appkit-text-xs)] btn-glow", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2.5, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) }), "Buy Now"] })), onAddToCart && (_jsxs(Button, { gap: "sm", type: "button", variant: "outline", size: "sm", onClick: (e) => {
137
+ }, className: "justify-center text-[length:var(--appkit-text-xs)] btn-glow", children: "Buy Now" })), onAddToCart && (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: (e) => {
124
138
  e.stopPropagation();
125
139
  e.preventDefault();
126
140
  onAddToCart(product);
127
- }, className: "justify-center text-[length:var(--appkit-text-xs)]", children: [_jsx("svg", { className: "h-3.5 w-3.5 flex-shrink-0", fill: "none", stroke: "currentColor", strokeWidth: 2, viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 3h2l.4 2M7 13h10l4-8H5.4m1.6 8L5 3H3m4 10v7a1 1 0 001 1h8a1 1 0 001-1v-7M9 21h6" }) }), "Cart"] }))] }))] })] })] }));
141
+ }, className: "justify-center text-[length:var(--appkit-text-xs)]", children: "Cart" }))] }))] })] })] }));
128
142
  const wrapped = href && !selectionMode ? (_jsx(Link, { href: href, className: "block h-full", children: cardBody })) : (cardBody);
129
143
  /*
130
144
  * 🛑 Default, not opt-in — nothing in either repo ever passed
@@ -138,29 +152,39 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
138
152
  * editor. `storeId` is the store SLUG, which is what `SessionUser.storeId`
139
153
  * holds — the component renders nothing for anyone who owns neither.
140
154
  */
141
- const ownerActions = renderOwnerActions ? (renderOwnerActions()) : product.id ? (_jsx(ProductOwnerQuickActions, { productId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined, recordLabel: product.title, placement: "card" })) : null;
155
+ const ownerActions = renderOwnerActions ? (renderOwnerActions()) : product.id ? (_jsx(RecordOwnerQuickActions, { resource: "PRODUCT", recordId: String(product.id), storeId: product.storeId ? String(product.storeId) : undefined, recordLabel: product.title, placement: "card" })) : null;
142
156
  // Selection is still suppressed: a bulk-select gesture and a per-record edit
143
157
  // are different intents, and offering both at once invites the wrong one.
144
158
  if (!ownerActions || selectionMode || isSelected)
145
159
  return wrapped;
146
160
  /*
147
- * 🛑 OUTSIDE the anchor, as a sibling — never inside `cardBody`.
161
+ * 🛑 STILL OUTSIDE THE ANCHOR, as a sibling — never inside `cardBody`.
148
162
  *
149
163
  * This card wraps its entire body in a `<Link>` whenever `href` is set, and
150
164
  * `InteractiveProductCard` wraps it in another one. A `<button>` inside an
151
165
  * `<a>` is invalid HTML that browsers resolve by closing the anchor early,
152
166
  * which breaks the CARD's navigation rather than just the button's —
153
- * stopPropagation stops the click but cannot fix the parse.
167
+ * stopPropagation stops the click but cannot fix the parse. That constraint
168
+ * is unchanged and is why this sits here rather than next to the title.
169
+ *
170
+ * 🛑 BUT NO LONGER ABSOLUTE, AND NO LONGER ON THE PHOTO.
171
+ *
172
+ * It used to be `absolute right-2 top-14 z-20`, chosen by elimination — the
173
+ * wishlist heart owns `top-2 right-2`, the checkbox and badges own top-left,
174
+ * `IconOverlay` owns bottom-right — which left the middle of the product
175
+ * photograph as the only free corner. So every seller-visible card had three
176
+ * icons sitting on the artwork, and on the pre-order card the same logic put
177
+ * them on top of the "Reserve now" button.
154
178
  *
155
- * Positioned `top-14 right-2`: directly below the 44px wishlist heart at
156
- * `top-2 right-2`, so it clears every occupied corner (checkbox and badges
157
- * top-left, IconOverlay bottom-right) without needing to know the hero's
158
- * height. The hero is `aspect-square`, so it is always taller than
159
- * 3.5rem + 44px and the toolbar lands on the photo.
179
+ * In normal flow at the FOOT of the card it collides with nothing by
180
+ * construction: no corner budget to reason about, no overlap with a CTA, and
181
+ * the same position on every card type. The toolbar supplies its own box, so
182
+ * it reads as a distinct owner strip rather than as part of the listing.
160
183
  *
161
- * `z-20` beats the in-card overlays, which all sit at `z-10`.
184
+ * `justify-end` keeps it on the side the eye already uses for card actions;
185
+ * `pt-0` because the card body's own bottom padding is the gap.
162
186
  */
163
- return (_jsxs(Div, { className: "relative h-full", children: [wrapped, _jsx(Div, { className: "absolute right-2 top-14 z-20", children: ownerActions })] }));
187
+ return (_jsxs(Div, { className: "flex h-full flex-col", children: [wrapped, _jsx(Div, { className: "flex justify-end px-[var(--appkit-space-3)] pb-[var(--appkit-space-3)] pt-0", children: ownerActions })] }));
164
188
  }
165
189
  function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
166
190
  const discount = product.originalPrice && product.originalPrice > product.price
@@ -178,7 +202,7 @@ function ProductListRow({ product, onClick, onAddToWishlist, isWishlisted, }) {
178
202
  .join(" "), children: [_jsx(Div, { className: `flex-shrink-0 w-16 h-16 sm:w-20 sm:h-20 ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "lg", children: product.mainImage ? (_jsx(MediaImage, { src: product.mainImage, alt: product.title, size: "thumbnail" })) : (_jsx(Div, { className: "w-full h-full bg-neutral-200" })) }), _jsxs(Stack, { className: "flex-1 min-w-0 gap-[2px]", children: [_jsx(Text, { size: "sm", weight: "medium", color: "primary", truncate: 2, children: product.title }), (product.categoryName || product.brand) && (_jsx(Span, { className: "text-[11px] truncate", color: "faint", children: [product.categoryName, product.brand].filter(Boolean).join(" · ") })), _jsx(CardStoreLine, { storeName: product.storeName, storeId: product.storeId, className: "text-[11px]" }), _jsxs(Row, { className: "mt-0.5", align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { size: "sm", weight: "semibold", className: "text-primary", children: _jsx(GatedPrice, { children: formatCurrency(product.price, getDefaultCurrency()) }) }), discount && (
179
203
  // The discount % is derived from price vs originalPrice, so it is a
180
204
  // money disclosure even though it carries no currency symbol.
181
- _jsx(PricesOnly, { children: _jsxs(Span, { weight: "bold", className: CLS_DISCOUNT_TEXT_BARE, children: ["-", discount, "%"] }) })), product.rating !== undefined && (_jsxs(Span, { layout: "flex", gap: "2xs", className: "text-[11px]", color: "faint", children: [_jsx(Span, { className: CLS_STAR_BARE, children: "\u2605" }), product.rating.toFixed(1)] })), (() => {
205
+ _jsx(PricesOnly, { children: _jsxs(Span, { weight: "bold", className: CLS_DISCOUNT_TEXT_BARE, children: ["-", discount, "%"] }) })), product.rating !== undefined && (product.reviewCount ?? 0) > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", className: "text-[11px]", color: "faint", children: [_jsx(Span, { className: CLS_STAR_BARE, children: "\u2605" }), product.rating.toFixed(1)] })), (() => {
182
206
  const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
183
207
  if (stock === undefined)
184
208
  return null;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * What a product card may say in its badge corner, and in what order.
3
+ *
4
+ * ── THE DEFECT THIS REPLACES ────────────────────────────────────────────────
5
+ *
6
+ * `ProductCard` rendered SEVEN independent badges into one absolutely-
7
+ * positioned stack, each unconditionally shown when its own condition held:
8
+ *
9
+ * -20% · Auction · Bundled · In Set · Taking Offers · Final Sale
10
+ * · Has Variants
11
+ *
12
+ * None of them knew about the others. A discounted auction that is a bundled
13
+ * set member with offers enabled, final sale and variants renders all seven,
14
+ * stacked top-left, covering the product photograph the card exists to show.
15
+ * Nothing is wrong with any one of those badges; the defect is that nobody
16
+ * decided which matters.
17
+ *
18
+ * ── THE RULE ────────────────────────────────────────────────────────────────
19
+ *
20
+ * 🛑 A BADGE EARNS ITS PLACE BY CHANGING WHETHER THE SHOPPER CLICKS. That is
21
+ * the whole test, and it sorts the seven cleanly:
22
+ *
23
+ * KEEPS ITS PLACE — changes the decision to click
24
+ * · listing TYPE: an auction and a pre-order are different transactions
25
+ * with different CTAs. Not knowing is a wasted click.
26
+ * · DISCOUNT: the single commercial reason to look at this card first.
27
+ *
28
+ * LOSES ITS PLACE — matters when deciding to BUY, not to click
29
+ * · Final Sale, Taking Offers, Bundled, In Set, Has Variants
30
+ *
31
+ * The second group is not deleted — it moves to the detail page, where there
32
+ * is room to state it in words rather than a 10px pill. A badge a shopper
33
+ * cannot act on from a grid is noise, and noise hides the badge they can act
34
+ * on, which is the actual cost of having seven.
35
+ *
36
+ * 🛑 `MAX_CARD_BADGES = 2` IS THE MECHANISM, and it has to exist separately
37
+ * from the precedence list. A precedence with no cap is still seven badges in
38
+ * a known order; the cap is what makes adding an eighth a design decision
39
+ * instead of a silent append. An overflow count ("+3") was considered and
40
+ * rejected — it is itself a badge, and one that says nothing.
41
+ *
42
+ * ── WHY A MODULE AND NOT A `.slice(0, 2)` AT THE CALL SITE ─────────────────
43
+ *
44
+ * Three card components render these (`ProductCard`, its list layout, and the
45
+ * per-type marketplace cards), and a cap applied at one of them is the shape
46
+ * this codebase has paid for repeatedly: `unitPriceFor` documented itself as
47
+ * "the single source of truth" and was then hand-copied into four more places,
48
+ * one of which never learned about locked prices and charged the list price
49
+ * for a negotiated sale (Root Cause #75).
50
+ *
51
+ * @tag domain:products
52
+ * @tag layer:shared
53
+ * @tag access:isomorphic
54
+ */
55
+ /** Stable ids, so a consumer can reason about precedence without string-matching. */
56
+ export declare const CARD_BADGE: {
57
+ readonly DISCOUNT: "discount";
58
+ readonly LISTING_TYPE: "listing-type";
59
+ };
60
+ export type CardBadgeId = (typeof CARD_BADGE)[keyof typeof CARD_BADGE];
61
+ export interface CardBadge {
62
+ id: CardBadgeId;
63
+ label: string;
64
+ /** Resolved by the caller from the listing-type plugin, or a local constant. */
65
+ className: string;
66
+ /** Long form, for the `title` attribute. */
67
+ title?: string;
68
+ }
69
+ /**
70
+ * 🛑 THE CAP. Two is not a round number — it is one commercial signal
71
+ * (discount) plus one transactional one (type), which are the only two
72
+ * questions a shopper answers before clicking. A third badge would have to
73
+ * displace one of those to earn its place.
74
+ */
75
+ export declare const MAX_CARD_BADGES = 2;
76
+ /**
77
+ * Precedence, highest first.
78
+ *
79
+ * 🛑 DISCOUNT OUTRANKS TYPE, deliberately, and this is the one judgement call
80
+ * in the file. Type is what the card IS; discount is why this card rather than
81
+ * the one beside it. In a grid the shopper is comparing, so the comparative
82
+ * signal goes first — and at the cap of two both are shown anyway, so the
83
+ * order only decides which is read first.
84
+ */
85
+ export declare const CARD_BADGE_PRECEDENCE: readonly CardBadgeId[];
86
+ /**
87
+ * Sort candidates by precedence and cap them.
88
+ *
89
+ * Takes already-resolved badges rather than a product, so this module needs no
90
+ * import of the listing-type registry or the product schema — it stays a pure
91
+ * ordering decision, which is the only thing it should be opinionated about.
92
+ *
93
+ * An id absent from `CARD_BADGE_PRECEDENCE` sorts LAST rather than being
94
+ * dropped: silently discarding a badge a caller built would be the harder
95
+ * failure to find, and the cap removes it anyway if it does not fit.
96
+ */
97
+ export declare function resolveCardBadges(candidates: readonly (CardBadge | null | undefined)[]): CardBadge[];
@@ -0,0 +1,100 @@
1
+ /**
2
+ * What a product card may say in its badge corner, and in what order.
3
+ *
4
+ * ── THE DEFECT THIS REPLACES ────────────────────────────────────────────────
5
+ *
6
+ * `ProductCard` rendered SEVEN independent badges into one absolutely-
7
+ * positioned stack, each unconditionally shown when its own condition held:
8
+ *
9
+ * -20% · Auction · Bundled · In Set · Taking Offers · Final Sale
10
+ * · Has Variants
11
+ *
12
+ * None of them knew about the others. A discounted auction that is a bundled
13
+ * set member with offers enabled, final sale and variants renders all seven,
14
+ * stacked top-left, covering the product photograph the card exists to show.
15
+ * Nothing is wrong with any one of those badges; the defect is that nobody
16
+ * decided which matters.
17
+ *
18
+ * ── THE RULE ────────────────────────────────────────────────────────────────
19
+ *
20
+ * 🛑 A BADGE EARNS ITS PLACE BY CHANGING WHETHER THE SHOPPER CLICKS. That is
21
+ * the whole test, and it sorts the seven cleanly:
22
+ *
23
+ * KEEPS ITS PLACE — changes the decision to click
24
+ * · listing TYPE: an auction and a pre-order are different transactions
25
+ * with different CTAs. Not knowing is a wasted click.
26
+ * · DISCOUNT: the single commercial reason to look at this card first.
27
+ *
28
+ * LOSES ITS PLACE — matters when deciding to BUY, not to click
29
+ * · Final Sale, Taking Offers, Bundled, In Set, Has Variants
30
+ *
31
+ * The second group is not deleted — it moves to the detail page, where there
32
+ * is room to state it in words rather than a 10px pill. A badge a shopper
33
+ * cannot act on from a grid is noise, and noise hides the badge they can act
34
+ * on, which is the actual cost of having seven.
35
+ *
36
+ * 🛑 `MAX_CARD_BADGES = 2` IS THE MECHANISM, and it has to exist separately
37
+ * from the precedence list. A precedence with no cap is still seven badges in
38
+ * a known order; the cap is what makes adding an eighth a design decision
39
+ * instead of a silent append. An overflow count ("+3") was considered and
40
+ * rejected — it is itself a badge, and one that says nothing.
41
+ *
42
+ * ── WHY A MODULE AND NOT A `.slice(0, 2)` AT THE CALL SITE ─────────────────
43
+ *
44
+ * Three card components render these (`ProductCard`, its list layout, and the
45
+ * per-type marketplace cards), and a cap applied at one of them is the shape
46
+ * this codebase has paid for repeatedly: `unitPriceFor` documented itself as
47
+ * "the single source of truth" and was then hand-copied into four more places,
48
+ * one of which never learned about locked prices and charged the list price
49
+ * for a negotiated sale (Root Cause #75).
50
+ *
51
+ * @tag domain:products
52
+ * @tag layer:shared
53
+ * @tag access:isomorphic
54
+ */
55
+ /** Stable ids, so a consumer can reason about precedence without string-matching. */
56
+ export const CARD_BADGE = {
57
+ DISCOUNT: "discount",
58
+ LISTING_TYPE: "listing-type",
59
+ };
60
+ /**
61
+ * 🛑 THE CAP. Two is not a round number — it is one commercial signal
62
+ * (discount) plus one transactional one (type), which are the only two
63
+ * questions a shopper answers before clicking. A third badge would have to
64
+ * displace one of those to earn its place.
65
+ */
66
+ export const MAX_CARD_BADGES = 2;
67
+ /**
68
+ * Precedence, highest first.
69
+ *
70
+ * 🛑 DISCOUNT OUTRANKS TYPE, deliberately, and this is the one judgement call
71
+ * in the file. Type is what the card IS; discount is why this card rather than
72
+ * the one beside it. In a grid the shopper is comparing, so the comparative
73
+ * signal goes first — and at the cap of two both are shown anyway, so the
74
+ * order only decides which is read first.
75
+ */
76
+ export const CARD_BADGE_PRECEDENCE = [
77
+ CARD_BADGE.DISCOUNT,
78
+ CARD_BADGE.LISTING_TYPE,
79
+ ];
80
+ /**
81
+ * Sort candidates by precedence and cap them.
82
+ *
83
+ * Takes already-resolved badges rather than a product, so this module needs no
84
+ * import of the listing-type registry or the product schema — it stays a pure
85
+ * ordering decision, which is the only thing it should be opinionated about.
86
+ *
87
+ * An id absent from `CARD_BADGE_PRECEDENCE` sorts LAST rather than being
88
+ * dropped: silently discarding a badge a caller built would be the harder
89
+ * failure to find, and the cap removes it anyway if it does not fit.
90
+ */
91
+ export function resolveCardBadges(candidates) {
92
+ const rank = (id) => {
93
+ const i = CARD_BADGE_PRECEDENCE.indexOf(id);
94
+ return i === -1 ? CARD_BADGE_PRECEDENCE.length : i;
95
+ };
96
+ return candidates
97
+ .filter((b) => b != null)
98
+ .sort((a, b) => rank(a.id) - rank(b.id))
99
+ .slice(0, MAX_CARD_BADGES);
100
+ }
@@ -15,7 +15,7 @@ import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../..
15
15
  */
16
16
  import { cacheManager } from "../../../core/CacheManager.js";
17
17
  import { serverLogger } from "../../../monitoring/index.js";
18
- import { generateUniqueId, slugify, generateBarcodeId } from "../../../utils/index.js";
18
+ import { generateUniqueId, slugify, generateBarcodeId, generateSku } from "../../../utils/index.js";
19
19
  import { parseSearchTxtQuery } from "../../../utils/search-txt.js";
20
20
  // The shared search plan/refine the other eleven migrated collections use.
21
21
  // `planSearchTxt` carries the `empty` guard that keeps a punctuation-only query
@@ -941,6 +941,30 @@ export class ProductRepository extends BaseRepository {
941
941
  }
942
942
  });
943
943
  const barcodeId = input.barcodeId ?? await generateBarcodeId(id);
944
+ /*
945
+ * The HUMAN key, derived on create beside the machine one.
946
+ *
947
+ * 🛑 Generated here rather than left to the form, because an absent `sku`
948
+ * is what produced "No SKU" on every row of /admin/products and an admin
949
+ * search box promising a field that did not exist. A key that is only
950
+ * present when someone remembered to type it is a key nothing can rely on.
951
+ *
952
+ * Synchronous, unlike `generateBarcodeId` — it is a `Math.imul` hash
953
+ * rather than `crypto.subtle`, specifically so seed rows and form state
954
+ * can call it without threading a promise through every row builder.
955
+ *
956
+ * Reads the DERIVED taxonomy below where it can, but the leaf is not
957
+ * resolved until `deriveTaxonomy` runs, so this uses the inbound value and
958
+ * accepts a slightly less specific token over a second await.
959
+ */
960
+ const sku = input.sku ??
961
+ generateSku({
962
+ productId: id,
963
+ categorySlug: Array.isArray(input.categorySlugs)
964
+ ? input.categorySlugs[0]
965
+ : input.category,
966
+ brand: input.brand,
967
+ });
944
968
  // Always derive on create — one extra read per listing created, and it is
945
969
  // what makes a UI-created product reachable from its own category page at
946
970
  // all. A seed row already carrying the correct chain derives to the same
@@ -955,6 +979,15 @@ export class ProductRepository extends BaseRepository {
955
979
  const withTaxonomy = {
956
980
  ...input,
957
981
  ...derived,
982
+ /*
983
+ * 🛑 `sku` goes in HERE, not just on `productData` below, and the
984
+ * difference matters: `buildProductSearchTxt(withTaxonomy)` is what
985
+ * builds the stored token list. A `sku` added only to the stored
986
+ * document would be invisible to every search — which is Root Cause
987
+ * #101's shape exactly, a field present on the object and absent from
988
+ * what a query reads.
989
+ */
990
+ sku,
958
991
  ...(input.images !== undefined ? { images: toProductImages(input.images) } : {}),
959
992
  };
960
993
  const productData = {