@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
@@ -4,6 +4,7 @@ import React, { useState, useCallback } from "react";
4
4
  import { useActionDispatch } from "../../../react/hooks/use-action-dispatch";
5
5
  import { X, Pencil, Trash2 } from "lucide-react";
6
6
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
7
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
8
  import { Alert, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell, Badge, Button } from "../../../ui";
8
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
10
  import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -86,6 +87,7 @@ const PRODUCT_COLUMNS = [
86
87
  ];
87
88
  export function SellerProductsView({ onDeleteProduct, children, ...props }) {
88
89
  const hasChildren = React.Children.count(children) > 0;
90
+ const [view, setView] = useState("table");
89
91
  const dispatch = useActionDispatch();
90
92
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
91
93
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -175,6 +177,7 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
175
177
  });
176
178
  const currentPage = table.getNumber("page", 1);
177
179
  const totalPages = Math.ceil(total / PAGE_SIZE);
180
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
178
181
  if (hasChildren) {
179
182
  return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
180
183
  }
@@ -201,7 +204,7 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
201
204
  setDeletingId(null);
202
205
  }
203
206
  };
204
- return (_jsx(_Fragment, { children: _jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(TypeChips, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: listingKind !== "all"
207
+ return (_jsx(_Fragment, { children: _jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), _jsx(TypeChips, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx("div", { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), _jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: listingKind !== "all"
205
208
  ? `No ${listingKind} listings found`
206
209
  : "No products listed yet", getRowHref: (row) => row.listingKind === "auction"
207
210
  ? String(ROUTES.STORE.AUCTIONS_EDIT(row.id))
@@ -89,8 +89,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
89
89
  createdAt: string;
90
90
  updatedAt: string;
91
91
  status: "pending" | "active" | "rejected" | "suspended";
92
- isPublic: boolean;
93
92
  storeName: string;
93
+ isPublic: boolean;
94
94
  storeSlug: string;
95
95
  ownerId: string;
96
96
  location?: string | undefined;
@@ -102,17 +102,17 @@ export declare const sellerStoreSchema: z.ZodObject<{
102
102
  } | undefined;
103
103
  returnPolicy?: string | undefined;
104
104
  website?: string | undefined;
105
+ storeDescription?: string | undefined;
106
+ storeLogoURL?: string | undefined;
107
+ storeBannerURL?: string | undefined;
108
+ bio?: string | undefined;
105
109
  socialLinks?: {
106
110
  instagram?: string | undefined;
107
111
  facebook?: string | undefined;
108
112
  twitter?: string | undefined;
109
113
  linkedin?: string | undefined;
110
114
  } | undefined;
111
- storeDescription?: string | undefined;
112
115
  storeCategory?: string | undefined;
113
- storeLogoURL?: string | undefined;
114
- storeBannerURL?: string | undefined;
115
- bio?: string | undefined;
116
116
  shippingPolicy?: string | undefined;
117
117
  isVacationMode?: boolean | undefined;
118
118
  vacationMessage?: string | undefined;
@@ -121,8 +121,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
121
121
  createdAt: string;
122
122
  updatedAt: string;
123
123
  status: "pending" | "active" | "rejected" | "suspended";
124
- isPublic: boolean;
125
124
  storeName: string;
125
+ isPublic: boolean;
126
126
  storeSlug: string;
127
127
  ownerId: string;
128
128
  location?: string | undefined;
@@ -134,17 +134,17 @@ export declare const sellerStoreSchema: z.ZodObject<{
134
134
  } | undefined;
135
135
  returnPolicy?: string | undefined;
136
136
  website?: string | undefined;
137
+ storeDescription?: string | undefined;
138
+ storeLogoURL?: string | undefined;
139
+ storeBannerURL?: string | undefined;
140
+ bio?: string | undefined;
137
141
  socialLinks?: {
138
142
  instagram?: string | undefined;
139
143
  facebook?: string | undefined;
140
144
  twitter?: string | undefined;
141
145
  linkedin?: string | undefined;
142
146
  } | undefined;
143
- storeDescription?: string | undefined;
144
147
  storeCategory?: string | undefined;
145
- storeLogoURL?: string | undefined;
146
- storeBannerURL?: string | undefined;
147
- bio?: string | undefined;
148
148
  shippingPolicy?: string | undefined;
149
149
  isVacationMode?: boolean | undefined;
150
150
  vacationMessage?: string | undefined;
@@ -22,6 +22,9 @@ const SAFE_STORE_FILTER_FIELDS = new Set([
22
22
  "storeCategory",
23
23
  "status",
24
24
  "isPublic",
25
+ "isFeatured",
26
+ "averageRating",
27
+ "stats.totalProducts",
25
28
  ]);
26
29
  function validateSieveFilters(raw, allowedFields) {
27
30
  return raw
@@ -40,7 +43,13 @@ export async function GET(request) {
40
43
  const std = parseListingParams(url);
41
44
  const page = std.page ?? DEFAULT_PAGE;
42
45
  const pageSize = std.pageSize ?? DEFAULT_PAGE_SIZE;
43
- const sort = std.sorts ?? DEFAULT_SORT;
46
+ // Translate user-friendly sort names to Firestore nested field paths.
47
+ const rawSort = std.sorts ?? DEFAULT_SORT;
48
+ const sort = rawSort
49
+ .replace(/^-itemsSold$/, "-stats.itemsSold")
50
+ .replace(/^itemsSold$/, "stats.itemsSold")
51
+ .replace(/^-averageRating$/, "-stats.averageRating")
52
+ .replace(/^averageRating$/, "stats.averageRating");
44
53
  const parts = ["status==active,isPublic==true"];
45
54
  if (std.q)
46
55
  parts.push(`storeName@=*${std.q}`);
@@ -127,5 +127,6 @@ export function StoreAuctionsListing({ storeId, initialData }) {
127
127
  isWishlisted: (productId) => wishlistedIds.has(productId),
128
128
  };
129
129
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
130
- return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "auctionEndDate", sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: (v) => { setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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-4 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))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions, labels: { emptyTitle: "No auctions yet", emptyDescription: "This store has no active auctions." } })) }), 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" }) }), _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})` : ""] }) })] })] }))] }));
130
+ return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store auctions...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "auctionEndDate", sortOptions: AUCTION_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: (v) => { if (v === "table")
131
+ return; setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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-4 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))) })) : (_jsx(MarketplaceAuctionGrid, { auctions: auctions, variant: view === "list" ? "list" : "grid", gridClassName: view === "list" ? "flex flex-col gap-4" : gridClass, wishlistActions: wishlistActions, labels: { emptyTitle: "No auctions yet", emptyDescription: "This store has no active auctions." } })) }), 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" }) }), _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})` : ""] }) })] })] }))] }));
131
132
  }
@@ -87,5 +87,6 @@ export function StorePreOrdersListing({ storeId, initialData }) {
87
87
  table.setPage(1);
88
88
  }, [searchInput, table]);
89
89
  const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4";
90
- return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: (v) => { setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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))) })) : preOrders.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "This store has no pre-orders yet." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) : (_jsx("div", { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, 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" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _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})` : ""] }) })] })] }))] }));
90
+ return (_jsxs("div", { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store pre-orders...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: PREORDER_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, view: view, onViewChange: (v) => { if (v === "table")
91
+ return; setView(v); table.set("view", v); }, onResetAll: resetAll, hasActiveState: hasActiveState }), 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))) })) : preOrders.length === 0 ? (_jsx("p", { className: "py-12 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "This store has no pre-orders yet." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "list", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, product.id))) })) : (_jsx("div", { className: gridClass, children: preOrders.map((product) => (_jsx(MarketplacePreorderCard, { product: product, variant: "grid", hrefBuilder: (p) => String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(p.id)) }, 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" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4", children: _jsx(ProductFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) }), _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})` : ""] }) })] })] }))] }));
91
92
  }
@@ -25,5 +25,5 @@ export function StoreReviewsListing({ storeSlug }) {
25
25
  : "border-zinc-200 text-zinc-600 hover:bg-zinc-50 dark:border-zinc-700 dark:text-zinc-300",
26
26
  ].join(" "), children: opt.label }, opt.value))) }), isLoading ? (_jsx(Stack, { align: "center", gap: "3", className: "justify-center py-12", children: _jsx(Text, { className: "text-zinc-500", children: "Loading reviews..." }) })) : paginated.length === 0 ? (_jsxs(Stack, { align: "center", gap: "3", className: "justify-center py-24 text-center", children: [_jsx(Text, { className: "text-xl font-medium text-zinc-900 dark:text-zinc-50", children: "No reviews yet" }), _jsx(Text, { className: "text-sm text-zinc-500", children: ratingFilter > 0
27
27
  ? `No ${ratingFilter}-star reviews yet.`
28
- : "Be the first to review this store by purchasing a product." })] })) : (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: 3, gap: "md", children: paginated.map((review) => (_jsx(ReviewCard, { review: review }, review.id))) }), totalPages > 1 && (_jsx(Row, { className: "mt-8 justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: setPage }) }))] }))] }) }));
28
+ : "Be the first to review this store by purchasing a product." })] })) : (_jsxs(_Fragment, { children: [_jsx(Grid, { cols: 3, gap: "md", children: paginated.map((review) => (_jsx(ReviewCard, { review: review, context: "store" }, review.id))) }), totalPages > 1 && (_jsx(Row, { className: "mt-8 justify-center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: setPage }) }))] }))] }) }));
29
29
  }
@@ -4,22 +4,33 @@ import { useState, useCallback, useMemo } from "react";
4
4
  import { SlidersHorizontal, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
6
  import { useStores } from "../hooks/useStores";
7
- import { BulkActionsBar, Pagination, ListingToolbar } from "../../../ui";
7
+ import { BulkActionBar, Pagination, ListingToolbar } from "../../../ui";
8
8
  import { ROUTES } from "../../../next";
9
9
  import { InteractiveStoreCard } from "./InteractiveStoreCard";
10
10
  import { StoreFilters } from "./StoreFilters";
11
11
  import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
12
+ import { TABLE_KEYS, VIEW_MODE } from "../../../constants/table-keys";
13
+ import { sortBy } from "../../../constants/sort";
14
+ import { STORE_FIELDS } from "../../../constants/field-names";
15
+ const DEFAULT_SORT = sortBy(STORE_FIELDS.CREATED_AT);
12
16
  const STORE_SORT_OPTIONS = [
13
- { value: "-createdAt", label: "Newest First" },
14
- { value: "storeName", label: "Name A–Z" },
17
+ { value: sortBy(STORE_FIELDS.CREATED_AT), label: "Newest First" },
18
+ { value: sortBy(STORE_FIELDS.STORE_NAME, "ASC"), label: "Name A–Z" },
15
19
  { value: "-itemsSold", label: "Most Sales" },
16
20
  { value: "-averageRating", label: "Top Rated" },
17
21
  ];
18
- const FILTER_KEYS = ["category", "rating", "minProductCount", "maxProductCount", "featured"];
22
+ const FILTER_KEYS = [TABLE_KEYS.CATEGORY, "rating", "minProductCount", "maxProductCount", TABLE_KEYS.FEATURED];
19
23
  export function StoresIndexListing({ initialData }) {
20
- const table = useUrlTable({ defaults: { pageSize: "24", sort: "-createdAt" } });
21
- const [searchInput, setSearchInput] = useState(table.get("q") || "");
24
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
25
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
22
26
  const [filterOpen, setFilterOpen] = useState(false);
27
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
28
+ const handleViewToggle = (next) => {
29
+ if (next === VIEW_MODE.TABLE)
30
+ return;
31
+ setView(next);
32
+ table.set(TABLE_KEYS.VIEW, next);
33
+ };
23
34
  // Pending filter state — buffered until "Apply Filters" clicked
24
35
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
25
36
  const pendingTable = useMemo(() => ({
@@ -62,24 +73,24 @@ export function StoresIndexListing({ initialData }) {
62
73
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
63
74
  }, []);
64
75
  const resetAll = useCallback(() => {
65
- const updates = { q: "", sort: "" };
76
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" };
66
77
  for (const k of FILTER_KEYS)
67
78
  updates[k] = "";
68
79
  table.setMany(updates);
69
80
  setSearchInput("");
70
81
  }, [table]);
71
82
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
72
- const hasActiveState = !!table.get("q") ||
73
- table.get("sort") !== "-createdAt" ||
83
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
84
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
74
85
  activeFilterCount > 0;
75
86
  const commitSearch = useCallback(() => {
76
- table.set("q", searchInput.trim());
87
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
77
88
  }, [searchInput, table]);
78
89
  // Build sieve filters from applied URL params
79
- const ratingRaw = table.get("rating");
90
+ const ratingRaw = table.get(TABLE_KEYS.RATING);
80
91
  const minProductCount = table.get("minProductCount");
81
92
  const maxProductCount = table.get("maxProductCount");
82
- const featured = table.get("featured");
93
+ const featured = table.get(TABLE_KEYS.FEATURED);
83
94
  const filterParts = [];
84
95
  if (ratingRaw) {
85
96
  const ratings = ratingRaw.split("|").filter(Boolean);
@@ -93,26 +104,33 @@ export function StoresIndexListing({ initialData }) {
93
104
  if (featured === "true")
94
105
  filterParts.push("isFeatured==true");
95
106
  const { stores, totalPages, isLoading } = useStores({
96
- q: table.get("q") || undefined,
97
- page: table.getNumber("page", 1),
98
- pageSize: table.getNumber("pageSize", 24),
99
- sort: table.get("sort") || undefined,
100
- category: table.get("category") || undefined,
107
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
108
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
109
+ pageSize: table.getNumber(TABLE_KEYS.PAGE_SIZE, 24),
110
+ sort: table.get(TABLE_KEYS.SORT) || undefined,
111
+ category: table.get(TABLE_KEYS.CATEGORY) || undefined,
101
112
  filters: filterParts.length > 0 ? filterParts.join(",") : undefined,
102
113
  }, { initialData });
103
114
  const selection = useBulkSelection({ items: stores, keyExtractor: (s) => s.id ?? s.storeSlug });
104
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || "-createdAt", sortOptions: STORE_SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, hideViewToggle: true, onResetAll: resetAll, hasActiveState: hasActiveState, bulkMode: selection.isSelecting, bulkSelectedCount: selection.selectedCount, bulkTotalCount: stores.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), 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: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-4 space-y-2.5", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("div", { className: "h-10 w-10 rounded-lg bg-zinc-200 dark:bg-slate-700" }) }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] })] }, i))) })) : stores.length === 0 ? (_jsx("p", { className: "py-16 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No stores found." })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: stores.map((store) => {
105
- const storeKey = store.storeSlug ?? store.id;
106
- return (_jsx(InteractiveStoreCard, { store: store, href: String(ROUTES.PUBLIC.STORE_DETAIL(storeKey)), selectable: selection.isSelecting, isSelected: selection.isSelected(store.id ?? store.storeSlug), onSelect: (id, sel) => { void sel; selection.toggle(id); } }, storeKey));
107
- }) })) }), _jsx(BulkActionsBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
115
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search stores...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: STORE_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: stores.length, onBulkSelectAll: selection.toggleAll, onBulkClear: selection.clearSelection }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
108
116
  {
109
- key: "compare",
117
+ id: "compare",
110
118
  label: "Compare",
111
119
  variant: "secondary",
112
- onClick: () => {
113
- /* BK3 compare overlay — wired in a later session */
114
- selection.clearSelection();
115
- },
120
+ onClick: () => { selection.clearSelection(); },
121
+ },
122
+ {
123
+ id: "visit",
124
+ label: "Visit Store",
125
+ variant: "primary",
126
+ disabled: selection.selectedCount !== 1,
127
+ onClick: () => { selection.clearSelection(); },
116
128
  },
117
- ] }), 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(StoreFilters, { table: pendingTable }) }), _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})` : ""] }) })] })] }))] }));
129
+ ] }), 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: table.getNumber("page", 1), totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx("div", { className: "py-6", children: isLoading ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: Array.from({ length: 6 }).map((_, i) => (_jsxs("div", { className: "rounded-xl border border-zinc-100 dark:border-slate-700 overflow-hidden animate-pulse", children: [_jsx("div", { className: "aspect-video bg-zinc-200 dark:bg-slate-700" }), _jsxs("div", { className: "p-4 space-y-2.5", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("div", { className: "h-10 w-10 rounded-lg bg-zinc-200 dark:bg-slate-700" }) }), _jsx("div", { className: "h-4 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-full" }), _jsx("div", { className: "h-3 bg-zinc-200 dark:bg-slate-700 rounded w-1/2" })] })] }, i))) })) : stores.length === 0 ? (_jsx("p", { className: "py-16 text-center text-sm text-zinc-500 dark:text-zinc-400", children: "No stores found." })) : view === "list" ? (_jsx("div", { className: "flex flex-col divide-y divide-zinc-100 dark:divide-zinc-800 rounded-xl border border-zinc-100 dark:border-zinc-800", children: stores.map((store) => {
130
+ const storeKey = store.storeSlug ?? store.id;
131
+ return (_jsx(InteractiveStoreCard, { store: store, href: String(ROUTES.PUBLIC.STORE_DETAIL(storeKey)), selectable: selection.isSelecting, isSelected: selection.isSelected(store.id ?? store.storeSlug), onSelect: (id, sel) => { void sel; selection.toggle(id); } }, storeKey));
132
+ }) })) : (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6", children: stores.map((store) => {
133
+ const storeKey = store.storeSlug ?? store.id;
134
+ return (_jsx(InteractiveStoreCard, { store: store, href: String(ROUTES.PUBLIC.STORE_DETAIL(storeKey)), selectable: selection.isSelecting, isSelected: selection.isSelected(store.id ?? store.storeSlug), onSelect: (id, sel) => { void sel; selection.toggle(id); } }, storeKey));
135
+ }) })) }), 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(StoreFilters, { table: pendingTable }) }), _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})` : ""] }) })] })] }))] }));
118
136
  }
@@ -68,6 +68,7 @@ export interface StoreDocument {
68
68
  returnPolicy?: string;
69
69
  shippingPolicy?: string;
70
70
  isPublic: boolean;
71
+ isFeatured?: boolean;
71
72
  isVacationMode?: boolean;
72
73
  vacationMessage?: string;
73
74
  stats?: {
@@ -34,32 +34,32 @@ export declare const storeListItemSchema: z.ZodObject<{
34
34
  }, "strip", z.ZodTypeAny, {
35
35
  id: string;
36
36
  status: string;
37
- isPublic: boolean;
38
37
  storeName: string;
38
+ isPublic: boolean;
39
39
  storeSlug: string;
40
40
  ownerId: string;
41
41
  createdAt?: string | undefined;
42
42
  itemsSold?: number | undefined;
43
43
  storeDescription?: string | undefined;
44
- storeCategory?: string | undefined;
45
44
  storeLogoURL?: string | undefined;
46
45
  storeBannerURL?: string | undefined;
46
+ storeCategory?: string | undefined;
47
47
  totalProducts?: number | undefined;
48
48
  totalReviews?: number | undefined;
49
49
  averageRating?: number | undefined;
50
50
  }, {
51
51
  id: string;
52
52
  status: string;
53
- isPublic: boolean;
54
53
  storeName: string;
54
+ isPublic: boolean;
55
55
  storeSlug: string;
56
56
  ownerId: string;
57
57
  createdAt?: string | undefined;
58
58
  itemsSold?: number | undefined;
59
59
  storeDescription?: string | undefined;
60
- storeCategory?: string | undefined;
61
60
  storeLogoURL?: string | undefined;
62
61
  storeBannerURL?: string | undefined;
62
+ storeCategory?: string | undefined;
63
63
  totalProducts?: number | undefined;
64
64
  totalReviews?: number | undefined;
65
65
  averageRating?: number | undefined;
package/dist/index.d.ts CHANGED
@@ -528,7 +528,7 @@ export { storeRepository } from "./repositories/index";
528
528
  export { scammerRepository } from "./repositories/index";
529
529
  export { supportRepository, SupportRepository } from "./repositories/index";
530
530
  export type { SupportTicketDocument, SupportTicketCreateInput, SupportTicketUpdateInput, TicketMessage, TicketCategory, TicketStatus, TicketPriority, } from "./repositories/index";
531
- export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
531
+ export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, SUPPORT_TICKET_FIELDS, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
532
532
  export { supportTicketsSeedData } from "./seed/index";
533
533
  export { productFeaturesRepository } from "./repositories/index";
534
534
  export type { ProductFeatureListFilter } from "./repositories/index";
@@ -3047,3 +3047,10 @@ export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPre
3047
3047
  export type { ListingCheckoutRule, CategoryCheckoutRule, CartItemProductPair, OrderItemInput, RefundPolicy, } from "./_internal/shared/checkout/rules";
3048
3048
  export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, } from "./_internal/shared/media/limits";
3049
3049
  export type { MediaKind, AllowedImageMime, AllowedVideoMime, AllowedDocMime, AllowedMime, } from "./_internal/shared/media/limits";
3050
+ export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, BLOG_FIELDS, BRAND_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, COUPON_USAGE_FIELDS, CONVERSATION_FIELDS, CAROUSEL_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
3051
+ export { SORT_DIR, sortBy } from "./constants/sort";
3052
+ export type { SortDirection } from "./constants/sort";
3053
+ export { TABLE_KEYS, VIEW_MODE } from "./constants/table-keys";
3054
+ export type { TableKey, ViewMode } from "./constants/table-keys";
3055
+ export { SIEVE_OP, SIEVE_PIPE_OPS, sieveFilter, sieveMultiEq, expandSieveParam, sieveAnd, } from "./utils/sieve-builder";
3056
+ export type { SieveOp } from "./utils/sieve-builder";
package/dist/index.js CHANGED
@@ -1084,7 +1084,7 @@ export { storeRepository } from "./repositories/index";
1084
1084
  export { scammerRepository } from "./repositories/index";
1085
1085
  // supportRepository - Server-only repository for support tickets (BAN1).
1086
1086
  export { supportRepository, SupportRepository } from "./repositories/index";
1087
- export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
1087
+ export { ELIGIBLE_ORDER_STATUSES_FOR_TICKET, SUPPORT_TICKET_COLLECTION, ACTIVE_TICKET_STATUSES, SUPPORT_TICKET_FIELDS, TicketCategoryValues, TicketStatusValues, TicketPriorityValues, } from "./features/support/schemas/firestore";
1088
1088
  // Support tickets — seed data
1089
1089
  export { supportTicketsSeedData } from "./seed/index";
1090
1090
  // SB-UNI-B — sublistingCategoriesRepository + SublistingCategoryDocument deleted.
@@ -5488,3 +5488,14 @@ export { LISTING_TYPE_REGISTRY, pluginFor } from "./_internal/shared/listing-typ
5488
5488
  export { getListingRule, getCategoryRule, pickOrderType, getSplitKey, runSyncPreflight, CHECKOUT_RULES, CATEGORY_CHECKOUT_RULES, CHECKOUT_MAX_ORDERS_PER_TX, PRIZE_DRAW_MAX_REVEALS_PER_ORDER, BUNDLE_MAX_QTY_PER_TX, STANDARD_MAX_QTY_PER_LINE, } from "./_internal/shared/checkout/rules";
5489
5489
  // Media upload limits — pure constants, safe in both client and server bundles.
5490
5490
  export { MEGABYTE, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MAX_LABEL, MAX_BYTES, ALLOWED_IMAGE_MIMES, ALLOWED_VIDEO_MIMES, ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_TYPES_LABEL, MIME_TO_EXT, PDF_MAGIC, VIDEO_CONVERSION_HINTS, classifyMime, isAllowedMime, maxBytesFor, getConversionHint, } from "./_internal/shared/media/limits";
5491
+ // ── Field name constants — canonical source of truth for all Firestore field names ──
5492
+ // NOTE: STORE_FIELDS, CATEGORY_FIELDS, EVENT_FIELDS, EVENT_ENTRY_FIELDS, PAYOUT_FIELDS,
5493
+ // COUPON_FIELDS, CART_FIELDS, USER_FIELDS, ADDRESS_FIELDS, NOTIFICATION_FIELDS,
5494
+ // SESSION_FIELDS, SCAMMER_FIELDS are already exported from feature barrels above.
5495
+ export { PRODUCT_FIELDS, PRODUCT_STATUS_TRANSITIONS, ORDER_FIELDS, REVIEW_FIELDS, BID_FIELDS, AD_FIELDS, BLOG_FIELDS, BRAND_FIELDS, WISHLIST_FIELDS, HISTORY_FIELDS, COUPON_USAGE_FIELDS, CONVERSATION_FIELDS, CAROUSEL_FIELDS, FAQ_FIELDS, HOMEPAGE_SECTION_FIELDS, SITE_SETTINGS_FIELDS, COMMON_FIELDS, OAUTH_STATE_VALUES, SCHEMA_DEFAULTS, } from "./constants/field-names";
5496
+ // ── Sort helpers ──────────────────────────────────────────────────────────────
5497
+ export { SORT_DIR, sortBy } from "./constants/sort";
5498
+ // ── URL table keys + view mode ────────────────────────────────────────────────
5499
+ export { TABLE_KEYS, VIEW_MODE } from "./constants/table-keys";
5500
+ // ── Sieve query builder ───────────────────────────────────────────────────────
5501
+ export { SIEVE_OP, SIEVE_PIPE_OPS, sieveFilter, sieveMultiEq, expandSieveParam, sieveAnd, } from "./utils/sieve-builder";
@@ -34,7 +34,8 @@
34
34
  */
35
35
  import { useRouter, usePathname, useSearchParams } from "next/navigation";
36
36
  import { useCallback, useRef } from "react";
37
- const NON_RESETTING_KEYS = ["page", "pageSize", "view"];
37
+ import { TABLE_KEYS } from "../../constants/table-keys";
38
+ const NON_RESETTING_KEYS = [TABLE_KEYS.PAGE, TABLE_KEYS.PAGE_SIZE, TABLE_KEYS.VIEW];
38
39
  export function useUrlTable(options) {
39
40
  // Always call native hooks unconditionally (React rules).
40
41
  const nativeRouter = useRouter();