@mohasinac/appkit 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/_internal/client/features/layout/navActive.d.ts +17 -0
  2. package/dist/_internal/client/features/layout/navActive.js +33 -0
  3. package/dist/_internal/server/features/auth/actions.d.ts +0 -1
  4. package/dist/_internal/server/features/auth/actions.js +0 -1
  5. package/dist/_internal/server/features/bundles/data.d.ts +22 -4
  6. package/dist/_internal/server/features/bundles/data.js +41 -4
  7. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  8. package/dist/_internal/server/features/bundles/index.js +1 -1
  9. package/dist/_internal/server/features/checkout/actions.js +7 -78
  10. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  11. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  12. package/dist/_internal/server/features/orders/adapters.js +1 -14
  13. package/dist/_internal/server/features/products/actions.js +8 -3
  14. package/dist/_internal/server/features/products/service.d.ts +15 -0
  15. package/dist/_internal/server/features/products/service.js +41 -0
  16. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  17. package/dist/_internal/server/functions/firestore.js +17 -1
  18. package/dist/_internal/server/functions/scheduled.d.ts +2 -5
  19. package/dist/_internal/server/functions/scheduled.js +6 -30
  20. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  21. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  22. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  23. package/dist/_internal/server/jobs/core/index.js +4 -4
  24. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  25. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  26. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  27. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +2 -2
  28. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  29. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  30. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  31. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  32. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  33. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  34. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -4
  35. package/dist/_internal/server/jobs/handlers/index.js +8 -6
  36. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  37. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  38. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  39. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  40. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  41. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  42. package/dist/_internal/shared/actions/action-registry.js +40 -10
  43. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  44. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  45. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  46. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  47. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  48. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  49. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  50. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  51. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  52. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  53. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  54. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  55. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  56. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  57. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  58. package/dist/constants/api-endpoints.d.ts +6 -9
  59. package/dist/constants/api-endpoints.js +5 -5
  60. package/dist/constants/field-names.d.ts +17 -1
  61. package/dist/constants/field-names.js +17 -1
  62. package/dist/features/account/components/UserSidebar.js +21 -9
  63. package/dist/features/account/hooks/useNotifications.js +5 -0
  64. package/dist/features/admin/actions/admin-actions.js +6 -0
  65. package/dist/features/admin/actions/notification-actions.js +1 -2
  66. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  67. package/dist/features/admin/components/AdminGuideHubView.js +15 -1
  68. package/dist/features/admin/components/AdminPaymentsGuideView.d.ts +2 -0
  69. package/dist/features/admin/components/AdminPaymentsGuideView.js +36 -0
  70. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  71. package/dist/features/admin/components/AdminSidebar.js +18 -9
  72. package/dist/features/admin/components/AdminSiteSettingsView.js +33 -6
  73. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  74. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +2 -0
  75. package/dist/features/admin/components/AdminWhatsAppGuideView.js +35 -0
  76. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  77. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  78. package/dist/features/admin/schemas/firestore.d.ts +13 -5
  79. package/dist/features/admin/schemas/firestore.js +1 -2
  80. package/dist/features/auctions/actions/bid-actions.js +3 -1
  81. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  82. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  83. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  84. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  85. package/dist/features/auctions/components/CollapsibleBidHistory.js +22 -3
  86. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  87. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  88. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  89. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  90. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  91. package/dist/features/auctions/hooks/useBids.js +32 -0
  92. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  93. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  94. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  95. package/dist/features/auth/checkout-value-otp.js +14 -10
  96. package/dist/features/auth/server.d.ts +0 -1
  97. package/dist/features/auth/server.js +0 -1
  98. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  99. package/dist/features/blog/api/[slug]/route.js +12 -3
  100. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  101. package/dist/features/blog/components/BlogPostView.js +5 -4
  102. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  103. package/dist/features/blog/hooks/useBlog.js +2 -0
  104. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  105. package/dist/features/blog/repository/blog.repository.js +44 -0
  106. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  107. package/dist/features/blog/schemas/firestore.js +1 -0
  108. package/dist/features/blog/types/index.d.ts +2 -0
  109. package/dist/features/cart/components/index.d.ts +0 -2
  110. package/dist/features/cart/components/index.js +0 -1
  111. package/dist/features/cart/hooks/useCartCount.js +22 -5
  112. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  113. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  114. package/dist/features/cart/utils/guest-cart.js +10 -0
  115. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  116. package/dist/features/cart/utils/pending-ops.js +46 -5
  117. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  118. package/dist/features/categories/components/BundleDetailView.js +4 -2
  119. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  120. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  121. package/dist/features/checkout/actions/index.d.ts +0 -1
  122. package/dist/features/checkout/actions/index.js +0 -1
  123. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  124. package/dist/features/events/actions/event-actions.d.ts +2 -0
  125. package/dist/features/events/actions/event-actions.js +5 -0
  126. package/dist/features/events/components/EventCard.js +2 -3
  127. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  128. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  129. package/dist/features/events/components/index.d.ts +2 -0
  130. package/dist/features/events/components/index.js +1 -0
  131. package/dist/features/events/repository/events.repository.d.ts +6 -0
  132. package/dist/features/events/repository/events.repository.js +21 -0
  133. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  134. package/dist/features/history/utils/guest-history.d.ts +1 -1
  135. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  136. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  137. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  138. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  139. package/dist/features/layout/AppLayoutShell.js +44 -10
  140. package/dist/features/layout/NavbarLayout.js +1 -1
  141. package/dist/features/layout/TitleBarLayout.js +2 -2
  142. package/dist/features/media/MediaVideo.d.ts +7 -0
  143. package/dist/features/media/MediaVideo.js +36 -5
  144. package/dist/features/orders/components/OrdersList.js +1 -5
  145. package/dist/features/orders/schemas/firestore.d.ts +2 -8
  146. package/dist/features/orders/schemas/index.d.ts +3 -32
  147. package/dist/features/orders/schemas/index.js +1 -4
  148. package/dist/features/orders/types/index.d.ts +6 -9
  149. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  150. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  151. package/dist/features/products/actions/product-actions.js +13 -10
  152. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  153. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  154. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  155. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  156. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  157. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  158. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  159. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  160. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  161. package/dist/features/products/components/ProductForm.js +7 -14
  162. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  163. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  164. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  165. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  166. package/dist/features/products/components/ShowGroupSection.js +2 -2
  167. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  168. package/dist/features/products/components/index.d.ts +4 -2
  169. package/dist/features/products/components/index.js +1 -0
  170. package/dist/features/products/constants/action-defs.d.ts +1 -3
  171. package/dist/features/products/constants/action-defs.js +0 -5
  172. package/dist/features/products/repository/products.repository.d.ts +6 -0
  173. package/dist/features/products/repository/products.repository.js +20 -1
  174. package/dist/features/products/schemas/firestore.d.ts +28 -3
  175. package/dist/features/products/schemas/index.d.ts +11 -3
  176. package/dist/features/products/schemas/index.js +10 -2
  177. package/dist/features/products/types/index.d.ts +2 -1
  178. package/dist/features/reviews/actions/review-actions.js +1 -0
  179. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  180. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  181. package/dist/features/reviews/components/ReviewsList.js +2 -1
  182. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  183. package/dist/features/reviews/schemas/index.d.ts +6 -0
  184. package/dist/features/reviews/schemas/index.js +2 -0
  185. package/dist/features/reviews/types/index.d.ts +3 -0
  186. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  187. package/dist/features/scams/actions/scam-actions.js +3 -2
  188. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  189. package/dist/features/scams/components/ScamProfileView.js +2 -2
  190. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  191. package/dist/features/scams/repository/scammer.repository.js +24 -0
  192. package/dist/features/seller/actions/seller-actions.js +8 -0
  193. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  194. package/dist/features/seller/components/SellerSidebar.js +20 -9
  195. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  196. package/dist/features/stores/components/StoreGuideHubView.js +7 -1
  197. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  198. package/dist/features/stores/components/StoreNavTabs.js +22 -9
  199. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +3 -0
  200. package/dist/features/stores/components/StoreWhatsAppGuideView.js +12 -0
  201. package/dist/features/stores/components/index.d.ts +2 -0
  202. package/dist/features/stores/components/index.js +1 -0
  203. package/dist/features/tester/actions/index.d.ts +1 -0
  204. package/dist/features/tester/actions/index.js +1 -0
  205. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  206. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  207. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  208. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  209. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  210. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  211. package/dist/features/tester/components/index.d.ts +2 -0
  212. package/dist/features/tester/components/index.js +1 -0
  213. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  214. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  215. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  216. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  217. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  218. package/dist/features/tester/schemas/firestore.js +8 -0
  219. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  220. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  221. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  222. package/dist/features/tester/seed-data/products-tester-seed-data.js +154 -9
  223. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +252 -15
  224. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  225. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  226. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  227. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  228. package/dist/index.d.ts +23 -22
  229. package/dist/index.js +31 -58
  230. package/dist/jobs.d.ts +1 -1
  231. package/dist/jobs.js +2 -2
  232. package/dist/next/routing/route-map.d.ts +9 -2
  233. package/dist/next/routing/route-map.js +4 -1
  234. package/dist/schemas/registry.d.ts +6 -26
  235. package/dist/seed/bids-seed-data.js +14 -2
  236. package/dist/seed/blog-posts-seed-data.js +2 -1
  237. package/dist/seed/categories-seed-data.js +11 -2
  238. package/dist/seed/events-seed-data.js +11 -11
  239. package/dist/seed/faq-seed-data.js +5 -1
  240. package/dist/seed/products-auctions-seed-data.js +2 -2
  241. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  242. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  243. package/dist/seed/products-standard-seed-data.js +80 -3
  244. package/dist/seed/scammers-seed-data.js +75 -2
  245. package/dist/seed/site-settings-seed-data.js +5 -2
  246. package/dist/server-entry.d.ts +0 -1
  247. package/dist/server-entry.js +0 -1
  248. package/dist/server.d.ts +4 -20
  249. package/dist/server.js +10 -57
  250. package/dist/styles.css +1 -277
  251. package/dist/tailwind-utilities.css +1 -1
  252. package/dist/ui/components/BaseListingCard.js +1 -1
  253. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  254. package/dist/ui/components/ImageLightbox.js +6 -3
  255. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  256. package/dist/ui/components/ListingToolbar.js +5 -2
  257. package/dist/ui/components/StickyToolbar.js +13 -8
  258. package/package.json +1 -1
@@ -3,12 +3,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React, { useMemo } from "react";
6
- import { FilterChipGroup, Heading, ListingLayout, Span, Text } from "../../../ui";
6
+ import { useQueryClient } from "@tanstack/react-query";
7
+ import { FilterChipGroup, Heading, ListingLayout, RowActionMenu, Span, Text } from "../../../ui";
8
+ import { apiClient } from "../../../http";
9
+ import { useApiMutation } from "../../../client";
7
10
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
11
+ import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
8
12
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META, ROW_ACTION_ID } from "../../products/constants/action-defs";
9
13
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
10
14
  import { DataListingView } from "./DataListingView";
11
15
  import { AdminTesterChecklistItemEditorView } from "./AdminTesterChecklistItemEditorView";
16
+ const LISTING_QUERY_KEY = ["admin", "tester-checklist-items", "listing"];
12
17
  const COLUMNS = [
13
18
  {
14
19
  key: "primary",
@@ -36,14 +41,28 @@ const COLUMNS = [
36
41
  },
37
42
  ];
38
43
  export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
44
+ const queryClient = useQueryClient();
45
+ const reopenMutation = useApiMutation({
46
+ mutationFn: async (id) => {
47
+ await apiClient.post(ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEM_REOPEN(id), {});
48
+ },
49
+ successMessage: "Case reopened for retest.",
50
+ onSuccess: () => {
51
+ queryClient.invalidateQueries({ queryKey: LISTING_QUERY_KEY });
52
+ },
53
+ });
39
54
  const config = useMemo(() => ({
40
55
  portal: "admin",
41
56
  title: "Tester Checklist",
42
57
  searchPlaceholder: "Search test cases, groups, or pages",
43
58
  emptyLabel: "No checklist items found",
44
- filterKeys: ["isActive"],
59
+ filterKeys: ["isActive", "bugConfirmed"],
60
+ // Bug-confirmed (and reopened-away) cases are always isActive:false —
61
+ // default to Active so they don't clutter the catalog; "Inactive" /
62
+ // "All" remain an explicit escape hatch via the filter chips below.
63
+ filterDefaults: { isActive: "true" },
45
64
  defaultSort: sortBy("order", "ASC"),
46
- queryKey: ["admin", "tester-checklist-items", "listing"],
65
+ queryKey: LISTING_QUERY_KEY,
47
66
  endpoint: ADMIN_ENDPOINTS.TESTER_CHECKLIST_ITEMS,
48
67
  sortOptions: [
49
68
  { value: "order", label: "Order" },
@@ -52,20 +71,43 @@ export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
52
71
  { value: "label", label: "Test case A–Z" },
53
72
  ],
54
73
  columns: COLUMNS,
55
- mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
56
- id: toStringValue(item.id, `checklist-${index}`),
57
- primary: toStringValue(item.label, "Untitled test case"),
58
- secondary: `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")}`,
59
- phase: typeof item.phase === "number" ? item.phase : 1,
60
- status: toStringValue(typeof item.isActive === "boolean"
61
- ? item.isActive
62
- ? "Active"
63
- : "Inactive"
64
- : item.status, "Active"),
65
- updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
66
- })),
74
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => {
75
+ const bugConfirmed = item.bugConfirmed === true;
76
+ const version = typeof item.version === "number" ? item.version : 1;
77
+ const status = bugConfirmed
78
+ ? "Bug Confirmed"
79
+ : toStringValue(typeof item.isActive === "boolean"
80
+ ? item.isActive
81
+ ? "Active"
82
+ : "Inactive"
83
+ : item.status, "Active");
84
+ const secondary = bugConfirmed
85
+ ? `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")} — 🐛 found by ${toStringValue(item.bugHunterName, "unknown tester")} (v${version})`
86
+ : `${toStringValue(item.groupLabel, "Uncategorized")} / ${toStringValue(item.pageLabel, "")}`;
87
+ return {
88
+ id: toStringValue(item.id, `checklist-${index}`),
89
+ primary: toStringValue(item.label, "Untitled test case"),
90
+ secondary,
91
+ phase: typeof item.phase === "number" ? item.phase : 1,
92
+ status,
93
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
94
+ bugConfirmed,
95
+ supersededByItemId: item.supersededByItemId
96
+ ? toStringValue(item.supersededByItemId, "")
97
+ : undefined,
98
+ };
99
+ }),
67
100
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
68
- buildFilters: (state) => state.isActive ? sieveFilter("isActive", SIEVE_OP.EQ, state.isActive) : undefined,
101
+ buildFilters: (state) => {
102
+ const parts = [];
103
+ if (state.isActive && state.isActive !== "All") {
104
+ parts.push(sieveFilter("isActive", SIEVE_OP.EQ, state.isActive));
105
+ }
106
+ if (state.bugConfirmed && state.bugConfirmed !== "All") {
107
+ parts.push(sieveFilter("bugConfirmed", SIEVE_OP.EQ, state.bugConfirmed));
108
+ }
109
+ return parts.join(",") || undefined;
110
+ },
69
111
  primaryAction: {
70
112
  label: "Add Test Case",
71
113
  onClick: ({ openCreatePanel }) => openCreatePanel(),
@@ -86,14 +128,24 @@ export function AdminTesterChecklistView({ children, onBulkDelete, ...props }) {
86
128
  onClick: handlers[id],
87
129
  }));
88
130
  },
89
- renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: [
90
- { id: "All", label: "All" },
91
- { id: "true", label: "Active" },
92
- { id: "false", label: "Inactive" },
93
- ], value: pendingFilters.isActive || "", onChange: (v) => setPendingFilters((p) => ({ ...p, isActive: v })) })),
131
+ renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsxs(Stack, { gap: "md", children: [_jsx(FilterChipGroup, { label: "Status", tabs: [
132
+ { id: "All", label: "All" },
133
+ { id: "true", label: "Active" },
134
+ { id: "false", label: "Inactive" },
135
+ ], value: pendingFilters.isActive || "", onChange: (v) => setPendingFilters((p) => ({ ...p, isActive: v })) }), _jsx(FilterChipGroup, { label: "Bug status", tabs: [
136
+ { id: "All", label: "All" },
137
+ { id: "true", label: "Bug Confirmed" },
138
+ ], value: pendingFilters.bugConfirmed || "", onChange: (v) => setPendingFilters((p) => ({ ...p, bugConfirmed: v })) })] })),
139
+ renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
140
+ {
141
+ label: ACTIONS.ADMIN["reopen-checklist-item"].label,
142
+ disabled: !row.bugConfirmed || !!row.supersededByItemId,
143
+ onClick: () => reopenMutation.mutate(row.id),
144
+ },
145
+ ] })),
94
146
  renderEditor: ({ editId, closePanel }) => (_jsx(AdminTesterChecklistItemEditorView, { itemId: editId ?? undefined, onSaved: closePanel, onDeleted: closePanel, embedded: true })),
95
147
  resolveEditorTitle: ({ isCreate }) => (isCreate ? "Add Test Case" : "Edit Test Case"),
96
- }), [onBulkDelete]);
148
+ }), [onBulkDelete, reopenMutation]);
97
149
  if (React.Children.count(children) > 0) {
98
150
  return (_jsxs(ListingLayout, { portal: "admin", ...props, children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Tester Checklist" }), children] }));
99
151
  }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminWhatsAppGuideView(): React.JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Building2, Smartphone, KeyRound, MessageSquare, Power, FlaskConical } from "lucide-react";
3
+ import { Alert, Code, Div, Heading, Li, Row, Section, Span, Stack, Table, Tbody, Td, Text, Th, Thead, Tr, Ul } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminWhatsAppGuideView() {
6
+ return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(MessageSquare, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "WhatsApp Integration" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Connecting a real Meta WhatsApp Business Cloud API account so the platform-level order-notification addon and admin purchase announcements actually deliver. Do these steps outside the codebase, then paste the resulting values into Site Settings \u2192 WhatsApp." })] }), [
7
+ {
8
+ Icon: Building2, title: "Meta Business Manager & App",
9
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Create or verify a Business account at ", _jsx(Span, { weight: "bold", children: "business.facebook.com" }), ". Complete Business Verification (legal name, address, phone) \u2014 most message-template categories require a verified business before they'll be approved."] }), _jsxs(Li, { children: ["In ", _jsx(Span, { weight: "bold", children: "developers.facebook.com" }), ", create an App and add the ", _jsx(Span, { weight: "bold", children: "WhatsApp" }), " product. This gives you a test phone number for development immediately."] })] })),
10
+ },
11
+ {
12
+ Icon: Smartphone, title: "Register the Real Business Phone Number",
13
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "WhatsApp Manager \u2192 Phone Numbers \u2192 Add phone number \u2192 verify via SMS or voice OTP." }), _jsx(Li, { children: "Complete Display Name review \u2014 Meta approves the shown business name before it goes live." })] })),
14
+ },
15
+ {
16
+ Icon: KeyRound, title: "System User & Permanent Access Token",
17
+ content: (_jsxs(_Fragment, { children: [_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Business Settings \u2192 Users \u2192 System Users \u2192 create a System User with ", _jsx(Span, { weight: "bold", children: "Admin" }), " role."] }), _jsxs(Li, { children: ["Assign it the WhatsApp Business Account asset with ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_business_messaging" }), " + ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "whatsapp_business_management" }), " permissions, then generate a token. System User tokens don't expire the way personal User tokens do, so this is the one to use in production."] })] }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-3", size: "sm", children: ["Copy the token into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 Cloud API System User Token" }), ", and the ", _jsx(Span, { weight: "bold", children: "Phone Number ID" }), " (WhatsApp Manager \u2192 API Setup) into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 Phone Number ID" }), "."] })] })),
18
+ },
19
+ {
20
+ Icon: MessageSquare, title: "Message Templates",
21
+ content: (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text-muted)] mb-3", size: "sm", children: "Meta's Cloud API only allows free-form text messages within a 24-hour window after the customer has messaged the business first. Any business-initiated proactive notification \u2014 order placed, shipped, delivered, cancelled, refund initiated \u2014 needs a pre-approved Message Template." }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mb-3", size: "sm", children: ["WhatsApp Manager \u2192 Message Templates \u2192 create one template per notification type, category ", _jsx(Span, { weight: "bold", children: "UTILITY" }), " (transactional order updates get cheaper, faster approval than MARKETING):"] }), _jsx(Div, { overflow: "x-auto", children: _jsxs(Table, { size: "sm", children: [_jsx(Thead, { children: _jsxs(Tr, { className: "border-b border-[var(--appkit-color-border)]", children: [_jsx(Th, { align: "left", paddingSide: "pr-md", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: "Notification type" }), _jsx(Th, { align: "left", className: "text-[var(--appkit-color-text)]", padding: "xs-tall", weight: "semibold", children: "Suggested template name" })] }) }), _jsx(Tbody, { size: "sm", color: "muted", children: [
22
+ ["Order placed", "order_placed_update"],
23
+ ["Order confirmed", "order_confirmed_update"],
24
+ ["Order shipped", "order_shipped_update"],
25
+ ["Order delivered", "order_delivered_update"],
26
+ ["Order cancelled", "order_cancelled_update"],
27
+ ["Refund initiated", "refund_initiated_update"],
28
+ ].map(([type, name]) => (_jsxs(Tr, { className: "border-b border-[var(--appkit-color-border)]/50", children: [_jsx(Td, { paddingSide: "pr-md", padding: "xs-tall", children: type }), _jsx(Td, { padding: "xs-tall", children: _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: name }) })] }, type))) })] }) }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-3", size: "sm", children: ["Give each template two ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "{{1}}" }), "/", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "{{2}}" }), " body variables \u2014 the runner substitutes the notification title and message text in that order. Submit for review; approval usually takes minutes to ~24 hours. Don't go live on a template until its status shows ", _jsx(Span, { weight: "bold", children: "APPROVED" }), "."] }), _jsxs(Text, { className: "text-[var(--appkit-color-text-muted)] mt-2", size: "sm", children: ["Once each template is approved, copy its exact template name (not the display label you gave it in Meta's UI) into ", _jsx(Span, { weight: "bold", children: "Site Settings \u2192 WhatsApp \u2192 [type] template name" }), ", and set the approved language code (e.g. ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "en" }), ") in ", _jsx(Span, { weight: "bold", children: "Template language code" }), "."] })] })),
29
+ },
30
+ {
31
+ Icon: Power, title: "Enable the Addon & Test",
32
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Fees \u2192 \"Offer the WhatsApp order-updates addon at checkout\"" }), " \u2014 turn this on once credentials + at least one template are configured. Set the fee amount (default \u20B910)."] }), _jsx(Li, { children: "Place a real test order with the addon checked. Confirm the buyer receives the templated WhatsApp message. Until real credentials + an approved template exist, the async delivery job will fail at the Meta API call with a clear credential/template error \u2014 that's expected, not a code bug." })] })),
33
+ },
34
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", children: [_jsxs(Row, { className: "border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", padding: "inlineLg", align: "center", gap: "3", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, size: "base", weight: "semibold", children: title })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: content })] }, title))), _jsx(Alert, { variant: "info", children: _jsxs(Row, { align: "center", gap: "sm", children: [_jsx(FlaskConical, { className: "w-4 h-4 flex-shrink-0" }), _jsx(Text, { size: "sm", children: "Store-level Meta Commerce Catalog sync (product catalog on WhatsApp) uses separate, per-store credentials \u2014 see the Seller Guide's \"WhatsApp Catalog Sync\" page (Store dashboard \u2192 Guides). It shares the same Meta Business Manager account but not the same access token. The inbound-reply webhook (buyer messages the business number within 24h) uses the free-text send path and doesn't need a template." })] }) })] }));
35
+ }
@@ -10,6 +10,10 @@ export interface AdminListingConfig<TResponse, TRow extends {
10
10
  getTotal?: (response: TResponse, rows: TRow[]) => number;
11
11
  buildFilters: (filterState: Record<string, string>) => string | undefined;
12
12
  initialView?: "grid" | "list" | "table";
13
+ /** Values used for filterKeys absent from the URL — lets a view default to
14
+ * a non-"All" filter state (e.g. hide inactive rows) on first load, while
15
+ * "Clear filters" (which writes an explicit "") still shows everything. */
16
+ filterDefaults?: Record<string, string>;
13
17
  }
14
18
  export declare function useAdminListing<TResponse, TRow extends {
15
19
  id: string;
@@ -6,13 +6,15 @@ import { usePanelUrlSync } from "../../../react/hooks/use-panel-url-sync";
6
6
  import { useDataViewMode } from "../../account/hooks/useDataViewMode";
7
7
  import { useAdminListingData } from "./useAdminListingData";
8
8
  export function useAdminListing(config) {
9
- const { filterKeys, defaultSort, pageSize: defaultPageSize = 25, queryKey, endpoint, mapRows, getTotal, buildFilters, } = config;
9
+ const { filterKeys, defaultSort, pageSize: defaultPageSize = 25, queryKey, endpoint, mapRows, getTotal, buildFilters, filterDefaults = {}, } = config;
10
10
  // Persisted, viewport-aware view-mode: below 768px defaults to "list"
11
11
  // (AdminViewCards' one-full-width-card-per-row layout) unless the caller
12
12
  // explicitly pins hideTableView (grid-only views like coupons); the user's
13
13
  // own explicit choice, once made, always wins and persists across visits.
14
14
  const { view, setView } = useDataViewMode(config.initialView ?? "table");
15
- const table = useUrlTable({ defaults: { pageSize: String(defaultPageSize), sort: defaultSort } });
15
+ const table = useUrlTable({
16
+ defaults: { pageSize: String(defaultPageSize), sort: defaultSort, ...filterDefaults },
17
+ });
16
18
  // Reactive — table.getNumber reads the URL param, so a page-size selector
17
19
  // (Pagination's pageSize/onPageSizeChange) actually takes effect instead
18
20
  // of always falling back to the static config default.
@@ -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 {
@@ -65,9 +65,8 @@ export declare const NOTIFICATION_FIELDS: {
65
65
  readonly OFFER_COUNTER_ACCEPTED: NotificationType;
66
66
  readonly REFUND_INITIATED: NotificationType;
67
67
  readonly ACCOUNT_ACTION: NotificationType;
68
- readonly PRIZE_REVEAL_READY: NotificationType;
68
+ readonly PRIZE_WON: NotificationType;
69
69
  readonly PRIZE_REVEAL_EXPIRED: NotificationType;
70
- readonly PRIZE_REVEAL_REMINDER: NotificationType;
71
70
  };
72
71
  readonly PRIORITY_VALUES: {
73
72
  readonly LOW: NotificationPriority;
@@ -489,7 +488,7 @@ export interface SiteSettingsDocument extends BaseDocument {
489
488
  /**
490
489
  * Image watermark configuration applied by the `/api/media/[...slug]` CDN
491
490
  * proxy. When absent the proxy falls back to text watermark "letitrip.in" at
492
- * 30% width with 20% opacity.
491
+ * 10% width with 10% opacity, centered.
493
492
  */
494
493
  watermark?: {
495
494
  /** `"text"` renders the `text` field; `"image"` overlays `imageUrl`. */
@@ -504,8 +503,17 @@ export interface SiteSettingsDocument extends BaseDocument {
504
503
  imageUrl?: string;
505
504
  /** Percentage of target image width (0–100). 0 disables the watermark. */
506
505
  size?: number;
507
- /** Percentage opacity (0–100). Default 20. */
506
+ /** Percentage opacity (0–100). Default 10. */
508
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;
509
517
  };
510
518
  /** Encrypted provider credentials � never return raw to the client. */
511
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",
@@ -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?: string | number;
6
+ userId?: string;
7
+ bidderId?: string;
8
+ userName?: string;
9
+ bidderName?: string;
10
+ bidAmount?: number;
11
+ amount?: number;
12
+ bidDate?: string | Date;
13
+ createdAt?: string | Date;
14
+ bidAt?: string | Date;
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,31 @@
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
+ const placedAtRaw = b.bidDate ?? b.createdAt ?? b.bidAt ?? "";
16
+ return {
17
+ id: String(b.id ?? ""),
18
+ bidderId: String(b.userId ?? b.bidderId ?? ""),
19
+ bidderName: b.bidderName ?? b.userName,
20
+ amount: typeof b.bidAmount === "number" ? b.bidAmount : typeof b.amount === "number" ? b.amount : 0,
21
+ placedAt: placedAtRaw instanceof Date ? placedAtRaw.toISOString() : placedAtRaw,
22
+ };
23
+ }
24
+ export function CollapsibleBidHistory({ productId, initialData, currency, pageSize = 5 }) {
10
25
  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 }) }))] }));
26
+ const [page, setPage] = useState(1);
27
+ const { bids: rawBids, totalPages, isLoading } = useBids({ productId, page, pageSize }, { initialData, enabled: open });
28
+ const bids = rawBids.map(toBidHistoryEntry);
29
+ const total = initialData.total;
30
+ 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
31
  }
@@ -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;
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Row, Span } from "../../../ui";
4
+ import { formatCurrency } from "../../../utils/number.formatter";
5
+ import { useLiveAuctionBid } from "../hooks/useLiveAuctionBid";
6
+ /**
7
+ * LiveBidPrice — the current-bid + bid-count pill, kept live via the
8
+ * `/auction-bids/{id}` SSE channel. Replaces 3 near-identical static
9
+ * SSR-only displays on the auction detail page (info panel, desktop and
10
+ * mobile compact bid-summary cards) so every viewer watching the page sees
11
+ * a new bid the instant it's placed, not just after their own bid or a
12
+ * page reload.
13
+ */
14
+ export function LiveBidPrice({ productId, currentBid, bidCount, currency, isEnded, priceSize = "xl", badgeSize = "xs", }) {
15
+ const live = useLiveAuctionBid(productId, currentBid, bidCount, { enabled: !isEnded });
16
+ return (_jsxs(Row, { align: "center", gap: "sm", wrap: true, children: [_jsx(Span, { weight: "bold", className: "text-primary-600 dark:text-primary-400", size: priceSize, children: formatCurrency(live.currentBid, currency) }), _jsxs(Span, { size: badgeSize, weight: "medium", rounded: "full", padding: "pill-md", surface: "subtle", color: "muted", children: [live.bidCount, " ", live.bidCount === 1 ? "bid" : "bids"] })] }));
17
+ }
@@ -18,7 +18,7 @@ export interface PlaceBidFormClientProps {
18
18
  onPlaceBid: (input: PlaceBidInput) => Promise<unknown>;
19
19
  onBuyNow?: () => Promise<unknown>;
20
20
  }
21
- export declare function PlaceBidFormClient({ productId, currentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidsHaveStarted, bidCount, tags, onPlaceBid, onBuyNow, }: PlaceBidFormClientProps): React.JSX.Element;
21
+ export declare function PlaceBidFormClient({ productId, currentBid: ssrCurrentBid, startingBid, minBidIncrement, currency, isEnded, buyNowPrice, bidsHaveStarted, bidCount: ssrBidCount, tags, onPlaceBid, onBuyNow, }: PlaceBidFormClientProps): React.JSX.Element;
22
22
  /**
23
23
  * `PlaceBidModalButton` — opens a modal that hosts the same PlaceBidFormClient.
24
24
  *