@mohasinac/appkit 2.7.10 → 2.7.12

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 (148) hide show
  1. package/dist/_internal/server/jobs/core/assignSpinPrize.js +8 -7
  2. package/dist/_internal/server/jobs/core/bundleStockSync.js +11 -6
  3. package/dist/_internal/server/jobs/core/countersReconcile.js +3 -2
  4. package/dist/_internal/server/jobs/core/onProductStockChange.js +12 -7
  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-schemas.d.ts +32 -32
  13. package/dist/_internal/shared/features/events/schema.d.ts +4 -4
  14. package/dist/_internal/shared/features/products/schema.d.ts +20 -20
  15. package/dist/_internal/shared/features/promotions/schema.d.ts +2 -2
  16. package/dist/_internal/shared/features/reviews/schema.d.ts +1 -1
  17. package/dist/configs/next.js +17 -1
  18. package/dist/constants/field-names.d.ts +813 -0
  19. package/dist/constants/field-names.js +918 -0
  20. package/dist/constants/sort.d.ts +7 -0
  21. package/dist/constants/sort.js +5 -0
  22. package/dist/constants/table-keys.d.ts +48 -0
  23. package/dist/constants/table-keys.js +46 -0
  24. package/dist/features/account/components/AddressesIndexListing.js +9 -6
  25. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
  26. package/dist/features/admin/components/AdminBidsView.js +4 -1
  27. package/dist/features/admin/components/AdminBlogView.js +9 -2
  28. package/dist/features/admin/components/AdminBrandsView.js +9 -2
  29. package/dist/features/admin/components/AdminCarouselView.js +4 -1
  30. package/dist/features/admin/components/AdminCartsView.js +4 -1
  31. package/dist/features/admin/components/AdminCategoriesView.js +9 -2
  32. package/dist/features/admin/components/AdminContactView.js +4 -1
  33. package/dist/features/admin/components/AdminCouponsView.js +4 -1
  34. package/dist/features/admin/components/AdminFaqsView.js +5 -1
  35. package/dist/features/admin/components/AdminFeaturesView.js +5 -2
  36. package/dist/features/admin/components/AdminHistoryView.js +5 -1
  37. package/dist/features/admin/components/AdminNewsletterView.js +4 -1
  38. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  39. package/dist/features/admin/components/AdminOrdersView.js +14 -12
  40. package/dist/features/admin/components/AdminPayoutsView.js +4 -1
  41. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
  42. package/dist/features/admin/components/AdminProductsView.js +14 -3
  43. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
  44. package/dist/features/admin/components/AdminReviewsView.js +4 -1
  45. package/dist/features/admin/components/AdminScammersView.js +4 -1
  46. package/dist/features/admin/components/AdminSessionsView.js +4 -1
  47. package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
  48. package/dist/features/admin/components/AdminStoresView.js +10 -3
  49. package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
  50. package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
  51. package/dist/features/admin/components/AdminTeamView.js +4 -1
  52. package/dist/features/admin/components/AdminUsersView.js +9 -12
  53. package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
  54. package/dist/features/admin/components/AdminViewCards.js +53 -0
  55. package/dist/features/admin/components/AdminWishlistsView.js +4 -1
  56. package/dist/features/auctions/repository/bid.repository.js +1 -6
  57. package/dist/features/auctions/schemas/index.d.ts +4 -4
  58. package/dist/features/blog/actions/blog-actions.d.ts +16 -16
  59. package/dist/features/blog/components/BlogFilters.d.ts +28 -28
  60. package/dist/features/blog/components/BlogFilters.js +18 -15
  61. package/dist/features/blog/components/BlogIndexListing.js +24 -13
  62. package/dist/features/blog/schemas/index.d.ts +10 -10
  63. package/dist/features/categories/components/CategoriesIndexListing.js +30 -19
  64. package/dist/features/categories/schemas/index.d.ts +2 -2
  65. package/dist/features/events/components/AdminEventsView.js +5 -1
  66. package/dist/features/events/components/EventFilters.d.ts +24 -24
  67. package/dist/features/events/components/EventFilters.js +16 -13
  68. package/dist/features/events/components/EventStatusBadge.js +1 -0
  69. package/dist/features/events/components/EventsIndexListing.js +36 -24
  70. package/dist/features/events/components/EventsListPageView.js +13 -10
  71. package/dist/features/events/schemas/firestore.d.ts +1 -0
  72. package/dist/features/events/schemas/firestore.js +1 -0
  73. package/dist/features/events/schemas/index.d.ts +1 -1
  74. package/dist/features/events/types/index.d.ts +1 -1
  75. package/dist/features/faq/actions/faq-actions.d.ts +10 -10
  76. package/dist/features/faq/schemas/index.d.ts +4 -4
  77. package/dist/features/layout/TitleBarLayout.js +1 -1
  78. package/dist/features/orders/components/OrderFilters.d.ts +20 -20
  79. package/dist/features/orders/components/OrderFilters.js +33 -30
  80. package/dist/features/orders/repository/orders.repository.js +1 -15
  81. package/dist/features/orders/schemas/index.d.ts +2 -2
  82. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +44 -36
  83. package/dist/features/pre-orders/schemas/index.d.ts +3 -3
  84. package/dist/features/products/components/AuctionsIndexListing.js +63 -30
  85. package/dist/features/products/components/BidHistory.d.ts +0 -1
  86. package/dist/features/products/components/BidHistory.js +1 -1
  87. package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -37
  88. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  89. package/dist/features/products/components/ProductFilters.d.ts +20 -20
  90. package/dist/features/products/components/ProductFilters.js +62 -59
  91. package/dist/features/products/components/ProductsIndexListing.js +37 -29
  92. package/dist/features/products/components/ProductsIndexPageView.js +18 -10
  93. package/dist/features/products/hooks/useProducts.js +2 -0
  94. package/dist/features/products/repository/products.repository.js +2 -22
  95. package/dist/features/products/schemas/index.d.ts +28 -28
  96. package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
  97. package/dist/features/products/types/index.d.ts +2 -0
  98. package/dist/features/promotions/components/CouponsIndexListing.js +21 -17
  99. package/dist/features/promotions/schemas/index.d.ts +4 -4
  100. package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
  101. package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
  102. package/dist/features/reviews/components/ReviewFilters.js +10 -7
  103. package/dist/features/reviews/components/ReviewsIndexListing.js +25 -14
  104. package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
  105. package/dist/features/reviews/components/ReviewsList.js +11 -5
  106. package/dist/features/reviews/schemas/firestore.d.ts +1 -0
  107. package/dist/features/reviews/schemas/index.d.ts +8 -8
  108. package/dist/features/reviews/types/index.d.ts +1 -0
  109. package/dist/features/search/schemas/index.d.ts +2 -2
  110. package/dist/features/seller/components/SellerAuctionsView.js +5 -1
  111. package/dist/features/seller/components/SellerBidsView.js +4 -1
  112. package/dist/features/seller/components/SellerCouponsView.js +4 -1
  113. package/dist/features/seller/components/SellerOffersView.js +5 -1
  114. package/dist/features/seller/components/SellerOrdersView.js +4 -1
  115. package/dist/features/seller/components/SellerPayoutsView.js +5 -1
  116. package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
  117. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
  118. package/dist/features/seller/components/SellerProductsView.js +4 -1
  119. package/dist/features/seller/schemas/index.d.ts +10 -10
  120. package/dist/features/stores/api/route.js +10 -1
  121. package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
  122. package/dist/features/stores/components/StorePreOrdersListing.js +2 -1
  123. package/dist/features/stores/components/StoreReviewsListing.js +1 -1
  124. package/dist/features/stores/components/StoresIndexListing.js +45 -27
  125. package/dist/features/stores/schemas/firestore.d.ts +1 -0
  126. package/dist/features/stores/schemas/index.d.ts +4 -4
  127. package/dist/index.d.ts +8 -1
  128. package/dist/index.js +12 -1
  129. package/dist/react/hooks/useUrlTable.js +2 -1
  130. package/dist/seed/bids-seed-data.js +45 -44
  131. package/dist/seed/categories-seed-data.js +41 -40
  132. package/dist/seed/coupons-seed-data.js +41 -40
  133. package/dist/seed/products-auctions-seed-data.js +54 -53
  134. package/dist/seed/products-preorders-seed-data.js +25 -24
  135. package/dist/seed/products-prize-draws-seed-data.js +24 -23
  136. package/dist/seed/products-standard-seed-data.js +294 -293
  137. package/dist/seed/reviews-seed-data.js +61 -60
  138. package/dist/seed/scammers-seed-data.js +3 -2
  139. package/dist/seed/stores-seed-data.js +9 -8
  140. package/dist/seed/support-tickets-seed-data.js +13 -12
  141. package/dist/seed/users-seed-data.js +28 -27
  142. package/dist/tailwind-utilities.css +1 -1
  143. package/dist/ui/components/ListingToolbar.d.ts +7 -4
  144. package/dist/ui/components/ListingToolbar.js +4 -3
  145. package/dist/ui/components/SiteLogo.js +1 -1
  146. package/dist/utils/sieve-builder.d.ts +41 -0
  147. package/dist/utils/sieve-builder.js +66 -0
  148. package/package.json +1 -1
@@ -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[];
@@ -5,60 +5,63 @@ import { FilterFacetSection } from "../../filters/FilterFacetSection";
5
5
  import { RangeFilter } from "../../filters/RangeFilter";
6
6
  import { SwitchFilter } from "../../filters/SwitchFilter";
7
7
  import { Div } from "../../../ui";
8
+ import { TABLE_KEYS } from "../../../constants/table-keys";
9
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
10
+ import { sortBy } from "../../../constants/sort";
8
11
  export const PRODUCT_FILTER_KEYS = {
9
12
  admin: [
10
- "category",
11
- "condition",
12
- "minPrice",
13
- "maxPrice",
14
- "brand",
15
- "seller",
16
- "tags",
17
- "status",
13
+ TABLE_KEYS.CATEGORY,
14
+ TABLE_KEYS.CONDITION,
15
+ TABLE_KEYS.MIN_PRICE,
16
+ TABLE_KEYS.MAX_PRICE,
17
+ TABLE_KEYS.BRAND,
18
+ TABLE_KEYS.SELLER,
19
+ TABLE_KEYS.TAGS,
20
+ TABLE_KEYS.STATUS,
18
21
  ],
19
22
  seller: [
20
- "category",
21
- "condition",
22
- "minPrice",
23
- "maxPrice",
24
- "brand",
25
- "tags",
26
- "status",
23
+ TABLE_KEYS.CATEGORY,
24
+ TABLE_KEYS.CONDITION,
25
+ TABLE_KEYS.MIN_PRICE,
26
+ TABLE_KEYS.MAX_PRICE,
27
+ TABLE_KEYS.BRAND,
28
+ TABLE_KEYS.TAGS,
29
+ TABLE_KEYS.STATUS,
27
30
  ],
28
31
  public: [
29
- "category",
30
- "condition",
31
- "minPrice",
32
- "maxPrice",
33
- "brand",
34
- "seller",
35
- "tags",
32
+ TABLE_KEYS.CATEGORY,
33
+ TABLE_KEYS.CONDITION,
34
+ TABLE_KEYS.MIN_PRICE,
35
+ TABLE_KEYS.MAX_PRICE,
36
+ TABLE_KEYS.BRAND,
37
+ TABLE_KEYS.STORE_ID,
38
+ TABLE_KEYS.TAGS,
36
39
  ],
37
40
  };
38
41
  export const PRODUCT_ADMIN_SORT_OPTIONS = [
39
- { value: "-createdAt", label: "Newest First" },
40
- { value: "createdAt", label: "Oldest First" },
41
- { value: "-price", label: "Price: High to Low" },
42
- { value: "price", label: "Price: Low to High" },
43
- { value: "title", label: "Title A-Z" },
44
- { value: "-title", label: "Title Z-A" },
45
- { value: "-views", label: "Most Viewed" },
42
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
43
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
44
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
45
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
46
+ { value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A-Z" },
47
+ { value: sortBy(PRODUCT_FIELDS.TITLE), label: "Title Z-A" },
48
+ { value: sortBy(PRODUCT_FIELDS.VIEW_COUNT), label: "Most Viewed" },
46
49
  ];
47
50
  export const PRODUCT_SELLER_SORT_OPTIONS = [
48
- { value: "-createdAt", label: "Newest First" },
49
- { value: "createdAt", label: "Oldest First" },
50
- { value: "-price", label: "Price: High to Low" },
51
- { value: "price", label: "Price: Low to High" },
52
- { value: "title", label: "Title A-Z" },
51
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
52
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
53
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
54
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
55
+ { value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A-Z" },
53
56
  ];
54
57
  export const PRODUCT_PUBLIC_SORT_OPTIONS = [
55
- { value: "-createdAt", label: "Newest First" },
56
- { value: "createdAt", label: "Oldest First" },
57
- { value: "-price", label: "Price: High to Low" },
58
- { value: "price", label: "Price: Low to High" },
59
- { value: "title", label: "Title A–Z" },
60
- { value: "-title", label: "Title Z–A" },
61
- { value: "-views", label: "Most Viewed" },
58
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
59
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
60
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
61
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
62
+ { value: sortBy(PRODUCT_FIELDS.TITLE, "ASC"), label: "Title A–Z" },
63
+ { value: sortBy(PRODUCT_FIELDS.TITLE), label: "Title Z–A" },
64
+ { value: sortBy(PRODUCT_FIELDS.VIEW_COUNT), label: "Most Viewed" },
62
65
  ];
63
66
  export function getProductFilterKeys(variant) {
64
67
  return PRODUCT_FILTER_KEYS[variant];
@@ -79,31 +82,31 @@ export function ProductFilters({ table, categoryOptions = [], brandOptions = [],
79
82
  const resolvedStoreOptions = storeOptions.length > 0 ? storeOptions : (sellerOptions ?? []);
80
83
  const t = useTranslations("filters");
81
84
  const conditionOptions = [
82
- { value: "new", label: t("conditionNew") },
83
- { value: "used", label: t("conditionUsed") },
84
- { value: "refurbished", label: t("conditionRefurbished") },
85
- { value: "broken", label: t("conditionBroken") },
85
+ { value: PRODUCT_FIELDS.CONDITION_VALUES.NEW, label: t("conditionNew") },
86
+ { value: PRODUCT_FIELDS.CONDITION_VALUES.USED, label: t("conditionUsed") },
87
+ { value: PRODUCT_FIELDS.CONDITION_VALUES.REFURBISHED, label: t("conditionRefurbished") },
88
+ { value: PRODUCT_FIELDS.CONDITION_VALUES.BROKEN, label: t("conditionBroken") },
86
89
  ];
87
90
  const defaultStatusOptions = [
88
- { value: "published", label: t("statusPublished") },
89
- { value: "draft", label: t("statusDraft") },
90
- { value: "archived", label: t("statusArchived") },
91
+ { value: PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED, label: t("statusPublished") },
92
+ { value: PRODUCT_FIELDS.STATUS_VALUES.DRAFT, label: t("statusDraft") },
93
+ { value: PRODUCT_FIELDS.STATUS_VALUES.OUT_OF_STOCK, label: t("statusOutOfStock") },
91
94
  ];
92
- const selectedCategories = table.get("category")
93
- ? table.get("category").split("|").filter(Boolean)
95
+ const selectedCategories = table.get(TABLE_KEYS.CATEGORY)
96
+ ? table.get(TABLE_KEYS.CATEGORY).split("|").filter(Boolean)
94
97
  : [];
95
- const selectedBrands = table.get("brand") ? [table.get("brand")] : [];
96
- const selectedSellers = table.get("storeId") ? [table.get("storeId")] : [];
97
- const selectedConditions = table.get("condition")
98
- ? table.get("condition").split("|").filter(Boolean)
98
+ const selectedBrands = table.get(TABLE_KEYS.BRAND) ? [table.get(TABLE_KEYS.BRAND)] : [];
99
+ const selectedSellers = table.get(TABLE_KEYS.STORE_ID) ? [table.get(TABLE_KEYS.STORE_ID)] : [];
100
+ const selectedConditions = table.get(TABLE_KEYS.CONDITION)
101
+ ? table.get(TABLE_KEYS.CONDITION).split("|").filter(Boolean)
99
102
  : [];
100
- const selectedTags = table.get("tags")
101
- ? table.get("tags").split("|").filter(Boolean)
103
+ const selectedTags = table.get(TABLE_KEYS.TAGS)
104
+ ? table.get(TABLE_KEYS.TAGS).split("|").filter(Boolean)
102
105
  : [];
103
- const selectedStatuses = table.get("status")
104
- ? table.get("status").split("|").filter(Boolean)
106
+ const selectedStatuses = table.get(TABLE_KEYS.STATUS)
107
+ ? table.get(TABLE_KEYS.STATUS).split("|").filter(Boolean)
105
108
  : [];
106
109
  const resolvedVariant = variant ?? (showStatus ? "admin" : "public");
107
110
  const shouldShowStatus = resolvedVariant !== "public" || showStatus;
108
- return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set("category", vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set("condition", vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get("minPrice"), maxValue: table.get("maxPrice"), onMinChange: (v) => table.set("minPrice", v), onMaxChange: (v) => table.set("maxPrice", v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set("brand", vals[0] ?? ""), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })), resolvedStoreOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set("storeId", vals[0] ?? ""), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })), showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get("freeShipping") === "true", onChange: (v) => table.set("freeShipping", v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set("tags", vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false }))] }));
111
+ return (_jsxs(Div, { children: [categoryOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategories, onChange: (vals) => table.set(TABLE_KEYS.CATEGORY, vals.join("|")), searchable: true, defaultCollapsed: categoryOptions.length > 6 })), _jsx(FilterFacetSection, { title: t("condition"), options: conditionOptions, selected: selectedConditions, onChange: (vals) => table.set(TABLE_KEYS.CONDITION, vals.join("|")), searchable: false, defaultCollapsed: false }), _jsx(RangeFilter, { title: t("priceRange"), minValue: table.get(TABLE_KEYS.MIN_PRICE), maxValue: table.get(TABLE_KEYS.MAX_PRICE), onMinChange: (v) => table.set(TABLE_KEYS.MIN_PRICE, v), onMaxChange: (v) => table.set(TABLE_KEYS.MAX_PRICE, v), prefix: currencyPrefix, showSlider: true, minBound: 0, maxBound: 500000, step: 500, minPlaceholder: t("minPrice"), maxPlaceholder: t("maxPrice"), defaultCollapsed: false }), brandOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("brand"), options: brandOptions, selected: selectedBrands, onChange: (vals) => table.set(TABLE_KEYS.BRAND, vals[0] ?? ""), searchable: brandOptions.length > 4, defaultCollapsed: brandOptions.length > 6 })), resolvedStoreOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("store"), options: resolvedStoreOptions, selected: selectedSellers, onChange: (vals) => table.set(TABLE_KEYS.STORE_ID, vals[0] ?? ""), searchable: resolvedStoreOptions.length > 4, defaultCollapsed: resolvedStoreOptions.length > 6 })), showShipping && (_jsx(SwitchFilter, { title: t("shipping"), label: t("freeShippingOnly"), checked: table.get(TABLE_KEYS.FREE_SHIPPING) === "true", onChange: (v) => table.set(TABLE_KEYS.FREE_SHIPPING, v ? "true" : ""), defaultCollapsed: false })), tagOptions.length > 0 && (_jsx(FilterFacetSection, { title: t("tags"), options: tagOptions, selected: selectedTags, onChange: (vals) => table.set(TABLE_KEYS.TAGS, vals.join("|")), searchable: tagOptions.length > 4, defaultCollapsed: tagOptions.length > 6 })), shouldShowStatus && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions ?? defaultStatusOptions, selected: selectedStatuses, onChange: (vals) => table.set(TABLE_KEYS.STATUS, vals.join("|")), searchable: false, defaultCollapsed: false }))] }));
109
112
  }
@@ -5,7 +5,7 @@ import { X, ShoppingCart, Heart, SlidersHorizontal, Columns } from "lucide-react
5
5
  import { useRouter } from "next/navigation";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
7
  import { useProducts } from "../hooks/useProducts";
8
- import { Pagination, useToast, BulkActionsBar, ListingToolbar } from "../../../ui";
8
+ import { Pagination, useToast, BulkActionBar, ListingToolbar } from "../../../ui";
9
9
  import { ACTION_ID, ACTION_META, COMPARE_MAX_ITEMS } from "../constants/action-defs";
10
10
  import { CompareOverlay } from "./CompareOverlay";
11
11
  import { ROUTES } from "../../../next";
@@ -15,16 +15,20 @@ import { useGuestWishlist } from "../../wishlist/hooks/useGuestWishlist";
15
15
  import { pushCartOp, pushWishlistOp } from "../../cart/utils/pending-ops";
16
16
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
17
17
  import { useCategoryTree, categoriesToFacetOptions } from "../../categories/hooks/useCategoryTree";
18
- const FILTER_KEYS = ["category", "condition", "minPrice", "maxPrice", "brand", "storeId", "freeShipping", "tags"];
18
+ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
19
+ import { sortBy } from "../../../constants/sort";
20
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
21
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
22
+ const FILTER_KEYS = [TABLE_KEYS.CATEGORY, TABLE_KEYS.CONDITION, TABLE_KEYS.MIN_PRICE, TABLE_KEYS.MAX_PRICE, TABLE_KEYS.BRAND, TABLE_KEYS.STORE_ID, TABLE_KEYS.FREE_SHIPPING, TABLE_KEYS.TAGS];
19
23
  export function ProductsIndexListing({ initialData }) {
20
24
  const router = useRouter();
21
- const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
25
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
22
26
  const { showToast } = useToast();
23
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
27
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
24
28
  const [filterOpen, setFilterOpen] = useState(false);
25
29
  const [compareIds, setCompareIds] = useState([]);
26
- const showSold = table.get("showSold") === "true";
27
- const [view, setView] = useState(table.get("view") || "card");
30
+ const showSold = table.get(TABLE_KEYS.SHOW_SOLD) === "true";
31
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
28
32
  // Pending filter state — buffered until "Apply Filters" clicked
29
33
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
30
34
  const pendingTable = useMemo(() => ({
@@ -68,16 +72,16 @@ export function ProductsIndexListing({ initialData }) {
68
72
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
69
73
  }, []);
70
74
  const resetAll = useCallback(() => {
71
- const updates = { q: "", sort: "", showSold: "" };
75
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "", [TABLE_KEYS.SHOW_SOLD]: "" };
72
76
  for (const k of FILTER_KEYS)
73
77
  updates[k] = "";
74
78
  table.setMany(updates);
75
79
  setSearchInput("");
76
80
  }, [table]);
77
81
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
78
- const hasActiveState = !!table.get("q") ||
79
- table.get("showSold") === "true" ||
80
- table.get("sort") !== "-createdAt" ||
82
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
83
+ table.get(TABLE_KEYS.SHOW_SOLD) === "true" ||
84
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
81
85
  activeFilterCount > 0;
82
86
  const localCart = useGuestCart();
83
87
  const localWishlist = useGuestWishlist();
@@ -85,32 +89,36 @@ export function ProductsIndexListing({ initialData }) {
85
89
  const { categories } = useCategoryTree();
86
90
  const categoryOptions = categoriesToFacetOptions(categories);
87
91
  const params = {
88
- q: table.get("q") || undefined,
89
- category: table.get("category") || undefined,
90
- minPrice: table.get("minPrice") ? Number(table.get("minPrice")) : undefined,
91
- maxPrice: table.get("maxPrice") ? Number(table.get("maxPrice")) : undefined,
92
- condition: table.get("condition") || undefined,
93
- brand: table.get("brand") || undefined,
94
- storeId: table.get("storeId") || undefined,
95
- freeShipping: table.get("freeShipping") === "true" ? true : undefined,
96
- sort: table.get("sort") || "-createdAt",
97
- page: table.getNumber("page", 1),
98
- perPage: table.getNumber("pageSize", 24),
92
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
93
+ category: table.get(TABLE_KEYS.CATEGORY) || undefined,
94
+ minPrice: table.get(TABLE_KEYS.MIN_PRICE) ? Number(table.get(TABLE_KEYS.MIN_PRICE)) : undefined,
95
+ maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
96
+ condition: table.get(TABLE_KEYS.CONDITION) || undefined,
97
+ brand: table.get(TABLE_KEYS.BRAND) || undefined,
98
+ storeId: table.get(TABLE_KEYS.STORE_ID) || undefined,
99
+ freeShipping: table.get(TABLE_KEYS.FREE_SHIPPING) === "true" ? true : undefined,
100
+ sort: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
101
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
102
+ perPage: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
99
103
  listingType: "standard",
100
- status: showSold ? undefined : "published",
104
+ // Hide sold-out items by default. Uses stockQuantity>0 (always-present field)
105
+ // instead of status=="published" because sellers don't actively transition status.
106
+ inStock: showSold ? undefined : true,
101
107
  };
102
108
  const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
103
109
  const selection = useBulkSelection({ items: products, keyExtractor: (p) => p.id });
104
110
  const commitSearch = useCallback(() => {
105
- table.set("q", searchInput.trim());
111
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
106
112
  }, [searchInput, table]);
107
113
  const handleSearchKeyDown = (e) => {
108
114
  if (e.key === "Enter")
109
115
  commitSearch();
110
116
  };
111
117
  const handleViewToggle = (next) => {
118
+ if (next === VIEW_MODE.TABLE)
119
+ return;
112
120
  setView(next);
113
- table.set("view", next);
121
+ table.set(TABLE_KEYS.VIEW, next);
114
122
  };
115
123
  const handleWishlistToggle = useCallback((productId) => {
116
124
  const isWishlisted = wishlistedIds.has(productId);
@@ -143,9 +151,9 @@ export function ProductsIndexListing({ initialData }) {
143
151
  pushCartOp({ op: "add", productId: product.id, quantity: 1, productTitle: product.title, productImage: product.mainImage, price: product.price });
144
152
  router.push(String(ROUTES.USER.CART));
145
153
  }, [localCart, router]);
146
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get("sort") || "-createdAt", sortOptions: PRODUCT_PUBLIC_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view === "card" ? "grid" : "list", onViewChange: (v) => handleViewToggle(v === "grid" ? "card" : "list"), onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: products.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 sold" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showSold, onClick: () => table.set("showSold", showSold ? "" : "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 ${showSold ? "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 ${showSold ? "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: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: 10 }).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-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view, onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(BulkActionsBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
154
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, onSearchKeyDown: handleSearchKeyDown, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: PRODUCT_PUBLIC_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: products.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 sold" }), _jsx("button", { type: "button", role: "switch", "aria-checked": showSold, onClick: () => table.set(TABLE_KEYS.SHOW_SOLD, showSold ? "" : "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 ${showSold ? "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 ${showSold ? "translate-x-[19px]" : "translate-x-[3px]"}` }) })] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
147
155
  {
148
- key: ACTION_ID.ADD_TO_CART,
156
+ id: ACTION_ID.ADD_TO_CART,
149
157
  label: ACTION_META[ACTION_ID.ADD_TO_CART].label,
150
158
  icon: _jsx(ShoppingCart, { className: "h-3.5 w-3.5" }),
151
159
  variant: "primary",
@@ -160,7 +168,7 @@ export function ProductsIndexListing({ initialData }) {
160
168
  },
161
169
  },
162
170
  {
163
- key: ACTION_ID.ADD_TO_WISHLIST,
171
+ id: ACTION_ID.ADD_TO_WISHLIST,
164
172
  label: ACTION_META[ACTION_ID.ADD_TO_WISHLIST].label,
165
173
  icon: _jsx(Heart, { className: "h-3.5 w-3.5" }),
166
174
  variant: "secondary",
@@ -175,7 +183,7 @@ export function ProductsIndexListing({ initialData }) {
175
183
  },
176
184
  },
177
185
  {
178
- key: ACTION_ID.COMPARE,
186
+ id: ACTION_ID.COMPARE,
179
187
  label: ACTION_META[ACTION_ID.COMPARE].label,
180
188
  icon: _jsx(Columns, { className: "h-3.5 w-3.5" }),
181
189
  variant: "secondary",
@@ -185,7 +193,7 @@ export function ProductsIndexListing({ initialData }) {
185
193
  setCompareIds(ids);
186
194
  },
187
195
  },
188
- ] }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
196
+ ] }), 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: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4", children: Array.from({ length: 10 }).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-4 bg-zinc-200 dark:bg-slate-700 rounded w-1/3" })] })] }, i))) })) : (_jsx(ProductGrid, { products: products, getProductHref: (p) => String(ROUTES.PUBLIC.PRODUCT_DETAIL(p.slug || p.id)), view: view === "grid" ? "card" : "list", onWishlistToggle: handleWishlistToggle, wishlistedIds: wishlistedIds, onAddToCart: handleAddToCart, onBuyNow: handleBuyNow, selectionMode: selection.isSelecting, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle })) }), _jsx(CompareOverlay, { isOpen: compareIds.length > 0, productIds: compareIds, productType: "product", onClose: () => {
189
197
  setCompareIds([]);
190
198
  selection.clearSelection();
191
199
  }, onRemove: (id) => setCompareIds((ids) => ids.filter((i) => i !== 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" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9", categoryOptions: categoryOptions }) }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
@@ -5,36 +5,44 @@ import { Container, Heading, Main, Section } from "../../../ui";
5
5
  import { AdSlot } from "../../homepage/components/AdSlot";
6
6
  import { parseListingSearchParams } from "../../../utils/listing-params";
7
7
  import { ProductsIndexListing } from "./ProductsIndexListing";
8
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
9
+ import { sieveFilter, sieveMultiEq, sieveAnd, SIEVE_OP } from "../../../utils/sieve-builder";
10
+ import { sortBy } from "../../../constants/sort";
8
11
  const DEFAULT_PAGE = 1;
9
12
  const DEFAULT_PAGE_SIZE = 24;
10
- const DEFAULT_SORT = "-createdAt";
13
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
11
14
  function sp(params, key) {
12
15
  const v = params[key];
13
16
  return Array.isArray(v) ? v[0] ?? "" : v ?? "";
14
17
  }
15
18
  function buildProductFilters(params) {
16
- const parts = ["status==published", "listingType==standard"];
19
+ const parts = [
20
+ sieveFilter(PRODUCT_FIELDS.STATUS, SIEVE_OP.EQ, PRODUCT_FIELDS.STATUS_VALUES.PUBLISHED),
21
+ sieveFilter(PRODUCT_FIELDS.LISTING_TYPE, SIEVE_OP.EQ, "standard"),
22
+ ];
17
23
  const condition = sp(params, "condition");
18
24
  if (condition) {
19
25
  const values = condition.split("|").filter(Boolean);
20
26
  if (values.length === 1)
21
- parts.push(`condition==${values[0]}`);
27
+ parts.push(sieveFilter(PRODUCT_FIELDS.CONDITION, SIEVE_OP.EQ, values[0]));
28
+ // BUG FIX: pipe is invalid for ==; expand to multiple AND clauses
22
29
  else if (values.length > 1)
23
- parts.push(`condition==${values.join("|")}`);
30
+ parts.push(sieveMultiEq(PRODUCT_FIELDS.CONDITION, values));
24
31
  }
25
32
  const minPrice = sp(params, "minPrice");
26
33
  const maxPrice = sp(params, "maxPrice");
27
34
  if (minPrice)
28
- parts.push(`price>=${minPrice}`);
35
+ parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.GTE, minPrice));
29
36
  if (maxPrice)
30
- parts.push(`price<=${maxPrice}`);
37
+ parts.push(sieveFilter(PRODUCT_FIELDS.PRICE, SIEVE_OP.LTE, maxPrice));
31
38
  const storeId = sp(params, "seller");
32
39
  if (storeId)
33
- parts.push(`storeId==${storeId}`);
40
+ parts.push(sieveFilter(PRODUCT_FIELDS.STORE_ID, SIEVE_OP.EQ, storeId));
34
41
  const freeShipping = sp(params, "freeShipping");
35
- if (freeShipping === "true")
36
- parts.push("freeShipping==true");
37
- return parts.join(",");
42
+ if (freeShipping === "true") {
43
+ parts.push(sieveFilter(PRODUCT_FIELDS.SHIPPING_PAID_BY, SIEVE_OP.EQ, PRODUCT_FIELDS.SHIPPING_PAID_BY_VALUES.SELLER));
44
+ }
45
+ return sieveAnd(...parts);
38
46
  }
39
47
  export async function ProductsIndexPageView({ searchParams = {} }) {
40
48
  const std = parseListingSearchParams(searchParams);
@@ -46,6 +46,8 @@ export function useProducts(params = {}, opts) {
46
46
  sp.set("dateTo", params.dateTo);
47
47
  if (params.preOrderProductionStatus)
48
48
  sp.set("preOrderProductionStatus", params.preOrderProductionStatus);
49
+ if (params.prizeRevealStatus)
50
+ sp.set("prizeRevealStatus", params.prizeRevealStatus);
49
51
  if (params.brand)
50
52
  sp.set("brand", params.brand);
51
53
  if (params.freeShipping !== undefined)
@@ -4,33 +4,13 @@ import { BaseRepository, prepareForFirestore, } from "../../../providers/db-fire
4
4
  import { cacheManager } from "../../../core";
5
5
  import { generateUniqueId, slugify } from "../../../utils";
6
6
  import { PRODUCT_COLLECTION, ProductStatusValues, } from "../schemas";
7
- const PRODUCT_FIELDS = {
8
- STORE_ID: "storeId",
9
- STATUS: "status",
10
- FEATURED: "featured",
11
- CATEGORY: "category",
12
- SLUG: "slug",
13
- // SB1-G (S3 2026-05-13) — canonical discriminator. Legacy `isAuction` /
14
- // `isPreOrder` boolean fields were removed in this session.
15
- LISTING_TYPE: "listingType",
16
- PRE_ORDER_DELIVERY_DATE: "preOrderDeliveryDate",
17
- IS_PROMOTED: "isPromoted",
18
- AUCTION_END_DATE: "auctionEndDate",
19
- AVAILABLE_QUANTITY: "availableQuantity",
20
- VIEW_COUNT: "viewCount",
21
- };
7
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
22
8
  /**
23
9
  * Canonical listing-kind tokens used in `listingType`. Note: the public Sieve
24
10
  * alias accepts the legacy "preorder" / "product" spelling; we normalise to
25
11
  * the Firestore-stored value here.
26
12
  */
27
- const LISTING_TYPE_VALUES = {
28
- AUCTION: "auction",
29
- PRE_ORDER: "pre-order",
30
- STANDARD: "standard",
31
- PRIZE_DRAW: "prize-draw",
32
- BUNDLE: "bundle",
33
- };
13
+ const LISTING_TYPE_VALUES = PRODUCT_FIELDS.LISTING_TYPE_VALUES;
34
14
  // Reused Sieve clauses for FILTER_ALIASES. SB1-G replaced the boolean-combo
35
15
  // clauses with single-field `listingType==X` clauses now that every doc
36
16
  // carries the discriminator.