@mohasinac/appkit 4.9.5 → 4.11.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 (395) hide show
  1. package/dist/_internal/client/features/layout/DashboardLayoutClient.d.ts +6 -1
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +4 -1
  3. package/dist/_internal/client/features/lottery/LotteryPullForm.d.ts +1 -1
  4. package/dist/_internal/client/features/lottery/LotteryPullForm.js +2 -2
  5. package/dist/_internal/server/features/admin/daily-digest-actions.d.ts +18 -0
  6. package/dist/_internal/server/features/admin/daily-digest-actions.js +37 -0
  7. package/dist/_internal/server/features/auctions/actions.js +3 -2
  8. package/dist/_internal/server/features/auctions/service.d.ts +4 -3
  9. package/dist/_internal/server/features/auctions/service.js +6 -6
  10. package/dist/_internal/server/features/audit-log/actions.d.ts +25 -0
  11. package/dist/_internal/server/features/audit-log/actions.js +38 -0
  12. package/dist/_internal/server/features/brands/actions.js +0 -2
  13. package/dist/_internal/server/features/brands/og.d.ts +4 -1
  14. package/dist/_internal/server/features/brands/og.js +1 -1
  15. package/dist/_internal/server/features/bundles/data.d.ts +9 -0
  16. package/dist/_internal/server/features/bundles/data.js +14 -0
  17. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  18. package/dist/_internal/server/features/bundles/index.js +1 -1
  19. package/dist/_internal/server/features/catalogue/og.d.ts +20 -0
  20. package/dist/_internal/server/features/catalogue/og.js +27 -0
  21. package/dist/_internal/server/features/grouped/data.d.ts +16 -0
  22. package/dist/_internal/server/features/grouped/data.js +50 -1
  23. package/dist/_internal/server/features/grouped/index.d.ts +1 -1
  24. package/dist/_internal/server/features/grouped/index.js +1 -1
  25. package/dist/_internal/server/features/orders/actions.js +9 -0
  26. package/dist/_internal/server/features/orders/adapters.js +25 -0
  27. package/dist/_internal/server/features/products/data.d.ts +23 -0
  28. package/dist/_internal/server/features/products/data.js +159 -0
  29. package/dist/_internal/server/features/products/index.d.ts +1 -1
  30. package/dist/_internal/server/features/products/index.js +1 -1
  31. package/dist/_internal/server/functions/scheduled.d.ts +3 -1
  32. package/dist/_internal/server/functions/scheduled.js +17 -1
  33. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +40 -0
  34. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +164 -0
  35. package/dist/_internal/server/jobs/core/hardBanCascade.js +11 -0
  36. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -0
  37. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  38. package/dist/_internal/server/jobs/core/testerSandboxRefresh.d.ts +12 -0
  39. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +85 -0
  40. package/dist/_internal/server/jobs/core/whatsappNotify.js +16 -0
  41. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.d.ts +2 -0
  42. package/dist/_internal/server/jobs/handlers/dailyStatusDigest.js +4 -0
  43. package/dist/_internal/server/jobs/handlers/index.d.ts +2 -0
  44. package/dist/_internal/server/jobs/handlers/index.js +3 -0
  45. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.d.ts +2 -0
  46. package/dist/_internal/server/jobs/handlers/testerSandboxRefresh.js +2 -0
  47. package/dist/_internal/server/notifications/channel-health.d.ts +27 -0
  48. package/dist/_internal/server/notifications/channel-health.js +101 -0
  49. package/dist/_internal/shared/features/auctions/config.d.ts +20 -0
  50. package/dist/_internal/shared/features/auctions/config.js +31 -1
  51. package/dist/_internal/shared/features/brands/schema.d.ts +0 -6
  52. package/dist/_internal/shared/features/brands/schema.js +0 -1
  53. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +7 -0
  54. package/dist/_internal/shared/features/categories/bundle-schemas.js +3 -0
  55. package/dist/_internal/shared/features/orders/schema.d.ts +8 -8
  56. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -1
  57. package/dist/_internal/shared/listing-types/art/config.js +1 -1
  58. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -1
  59. package/dist/_internal/shared/listing-types/classified/config.js +1 -1
  60. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -1
  61. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -1
  62. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -1
  63. package/dist/_internal/shared/listing-types/live/config.js +1 -1
  64. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -1
  65. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  66. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -1
  67. package/dist/_internal/shared/listing-types/stickers/config.js +1 -1
  68. package/dist/client.d.ts +21 -12
  69. package/dist/client.js +14 -7
  70. package/dist/constants/api-endpoints.d.ts +9 -9
  71. package/dist/constants/api-endpoints.js +3 -3
  72. package/dist/contracts/client-auth.d.ts +3 -7
  73. package/dist/contracts/email.d.ts +1 -0
  74. package/dist/features/account/components/AddressForm.js +24 -2
  75. package/dist/features/account/components/BecomeSellerView.d.ts +1 -2
  76. package/dist/features/account/components/BecomeSellerView.js +1 -1
  77. package/dist/features/account/components/NotificationBell.js +1 -1
  78. package/dist/features/account/components/UserBidsView.d.ts +1 -0
  79. package/dist/features/account/components/UserBidsView.js +47 -0
  80. package/dist/features/account/components/UserOrdersView.d.ts +4 -11
  81. package/dist/features/account/components/UserOrdersView.js +54 -4
  82. package/dist/features/account/components/UserReturnsView.d.ts +4 -9
  83. package/dist/features/account/components/UserReturnsView.js +37 -3
  84. package/dist/features/account/components/index.d.ts +3 -2
  85. package/dist/features/account/components/index.js +1 -0
  86. package/dist/features/admin/actions/admin-actions.js +22 -0
  87. package/dist/features/admin/actions/admin-coupon-actions.js +10 -0
  88. package/dist/features/admin/actions/notification-actions.js +4 -3
  89. package/dist/features/admin/components/AdminAdsView.d.ts +2 -3
  90. package/dist/features/admin/components/AdminAdsView.js +86 -89
  91. package/dist/features/admin/components/AdminAllEventEntriesView.js +18 -3
  92. package/dist/features/admin/components/AdminAuditLogView.d.ts +11 -0
  93. package/dist/features/admin/components/AdminAuditLogView.js +95 -0
  94. package/dist/features/admin/components/AdminBlogEditorView.js +37 -3
  95. package/dist/features/admin/components/AdminBrandEditorView.js +10 -6
  96. package/dist/features/admin/components/AdminBundleEditorView.js +18 -2
  97. package/dist/features/admin/components/AdminCarouselEditorView.js +2 -2
  98. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +2 -2
  99. package/dist/features/admin/components/AdminCartsView.js +34 -4
  100. package/dist/features/admin/components/AdminCategoryEditorView.js +5 -4
  101. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  102. package/dist/features/admin/components/AdminFaqEditorView.js +2 -2
  103. package/dist/features/admin/components/AdminHistoryView.js +11 -1
  104. package/dist/features/admin/components/AdminNotificationsView.js +27 -1
  105. package/dist/features/admin/components/AdminOrderEditorView.d.ts +10 -1
  106. package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
  107. package/dist/features/admin/components/AdminOrdersView.js +92 -24
  108. package/dist/features/admin/components/AdminPayoutMarkPaidModal.d.ts +7 -0
  109. package/dist/features/admin/components/AdminPayoutMarkPaidModal.js +47 -0
  110. package/dist/features/admin/components/AdminPayoutsView.js +15 -31
  111. package/dist/features/admin/components/AdminProductEditorView.js +26 -3
  112. package/dist/features/admin/components/AdminSiteSettingsView.js +31 -4
  113. package/dist/features/admin/components/AdminStoreAddressesView.js +22 -2
  114. package/dist/features/admin/components/AdminStoresView.js +7 -0
  115. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +2 -2
  116. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +2 -2
  117. package/dist/features/admin/components/AdminUserEditorView.d.ts +2 -2
  118. package/dist/features/admin/components/AdminUserEditorView.js +4 -2
  119. package/dist/features/admin/components/AdminUsersView.js +1 -1
  120. package/dist/features/admin/components/AdminWishlistsView.js +14 -1
  121. package/dist/features/admin/components/DataListingView.d.ts +2 -2
  122. package/dist/features/admin/components/ViewAuditLogEntryModal.d.ts +19 -0
  123. package/dist/features/admin/components/ViewAuditLogEntryModal.js +22 -0
  124. package/dist/features/admin/components/ViewNotificationModal.d.ts +21 -0
  125. package/dist/features/admin/components/ViewNotificationModal.js +15 -0
  126. package/dist/features/admin/components/analytics/AdminPageViewsReportView.d.ts +4 -7
  127. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +56 -36
  128. package/dist/features/admin/components/index.d.ts +8 -1
  129. package/dist/features/admin/components/index.js +4 -0
  130. package/dist/features/admin/constants/filter-tabs.d.ts +49 -0
  131. package/dist/features/admin/constants/filter-tabs.js +27 -0
  132. package/dist/features/admin/hooks/useAdminListing.d.ts +6 -0
  133. package/dist/features/admin/hooks/useAdminListing.js +3 -1
  134. package/dist/features/admin/hooks/useAdminListingData.d.ts +9 -1
  135. package/dist/features/admin/hooks/useAdminListingData.js +9 -2
  136. package/dist/features/admin/repository/site-settings.repository.d.ts +5 -0
  137. package/dist/features/admin/schemas/firestore.d.ts +21 -0
  138. package/dist/features/admin/schemas/firestore.js +6 -0
  139. package/dist/features/analytics/types.d.ts +1 -1
  140. package/dist/features/analytics/types.js +8 -0
  141. package/dist/features/auctions/actions/bid-actions.js +4 -1
  142. package/dist/features/auctions/components/AuctionBottomActions.d.ts +2 -1
  143. package/dist/features/auctions/components/AuctionBottomActions.js +14 -1
  144. package/dist/features/auctions/components/AuctionDetailPageView.js +21 -9
  145. package/dist/features/auctions/components/LiveMinIncrement.d.ts +18 -0
  146. package/dist/features/auctions/components/LiveMinIncrement.js +17 -0
  147. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +5 -2
  148. package/dist/features/auctions/components/PlaceBidFormClient.js +7 -3
  149. package/dist/features/auctions/schemas/index.d.ts +5 -0
  150. package/dist/features/audit-log/repository/audit-log.repository.d.ts +45 -0
  151. package/dist/features/audit-log/repository/audit-log.repository.js +30 -0
  152. package/dist/features/audit-log/schemas/firestore.d.ts +59 -0
  153. package/dist/features/audit-log/schemas/firestore.js +35 -0
  154. package/dist/features/auth/actions/index.d.ts +0 -1
  155. package/dist/features/auth/actions/index.js +0 -1
  156. package/dist/features/auth/hooks/useAuth.d.ts +8 -35
  157. package/dist/features/auth/hooks/useAuth.js +21 -37
  158. package/dist/features/auth/permissions/constants.d.ts +1 -1
  159. package/dist/features/auth/permissions/constants.js +2 -0
  160. package/dist/features/blog/schemas/index.d.ts +32 -0
  161. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +31 -3
  162. package/dist/features/catalogue/components/CatalogueItemEditorView.js +2 -1
  163. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.d.ts +11 -0
  164. package/dist/features/catalogue/components/PublicCatalogueItemDetailView.js +33 -0
  165. package/dist/features/catalogue/components/PublicCatalogueView.js +2 -1
  166. package/dist/features/catalogue/index.d.ts +2 -0
  167. package/dist/features/catalogue/index.js +1 -0
  168. package/dist/features/categories/components/BrandDetailPageView.js +14 -12
  169. package/dist/features/categories/components/BundleDetailView.d.ts +3 -1
  170. package/dist/features/categories/components/BundleDetailView.js +3 -2
  171. package/dist/features/categories/components/CategoryDetailPageView.js +7 -3
  172. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.d.ts +14 -0
  173. package/dist/features/categories/components/CategoryHighlightsAndFaqSection.js +22 -0
  174. package/dist/features/categories/components/CategoryProductsView.d.ts +1 -5
  175. package/dist/features/categories/components/CategoryProductsView.js +1 -1
  176. package/dist/features/categories/components/CategorySelectorCreate.js +16 -2
  177. package/dist/features/categories/schemas/firestore.d.ts +19 -2
  178. package/dist/features/categories/schemas/index.d.ts +24 -3
  179. package/dist/features/categories/schemas/index.js +3 -1
  180. package/dist/features/categories/types/index.d.ts +7 -0
  181. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +17 -3
  182. package/dist/features/checkout/actions/checkout-value-otp-actions.js +101 -22
  183. package/dist/features/classified/components/ClassifiedContactSellerPanel.d.ts +12 -0
  184. package/dist/features/classified/components/ClassifiedContactSellerPanel.js +49 -0
  185. package/dist/features/classified/components/ClassifiedDetailPageView.d.ts +7 -0
  186. package/dist/features/classified/components/ClassifiedDetailPageView.js +69 -0
  187. package/dist/features/contact/components/ContactForm.js +17 -4
  188. package/dist/features/contact/components/ContactPageView.js +8 -2
  189. package/dist/features/contact/email.d.ts +1 -0
  190. package/dist/features/contact/email.js +3 -0
  191. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.d.ts +19 -0
  192. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +79 -0
  193. package/dist/features/events/components/AdminEventEditorView.js +54 -2
  194. package/dist/features/events/components/AdminEventEntriesView.d.ts +2 -16
  195. package/dist/features/events/components/AdminEventEntriesView.js +56 -89
  196. package/dist/features/events/components/EventFormDrawer.js +5 -1
  197. package/dist/features/events/components/EventRaffleEntryForm.d.ts +1 -3
  198. package/dist/features/events/components/EventRaffleEntryForm.js +1 -1
  199. package/dist/features/events/schemas/index.d.ts +22 -0
  200. package/dist/features/grouped/components/GroupedListingsCarousel.d.ts +13 -0
  201. package/dist/features/grouped/components/GroupedListingsCarousel.js +31 -0
  202. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +12 -0
  203. package/dist/features/grouped/repository/grouped-listings.repository.js +34 -0
  204. package/dist/features/homepage/components/CharacterHotspotForm.js +202 -187
  205. package/dist/features/homepage/components/SectionCarousel.d.ts +1 -7
  206. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  207. package/dist/features/layout/BottomActions.js +4 -3
  208. package/dist/features/layout/BottomActionsContext.d.ts +10 -0
  209. package/dist/features/layout/BottomActionsContext.js +5 -1
  210. package/dist/features/layout/TitleBarLayout.d.ts +1 -1
  211. package/dist/features/layout/TitleBarLayout.js +3 -3
  212. package/dist/features/layout/hooks/useBottomActions.d.ts +2 -0
  213. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  214. package/dist/features/live/components/LiveItemDetailPageView.d.ts +9 -0
  215. package/dist/features/live/components/LiveItemDetailPageView.js +74 -0
  216. package/dist/features/media/MediaImage.js +39 -3
  217. package/dist/features/media/MediaVideo.js +17 -1
  218. package/dist/features/media/types/index.d.ts +10 -0
  219. package/dist/features/media/types/index.js +1 -0
  220. package/dist/features/media/upload/MediaUploadField.d.ts +7 -1
  221. package/dist/features/media/upload/MediaUploadField.js +11 -7
  222. package/dist/features/media/upload/video-poster.d.ts +2 -0
  223. package/dist/features/media/upload/video-poster.js +16 -1
  224. package/dist/features/media/upload/video-poster.test.js +1 -1
  225. package/dist/features/orders/components/OrderStatusTimeline.d.ts +12 -0
  226. package/dist/features/orders/components/OrderStatusTimeline.js +55 -0
  227. package/dist/features/orders/components/index.d.ts +2 -0
  228. package/dist/features/orders/components/index.js +1 -0
  229. package/dist/features/orders/constants/payment-window.d.ts +25 -0
  230. package/dist/features/orders/constants/payment-window.js +25 -0
  231. package/dist/features/orders/repository/orders.repository.d.ts +32 -0
  232. package/dist/features/orders/repository/orders.repository.js +56 -0
  233. package/dist/features/orders/schemas/index.d.ts +24 -24
  234. package/dist/features/orders/types/index.d.ts +33 -0
  235. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
  236. package/dist/features/pre-orders/schemas/index.d.ts +5 -0
  237. package/dist/features/products/components/BidHistory.js +9 -1
  238. package/dist/features/products/components/GroupSettingsPanel.js +25 -3
  239. package/dist/features/products/components/PrizeDrawDetailPageView.js +11 -1
  240. package/dist/features/products/components/PrizeDrawEntryActions.d.ts +1 -4
  241. package/dist/features/products/components/PrizeDrawEntryActions.js +1 -1
  242. package/dist/features/products/components/ProductDetailActions.d.ts +1 -2
  243. package/dist/features/products/components/ProductDetailActions.js +1 -1
  244. package/dist/features/products/components/ProductDetailPageView.js +11 -116
  245. package/dist/features/products/components/ProductGrid.js +15 -1
  246. package/dist/features/products/components/RelatedItemsSection.d.ts +16 -0
  247. package/dist/features/products/components/RelatedItemsSection.js +17 -0
  248. package/dist/features/products/constants/action-defs.d.ts +1 -0
  249. package/dist/features/products/constants/action-defs.js +1 -0
  250. package/dist/features/products/schemas/index.d.ts +5 -0
  251. package/dist/features/promotions/components/CouponsIndexListing.d.ts +1 -3
  252. package/dist/features/promotions/components/CouponsIndexListing.js +1 -1
  253. package/dist/features/search/components/SearchView.d.ts +1 -3
  254. package/dist/features/search/components/SearchView.js +1 -1
  255. package/dist/features/seller/components/QuickProductForm.js +2 -1
  256. package/dist/features/seller/components/SellerBidsView.js +66 -107
  257. package/dist/features/seller/components/SellerGoogleReviewsView.js +17 -1
  258. package/dist/features/seller/components/SellerOrdersView.d.ts +15 -3
  259. package/dist/features/seller/components/SellerOrdersView.js +134 -163
  260. package/dist/features/seller/components/SellerPayoutMethodsView.js +60 -48
  261. package/dist/features/seller/components/SellerPayoutSettingsView.js +69 -4
  262. package/dist/features/seller/components/SellerPayoutsView.d.ts +12 -0
  263. package/dist/features/seller/components/SellerPayoutsView.js +25 -5
  264. package/dist/features/seller/components/SellerProductShell.d.ts +10 -3
  265. package/dist/features/seller/components/SellerProductShell.js +90 -37
  266. package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +2 -7
  267. package/dist/features/seller/components/SellerProductsFilterDrawer.js +4 -4
  268. package/dist/features/seller/components/SellerProductsView.d.ts +2 -4
  269. package/dist/features/seller/components/SellerProductsView.js +131 -189
  270. package/dist/features/seller/components/SellerReviewsView.js +95 -133
  271. package/dist/features/seller/components/SellerShippingConfigsView.js +47 -67
  272. package/dist/features/seller/components/SellerShippingView.js +41 -4
  273. package/dist/features/seller/components/SellerStoreCategoriesView.js +51 -59
  274. package/dist/features/seller/components/SellerStorefrontView.d.ts +6 -1
  275. package/dist/features/seller/components/SellerStorefrontView.js +42 -5
  276. package/dist/features/seller/components/index.d.ts +2 -2
  277. package/dist/features/seller/components/index.js +2 -2
  278. package/dist/features/shell/FormShell.d.ts +24 -4
  279. package/dist/features/shell/FormShell.js +12 -6
  280. package/dist/features/shell/QuickFormDrawer.js +67 -12
  281. package/dist/features/shell/StepForm.d.ts +30 -1
  282. package/dist/features/shell/StepForm.js +47 -4
  283. package/dist/features/shell/index.d.ts +1 -1
  284. package/dist/features/shell/index.js +1 -1
  285. package/dist/features/shipments/components/AdminShipmentEditorView.js +2 -2
  286. package/dist/features/shipments/schemas/validation.d.ts +14 -4
  287. package/dist/features/tester/components/AdminTesterFeedbackListView.js +23 -2
  288. package/dist/features/tester/components/TesterChecklistStepRow.js +1 -1
  289. package/dist/features/tester/components/TesterHubView.js +3 -2
  290. package/dist/features/tester/seed-data/orders-tester-seed-data.js +9 -1
  291. package/dist/features/tester/seed-data/products-tester-seed-data.js +43 -32
  292. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +487 -18
  293. package/dist/index.d.ts +17 -3
  294. package/dist/index.js +23 -5
  295. package/dist/next/routing/route-map.d.ts +13 -0
  296. package/dist/next/routing/route-map.js +12 -0
  297. package/dist/providers/email-resend/provider.js +1 -0
  298. package/dist/providers/firebase-client/auth.d.ts +1 -0
  299. package/dist/providers/firebase-client/auth.js +7 -1
  300. package/dist/repositories/index.d.ts +1 -0
  301. package/dist/repositories/index.js +1 -0
  302. package/dist/schemas/registry.d.ts +61 -25
  303. package/dist/security/pii-mask.js +1 -1
  304. package/dist/seed/bids-seed-data.js +48 -3
  305. package/dist/seed/categories-seed-data.js +69 -0
  306. package/dist/seed/grouped-listings-seed-data.js +167 -0
  307. package/dist/seed/payouts-seed-data.js +4 -4
  308. package/dist/seed/products-art-seed-data.js +188 -5
  309. package/dist/seed/products-auctions-seed-data.js +226 -6
  310. package/dist/seed/products-classifieds-seed-data.js +255 -8
  311. package/dist/seed/products-digital-codes-seed-data.js +258 -19
  312. package/dist/seed/products-live-items-seed-data.js +143 -10
  313. package/dist/seed/products-preorders-seed-data.js +213 -8
  314. package/dist/seed/products-prize-draws-seed-data.js +198 -0
  315. package/dist/seed/products-standard-seed-data.js +234 -4
  316. package/dist/seed/products-stickers-seed-data.js +178 -5
  317. package/dist/seed/site-settings-seed-data.js +23 -30
  318. package/dist/seed/store-extensions-seed-data.js +2 -2
  319. package/dist/server-entry.d.ts +6 -2
  320. package/dist/server-entry.js +24 -3
  321. package/dist/server.d.ts +10 -3
  322. package/dist/server.js +11 -4
  323. package/dist/styles.css +29 -1
  324. package/dist/tailwind-utilities.css +1 -1
  325. package/dist/ui/components/Form.d.ts +11 -2
  326. package/dist/ui/components/Form.js +4 -3
  327. package/dist/ui/components/FormField.d.ts +3 -1
  328. package/dist/ui/components/FormField.js +22 -4
  329. package/dist/ui/components/ImageLightbox.js +37 -3
  330. package/dist/ui/components/ImageLightbox.style.css +28 -0
  331. package/dist/ui/components/ListingLayout.d.ts +1 -2
  332. package/dist/ui/components/ListingLayout.js +1 -1
  333. package/dist/ui/components/RecordDetailModal.d.ts +68 -0
  334. package/dist/ui/components/RecordDetailModal.js +8 -0
  335. package/dist/ui/components/SlottedListingView.js +9 -1
  336. package/dist/ui/forms/FormErrorSummary.d.ts +25 -0
  337. package/dist/ui/forms/FormErrorSummary.js +37 -0
  338. package/dist/ui/forms/FormShell.d.ts +37 -2
  339. package/dist/ui/forms/FormShell.js +37 -7
  340. package/dist/ui/forms/FormShell.style.css +17 -0
  341. package/dist/ui/forms/index.d.ts +2 -0
  342. package/dist/ui/forms/index.js +1 -0
  343. package/dist/ui/index.d.ts +4 -0
  344. package/dist/ui/index.js +5 -0
  345. package/dist/utils/media-url.d.ts +12 -1
  346. package/dist/utils/media-url.js +59 -1
  347. package/dist/validation/schemas.d.ts +4 -4
  348. package/package.json +1 -1
  349. package/dist/_internal/client/features/classified/ClassifiedDetailView.d.ts +0 -9
  350. package/dist/_internal/client/features/classified/ClassifiedDetailView.js +0 -55
  351. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.d.ts +0 -18
  352. package/dist/_internal/client/features/digital-code/DigitalCodeDetailView.js +0 -21
  353. package/dist/_internal/client/features/live/LiveItemDetailView.d.ts +0 -9
  354. package/dist/_internal/client/features/live/LiveItemDetailView.js +0 -25
  355. package/dist/_internal/server/jobs/core/prizeRevealClose.d.ts +0 -2
  356. package/dist/_internal/server/jobs/core/prizeRevealClose.js +0 -28
  357. package/dist/_internal/server/jobs/core/prizeRevealExpiry.d.ts +0 -2
  358. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +0 -61
  359. package/dist/_internal/server/jobs/core/prizeRevealOpen.d.ts +0 -2
  360. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +0 -68
  361. package/dist/_internal/server/jobs/core/prizeRevealReminder.d.ts +0 -2
  362. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +0 -49
  363. package/dist/_internal/server/jobs/handlers/prizeRevealClose.d.ts +0 -2
  364. package/dist/_internal/server/jobs/handlers/prizeRevealClose.js +0 -2
  365. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.d.ts +0 -2
  366. package/dist/_internal/server/jobs/handlers/prizeRevealExpiry.js +0 -2
  367. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.d.ts +0 -2
  368. package/dist/_internal/server/jobs/handlers/prizeRevealOpen.js +0 -2
  369. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.d.ts +0 -2
  370. package/dist/_internal/server/jobs/handlers/prizeRevealReminder.js +0 -2
  371. package/dist/features/auth/consent-otp.d.ts +0 -57
  372. package/dist/features/auth/consent-otp.js +0 -127
  373. package/dist/features/cart/components/CheckoutOtpModal.d.ts +0 -15
  374. package/dist/features/cart/components/CheckoutOtpModal.js +0 -12
  375. package/dist/features/checkout/actions/checkout-actions.d.ts +0 -31
  376. package/dist/features/checkout/actions/checkout-actions.js +0 -124
  377. package/dist/features/classified/components/ClassifiedIndexListing.d.ts +0 -5
  378. package/dist/features/classified/components/ClassifiedIndexListing.js +0 -123
  379. package/dist/features/classified/components/ClassifiedListView.d.ts +0 -6
  380. package/dist/features/classified/components/ClassifiedListView.js +0 -44
  381. package/dist/features/digital-codes/components/DigitalCodesIndexListing.d.ts +0 -5
  382. package/dist/features/digital-codes/components/DigitalCodesIndexListing.js +0 -114
  383. package/dist/features/digital-codes/components/DigitalCodesListView.d.ts +0 -6
  384. package/dist/features/digital-codes/components/DigitalCodesListView.js +0 -38
  385. package/dist/features/live/components/LiveItemsIndexListing.d.ts +0 -5
  386. package/dist/features/live/components/LiveItemsIndexListing.js +0 -115
  387. package/dist/features/live/components/LiveItemsListView.d.ts +0 -6
  388. package/dist/features/live/components/LiveItemsListView.js +0 -44
  389. package/dist/features/seller/components/SellerTemplatesView.d.ts +0 -12
  390. package/dist/features/seller/components/SellerTemplatesView.js +0 -276
  391. package/dist/react/hooks/useVisibleItems.d.ts +0 -37
  392. package/dist/react/hooks/useVisibleItems.js +0 -59
  393. package/dist/ui/components/TabStrip.d.ts +0 -34
  394. package/dist/ui/components/TabStrip.js +0 -56
  395. package/dist/ui/components/TabStrip.style.css +0 -97
@@ -8,3 +8,28 @@ export declare const PAYMENT_WINDOW_MS: number;
8
8
  /** Cancellation reasons this feature writes to `OrderDocument.cancellationReason` (a free-form string field). */
9
9
  export declare const PAYMENT_WINDOW_EXPIRED_REASON = "payment_window_expired";
10
10
  export declare const PAYMENT_FRAUD_REJECTED_REASON = "payment_fraud_rejected";
11
+ /**
12
+ * The `paymentMethod` values that settle out-of-band and therefore require a
13
+ * buyer-uploaded proof + a human review pass. Single source of truth for
14
+ * `attachPaymentProofAction`'s guard, the admin payment-review queue, and the
15
+ * buyer/seller "needs payment proof" UI — previously each site inlined its own
16
+ * `pm !== "cash" && pm !== "upi_manual" && ...` chain and they could drift.
17
+ */
18
+ export declare const MANUAL_PAYMENT_METHODS: readonly string[];
19
+ export declare function isManualPaymentMethod(method: string | undefined | null): boolean;
20
+ /**
21
+ * The two states an unresolved manual payment can be in. Derived at query
22
+ * time from `paymentProofUrl` + `paymentReviewOutcome` rather than stored as
23
+ * its own field — a denormalised mirror would drift the moment any write path
24
+ * forgot it (CLAUDE.md Root Cause Pattern #42).
25
+ */
26
+ export type PaymentReviewQueueMode = "awaiting_proof" | "awaiting_verification";
27
+ export declare const PAYMENT_REVIEW_QUEUE_MODES: readonly PaymentReviewQueueMode[];
28
+ export declare function isPaymentReviewQueueMode(value: string): value is PaymentReviewQueueMode;
29
+ /**
30
+ * Hard bound on the payment-review queue's Firestore scan (Rule #6 — the
31
+ * Vercel 10s sync ceiling). Safe because an order only sits in this queue for
32
+ * 15 minutes (awaiting proof) or 2 hours (awaiting verification) before a
33
+ * scheduled sweep resolves it, so the live set is inherently small.
34
+ */
35
+ export declare const PAYMENT_REVIEW_QUEUE_SCAN_LIMIT = 500;
@@ -8,3 +8,28 @@ export const PAYMENT_WINDOW_MS = PAYMENT_WINDOW_MINUTES * 60 * 1000;
8
8
  /** Cancellation reasons this feature writes to `OrderDocument.cancellationReason` (a free-form string field). */
9
9
  export const PAYMENT_WINDOW_EXPIRED_REASON = "payment_window_expired";
10
10
  export const PAYMENT_FRAUD_REJECTED_REASON = "payment_fraud_rejected";
11
+ /**
12
+ * The `paymentMethod` values that settle out-of-band and therefore require a
13
+ * buyer-uploaded proof + a human review pass. Single source of truth for
14
+ * `attachPaymentProofAction`'s guard, the admin payment-review queue, and the
15
+ * buyer/seller "needs payment proof" UI — previously each site inlined its own
16
+ * `pm !== "cash" && pm !== "upi_manual" && ...` chain and they could drift.
17
+ */
18
+ export const MANUAL_PAYMENT_METHODS = ["cash", "upi_manual", "emi"];
19
+ export function isManualPaymentMethod(method) {
20
+ return !!method && MANUAL_PAYMENT_METHODS.includes(method);
21
+ }
22
+ export const PAYMENT_REVIEW_QUEUE_MODES = [
23
+ "awaiting_proof",
24
+ "awaiting_verification",
25
+ ];
26
+ export function isPaymentReviewQueueMode(value) {
27
+ return PAYMENT_REVIEW_QUEUE_MODES.includes(value);
28
+ }
29
+ /**
30
+ * Hard bound on the payment-review queue's Firestore scan (Rule #6 — the
31
+ * Vercel 10s sync ceiling). Safe because an order only sits in this queue for
32
+ * 15 minutes (awaiting proof) or 2 hours (awaiting verification) before a
33
+ * scheduled sweep resolves it, so the live set is inherently small.
34
+ */
35
+ export const PAYMENT_REVIEW_QUEUE_SCAN_LIMIT = 500;
@@ -3,6 +3,7 @@ import type { FirebaseSieveResult, SieveModel } from "../../../providers/db-fire
3
3
  import { BaseRepository, parseSieveDateValue } from "../../../providers/db-firebase";
4
4
  import { type OrderCreateInput, type OrderDocument, type OrderRefundEvent } from "../schemas";
5
5
  import type { OrderStatus, PaymentStatus } from "../types";
6
+ import { type PaymentReviewQueueMode } from "../constants/payment-window";
6
7
  declare class OrderRepository extends BaseRepository<OrderDocument> {
7
8
  constructor();
8
9
  private decryptOrder;
@@ -217,6 +218,37 @@ declare class OrderRepository extends BaseRepository<OrderDocument> {
217
218
  ref: DocumentReference;
218
219
  data: OrderDocument;
219
220
  }>>;
221
+ /**
222
+ * Admin/seller payment-review queue — the manual-payment orders that are
223
+ * waiting on somebody. Two modes:
224
+ *
225
+ * - `awaiting_proof` — buyer hasn't uploaded a screenshot yet
226
+ * (the 15-minute `paymentDeadline` window).
227
+ * - `awaiting_verification` — proof submitted, no admin decision recorded
228
+ * yet (the 2-hour auto-approve window).
229
+ *
230
+ * Both refinements — proof presence and manual `paymentMethod` — are done
231
+ * in-memory rather than as Firestore clauses, deliberately:
232
+ *
233
+ * - `paymentProofUrl != null` would silently exclude every doc where the
234
+ * field was never set at all, which is exactly the `awaiting_proof` set
235
+ * (same reasoning as `getExpiredPaymentDeadlines` /
236
+ * `getUnreviewedProofPastDeadline` above).
237
+ * - keeping `paymentMethod` out of the query means this reuses the
238
+ * existing `(status, paymentStatus, createdAt)` composite index instead
239
+ * of needing a new four-field one.
240
+ *
241
+ * The scan is hard-bounded at {@link PAYMENT_REVIEW_QUEUE_SCAN_LIMIT} for
242
+ * the Vercel 10s ceiling. That bound is generous for this queue by
243
+ * construction: an order only sits in it for 15 minutes (awaiting proof) or
244
+ * 2 hours (awaiting verification) before a scheduled sweep resolves it, so
245
+ * the live set is inherently small. `total` therefore counts matches within
246
+ * the scan window, not an unbounded collection-wide count.
247
+ */
248
+ listPaymentReviewQueue(mode: PaymentReviewQueueMode, opts?: {
249
+ page?: number;
250
+ pageSize?: number;
251
+ }): Promise<FirebaseSieveResult<OrderDocument>>;
220
252
  /**
221
253
  * Cloud Functions: find delivered orders eligible for the weekly payout sweep.
222
254
  *
@@ -4,6 +4,7 @@ import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../..
4
4
  import { decryptPiiFields, encryptPiiFields, ORDER_PII_FIELDS, } from "../../../security";
5
5
  import { serverTimestamp } from "../../../contracts/field-ops";
6
6
  import { createOrderId, ORDER_COLLECTION, OrderStatusValues, } from "../schemas";
7
+ import { MANUAL_PAYMENT_METHODS, PAYMENT_REVIEW_QUEUE_SCAN_LIMIT, } from "../constants/payment-window";
7
8
  import { ORDER_FIELDS } from "../../../constants/field-names";
8
9
  /**
9
10
  * Statuses that count toward a user's per-product / per-bundle purchase
@@ -340,6 +341,61 @@ class OrderRepository extends BaseRepository {
340
341
  }))
341
342
  .filter((entry) => !!entry.data.paymentProofUrl && !entry.data.paymentReviewOutcome);
342
343
  }
344
+ /**
345
+ * Admin/seller payment-review queue — the manual-payment orders that are
346
+ * waiting on somebody. Two modes:
347
+ *
348
+ * - `awaiting_proof` — buyer hasn't uploaded a screenshot yet
349
+ * (the 15-minute `paymentDeadline` window).
350
+ * - `awaiting_verification` — proof submitted, no admin decision recorded
351
+ * yet (the 2-hour auto-approve window).
352
+ *
353
+ * Both refinements — proof presence and manual `paymentMethod` — are done
354
+ * in-memory rather than as Firestore clauses, deliberately:
355
+ *
356
+ * - `paymentProofUrl != null` would silently exclude every doc where the
357
+ * field was never set at all, which is exactly the `awaiting_proof` set
358
+ * (same reasoning as `getExpiredPaymentDeadlines` /
359
+ * `getUnreviewedProofPastDeadline` above).
360
+ * - keeping `paymentMethod` out of the query means this reuses the
361
+ * existing `(status, paymentStatus, createdAt)` composite index instead
362
+ * of needing a new four-field one.
363
+ *
364
+ * The scan is hard-bounded at {@link PAYMENT_REVIEW_QUEUE_SCAN_LIMIT} for
365
+ * the Vercel 10s ceiling. That bound is generous for this queue by
366
+ * construction: an order only sits in it for 15 minutes (awaiting proof) or
367
+ * 2 hours (awaiting verification) before a scheduled sweep resolves it, so
368
+ * the live set is inherently small. `total` therefore counts matches within
369
+ * the scan window, not an unbounded collection-wide count.
370
+ */
371
+ async listPaymentReviewQueue(mode, opts = {}) {
372
+ const page = Math.max(1, opts.page ?? 1);
373
+ const pageSize = Math.min(50, Math.max(1, opts.pageSize ?? 25));
374
+ const snap = await this.db
375
+ .collection(this.collection)
376
+ .where(ORDER_FIELDS.STATUS, "==", OrderStatusValues.PENDING)
377
+ .where(ORDER_FIELDS.PAYMENT_STATUS, "==", "pending")
378
+ .orderBy(ORDER_FIELDS.CREATED_AT, "desc")
379
+ .limit(PAYMENT_REVIEW_QUEUE_SCAN_LIMIT)
380
+ .get();
381
+ const matches = snap.docs
382
+ .map((d) => this.decryptOrder({ id: d.id, ...d.data() }))
383
+ .filter((order) => MANUAL_PAYMENT_METHODS.includes(order.paymentMethod ?? ""))
384
+ .filter((order) => mode === "awaiting_proof"
385
+ ? !order.paymentProofUrl
386
+ : !!order.paymentProofUrl && !order.paymentReviewOutcome);
387
+ const total = matches.length;
388
+ const totalPages = total === 0 ? 0 : Math.max(1, Math.ceil(total / pageSize));
389
+ const start = (page - 1) * pageSize;
390
+ return {
391
+ items: matches.slice(start, start + pageSize),
392
+ total,
393
+ page,
394
+ pageSize,
395
+ totalPages,
396
+ hasMore: page < totalPages,
397
+ };
398
+ }
343
399
  /**
344
400
  * Cloud Functions: find delivered orders eligible for the weekly payout sweep.
345
401
  *
@@ -570,15 +570,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
570
570
  bin: string;
571
571
  } | undefined;
572
572
  payoutStatus?: "paid" | "eligible" | "requested" | undefined;
573
- items?: {
574
- productId: string;
575
- productTitle: string;
576
- quantity: number;
577
- unitPrice: number;
578
- totalPrice: number;
579
- listingType?: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | undefined;
580
- prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
581
- }[] | undefined;
582
573
  paymentId?: string | undefined;
583
574
  shippingAddress?: string | undefined;
584
575
  trackingNumber?: string | undefined;
@@ -601,6 +592,15 @@ export declare const orderFirestoreSchema: z.ZodObject<{
601
592
  shippingMethod?: "custom" | undefined;
602
593
  shippingCarrier?: string | undefined;
603
594
  trackingUrl?: string | undefined;
595
+ items?: {
596
+ productId: string;
597
+ productTitle: string;
598
+ quantity: number;
599
+ unitPrice: number;
600
+ totalPrice: number;
601
+ listingType?: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | undefined;
602
+ prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
603
+ }[] | undefined;
604
604
  bundleId?: string | undefined;
605
605
  prizeWon?: {
606
606
  title: string;
@@ -612,6 +612,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
612
612
  } | undefined;
613
613
  prizeDrawProductId?: string | undefined;
614
614
  couponCode?: string | undefined;
615
+ emiEnabled?: boolean | undefined;
616
+ emiTenureMonths?: number | undefined;
615
617
  orderType?: string | undefined;
616
618
  imageUrls?: string[] | undefined;
617
619
  refundFeeDeducted?: number | undefined;
@@ -627,8 +629,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
627
629
  couponId?: string | undefined;
628
630
  }[] | undefined;
629
631
  codHandlingFee?: number | undefined;
630
- emiEnabled?: boolean | undefined;
631
- emiTenureMonths?: number | undefined;
632
632
  emiTokenAmount?: number | undefined;
633
633
  emiSurchargeAmount?: number | undefined;
634
634
  emiInstallments?: {
@@ -718,15 +718,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
718
718
  bin: string;
719
719
  } | undefined;
720
720
  payoutStatus?: "paid" | "eligible" | "requested" | undefined;
721
- items?: {
722
- productId: string;
723
- productTitle: string;
724
- quantity: number;
725
- unitPrice: number;
726
- totalPrice: number;
727
- listingType?: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | undefined;
728
- prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
729
- }[] | undefined;
730
721
  paymentId?: string | undefined;
731
722
  shippingAddress?: string | undefined;
732
723
  trackingNumber?: string | undefined;
@@ -749,6 +740,15 @@ export declare const orderFirestoreSchema: z.ZodObject<{
749
740
  shippingMethod?: "custom" | undefined;
750
741
  shippingCarrier?: string | undefined;
751
742
  trackingUrl?: string | undefined;
743
+ items?: {
744
+ productId: string;
745
+ productTitle: string;
746
+ quantity: number;
747
+ unitPrice: number;
748
+ totalPrice: number;
749
+ listingType?: "classified" | "standard" | "auction" | "pre-order" | "prize-draw" | "digital-code" | "live" | undefined;
750
+ prizeRevealStatus?: "closed" | "open" | "pending" | "revealed" | undefined;
751
+ }[] | undefined;
752
752
  bundleId?: string | undefined;
753
753
  prizeWon?: {
754
754
  title: string;
@@ -760,6 +760,8 @@ export declare const orderFirestoreSchema: z.ZodObject<{
760
760
  } | undefined;
761
761
  prizeDrawProductId?: string | undefined;
762
762
  couponCode?: string | undefined;
763
+ emiEnabled?: boolean | undefined;
764
+ emiTenureMonths?: number | undefined;
763
765
  orderType?: string | undefined;
764
766
  imageUrls?: string[] | undefined;
765
767
  refundFeeDeducted?: number | undefined;
@@ -775,8 +777,6 @@ export declare const orderFirestoreSchema: z.ZodObject<{
775
777
  couponId?: string | undefined;
776
778
  }[] | undefined;
777
779
  codHandlingFee?: number | undefined;
778
- emiEnabled?: boolean | undefined;
779
- emiTenureMonths?: number | undefined;
780
780
  emiTokenAmount?: number | undefined;
781
781
  emiSurchargeAmount?: number | undefined;
782
782
  emiInstallments?: {
@@ -962,6 +962,7 @@ export declare const orderSchema: z.ZodObject<{
962
962
  [k: string]: unknown;
963
963
  };
964
964
  userId: string;
965
+ paymentStatus: string;
965
966
  items: {
966
967
  title: string;
967
968
  price: number;
@@ -972,7 +973,6 @@ export declare const orderSchema: z.ZodObject<{
972
973
  storeId?: string | undefined;
973
974
  attributes?: Record<string, string> | undefined;
974
975
  }[];
975
- paymentStatus: string;
976
976
  total: number;
977
977
  subtotal: number;
978
978
  orderStatus: "pending" | "cancelled" | "processing" | "refunded" | "delivered" | "confirmed" | "shipped" | "return_requested" | "returned";
@@ -999,6 +999,7 @@ export declare const orderSchema: z.ZodObject<{
999
999
  [k: string]: unknown;
1000
1000
  };
1001
1001
  userId: string;
1002
+ paymentStatus: string;
1002
1003
  items: {
1003
1004
  title: string;
1004
1005
  price: number;
@@ -1009,7 +1010,6 @@ export declare const orderSchema: z.ZodObject<{
1009
1010
  storeId?: string | undefined;
1010
1011
  attributes?: Record<string, string> | undefined;
1011
1012
  }[];
1012
- paymentStatus: string;
1013
1013
  total: number;
1014
1014
  subtotal: number;
1015
1015
  orderStatus: "pending" | "cancelled" | "processing" | "refunded" | "delivered" | "confirmed" | "shipped" | "return_requested" | "returned";
@@ -67,6 +67,8 @@ export interface Order {
67
67
  couponCode?: string;
68
68
  trackingNumber?: string;
69
69
  shippingCarrier?: string;
70
+ /** Carrier tracking page URL — rendered as an external link that opens in a new tab. No live-tracking API integration; this is just the passthrough URL a seller/admin entered. */
71
+ trackingUrl?: string;
70
72
  notes?: string;
71
73
  timeline?: OrderTimeline[];
72
74
  physicalLocation?: {
@@ -76,10 +78,41 @@ export interface Order {
76
78
  };
77
79
  createdAt?: string;
78
80
  updatedAt?: string;
81
+ /** Real per-status timestamps written by the order-status-update path — used to render a genuine status timeline (not fabricated/estimated). */
82
+ orderDate?: string;
83
+ shippingDate?: string;
84
+ deliveryDate?: string;
85
+ cancellationDate?: string;
79
86
  /** Tier PP — true when the 2-hour auto-approve sweep confirmed this order's payment instead of a manual admin review. Drives the "Raise a dispute" CTA. */
80
87
  autoApproved?: boolean;
81
88
  disputeRaised?: boolean;
82
89
  disputeStatus?: "open" | "resolved";
90
+ /**
91
+ * Manual-payment (cash / UPI / EMI) fields. Every one of these already
92
+ * existed on `OrderDocument` but was dropped by `orderDocumentToOrder`,
93
+ * so the buyer-facing surfaces that need them read `undefined`:
94
+ * `/user/orders/[id]/payment` gated its whole render on `paymentMethod`
95
+ * and therefore told every buyer "this order does not require manual
96
+ * payment upload", and the 15-minute countdown + UPI ID never rendered.
97
+ * (CLAUDE.md Root Cause Pattern #38 — adapter/serializer field drift.)
98
+ */
99
+ paymentMethod?: string;
100
+ /** UPI ID the buyer was told to pay to — shown on the payment page and diffed against `buyerReportedUpiId` during admin review. */
101
+ displayedUpiId?: string;
102
+ /** ISO end of the 15-minute payment window; drives the buyer countdown. */
103
+ paymentDeadline?: string;
104
+ paymentProofUrl?: string;
105
+ paymentProofUploadedAt?: string;
106
+ paymentTransactionId?: string;
107
+ buyerReportedUpiId?: string;
108
+ buyerMarkedPaid?: boolean;
109
+ paymentUpiMismatch?: boolean;
110
+ /** Admin decision on the submitted proof; unset means "not reviewed yet". */
111
+ paymentReviewOutcome?: "approved" | "reupload_requested" | "rejected_fraud";
112
+ /** Admin's note — the reason shown to the buyer on a re-upload request or fraud rejection. */
113
+ paymentReviewNote?: string;
114
+ /** Why the order was cancelled — distinguishes a payment-window expiry / fraud rejection from an ordinary cancellation. */
115
+ cancellationReason?: string;
83
116
  /**
84
117
  * Reveal-mode prize-draw fields (SB4-H/SB8-C). Set once the winner has
85
118
  * been automatically assigned to this order (on payment confirmation,
@@ -30,6 +30,10 @@ import { ShareButton } from "../../products/components/ShareButton";
30
30
  import { SublistingCarouselSection } from "../../products/components/SublistingCarouselSection";
31
31
  import { HistoryTracker } from "../../history/components/HistoryTracker";
32
32
  import { ShowGroupSection } from "../../products/components/ShowGroupSection";
33
+ import { RelatedItemsSection } from "../../products/components/RelatedItemsSection";
34
+ import { computeRelatedItems } from "../../../_internal/server/features/products/data";
35
+ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
36
+ import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
33
37
  function toDescriptionHtml(raw) {
34
38
  if (!raw)
35
39
  return "";
@@ -141,6 +145,10 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
141
145
  const groupId = typeof p.groupId === "string" ? p.groupId : null;
142
146
  const isGroupParent = p.isGroupParent === true;
143
147
  const groupTitle = typeof p.groupTitle === "string" ? p.groupTitle : undefined;
148
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups] = await Promise.all([
149
+ computeRelatedItems(product),
150
+ getGroupsWithItemsForProduct(product.id),
151
+ ]);
144
152
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "preorder", snapshot: {
145
153
  title: typeof p.title === "string" ? p.title : undefined,
146
154
  thumb: typeof p.mainImage === "string"
@@ -159,5 +167,5 @@ export async function PreOrderDetailPageView({ id, initialPreOrder, onReserveNow
159
167
  id: s.id,
160
168
  label: s.title,
161
169
  content: _jsx(CustomSectionTabContent, { section: s }),
162
- })) })), renderBuyBar: () => (_jsx(PreOrderBuyBarPanel, { reserveTarget: reserveTarget, reservedCount: reservedCount, progressPct: progressPct, productId: String(product.id), price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })) }), _jsx(PreOrderBottomActions, { price: price, currency: currency })] })] }));
170
+ })) })), renderBuyBar: () => (_jsx(PreOrderBuyBarPanel, { reserveTarget: reserveTarget, reservedCount: reservedCount, progressPct: progressPct, productId: String(product.id), price: price, currency: currency, depositAmount: depositAmount, depositPercent: depositPercent, isCancellable: isCancellable, tags: tags, onReserveNow: onReserveNow })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName || category || undefined, brandLabel: brand || undefined, storeLabel: storeName || undefined })] })) }), _jsx(PreOrderBottomActions, { price: price, currency: currency })] })] }));
163
171
  }
@@ -21,6 +21,7 @@ export declare const preorderItemSchema: z.ZodObject<{
21
21
  thumbnailUrl: z.ZodOptional<z.ZodString>;
22
22
  source: z.ZodOptional<z.ZodEnum<["upload", "youtube", "external"]>>;
23
23
  youtubeId: z.ZodOptional<z.ZodString>;
24
+ duration: z.ZodOptional<z.ZodNumber>;
24
25
  }, "strip", z.ZodTypeAny, {
25
26
  type: "video" | "image" | "file";
26
27
  url: string;
@@ -28,6 +29,7 @@ export declare const preorderItemSchema: z.ZodObject<{
28
29
  alt?: string | undefined;
29
30
  thumbnailUrl?: string | undefined;
30
31
  youtubeId?: string | undefined;
32
+ duration?: number | undefined;
31
33
  }, {
32
34
  type: "video" | "image" | "file";
33
35
  url: string;
@@ -35,6 +37,7 @@ export declare const preorderItemSchema: z.ZodObject<{
35
37
  alt?: string | undefined;
36
38
  thumbnailUrl?: string | undefined;
37
39
  youtubeId?: string | undefined;
40
+ duration?: number | undefined;
38
41
  }>, "many">>;
39
42
  salePrice: z.ZodNumber;
40
43
  regularPrice: z.ZodNumber;
@@ -62,6 +65,7 @@ export declare const preorderItemSchema: z.ZodObject<{
62
65
  alt?: string | undefined;
63
66
  thumbnailUrl?: string | undefined;
64
67
  youtubeId?: string | undefined;
68
+ duration?: number | undefined;
65
69
  }[] | undefined;
66
70
  franchise?: string | undefined;
67
71
  preorderShipDate?: string | undefined;
@@ -82,6 +86,7 @@ export declare const preorderItemSchema: z.ZodObject<{
82
86
  alt?: string | undefined;
83
87
  thumbnailUrl?: string | undefined;
84
88
  youtubeId?: string | undefined;
89
+ duration?: number | undefined;
85
90
  }[] | undefined;
86
91
  isFeatured?: boolean | undefined;
87
92
  franchise?: string | undefined;
@@ -19,6 +19,14 @@ function formatBidDate(raw) {
19
19
  return raw;
20
20
  }
21
21
  }
22
+ /** Bidder identity is already masked server-side (`maskPublicBid`) when a
23
+ * display name is present; this is only a fallback for bare bidder ids. */
24
+ function maskBidderId(id) {
25
+ if (!id)
26
+ return "Anonymous";
27
+ const tail = id.slice(-4);
28
+ return `Bidder ***${tail}`;
29
+ }
22
30
  export function BidHistory({ isLoading = false, isEmpty = false, renderList, renderBid, bids = [], renderEmpty, renderSkeleton, currency, labels = {}, className = "", }) {
23
31
  if (isLoading) {
24
32
  if (renderSkeleton)
@@ -32,5 +40,5 @@ export function BidHistory({ isLoading = false, isEmpty = false, renderList, ren
32
40
  }
33
41
  if (renderList)
34
42
  return _jsx(Div, { className: className, children: renderList() });
35
- return (_jsxs(Div, { className: className, children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-3 tracking-wide", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.title })), _jsx(Stack, { gap: "sm", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs(Div, { layout: "flex", align: "center", justify: "between", rounded: "lg", padding: "sm", textSize: "sm", className: "border border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", "data-section": "bidhistory-div-422", children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }), _jsx(Span, { size: "xs", color: "muted", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
43
+ return (_jsxs(Div, { className: className, children: [labels.title && (_jsx(Heading, { level: 3, className: "mb-3 tracking-wide", color: "muted", size: "sm", weight: "semibold", transform: "uppercase", children: labels.title })), _jsx(Stack, { gap: "sm", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs(Div, { layout: "flex", align: "center", justify: "between", rounded: "lg", padding: "sm", textSize: "sm", className: "border border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", "data-section": "bidhistory-div-422", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }), _jsx(Span, { size: "xs", color: "muted", children: bid.bidderName || maskBidderId(bid.bidderId) })] }), _jsx(Span, { size: "xs", color: "muted", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
36
44
  }
@@ -1,12 +1,22 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
- import { useState } from "react";
4
+ import { useEffect, useState } from "react";
5
+ import { z } from "zod";
5
6
  import { Button, Code, ConfirmDeleteModal, Div, FormField, Heading, Modal, Row, Select, SideDrawer, Span, Stack, Tabs, TabsContent, TabsList, TabsTrigger, Text, useToast } from "../../../ui";
7
+ import { FormShellContext, useFormShellState, FormErrorSummary } from "../../../ui/forms";
6
8
  import { MediaImage } from "../../media/MediaImage";
7
9
  import { apiClient } from "../../../http";
8
10
  import { PRODUCT_ENDPOINTS } from "../../../constants/api-endpoints";
9
11
  import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
12
+ const groupTitleSchema = z.object({
13
+ groupTitle: z.string().min(1, "Group title is required"),
14
+ });
15
+ const createChildSchema = z.object({
16
+ title: z.string().min(1, "Title is required"),
17
+ price: z.string().refine((v) => v.trim() !== "" && !Number.isNaN(parseFloat(v)) && parseFloat(v) > 0, "Enter a valid price"),
18
+ condition: z.string(),
19
+ });
10
20
  const __P = {
11
21
  p4: "p-[var(--appkit-space-4)]",
12
22
  };
@@ -33,6 +43,11 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
33
43
  const [linkTargets, setLinkTargets] = useState([]);
34
44
  const [children, setChildren] = useState(null);
35
45
  const [confirmAction, setConfirmAction] = useState(null);
46
+ const { shellCtx: titleShellCtx, validate: validateTitle } = useFormShellState(groupTitleSchema);
47
+ useEffect(() => {
48
+ validateTitle({ groupTitle: editTitle });
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ }, [editTitle, validateTitle]);
36
51
  if (isAuction)
37
52
  return null;
38
53
  const groupEndpoint = `${storeProductsEndpoint}/${productId}/group`;
@@ -69,6 +84,8 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
69
84
  }
70
85
  }
71
86
  async function saveTitle() {
87
+ if (Object.keys(titleShellCtx.errors).length > 0)
88
+ return;
72
89
  setLoading(true);
73
90
  try {
74
91
  await apiClient.patch(groupEndpoint, { groupTitle: editTitle });
@@ -218,8 +235,13 @@ export function GroupSettingsPanel({ productId, productSlug, groupId, isGroupPar
218
235
  setOpen(next);
219
236
  if (next && isGroupParent && children === null)
220
237
  loadChildren();
221
- }, paddingX: "none", paddingY: "xs", justify: "between", className: "w-full text-left group", "aria-expanded": open, children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "muted", children: "Group Settings" }), _jsx(Span, { size: "xs", className: "group-hover:text-[var(--appkit-color-text-muted)] dark:group-hover:text-[var(--appkit-color-text-faint)] transition-colors", color: "faint", children: open ? "▲" : "▼" })] }), open && (_jsxs(Stack, { gap: "sm", className: `mt-3 ${__P.p4}`, surface: "muted", rounded: "lg", border: "default", children: [!groupId && !groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Group related listings together \u2014 e.g. a set, bundle, or multi-part item. Parts can be sold individually but shown together." }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: startGroup, isLoading: loading, children: "Start a group" })] })), isGroupParent && groupId && (_jsxs(Stack, { gap: "md", children: [_jsxs(Row, { align: "start", gap: "sm", wrap: true, children: [_jsx(Div, { className: "flex-1 min-w-[200px]", children: _jsx(FormField, { name: "groupTitle", label: "Group title", type: "text", value: editTitle, onChange: setEditTitle, placeholder: "e.g. Human Toy Complete Set" }) }), _jsx(Div, { padding: "t-lg", children: _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: saveTitle, isLoading: loading, children: "Save title" }) })] }), _jsxs(Div, { children: [_jsxs(Text, { className: "mb-2", color: "muted", size: "xs", weight: "semibold", children: ["Members (", childSlugsCount + 1, " \u2014 including this listing)"] }), loading && !children ? (_jsx(Text, { size: "xs", color: "faint", children: "Loading\u2026" })) : (_jsxs(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: [_jsxs(Row, { align: "center", gap: "sm", padding: "y-xs", children: [_jsx(Span, { padding: "pill-2xs", weight: "semibold", className: "bg-[var(--appkit-color-primary)]/10 text-[var(--appkit-color-primary)] text-[10px]", rounded: "default", children: "Parent" }), _jsx(Text, { className: "flex-1", color: "primary", size: "sm", children: productSlug })] }), (children ?? []).filter((c) => c.id !== productId).map((child) => (_jsxs(Row, { align: "center", gap: "sm", padding: "y-xs", children: [child.images?.[0] ? (_jsx(Div, { className: "relative w-8 h-8 flex-shrink-0 border border-[var(--appkit-color-border)]", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: child.images[0], alt: child.title, size: "thumbnail" }) })) : (_jsx(Div, { className: "w-8 h-8", surface: "subtle", rounded: "full" })), _jsx(Text, { className: "flex-1 truncate", color: "primary", size: "sm", children: child.title }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => requestUnlinkChild(child.id), isLoading: loading, className: `${CLS_DELETE_LINK} text-[length:var(--appkit-text-xs)]`, children: "Unlink" })] }, child.id)))] }))] }), _jsxs(Row, { gap: "sm", wrap: true, children: [_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setShowAddModal(true), children: "Add child listing" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestDissolveGroup, isLoading: loading, className: CLS_DELETE_LINK, children: "Dissolve group" })] })] })), !isGroupParent && groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", color: "muted", children: ["Part of:", " ", _jsx(Span, { weight: "medium", color: "primary", children: groupTitle ?? groupParentSlug })] }), _jsxs(Text, { size: "xs", color: "muted", children: ["Parent listing: ", _jsx(Code, { className: "font-mono", children: groupParentSlug })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestLeaveGroup, isLoading: loading, className: `${CLS_DELETE_LINK} w-fit`, children: "Leave group" })] }))] })), (groupChildSlugs?.length ?? 0) >= 4 ? (_jsx(SideDrawer, { isOpen: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, storeProductsEndpoint: storeProductsEndpoint, linkTargets: linkTargets, setLinkTargets: setLinkTargets, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })) : (_jsx(Modal, { open: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", size: "lg", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, storeProductsEndpoint: storeProductsEndpoint, linkTargets: linkTargets, setLinkTargets: setLinkTargets, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })), confirmAction && (_jsx(ConfirmDeleteModal, { isOpen: true, title: confirmAction.title, message: confirmAction.message, onConfirm: confirmAction.onConfirm, onClose: () => setConfirmAction(null), isDeleting: loading }))] }));
238
+ }, paddingX: "none", paddingY: "xs", justify: "between", className: "w-full text-left group", "aria-expanded": open, children: [_jsx(Heading, { level: 3, size: "sm", weight: "semibold", color: "muted", children: "Group Settings" }), _jsx(Span, { size: "xs", className: "group-hover:text-[var(--appkit-color-text-muted)] dark:group-hover:text-[var(--appkit-color-text-faint)] transition-colors", color: "faint", children: open ? "▲" : "▼" })] }), open && (_jsxs(Stack, { gap: "sm", className: `mt-3 ${__P.p4}`, surface: "muted", rounded: "lg", border: "default", children: [!groupId && !groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Group related listings together \u2014 e.g. a set, bundle, or multi-part item. Parts can be sold individually but shown together." }), _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: startGroup, isLoading: loading, children: "Start a group" })] })), isGroupParent && groupId && (_jsxs(Stack, { gap: "md", children: [_jsxs(FormShellContext.Provider, { value: titleShellCtx, children: [_jsxs(Row, { align: "start", gap: "sm", wrap: true, children: [_jsx(Div, { className: "flex-1 min-w-[200px]", children: _jsx(FormField, { name: "groupTitle", label: "Group title", type: "text", value: editTitle, onChange: setEditTitle, placeholder: "e.g. Human Toy Complete Set" }) }), _jsx(Div, { padding: "t-lg", children: _jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: saveTitle, isLoading: loading, children: "Save title" }) })] }), _jsx(FormErrorSummary, {})] }), _jsxs(Div, { children: [_jsxs(Text, { className: "mb-2", color: "muted", size: "xs", weight: "semibold", children: ["Members (", childSlugsCount + 1, " \u2014 including this listing)"] }), loading && !children ? (_jsx(Text, { size: "xs", color: "faint", children: "Loading\u2026" })) : (_jsxs(Div, { className: "divide-y divide-zinc-100 divide-[var(--appkit-color-border-subtle)]", children: [_jsxs(Row, { align: "center", gap: "sm", padding: "y-xs", children: [_jsx(Span, { padding: "pill-2xs", weight: "semibold", className: "bg-[var(--appkit-color-primary)]/10 text-[var(--appkit-color-primary)] text-[10px]", rounded: "default", children: "Parent" }), _jsx(Text, { className: "flex-1", color: "primary", size: "sm", children: productSlug })] }), (children ?? []).filter((c) => c.id !== productId).map((child) => (_jsxs(Row, { align: "center", gap: "sm", padding: "y-xs", children: [child.images?.[0] ? (_jsx(Div, { className: "relative w-8 h-8 flex-shrink-0 border border-[var(--appkit-color-border)]", rounded: "full", overflow: "hidden", children: _jsx(MediaImage, { src: child.images[0], alt: child.title, size: "thumbnail" }) })) : (_jsx(Div, { className: "w-8 h-8", surface: "subtle", rounded: "full" })), _jsx(Text, { className: "flex-1 truncate", color: "primary", size: "sm", children: child.title }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => requestUnlinkChild(child.id), isLoading: loading, className: `${CLS_DELETE_LINK} text-[length:var(--appkit-text-xs)]`, children: "Unlink" })] }, child.id)))] }))] }), _jsxs(Row, { gap: "sm", wrap: true, children: [_jsx(Button, { type: "button", variant: "secondary", size: "sm", onClick: () => setShowAddModal(true), children: "Add child listing" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestDissolveGroup, isLoading: loading, className: CLS_DELETE_LINK, children: "Dissolve group" })] })] })), !isGroupParent && groupParentSlug && (_jsxs(Stack, { gap: "xs", children: [_jsxs(Text, { size: "sm", color: "muted", children: ["Part of:", " ", _jsx(Span, { weight: "medium", color: "primary", children: groupTitle ?? groupParentSlug })] }), _jsxs(Text, { size: "xs", color: "muted", children: ["Parent listing: ", _jsx(Code, { className: "font-mono", children: groupParentSlug })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: requestLeaveGroup, isLoading: loading, className: `${CLS_DELETE_LINK} w-fit`, children: "Leave group" })] }))] })), (groupChildSlugs?.length ?? 0) >= 4 ? (_jsx(SideDrawer, { isOpen: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, storeProductsEndpoint: storeProductsEndpoint, linkTargets: linkTargets, setLinkTargets: setLinkTargets, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })) : (_jsx(Modal, { open: showAddModal, onClose: () => setShowAddModal(false), title: "Add child listing", size: "lg", children: _jsx(AddChildContent, { addTab: addTab, setAddTab: setAddTab, createForm: createForm, setCreateForm: setCreateForm, productSlug: productSlug, storeProductsEndpoint: storeProductsEndpoint, linkTargets: linkTargets, setLinkTargets: setLinkTargets, onAddCreate: addCreateChild, onAddLink: addLinkChild, loading: loading }) })), confirmAction && (_jsx(ConfirmDeleteModal, { isOpen: true, title: confirmAction.title, message: confirmAction.message, onConfirm: confirmAction.onConfirm, onClose: () => setConfirmAction(null), isDeleting: loading }))] }));
222
239
  }
223
240
  function AddChildContent({ addTab, setAddTab, createForm, setCreateForm, productSlug, storeProductsEndpoint, linkTargets, setLinkTargets, onAddCreate, onAddLink, loading, }) {
224
- return (_jsx(Stack, { gap: "md", children: _jsxs(Tabs, { value: addTab, onChange: (v) => setAddTab(v), children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "create", children: "Create new child" }), _jsx(TabsTrigger, { value: "link", children: "Link existing" })] }), _jsx(TabsContent, { value: "create", children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(FormField, { name: "childTitle", label: "Title *", type: "text", value: createForm.title, onChange: (v) => setCreateForm({ ...createForm, title: v }), placeholder: `${productSlug}-part` }), _jsx(FormField, { name: "childPrice", label: "Price (\u20B9) *", type: "number", value: createForm.price, onChange: (v) => setCreateForm({ ...createForm, price: v }), placeholder: "0" }), _jsx(Select, { label: "Condition", value: createForm.condition, onValueChange: (v) => setCreateForm({ ...createForm, condition: v }), options: CONDITION_OPTIONS }), _jsx(Text, { className: "dark:text-[var(--appkit-color-text-faint)]/80", color: "faint", size: "xs", children: "Other fields (category, brand, shipping, return policy) are inherited from this parent listing. Need more control? Edit the full listing after saving." }), _jsx(Button, { type: "button", onClick: onAddCreate, isLoading: loading, disabled: !createForm.title || !createForm.price, children: "Create and link child" })] }) }), _jsx(TabsContent, { value: "link", children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(Text, { size: "xs", color: "muted", children: "Search your existing products or pre-orders. Auctions cannot be linked. Select multiple to bulk-link." }), _jsx(ProductInlineSelect, { scope: "store", endpoint: storeProductsEndpoint, filters: "listingType==standard", multiple: true, value: linkTargets, onChange: setLinkTargets, placeholder: "Search listings\u2026" }), _jsx(Button, { type: "button", onClick: onAddLink, isLoading: loading, disabled: linkTargets.length === 0, children: linkTargets.length > 1 ? `Link ${linkTargets.length} to group` : "Link to group" })] }) })] }) }));
241
+ const { shellCtx, validate } = useFormShellState(createChildSchema);
242
+ useEffect(() => {
243
+ validate(createForm);
244
+ // eslint-disable-next-line react-hooks/exhaustive-deps
245
+ }, [createForm, validate]);
246
+ return (_jsx(Stack, { gap: "md", children: _jsxs(Tabs, { value: addTab, onChange: (v) => setAddTab(v), children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "create", children: "Create new child" }), _jsx(TabsTrigger, { value: "link", children: "Link existing" })] }), _jsx(TabsContent, { value: "create", children: _jsx(FormShellContext.Provider, { value: shellCtx, children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(FormField, { name: "title", label: "Title *", type: "text", value: createForm.title, onChange: (v) => setCreateForm({ ...createForm, title: v }), placeholder: `${productSlug}-part` }), _jsx(FormField, { name: "price", label: "Price (\u20B9) *", type: "number", value: createForm.price, onChange: (v) => setCreateForm({ ...createForm, price: v }), placeholder: "0" }), _jsx(Select, { label: "Condition", value: createForm.condition, onValueChange: (v) => setCreateForm({ ...createForm, condition: v }), options: CONDITION_OPTIONS }), _jsx(Text, { className: "dark:text-[var(--appkit-color-text-faint)]/80", color: "faint", size: "xs", children: "Other fields (category, brand, shipping, return policy) are inherited from this parent listing. Need more control? Edit the full listing after saving." }), _jsx(FormErrorSummary, {}), _jsx(Button, { type: "button", onClick: onAddCreate, isLoading: loading, disabled: !createForm.title || !createForm.price, children: "Create and link child" })] }) }) }), _jsx(TabsContent, { value: "link", children: _jsxs(Stack, { gap: "sm", className: "mt-4", children: [_jsx(Text, { size: "xs", color: "muted", children: "Search your existing products or pre-orders. Auctions cannot be linked. Select multiple to bulk-link." }), _jsx(ProductInlineSelect, { scope: "store", endpoint: storeProductsEndpoint, filters: "listingType==standard", multiple: true, value: linkTargets, onChange: setLinkTargets, placeholder: "Search listings\u2026" }), _jsx(Button, { type: "button", onClick: onAddLink, isLoading: loading, disabled: linkTargets.length === 0, children: linkTargets.length > 1 ? `Link ${linkTargets.length} to group` : "Link to group" })] }) })] }) }));
225
247
  }
@@ -18,6 +18,10 @@ import { ShareButton } from "./ShareButton";
18
18
  import { PrizeDrawCollage } from "./PrizeDrawCollage";
19
19
  import { PrizeDrawEntryActions } from "./PrizeDrawEntryActions";
20
20
  import { HistoryTracker } from "../../history/components/HistoryTracker";
21
+ import { RelatedItemsSection } from "./RelatedItemsSection";
22
+ import { computeRelatedItems } from "../../../_internal/server/features/products/data";
23
+ import { GroupedListingsCarousel } from "../../grouped/components/GroupedListingsCarousel";
24
+ import { getGroupsWithItemsForProduct } from "../../../_internal/server/features/grouped/data";
21
25
  function toDescriptionHtml(raw) {
22
26
  if (!raw)
23
27
  return "";
@@ -112,11 +116,17 @@ export async function PrizeDrawDetailPageView({ id, initialPrizeDraw, currentUse
112
116
  ? String(ROUTES.PUBLIC.STORE_DETAIL(storeSlug))
113
117
  : null;
114
118
  const descriptionHtml = toDescriptionHtml(p.description);
119
+ const categoryName = Array.isArray(p.categoryNames) && p.categoryNames.length > 0 ? String(p.categoryNames[0]) : undefined;
120
+ const brand = typeof p.brand === "string" ? p.brand : undefined;
121
+ const [{ relatedItems, relatedByBrand, relatedByTags, relatedByStore }, groups] = await Promise.all([
122
+ computeRelatedItems(product),
123
+ getGroupsWithItemsForProduct(product.id),
124
+ ]);
115
125
  return (_jsxs(Main, { children: [_jsx(HistoryTracker, { productId: String(p.id ?? p.slug ?? ""), productType: "prize-draw", snapshot: {
116
126
  title,
117
127
  thumb,
118
128
  price: pricePerEntry,
119
129
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
120
130
  storeName: storeName ?? undefined,
121
- } }), _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", className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_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_PRIZE_BADGE, children: "Prize Draw" }), _jsx(Span, { size: "xs", weight: "semibold", className: `inline-block ${statusClass(revealStatus)}`, padding: "pill-sm", rounded: "full", children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsx(Div, { className: `${__P.p4}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Entries" }), _jsxs(Text, { size: "sm", weight: "semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window opens" }), _jsx(Text, { size: "sm", weight: "medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window closes" }), _jsx(Text, { size: "sm", weight: "medium", children: revealEnd.toLocaleString() })] }))] }) }), 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: "Sold 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined })), renderBuyBar: () => (_jsx(Div, { padding: "5", id: "prize-draw-buy-bar", border: "subtle", surface: "muted", rounded: "xl", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, thumb: thumb, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl, storeId: typeof p.storeId === "string" ? p.storeId : undefined, storeName: storeName ?? undefined }) })) }), _jsx(PrizeDrawBottomActions, { pricePerEntry: pricePerEntry, currency: currency, closed: revealStatus === "closed" || remaining === 0 })] })] }));
131
+ } }), _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", className: "flex items-center gap-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-wrap", children: [_jsx(Link, { href: String(ROUTES.HOME), className: "hover:text-primary-600 transition-colors", children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.PRIZE_DRAWS), className: "hover:text-primary-600 transition-colors", children: "Prize Draws" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(PreOrderDetailView, { renderGallery: () => (_jsx(PrizeDrawCollage, { items: items, hideWonState: true })), renderInfo: () => (_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_PRIZE_BADGE, children: "Prize Draw" }), _jsx(Span, { size: "xs", weight: "semibold", className: `inline-block ${statusClass(revealStatus)}`, padding: "pill-sm", rounded: "full", children: statusLabel(revealStatus) }), maxPerUser !== null && (_jsxs(Span, { size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", surface: "subtle", color: "muted", children: ["Limit: ", maxPerUser, " entries per customer"] })), maxPerUser !== null && userEntriesUsed !== null && (_jsxs(Span, { size: "xs", weight: "semibold", className: CLS_PRIZE_BADGE, children: ["You have used ", userEntriesUsed, "/", maxPerUser] }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsx(Div, { className: `${__P.p4}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Entries" }), _jsxs(Text, { size: "sm", weight: "semibold", children: [current, " / ", max, " (", remaining, " left)"] })] }), revealStart && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window opens" }), _jsx(Text, { size: "sm", weight: "medium", children: revealStart.toLocaleString() })] })), revealEnd && (_jsxs(Row, { justify: "between", align: "center", children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Reveal window closes" }), _jsx(Text, { size: "sm", weight: "medium", children: revealEnd.toLocaleString() })] }))] }) }), 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: "Sold 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" }))] }) }))] })), renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined })), renderBuyBar: () => (_jsx(Div, { padding: "5", id: "prize-draw-buy-bar", border: "subtle", surface: "muted", rounded: "xl", children: _jsx(PrizeDrawEntryActions, { productId: String(product.id), productSlug: slug, title: title, pricePerEntry: pricePerEntry, currency: currency, remainingEntries: remaining, revealStatus: revealStatus, prizeGithubFileUrl: githubUrl }) })), renderRelated: () => (_jsxs(Stack, { gap: "xl", children: [_jsx(GroupedListingsCarousel, { groups: groups }), _jsx(RelatedItemsSection, { relatedItems: relatedItems, relatedByBrand: relatedByBrand, relatedByTags: relatedByTags, relatedByStore: relatedByStore, categoryLabel: categoryName, brandLabel: brand, storeLabel: storeName ?? undefined })] })) }), _jsx(PrizeDrawBottomActions, { pricePerEntry: pricePerEntry, currency: currency, closed: revealStatus === "closed" || remaining === 0 })] })] }));
122
132
  }
@@ -2,14 +2,11 @@ export interface PrizeDrawEntryActionsProps {
2
2
  productId: string;
3
3
  productSlug?: string;
4
4
  title: string;
5
- thumb?: string;
6
5
  pricePerEntry: number;
7
6
  currency: string;
8
7
  remainingEntries: number;
9
8
  revealStatus: "pending" | "open" | "closed" | undefined;
10
9
  prizeGithubFileUrl?: string;
11
- storeId?: string;
12
- storeName?: string;
13
10
  }
14
11
  /**
15
12
  * Client buy panel for a prize-draw detail page (SB4-G).
@@ -18,4 +15,4 @@ export interface PrizeDrawEntryActionsProps {
18
15
  * - On confirm → add 1 entry to guest cart and route to /user/cart
19
16
  * - "View RNG source" link → prizeGithubFileUrl
20
17
  */
21
- export declare function PrizeDrawEntryActions({ productId, productSlug, title, thumb: _thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId: _storeId, storeName: _storeName, }: PrizeDrawEntryActionsProps): import("react").JSX.Element;
18
+ export declare function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }: PrizeDrawEntryActionsProps): import("react").JSX.Element;
@@ -16,7 +16,7 @@ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
16
16
  * - On confirm → add 1 entry to guest cart and route to /user/cart
17
17
  * - "View RNG source" link → prizeGithubFileUrl
18
18
  */
19
- export function PrizeDrawEntryActions({ productId, productSlug, title, thumb: _thumb, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, storeId: _storeId, storeName: _storeName, }) {
19
+ export function PrizeDrawEntryActions({ productId, productSlug, title, pricePerEntry, currency, remainingEntries, revealStatus, prizeGithubFileUrl, }) {
20
20
  const router = useRouter();
21
21
  const { requireAuth, modalOpen, modalMessage, closeModal } = useAuthGate();
22
22
  const [consentOpen, setConsentOpen] = useState(false);