@mohasinac/appkit 2.7.11 → 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 (141) 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/constants/field-names.d.ts +813 -0
  18. package/dist/constants/field-names.js +918 -0
  19. package/dist/constants/sort.d.ts +7 -0
  20. package/dist/constants/sort.js +5 -0
  21. package/dist/constants/table-keys.d.ts +48 -0
  22. package/dist/constants/table-keys.js +46 -0
  23. package/dist/features/account/components/AddressesIndexListing.js +9 -6
  24. package/dist/features/admin/components/AdminAllEventEntriesView.js +4 -1
  25. package/dist/features/admin/components/AdminBidsView.js +4 -1
  26. package/dist/features/admin/components/AdminBlogView.js +9 -2
  27. package/dist/features/admin/components/AdminBrandsView.js +9 -2
  28. package/dist/features/admin/components/AdminCarouselView.js +4 -1
  29. package/dist/features/admin/components/AdminCartsView.js +4 -1
  30. package/dist/features/admin/components/AdminCategoriesView.js +9 -2
  31. package/dist/features/admin/components/AdminContactView.js +4 -1
  32. package/dist/features/admin/components/AdminCouponsView.js +4 -1
  33. package/dist/features/admin/components/AdminFaqsView.js +5 -1
  34. package/dist/features/admin/components/AdminFeaturesView.js +5 -2
  35. package/dist/features/admin/components/AdminHistoryView.js +5 -1
  36. package/dist/features/admin/components/AdminNewsletterView.js +4 -1
  37. package/dist/features/admin/components/AdminNotificationsView.js +4 -1
  38. package/dist/features/admin/components/AdminOrdersView.js +14 -12
  39. package/dist/features/admin/components/AdminPayoutsView.js +4 -1
  40. package/dist/features/admin/components/AdminPrizeDrawsView.js +4 -1
  41. package/dist/features/admin/components/AdminProductsView.js +14 -3
  42. package/dist/features/admin/components/AdminReturnRequestsView.js +4 -1
  43. package/dist/features/admin/components/AdminReviewsView.js +4 -1
  44. package/dist/features/admin/components/AdminScammersView.js +4 -1
  45. package/dist/features/admin/components/AdminSessionsView.js +4 -1
  46. package/dist/features/admin/components/AdminStoreAddressesView.js +5 -1
  47. package/dist/features/admin/components/AdminStoresView.js +10 -3
  48. package/dist/features/admin/components/AdminSublistingCategoriesView.js +4 -1
  49. package/dist/features/admin/components/AdminSupportTicketsView.js +4 -1
  50. package/dist/features/admin/components/AdminTeamView.js +4 -1
  51. package/dist/features/admin/components/AdminUsersView.js +9 -12
  52. package/dist/features/admin/components/AdminViewCards.d.ts +12 -0
  53. package/dist/features/admin/components/AdminViewCards.js +53 -0
  54. package/dist/features/admin/components/AdminWishlistsView.js +4 -1
  55. package/dist/features/auctions/repository/bid.repository.js +1 -6
  56. package/dist/features/auctions/schemas/index.d.ts +4 -4
  57. package/dist/features/blog/actions/blog-actions.d.ts +16 -16
  58. package/dist/features/blog/components/BlogFilters.d.ts +28 -28
  59. package/dist/features/blog/components/BlogFilters.js +18 -15
  60. package/dist/features/blog/components/BlogIndexListing.js +20 -16
  61. package/dist/features/blog/schemas/index.d.ts +10 -10
  62. package/dist/features/categories/components/CategoriesIndexListing.js +25 -21
  63. package/dist/features/categories/schemas/index.d.ts +2 -2
  64. package/dist/features/events/components/AdminEventsView.js +5 -1
  65. package/dist/features/events/components/EventFilters.d.ts +24 -24
  66. package/dist/features/events/components/EventFilters.js +16 -13
  67. package/dist/features/events/components/EventStatusBadge.js +1 -0
  68. package/dist/features/events/components/EventsIndexListing.js +31 -26
  69. package/dist/features/events/components/EventsListPageView.js +13 -10
  70. package/dist/features/events/schemas/firestore.d.ts +1 -0
  71. package/dist/features/events/schemas/firestore.js +1 -0
  72. package/dist/features/events/schemas/index.d.ts +1 -1
  73. package/dist/features/events/types/index.d.ts +1 -1
  74. package/dist/features/faq/actions/faq-actions.d.ts +10 -10
  75. package/dist/features/faq/schemas/index.d.ts +4 -4
  76. package/dist/features/orders/components/OrderFilters.d.ts +20 -20
  77. package/dist/features/orders/components/OrderFilters.js +33 -30
  78. package/dist/features/orders/repository/orders.repository.js +1 -15
  79. package/dist/features/orders/schemas/index.d.ts +2 -2
  80. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +35 -31
  81. package/dist/features/pre-orders/schemas/index.d.ts +3 -3
  82. package/dist/features/products/components/AuctionsIndexListing.js +34 -30
  83. package/dist/features/products/components/BidHistory.d.ts +0 -1
  84. package/dist/features/products/components/BidHistory.js +1 -1
  85. package/dist/features/products/components/PrizeDrawsIndexListing.js +44 -40
  86. package/dist/features/products/components/ProductDetailPageView.js +1 -1
  87. package/dist/features/products/components/ProductFilters.d.ts +20 -20
  88. package/dist/features/products/components/ProductFilters.js +62 -59
  89. package/dist/features/products/components/ProductsIndexListing.js +28 -24
  90. package/dist/features/products/components/ProductsIndexPageView.js +18 -10
  91. package/dist/features/products/hooks/useProducts.js +2 -0
  92. package/dist/features/products/repository/products.repository.js +2 -22
  93. package/dist/features/products/schemas/index.d.ts +28 -28
  94. package/dist/features/products/schemas/product-features.validators.d.ts +12 -12
  95. package/dist/features/products/types/index.d.ts +2 -0
  96. package/dist/features/promotions/components/CouponsIndexListing.js +21 -17
  97. package/dist/features/promotions/schemas/index.d.ts +4 -4
  98. package/dist/features/reviews/components/ReviewDetailShell.js +6 -5
  99. package/dist/features/reviews/components/ReviewFilters.d.ts +16 -16
  100. package/dist/features/reviews/components/ReviewFilters.js +10 -7
  101. package/dist/features/reviews/components/ReviewsIndexListing.js +21 -17
  102. package/dist/features/reviews/components/ReviewsList.d.ts +5 -2
  103. package/dist/features/reviews/components/ReviewsList.js +11 -5
  104. package/dist/features/reviews/schemas/firestore.d.ts +1 -0
  105. package/dist/features/reviews/schemas/index.d.ts +8 -8
  106. package/dist/features/reviews/types/index.d.ts +1 -0
  107. package/dist/features/search/schemas/index.d.ts +2 -2
  108. package/dist/features/seller/components/SellerAuctionsView.js +5 -1
  109. package/dist/features/seller/components/SellerBidsView.js +4 -1
  110. package/dist/features/seller/components/SellerCouponsView.js +4 -1
  111. package/dist/features/seller/components/SellerOffersView.js +5 -1
  112. package/dist/features/seller/components/SellerOrdersView.js +4 -1
  113. package/dist/features/seller/components/SellerPayoutsView.js +5 -1
  114. package/dist/features/seller/components/SellerPreOrdersView.js +4 -1
  115. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -1
  116. package/dist/features/seller/components/SellerProductsView.js +4 -1
  117. package/dist/features/seller/schemas/index.d.ts +10 -10
  118. package/dist/features/stores/api/route.js +10 -1
  119. package/dist/features/stores/components/StoreReviewsListing.js +1 -1
  120. package/dist/features/stores/components/StoresIndexListing.js +24 -20
  121. package/dist/features/stores/schemas/firestore.d.ts +1 -0
  122. package/dist/features/stores/schemas/index.d.ts +4 -4
  123. package/dist/index.d.ts +8 -1
  124. package/dist/index.js +12 -1
  125. package/dist/react/hooks/useUrlTable.js +2 -1
  126. package/dist/seed/bids-seed-data.js +45 -44
  127. package/dist/seed/categories-seed-data.js +41 -40
  128. package/dist/seed/coupons-seed-data.js +41 -40
  129. package/dist/seed/products-auctions-seed-data.js +54 -53
  130. package/dist/seed/products-preorders-seed-data.js +25 -24
  131. package/dist/seed/products-prize-draws-seed-data.js +24 -23
  132. package/dist/seed/products-standard-seed-data.js +294 -293
  133. package/dist/seed/reviews-seed-data.js +61 -60
  134. package/dist/seed/scammers-seed-data.js +3 -2
  135. package/dist/seed/stores-seed-data.js +9 -8
  136. package/dist/seed/support-tickets-seed-data.js +13 -12
  137. package/dist/seed/users-seed-data.js +28 -27
  138. package/dist/tailwind-utilities.css +1 -1
  139. package/dist/utils/sieve-builder.d.ts +41 -0
  140. package/dist/utils/sieve-builder.js +66 -0
  141. package/package.json +1 -1
@@ -27,8 +27,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
27
27
  currency?: string | undefined;
28
28
  status?: string | undefined;
29
29
  price?: number | undefined;
30
- listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
31
30
  featured?: boolean | undefined;
31
+ listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
32
32
  isPromoted?: boolean | undefined;
33
33
  mainImage?: string | undefined;
34
34
  }, {
@@ -38,8 +38,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
38
38
  currency?: string | undefined;
39
39
  status?: string | undefined;
40
40
  price?: number | undefined;
41
- listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
42
41
  featured?: boolean | undefined;
42
+ listingType?: "auction" | "standard" | "pre-order" | "prize-draw" | "bundle" | undefined;
43
43
  isPromoted?: boolean | undefined;
44
44
  mainImage?: string | undefined;
45
45
  }>;
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { AdminViewCards } from "../../admin/components/AdminViewCards";
6
8
  import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
7
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
10
  import { SELLER_AUCTION_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -20,6 +22,7 @@ const SORT_OPTIONS = [
20
22
  const STATUS_OPTIONS = SELLER_AUCTION_STATUS_TABS;
21
23
  export function SellerAuctionsView({ renderHeader, children, ...props }) {
22
24
  const hasChildren = React.Children.count(children) > 0;
25
+ const [view, setView] = useState("table");
23
26
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
24
27
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
25
28
  const [filterOpen, setFilterOpen] = useState(false);
@@ -74,8 +77,9 @@ export function SellerAuctionsView({ renderHeader, children, ...props }) {
74
77
  });
75
78
  const currentPage = table.getNumber("page", 1);
76
79
  const totalPages = Math.ceil(total / PAGE_SIZE);
80
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
77
81
  if (hasChildren) {
78
82
  return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
79
83
  }
80
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No auctions found" })] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
84
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search auctions by product name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No auctions found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No auctions found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
81
85
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Badge, Div, FilterChipGroup, ListingToolbar, Pagination } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { SELLER_BID_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -34,6 +35,7 @@ const STATUS_BADGE = {
34
35
  // ---------------------------------------------------------------------------
35
36
  export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
36
37
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
38
+ const [view, setView] = useState("table");
37
39
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
38
40
  const [filterOpen, setFilterOpen] = useState(false);
39
41
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
@@ -119,5 +121,6 @@ export function SellerBidsView({ endpoint = SELLER_ENDPOINTS.BIDS }) {
119
121
  render: (row) => _jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: row.bidDate }),
120
122
  },
121
123
  ];
122
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by bidder name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No bids found for your 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Bid Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "" }) }), _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-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
124
+ const selection = useBulkSelection({ items: rows ?? [], keyExtractor: (r) => r.id });
125
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by bidder name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No bids found for your 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Bid Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })), allId: "" }) }), _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-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
123
126
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X, Plus } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Button, Div, ListingToolbar, Pagination, ListingViewShell, Text } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { toRecordArray, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
@@ -30,6 +31,7 @@ function getIsActive(item) {
30
31
  // ---------------------------------------------------------------------------
31
32
  export function SellerCouponsView({ onCreateClick, onEditClick, onToggle, onDelete, children, ...props }) {
32
33
  const hasChildren = React.Children.count(children) > 0;
34
+ const [view, setView] = useState("table");
33
35
  const [togglingId, setTogglingId] = useState(null);
34
36
  const [deletingId, setDeletingId] = useState(null);
35
37
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
@@ -104,10 +106,11 @@ export function SellerCouponsView({ onCreateClick, onEditClick, onToggle, onDele
104
106
  setDeletingId(null);
105
107
  }
106
108
  }, [onDelete, refetch]);
109
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
107
110
  if (hasChildren) {
108
111
  return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
109
112
  }
110
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by coupon code", 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, extra: onCreateClick
113
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by coupon code", 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, extra: onCreateClick
111
114
  ? (_jsxs(Button, { size: "sm", onClick: onCreateClick, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "Add Coupon" })] }))
112
115
  : undefined }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "fluid-grid-card gap-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Div, { className: "rounded-xl border-2 border-zinc-100 dark:border-slate-700 p-4 animate-pulse space-y-3", children: [_jsx(Div, { className: "h-6 bg-zinc-200 dark:bg-slate-700 rounded w-2/3" }), _jsx(Div, { className: "h-4 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))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No coupons found \u2014 create your first coupon" }) })) : (_jsx(Div, { className: "fluid-grid-card gap-3", children: rows.map((row) => (_jsx(CouponCard, { coupon: row.raw, onEdit: onEditClick ? handleEdit : undefined, onToggleActive: onToggle ? handleToggle : undefined, onDelete: onDelete ? handleDelete : undefined, className: togglingId === row.id || deletingId === row.id ? "pointer-events-none opacity-60" : undefined }, row.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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", 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 space-y-5", children: _jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [{ label: "All", value: "" }, { label: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value
113
116
  ? "bg-[var(--appkit-color-primary)] text-white border-[var(--appkit-color-primary)]"
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { AdminViewCards } from "../../admin/components/AdminViewCards";
6
8
  import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
7
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
10
  import { SELLER_OFFER_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -18,6 +20,7 @@ const SORT_OPTIONS = [
18
20
  const STATUS_OPTIONS = SELLER_OFFER_STATUS_TABS;
19
21
  export function SellerOffersView({ children, ...props }) {
20
22
  const hasChildren = React.Children.count(children) > 0;
23
+ const [view, setView] = useState("table");
21
24
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
22
25
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
23
26
  const [filterOpen, setFilterOpen] = useState(false);
@@ -72,8 +75,9 @@ export function SellerOffersView({ children, ...props }) {
72
75
  });
73
76
  const currentPage = table.getNumber("page", 1);
74
77
  const totalPages = Math.ceil(total / PAGE_SIZE);
78
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
75
79
  if (hasChildren) {
76
80
  return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
77
81
  }
78
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search offers by product or buyer name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No offers received" })] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
82
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search offers by product or buyer name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No offers received" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No offers received", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
79
83
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X, Eye } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Badge, Button, Div, FilterChipGroup, Heading, Input, ListingToolbar, Pagination, ListingViewShell, Select, SideDrawer, Stack, Text } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { SELLER_ORDER_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -108,6 +109,7 @@ function OrderDetailDrawer({ orderId, apiBase, onClose, }) {
108
109
  // ---------------------------------------------------------------------------
109
110
  export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS, children, ...props }) {
110
111
  const hasChildren = React.Children.count(children) > 0;
112
+ const [view, setView] = useState("table");
111
113
  const [selectedOrderId, setSelectedOrderId] = useState(null);
112
114
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
113
115
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -190,8 +192,9 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
190
192
  },
191
193
  ];
192
194
  const renderRowActions = useCallback((row) => (_jsx("button", { type: "button", onClick: () => setSelectedOrderId(row.id), title: "View order details", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 hover:text-zinc-700 dark:hover:text-zinc-300 transition-colors", children: _jsx(Eye, { className: "h-4 w-4" }) })), []);
195
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
193
196
  if (hasChildren) {
194
197
  return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
195
198
  }
196
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No orders yet", renderRowActions: renderRowActions })] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), selectedOrderId && (_jsx(OrderDetailDrawer, { orderId: selectedOrderId, apiBase: orderDetailApiBase, onClose: () => setSelectedOrderId(null) }))] }));
199
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search by order ID or buyer name", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx(Div, { className: "mb-4 rounded-xl border border-red-200 bg-red-50 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, columns: columns, isLoading: isLoading, emptyLabel: "No orders yet", renderRowActions: renderRowActions })] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white transition-colors active:scale-[0.98]", children: ["Apply", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] })), selectedOrderId && (_jsx(OrderDetailDrawer, { orderId: selectedOrderId, apiBase: orderDetailApiBase, onClose: () => setSelectedOrderId(null) }))] }));
197
200
  }
@@ -3,6 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { AdminViewCards } from "../../admin/components/AdminViewCards";
6
8
  import { FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
7
9
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
10
  import { ADMIN_PAYOUT_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -18,6 +20,7 @@ const SORT_OPTIONS = [
18
20
  const STATUS_OPTIONS = ADMIN_PAYOUT_STATUS_TABS;
19
21
  export function SellerPayoutsView({ children, ...props }) {
20
22
  const hasChildren = React.Children.count(children) > 0;
23
+ const [view, setView] = useState("table");
21
24
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
22
25
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
23
26
  const [filterOpen, setFilterOpen] = useState(false);
@@ -72,8 +75,9 @@ export function SellerPayoutsView({ children, ...props }) {
72
75
  });
73
76
  const currentPage = table.getNumber("page", 1);
74
77
  const totalPages = Math.ceil(total / PAGE_SIZE);
78
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
75
79
  if (hasChildren) {
76
80
  return _jsx(ListingViewShell, { portal: "seller", ...props, children: children });
77
81
  }
78
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search payouts by payout # or amount", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found" })] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
82
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search payouts by payout # or amount", 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 }), 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: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), view === "table" ? (_jsx(DataTable, { rows: rows, isLoading: isLoading, emptyLabel: "No payouts found" })) : (_jsx(AdminViewCards, { rows: rows, view: view, isLoading: isLoading, emptyLabel: "No payouts found", onRowClick: undefined, selectedIdSet: selection.selectedIdSet, onToggleSelect: selection.toggle }))] }), 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: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "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", 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 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _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})` : ""] }) })] })] }))] }));
79
83
  }
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { Pencil, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Alert, Badge, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { SELLER_PRE_ORDER_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -70,6 +71,7 @@ const PRE_ORDER_COLUMNS = [
70
71
  ];
71
72
  export function SellerPreOrdersView({ children, ...props }) {
72
73
  const hasChildren = React.Children.count(children) > 0;
74
+ const [view, setView] = useState("table");
73
75
  const dispatch = useActionDispatch();
74
76
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
75
77
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -131,10 +133,11 @@ export function SellerPreOrdersView({ children, ...props }) {
131
133
  });
132
134
  const currentPage = table.getNumber("page", 1);
133
135
  const totalPages = Math.ceil(total / PAGE_SIZE);
136
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
134
137
  if (hasChildren) {
135
138
  return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
136
139
  }
137
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders 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 }), 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: PRE_ORDER_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No pre-orders listed yet", getRowHref: (row) => String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
140
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search pre-orders 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 }), 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: PRE_ORDER_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No pre-orders listed yet", getRowHref: (row) => String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
138
141
  e.stopPropagation();
139
142
  void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRE_ORDERS_EDIT(row.id)) });
140
143
  }, "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) })) })] }), 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-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import React, { useState, useCallback } from "react";
4
4
  import { Pencil, X } from "lucide-react";
5
5
  import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
7
  import { Alert, Badge, Button, FilterChipGroup, ListingToolbar, Pagination, ListingViewShell } from "../../../ui";
7
8
  import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
9
  import { SELLER_PRIZE_DRAW_STATUS_TABS } from "../../admin/constants/filter-tabs";
@@ -67,6 +68,7 @@ const PRIZE_DRAW_COLUMNS = [
67
68
  ];
68
69
  export function SellerPrizeDrawsView({ children, ...props }) {
69
70
  const hasChildren = React.Children.count(children) > 0;
71
+ const [view, setView] = useState("table");
70
72
  const dispatch = useActionDispatch();
71
73
  const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
72
74
  const [searchInput, setSearchInput] = useState(table.get("q") || "");
@@ -128,10 +130,11 @@ export function SellerPrizeDrawsView({ children, ...props }) {
128
130
  });
129
131
  const currentPage = table.getNumber("page", 1);
130
132
  const totalPages = Math.ceil(total / PAGE_SIZE);
133
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
131
134
  if (hasChildren) {
132
135
  return (_jsx(ListingViewShell, { portal: "seller", ...props, children: children }));
133
136
  }
134
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws 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 }), 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: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws listed yet", getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
137
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search prize draws 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 }), 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: PRIZE_DRAW_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: "No prize draws listed yet", getRowHref: (row) => String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => {
135
138
  e.stopPropagation();
136
139
  void dispatch({ type: "NAVIGATE", href: String(ROUTES.STORE.PRIZE_DRAWS_EDIT(row.id)) });
137
140
  }, "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) })) })] }), 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-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)] transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx("div", { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-[var(--appkit-color-primary)] py-2.5 text-sm font-semibold text-white hover:opacity-90 transition-opacity active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }));
@@ -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}`);
@@ -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
  }
@@ -9,23 +9,27 @@ 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);
23
- const [view, setView] = useState(table.get("view") || "grid");
27
+ const [view, setView] = useState(table.get(TABLE_KEYS.VIEW) || VIEW_MODE.GRID);
24
28
  const handleViewToggle = (next) => {
25
- if (next === "table")
29
+ if (next === VIEW_MODE.TABLE)
26
30
  return;
27
31
  setView(next);
28
- table.set("view", next);
32
+ table.set(TABLE_KEYS.VIEW, next);
29
33
  };
30
34
  // Pending filter state — buffered until "Apply Filters" clicked
31
35
  const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
@@ -69,24 +73,24 @@ export function StoresIndexListing({ initialData }) {
69
73
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
70
74
  }, []);
71
75
  const resetAll = useCallback(() => {
72
- const updates = { q: "", sort: "" };
76
+ const updates = { [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" };
73
77
  for (const k of FILTER_KEYS)
74
78
  updates[k] = "";
75
79
  table.setMany(updates);
76
80
  setSearchInput("");
77
81
  }, [table]);
78
82
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
79
- const hasActiveState = !!table.get("q") ||
80
- table.get("sort") !== "-createdAt" ||
83
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) ||
84
+ table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT ||
81
85
  activeFilterCount > 0;
82
86
  const commitSearch = useCallback(() => {
83
- table.set("q", searchInput.trim());
87
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
84
88
  }, [searchInput, table]);
85
89
  // Build sieve filters from applied URL params
86
- const ratingRaw = table.get("rating");
90
+ const ratingRaw = table.get(TABLE_KEYS.RATING);
87
91
  const minProductCount = table.get("minProductCount");
88
92
  const maxProductCount = table.get("maxProductCount");
89
- const featured = table.get("featured");
93
+ const featured = table.get(TABLE_KEYS.FEATURED);
90
94
  const filterParts = [];
91
95
  if (ratingRaw) {
92
96
  const ratings = ratingRaw.split("|").filter(Boolean);
@@ -100,15 +104,15 @@ export function StoresIndexListing({ initialData }) {
100
104
  if (featured === "true")
101
105
  filterParts.push("isFeatured==true");
102
106
  const { stores, totalPages, isLoading } = useStores({
103
- q: table.get("q") || undefined,
104
- page: table.getNumber("page", 1),
105
- pageSize: table.getNumber("pageSize", 24),
106
- sort: table.get("sort") || undefined,
107
- 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,
108
112
  filters: filterParts.length > 0 ? filterParts.join(",") : undefined,
109
113
  }, { initialData });
110
114
  const selection = useBulkSelection({ items: stores, keyExtractor: (s) => s.id ?? s.storeSlug });
111
- 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); }, 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: [
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: [
112
116
  {
113
117
  id: "compare",
114
118
  label: "Compare",
@@ -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?: {