@mohasinac/appkit 4.1.3 → 4.3.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 (276) hide show
  1. package/dist/_internal/server/features/auth/actions.d.ts +0 -1
  2. package/dist/_internal/server/features/auth/actions.js +0 -1
  3. package/dist/_internal/server/features/bundles/data.d.ts +22 -4
  4. package/dist/_internal/server/features/bundles/data.js +41 -4
  5. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  6. package/dist/_internal/server/features/bundles/index.js +1 -1
  7. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/actions.js +249 -257
  9. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  10. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  11. package/dist/_internal/server/features/orders/adapters.js +1 -14
  12. package/dist/_internal/server/features/products/actions.js +8 -3
  13. package/dist/_internal/server/features/products/service.d.ts +15 -0
  14. package/dist/_internal/server/features/products/service.js +41 -0
  15. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  16. package/dist/_internal/server/functions/firestore.js +17 -1
  17. package/dist/_internal/server/functions/scheduled.d.ts +3 -5
  18. package/dist/_internal/server/functions/scheduled.js +14 -30
  19. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  20. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  21. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  22. package/dist/_internal/server/jobs/core/index.js +4 -4
  23. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  24. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  25. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  26. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  27. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  28. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  29. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  30. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  31. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +3 -2
  32. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  33. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  34. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  35. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  36. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  37. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  38. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  39. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  40. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  41. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  42. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  43. package/dist/_internal/server/jobs/handlers/index.d.ts +4 -4
  44. package/dist/_internal/server/jobs/handlers/index.js +9 -6
  45. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  46. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  47. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  48. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  49. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  50. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  51. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  52. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  53. package/dist/_internal/shared/actions/action-registry.js +40 -10
  54. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  55. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  56. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  57. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  58. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  59. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  60. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  61. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  62. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  63. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  64. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  65. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  66. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  67. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  68. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  69. package/dist/_internal/shared/features/checkout/config.js +7 -0
  70. package/dist/_internal/shared/fees/calculator.d.ts +26 -0
  71. package/dist/_internal/shared/fees/calculator.js +25 -0
  72. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  73. package/dist/constants/api-endpoints.d.ts +6 -9
  74. package/dist/constants/api-endpoints.js +5 -5
  75. package/dist/constants/field-names.d.ts +17 -1
  76. package/dist/constants/field-names.js +17 -1
  77. package/dist/features/about/components/FeesView.js +18 -0
  78. package/dist/features/account/hooks/useNotifications.js +5 -0
  79. package/dist/features/admin/actions/admin-actions.js +6 -0
  80. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  81. package/dist/features/admin/actions/notification-actions.js +57 -12
  82. package/dist/features/admin/components/AdminAdsView.js +1 -1
  83. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  84. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  85. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  86. package/dist/features/admin/components/AdminSiteSettingsView.js +115 -11
  87. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  88. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  89. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  90. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  91. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  92. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  93. package/dist/features/admin/schemas/firestore.d.ts +54 -5
  94. package/dist/features/admin/schemas/firestore.js +63 -26
  95. package/dist/features/auctions/actions/bid-actions.js +3 -1
  96. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  97. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  98. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  99. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  100. package/dist/features/auctions/components/CollapsibleBidHistory.js +21 -3
  101. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  102. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  103. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  104. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  105. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  106. package/dist/features/auctions/hooks/useBids.js +32 -0
  107. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  108. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  109. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  110. package/dist/features/auth/checkout-value-otp.js +14 -10
  111. package/dist/features/auth/server.d.ts +0 -1
  112. package/dist/features/auth/server.js +0 -1
  113. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  114. package/dist/features/blog/api/[slug]/route.js +12 -3
  115. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  116. package/dist/features/blog/components/BlogPostView.js +5 -4
  117. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  118. package/dist/features/blog/hooks/useBlog.js +2 -0
  119. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  120. package/dist/features/blog/repository/blog.repository.js +44 -0
  121. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  122. package/dist/features/blog/schemas/firestore.js +1 -0
  123. package/dist/features/blog/types/index.d.ts +2 -0
  124. package/dist/features/cart/components/index.d.ts +0 -2
  125. package/dist/features/cart/components/index.js +0 -1
  126. package/dist/features/cart/hooks/useCartCount.js +22 -5
  127. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  128. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  129. package/dist/features/cart/utils/guest-cart.js +10 -0
  130. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  131. package/dist/features/cart/utils/pending-ops.js +46 -5
  132. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  133. package/dist/features/categories/components/BundleDetailView.js +4 -2
  134. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  135. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  136. package/dist/features/checkout/actions/index.d.ts +0 -1
  137. package/dist/features/checkout/actions/index.js +0 -1
  138. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  139. package/dist/features/events/actions/event-actions.d.ts +2 -0
  140. package/dist/features/events/actions/event-actions.js +5 -0
  141. package/dist/features/events/components/EventCard.js +1 -2
  142. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  143. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  144. package/dist/features/events/components/index.d.ts +2 -0
  145. package/dist/features/events/components/index.js +1 -0
  146. package/dist/features/events/repository/events.repository.d.ts +6 -0
  147. package/dist/features/events/repository/events.repository.js +21 -0
  148. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  149. package/dist/features/history/utils/guest-history.d.ts +1 -1
  150. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  151. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  152. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  153. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  154. package/dist/features/layout/AppLayoutShell.js +1 -1
  155. package/dist/features/layout/BackToTop.d.ts +9 -5
  156. package/dist/features/layout/BackToTop.js +17 -21
  157. package/dist/features/layout/NavbarLayout.js +1 -1
  158. package/dist/features/layout/TitleBarLayout.js +2 -2
  159. package/dist/features/media/MediaVideo.d.ts +7 -0
  160. package/dist/features/media/MediaVideo.js +36 -5
  161. package/dist/features/orders/actions/refund-actions.js +1 -0
  162. package/dist/features/orders/components/OrdersList.js +1 -5
  163. package/dist/features/orders/schemas/firestore.d.ts +16 -8
  164. package/dist/features/orders/schemas/index.d.ts +3 -32
  165. package/dist/features/orders/schemas/index.js +1 -4
  166. package/dist/features/orders/types/index.d.ts +6 -9
  167. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  168. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  169. package/dist/features/products/actions/product-actions.js +13 -10
  170. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  171. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  172. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  173. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  174. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  175. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  176. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  177. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  178. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  179. package/dist/features/products/components/ProductForm.js +7 -14
  180. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  181. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  182. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  183. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  184. package/dist/features/products/components/ShowGroupSection.js +2 -2
  185. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  186. package/dist/features/products/components/index.d.ts +4 -2
  187. package/dist/features/products/components/index.js +1 -0
  188. package/dist/features/products/constants/action-defs.d.ts +28 -4
  189. package/dist/features/products/constants/action-defs.js +76 -3
  190. package/dist/features/products/repository/products.repository.d.ts +6 -0
  191. package/dist/features/products/repository/products.repository.js +20 -1
  192. package/dist/features/products/schemas/firestore.d.ts +26 -2
  193. package/dist/features/products/schemas/index.d.ts +11 -3
  194. package/dist/features/products/schemas/index.js +10 -2
  195. package/dist/features/products/types/index.d.ts +2 -1
  196. package/dist/features/reviews/actions/review-actions.js +1 -0
  197. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  198. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  199. package/dist/features/reviews/components/ReviewsList.js +2 -1
  200. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  201. package/dist/features/reviews/schemas/index.d.ts +6 -0
  202. package/dist/features/reviews/schemas/index.js +2 -0
  203. package/dist/features/reviews/types/index.d.ts +3 -0
  204. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  205. package/dist/features/scams/actions/scam-actions.js +3 -2
  206. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  207. package/dist/features/scams/components/ScamProfileView.js +2 -2
  208. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  209. package/dist/features/scams/repository/scammer.repository.js +24 -0
  210. package/dist/features/seller/actions/seller-actions.js +8 -0
  211. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  212. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  213. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  214. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  215. package/dist/features/stores/components/StoreNavTabs.js +21 -9
  216. package/dist/features/tester/actions/index.d.ts +1 -0
  217. package/dist/features/tester/actions/index.js +1 -0
  218. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  219. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  220. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  221. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  222. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  223. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  224. package/dist/features/tester/components/index.d.ts +2 -0
  225. package/dist/features/tester/components/index.js +1 -0
  226. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  227. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  228. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  229. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  230. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  231. package/dist/features/tester/schemas/firestore.js +8 -0
  232. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  233. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  234. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  235. package/dist/features/tester/seed-data/products-tester-seed-data.js +152 -8
  236. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +249 -14
  237. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  238. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  239. package/dist/features/whatsapp-bot/types/index.d.ts +19 -0
  240. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  241. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  242. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  243. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  244. package/dist/index.d.ts +23 -24
  245. package/dist/index.js +29 -60
  246. package/dist/jobs.d.ts +1 -1
  247. package/dist/jobs.js +2 -2
  248. package/dist/next/routing/route-map.d.ts +4 -0
  249. package/dist/next/routing/route-map.js +2 -0
  250. package/dist/repositories/index.d.ts +2 -0
  251. package/dist/repositories/index.js +1 -0
  252. package/dist/schemas/registry.d.ts +6 -26
  253. package/dist/seed/bids-seed-data.js +14 -2
  254. package/dist/seed/blog-posts-seed-data.js +2 -1
  255. package/dist/seed/categories-seed-data.js +11 -2
  256. package/dist/seed/events-seed-data.js +11 -11
  257. package/dist/seed/faq-seed-data.js +5 -1
  258. package/dist/seed/products-auctions-seed-data.js +2 -2
  259. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  260. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  261. package/dist/seed/products-standard-seed-data.js +82 -3
  262. package/dist/seed/scammers-seed-data.js +75 -2
  263. package/dist/seed/site-settings-seed-data.js +12 -2
  264. package/dist/server-entry.d.ts +0 -1
  265. package/dist/server-entry.js +0 -1
  266. package/dist/server.d.ts +6 -22
  267. package/dist/server.js +12 -59
  268. package/dist/styles.css +1 -1
  269. package/dist/tailwind-utilities.css +1 -1
  270. package/dist/ui/components/BaseListingCard.js +1 -1
  271. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  272. package/dist/ui/components/ImageLightbox.js +6 -3
  273. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  274. package/dist/ui/components/ListingToolbar.js +5 -2
  275. package/dist/ui/components/StickyToolbar.js +13 -8
  276. package/package.json +1 -1
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Analytics Rollup Repository
3
+ *
4
+ * Singleton `analytics/dashboardRollup` doc — a pre-computed aggregate that
5
+ * replaces an unbounded `orderRepository.findByStatus("delivered")` scan on
6
+ * every admin dashboard page load. Written daily by the `revenueRollup`
7
+ * scheduled Firebase Function (`appkit/src/_internal/server/jobs/core/revenueRollup.ts`),
8
+ * read by `GET /api/admin/dashboard`.
9
+ */
10
+ import { BaseRepository } from "../../../providers/db-firebase";
11
+ export interface AnalyticsRollupDocument {
12
+ id: string;
13
+ totalRevenue: number;
14
+ deliveredOrderCount: number;
15
+ computedAt: Date;
16
+ }
17
+ export declare class AnalyticsRollupRepository extends BaseRepository<AnalyticsRollupDocument> {
18
+ constructor();
19
+ getDashboardRollup(): Promise<AnalyticsRollupDocument | null>;
20
+ setDashboardRollup(data: {
21
+ totalRevenue: number;
22
+ deliveredOrderCount: number;
23
+ }): Promise<void>;
24
+ }
25
+ export declare const analyticsRollupRepository: AnalyticsRollupRepository;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Analytics Rollup Repository
3
+ *
4
+ * Singleton `analytics/dashboardRollup` doc — a pre-computed aggregate that
5
+ * replaces an unbounded `orderRepository.findByStatus("delivered")` scan on
6
+ * every admin dashboard page load. Written daily by the `revenueRollup`
7
+ * scheduled Firebase Function (`appkit/src/_internal/server/jobs/core/revenueRollup.ts`),
8
+ * read by `GET /api/admin/dashboard`.
9
+ */
10
+ import { BaseRepository } from "../../../providers/db-firebase";
11
+ const ANALYTICS_COLLECTION = "analytics";
12
+ const DASHBOARD_ROLLUP_ID = "dashboardRollup";
13
+ export class AnalyticsRollupRepository extends BaseRepository {
14
+ constructor() {
15
+ super(ANALYTICS_COLLECTION);
16
+ }
17
+ async getDashboardRollup() {
18
+ return this.findById(DASHBOARD_ROLLUP_ID);
19
+ }
20
+ async setDashboardRollup(data) {
21
+ const existing = await this.findById(DASHBOARD_ROLLUP_ID);
22
+ const payload = { ...data, computedAt: new Date() };
23
+ if (existing) {
24
+ await this.update(DASHBOARD_ROLLUP_ID, payload);
25
+ }
26
+ else {
27
+ await this.createWithId(DASHBOARD_ROLLUP_ID, payload);
28
+ }
29
+ }
30
+ }
31
+ export const analyticsRollupRepository = new AnalyticsRollupRepository();
@@ -72,9 +72,16 @@ export class SiteSettingsRepository extends BaseRepository {
72
72
  mergeEncryptedCredentials(existing, updates) {
73
73
  const encrypted = { ...existing };
74
74
  for (const [key, value] of Object.entries(updates)) {
75
- if (value && value.trim()) {
75
+ if (typeof value === "string" && value.trim()) {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
76
77
  encrypted[key] = encryptSecret(value.trim());
77
78
  }
79
+ else if (value !== undefined && typeof value !== "string") {
80
+ // Non-string credential fields (e.g. whatsappTemplates: Record<string,string>)
81
+ // are stored as-is — they are not secret values to encrypt.
82
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
+ encrypted[key] = value;
84
+ }
78
85
  // Empty / undefined → keep whatever was already stored
79
86
  }
80
87
  return encrypted;
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import type { AboutContentDocument } from "../../about/schemas/firestore";
6
6
  export declare const ADMIN_CHECKOUT_BYPASS_FLAG_KEY: "adminCheckoutBypass";
7
- export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_reveal_ready" | "prize_reveal_expired" | "prize_reveal_reminder" | "emi_installment_due_soon" | "emi_installment_overdue" | "payment_review";
7
+ export type NotificationType = "order_placed" | "order_confirmed" | "order_shipped" | "order_delivered" | "order_cancelled" | "bid_placed" | "bid_outbid" | "bid_won" | "bid_lost" | "review_approved" | "review_replied" | "product_available" | "promotion" | "system" | "welcome" | "account_action" | "offer_received" | "offer_responded" | "offer_expired" | "offer_counter_accepted" | "refund_initiated" | "prize_won" | "prize_reveal_expired" | "emi_installment_due_soon" | "emi_installment_overdue" | "payment_review";
8
8
  import type { BaseDocument } from "../../../_internal/shared/types/base-document";
9
9
  export type NotificationPriority = "low" | "normal" | "high";
10
10
  export interface NotificationDocument extends BaseDocument {
@@ -20,6 +20,10 @@ export interface NotificationDocument extends BaseDocument {
20
20
  readAt?: Date;
21
21
  relatedId?: string;
22
22
  relatedType?: "order" | "product" | "bid" | "review" | "blog" | "user" | "offer" | "support_ticket" | "scammer";
23
+ /** Outcome of the async whatsappNotify job for this notification's WhatsApp send, if one was enqueued. */
24
+ whatsappStatus?: "queued" | "sent" | "failed" | "skipped";
25
+ /** jobs/{id} doc that ran (or is running) the WhatsApp send for this notification. */
26
+ whatsappJobId?: string;
23
27
  }
24
28
  export declare const NOTIFICATIONS_COLLECTION: "notifications";
25
29
  export declare const NOTIFICATIONS_INDEXED_FIELDS: readonly ["userId", "isRead", "createdAt", "type"];
@@ -61,9 +65,8 @@ export declare const NOTIFICATION_FIELDS: {
61
65
  readonly OFFER_COUNTER_ACCEPTED: NotificationType;
62
66
  readonly REFUND_INITIATED: NotificationType;
63
67
  readonly ACCOUNT_ACTION: NotificationType;
64
- readonly PRIZE_REVEAL_READY: NotificationType;
68
+ readonly PRIZE_WON: NotificationType;
65
69
  readonly PRIZE_REVEAL_EXPIRED: NotificationType;
66
- readonly PRIZE_REVEAL_REMINDER: NotificationType;
67
70
  };
68
71
  readonly PRIORITY_VALUES: {
69
72
  readonly LOW: NotificationPriority;
@@ -163,6 +166,23 @@ export interface SiteSettingsCredentials {
163
166
  whatsappCloudApiToken?: string;
164
167
  /** Comma-separated digits-only phone numbers that receive order announcements */
165
168
  whatsappAdminNotifyNumbers?: string;
169
+ /**
170
+ * Approved Meta message-template names, one flat string field per
171
+ * order-lifecycle notification type — every `SiteSettingsCredentials`
172
+ * field is stored as an individually-encrypted string (see
173
+ * `getDecryptedCredentials`/`getCredentialsMasked`, which iterate + mask
174
+ * every field as a string), so this can't be a `Record<string,string>`
175
+ * map. Business-initiated WhatsApp sends outside the 24h customer-service
176
+ * window require a pre-approved template.
177
+ */
178
+ whatsappTemplateOrderPlaced?: string;
179
+ whatsappTemplateOrderConfirmed?: string;
180
+ whatsappTemplateOrderShipped?: string;
181
+ whatsappTemplateOrderDelivered?: string;
182
+ whatsappTemplateOrderCancelled?: string;
183
+ whatsappTemplateRefundInitiated?: string;
184
+ /** BCP-47 language code the templates above were approved in, e.g. "en" or "en_US". */
185
+ whatsappTemplateLanguage?: string;
166
186
  }
167
187
  export interface SiteSettingsCredentialsMasked {
168
188
  razorpayKeyId?: string;
@@ -184,6 +204,13 @@ export interface SiteSettingsCredentialsMasked {
184
204
  whatsappPhoneNumberId?: string;
185
205
  whatsappCloudApiToken?: string;
186
206
  whatsappAdminNotifyNumbers?: string;
207
+ whatsappTemplateOrderPlaced?: string;
208
+ whatsappTemplateOrderConfirmed?: string;
209
+ whatsappTemplateOrderShipped?: string;
210
+ whatsappTemplateOrderDelivered?: string;
211
+ whatsappTemplateOrderCancelled?: string;
212
+ whatsappTemplateRefundInitiated?: string;
213
+ whatsappTemplateLanguage?: string;
187
214
  }
188
215
  export interface NotificationChannelConfig {
189
216
  inApp: {
@@ -320,6 +347,19 @@ export interface SiteSettingsDocument extends BaseDocument {
320
347
  /** COD handling fee charged to the buyer: max(codHandlingFeeMin, subtotal × codHandlingFeePercent / 100). */
321
348
  codHandlingFeeMin: number;
322
349
  codHandlingFeePercent: number;
350
+ /** Admin master toggle — whether the WhatsApp order-updates addon is offered at checkout at all. */
351
+ whatsappNotifyFeeEnabled: boolean;
352
+ /** Flat rupee fee charged to the buyer when they opt into the WhatsApp order-updates addon. */
353
+ whatsappNotifyFee: number;
354
+ /** Admin master toggle — whether the gift-wrap addon is offered at checkout at all. */
355
+ giftWrapFeeEnabled: boolean;
356
+ /** Flat rupee fee charged to the buyer when they opt into gift wrap. */
357
+ giftWrapFee: number;
358
+ /** Admin master toggle — whether the shipment-protection addon is offered at checkout at all. */
359
+ shipmentProtectionFeeEnabled: boolean;
360
+ /** Shipment protection fee: max(shipmentProtectionFeeMin, subtotal × shipmentProtectionFeePercent / 100). */
361
+ shipmentProtectionFeePercent: number;
362
+ shipmentProtectionFeeMin: number;
323
363
  sellerShippingFixed: number;
324
364
  platformShippingPercent: number;
325
365
  platformShippingFixedMin: number;
@@ -448,7 +488,7 @@ export interface SiteSettingsDocument extends BaseDocument {
448
488
  /**
449
489
  * Image watermark configuration applied by the `/api/media/[...slug]` CDN
450
490
  * proxy. When absent the proxy falls back to text watermark "letitrip.in" at
451
- * 30% width with 20% opacity.
491
+ * 10% width with 10% opacity, centered.
452
492
  */
453
493
  watermark?: {
454
494
  /** `"text"` renders the `text` field; `"image"` overlays `imageUrl`. */
@@ -463,8 +503,17 @@ export interface SiteSettingsDocument extends BaseDocument {
463
503
  imageUrl?: string;
464
504
  /** Percentage of target image width (0–100). 0 disables the watermark. */
465
505
  size?: number;
466
- /** Percentage opacity (0–100). Default 20. */
506
+ /** Percentage opacity (0–100). Default 10. */
467
507
  opacity?: number;
508
+ /**
509
+ * Anchor preset: 4 corners + center, or `"custom"` to use `offsetX`/
510
+ * `offsetY` instead. Default `"center"`.
511
+ */
512
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | "center" | "custom";
513
+ /** `position: "custom"` only — % of image width from center. +right / -left. Default 0. */
514
+ offsetX?: number;
515
+ /** `position: "custom"` only — % of image height from center. +down / -up. Default 0. */
516
+ offsetY?: number;
468
517
  };
469
518
  /** Encrypted provider credentials � never return raw to the client. */
470
519
  credentials?: SiteSettingsCredentials;
@@ -52,9 +52,8 @@ export const NOTIFICATION_FIELDS = {
52
52
  OFFER_COUNTER_ACCEPTED: "offer_counter_accepted",
53
53
  REFUND_INITIATED: "refund_initiated",
54
54
  ACCOUNT_ACTION: "account_action",
55
- PRIZE_REVEAL_READY: "prize_reveal_ready",
55
+ PRIZE_WON: "prize_won",
56
56
  PRIZE_REVEAL_EXPIRED: "prize_reveal_expired",
57
- PRIZE_REVEAL_REMINDER: "prize_reveal_reminder",
58
57
  },
59
58
  PRIORITY_VALUES: {
60
59
  LOW: "low",
@@ -189,6 +188,13 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
189
188
  codDepositPercent: 10,
190
189
  codHandlingFeeMin: 200,
191
190
  codHandlingFeePercent: 10,
191
+ whatsappNotifyFeeEnabled: false,
192
+ whatsappNotifyFee: 10,
193
+ giftWrapFeeEnabled: false,
194
+ giftWrapFee: 49,
195
+ shipmentProtectionFeeEnabled: false,
196
+ shipmentProtectionFeePercent: 2,
197
+ shipmentProtectionFeeMin: 30,
192
198
  sellerShippingFixed: 0,
193
199
  platformShippingPercent: 10,
194
200
  platformShippingFixedMin: 0,
@@ -284,61 +290,92 @@ export const DEFAULT_TRUST_BAR_ITEMS = [
284
290
  { icon: "??", label: "24/7 Support", visible: true },
285
291
  { icon: "?", label: "Authentic Sellers", visible: true },
286
292
  ];
293
+ // NOTE: labelKey/descKey hold plain display text (not i18n message keys) —
294
+ // this feature-flag list has no matching next-intl namespace, so the admin
295
+ // UI reads these two fields directly rather than through getTranslations().
287
296
  export const FEATURE_FLAG_META = [
288
297
  {
289
298
  key: "chats",
290
- labelKey: "chats",
291
- descKey: "chatsDesc",
292
- icon: "??",
299
+ labelKey: "In-app chats",
300
+ descKey: "Buyer/seller direct messaging.",
301
+ icon: "\u{1F4AC}",
293
302
  category: "platform",
294
303
  },
295
304
  {
296
305
  key: "smsVerification",
297
- labelKey: "smsVerification",
298
- descKey: "smsVerificationDesc",
299
- icon: "??",
306
+ labelKey: "SMS verification",
307
+ descKey: "Phone-number OTP verification during signup/checkout.",
308
+ icon: "\u{1F4F1}",
300
309
  category: "platform",
301
310
  },
302
311
  {
303
312
  key: "translations",
304
- labelKey: "translations",
305
- descKey: "translationsDesc",
306
- icon: "??",
313
+ labelKey: "Translations",
314
+ descKey: "Locale switcher and translated UI strings.",
315
+ icon: "\u{1F310}",
307
316
  category: "platform",
308
317
  },
309
318
  {
310
319
  key: "auctions",
311
- labelKey: "auctions",
312
- descKey: "auctionsDesc",
313
- icon: "??",
320
+ labelKey: "Auctions",
321
+ descKey: "Auction listings and bidding.",
322
+ icon: "\u{1F528}",
314
323
  category: "platform",
315
324
  },
316
325
  {
317
326
  key: "reviews",
318
- labelKey: "reviews",
319
- descKey: "reviewsDesc",
320
- icon: "?",
327
+ labelKey: "Reviews",
328
+ descKey: "Product and store reviews.",
329
+ icon: "",
321
330
  category: "platform",
322
331
  },
323
332
  {
324
333
  key: "notifications",
325
- labelKey: "notifications",
326
- descKey: "notificationsDesc",
327
- icon: "??",
334
+ labelKey: "Notifications",
335
+ descKey: "In-app notification bell and inbox.",
336
+ icon: "\u{1F514}",
328
337
  category: "platform",
329
338
  },
330
339
  {
331
340
  key: "sellerRegistration",
332
- labelKey: "sellerRegistration",
333
- descKey: "sellerRegistrationDesc",
334
- icon: "??",
341
+ labelKey: "Seller registration",
342
+ descKey: "\"Become a seller\" signup flow.",
343
+ icon: "\u{1F3EA}",
335
344
  category: "platform",
336
345
  },
337
346
  {
338
347
  key: "preOrders",
339
- labelKey: "preOrders",
340
- descKey: "preOrdersDesc",
341
- icon: "??",
348
+ labelKey: "Pre-orders",
349
+ descKey: "Pre-order listings and deposit checkout.",
350
+ icon: "\u{1F4E6}",
351
+ category: "platform",
352
+ },
353
+ {
354
+ key: "wishlists",
355
+ labelKey: "Wishlists",
356
+ descKey: "Save-for-later wishlist feature.",
357
+ icon: "❤️",
358
+ category: "platform",
359
+ },
360
+ {
361
+ key: "events",
362
+ labelKey: "Events",
363
+ descKey: "Sales, raffles, spin-wheel, and lottery events.",
364
+ icon: "\u{1F389}",
365
+ category: "platform",
366
+ },
367
+ {
368
+ key: "blog",
369
+ labelKey: "Blog",
370
+ descKey: "Blog posts section.",
371
+ icon: "\u{1F4DD}",
372
+ category: "platform",
373
+ },
374
+ {
375
+ key: "coupons",
376
+ labelKey: "Coupons",
377
+ descKey: "Coupon codes and discounts.",
378
+ icon: "\u{1F3AB}",
342
379
  category: "platform",
343
380
  },
344
381
  ];
@@ -130,13 +130,15 @@ export async function placeBid(userId, userEmail, input) {
130
130
  });
131
131
  });
132
132
  const finalVisibleForRtdb = newBidWins ? visibleBid : (bumpedPreviousVisible ?? prevVisible);
133
+ const finalBidCountForRtdb = (product.bidCount ?? 0) + 1;
133
134
  try {
134
135
  const rtdb = getAdminRealtimeDb();
135
136
  await rtdb.ref(`/auction-bids/${productId}`).set({
136
137
  currentBid: finalVisibleForRtdb,
138
+ bidCount: finalBidCountForRtdb,
137
139
  lastBid: {
138
140
  amount: finalVisibleForRtdb,
139
- bidderName: "Bidder",
141
+ bidderName: bid.userName,
140
142
  timestamp: Date.now(),
141
143
  },
142
144
  updatedAt: Date.now(),
@@ -9,5 +9,10 @@ export interface AuctionBidsTableProps {
9
9
  bids: BidDocument[];
10
10
  portal?: "buyer" | "store" | "admin";
11
11
  emptyLabel?: React.ReactNode;
12
+ /** Server-side pagination — the auction groups shown are already just the
13
+ * current page's worth of bids grouped by auction, not the full history. */
14
+ totalPages?: number;
15
+ currentPage?: number;
16
+ onPageChange?: (page: number) => void;
12
17
  }
13
- export declare function AuctionBidsTable({ bids, portal, emptyLabel, }: AuctionBidsTableProps): React.JSX.Element;
18
+ export declare function AuctionBidsTable({ bids, portal, emptyLabel, totalPages, currentPage, onPageChange, }: AuctionBidsTableProps): React.JSX.Element;
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useMemo, useState } from "react";
4
4
  import { ChevronRight } from "lucide-react";
5
5
  import { Badge, Button, Div, Row, Span, Stack, Text } from "../../../ui";
6
+ import { Pagination } from "../../../ui/components/Pagination";
6
7
  const __O = {
7
8
  hidden: "overflow-hidden",
8
9
  };
@@ -51,15 +52,18 @@ function groupByAuction(bids) {
51
52
  }
52
53
  function AuctionRow({ auction, portal, }) {
53
54
  const [expanded, setExpanded] = useState(false);
54
- const sorted = useMemo(() => [...auction.bids].sort((a, b) => b.bidAmount - a.bidAmount), [auction.bids]);
55
- const highest = sorted[0]?.bidAmount ?? 0;
55
+ // Most-recent bid first matches the caller's own newest-first ordering
56
+ // (see UserBidsPage) instead of re-sorting by amount, which silently threw
57
+ // that ordering away for the expanded per-auction row list.
58
+ const sorted = useMemo(() => [...auction.bids].sort((a, b) => +new Date(b.bidDate) - +new Date(a.bidDate)), [auction.bids]);
59
+ const highest = auction.bids.reduce((max, b) => Math.max(max, b.bidAmount), 0);
56
60
  const isWinning = auction.bids.some((b) => b.isWinning);
57
61
  return (_jsxs(Div, { className: `border border-[var(--appkit-color-border)] ${__O.hidden} bg-[var(--appkit-color-surface)]`, rounded: "xl", shadow: "sm", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setExpanded((v) => !v), gap: "lg", paddingX: "md", paddingY: "md", className: "w-full text-left", "aria-expanded": expanded, children: [_jsx(ChevronRight, { className: `shrink-0 text-[var(--appkit-color-text-muted)] transition-transform ${expanded ? "rotate-90" : ""}`, size: 16 }), _jsx(Div, { className: "flex-1 min-w-0", children: _jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", size: "sm", weight: "semibold", children: auction.productTitle }) }), _jsxs(Row, { gap: "sm", className: "shrink-0", children: [isWinning && (_jsx(Badge, { variant: "active", children: "Winning" })), _jsxs(Text, { variant: "secondary", size: "xs", children: [auction.bids.length, " bid", auction.bids.length !== 1 ? "s" : ""] }), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "semibold", children: formatBidAmount(highest) })] })] }), expanded && (_jsxs(Div, { className: "border-t border-[var(--appkit-color-border)]", children: [_jsxs(Div, { textSize: "xs", textWeight: "medium", paddingX: "x-md", paddingY: "y-xs", className: `grid text-[var(--appkit-color-text-muted)] uppercase tracking-wide border-b border-[var(--appkit-color-border-subtle)] ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [_jsx(Span, { children: portal === "buyer" ? "Amount" : "Bidder" }), portal !== "buyer" && _jsx(Span, { children: "Amount" }), _jsx(Span, { children: "Status" }), _jsx(Span, { className: "text-right", children: "Time" })] }), sorted.map((bid) => (_jsxs(Div, { layout: "grid", align: "center", paddingX: "x-md", paddingY: "y-xs-tall", className: `border-b border-[var(--appkit-color-border-subtle)] last:border-0 hover:bg-[var(--appkit-color-border-subtle)] transition-colors ${portal === "buyer" ? "[grid-template-columns:1fr_auto_auto]" : "[grid-template-columns:1fr_1fr_auto_auto]"}`, children: [portal !== "buyer" && (_jsx(Text, { className: "text-[var(--appkit-color-text)] truncate pr-[0.75rem]", size: "sm", children: bid.userName || bid.userId })), _jsx(Text, { className: "text-[var(--appkit-color-text)]", size: "sm", weight: "medium", children: formatBidAmount(bid.bidAmount) }), _jsx(Badge, { variant: STATUS_VARIANT[bid.status] ?? "pending", className: "capitalize", children: bid.status }), _jsx(Text, { variant: "secondary", size: "xs", align: "end", children: relDate(bid.bidDate) })] }, bid.id)))] }))] }));
58
62
  }
59
- export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", }) {
63
+ export function AuctionBidsTable({ bids, portal = "buyer", emptyLabel = "No bids found.", totalPages, currentPage, onPageChange, }) {
60
64
  const auctions = useMemo(() => groupByAuction(bids), [bids]);
61
65
  if (auctions.length === 0) {
62
66
  return (_jsx(Div, { padding: "y-6xl", className: "text-left", children: _jsx(Text, { variant: "secondary", children: emptyLabel }) }));
63
67
  }
64
- return (_jsx(Stack, { gap: "3", children: auctions.map((auction) => (_jsx(AuctionRow, { auction: auction, portal: portal }, auction.productId))) }));
68
+ return (_jsxs(Stack, { gap: "3", children: [auctions.map((auction) => (_jsx(AuctionRow, { auction: auction, portal: portal }, auction.productId))), totalPages !== undefined && totalPages > 1 && currentPage !== undefined && onPageChange && (_jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: onPageChange }) }))] }));
65
69
  }
@@ -31,6 +31,7 @@ import { MarketplaceAuctionGrid } from "./MarketplaceAuctionGrid";
31
31
  import { listReviewsBySeller } from "../../reviews/actions/review-actions";
32
32
  import { PlaceBidModalButton } from "./PlaceBidFormClient";
33
33
  import { CollapsibleBidHistory } from "./CollapsibleBidHistory";
34
+ import { LiveBidPrice } from "./LiveBidPrice";
34
35
  import { SublistingCarouselSection } from "../../products/components/SublistingCarouselSection";
35
36
  function toDescriptionHtml(raw) {
36
37
  if (!raw)
@@ -39,8 +40,8 @@ function toDescriptionHtml(raw) {
39
40
  return normalizeRichTextHtml(s);
40
41
  }
41
42
  function renderAuctionInfoPanel(props) {
42
- const { title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
43
- return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), isEnded ? (_jsx(Span, { color: "error", surface: "danger-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Ended" })) : (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Active" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", size: "2xl", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "sm", weight: "medium", rounded: "full", padding: "pill-md", surface: "subtle", color: "muted", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids"] })] }), endDate && _jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: [isEnded ? "Ended" : "Ends", " ", _jsx(Span, { weight: "medium", color: "muted", children: endDate.toLocaleString() })] })] }), buyNowPrice !== null && !isEnded && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: formatCurrency(buyNowPrice, currency) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
43
+ const { productId, title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref } = props;
44
+ return (_jsxs(Stack, { gap: "md", children: [_jsxs(Div, { children: [_jsxs(Row, { gap: "xs", wrap: true, className: "mb-2", children: [_jsx(Span, { size: "xs", weight: "semibold", className: CLS_LIVE_BADGE, children: "\uD83C\uDFF7\uFE0F Live Auction" }), isEnded ? (_jsx(Span, { color: "error", surface: "danger-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Ended" })) : (_jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "medium", className: "inline-block", padding: "pill-sm", rounded: "full", children: "Active" }))] }), _jsx(Heading, { level: 1, className: "leading-snug", smSize: "2xl", color: "primary", size: "xl", weight: "bold", children: title })] }), _jsxs(Div, { children: [_jsx(Text, { className: "mb-0.5", color: "muted", size: "xs", children: "Current bid" }), _jsx(LiveBidPrice, { productId: productId, currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "2xl", badgeSize: "sm" }), endDate && _jsxs(Text, { className: "mt-1.5", color: "muted", size: "sm", children: [isEnded ? "Ended" : "Ends", " ", _jsx(Span, { weight: "medium", color: "muted", children: endDate.toLocaleString() })] })] }), buyNowPrice !== null && !isEnded && (_jsxs(Row, { align: "center", gap: "sm", className: "border border-[var(--appkit-color-primary-200)] dark:border-[var(--appkit-color-primary-800)] bg-primary-50 dark:bg-primary-900/20", padding: "inlineSm", rounded: "lg", children: [_jsx(Span, { size: "xs", color: "muted", children: "Buy Now:" }), _jsx(Span, { size: "base", weight: "bold", className: "text-primary-700 dark:text-primary-300", children: formatCurrency(buyNowPrice, currency) })] })), _jsx(ProductFeatureBadges, { featured: featured, freeShipping: freeShipping, condition: condition ?? undefined, labels: { featured: "Featured", fasterDelivery: "Faster Delivery", ratedSeller: "Rated Seller", condition: "Condition", conditionNew: "New", conditionUsed: "Used", conditionBroken: "For Parts", conditionRefurbished: "Refurbished", returnable: "Returnable", freeShipping: "Free Shipping", codAvailable: "Cash on Delivery", emiAvailable: "EMI Available", wishlistCount: (n) => `${n} wishlisted`, categoryProductCount: (n, cat) => `${n} in ${cat}` } }), (categoryName || category || brand) && (_jsxs(Row, { gap: "sm", wrap: true, children: [category && _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: categoryName || category }), !category && categoryName && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: categoryName }), brand && brandSlug && _jsx(Link, { href: String(ROUTES.PUBLIC.BRAND_DETAIL(brandSlug)), className: "inline-flex items-center rounded-full border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-[var(--appkit-space-2-5)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium text-[var(--appkit-color-text-muted)] transition-colors hover:border-primary-300 hover:bg-primary-50 hover:text-primary-700 dark:hover:border-primary-700/60 dark:hover:bg-primary-900/20 dark:hover:text-primary-400", children: brand }), brand && !brandSlug && _jsx(Span, { layout: "inline-flex", size: "xs", weight: "medium", border: "subtle", rounded: "full", padding: "pill-sm-tall", surface: "muted", color: "muted", children: brand })] })), productFeatures && features.length > 0 && _jsx(FeatureBadgeList, { productFeatureIds: features, features: productFeatures }), !productFeatures && features.length > 0 && (_jsxs(Div, { border: "subtle", surface: "muted", padding: "inline", rounded: "xl", children: [_jsx(Text, { className: "mb-2 tracking-wide", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "About this item" }), _jsx(Ul, { spacing: "comfortable", size: "sm", color: "primary", children: features.map((f, i) => _jsxs(Li, { layout: "flex-start", gap: "2", children: [_jsx(Span, { className: "mt-0.5 flex-shrink-0 text-primary-500", children: "\u2022" }), f] }, i)) })] })), descriptionHtml && _jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "text-[length:var(--appkit-text-sm)] leading-relaxed text-[var(--appkit-color-text-muted)] line-clamp-4" }), safeSeller && (_jsx(Div, { className: `${__P.p3}`, border: "subtle", rounded: "xl", surface: "muted", children: _jsxs(Row, { justify: "between", align: "center", children: [_jsxs(Div, { children: [_jsx(Text, { className: "text-[10px] tracking-wide mb-0.5", color: "faint", transform: "uppercase", children: "Listed by" }), _jsx(Text, { size: "sm", weight: "semibold", color: "primary", children: safeSeller })] }), storeHref && _jsx(Link, { href: storeHref, className: "shrink-0 rounded-lg bg-primary/10 dark:bg-primary/20 px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-semibold text-primary-700 dark:text-primary-300 hover:bg-primary/20 dark:hover:bg-primary/30 transition-colors", children: "Visit Store \u2192" })] }) }))] }));
44
45
  }
45
46
  function renderAuctionStoreReviews(storeReviews) {
46
47
  if (storeReviews.length === 0)
@@ -53,7 +54,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
53
54
  ? (initialAuction ?? undefined)
54
55
  : await productRepository.findByIdOrSlug(id).catch(() => undefined);
55
56
  const bidsResult = product
56
- ? await listBidsByProduct(String(product.id), { pageSize: 20 }).catch(() => null)
57
+ ? await listBidsByProduct(String(product.id), { pageSize: 5 }).catch(() => null)
57
58
  : null;
58
59
  const storeId = product?.storeId;
59
60
  const storeReviews = storeId
@@ -83,6 +84,7 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
83
84
  : typeof p.mainImage === "string"
84
85
  ? [p.mainImage]
85
86
  : [];
87
+ const productVideo = p.video;
86
88
  const endDate = p.auctionEndDate ? new Date(p.auctionEndDate) : null;
87
89
  const isEnded = endDate ? endDate < new Date() : false;
88
90
  const bidCount = typeof p.bidCount === "number" ? p.bidCount : 0;
@@ -125,23 +127,18 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
125
127
  price: typeof p.price === "number" ? p.price : undefined,
126
128
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
127
129
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
128
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { size: "xl", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
130
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, video: productVideo, productName: title })), renderInfo: () => renderAuctionInfoPanel({ productId: String(product.id), title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "xl", badgeSize: "xs" }), !isEnded && (_jsxs(Span, { size: "xs", color: "muted", children: ["min increment ", formatCurrency(minBidIncrement, currency)] }))] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
129
131
  /* Read-only bid panel — shown when no bid action is wired (preview/demo) */
130
- _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && !bidsHaveStarted && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Stack, { className: `lg:hidden ${__P.p4}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: `${__P.p4} lg:hidden`, border: "subtle", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
132
+ _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && !bidsHaveStarted && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Stack, { className: `lg:hidden ${__P.p4}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsx(LiveBidPrice, { productId: String(product.id), currentBid: currentBid, bidCount: bidCount, currency: currency, isEnded: isEnded, priceSize: "base", badgeSize: "xs" }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: `${__P.p4} lg:hidden`, border: "subtle", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
131
133
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
132
134
  : undefined, 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, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
133
135
  id: s.id,
134
136
  label: s.title,
135
137
  content: _jsx(CustomSectionTabContent, { section: s }),
136
138
  })) })), renderBidHistory: () => {
137
- const bids = (bidsResult?.items ?? []).map((b) => ({
138
- id: String(b.id ?? ""),
139
- bidderId: String(b.userId ?? b.bidderId ?? ""),
140
- bidderName: (b.bidderName ?? b.userName),
141
- amount: (typeof b.bidAmount === "number" ? b.bidAmount : typeof b.amount === "number" ? b.amount : 0),
142
- placedAt: (b.bidDate ?? b.createdAt ?? b.bidAt ?? ""),
143
- }));
144
- return _jsx(CollapsibleBidHistory, { bids: bids, currency: currency });
139
+ if (!bidsResult)
140
+ return null;
141
+ return (_jsx(CollapsibleBidHistory, { productId: String(product.id), initialData: bidsResult, currency: currency }));
145
142
  }, renderRelated: () => {
146
143
  const now = new Date();
147
144
  const related = relatedDocs
@@ -1,8 +1,29 @@
1
1
  import React from "react";
2
2
  import type { BidHistoryEntry } from "../../products/components/BidHistory";
3
+ import { type BidListResponse } from "../hooks/useBids";
4
+ interface RawBidLike {
5
+ id?: unknown;
6
+ userId?: unknown;
7
+ bidderId?: unknown;
8
+ userName?: unknown;
9
+ bidderName?: unknown;
10
+ bidAmount?: unknown;
11
+ amount?: unknown;
12
+ bidDate?: unknown;
13
+ createdAt?: unknown;
14
+ bidAt?: unknown;
15
+ }
16
+ /** Shared shape mapper — SSR's raw bid docs and useBids()'s client-fetched
17
+ * pages both go through this so every page (SSR page 1, client pages 2+)
18
+ * renders identically. */
19
+ export declare function toBidHistoryEntry(b: RawBidLike): BidHistoryEntry;
3
20
  interface CollapsibleBidHistoryProps {
4
- bids: BidHistoryEntry[];
21
+ productId: string;
22
+ /** SSR page-1 result — seeds `useBids()`'s cache so opening the section
23
+ * doesn't trigger a redundant fetch for the page already rendered. */
24
+ initialData: BidListResponse;
5
25
  currency: string;
26
+ pageSize?: number;
6
27
  }
7
- export declare function CollapsibleBidHistory({ bids, currency }: CollapsibleBidHistoryProps): React.JSX.Element;
28
+ export declare function CollapsibleBidHistory({ productId, initialData, currency, pageSize }: CollapsibleBidHistoryProps): React.JSX.Element;
8
29
  export {};
@@ -1,12 +1,30 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from "react";
4
- import { Button, Div, Span } from "../../../ui";
4
+ import { Button, Div, Row, Span } from "../../../ui";
5
+ import { Pagination } from "../../../ui/components/Pagination";
5
6
  import { BidHistory } from "../../products/components/BidHistory";
7
+ import { useBids } from "../hooks/useBids";
6
8
  const __O = {
7
9
  hidden: "overflow-hidden",
8
10
  };
9
- export function CollapsibleBidHistory({ bids, currency }) {
11
+ /** Shared shape mapper SSR's raw bid docs and useBids()'s client-fetched
12
+ * pages both go through this so every page (SSR page 1, client pages 2+)
13
+ * renders identically. */
14
+ export function toBidHistoryEntry(b) {
15
+ return {
16
+ id: String(b.id ?? ""),
17
+ bidderId: String(b.userId ?? b.bidderId ?? ""),
18
+ bidderName: (b.bidderName ?? b.userName),
19
+ amount: typeof b.bidAmount === "number" ? b.bidAmount : typeof b.amount === "number" ? b.amount : 0,
20
+ placedAt: (b.bidDate ?? b.createdAt ?? b.bidAt ?? ""),
21
+ };
22
+ }
23
+ export function CollapsibleBidHistory({ productId, initialData, currency, pageSize = 5 }) {
10
24
  const [open, setOpen] = useState(false);
11
- return (_jsxs(Div, { className: `mt-6 ${__O.hidden}`, border: "subtle", rounded: "xl", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", rounded: "none", className: "w-full justify-between bg-[var(--appkit-color-surface)]/60 hover:bg-[var(--appkit-color-surface-elevated)]", "aria-expanded": open, children: [_jsxs(Span, { size: "sm", weight: "semibold", className: "tracking-wide", color: "muted", transform: "uppercase", children: ["Bid History", bids.length > 0 && (_jsx(Span, { size: "xs", weight: "medium", className: "ml-2 normal-case tracking-normal", rounded: "full", padding: "pill-xs", surface: "subtle", color: "muted", children: bids.length }))] }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform duration-200 ${open ? "rotate-180" : ""}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), open && (_jsx(Div, { surface: "default", padding: "md", children: _jsx(BidHistory, { bids: bids, isEmpty: bids.length === 0, currency: currency }) }))] }));
25
+ const [page, setPage] = useState(1);
26
+ const { bids: rawBids, totalPages, isLoading } = useBids({ productId, page, pageSize }, { initialData, enabled: open });
27
+ const bids = rawBids.map(toBidHistoryEntry);
28
+ const total = initialData.total;
29
+ return (_jsxs(Div, { className: `mt-6 ${__O.hidden}`, border: "subtle", rounded: "xl", children: [_jsxs(Button, { type: "button", variant: "ghost", onClick: () => setOpen((v) => !v), paddingX: "md", paddingY: "md", rounded: "none", className: "w-full justify-between bg-[var(--appkit-color-surface)]/60 hover:bg-[var(--appkit-color-surface-elevated)]", "aria-expanded": open, children: [_jsxs(Span, { size: "sm", weight: "semibold", className: "tracking-wide", color: "muted", transform: "uppercase", children: ["Bid History", total > 0 && (_jsx(Span, { size: "xs", weight: "medium", className: "ml-2 normal-case tracking-normal", rounded: "full", padding: "pill-xs", surface: "subtle", color: "muted", children: total }))] }), _jsx("svg", { className: `h-4 w-4 text-zinc-400 transition-transform duration-200 ${open ? "rotate-180" : ""}`, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19 9l-7 7-7-7" }) })] }), open && (_jsxs(Div, { surface: "default", padding: "md", children: [_jsx(BidHistory, { bids: bids, isLoading: isLoading && bids.length === 0, isEmpty: !isLoading && bids.length === 0, currency: currency }), totalPages > 1 && (_jsx(Row, { justify: "center", className: "mt-4", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: setPage, size: "sm" }) }))] }))] }));
12
30
  }
@@ -0,0 +1,19 @@
1
+ export interface LiveBidPriceProps {
2
+ productId: string;
3
+ currentBid: number;
4
+ bidCount: number;
5
+ currency: string;
6
+ isEnded: boolean;
7
+ /** Matches the appkit `<Span size>` scale — pick to match the call site. */
8
+ priceSize?: "base" | "lg" | "xl" | "2xl";
9
+ badgeSize?: "xs" | "sm";
10
+ }
11
+ /**
12
+ * LiveBidPrice — the current-bid + bid-count pill, kept live via the
13
+ * `/auction-bids/{id}` SSE channel. Replaces 3 near-identical static
14
+ * SSR-only displays on the auction detail page (info panel, desktop and
15
+ * mobile compact bid-summary cards) so every viewer watching the page sees
16
+ * a new bid the instant it's placed, not just after their own bid or a
17
+ * page reload.
18
+ */
19
+ export declare function LiveBidPrice({ productId, currentBid, bidCount, currency, isEnded, priceSize, badgeSize, }: LiveBidPriceProps): import("react").JSX.Element;