@mohasinac/appkit 4.11.2 → 4.12.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 (246) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +40 -3
  2. package/dist/_internal/server/features/checkout/actions.js +232 -47
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/orders/adapters.js +20 -0
  5. package/dist/_internal/server/features/products/data.d.ts +12 -3
  6. package/dist/_internal/server/features/products/data.js +55 -4
  7. package/dist/_internal/server/features/products/index.d.ts +1 -1
  8. package/dist/_internal/server/features/products/index.js +1 -1
  9. package/dist/_internal/server/features/products/list-public.d.ts +47 -0
  10. package/dist/_internal/server/features/products/list-public.js +131 -6
  11. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  12. package/dist/_internal/server/features/reviews/data.js +3 -2
  13. package/dist/_internal/server/features/tester/visibility.js +3 -3
  14. package/dist/_internal/server/jobs/core/auctionSettlement.js +5 -2
  15. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  16. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  17. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  18. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  19. package/dist/_internal/shared/checkout/lanes.d.ts +20 -0
  20. package/dist/_internal/shared/checkout/lanes.js +28 -0
  21. package/dist/_internal/shared/features/checkout/config.d.ts +4 -0
  22. package/dist/_internal/shared/features/checkout/config.js +8 -0
  23. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  24. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  25. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  26. package/dist/_internal/shared/features/reviews/config.js +8 -1
  27. package/dist/_internal/shared/fees/calculator.d.ts +34 -2
  28. package/dist/_internal/shared/fees/calculator.js +45 -1
  29. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  30. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  31. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  32. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  33. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  34. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  35. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  36. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  37. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  38. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  39. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  40. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  41. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  42. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  43. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  44. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  45. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  46. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  47. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  48. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  49. package/dist/client.d.ts +12 -2
  50. package/dist/client.js +8 -2
  51. package/dist/constants/api-endpoints.d.ts +6 -0
  52. package/dist/constants/api-endpoints.js +2 -0
  53. package/dist/constants/field-names.d.ts +12 -0
  54. package/dist/constants/field-names.js +12 -0
  55. package/dist/constants/table-keys.d.ts +2 -0
  56. package/dist/constants/table-keys.js +2 -0
  57. package/dist/features/account/components/UserOrdersView.js +27 -5
  58. package/dist/features/admin/components/AdminAddressesView.js +21 -11
  59. package/dist/features/admin/components/AdminArtView.d.ts +9 -0
  60. package/dist/features/admin/components/AdminArtView.js +11 -35
  61. package/dist/features/admin/components/AdminClassifiedView.d.ts +9 -0
  62. package/dist/features/admin/components/AdminClassifiedView.js +11 -35
  63. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  64. package/dist/features/admin/components/AdminDigitalCodesView.d.ts +9 -0
  65. package/dist/features/admin/components/AdminDigitalCodesView.js +11 -42
  66. package/dist/features/admin/components/AdminLiveView.d.ts +9 -0
  67. package/dist/features/admin/components/AdminLiveView.js +11 -41
  68. package/dist/features/admin/components/AdminOrderEditorView.d.ts +8 -1
  69. package/dist/features/admin/components/AdminOrderEditorView.js +4 -3
  70. package/dist/features/admin/components/AdminOrdersView.js +14 -1
  71. package/dist/features/admin/components/AdminPaymentMethodsView.js +18 -11
  72. package/dist/features/admin/components/AdminProductsView.js +34 -18
  73. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  74. package/dist/features/admin/components/AdminSiteSettingsView.js +4 -2
  75. package/dist/features/admin/components/AdminStickersView.d.ts +9 -0
  76. package/dist/features/admin/components/AdminStickersView.js +11 -35
  77. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  78. package/dist/features/admin/constants/filter-tabs.js +22 -9
  79. package/dist/features/admin/hooks/useAdminListing.js +7 -2
  80. package/dist/features/admin/schemas/firestore.d.ts +5 -0
  81. package/dist/features/admin/schemas/firestore.js +1 -0
  82. package/dist/features/auctions/actions/bid-actions.js +2 -2
  83. package/dist/features/auctions/components/AuctionBidsTable.js +1 -1
  84. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  85. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  86. package/dist/features/cart/components/CartDrawer.d.ts +9 -1
  87. package/dist/features/cart/components/CartDrawer.js +4 -3
  88. package/dist/features/cart/components/CartPriceBreakdown.d.ts +61 -0
  89. package/dist/features/cart/components/CartPriceBreakdown.js +25 -0
  90. package/dist/features/cart/components/StoreAddonsPicker.d.ts +47 -0
  91. package/dist/features/cart/components/StoreAddonsPicker.js +21 -0
  92. package/dist/features/cart/components/index.d.ts +4 -0
  93. package/dist/features/cart/components/index.js +2 -0
  94. package/dist/features/cart/repository/cart.repository.d.ts +20 -2
  95. package/dist/features/cart/repository/cart.repository.js +49 -0
  96. package/dist/features/cart/schemas/firestore.d.ts +26 -2
  97. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  98. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  99. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  100. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  101. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  102. package/dist/features/contact/email.js +5 -1
  103. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  104. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  105. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  106. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  107. package/dist/features/layout/AppLayoutShell.js +9 -2
  108. package/dist/features/layout/BottomActions.js +62 -6
  109. package/dist/features/layout/BottomActionsContext.d.ts +26 -0
  110. package/dist/features/layout/BottomActionsContext.js +18 -1
  111. package/dist/features/layout/hooks/useBottomActions.d.ts +12 -1
  112. package/dist/features/layout/hooks/useBottomActions.js +14 -1
  113. package/dist/features/layout/index.d.ts +1 -1
  114. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  115. package/dist/features/orders/components/OrderAddonBadges.d.ts +36 -0
  116. package/dist/features/orders/components/OrderAddonBadges.js +28 -0
  117. package/dist/features/orders/components/index.d.ts +1 -0
  118. package/dist/features/orders/components/index.js +1 -0
  119. package/dist/features/orders/repository/orders.repository.d.ts +36 -0
  120. package/dist/features/orders/repository/orders.repository.js +18 -0
  121. package/dist/features/orders/types/index.d.ts +38 -0
  122. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  123. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  124. package/dist/features/products/components/ArtStickersListView.js +5 -4
  125. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  126. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  127. package/dist/features/products/components/ListingBottomActions.js +25 -0
  128. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  129. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  130. package/dist/features/products/components/PrizeDrawsListingView.js +18 -33
  131. package/dist/features/products/components/ProductDetailActions.js +4 -0
  132. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  133. package/dist/features/products/components/ProductsIndexListing.js +87 -11
  134. package/dist/features/products/components/ProductsIndexPageView.js +14 -2
  135. package/dist/features/products/config/listing-type-listing-config.d.ts +31 -0
  136. package/dist/features/products/config/listing-type-listing-config.js +74 -0
  137. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  138. package/dist/features/products/constants/listing-tabs.js +71 -58
  139. package/dist/features/products/constants/sieve.d.ts +104 -1
  140. package/dist/features/products/constants/sieve.js +27 -7
  141. package/dist/features/products/hooks/useProducts.js +14 -0
  142. package/dist/features/products/repository/products.repository.d.ts +45 -1
  143. package/dist/features/products/repository/products.repository.js +26 -3
  144. package/dist/features/products/types/index.d.ts +15 -0
  145. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  146. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  147. package/dist/features/products/utils/listing-type.d.ts +24 -0
  148. package/dist/features/products/utils/listing-type.js +48 -0
  149. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  150. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  151. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  152. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  153. package/dist/features/promotions/actions/index.d.ts +1 -0
  154. package/dist/features/promotions/actions/index.js +1 -0
  155. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  156. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  157. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  158. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  159. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  160. package/dist/features/promotions/components/index.d.ts +2 -0
  161. package/dist/features/promotions/components/index.js +1 -0
  162. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  163. package/dist/features/promotions/constants/coupon-help.js +42 -0
  164. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  165. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  166. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  167. package/dist/features/promotions/schemas/firestore.js +1 -1
  168. package/dist/features/promotions/schemas/index.d.ts +0 -20
  169. package/dist/features/promotions/schemas/index.js +0 -1
  170. package/dist/features/reviews/actions/review-actions.js +5 -1
  171. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  172. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  173. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  174. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  175. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  176. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  177. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  178. package/dist/features/reviews/components/index.d.ts +2 -0
  179. package/dist/features/reviews/components/index.js +1 -0
  180. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  181. package/dist/features/reviews/hooks/useReviews.js +14 -6
  182. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  183. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  184. package/dist/features/reviews/types/index.d.ts +8 -1
  185. package/dist/features/seller/components/SellerOrdersView.js +2 -1
  186. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  187. package/dist/features/seller/components/SellerProductsView.js +21 -18
  188. package/dist/features/stores/actions/store-query-actions.js +1 -1
  189. package/dist/features/stores/components/StoreArtStickersPageView.d.ts +21 -0
  190. package/dist/features/stores/components/StoreArtStickersPageView.js +30 -0
  191. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  192. package/dist/features/stores/components/StoreAuctionsPageView.d.ts +4 -1
  193. package/dist/features/stores/components/StoreAuctionsPageView.js +17 -17
  194. package/dist/features/stores/components/StoreClassifiedsListing.js +7 -0
  195. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -1
  196. package/dist/features/stores/components/StoreClassifiedsPageView.js +16 -12
  197. package/dist/features/stores/components/StoreDetailLayoutView.js +4 -3
  198. package/dist/features/stores/components/StoreDigitalCodesListing.js +5 -0
  199. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -1
  200. package/dist/features/stores/components/StoreDigitalCodesPageView.js +16 -12
  201. package/dist/features/stores/components/StoreLiveItemsListing.js +7 -0
  202. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -1
  203. package/dist/features/stores/components/StoreLiveItemsPageView.js +16 -12
  204. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  205. package/dist/features/stores/components/StorePreOrdersPageView.d.ts +4 -1
  206. package/dist/features/stores/components/StorePreOrdersPageView.js +17 -17
  207. package/dist/features/stores/components/StorePrizeDrawsPageView.d.ts +4 -8
  208. package/dist/features/stores/components/StorePrizeDrawsPageView.js +17 -22
  209. package/dist/features/stores/components/StoreProductsListing.d.ts +7 -1
  210. package/dist/features/stores/components/StoreProductsListing.js +3 -2
  211. package/dist/features/stores/components/StoreProductsPageView.d.ts +1 -1
  212. package/dist/features/stores/components/StoreProductsPageView.js +16 -21
  213. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  214. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  215. package/dist/features/stores/schemas/index.d.ts +2 -2
  216. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  217. package/dist/features/tester/seed-data/index.d.ts +1 -0
  218. package/dist/features/tester/seed-data/index.js +1 -0
  219. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  220. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  221. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +939 -1
  222. package/dist/index.d.ts +9 -3
  223. package/dist/index.js +11 -2
  224. package/dist/next/routing/route-map.d.ts +2 -0
  225. package/dist/next/routing/route-map.js +5 -0
  226. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  227. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  228. package/dist/schemas/registry.d.ts +0 -12
  229. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  230. package/dist/seed/coupons-seed-data.js +29 -10
  231. package/dist/seed/faq-seed-data.js +2 -2
  232. package/dist/seed/manifest.js +2 -2
  233. package/dist/seed/reviews-seed-data.js +52 -0
  234. package/dist/server-entry.d.ts +1 -0
  235. package/dist/server-entry.js +1 -0
  236. package/dist/styles.css +55 -39
  237. package/dist/tailwind-utilities.css +1 -1
  238. package/dist/ui/components/Checkbox.d.ts +10 -1
  239. package/dist/ui/components/Checkbox.js +14 -2
  240. package/dist/ui/components/Checkbox.style.css +11 -0
  241. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  242. package/dist/ui/components/FilterChipGroup.js +33 -4
  243. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  244. package/dist/ui/components/HorizontalScroller.js +9 -5
  245. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  246. package/package.json +1 -1
@@ -13,6 +13,8 @@ import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firest
13
13
  import { PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
14
14
  import { PRODUCT_FIELDS } from "../../../../constants/field-names";
15
15
  import { SIEVE_OP, sieveAnd, sieveFilter, sortBy } from "../../../../utils/sieve-builder";
16
+ import { REVIEW_FIELDS } from "../../../../constants/field-names";
17
+ import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/config";
16
18
  // ---------------------------------------------------------------------------
17
19
  // Request-scoped cache — each function is deduplicated per RSC render tree.
18
20
  // Both generateMetadata() and the page component get the same promise.
@@ -26,8 +28,8 @@ export const getProductForDetail = cache(async (slugOrId) => {
26
28
  return product;
27
29
  });
28
30
  /** Fetch the first page of approved reviews for a product, deduped per request. */
29
- export const getReviewsForProduct = cache(async (productId) => {
30
- return reviewRepository.findApprovedByProduct(productId).catch(() => []);
31
+ export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
32
+ return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
31
33
  });
32
34
  /** Firestore review doc → client Review shape. Shared by every listing-type detail page's reviews tab. */
33
35
  export function toReview(doc) {
@@ -57,10 +59,59 @@ export function toReview(doc) {
57
59
  };
58
60
  }
59
61
  /** Fetch + map approved reviews for a product straight to the client Review shape, deduped per request. */
60
- export const getReviewItemsForProduct = cache(async (productId) => {
61
- const docs = await reviewRepository.findApprovedByProduct(productId).catch(() => []);
62
+ export const getReviewItemsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
63
+ const docs = await reviewRepository
64
+ .findApprovedByProduct(productId, limit)
65
+ .catch(() => []);
62
66
  return docs.map(toReview);
63
67
  });
68
+ /**
69
+ * Page 1 of a product's approved reviews, shaped exactly like `GET /api/reviews?productId=`
70
+ * so it can seed `<ReviewsListingPanel initialData>` without a client round-trip.
71
+ *
72
+ * The defaults here MUST match the panel's bare (unfiltered, page-1) query defaults —
73
+ * `staleTime: Infinity` freezes whatever `initialData` is handed over, so a mismatch would
74
+ * pin the wrong rows into the cache (Recurrent Root Cause #30).
75
+ */
76
+ export const getReviewPageForProduct = cache(async (productId, pageSize = REVIEWS_DETAIL_PAGE_SIZE) => {
77
+ const empty = {
78
+ items: [],
79
+ total: 0,
80
+ page: 1,
81
+ pageSize,
82
+ totalPages: 1,
83
+ hasMore: false,
84
+ averageRating: 0,
85
+ ratingDistribution: {},
86
+ totalApproved: 0,
87
+ };
88
+ try {
89
+ const [summary, result] = await Promise.all([
90
+ reviewRepository.getApprovedRatingSummary(productId),
91
+ reviewRepository.listForProduct(productId, {
92
+ filters: sieveFilter(REVIEW_FIELDS.STATUS, SIEVE_OP.EQ, "approved"),
93
+ sorts: sortBy(REVIEW_FIELDS.CREATED_AT),
94
+ page: 1,
95
+ pageSize,
96
+ }),
97
+ ]);
98
+ return {
99
+ items: result.items.map(toReview),
100
+ total: result.total,
101
+ page: result.page,
102
+ pageSize: result.pageSize,
103
+ totalPages: result.totalPages,
104
+ hasMore: result.hasMore,
105
+ averageRating: summary.averageRating,
106
+ ratingDistribution: summary.ratingDistribution,
107
+ totalApproved: summary.total,
108
+ };
109
+ }
110
+ catch (err) {
111
+ void normalizeError(err);
112
+ return empty;
113
+ }
114
+ });
64
115
  /** List all published products for sitemap generation. */
65
116
  export async function listSitemapProducts() {
66
117
  try {
@@ -1,4 +1,4 @@
1
- export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, type RelatedItemsResult, } from "./data";
1
+ export { getProductForDetail, getReviewsForProduct, listSitemapProducts, type SitemapProduct, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, getReviewPageForProduct, type RelatedItemsResult, } from "./data";
2
2
  export { listPublicProducts, parsePublicProductParams, PUBLIC_PRODUCT_MAX_PAGE_SIZE, type PublicProductListInput, type PublicProductListResult, type PublicProductListOptions, type PublicProductExecutor, type PublicProductQuery, type ExecutorResult, } from "./list-public";
3
3
  export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
4
4
  export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
@@ -1,4 +1,4 @@
1
- export { getProductForDetail, getReviewsForProduct, listSitemapProducts, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, } from "./data";
1
+ export { getProductForDetail, getReviewsForProduct, listSitemapProducts, computeRelatedItems, toProductItem, toReview, getReviewItemsForProduct, getReviewPageForProduct, } from "./data";
2
2
  export { listPublicProducts, parsePublicProductParams, PUBLIC_PRODUCT_MAX_PAGE_SIZE, } from "./list-public";
3
3
  export { assertProductOwnership, assertStatusTransition, assertInStock, effectivePrice, isAvailableForPurchase, } from "./service";
4
4
  export { createProductAction, createAuctionAction, createPreOrderAction, updateProductAction, deleteProductAction, setProductStatusAction, setProductFeaturedAction, } from "./actions";
@@ -1,6 +1,25 @@
1
1
  import type { JsonValue } from "../../../../schemas/types";
2
2
  /** Vercel Hobby Fluid Compute ceiling (CLAUDE.md Rule #6) — never fetch more at once. */
3
3
  export declare const PUBLIC_PRODUCT_MAX_PAGE_SIZE = 50;
4
+ /**
5
+ * Which "Show X" defaults apply to a set of listing types.
6
+ *
7
+ * THE POINT OF THIS FUNCTION is that SSR and the client refetch call the SAME
8
+ * implementation. `staleTime: Infinity` freezes SSR `initialData` forever, so
9
+ * if the SSR filter-builder computes a different default than the client's
10
+ * bare-URL state, the first paint and every later refetch disagree and one of
11
+ * them is wrong — permanently (Root Cause #30). Two mirrored literals is
12
+ * exactly how that bug is written; one shared function is how it isn't.
13
+ *
14
+ * `/products` now spans all nine listing types, which makes this load-bearing
15
+ * rather than theoretical: a mixed set includes both sold-out-able types
16
+ * (products, art) and time-boxed ones (auctions, pre-orders, prize draws), so
17
+ * BOTH toggles can apply at once.
18
+ */
19
+ export declare function defaultTogglesForListingTypes(types: readonly string[] | undefined): {
20
+ hideSoldByDefault: boolean;
21
+ hideEndedByDefault: boolean;
22
+ };
4
23
  /**
5
24
  * A product document as it comes back from either executor. Deliberately
6
25
  * `JsonValue`-shaped rather than `ProductDocument`: the upstream listingProcessor
@@ -30,6 +49,16 @@ export interface PublicProductListInput {
30
49
  freeShipping?: boolean;
31
50
  isPartOfBundle?: boolean;
32
51
  features?: readonly string[];
52
+ /** Pipe-joined multi-select over the `tags` array field. */
53
+ tags?: readonly string[];
54
+ /** Pipe-joined multi-select over `sublistingCategoryId`. */
55
+ sublistingCategoryIds?: readonly string[];
56
+ /**
57
+ * Per-listing-type facets (classified meetup city, digital-code delivery
58
+ * method, live-item species, …), keyed by TABLE_KEY. Resolved to their
59
+ * nested Firestore paths via `TYPE_FACET_FIELD`.
60
+ */
61
+ typeFacets?: Record<string, string>;
33
62
  preOrderProductionStatus?: string;
34
63
  prizeRevealStatus?: string;
35
64
  /** Hide sold-out rows. Applied in memory — never pushed into Firestore. */
@@ -108,4 +137,22 @@ export declare function parsePublicProductParams(params: URLSearchParams | Recor
108
137
  hideEndedByDefault?: boolean;
109
138
  }): PublicProductListInput;
110
139
  export declare function listPublicProducts(input: PublicProductListInput, opts?: PublicProductListOptions): Promise<PublicProductListResult | null>;
140
+ /**
141
+ * One store tab's worth of listings.
142
+ *
143
+ * Every `Store*PageView` used to hand-roll this: `productRepository.list()`
144
+ * with an inline `sieveAnd(...)` and a `.catch(() => null)`. That is the exact
145
+ * shape that made `/art` render empty — an unsafe inequality (or any missing
146
+ * index) becomes FAILED_PRECONDITION, the catch turns it into `null`, and the
147
+ * page shows "no results" with nothing logged. Eight views carried it, and
148
+ * `audit-listing-filter-parity` had none of them registered.
149
+ *
150
+ * Routing them through `listPublicProducts` also gives every store tab
151
+ * something it never had: the URL's own sort/filter/page params reach the SSR
152
+ * fetch, so the first paint matches what the toolbar says is selected.
153
+ */
154
+ export declare function listStoreProducts(storeId: string, listingTypes: readonly string[], searchParams?: URLSearchParams | Record<string, string | string[] | undefined>, defaults?: {
155
+ pageSize?: number;
156
+ sorts?: string;
157
+ }): Promise<PublicProductListResult | null>;
111
158
  export {};
@@ -32,8 +32,36 @@ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
32
32
  import { TABLE_KEYS } from "../../../../constants/table-keys";
33
33
  import { sortBy } from "../../../../constants/sort";
34
34
  import { SIEVE_OP, sieveAnd, sieveFilter, expandSieveParam, } from "../../../../utils/sieve-builder";
35
+ import { ALL_LISTING_TYPES } from "../../../shared/listing-types/feature-flags";
36
+ import { hideDefaultsFor } from "../../../shared/listing-types/_registry";
35
37
  /** Vercel Hobby Fluid Compute ceiling (CLAUDE.md Rule #6) — never fetch more at once. */
36
38
  export const PUBLIC_PRODUCT_MAX_PAGE_SIZE = 50;
39
+ /**
40
+ * Which "Show X" defaults apply to a set of listing types.
41
+ *
42
+ * THE POINT OF THIS FUNCTION is that SSR and the client refetch call the SAME
43
+ * implementation. `staleTime: Infinity` freezes SSR `initialData` forever, so
44
+ * if the SSR filter-builder computes a different default than the client's
45
+ * bare-URL state, the first paint and every later refetch disagree and one of
46
+ * them is wrong — permanently (Root Cause #30). Two mirrored literals is
47
+ * exactly how that bug is written; one shared function is how it isn't.
48
+ *
49
+ * `/products` now spans all nine listing types, which makes this load-bearing
50
+ * rather than theoretical: a mixed set includes both sold-out-able types
51
+ * (products, art) and time-boxed ones (auctions, pre-orders, prize draws), so
52
+ * BOTH toggles can apply at once.
53
+ */
54
+ export function defaultTogglesForListingTypes(types) {
55
+ // No explicit set = every type is in play, so both defaults apply.
56
+ const effective = types && types.length > 0 ? types : ALL_LISTING_TYPES;
57
+ const defaults = hideDefaultsFor(effective.filter((t) => ALL_LISTING_TYPES.includes(t)));
58
+ return {
59
+ hideSoldByDefault: defaults.includes("sold"),
60
+ // "ended" and "closed" are the same query mechanic (a date range against
61
+ // the type's own end field); they differ only in the toggle's wording.
62
+ hideEndedByDefault: defaults.includes("ended") || defaults.includes("closed"),
63
+ };
64
+ }
37
65
  const DEFAULT_PAGE = 1;
38
66
  const DEFAULT_PAGE_SIZE = 24;
39
67
  const DEFAULT_SORTS = sortBy(PRODUCT_FIELDS.CREATED_AT);
@@ -52,6 +80,40 @@ function num(raw) {
52
80
  const n = Number(raw);
53
81
  return Number.isFinite(n) ? n : undefined;
54
82
  }
83
+ /**
84
+ * Per-type facet URL key → the Firestore field path it filters.
85
+ *
86
+ * Every target is already allowlisted in the products repository's
87
+ * SIEVE_FIELDS — the nested `classified.*` / `digitalCode.*` / `liveItem.*`
88
+ * entries have been there since those types shipped. What was missing was any
89
+ * code path that actually emitted a clause for them: the store listings
90
+ * declared these keys in their FILTER_KEYS (so they counted toward the
91
+ * filter badge) and rendered the controls, but never put them on the wire.
92
+ */
93
+ const TYPE_FACET_FIELD = {
94
+ [TABLE_KEYS.CITY]: "classified.meetupArea.city",
95
+ [TABLE_KEYS.NEGOTIABLE]: "classified.negotiable",
96
+ [TABLE_KEYS.ACCEPTS_SHIPPING]: "classified.acceptsShipping",
97
+ [TABLE_KEYS.DELIVERY_METHOD]: "digitalCode.codeDeliveryMethod",
98
+ [TABLE_KEYS.SPECIES]: "liveItem.species",
99
+ [TABLE_KEYS.LIVE_SEX]: "liveItem.sex",
100
+ [TABLE_KEYS.JURISDICTION]: "liveItem.jurisdictionAllowed",
101
+ };
102
+ /** Read every known per-type facet present in the params. */
103
+ function readTypeFacets(get) {
104
+ const out = {};
105
+ for (const key of Object.keys(TYPE_FACET_FIELD)) {
106
+ const value = get(key);
107
+ if (value)
108
+ out[key] = value;
109
+ }
110
+ return Object.keys(out).length > 0 ? out : undefined;
111
+ }
112
+ /** Split a pipe-joined multi-select value; `undefined` when empty. */
113
+ function pipeList(raw) {
114
+ const parts = raw.split("|").map((s) => s.trim()).filter(Boolean);
115
+ return parts.length > 0 ? parts : undefined;
116
+ }
55
117
  /**
56
118
  * Read the shared public-listing query params. SSR views hand in Next's
57
119
  * `searchParams` record; the API route hands in `url.searchParams`. Both produce
@@ -59,14 +121,19 @@ function num(raw) {
59
121
  */
60
122
  export function parsePublicProductParams(params, defaults) {
61
123
  const get = (k) => first(params, k);
62
- // A single type chip narrows the page's span; anything else keeps the full span.
124
+ // The type filter is a multi-select checkbox group, so its URL value is a
125
+ // pipe-joined set (`auction|pre-order`). Selected types NARROW the page's
126
+ // span; anything outside the span is discarded rather than allowed to widen
127
+ // it (an /art URL must not be able to ask for auctions). Nothing selected —
128
+ // or nothing valid selected — means the page's full span.
63
129
  const typeParam = get(TABLE_KEYS.LISTING_TYPE);
64
130
  const span = defaults?.listingTypes;
65
- const listingTypes = typeParam
66
- ? span && !span.includes(typeParam)
67
- ? span // an unknown chip value must not widen the page beyond its own span
68
- : [typeParam]
69
- : span;
131
+ const selected = typeParam
132
+ .split("|")
133
+ .map((t) => t.trim())
134
+ .filter((t) => t && t !== "All");
135
+ const narrowed = span ? selected.filter((t) => span.includes(t)) : selected;
136
+ const listingTypes = narrowed.length > 0 ? narrowed : span;
70
137
  const featuresRaw = get(TABLE_KEYS.FEATURES);
71
138
  return {
72
139
  listingTypes,
@@ -85,6 +152,12 @@ export function parsePublicProductParams(params, defaults) {
85
152
  freeShipping: get(TABLE_KEYS.FREE_SHIPPING) === "true" || undefined,
86
153
  isPartOfBundle: get(TABLE_KEYS.IS_PART_OF_BUNDLE) === "true" || undefined,
87
154
  features: featuresRaw ? featuresRaw.split("|").filter(Boolean) : undefined,
155
+ // Tags and Sublisting Type were rendered by <ProductFilters> for all three
156
+ // variants and counted toward the active-filter badge, but were never
157
+ // parsed here — so ticking one inflated the badge and changed nothing.
158
+ tags: pipeList(get(TABLE_KEYS.TAGS)),
159
+ sublistingCategoryIds: pipeList(get(TABLE_KEYS.SUBLISTING_CATEGORY)),
160
+ typeFacets: readTypeFacets(get),
88
161
  preOrderProductionStatus: get(TABLE_KEYS.PREORDER_STATUS) || get("preOrderStatus") || undefined,
89
162
  prizeRevealStatus: get(TABLE_KEYS.PRIZE_REVEAL_STATUS) || undefined,
90
163
  // Three spellings of one intent — /products and /art call it "Show sold",
@@ -178,6 +251,31 @@ function buildFirestoreSafeFilters(input) {
178
251
  if (input.features?.length === 1) {
179
252
  parts.push(sieveFilter(PRODUCT_FIELDS.FEATURES, SIEVE_OP.CONTAINS, input.features[0]));
180
253
  }
254
+ // `tags` is an array field, so the same one-value limit applies.
255
+ if (input.tags?.length === 1) {
256
+ parts.push(sieveFilter(PRODUCT_FIELDS.TAGS, SIEVE_OP.CONTAINS, input.tags[0]));
257
+ }
258
+ // A scalar field, so several values ARE expressible as an OR-group (which
259
+ // the Firebase adapter upgrades to `in`).
260
+ if (input.sublistingCategoryIds?.length) {
261
+ parts.push(sieveFilter(PRODUCT_FIELDS.SUBLISTING_CATEGORY_ID, SIEVE_OP.EQ, input.sublistingCategoryIds.join("|")));
262
+ }
263
+ // Per-type facets. Unknown keys can't reach here — `readTypeFacets` only
264
+ // reads the ones in TYPE_FACET_FIELD, so an arbitrary query param can never
265
+ // be turned into a Firestore field path.
266
+ for (const [key, raw] of Object.entries(input.typeFacets ?? {})) {
267
+ const field = TYPE_FACET_FIELD[key];
268
+ if (!field || !raw)
269
+ continue;
270
+ // `jurisdictionAllowed` is an array field — array-contains, not equality.
271
+ const op = field.endsWith("jurisdictionAllowed") ? SIEVE_OP.CONTAINS : SIEVE_OP.EQ;
272
+ const values = raw.split("|").filter(Boolean);
273
+ if (values.length === 0)
274
+ continue;
275
+ parts.push(op === SIEVE_OP.CONTAINS
276
+ ? sieveFilter(field, op, values[0])
277
+ : sieveFilter(field, op, values.join("|")));
278
+ }
181
279
  if (input.rawFilters)
182
280
  parts.push(input.rawFilters);
183
281
  return sieveAnd(...parts.filter(Boolean));
@@ -320,6 +418,33 @@ export async function listPublicProducts(input, opts) {
320
418
  sorts,
321
419
  };
322
420
  }
421
+ /**
422
+ * One store tab's worth of listings.
423
+ *
424
+ * Every `Store*PageView` used to hand-roll this: `productRepository.list()`
425
+ * with an inline `sieveAnd(...)` and a `.catch(() => null)`. That is the exact
426
+ * shape that made `/art` render empty — an unsafe inequality (or any missing
427
+ * index) becomes FAILED_PRECONDITION, the catch turns it into `null`, and the
428
+ * page shows "no results" with nothing logged. Eight views carried it, and
429
+ * `audit-listing-filter-parity` had none of them registered.
430
+ *
431
+ * Routing them through `listPublicProducts` also gives every store tab
432
+ * something it never had: the URL's own sort/filter/page params reach the SSR
433
+ * fetch, so the first paint matches what the toolbar says is selected.
434
+ */
435
+ export async function listStoreProducts(storeId, listingTypes, searchParams = {}, defaults) {
436
+ return listPublicProducts({
437
+ ...parsePublicProductParams(searchParams, {
438
+ listingTypes,
439
+ pageSize: defaults?.pageSize ?? DEFAULT_PAGE_SIZE,
440
+ sorts: defaults?.sorts,
441
+ ...defaultTogglesForListingTypes(listingTypes),
442
+ }),
443
+ // The route owns the store identity — a `?storeId=` in the URL must not be
444
+ // able to make one store's tab render another store's inventory.
445
+ storeId,
446
+ });
447
+ }
323
448
  function sortRows(rows, field, desc) {
324
449
  return [...rows].sort((a, b) => {
325
450
  const av = a[field];
@@ -1,4 +1,4 @@
1
- export declare const getReviewsForProduct: (productId: string) => Promise<never[] | import("../../../..").ReviewDocument[]>;
1
+ export declare const getReviewsForProduct: (productId: string, limit?: number) => Promise<never[] | import("../../../..").ReviewDocument[]>;
2
2
  export declare const getReviewsForStore: (storeId: string, page?: number, pageSize?: number) => Promise<import("../../../..").FirebaseSieveResult<import("../../../..").ReviewDocument> | {
3
3
  items: never[];
4
4
  total: number;
@@ -1,7 +1,8 @@
1
1
  import { cache } from "react";
2
2
  import { reviewRepository, orderRepository } from "../../../../repositories";
3
- export const getReviewsForProduct = cache(async (productId) => {
4
- return reviewRepository.findApprovedByProduct(productId).catch(() => []);
3
+ import { REVIEWS_DETAIL_PAGE_SIZE } from "../../../shared/features/reviews/config";
4
+ export const getReviewsForProduct = cache(async (productId, limit = REVIEWS_DETAIL_PAGE_SIZE) => {
5
+ return reviewRepository.findApprovedByProduct(productId, limit).catch(() => []);
5
6
  });
6
7
  export const getReviewsForStore = cache(async (storeId, page = 1, pageSize = 20) => {
7
8
  return reviewRepository.listForStore(storeId, { page, pageSize }).catch(() => ({
@@ -3,9 +3,9 @@
3
3
  * created as fully public/active documents so testers exercise the real browse/search/
4
4
  * checkout code paths, but must stay invisible to non-testers. Filtering happens at the
5
5
  * application layer (post-fetch, in-memory) rather than via a Firestore `where` clause,
6
- * because `isTestData` is a new optional field — a Firestore inequality filter
7
- * (`where("isTestData", "!=", true)`) would silently exclude every pre-existing document
8
- * that doesn't have the field set at all, which is worse than the problem it solves.
6
+ * because `isTestData` is a new optional field — a Firestore inequality filter on it
7
+ * (a `!=` clause) would silently exclude every pre-existing document that doesn't have
8
+ * the field set at all, which is worse than the problem it solves.
9
9
  */
10
10
  import { isAdminUser } from "../../../../features/auth/role-predicates";
11
11
  /** True when the viewer is allowed to see isTestData:true documents. */
@@ -2,12 +2,15 @@ import { bidRepository, cartRepository, productRepository, storeRepository, } fr
2
2
  import { sendNotification } from "../../../../features/admin/actions/notification-actions";
3
3
  import { AUCTION_MESSAGES } from "../handlers/messages";
4
4
  import { ROUTES } from "../../../../next/routing/route-map";
5
- import { CART_LANE } from "../../../shared/checkout/lanes";
5
+ import { CART_LANE, LANE_CHECKOUT_WINDOW_MS } from "../../../shared/checkout/lanes";
6
6
  /**
7
7
  * How long the winner has to pay before the locked cart line lapses. Mirrors
8
8
  * the accepted-offer window so both locked lanes behave the same.
9
9
  */
10
- const AUCTION_CHECKOUT_WINDOW_MS = 48 * 60 * 60 * 1000;
10
+ // Was a local `48 * 60 * 60 * 1000` here AND an identical one in bid-actions.ts
11
+ // (the Buy-Now path), with nothing linking them — changing one would have given
12
+ // the same kind of win two different deadlines depending on how it was won.
13
+ const AUCTION_CHECKOUT_WINDOW_MS = LANE_CHECKOUT_WINDOW_MS.auction;
11
14
  /** Deep link the winner straight into the auction checkout lane. */
12
15
  const AUCTION_CHECKOUT_URL = `${String(ROUTES.USER.CHECKOUT)}?lane=${CART_LANE.AUCTION}`;
13
16
  async function settleAuction(ctx, product) {
@@ -1,5 +1,5 @@
1
1
  import type { JobContext } from "../runtime/types";
2
- export declare const SANDBOX_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events"];
2
+ export declare const SANDBOX_COLLECTIONS: readonly ["categories", "stores", "products", "blogPosts", "events", "offers"];
3
3
  export interface TesterSandboxCleanupOptions {
4
4
  /** true = wipe every isTestData:true doc regardless of testDataExpiresAt (manual force-purge). */
5
5
  force?: boolean;
@@ -3,7 +3,10 @@ import { BID_FIELDS } from "../../../../constants/field-names";
3
3
  // Exported so testerSandboxRefresh.ts (the every-4h revert+prune job) reuses
4
4
  // the exact same collection scope instead of redefining it — one source of
5
5
  // truth for "what counts as sandbox scope."
6
- export const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events"];
6
+ // Keep in sync with testerSandboxRefresh.ts's SEED_BY_COLLECTION the daily
7
+ // TTL cleanup and the 4-hourly revert+prune must agree on what "sandbox" means
8
+ // (CLAUDE.md § Tester QA standards #3).
9
+ export const SANDBOX_COLLECTIONS = ["categories", "stores", "products", "blogPosts", "events", "offers"];
7
10
  const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
8
11
  export async function runTesterSandboxCleanup(ctx, opts = {}) {
9
12
  const cutoff = opts.force ? null : new Date();
@@ -8,6 +8,7 @@ import { storesTesterSeedData } from "../../../../features/tester/seed-data/stor
8
8
  import { productsTesterSeedData } from "../../../../features/tester/seed-data/products-tester-seed-data";
9
9
  import { blogTesterSeedData } from "../../../../features/tester/seed-data/blog-tester-seed-data";
10
10
  import { eventsTesterSeedData } from "../../../../features/tester/seed-data/events-tester-seed-data";
11
+ import { offersTesterSeedData } from "../../../../features/tester/seed-data/offers-tester-seed-data";
11
12
  import { bidsTesterSeedData } from "../../../../features/tester/seed-data/bids-tester-seed-data";
12
13
  const SEED_BY_COLLECTION = {
13
14
  categories: categoriesTesterSeedData,
@@ -15,6 +16,7 @@ const SEED_BY_COLLECTION = {
15
16
  products: productsTesterSeedData,
16
17
  blogPosts: blogTesterSeedData,
17
18
  events: eventsTesterSeedData,
19
+ offers: offersTesterSeedData,
18
20
  };
19
21
  const BID_CHUNK_SIZE = 30; // Firestore `in` query cap
20
22
  /**
@@ -2,6 +2,7 @@
2
2
  * Shared helpers for job handlers.
3
3
  */
4
4
  import { getAdminRealtimeDb } from "../../../../providers/db-firebase";
5
+ import { PRODUCT_FIELDS } from "../../../../constants/field-names";
5
6
  import { BATCH_LIMIT } from "./messages";
6
7
  /** Batch-delete document refs, respecting Firestore's 500-write batch limit. */
7
8
  export async function batchDelete(ctx, refs) {
@@ -23,7 +24,9 @@ export { getAdminRealtimeDb };
23
24
  * only the ones past testDataExpiresAt (the scheduled 7-day sweep).
24
25
  */
25
26
  export async function getTestDataRefs(db, collection, cutoff) {
26
- let q = db.collection(collection).where("isTestData", "==", true);
27
+ let q = db
28
+ .collection(collection)
29
+ .where(PRODUCT_FIELDS.IS_TEST_DATA, "==", true);
27
30
  if (cutoff)
28
31
  q = q.where("testDataExpiresAt", "<=", cutoff);
29
32
  const snap = await q.limit(200).get();
@@ -11,6 +11,26 @@ export type CartLane = (typeof CART_LANE)[keyof typeof CART_LANE];
11
11
  */
12
12
  export declare const CART_LANE_PRIORITY: readonly CartLane[];
13
13
  export declare const CART_LANE_LABELS: Record<CartLane, string>;
14
+ /**
15
+ * How long a locked line survives before it lapses, per lane.
16
+ *
17
+ * Lives here, with the lane model, because the deadline is a property OF the
18
+ * lane — and because `AUCTION_CHECKOUT_WINDOW_MS` was previously declared twice
19
+ * (`auctionSettlement.ts` and `bid-actions.ts`, i.e. the settlement path and the
20
+ * Buy-Now path) with no link between them: changing one would silently give the
21
+ * same kind of win two different deadlines depending on how it was won.
22
+ *
23
+ * The two windows are equal today but mean different things, so they stay
24
+ * separate names rather than one shared constant:
25
+ * - **auction** — payment is MANDATORY. You bid, you won, you owe it. Past the
26
+ * deadline the bid is forfeited and the buyer is warned that repeated
27
+ * non-payment restricts their account.
28
+ * - **offer** — payment is OPTIONAL. Declining to buy at your own offer price
29
+ * is the buyer's right; the offer simply lapses with no penalty.
30
+ */
31
+ export declare const LANE_CHECKOUT_WINDOW_MS: Record<"auction" | "offer", number>;
32
+ /** 2 days, in hours — for buyer-facing copy ("pay within 48 hours"). */
33
+ export declare const LANE_CHECKOUT_WINDOW_HOURS: Record<"auction" | "offer", number>;
14
34
  /** The two lanes whose price is fixed outside the listing. */
15
35
  export declare function isLockedLane(lane: CartLane): boolean;
16
36
  /** Minimal shape this module needs — works on cart documents and client rows alike. */
@@ -44,6 +44,34 @@ export const CART_LANE_LABELS = {
44
44
  offer: "Accepted offers",
45
45
  standard: "Cart",
46
46
  };
47
+ /**
48
+ * How long a locked line survives before it lapses, per lane.
49
+ *
50
+ * Lives here, with the lane model, because the deadline is a property OF the
51
+ * lane — and because `AUCTION_CHECKOUT_WINDOW_MS` was previously declared twice
52
+ * (`auctionSettlement.ts` and `bid-actions.ts`, i.e. the settlement path and the
53
+ * Buy-Now path) with no link between them: changing one would silently give the
54
+ * same kind of win two different deadlines depending on how it was won.
55
+ *
56
+ * The two windows are equal today but mean different things, so they stay
57
+ * separate names rather than one shared constant:
58
+ * - **auction** — payment is MANDATORY. You bid, you won, you owe it. Past the
59
+ * deadline the bid is forfeited and the buyer is warned that repeated
60
+ * non-payment restricts their account.
61
+ * - **offer** — payment is OPTIONAL. Declining to buy at your own offer price
62
+ * is the buyer's right; the offer simply lapses with no penalty.
63
+ */
64
+ export const LANE_CHECKOUT_WINDOW_MS = {
65
+ /** 2 days. Mandatory — enforced by the expiry sweep, which forfeits the bid. */
66
+ auction: 48 * 60 * 60 * 1000,
67
+ /** 2 days. Optional — lapses quietly, no penalty. */
68
+ offer: 48 * 60 * 60 * 1000,
69
+ };
70
+ /** 2 days, in hours — for buyer-facing copy ("pay within 48 hours"). */
71
+ export const LANE_CHECKOUT_WINDOW_HOURS = {
72
+ auction: LANE_CHECKOUT_WINDOW_MS.auction / (60 * 60 * 1000),
73
+ offer: LANE_CHECKOUT_WINDOW_MS.offer / (60 * 60 * 1000),
74
+ };
47
75
  /** The two lanes whose price is fixed outside the listing. */
48
76
  export function isLockedLane(lane) {
49
77
  return lane === CART_LANE.AUCTION || lane === CART_LANE.OFFER;
@@ -1,4 +1,8 @@
1
1
  export declare const CHECKOUT_DEFAULT_COMMISSIONS: {
2
+ readonly platformFeePercent: 5;
3
+ readonly gstPercent: 18;
4
+ readonly minimumTransactionFee: 0;
5
+ readonly platformFeeMax: 10;
2
6
  readonly codDepositPercent: 10;
3
7
  readonly codHandlingFeeMin: 200;
4
8
  readonly codHandlingFeePercent: 10;
@@ -1,4 +1,12 @@
1
1
  export const CHECKOUT_DEFAULT_COMMISSIONS = {
2
+ // Platform-commission rates. Previously absent here because no checkout path
3
+ // computed a buyer-facing platform fee — the moment one did, a siteSettings
4
+ // document that failed to load would have produced NaN fees rather than a
5
+ // sane fallback. Mirrors the SiteSettingsDocument defaults.
6
+ platformFeePercent: 5,
7
+ gstPercent: 18,
8
+ minimumTransactionFee: 0,
9
+ platformFeeMax: 10,
2
10
  codDepositPercent: 10,
3
11
  codHandlingFeeMin: 200,
4
12
  codHandlingFeePercent: 10,
@@ -23,7 +23,6 @@ export declare const createCouponSchema: z.ZodObject<{
23
23
  name: z.ZodString;
24
24
  type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
25
25
  scope: z.ZodEnum<["admin", "seller"]>;
26
- sellerId: z.ZodOptional<z.ZodString>;
27
26
  discount: z.ZodObject<{
28
27
  value: z.ZodNumber;
29
28
  maxDiscount: z.ZodOptional<z.ZodNumber>;
@@ -62,17 +61,14 @@ export declare const createCouponSchema: z.ZodObject<{
62
61
  }>;
63
62
  restrictions: z.ZodOptional<z.ZodObject<{
64
63
  firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
65
- combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
66
64
  applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
67
65
  applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
68
66
  }, "strip", z.ZodTypeAny, {
69
67
  firstTimeUserOnly: boolean;
70
- combineWithSellerCoupons: boolean;
71
68
  applicableProducts?: string[] | undefined;
72
69
  applicableCategories?: string[] | undefined;
73
70
  }, {
74
71
  firstTimeUserOnly?: boolean | undefined;
75
- combineWithSellerCoupons?: boolean | undefined;
76
72
  applicableProducts?: string[] | undefined;
77
73
  applicableCategories?: string[] | undefined;
78
74
  }>>;
@@ -95,10 +91,8 @@ export declare const createCouponSchema: z.ZodObject<{
95
91
  endDate: string;
96
92
  startDate: string;
97
93
  };
98
- sellerId?: string | undefined;
99
94
  restrictions?: {
100
95
  firstTimeUserOnly: boolean;
101
- combineWithSellerCoupons: boolean;
102
96
  applicableProducts?: string[] | undefined;
103
97
  applicableCategories?: string[] | undefined;
104
98
  } | undefined;
@@ -121,10 +115,8 @@ export declare const createCouponSchema: z.ZodObject<{
121
115
  startDate: string;
122
116
  isActive?: boolean | undefined;
123
117
  };
124
- sellerId?: string | undefined;
125
118
  restrictions?: {
126
119
  firstTimeUserOnly?: boolean | undefined;
127
- combineWithSellerCoupons?: boolean | undefined;
128
120
  applicableProducts?: string[] | undefined;
129
121
  applicableCategories?: string[] | undefined;
130
122
  } | undefined;
@@ -134,7 +126,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
134
126
  name: z.ZodOptional<z.ZodString>;
135
127
  type: z.ZodOptional<z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>>;
136
128
  scope: z.ZodOptional<z.ZodEnum<["admin", "seller"]>>;
137
- sellerId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
138
129
  discount: z.ZodOptional<z.ZodObject<{
139
130
  value: z.ZodNumber;
140
131
  maxDiscount: z.ZodOptional<z.ZodNumber>;
@@ -173,17 +164,14 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
173
164
  }>>;
174
165
  restrictions: z.ZodOptional<z.ZodOptional<z.ZodObject<{
175
166
  firstTimeUserOnly: z.ZodDefault<z.ZodBoolean>;
176
- combineWithSellerCoupons: z.ZodDefault<z.ZodBoolean>;
177
167
  applicableProducts: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
178
168
  applicableCategories: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
179
169
  }, "strip", z.ZodTypeAny, {
180
170
  firstTimeUserOnly: boolean;
181
- combineWithSellerCoupons: boolean;
182
171
  applicableProducts?: string[] | undefined;
183
172
  applicableCategories?: string[] | undefined;
184
173
  }, {
185
174
  firstTimeUserOnly?: boolean | undefined;
186
- combineWithSellerCoupons?: boolean | undefined;
187
175
  applicableProducts?: string[] | undefined;
188
176
  applicableCategories?: string[] | undefined;
189
177
  }>>>;
@@ -191,7 +179,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
191
179
  type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
192
180
  scope?: "seller" | "admin" | undefined;
193
181
  name?: string | undefined;
194
- sellerId?: string | undefined;
195
182
  discount?: {
196
183
  value: number;
197
184
  minPurchase: number;
@@ -208,7 +195,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
208
195
  } | undefined;
209
196
  restrictions?: {
210
197
  firstTimeUserOnly: boolean;
211
- combineWithSellerCoupons: boolean;
212
198
  applicableProducts?: string[] | undefined;
213
199
  applicableCategories?: string[] | undefined;
214
200
  } | undefined;
@@ -216,7 +202,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
216
202
  type?: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y" | undefined;
217
203
  scope?: "seller" | "admin" | undefined;
218
204
  name?: string | undefined;
219
- sellerId?: string | undefined;
220
205
  discount?: {
221
206
  value: number;
222
207
  maxDiscount?: number | undefined;
@@ -233,7 +218,6 @@ export declare const updateCouponSchema: z.ZodObject<Omit<{
233
218
  } | undefined;
234
219
  restrictions?: {
235
220
  firstTimeUserOnly?: boolean | undefined;
236
- combineWithSellerCoupons?: boolean | undefined;
237
221
  applicableProducts?: string[] | undefined;
238
222
  applicableCategories?: string[] | undefined;
239
223
  } | undefined;