@mohasinac/appkit 4.11.1 → 4.11.3

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 (212) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +4 -0
  2. package/dist/_internal/server/features/checkout/actions.js +138 -27
  3. package/dist/_internal/server/features/checkout/data.d.ts +10 -0
  4. package/dist/_internal/server/features/checkout/locked-lines.d.ts +30 -0
  5. package/dist/_internal/server/features/checkout/locked-lines.js +116 -0
  6. package/dist/_internal/server/features/orders/adapters.js +7 -0
  7. package/dist/_internal/server/features/products/data.d.ts +12 -3
  8. package/dist/_internal/server/features/products/data.js +55 -4
  9. package/dist/_internal/server/features/products/index.d.ts +2 -1
  10. package/dist/_internal/server/features/products/index.js +2 -1
  11. package/dist/_internal/server/features/products/list-public.d.ts +130 -0
  12. package/dist/_internal/server/features/products/list-public.js +375 -0
  13. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  14. package/dist/_internal/server/features/reviews/data.js +3 -2
  15. package/dist/_internal/server/features/tester/visibility.js +3 -3
  16. package/dist/_internal/server/jobs/core/auctionSettlement.js +89 -16
  17. package/dist/_internal/server/jobs/core/offerExpiry.js +116 -4
  18. package/dist/_internal/server/jobs/core/testerSandboxCleanup.d.ts +1 -1
  19. package/dist/_internal/server/jobs/core/testerSandboxCleanup.js +4 -1
  20. package/dist/_internal/server/jobs/core/testerSandboxRefresh.js +2 -0
  21. package/dist/_internal/server/jobs/handlers/_helpers.js +4 -1
  22. package/dist/_internal/server/jobs/handlers/messages.d.ts +5 -0
  23. package/dist/_internal/server/jobs/handlers/messages.js +5 -0
  24. package/dist/_internal/shared/checkout/lanes.d.ts +41 -0
  25. package/dist/_internal/shared/checkout/lanes.js +106 -0
  26. package/dist/_internal/shared/checkout/order-math.d.ts +19 -0
  27. package/dist/_internal/shared/checkout/order-math.js +30 -6
  28. package/dist/_internal/shared/features/promotions/schema.d.ts +0 -16
  29. package/dist/_internal/shared/features/promotions/schema.js +0 -2
  30. package/dist/_internal/shared/features/reviews/config.d.ts +8 -1
  31. package/dist/_internal/shared/features/reviews/config.js +8 -1
  32. package/dist/_internal/shared/listing-types/_registry.d.ts +88 -1
  33. package/dist/_internal/shared/listing-types/_registry.js +63 -1
  34. package/dist/_internal/shared/listing-types/art/config.d.ts +59 -0
  35. package/dist/_internal/shared/listing-types/art/config.js +12 -0
  36. package/dist/_internal/shared/listing-types/auction/config.d.ts +53 -0
  37. package/dist/_internal/shared/listing-types/auction/config.js +17 -0
  38. package/dist/_internal/shared/listing-types/classified/config.d.ts +59 -0
  39. package/dist/_internal/shared/listing-types/classified/config.js +14 -0
  40. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +59 -0
  41. package/dist/_internal/shared/listing-types/digital-code/config.js +12 -0
  42. package/dist/_internal/shared/listing-types/feature-flags.d.ts +1 -0
  43. package/dist/_internal/shared/listing-types/feature-flags.js +24 -10
  44. package/dist/_internal/shared/listing-types/live/config.d.ts +59 -0
  45. package/dist/_internal/shared/listing-types/live/config.js +14 -0
  46. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +110 -0
  47. package/dist/_internal/shared/listing-types/pre-order/config.js +14 -0
  48. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +44 -0
  49. package/dist/_internal/shared/listing-types/prize-draw/config.js +10 -0
  50. package/dist/_internal/shared/listing-types/standard/config.d.ts +59 -0
  51. package/dist/_internal/shared/listing-types/standard/config.js +9 -0
  52. package/dist/_internal/shared/listing-types/stickers/config.d.ts +59 -0
  53. package/dist/_internal/shared/listing-types/stickers/config.js +12 -0
  54. package/dist/client.d.ts +7 -2
  55. package/dist/client.js +8 -2
  56. package/dist/constants/field-names.d.ts +12 -0
  57. package/dist/constants/field-names.js +12 -0
  58. package/dist/constants/table-keys.d.ts +2 -0
  59. package/dist/constants/table-keys.js +2 -0
  60. package/dist/features/account/components/NotificationBell.js +1 -0
  61. package/dist/features/account/components/UserOffersPanel.js +8 -1
  62. package/dist/features/account/components/UserOrdersView.js +27 -5
  63. package/dist/features/admin/actions/notification-actions.js +1 -1
  64. package/dist/features/admin/components/AdminAddressesView.js +3 -1
  65. package/dist/features/admin/components/AdminCouponEditorView.js +1 -4
  66. package/dist/features/admin/components/AdminPaymentMethodsView.js +3 -1
  67. package/dist/features/admin/components/AdminProductsView.js +34 -18
  68. package/dist/features/admin/constants/filter-tabs.d.ts +26 -9
  69. package/dist/features/admin/constants/filter-tabs.js +22 -9
  70. package/dist/features/admin/schemas/firestore.d.ts +2 -1
  71. package/dist/features/admin/schemas/firestore.js +1 -0
  72. package/dist/features/auctions/actions/bid-actions.d.ts +11 -1
  73. package/dist/features/auctions/actions/bid-actions.js +29 -15
  74. package/dist/features/auctions/components/AuctionBidsTable.js +6 -2
  75. package/dist/features/auctions/components/AuctionBottomActions.js +1 -0
  76. package/dist/features/auctions/components/AuctionDetailPageView.js +13 -11
  77. package/dist/features/auctions/components/AuctionsListView.js +12 -53
  78. package/dist/features/auctions/components/PlaceBidFormClient.js +10 -1
  79. package/dist/features/auctions/repository/bid.repository.d.ts +15 -0
  80. package/dist/features/auctions/repository/bid.repository.js +19 -0
  81. package/dist/features/auctions/schemas/firestore.d.ts +11 -1
  82. package/dist/features/auctions/schemas/firestore.js +1 -0
  83. package/dist/features/cart/actions/cart-actions.d.ts +11 -0
  84. package/dist/features/cart/actions/cart-actions.js +24 -0
  85. package/dist/features/cart/repository/cart.repository.d.ts +24 -1
  86. package/dist/features/cart/repository/cart.repository.js +71 -6
  87. package/dist/features/cart/schemas/firestore.d.ts +23 -4
  88. package/dist/features/categories/components/BrandDetailTabs.js +31 -11
  89. package/dist/features/categories/components/CategoryDetailTabs.js +27 -7
  90. package/dist/features/categories/components/CategoryProductsListing.d.ts +9 -1
  91. package/dist/features/categories/components/CategoryProductsListing.js +5 -2
  92. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -1
  93. package/dist/features/contact/email.js +56 -1
  94. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +17 -16
  95. package/dist/features/homepage/components/FeaturedResultsSection.js +1 -1
  96. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  97. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  98. package/dist/features/layout/AppLayoutShell.js +9 -2
  99. package/dist/features/layout/BottomActions.js +26 -3
  100. package/dist/features/layout/BottomActionsContext.d.ts +14 -0
  101. package/dist/features/layout/BottomActionsContext.js +13 -1
  102. package/dist/features/layout/hooks/useBottomActions.d.ts +6 -1
  103. package/dist/features/layout/hooks/useBottomActions.js +4 -1
  104. package/dist/features/layout/index.d.ts +1 -1
  105. package/dist/features/live/components/LiveItemDetailPageView.js +7 -6
  106. package/dist/features/orders/repository/orders.repository.d.ts +4 -15
  107. package/dist/features/orders/repository/orders.repository.js +4 -27
  108. package/dist/features/orders/types/index.d.ts +16 -0
  109. package/dist/features/orders/utils/order-splitter.js +14 -2
  110. package/dist/features/pre-orders/components/PreOrderBottomActions.d.ts +1 -1
  111. package/dist/features/pre-orders/components/PreOrderBottomActions.js +3 -19
  112. package/dist/features/pre-orders/components/PreOrdersListView.js +11 -39
  113. package/dist/features/products/components/ArtStickersListView.js +10 -49
  114. package/dist/features/products/components/AuctionsIndexListing.js +1 -2
  115. package/dist/features/products/components/ListingBottomActions.d.ts +28 -0
  116. package/dist/features/products/components/ListingBottomActions.js +25 -0
  117. package/dist/features/products/components/PrizeDrawBottomActions.d.ts +1 -1
  118. package/dist/features/products/components/PrizeDrawBottomActions.js +3 -21
  119. package/dist/features/products/components/ProductDetailActions.js +4 -0
  120. package/dist/features/products/components/ProductDetailPageView.js +7 -7
  121. package/dist/features/products/components/ProductsIndexPageView.js +9 -62
  122. package/dist/features/products/constants/listing-tabs.d.ts +43 -198
  123. package/dist/features/products/constants/listing-tabs.js +71 -58
  124. package/dist/features/products/constants/sieve.d.ts +104 -1
  125. package/dist/features/products/constants/sieve.js +27 -7
  126. package/dist/features/products/repository/products.repository.d.ts +45 -1
  127. package/dist/features/products/repository/products.repository.js +67 -8
  128. package/dist/features/products/utils/listing-badge-variant.d.ts +20 -1
  129. package/dist/features/products/utils/listing-badge-variant.js +26 -1
  130. package/dist/features/products/utils/listing-type.d.ts +24 -0
  131. package/dist/features/products/utils/listing-type.js +48 -0
  132. package/dist/features/promotions/actions/coupon-actions.d.ts +7 -0
  133. package/dist/features/promotions/actions/coupon-actions.js +12 -1
  134. package/dist/features/promotions/actions/coupon-stacking.d.ts +24 -0
  135. package/dist/features/promotions/actions/coupon-stacking.js +21 -0
  136. package/dist/features/promotions/actions/index.d.ts +1 -0
  137. package/dist/features/promotions/actions/index.js +1 -0
  138. package/dist/features/promotions/actions/seller-coupon-actions.d.ts +0 -1
  139. package/dist/features/promotions/actions/seller-coupon-actions.js +0 -1
  140. package/dist/features/promotions/components/CouponHelpDetails.d.ts +20 -0
  141. package/dist/features/promotions/components/CouponHelpDetails.js +17 -0
  142. package/dist/features/promotions/components/CouponsIndexListing.js +2 -1
  143. package/dist/features/promotions/components/index.d.ts +2 -0
  144. package/dist/features/promotions/components/index.js +1 -0
  145. package/dist/features/promotions/constants/coupon-help.d.ts +27 -0
  146. package/dist/features/promotions/constants/coupon-help.js +42 -0
  147. package/dist/features/promotions/repository/coupons.repository.d.ts +10 -1
  148. package/dist/features/promotions/repository/coupons.repository.js +22 -8
  149. package/dist/features/promotions/schemas/firestore.d.ts +0 -1
  150. package/dist/features/promotions/schemas/firestore.js +1 -1
  151. package/dist/features/promotions/schemas/index.d.ts +0 -20
  152. package/dist/features/promotions/schemas/index.js +0 -1
  153. package/dist/features/reviews/actions/review-actions.js +5 -1
  154. package/dist/features/reviews/components/ReviewDetailPageView.js +2 -1
  155. package/dist/features/reviews/components/ReviewFilters.d.ts +7 -1
  156. package/dist/features/reviews/components/ReviewFilters.js +4 -3
  157. package/dist/features/reviews/components/ReviewsIndexListing.d.ts +6 -0
  158. package/dist/features/reviews/components/ReviewsIndexListing.js +9 -109
  159. package/dist/features/reviews/components/ReviewsListingPanel.d.ts +41 -0
  160. package/dist/features/reviews/components/ReviewsListingPanel.js +226 -0
  161. package/dist/features/reviews/components/index.d.ts +2 -0
  162. package/dist/features/reviews/components/index.js +1 -0
  163. package/dist/features/reviews/hooks/useReviews.d.ts +8 -0
  164. package/dist/features/reviews/hooks/useReviews.js +14 -6
  165. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  166. package/dist/features/reviews/repository/reviews.repository.js +77 -41
  167. package/dist/features/reviews/types/index.d.ts +8 -1
  168. package/dist/features/seller/actions/offer-actions.d.ts +6 -0
  169. package/dist/features/seller/actions/offer-actions.js +8 -9
  170. package/dist/features/seller/components/SellerOffersView.d.ts +6 -1
  171. package/dist/features/seller/components/SellerOffersView.js +59 -8
  172. package/dist/features/seller/components/SellerProductsCards.js +2 -2
  173. package/dist/features/seller/components/SellerProductsView.js +21 -18
  174. package/dist/features/seller/repository/offer.repository.d.ts +18 -2
  175. package/dist/features/seller/repository/offer.repository.js +38 -2
  176. package/dist/features/seller/schemas/firestore.d.ts +7 -2
  177. package/dist/features/seller/schemas/firestore.js +3 -0
  178. package/dist/features/seller/schemas/offer-forms.d.ts +12 -0
  179. package/dist/features/seller/schemas/offer-forms.js +28 -0
  180. package/dist/features/stores/actions/store-query-actions.js +1 -1
  181. package/dist/features/stores/components/StoreAuctionsListing.js +5 -11
  182. package/dist/features/stores/components/StorePreOrdersListing.js +6 -11
  183. package/dist/features/stores/components/StoreReviewsListing.d.ts +4 -0
  184. package/dist/features/stores/components/StoreReviewsListing.js +7 -108
  185. package/dist/features/stores/schemas/index.d.ts +2 -2
  186. package/dist/features/tester/seed-data/coupons-tester-seed-data.js +3 -3
  187. package/dist/features/tester/seed-data/index.d.ts +1 -0
  188. package/dist/features/tester/seed-data/index.js +1 -0
  189. package/dist/features/tester/seed-data/offers-tester-seed-data.d.ts +2 -0
  190. package/dist/features/tester/seed-data/offers-tester-seed-data.js +61 -0
  191. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +337 -1
  192. package/dist/index.d.ts +11 -4
  193. package/dist/index.js +15 -3
  194. package/dist/react/hooks/useListingTypeFlags.d.ts +12 -19
  195. package/dist/react/hooks/useListingTypeFlags.js +4 -9
  196. package/dist/schemas/registry.d.ts +0 -12
  197. package/dist/seed/claimed-coupons-seed-data.js +3 -3
  198. package/dist/seed/coupons-seed-data.js +29 -10
  199. package/dist/seed/faq-seed-data.js +2 -2
  200. package/dist/seed/manifest.js +2 -2
  201. package/dist/server-entry.d.ts +1 -0
  202. package/dist/server-entry.js +1 -0
  203. package/dist/server.d.ts +1 -0
  204. package/dist/server.js +3 -0
  205. package/dist/styles.css +44 -39
  206. package/dist/tailwind-utilities.css +1 -1
  207. package/dist/ui/components/FilterChipGroup.d.ts +21 -3
  208. package/dist/ui/components/FilterChipGroup.js +33 -4
  209. package/dist/ui/components/HorizontalScroller.d.ts +1 -2
  210. package/dist/ui/components/HorizontalScroller.js +9 -5
  211. package/dist/ui/components/HorizontalScroller.style.css +43 -38
  212. package/package.json +1 -1
@@ -1,15 +1,20 @@
1
1
  "use client";
2
- import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { sieveFilter, SIEVE_OP } from "@mohasinac/appkit/client";
4
4
  import { sortBy } from "@mohasinac/appkit/client";
5
5
  import React from "react";
6
- import { FilterChipGroup, ListingLayout, RowActionMenu } from "../../../ui";
6
+ import { FilterChipGroup, ListingLayout, RowActionMenu, RecordDetailModal } from "../../../ui";
7
+ import { QuickFormDrawer } from "../../shell/QuickFormDrawer";
8
+ import { counterOfferFormSchema } from "../schemas/offer-forms";
7
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
10
  import { SELLER_OFFER_STATUS_TABS } from "../../admin/constants/filter-tabs";
9
11
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
10
12
  import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../../admin/hooks/useAdminListingData";
11
13
  import { DataListingView } from "../../admin/components/DataListingView";
12
14
  export function SellerOffersView({ children, onAcceptOffer, onRejectOffer, onCounterOffer, ...props }) {
15
+ const [detailRow, setDetailRow] = React.useState(null);
16
+ const [counterRow, setCounterRow] = React.useState(null);
17
+ const [isCountering, setIsCountering] = React.useState(false);
13
18
  if (React.Children.count(children) > 0) {
14
19
  return (_jsx(ListingLayout, { portal: "seller", ...props, children: children }));
15
20
  }
@@ -26,26 +31,31 @@ export function SellerOffersView({ children, onAcceptOffer, onRejectOffer, onCou
26
31
  { value: sortBy("createdAt", "DESC"), label: "Newest" },
27
32
  { value: sortBy("createdAt", "ASC"), label: "Oldest" },
28
33
  ],
29
- mapRows: (response) => toRecordArray(response.offers).map((item, index) => ({
34
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
30
35
  id: toStringValue(item.id, `offer-${index}`),
31
36
  primary: toStringValue(item.productTitle ?? item.title, "Untitled product"),
32
37
  secondary: [
33
38
  `Offer: ${toCurrency(item.offerAmount ?? item.amount)}`,
39
+ `Listed: ${toCurrency(item.listedPrice)}`,
34
40
  toStringValue(item.buyerName ?? "Unknown buyer", "Unknown buyer"),
35
41
  ].join(" · "),
36
42
  status: toStringValue(item.status, "Pending"),
37
43
  updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
44
+ detail: item,
38
45
  })),
39
- getTotal: (response, mappedRows) => typeof response.meta?.total === "number"
40
- ? response.meta.total
41
- : mappedRows.length,
46
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
47
+ // A menu of pure mutations is not a detail affordance — the seller must be
48
+ // able to READ the offer (buyer note, listed vs offered price, expiry)
49
+ // before accepting or declining it (Root Cause #56).
50
+ onRowClick: (row) => setDetailRow(row),
42
51
  buildFilters: (state) => state.status && state.status !== "All" ? sieveFilter("status", SIEVE_OP.EQ, state.status) : undefined,
43
52
  renderRowActions: (row) => (_jsx(RowActionMenu, { actions: [
53
+ { label: "View details", onClick: () => setDetailRow(row) },
44
54
  ...(onAcceptOffer
45
55
  ? [{ label: ACTIONS.STORE["accept-offer"].label, onClick: () => void onAcceptOffer(row.id) }]
46
56
  : []),
47
57
  ...(onCounterOffer
48
- ? [{ label: ACTIONS.STORE["counter-offer"].label, onClick: () => onCounterOffer(row.id) }]
58
+ ? [{ label: ACTIONS.STORE["counter-offer"].label, onClick: () => setCounterRow(row) }]
49
59
  : []),
50
60
  ...(onRejectOffer
51
61
  ? [{ label: ACTIONS.STORE["reject-offer"].label, destructive: true, onClick: () => void onRejectOffer(row.id) }]
@@ -53,5 +63,46 @@ export function SellerOffersView({ children, onAcceptOffer, onRejectOffer, onCou
53
63
  ] })),
54
64
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: SELLER_OFFER_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
55
65
  };
56
- return _jsx(DataListingView, { config: config });
66
+ const d = detailRow?.detail ?? {};
67
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detailRow !== null, onClose: () => setDetailRow(null), title: detailRow?.primary ?? "Offer", badges: detailRow ? [{ label: detailRow.status }] : undefined, description: toStringValue(d.buyerNote, ""), fields: [
68
+ { label: "Buyer", value: toStringValue(d.buyerName, "Unknown buyer") },
69
+ { label: "Listed price", value: toCurrency(d.listedPrice) },
70
+ { label: "Offered", value: toCurrency(d.offerAmount) },
71
+ ...(d.counterAmount
72
+ ? [{ label: "Your counter", value: toCurrency(d.counterAmount) }]
73
+ : []),
74
+ ...(d.lockedPrice
75
+ ? [{ label: "Agreed price", value: toCurrency(d.lockedPrice) }]
76
+ : []),
77
+ { label: "Offer expires", value: toRelativeDate(d.expiresAt) },
78
+ ...(d.checkoutDeadline
79
+ ? [{ label: "Buyer must pay by", value: toRelativeDate(d.checkoutDeadline) }]
80
+ : []),
81
+ ...(d.sellerNote ? [{ label: "Your note", value: toStringValue(d.sellerNote, "") }] : []),
82
+ ] }), _jsx(QuickFormDrawer, { isOpen: counterRow !== null, onClose: () => setCounterRow(null), title: `Counter offer — ${counterRow?.primary ?? ""}`, submitLabel: "Send counter", isLoading: isCountering, schema: counterOfferFormSchema, fields: [
83
+ {
84
+ name: "counterAmount",
85
+ label: "Your counter price (₹)",
86
+ type: "number",
87
+ required: true,
88
+ helperText: "Must be below your listed price and different from the buyer's offer.",
89
+ },
90
+ {
91
+ name: "sellerNote",
92
+ label: "Note to buyer (optional)",
93
+ type: "textarea",
94
+ placeholder: "Best I can do is …",
95
+ },
96
+ ], onSubmit: async (values) => {
97
+ if (!counterRow || !onCounterOffer)
98
+ return;
99
+ setIsCountering(true);
100
+ try {
101
+ await onCounterOffer(counterRow.id, Number(values.counterAmount), values.sellerNote ? String(values.sellerNote) : undefined);
102
+ setCounterRow(null);
103
+ }
104
+ finally {
105
+ setIsCountering(false);
106
+ }
107
+ } })] }));
57
108
  }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { Badge, Button, Checkbox, Div, Grid, Row, Stack, Text, TextLink } from "../../../ui";
4
4
  import { MediaImage } from "../../media/MediaImage";
5
5
  import { ROW_ACTION_META, ROW_ACTION_ID } from "../../../features/products/constants/action-defs";
6
- import { LISTING_BADGE_VARIANT } from "../../products/utils/listing-badge-variant";
6
+ import { listingBadgeVariant } from "../../products/utils/listing-badge-variant";
7
7
  const __P = {
8
8
  p3: "p-[var(--appkit-space-3)]",
9
9
  };
@@ -25,6 +25,6 @@ export function SellerProductsCards({ view, rows, isLoading, listingKind, select
25
25
  return (_jsxs(Container, { ...containerProps, children: [rows.length === 0 && !isLoading && (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)] col-span-full", size: "sm", children: listingKind !== "all" ? `No ${listingKind} listings found` : "No products listed yet" })), rows.map((row) => {
26
26
  const isSelected = selectedIds.has(row.id);
27
27
  const href = buildHref(row);
28
- return view === "grid" ? (_jsxs(Div, { className: "group relative bg-[var(--appkit-color-surface)] overflow-hidden", shadow: "hover-md", rounded: "xl", border: isSelected ? "strong" : "default", children: [_jsx(Div, { className: "absolute top-2 left-2 z-10", children: _jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", onClick: (e) => e.stopPropagation(), "aria-label": "Select" }) }), _jsxs(TextLink, { href: href, className: "block", children: [_jsx(Div, { className: "w-full aspect-square", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "card" }) }), _jsxs(Stack, { className: `${__P.p3}`, gap: "xs", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: LISTING_BADGE_VARIANT[row.listingKind] ?? "default", children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] })] }), _jsxs(Row, { className: "border-t border-[var(--appkit-color-border)]", justify: "end", gap: "xs", padding: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onEdit(row); }, "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDuplicate(row); }, "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDelete(row); }, "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id)) : (_jsxs(Row, { className: "bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-raised)]", align: "center", gap: "3", rounded: "lg", padding: "inlineSm", border: isSelected ? "strong" : "default", children: [_jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", "aria-label": "Select" }), _jsx(Div, { className: `w-12 h-12 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "thumbnail" }) }), _jsxs(TextLink, { href: href, className: "flex-1 min-w-0", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: LISTING_BADGE_VARIANT[row.listingKind] ?? "default", children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onEdit(row), "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDuplicate(row), "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(row), "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id));
28
+ return view === "grid" ? (_jsxs(Div, { className: "group relative bg-[var(--appkit-color-surface)] overflow-hidden", shadow: "hover-md", rounded: "xl", border: isSelected ? "strong" : "default", children: [_jsx(Div, { className: "absolute top-2 left-2 z-10", children: _jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", onClick: (e) => e.stopPropagation(), "aria-label": "Select" }) }), _jsxs(TextLink, { href: href, className: "block", children: [_jsx(Div, { className: "w-full aspect-square", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "card" }) }), _jsxs(Stack, { className: `${__P.p3}`, gap: "xs", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] })] }), _jsxs(Row, { className: "border-t border-[var(--appkit-color-border)]", justify: "end", gap: "xs", padding: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onEdit(row); }, "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDuplicate(row); }, "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); onDelete(row); }, "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id)) : (_jsxs(Row, { className: "bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-raised)]", align: "center", gap: "3", rounded: "lg", padding: "inlineSm", border: isSelected ? "strong" : "default", children: [_jsx(Checkbox, { bare: true, checked: isSelected, onChange: () => toggle(row.id), className: "h-4 w-4 rounded border-[var(--appkit-color-border)]", "aria-label": "Select" }), _jsx(Div, { className: `w-12 h-12 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: row.imageUrl, alt: row.primary, size: "thumbnail" }) }), _jsxs(TextLink, { href: href, className: "flex-1 min-w-0", children: [_jsx(Text, { className: "line-clamp-1", size: "sm", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] line-clamp-1", size: "xs", children: row.secondary })] })] }), _jsxs(Row, { className: "flex-shrink-0", gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onEdit(row), "aria-label": "Edit", children: ROW_ACTION_META[ROW_ACTION_ID.EDIT].label }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDuplicate(row), "aria-label": "Duplicate", children: ROW_ACTION_META[ROW_ACTION_ID.DUPLICATE].label }), onDelete && (_jsx(Button, { variant: "ghost", size: "sm", onClick: () => onDelete(row), "aria-label": "Delete", children: ROW_ACTION_META[ROW_ACTION_ID.DELETE].label }))] })] }, row.id));
29
29
  })] }));
30
30
  }
@@ -17,9 +17,11 @@ import { ROUTES } from "../../../constants";
17
17
  import { normalizeListingType } from "../../products/utils/listing-type";
18
18
  import { toRecordArray, toRelativeDate, toStringValue } from "../hooks/useSellerListingData";
19
19
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags";
20
+ import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags";
21
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
20
22
  import { SellerProductsCards } from "./SellerProductsCards";
21
23
  import { SellerProductsFilterFields } from "./SellerProductsFilterDrawer";
22
- import { LISTING_BADGE_VARIANT } from "../../products/utils/listing-badge-variant";
24
+ import { listingBadgeVariant } from "../../products/utils/listing-badge-variant";
23
25
  import { DataListingView } from "../../admin/components/DataListingView";
24
26
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
25
27
  import { buildBulkAction } from "../../../_internal/shared/actions/bulk-helpers";
@@ -43,21 +45,24 @@ const SORT_OPTIONS = [
43
45
  { value: sortBy("title", "DESC"), label: "Title Z–A" },
44
46
  { value: sortBy("price", "DESC"), label: "Price High" },
45
47
  { value: sortBy("price", "ASC"), label: "Price Low" },
48
+ // Now genuinely sortable — see the note on AdminProductsView's sortOptions.
49
+ { value: sortBy("featured", "DESC"), label: "Featured first" },
50
+ { value: sortBy("isPromoted", "DESC"), label: "Promoted first" },
46
51
  ];
47
52
  const STATUS_OPTIONS = SELLER_PRODUCT_STATUS_TABS;
48
53
  function TypeDropdown({ active, onChange, }) {
49
54
  const flags = useListingTypeFlags();
55
+ // Derived from ALL_LISTING_TYPES + the plugin registry (2026-08-21). This
56
+ // used to be a hand-written list that had no `art`/`stickers` entries and
57
+ // still offered `bundle` — which stopped being a listingType in SB-UNI-D, so
58
+ // picking it filtered on a value no product can hold and returned nothing.
50
59
  const options = [
51
60
  { value: "all", label: "All listings" },
52
- flags.standard && { value: "standard", label: "Standard" },
53
- flags.auction && { value: "auction", label: "Auction" },
54
- flags["pre-order"] && { value: "pre-order", label: "Pre-order" },
55
- flags["prize-draw"] && { value: "prize-draw", label: "Prize Draw" },
56
- { value: "bundle", label: "Bundle" },
57
- flags.classified && { value: "classified", label: "Classified" },
58
- flags["digital-code"] && { value: "digital-code", label: "Digital Code" },
59
- flags.live && { value: "live", label: "Live" },
60
- ].filter(Boolean);
61
+ ...ALL_LISTING_TYPES.filter(flags.isEnabled).map((type) => ({
62
+ value: type,
63
+ label: pluginFor(type).typeLabel,
64
+ })),
65
+ ];
61
66
  return (_jsxs(Row, { paddingX: "x-sm-lg-md", border: "bottom", padding: "y-xs", gap: "sm", children: [_jsx(Text, { className: "tracking-wide text-[var(--appkit-color-text-muted)]", size: "xs", weight: "semibold", transform: "uppercase", children: "Listing type" }), _jsx(Select, { options: options, value: active, onValueChange: (v) => onChange(v), wrapperClassName: "sm:max-w-xs", "aria-label": "Filter by listing type" })] }));
62
67
  }
63
68
  const PRODUCT_COLUMNS = [
@@ -70,7 +75,7 @@ const PRODUCT_COLUMNS = [
70
75
  {
71
76
  key: "primary",
72
77
  header: "Product",
73
- render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: LISTING_BADGE_VARIANT[row.listingKind] ?? "default", children: row.listingKind }), _jsx(Span, { size: "xs", color: "muted", children: row.secondary })] })] })),
78
+ render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] line-clamp-1", weight: "medium", children: row.primary }), _jsxs(Row, { gap: "sm", children: [_jsx(Badge, { variant: listingBadgeVariant(row.listingKind), children: row.listingKind }), _jsx(Span, { size: "xs", color: "muted", children: row.secondary })] })] })),
74
79
  },
75
80
  {
76
81
  key: "price",
@@ -206,13 +211,11 @@ export function SellerProductsView({ onDeleteProduct, onCreateClick, }) {
206
211
  mapRows: (response) => {
207
212
  const rows = toRecordArray(response.products).map((item, index) => {
208
213
  const lt = normalizeListingType(item);
209
- const kind = lt === "auction"
210
- ? "auction"
211
- : lt === "pre-order"
212
- ? "pre-order"
213
- : lt === "prize-draw"
214
- ? "prize-draw"
215
- : "standard";
214
+ // The row's own type, verbatim. This used to be a 4-branch ternary
215
+ // that collapsed classified / digital-code / live / art / stickers all
216
+ // down to "standard", so five of nine types displayed the wrong badge
217
+ // in the seller table (2026-08-21).
218
+ const kind = lt;
216
219
  const priceRaw = typeof item.price === "number" ? item.price : 0;
217
220
  const auctionSecondary = kind === "auction"
218
221
  ? [
@@ -51,15 +51,31 @@ declare class OfferRepository extends BaseRepository<OfferDocument> {
51
51
  */
52
52
  countByBuyerAndProduct(buyerUid: string, productId: string, since: Date): Promise<number>;
53
53
  updateStatus(offerId: string, patch: OfferUpdateInput): Promise<OfferDocument>;
54
- accept(offerId: string, lockedPrice: number, sellerNote?: string): Promise<OfferDocument>;
54
+ accept(offerId: string, lockedPrice: number, sellerNote?: string, checkoutDeadline?: Date): Promise<OfferDocument>;
55
55
  decline(offerId: string, sellerNote?: string): Promise<OfferDocument>;
56
56
  counter(offerId: string, counterAmount: number, sellerNote?: string): Promise<OfferDocument>;
57
- acceptCounter(offerId: string): Promise<OfferDocument>;
57
+ acceptCounter(offerId: string, checkoutDeadline?: Date): Promise<OfferDocument>;
58
58
  withdraw(offerId: string): Promise<OfferDocument>;
59
+ /**
60
+ * Terminal transition, called once the buyer's order for this offer exists.
61
+ *
62
+ * `"paid"` has been in `OfferStatusValues` since the feature shipped but had
63
+ * NO server-side writer — the only thing that ever set it was an optimistic
64
+ * client-side patch in UserOffersPanel, which meant a reload showed the offer
65
+ * back at "accepted" and it could be added to the cart and ordered again.
66
+ */
67
+ markPaid(offerId: string, orderId: string): Promise<OfferDocument>;
59
68
  /**
60
69
  * Cloud Functions compatibility: pending/countered offers already expired.
61
70
  */
62
71
  findExpiredActive(now: Date): Promise<OfferDocument[]>;
72
+ /**
73
+ * Accepted offers whose 48h checkout window has lapsed. These were never
74
+ * swept before — `findExpiredActive` only looks at pending/countered — so an
75
+ * accepted-but-never-paid offer stayed "accepted" forever and its locked
76
+ * price remained claimable long after the seller expected it to lapse.
77
+ */
78
+ findExpiredAccepted(now: Date): Promise<OfferDocument[]>;
63
79
  expireMany(offerIds: string[]): Promise<void>;
64
80
  }
65
81
  export declare const offerRepository: OfferRepository;
@@ -134,13 +134,18 @@ class OfferRepository extends BaseRepository {
134
134
  updatedAt: new Date(),
135
135
  });
136
136
  }
137
- async accept(offerId, lockedPrice, sellerNote) {
137
+ async accept(offerId, lockedPrice, sellerNote, checkoutDeadline) {
138
+ // One write, not two. `checkoutDeadline` used to be missing from
139
+ // OfferUpdateInput, which forced callers to follow every accept() with a
140
+ // separate generic update() — leaving a window where an offer was
141
+ // "accepted" with no deadline at all.
138
142
  return this.updateStatus(offerId, {
139
143
  status: "accepted",
140
144
  lockedPrice,
141
145
  sellerNote,
142
146
  acceptedAt: new Date(),
143
147
  respondedAt: new Date(),
148
+ ...(checkoutDeadline ? { checkoutDeadline } : {}),
144
149
  });
145
150
  }
146
151
  async decline(offerId, sellerNote) {
@@ -158,7 +163,7 @@ class OfferRepository extends BaseRepository {
158
163
  respondedAt: new Date(),
159
164
  });
160
165
  }
161
- async acceptCounter(offerId) {
166
+ async acceptCounter(offerId, checkoutDeadline) {
162
167
  const offer = await this.findById(offerId);
163
168
  if (!offer || !offer.counterAmount)
164
169
  throw new Error("Offer or counter not found");
@@ -167,6 +172,7 @@ class OfferRepository extends BaseRepository {
167
172
  lockedPrice: offer.counterAmount,
168
173
  acceptedAt: new Date(),
169
174
  respondedAt: new Date(),
175
+ ...(checkoutDeadline ? { checkoutDeadline } : {}),
170
176
  });
171
177
  }
172
178
  async withdraw(offerId) {
@@ -175,6 +181,21 @@ class OfferRepository extends BaseRepository {
175
181
  respondedAt: new Date(),
176
182
  });
177
183
  }
184
+ /**
185
+ * Terminal transition, called once the buyer's order for this offer exists.
186
+ *
187
+ * `"paid"` has been in `OfferStatusValues` since the feature shipped but had
188
+ * NO server-side writer — the only thing that ever set it was an optimistic
189
+ * client-side patch in UserOffersPanel, which meant a reload showed the offer
190
+ * back at "accepted" and it could be added to the cart and ordered again.
191
+ */
192
+ async markPaid(offerId, orderId) {
193
+ return this.updateStatus(offerId, {
194
+ status: "paid",
195
+ paidOrderId: orderId,
196
+ paidAt: new Date(),
197
+ });
198
+ }
178
199
  /**
179
200
  * Cloud Functions compatibility: pending/countered offers already expired.
180
201
  */
@@ -187,6 +208,21 @@ class OfferRepository extends BaseRepository {
187
208
  .get();
188
209
  return snapshot.docs.map((doc) => this.mapDoc(doc));
189
210
  }
211
+ /**
212
+ * Accepted offers whose 48h checkout window has lapsed. These were never
213
+ * swept before — `findExpiredActive` only looks at pending/countered — so an
214
+ * accepted-but-never-paid offer stayed "accepted" forever and its locked
215
+ * price remained claimable long after the seller expected it to lapse.
216
+ */
217
+ async findExpiredAccepted(now) {
218
+ const snapshot = await this.db
219
+ .collection(this.collection)
220
+ .where(OFFER_FIELDS.STATUS, "==", "accepted")
221
+ .where(OFFER_FIELDS.CHECKOUT_DEADLINE, "<=", now)
222
+ .limit(500)
223
+ .get();
224
+ return snapshot.docs.map((doc) => this.mapDoc(doc));
225
+ }
190
226
  async expireMany(offerIds) {
191
227
  const batch = this.db.batch();
192
228
  const now = new Date();
@@ -36,9 +36,12 @@ export interface OfferDocument extends BaseDocument {
36
36
  acceptedAt?: Date;
37
37
  checkoutDeadline?: Date;
38
38
  respondedAt?: Date;
39
+ /** Set by `markPaid` once the buyer's order exists — makes "paid" auditable. */
40
+ paidOrderId?: string;
41
+ paidAt?: Date;
39
42
  }
40
43
  export declare const OFFER_COLLECTION: "offers";
41
- export declare const OFFER_INDEXED_FIELDS: readonly ["buyerUid", "storeId", "productId", "status", "createdAt", "expiresAt"];
44
+ export declare const OFFER_INDEXED_FIELDS: readonly ["buyerUid", "storeId", "productId", "status", "createdAt", "expiresAt", "checkoutDeadline"];
42
45
  export declare const OFFER_FIELDS: {
43
46
  readonly ID: "id";
44
47
  readonly PRODUCT_ID: "productId";
@@ -60,11 +63,13 @@ export declare const OFFER_FIELDS: {
60
63
  readonly ACCEPTED_AT: "acceptedAt";
61
64
  readonly CHECKOUT_DEADLINE: "checkoutDeadline";
62
65
  readonly RESPONDED_AT: "respondedAt";
66
+ readonly PAID_ORDER_ID: "paidOrderId";
67
+ readonly PAID_AT: "paidAt";
63
68
  readonly CREATED_AT: "createdAt";
64
69
  readonly UPDATED_AT: "updatedAt";
65
70
  };
66
71
  export type OfferCreateInput = Pick<OfferDocument, "productId" | "productTitle" | "productSlug" | "productImageUrl" | "buyerUid" | "buyerName" | "buyerEmail" | "storeId" | "storeName" | "offerAmount" | "listedPrice" | "currency" | "buyerNote">;
67
- export type OfferUpdateInput = Partial<Pick<OfferDocument, "status" | "counterAmount" | "lockedPrice" | "sellerNote" | "acceptedAt" | "respondedAt" | "updatedAt">>;
72
+ export type OfferUpdateInput = Partial<Pick<OfferDocument, "status" | "counterAmount" | "lockedPrice" | "sellerNote" | "acceptedAt" | "checkoutDeadline" | "respondedAt" | "paidOrderId" | "paidAt" | "updatedAt">>;
68
73
  export declare const offerQueryHelpers: {
69
74
  readonly byBuyer: (buyerUid: string) => readonly ["buyerUid", "==", string];
70
75
  readonly byStore: (storeId: string) => readonly ["storeId", "==", string];
@@ -20,6 +20,7 @@ export const OFFER_INDEXED_FIELDS = [
20
20
  "status",
21
21
  "createdAt",
22
22
  "expiresAt",
23
+ "checkoutDeadline",
23
24
  ];
24
25
  export const OFFER_FIELDS = {
25
26
  ID: "id",
@@ -42,6 +43,8 @@ export const OFFER_FIELDS = {
42
43
  ACCEPTED_AT: "acceptedAt",
43
44
  CHECKOUT_DEADLINE: "checkoutDeadline",
44
45
  RESPONDED_AT: "respondedAt",
46
+ PAID_ORDER_ID: "paidOrderId",
47
+ PAID_AT: "paidAt",
45
48
  CREATED_AT: "createdAt",
46
49
  UPDATED_AT: "updatedAt",
47
50
  };
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ export declare const counterOfferFormSchema: z.ZodObject<{
3
+ counterAmount: z.ZodNumber;
4
+ sellerNote: z.ZodOptional<z.ZodString>;
5
+ }, "strip", z.ZodTypeAny, {
6
+ counterAmount: number;
7
+ sellerNote?: string | undefined;
8
+ }, {
9
+ counterAmount: number;
10
+ sellerNote?: string | undefined;
11
+ }>;
12
+ export type CounterOfferFormValues = z.infer<typeof counterOfferFormSchema>;
@@ -0,0 +1,28 @@
1
+ /*
2
+ * WHY: Rule #9 — every appkit form is schema-driven. The seller's counter-offer
3
+ * drawer had no form at all before 2026-08-21 (the view took an
4
+ * `(id) => void` callback and left each consumer to invent an input), so
5
+ * `respondToOffer`'s counter branch had zero real callers.
6
+ * WHAT: Zod schema for the counter-offer QuickFormDrawer. Deliberately only the
7
+ * field-shape rules that can be checked without loading the offer — the
8
+ * relational rules (below listed price, different from the buyer's offer)
9
+ * stay server-side in `respondToOffer`, which is the only place that can
10
+ * be trusted with them.
11
+ *
12
+ * EXPORTS:
13
+ * counterOfferFormSchema, type CounterOfferFormValues
14
+ *
15
+ * @tag domain:seller,offers
16
+ * @tag layer:schema
17
+ * @tag pattern:none
18
+ * @tag access:isomorphic
19
+ * @tag consumers:SellerOffersView
20
+ * @tag sideEffects:none
21
+ */
22
+ import { z } from "zod";
23
+ export const counterOfferFormSchema = z.object({
24
+ counterAmount: z.coerce
25
+ .number({ invalid_type_error: "Enter a counter amount." })
26
+ .positive("Counter amount must be greater than zero."),
27
+ sellerNote: z.string().max(500, "Keep your note under 500 characters.").optional(),
28
+ });
@@ -85,7 +85,7 @@ export async function getStoreReviews(storeSlug) {
85
85
  const publishedProducts = allProducts
86
86
  .filter((p) => p.status === ProductStatusValues.PUBLISHED)
87
87
  .slice(0, 20);
88
- const reviewArrays = await Promise.all(publishedProducts.map((p) => reviewRepository.findApprovedByProduct(p.id)));
88
+ const reviewArrays = await Promise.all(publishedProducts.map((p) => reviewRepository.findApprovedByProduct(p.id, 50)));
89
89
  const allReviews = reviewArrays
90
90
  .flat()
91
91
  .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
@@ -12,19 +12,13 @@ import { AuctionFilters } from "../../auctions/components/AuctionFilters";
12
12
  import { getDefaultCurrency } from "../../../core/baseline-resolver";
13
13
  import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
14
14
  import { pushWishlistOp } from "../../cart/utils/pending-ops";
15
- import { PRODUCT_FIELDS } from "../../../constants/field-names";
16
- import { sortBy } from "../../../constants/sort";
15
+ import { AUCTION_PUBLIC_SORT_OPTIONS } from "../../products/constants/sieve";
17
16
  const __P = {
18
17
  p3: "p-[var(--appkit-space-3)]",
19
18
  };
20
- const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.AUCTION_END_DATE, "ASC");
21
- const AUCTION_SORT_OPTIONS = [
22
- { value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE, "ASC"), label: "Ending Soonest" },
23
- { value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE), label: "Ending Latest" },
24
- { value: sortBy(PRODUCT_FIELDS.CURRENT_BID), label: "Highest Bid" },
25
- { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
26
- { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
27
- ];
19
+ // Shared with /auctions — a local copy here drifted ("Ending Soonest" vs
20
+ // "Ending Soon", and it silently omitted Most Bids / Buy It Now).
21
+ const DEFAULT_SORT = AUCTION_PUBLIC_SORT_OPTIONS[0].value;
28
22
  const FILTER_KEYS = ["minBid", "maxBid", "dateFrom", "dateTo"];
29
23
  export function StoreAuctionsListing({ storeId, initialData }) {
30
24
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
@@ -106,7 +100,7 @@ export function StoreAuctionsListing({ storeId, initialData }) {
106
100
  isWishlisted: (productId) => wishlistedIds.has(productId),
107
101
  };
108
102
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-[var(--appkit-space-4)]";
109
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => {
103
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: AUCTION_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
110
104
  table.set("sort", v);
111
105
  }, view: view, onViewChange: (v) => {
112
106
  if (v === "table")
@@ -8,19 +8,14 @@ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Stack, Text, Sticky
8
8
  import { MarketplacePreorderCard } from "../../pre-orders/components/MarketplacePreorderCard";
9
9
  import { ProductFilters } from "../../products/components/ProductFilters";
10
10
  import { ROUTES } from "../../../next";
11
- import { PRODUCT_FIELDS } from "../../../constants/field-names";
12
- import { sortBy } from "../../../constants/sort";
11
+ import { PREORDER_PUBLIC_SORT_OPTIONS } from "../../products/constants/sieve";
13
12
  const __P = {
14
13
  p3: "p-[var(--appkit-space-3)]",
15
14
  };
16
- const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
17
- const PREORDER_SORT_OPTIONS = [
18
- { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
19
- { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
20
- { value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE, "ASC"), label: "Delivery Soon" },
21
- { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
22
- { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
23
- ];
15
+ // Shared with /pre-orders. The local copy also defaulted to -createdAt,
16
+ // so a store tab opened Newest-first while the public page opened
17
+ // Earliest-Delivery-first for the same data.
18
+ const DEFAULT_SORT = PREORDER_PUBLIC_SORT_OPTIONS[0].value;
24
19
  const FILTER_KEYS = ["minPrice", "maxPrice"];
25
20
  export function StorePreOrdersListing({ storeId, initialData }) {
26
21
  const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
@@ -59,7 +54,7 @@ export function StorePreOrdersListing({ storeId, initialData }) {
59
54
  table.set("q", searchInput.trim());
60
55
  }, [searchInput, table]);
61
56
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-[var(--appkit-space-4)]";
62
- return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => {
57
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: PREORDER_PUBLIC_SORT_OPTIONS, onSortChange: (v) => {
63
58
  table.set("sort", v);
64
59
  }, view: view, onViewChange: (v) => {
65
60
  if (v === "table")
@@ -2,4 +2,8 @@ import React from "react";
2
2
  export interface StoreReviewsListingProps {
3
3
  storeSlug: string;
4
4
  }
5
+ /**
6
+ * The `/stores/[slug]/reviews` tab. Delegates to the shared `ReviewsListingPanel` —
7
+ * see the note in `ReviewsIndexListing` for why the two were merged.
8
+ */
5
9
  export declare function StoreReviewsListing({ storeSlug }: StoreReviewsListingProps): React.JSX.Element;