@mohasinac/appkit 2.7.11 → 2.7.13

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 (152) hide show
  1. package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
  2. package/dist/_internal/server/jobs/core/bundleStockSync.js +17 -20
  3. package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
  4. package/dist/_internal/server/jobs/core/onProductStockChange.js +18 -20
  5. package/dist/_internal/server/jobs/core/onProductWrite.js +3 -2
  6. package/dist/_internal/server/jobs/core/prizeRevealClose.js +7 -5
  7. package/dist/_internal/server/jobs/core/prizeRevealExpiry.js +11 -6
  8. package/dist/_internal/server/jobs/core/prizeRevealOpen.js +13 -7
  9. package/dist/_internal/server/jobs/core/prizeRevealReminder.js +9 -4
  10. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +3 -2
  11. package/dist/_internal/shared/features/blog/schema.d.ts +4 -4
  12. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +3 -6
  13. package/dist/_internal/shared/features/categories/bundle-copy.js +3 -6
  14. package/dist/_internal/shared/features/categories/bundle-schemas.d.ts +32 -32
  15. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  16. package/dist/_internal/shared/features/products/schema.d.ts +20 -20
  17. package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
  18. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  19. package/dist/constants/field-names.d.ts +824 -0
  20. package/dist/constants/field-names.js +929 -0
  21. package/dist/constants/sort.d.ts +7 -0
  22. package/dist/constants/sort.js +5 -0
  23. package/dist/constants/table-keys.d.ts +48 -0
  24. package/dist/constants/table-keys.js +46 -0
  25. package/dist/features/account/components/AddressesIndexListing.js +9 -6
  26. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
  27. package/dist/features/admin/components/AdminBidsView.js +4 -1
  28. package/dist/features/admin/components/AdminBlogView.js +9 -2
  29. package/dist/features/admin/components/AdminBrandsView.js +9 -2
  30. package/dist/features/admin/components/AdminBundlesView.js +0 -1
  31. package/dist/features/admin/components/AdminCarouselView.js +4 -1
  32. package/dist/features/admin/components/AdminCartsView.js +4 -1
  33. package/dist/features/admin/components/AdminCategoriesView.js +9 -2
  34. package/dist/features/admin/components/AdminContactView.js +4 -1
  35. package/dist/features/admin/components/AdminCouponsView.js +4 -1
  36. package/dist/features/admin/components/AdminFaqsView.js +5 -1
  37. package/dist/features/admin/components/AdminFeaturesView.js +5 -2
  38. package/dist/features/admin/components/AdminHistoryView.js +5 -1
  39. package/dist/features/admin/components/AdminNewsletterView.js +4 -1
  40. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  41. package/dist/features/admin/components/AdminOrdersView.js +14 -12
  42. package/dist/features/admin/components/AdminPayoutsView.js +4 -1
  43. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
  44. package/dist/features/admin/components/AdminProductsView.js +14 -3
  45. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
  46. package/dist/features/admin/components/AdminReviewsView.js +4 -1
  47. package/dist/features/admin/components/AdminScammersView.js +4 -1
  48. package/dist/features/admin/components/AdminSessionsView.js +4 -1
  49. package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
  50. package/dist/features/admin/components/AdminStoresView.js +10 -3
  51. package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
  52. package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
  53. package/dist/features/admin/components/AdminTeamView.js +4 -1
  54. package/dist/features/admin/components/AdminUsersView.js +9 -12
  55. package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
  56. package/dist/features/admin/components/AdminViewCards.js +53 -0
  57. package/dist/features/admin/components/AdminWishlistsView.js +4 -1
  58. package/dist/features/auctions/repository/bid.repository.js +1 -6
  59. package/dist/features/auctions/schemas/index.d.ts +4 -4
  60. package/dist/features/blog/actions/blog-actions.d.ts +16 -16
  61. package/dist/features/blog/components/BlogFilters.d.ts +28 -28
  62. package/dist/features/blog/components/BlogFilters.js +18 -15
  63. package/dist/features/blog/components/BlogIndexListing.js +20 -16
  64. package/dist/features/blog/schemas/index.d.ts +10 -10
  65. package/dist/features/categories/components/BundleDetailView.js +0 -1
  66. package/dist/features/categories/components/CategoriesIndexListing.js +25 -21
  67. package/dist/features/categories/components/CategoryBundlesListing.js +2 -4
  68. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  69. package/dist/features/categories/schemas/index.d.ts +2 -2
  70. package/dist/features/events/components/AdminEventsView.js +5 -1
  71. package/dist/features/events/components/EventFilters.d.ts +24 -24
  72. package/dist/features/events/components/EventFilters.js +16 -13
  73. package/dist/features/events/components/EventStatusBadge.js +1 -0
  74. package/dist/features/events/components/EventsIndexListing.js +31 -26
  75. package/dist/features/events/components/EventsListPageView.js +13 -10
  76. package/dist/features/events/schemas/firestore.d.ts +1 -0
  77. package/dist/features/events/schemas/firestore.js +1 -0
  78. package/dist/features/events/schemas/index.d.ts +1 -1
  79. package/dist/features/events/types/index.d.ts +1 -1
  80. package/dist/features/faq/actions/faq-actions.d.ts +10 -10
  81. package/dist/features/faq/schemas/index.d.ts +4 -4
  82. package/dist/features/homepage/components/FeaturedBundlesSection.js +1 -3
  83. package/dist/features/orders/components/OrderFilters.d.ts +20 -20
  84. package/dist/features/orders/components/OrderFilters.js +33 -30
  85. package/dist/features/orders/repository/orders.repository.js +1 -15
  86. package/dist/features/orders/schemas/index.d.ts +2 -2
  87. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +35 -31
  88. package/dist/features/pre-orders/repository/preorders.repository.js +8 -4
  89. package/dist/features/pre-orders/schemas/index.d.ts +3 -3
  90. package/dist/features/products/components/AuctionsIndexListing.js +34 -30
  91. package/dist/features/products/components/BidHistory.d.ts +0 -1
  92. package/dist/features/products/components/BidHistory.js +1 -1
  93. package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -40
  94. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  95. package/dist/features/products/components/ProductFilters.d.ts +20 -20
  96. package/dist/features/products/components/ProductFilters.js +62 -59
  97. package/dist/features/products/components/ProductsIndexListing.js +28 -24
  98. package/dist/features/products/components/ProductsIndexPageView.js +18 -10
  99. package/dist/features/products/hooks/useProducts.js +2 -0
  100. package/dist/features/products/repository/products.repository.js +2 -22
  101. package/dist/features/products/schemas/index.d.ts +28 -28
  102. package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
  103. package/dist/features/products/types/index.d.ts +2 -0
  104. package/dist/features/promotions/components/CouponsIndexListing.js +57 -32
  105. package/dist/features/promotions/schemas/index.d.ts +4 -4
  106. package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
  107. package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
  108. package/dist/features/reviews/components/ReviewFilters.js +10 -7
  109. package/dist/features/reviews/components/ReviewsIndexListing.js +21 -17
  110. package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
  111. package/dist/features/reviews/components/ReviewsList.js +11 -5
  112. package/dist/features/reviews/schemas/firestore.d.ts +1 -0
  113. package/dist/features/reviews/schemas/index.d.ts +8 -8
  114. package/dist/features/reviews/types/index.d.ts +1 -0
  115. package/dist/features/search/schemas/index.d.ts +2 -2
  116. package/dist/features/seller/components/SellerAuctionsView.js +5 -1
  117. package/dist/features/seller/components/SellerBidsView.js +4 -1
  118. package/dist/features/seller/components/SellerCouponsView.js +4 -1
  119. package/dist/features/seller/components/SellerOffersView.js +5 -1
  120. package/dist/features/seller/components/SellerOrdersView.js +4 -1
  121. package/dist/features/seller/components/SellerPayoutsView.js +5 -1
  122. package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
  123. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
  124. package/dist/features/seller/components/SellerProductsView.js +4 -1
  125. package/dist/features/seller/schemas/index.d.ts +12 -12
  126. package/dist/features/stores/api/route.js +10 -1
  127. package/dist/features/stores/components/StoreReviewsListing.js +1 -1
  128. package/dist/features/stores/components/StoresIndexListing.js +24 -20
  129. package/dist/features/stores/repository/stores.repository.js +8 -5
  130. package/dist/features/stores/schemas/firestore.d.ts +1 -0
  131. package/dist/features/stores/schemas/index.d.ts +4 -4
  132. package/dist/index.d.ts +8 -1
  133. package/dist/index.js +12 -1
  134. package/dist/providers/firebase-client/auth.d.ts +7 -0
  135. package/dist/providers/firebase-client/auth.js +15 -8
  136. package/dist/react/hooks/useUrlTable.js +2 -1
  137. package/dist/seed/bids-seed-data.js +45 -44
  138. package/dist/seed/categories-seed-data.js +41 -40
  139. package/dist/seed/coupons-seed-data.js +41 -40
  140. package/dist/seed/products-auctions-seed-data.js +54 -53
  141. package/dist/seed/products-preorders-seed-data.js +25 -24
  142. package/dist/seed/products-prize-draws-seed-data.js +24 -23
  143. package/dist/seed/products-standard-seed-data.js +294 -293
  144. package/dist/seed/reviews-seed-data.js +61 -60
  145. package/dist/seed/scammers-seed-data.js +3 -2
  146. package/dist/seed/stores-seed-data.js +9 -8
  147. package/dist/seed/support-tickets-seed-data.js +13 -12
  148. package/dist/seed/users-seed-data.js +28 -27
  149. package/dist/tailwind-utilities.css +1 -1
  150. package/dist/utils/sieve-builder.d.ts +41 -0
  151. package/dist/utils/sieve-builder.js +66 -0
  152. package/package.json +2 -2
@@ -16,23 +16,27 @@ import { pushCartOp, pushWishlistOp } from "../../cart/utils/pending-ops";
16
16
  import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
17
17
  import { useBrands } from "../../products/hooks/useBrands";
18
18
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
19
+ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
20
+ import { sortBy } from "../../../constants/sort";
21
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
22
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
19
23
  const PREORDER_SORT_OPTIONS = [
20
- { value: "-createdAt", label: "Newest First" },
21
- { value: "createdAt", label: "Oldest First" },
22
- { value: "preOrderDeliveryDate", label: "Delivery: Soonest" },
23
- { value: "-preOrderDeliveryDate", label: "Delivery: Furthest" },
24
- { value: "price", label: "Price: Low to High" },
25
- { value: "-price", label: "Price: High to Low" },
24
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
25
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
26
+ { value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE, "ASC"), label: "Delivery: Soonest" },
27
+ { value: sortBy(PRODUCT_FIELDS.PRE_ORDER_DELIVERY_DATE), label: "Delivery: Furthest" },
28
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
29
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
26
30
  ];
27
- const FILTER_KEYS = ["category", "brand", "minPrice", "maxPrice", "storeId", "preOrderProductionStatus", "dateFrom", "dateTo"];
31
+ const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_PRICE, TABLE_KEYS.MAX_PRICE, TABLE_KEYS.STORE_ID, TABLE_KEYS.PREORDER_STATUS, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
28
32
  export function PreOrdersIndexListing({ initialData, categorySlug, brandName }) {
29
- const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
33
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
30
34
  const { showToast } = useToast();
31
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
35
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
32
36
  const [filterOpen, setFilterOpen] = useState(false);
33
37
  const [compareIds, setCompareIds] = useState([]);
34
- const showClosed = table.get("showClosed") === "true";
35
- const [view, setView] = useState(table.get("view") || "grid");
38
+ const showClosed = table.get(TABLE_KEYS.SHOW_CLOSED) === "true";
39
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
36
40
  const localCart = useGuestCart();
37
41
  const localWishlist = useGuestWishlist();
38
42
  const { categories } = useCategoryTree();
@@ -81,31 +85,31 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
81
85
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
82
86
  }, []);
83
87
  const resetAll = useCallback(() => {
84
- const updates = { q: "", sort: "", showClosed: "" };
88
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_CLOSED]: "" };
85
89
  for (const k of FILTER_KEYS)
86
90
  updates[k] = "";
87
91
  table.setMany(updates);
88
92
  setSearchInput("");
89
93
  }, [table]);
90
94
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
91
- const hasActiveState = !!table.get("q") ||
92
- table.get("showClosed") === "true" ||
93
- table.get("sort") !== "-createdAt" ||
95
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
96
+ table.get(TABLE_KEYS.SHOW_CLOSED) === "true" ||
97
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
94
98
  activeFilterCount > 0;
95
99
  const params = {
96
- q: table.get("q") || undefined,
97
- category: table.get("category") || undefined,
100
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
101
+ category: table.get(TABLE_KEYS.CATEGORY) || undefined,
98
102
  categorySlug: categorySlug || undefined,
99
- brand: brandName || table.get("brand") || undefined,
100
- minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
101
- maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
102
- storeId: table.get("storeId") || undefined,
103
- preOrderProductionStatus: (table.get("preOrderProductionStatus") || undefined),
104
- dateFrom: table.get("dateFrom") || undefined,
105
- dateTo: table.get("dateTo") || undefined,
106
- sort: table.get("sort") || "-createdAt",
107
- page: table.getNumber("page", 1),
108
- perPage: table.getNumber("pageSize", 24),
103
+ brand: brandName || table.get(TABLE_KEYS.BRAND) || undefined,
104
+ minPrice: table.get(TABLE_KEYS.MIN_PRICE) ? Number(table.get(TABLE_KEYS.MIN_PRICE)) : undefined,
105
+ maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
106
+ storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
107
+ preOrderProductionStatus: (table.get(TABLE_KEYS.PREORDER_STATUS) || undefined),
108
+ dateFrom: table.get(TABLE_KEYS.DATE_FROM) || undefined,
109
+ dateTo: table.get(TABLE_KEYS.DATE_TO) || undefined,
110
+ sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
111
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
112
+ perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
109
113
  listingType: "pre-order",
110
114
  // Hide out-of-stock pre-orders by default. Uses stockQuantity>0 (always-present field).
111
115
  // Quota over-sign is intentional — never block pre-orders by availability.
@@ -113,17 +117,17 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
113
117
  };
114
118
  const { products: preOrders, totalPages, page, isLoading } = useProducts(params, { initialData });
115
119
  const commitSearch = useCallback(() => {
116
- table.set("q", searchInput.trim());
120
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
117
121
  }, [searchInput, table]);
118
122
  const handleSearchKeyDown = (e) => {
119
123
  if (e.key === "Enter")
120
124
  commitSearch();
121
125
  };
122
126
  const handleViewToggle = (next) => {
123
- if (next === "table")
127
+ if (next === VIEW_MODE.TABLE)
124
128
  return;
125
129
  setView(next);
126
- table.set("view", next);
130
+ table.set(TABLE_KEYS.VIEW, next);
127
131
  };
128
132
  const wishlistActions = {
129
133
  addToWishlist: (productId) => {
@@ -151,7 +155,7 @@ export function PreOrdersIndexListing({ initialData, categorySlug, brandName })
151
155
  }, [localCart, showToast]);
152
156
  const selection = useBulkSelection({ items: preOrders, keyExtractor: (p) => p.id });
153
157
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
154
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get("sort") || "-createdAt", sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set("showClosed", showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
158
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: preOrders.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set(TABLE_KEYS.SHOW_CLOSED, showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
155
159
  {
156
160
  id: ACTION_ID.ADD_TO_CART,
157
161
  label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
@@ -1,19 +1,23 @@
1
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
2
+ const { LISTING_TYPE, STATUS, FEATURED, CREATED_AT } = PRODUCT_FIELDS;
3
+ const { PRE_ORDER } = PRODUCT_FIELDS.LISTING_TYPE_VALUES;
4
+ const { PUBLISHED } = PRODUCT_FIELDS.STATUS_VALUES;
1
5
  export class PreordersRepository {
2
6
  constructor(repo) {
3
7
  this.repo = repo;
4
8
  }
5
9
  async findPreorders() {
6
10
  const result = await this.repo.findAll({
7
- filters: "isPreorder==true,active==true",
8
- sort: "createdAt",
11
+ filters: `${LISTING_TYPE}==${PRE_ORDER},${STATUS}==${PUBLISHED}`,
12
+ sort: CREATED_AT,
9
13
  order: "desc",
10
14
  });
11
15
  return result.data;
12
16
  }
13
17
  async findFeaturedPreorders() {
14
18
  const result = await this.repo.findAll({
15
- filters: "isPreorder==true,active==true,isFeatured==true",
16
- sort: "createdAt",
19
+ filters: `${LISTING_TYPE}==${PRE_ORDER},${STATUS}==${PUBLISHED},${FEATURED}==true`,
20
+ sort: CREATED_AT,
17
21
  order: "desc",
18
22
  });
19
23
  return result.data;
@@ -48,8 +48,8 @@ export declare const preorderItemSchema: z.ZodObject<{
48
48
  id: string;
49
49
  name: string;
50
50
  active: boolean;
51
- slug: string;
52
51
  isFeatured: boolean;
52
+ slug: string;
53
53
  salePrice: number;
54
54
  regularPrice: number;
55
55
  createdAt?: string | undefined;
@@ -61,8 +61,8 @@ export declare const preorderItemSchema: z.ZodObject<{
61
61
  thumbnailUrl?: string | undefined;
62
62
  youtubeId?: string | undefined;
63
63
  }[] | undefined;
64
- images?: string[] | undefined;
65
64
  brand?: string | undefined;
65
+ images?: string[] | undefined;
66
66
  franchise?: string | undefined;
67
67
  preorderShipDate?: string | undefined;
68
68
  }, {
@@ -81,9 +81,9 @@ export declare const preorderItemSchema: z.ZodObject<{
81
81
  youtubeId?: string | undefined;
82
82
  }[] | undefined;
83
83
  active?: boolean | undefined;
84
- images?: string[] | undefined;
85
84
  brand?: string | undefined;
86
85
  isFeatured?: boolean | undefined;
86
+ images?: string[] | undefined;
87
87
  franchise?: string | undefined;
88
88
  preorderShipDate?: string | undefined;
89
89
  }>;
@@ -12,22 +12,26 @@ import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
12
12
  import { pushWishlistOp } from "../../cart/utils/pending-ops";
13
13
  import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
14
14
  import { useBrands } from "../hooks/useBrands";
15
+ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
16
+ import { sortBy } from "../../../constants/sort";
17
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
18
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.AUCTION_END_DATE, "ASC");
15
19
  const AUCTION_SORT_OPTIONS = [
16
- { value: "auctionEndDate", label: "Ending Soonest" },
17
- { value: "-auctionEndDate", label: "Ending Latest" },
18
- { value: "currentBid", label: "Bid: Low to High" },
19
- { value: "-currentBid", label: "Bid: High to Low" },
20
- { value: "-createdAt", label: "Newly Listed" },
21
- { value: "createdAt", label: "Oldest Listed" },
20
+ { value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE, "ASC"), label: "Ending Soonest" },
21
+ { value: sortBy(PRODUCT_FIELDS.AUCTION_END_DATE), label: "Ending Latest" },
22
+ { value: sortBy(PRODUCT_FIELDS.CURRENT_BID, "ASC"), label: "Bid: Low to High" },
23
+ { value: sortBy(PRODUCT_FIELDS.CURRENT_BID), label: "Bid: High to Low" },
24
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newly Listed" },
25
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest Listed" },
22
26
  ];
23
- const FILTER_KEYS = ["category", "brand", "minBid", "maxBid", "storeId", "dateFrom", "dateTo", "condition"];
27
+ const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.BRAND, TABLE_KEYS.MIN_BID, TABLE_KEYS.MAX_BID, TABLE_KEYS.STORE_ID, TABLE_KEYS.DATE_FROM, TABLE_KEYS.DATE_TO];
24
28
  export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
25
- const table = useUrlTable({ defaults: { pageSize: "24", sort: "auctionEndDate" } });
29
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
26
30
  const { showToast } = useToast();
27
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
31
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
28
32
  const [filterOpen, setFilterOpen] = useState(false);
29
- const showEnded = table.get("showEnded") === "true";
30
- const [view, setView] = useState(table.get("view") || "grid");
33
+ const showEnded = table.get(TABLE_KEYS.SHOW_ENDED) === "true";
34
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
31
35
  const localWishlist = useGuestWishlist();
32
36
  const wishlistedIds = new Set(localWishlist.items.filter((i) => i.type === "auction").map((i) => i.itemId));
33
37
  const { categories } = useCategoryTree();
@@ -75,50 +79,50 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
75
79
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
76
80
  }, []);
77
81
  const resetAll = useCallback(() => {
78
- const updates = { q: "", sort: "", showEnded: "" };
82
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_ENDED]: "" };
79
83
  for (const k of FILTER_KEYS)
80
84
  updates[k] = "";
81
85
  table.setMany(updates);
82
86
  setSearchInput("");
83
87
  }, [table]);
84
88
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
85
- const hasActiveState = !!table.get("q") ||
86
- table.get("showEnded") === "true" ||
87
- table.get("sort") !== "auctionEndDate" ||
89
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
90
+ table.get(TABLE_KEYS.SHOW_ENDED) === "true" ||
91
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
88
92
  activeFilterCount > 0;
89
93
  const params = {
90
- q: table.get("q") || undefined,
91
- category: table.get("category") || undefined,
94
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
95
+ category: table.get(TABLE_KEYS.CATEGORY) || undefined,
92
96
  categorySlug: categorySlug || undefined,
93
- brand: brandName || table.get("brand") || undefined,
94
- minBid: table.get("minBid") ? Number(table.get("minBid")) : undefined,
95
- maxBid: table.get("maxBid") ? Number(table.get("maxBid")) : undefined,
96
- storeId: table.get("storeId") || undefined,
97
+ brand: brandName || table.get(TABLE_KEYS.BRAND) || undefined,
98
+ minBid: table.get(TABLE_KEYS.MIN_BID) ? Number(table.get(TABLE_KEYS.MIN_BID)) : undefined,
99
+ maxBid: table.get(TABLE_KEYS.MAX_BID) ? Number(table.get(TABLE_KEYS.MAX_BID)) : undefined,
100
+ storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
97
101
  // When showEnded is false (default), force dateFrom=now so only live auctions appear.
98
102
  // When showEnded is true, respect the filter-drawer value (or show all if none set).
99
103
  dateFrom: showEnded
100
- ? (table.get("dateFrom") || undefined)
104
+ ? (table.get(TABLE_KEYS.DATE_FROM) || undefined)
101
105
  : new Date().toISOString(),
102
- dateTo: table.get("dateTo") || undefined,
103
- sort: table.get("sort") || "auctionEndDate",
104
- page: table.getNumber("page", 1),
105
- perPage: table.getNumber("pageSize", 24),
106
+ dateTo: table.get(TABLE_KEYS.DATE_TO) || undefined,
107
+ sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
108
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
109
+ perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
106
110
  listingType: "auction",
107
111
  };
108
112
  const { products: auctions, totalPages, page, isLoading } = useProducts(params, { initialData });
109
113
  const selection = useBulkSelection({ items: auctions, keyExtractor: (a) => a.id });
110
114
  const commitSearch = useCallback(() => {
111
- table.set("q", searchInput.trim());
115
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
112
116
  }, [searchInput, table]);
113
117
  const handleSearchKeyDown = (e) => {
114
118
  if (e.key === "Enter")
115
119
  commitSearch();
116
120
  };
117
121
  const handleViewToggle = (next) => {
118
- if (next === "table")
122
+ if (next === VIEW_MODE.TABLE)
119
123
  return;
120
124
  setView(next);
121
- table.set("view", next);
125
+ table.set(TABLE_KEYS.VIEW, next);
122
126
  };
123
127
  const wishlistActions = {
124
128
  addToWishlist: (productId) => {
@@ -136,7 +140,7 @@ export function AuctionsIndexListing({ initialData, categorySlug, brandName }) {
136
140
  isWishlisted: (productId) => wishlistedIds.has(productId),
137
141
  };
138
142
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-3 gap-4";
139
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get("sort") || "auctionEndDate", sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show ended" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showEnded, onClick: () => table.set("showEnded", showEnded ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showEnded ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showEnded ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
143
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, view: view, onViewChange: handleViewToggle, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: auctions.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show ended" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showEnded, onClick: () => table.set(TABLE_KEYS.SHOW_ENDED, showEnded ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showEnded ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showEnded ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
140
144
  {
141
145
  id: "watchlist",
142
146
  label: "Add to Watchlist",
@@ -20,7 +20,6 @@ export interface BidHistoryProps {
20
20
  labels?: {
21
21
  title?: string;
22
22
  noHistory?: string;
23
- bidder?: string;
24
23
  amount?: string;
25
24
  time?: string;
26
25
  };
@@ -30,5 +30,5 @@ export function BidHistory({ isLoading = false, isEmpty = false, renderList, ren
30
30
  }
31
31
  if (renderList)
32
32
  return _jsx("div", { className: className, "data-section": "bidhistory-div-420", children: renderList() });
33
- return (_jsxs("div", { className: className, "data-section": "bidhistory-div-421", children: [labels.title && (_jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: labels.title })), _jsx("div", { className: "space-y-2", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-3 text-sm", "data-section": "bidhistory-div-422", children: [_jsx(Span, { className: "font-medium text-zinc-700 dark:text-zinc-300 truncate max-w-[40%]", children: bid.bidderName ?? bid.bidderId }), _jsx(Span, { className: "font-bold text-primary-600 dark:text-primary-400", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }), _jsx(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
33
+ return (_jsxs("div", { className: className, "data-section": "bidhistory-div-421", children: [labels.title && (_jsx("h3", { className: "mb-3 text-sm font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400", children: labels.title })), _jsx("div", { className: "space-y-2", children: bids.map((bid, i) => renderBid ? (_jsx(React.Fragment, { children: renderBid(bid, i) }, bid.id)) : (_jsxs("div", { className: "flex items-center justify-between rounded-lg border border-zinc-100 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-3 text-sm", "data-section": "bidhistory-div-422", children: [_jsx(Span, { className: "font-bold text-primary-600 dark:text-primary-400", children: currency ? formatCurrency(bid.amount, currency) : bid.amount.toLocaleString() }), _jsx(Span, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: formatBidDate(bid.placedAt) })] }, bid.id))) })] }));
34
34
  }
@@ -9,28 +9,32 @@ import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hook
9
9
  import { useBrands } from "../hooks/useBrands";
10
10
  import { MarketplacePrizeDrawCard } from "./MarketplacePrizeDrawCard";
11
11
  import { ProductFilters } from "./ProductFilters";
12
+ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
13
+ import { sortBy } from "../../../constants/sort";
14
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
15
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
12
16
  const PRIZE_DRAW_SORT_OPTIONS = [
13
- { value: "-createdAt", label: "Newest First" },
14
- { value: "createdAt", label: "Oldest First" },
15
- { value: "prizeRevealWindowStart", label: "Reveal: Soonest" },
16
- { value: "-prizeRevealWindowStart", label: "Reveal: Furthest" },
17
- { value: "pricePerEntry", label: "Entry: Low to High" },
18
- { value: "-pricePerEntry", label: "Entry: High to Low" },
17
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
18
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
19
+ { value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START, "ASC"), label: "Reveal: Soonest" },
20
+ { value: sortBy(PRODUCT_FIELDS.PRIZE_REVEAL_WINDOW_START), label: "Reveal: Furthest" },
21
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Entry: Low to High" },
22
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Entry: High to Low" },
19
23
  ];
20
24
  const FILTER_KEYS = [
21
- "category",
22
- "brand",
23
- "minPrice",
24
- "maxPrice",
25
- "storeId",
26
- "prizeRevealStatus",
25
+ TABLE_KEYS.CATEGORY,
26
+ TABLE_KEYS.BRAND,
27
+ TABLE_KEYS.MIN_PRICE,
28
+ TABLE_KEYS.MAX_PRICE,
29
+ TABLE_KEYS.STORE_ID,
30
+ TABLE_KEYS.PRIZE_REVEAL_STATUS,
27
31
  ];
28
32
  export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, storeId: forcedStoreId, }) {
29
- const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
30
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
33
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
34
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
31
35
  const [filterOpen, setFilterOpen] = useState(false);
32
- const showClosed = table.get("showClosed") === "true";
33
- const [view, setView] = useState(table.get("view") || "grid");
36
+ const showClosed = table.get(TABLE_KEYS.SHOW_CLOSED) === "true";
37
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
34
38
  const { categories } = useCategoryTree();
35
39
  const categoryOptions = categoriesToFacetOptions(categories);
36
40
  const { brandOptions } = useBrands();
@@ -75,53 +79,53 @@ export function PrizeDrawsIndexListing({ initialData, categorySlug, brandName, s
75
79
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
76
80
  }, []);
77
81
  const resetAll = useCallback(() => {
78
- const updates = { q: "", sort: "", showClosed: "" };
82
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_CLOSED]: "" };
79
83
  for (const k of FILTER_KEYS)
80
84
  updates[k] = "";
81
85
  table.setMany(updates);
82
86
  setSearchInput("");
83
87
  }, [table]);
84
88
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
85
- const hasActiveState = !!table.get("q") ||
89
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
86
90
  showClosed ||
87
- table.get("sort") !== "-createdAt" ||
91
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
88
92
  activeFilterCount > 0;
93
+ const revealFilter = (table.get(TABLE_KEYS.PRIZE_REVEAL_STATUS) || undefined);
89
94
  const params = {
90
- q: table.get("q") || undefined,
91
- category: table.get("category") || undefined,
95
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
96
+ category: table.get(TABLE_KEYS.CATEGORY) || undefined,
92
97
  categorySlug: categorySlug || undefined,
93
- brand: brandName || table.get("brand") || undefined,
94
- minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
95
- maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
96
- storeId: forcedStoreId || table.get("storeId") || undefined,
97
- sort: table.get("sort") || "-createdAt",
98
- page: table.getNumber("page", 1),
99
- perPage: table.getNumber("pageSize", 24),
98
+ brand: brandName || table.get(TABLE_KEYS.BRAND) || undefined,
99
+ minPrice: table.get(TABLE_KEYS.MIN_PRICE) ? Number(table.get(TABLE_KEYS.MIN_PRICE)) : undefined,
100
+ maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
101
+ storeId: forcedStoreId || table.get(TABLE_KEYS.STORE_ID) || undefined,
102
+ prizeRevealStatus: revealFilter,
103
+ sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
104
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
105
+ perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
100
106
  listingType: "prize-draw",
101
107
  };
102
108
  const { products: draws, totalPages, page, isLoading } = useProducts(params, { initialData });
103
- // Client-side filter for prizeRevealStatus until the server query supports it.
104
- const revealFilter = table.get("prizeRevealStatus");
105
- const filteredDraws = revealFilter
106
- ? draws.filter((d) => d.prizeRevealStatus === revealFilter)
107
- : !showClosed
108
- ? draws.filter((d) => d.prizeRevealStatus !== "closed")
109
- : draws;
109
+ // When no explicit reveal-status filter is set, hide closed draws client-side
110
+ // as a UX default (showClosed toggle). Server handles explicit status filters.
111
+ const filteredDraws = !revealFilter && !showClosed
112
+ ? draws.filter((d) => d.prizeRevealStatus !== PRODUCT_FIELDS.PRIZE_REVEAL_STATUS_VALUES.CLOSED)
113
+ : draws;
110
114
  const commitSearch = useCallback(() => {
111
- table.set("q", searchInput.trim());
115
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
112
116
  }, [searchInput, table]);
113
117
  const handleSearchKeyDown = (e) => {
114
118
  if (e.key === "Enter")
115
119
  commitSearch();
116
120
  };
117
121
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
118
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get("sort") || "-createdAt", sortOptions: PRIZE_DRAW_SORT_OPTIONS, onSortChange: (v) => {
119
- table.set("sort", v);
122
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRIZE_DRAW_SORT_OPTIONS, onSortChange: (v) => {
123
+ table.set(TABLE_KEYS.SORT, v);
120
124
  }, view: view, onViewChange: (v) => {
121
- if (v === "table")
125
+ if (v === VIEW_MODE.TABLE)
122
126
  return;
123
127
  setView(v);
124
- table.set("view", v);
128
+ table.set(TABLE_KEYS.VIEW, v);
125
129
  }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs("label", { className: "flex items-center gap-1.5 cursor-pointer select-none shrink-0", children: [_jsx("span", { className: "hidden sm:inline text-xs text-zinc-600 dark:text-zinc-300 whitespace-nowrap", children: "Show closed" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showClosed, onClick: () => table.set("showClosed", showClosed ? "" : "true"), className: `relative inline-flex h-5 w-9 flex-shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 ${showClosed ? "bg-primary" : "bg-zinc-300 dark:bg-slate-600"}`, children: _jsx("span", { className: `inline-block h-3.5 w-3.5 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${showClosed ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: gridClass, children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-square bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-3 space-y-2", children: [_jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-3/4" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" }), _jsx("div", { className: "h-8 bg-zinc-200 dark:bg-slate-700 rounded" })] })] }, i))) })) : filteredDraws.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No prize draws found." })) : (_jsx("div", { className: gridClass, children: filteredDraws.map((product) => (_jsx(MarketplacePrizeDrawCard, { product: product, variant: view }, product.id))) })) }), filterOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsxs("span", { className: "flex items-center gap-2 text-base font-semibold text-zinc-900 dark:text-zinc-100", children: [_jsx(SlidersHorizontal, { className: "h-4 w-4" }), "Filters"] }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close filters", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-4", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "prizeRevealStatusFilter", className: "block text-xs font-semibold text-zinc-700 dark:text-zinc-300 mb-1.5", children: "Reveal status" }), _jsxs("select", { id: "prizeRevealStatusFilter", value: pendingFilters.prizeRevealStatus ?? "", onChange: (e) => setPendingFilters((p) => ({
126
130
  ...p,
127
131
  prizeRevealStatus: e.target.value,
@@ -259,7 +259,7 @@ export async function ProductDetailPageView({ slug, initialProduct, renderOfferA
259
259
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
260
260
  : undefined, renderGroupSection: groupId
261
261
  ? () => (_jsx(ShowGroupSection, { groupId: groupId, currentSlug: String(p.slug ?? product.id), isParent: isGroupParent, groupTitle: groupTitle }))
262
- : undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined, specsContent: specs.length > 0 ? (_jsx("dl", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800 overflow-hidden text-sm", children: specs.map((s, i) => (_jsxs("div", { className: "flex gap-4 px-4 py-3 bg-white dark:bg-zinc-900 even:bg-zinc-50 dark:even:bg-zinc-800/50", children: [_jsx("dt", { className: "w-36 flex-shrink-0 font-medium text-zinc-700 dark:text-zinc-300", children: s.name }), _jsxs("dd", { className: "flex-1 text-zinc-600 dark:text-zinc-400", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx("ul", { className: "space-y-2", children: ingredients.map((item, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 rounded-full bg-primary-400" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx("ol", { className: "space-y-3", children: howToUse.map((step, i) => (_jsxs("li", { className: "flex items-start gap-3 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900/30 text-xs font-bold text-primary-700 dark:text-primary-300", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
262
+ : undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-zinc-700 dark:text-zinc-300" })) : undefined, specsContent: specs.length > 0 ? (_jsx("dl", { className: "divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800 overflow-hidden text-sm", children: specs.map((s, i) => (_jsxs("div", { className: "flex gap-4 px-4 py-3 bg-white dark:bg-zinc-900 even:bg-zinc-50 dark:even:bg-zinc-800/50", children: [_jsx("dt", { className: "w-36 flex-shrink-0 font-medium text-zinc-700 dark:text-zinc-300", children: s.name }), _jsxs("dd", { className: "flex-1 text-zinc-600 dark:text-zinc-400", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, ingredientsContent: ingredients.length > 0 ? (_jsx("ul", { className: "space-y-2", children: ingredients.map((item, i) => (_jsxs("li", { className: "flex items-start gap-2 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "mt-1 flex-shrink-0 h-1.5 w-1.5 rounded-full bg-primary-400" }), item] }, i))) })) : undefined, howToUseContent: howToUse.length > 0 ? (_jsx("ol", { className: "space-y-3", children: howToUse.map((step, i) => (_jsxs("li", { className: "flex items-start gap-3 text-sm text-zinc-700 dark:text-zinc-300", children: [_jsx(Span, { className: "flex-shrink-0 flex h-6 w-6 items-center justify-center rounded-full bg-primary-100 dark:bg-primary-900/30 text-xs font-bold text-primary-700 dark:text-primary-300", children: i + 1 }), step] }, i))) })) : undefined, reviewsContent: _jsx(ReviewsList, { reviews: reviews, context: "listing", emptyLabel: "No reviews yet \u2014 be the first to review this product." }), customTabs: customSections.map((s) => ({
263
263
  id: s.id,
264
264
  label: s.title,
265
265
  content: _jsx(CustomSectionTabContent, { section: s }),
@@ -5,66 +5,66 @@ export type ProductFilterVariant = "admin" | "seller" | "public";
5
5
  export declare const PRODUCT_FILTER_KEYS: {
6
6
  readonly admin: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "seller", "tags", "status"];
7
7
  readonly seller: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "tags", "status"];
8
- readonly public: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "seller", "tags"];
8
+ readonly public: readonly ["category", "condition", "minPrice", "maxPrice", "brand", "storeId", "tags"];
9
9
  };
10
10
  export declare const PRODUCT_ADMIN_SORT_OPTIONS: readonly [{
11
- readonly value: "-createdAt";
11
+ readonly value: string;
12
12
  readonly label: "Newest First";
13
13
  }, {
14
- readonly value: "createdAt";
14
+ readonly value: string;
15
15
  readonly label: "Oldest First";
16
16
  }, {
17
- readonly value: "-price";
17
+ readonly value: string;
18
18
  readonly label: "Price: High to Low";
19
19
  }, {
20
- readonly value: "price";
20
+ readonly value: string;
21
21
  readonly label: "Price: Low to High";
22
22
  }, {
23
- readonly value: "title";
23
+ readonly value: string;
24
24
  readonly label: "Title A-Z";
25
25
  }, {
26
- readonly value: "-title";
26
+ readonly value: string;
27
27
  readonly label: "Title Z-A";
28
28
  }, {
29
- readonly value: "-views";
29
+ readonly value: string;
30
30
  readonly label: "Most Viewed";
31
31
  }];
32
32
  export declare const PRODUCT_SELLER_SORT_OPTIONS: readonly [{
33
- readonly value: "-createdAt";
33
+ readonly value: string;
34
34
  readonly label: "Newest First";
35
35
  }, {
36
- readonly value: "createdAt";
36
+ readonly value: string;
37
37
  readonly label: "Oldest First";
38
38
  }, {
39
- readonly value: "-price";
39
+ readonly value: string;
40
40
  readonly label: "Price: High to Low";
41
41
  }, {
42
- readonly value: "price";
42
+ readonly value: string;
43
43
  readonly label: "Price: Low to High";
44
44
  }, {
45
- readonly value: "title";
45
+ readonly value: string;
46
46
  readonly label: "Title A-Z";
47
47
  }];
48
48
  export declare const PRODUCT_PUBLIC_SORT_OPTIONS: readonly [{
49
- readonly value: "-createdAt";
49
+ readonly value: string;
50
50
  readonly label: "Newest First";
51
51
  }, {
52
- readonly value: "createdAt";
52
+ readonly value: string;
53
53
  readonly label: "Oldest First";
54
54
  }, {
55
- readonly value: "-price";
55
+ readonly value: string;
56
56
  readonly label: "Price: High to Low";
57
57
  }, {
58
- readonly value: "price";
58
+ readonly value: string;
59
59
  readonly label: "Price: Low to High";
60
60
  }, {
61
- readonly value: "title";
61
+ readonly value: string;
62
62
  readonly label: "Title A–Z";
63
63
  }, {
64
- readonly value: "-title";
64
+ readonly value: string;
65
65
  readonly label: "Title Z–A";
66
66
  }, {
67
- readonly value: "-views";
67
+ readonly value: string;
68
68
  readonly label: "Most Viewed";
69
69
  }];
70
70
  export declare function getProductFilterKeys(variant: ProductFilterVariant): readonly string[];